在 java 中以编程方式生成 CSR 时添加 SAN 名称

Add SAN names while generating a CSR in java Programmatically

我正在使用以下代码在 java 中生成 CSR:

package demo;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;

import sun.security.pkcs10.PKCS10;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.X500Name;

/**
 * This class generates PKCS10 certificate signing request
 *
 * @author Pankaj@JournalDev.com
 * @version 1.0
 */
public class GenerateCSR {
    private static PublicKey publicKey = null;
    private static PrivateKey privateKey = null;
    private static KeyPairGenerator keyGen = null;
    private static GenerateCSR gcsr = null;

    private GenerateCSR() {
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGen.initialize(2048, new SecureRandom());
        KeyPair keypair = keyGen.generateKeyPair();
        publicKey = keypair.getPublic();
        privateKey = keypair.getPrivate();
    }

    public static GenerateCSR getInstance() {
        if (gcsr == null)
            gcsr = new GenerateCSR();
        return gcsr;
    }

    public String getCSR(String cn) throws Exception {
        byte[] csr = generatePKCS10(cn, "Java", "JournalDev", "Cupertino", "California", "USA");
        return new String(csr);
    }

    /**
     *
     * @param CN Common Name, is X.509 speak for the name that distinguishes the Certificate best, and ties it to your
     *        Organization
     * @param OU Organizational unit
     * @param O Organization NAME
     * @param L Location
     * @param S State
     * @param C Country
     * @return
     * @throws Exception
     */
    private static byte[] generatePKCS10(String CN, String OU, String O, String L, String S, String C)
                    throws Exception {
        GeneralNames generalNames = new GeneralNames();
        generalNames.add(new GeneralName(new DerValue("b")));
        generalNames.add(new GeneralName(new DerValue("a")));

        CertificateExtensions ext = new CertificateExtensions();

        ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));

        // generate PKCS10 certificate request
        String sigAlg = "MD5WithRSA";
        PKCS10 pkcs10 = new PKCS10(publicKey);
        Signature signature = Signature.getInstance(sigAlg);
        signature.initSign(privateKey);
        // common, orgUnit, org, locality, state, country
        X500Name x500Name = new X500Name(CN, OU, O, L, S, C);
        pkcs10.encodeAndSign(x500Name, signature);
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(bs);
        pkcs10.print(ps);
        byte[] c = bs.toByteArray();
        try {
            if (ps != null)
                ps.close();
            if (bs != null)
                bs.close();
        } catch (Throwable th) {
        }
        return c;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public static void main(String[] args) throws Exception {
        GenerateCSR gcsr = GenerateCSR.getInstance();

        System.out.println("Public Key:\n" + gcsr.getPublicKey().toString());

        System.out.println("Private Key:\n" + gcsr.getPrivateKey().toString());
        String csr = gcsr.getCSR("journaldev.com <https://www.journaldev.com>");
        System.out.println("CSR Request Generated!!");
        System.out.println(csr);
    }

}

如您所见,我正在使用以下代码添加 SAN 名称

      GeneralNames generalNames = new GeneralNames();
        generalNames.add(new GeneralName(new DerValue("b")));
        generalNames.add(new GeneralName(new DerValue("a")));

        CertificateExtensions ext = new CertificateExtensions();

        ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));

我的问题是:

  1. 这是正确的方法吗?
  2. 如果是如何使用CertificateExtensions的对象以及传递到哪里。

我指的是 问题,那里提到我必须在证书的构造函数中传递它,但我不能让 X500Name 的构造函数只允许字符串值。

正如我在评论中所述,sun.* 类 不打算被其他程序员使用,我个人永远不会使用它们。 Bouncycastle 库可以做这一切,甚至更多。但是,如果您坚持使用这些 类,那么您将需要使用更多才能达到预期效果。注意:由于 类 没有记录在案,我这里的内容主要是实验结果。

请注意,MD5 不能用于签名,对于此类应用程序来说,它是完全不安全的。我已经用 SHA256 替换了它。

考虑一下我修改过的这段代码。名称本身只是示例:

// ...

import sun.security.pkcs10.PKCS10Attribute;
import sun.security.pkcs10.PKCS10Attributes;
import sun.security.x509.*;
import sun.security.pkcs10.PKCS10;
import sun.security.pkcs.PKCS9Attribute;

// ....

GeneralNames generalNames = new GeneralNames();
generalNames.add(new GeneralName(new DNSName("a.example.com")));
generalNames.add(new GeneralName(new DNSName("never.ever.example.com")));
generalNames.add(new GeneralName(new IPAddressName("192.168.1.250")));
CertificateExtensions ext = new CertificateExtensions();

ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));
var pkcs9Attr = new PKCS9Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, ext);
var pkcs10Attrs = new PKCS10Attributes(new PKCS10Attribute[] {
    new PKCS10Attribute(pkcs9Attr)
});

// generate PKCS10 certificate request
String sigAlg = "SHA256WithRSA";
PKCS10 pkcs10 = new PKCS10(publicKey, pkcs10Attrs);
Signature signature = Signature.getInstance(sigAlg);
signature.initSign(privateKey);
// common, orgUnit, org, locality, state, country
X500Name x500Name = new X500Name(CN, OU, O, L, S, C);
pkcs10.encodeAndSign(x500Name, signature);
ByteArrayOutputStream bs = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(bs);
pkcs10.print(ps);

这应该会生成一个带有正确 subjectAltName 扩展名的 PKCS10 证书请求。

如果有人想在以后引用它,请发布使用充气城堡的代码片段。它包括将 SAN 名称添加到证书中。

创建 CSR 还包括以下步骤:

  1. 生成一对非对称密钥(private+public),通常,您将私钥保存在服务器的安全位置。

  2. 创建一个证书,添加有关您的域名的信息(也可以使用通配符或 SAN 名称)、有关您的组织、城市、州等的信息(您可以通过以下代码执行此操作)或命令行工具,如 OpenSSL 或 java keytool)。

  3. 通常,您的 CSR 还将包含您的 public 密钥,并且由您的私钥签名。

  4. 生成 CSR 后,您会将其提交给受信任且知名的证书颁发机构进行签名,或者如果您在专用网络或开发环境中,您可以自行签名还有。

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.IPAddress;

public class BouncyCastle {

    public static void main(String[] args)
                    throws OperatorCreationException, NoSuchAlgorithmException, NoSuchProviderException, IOException {
        Security.addProvider(new BouncyCastleProvider());
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
        keyPairGenerator.initialize(4096);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(
                        new X500Principal(
                                        "CN=my.example.com, C=US, ST=Texas, L=Austin, O=Example ORG, OU=Engineering"),
                        keyPair.getPublic());
        List<String> extensions = new ArrayList<>();
        extensions.add("tx9918-otr01.my.example.com");
        extensions.add("tx9918-otr02.my.example.com");
        extensions.add("tx9918-otr03.my.example.com");
        extensions.add("tx9918-otr04.my.example.com");

        p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
                        createDomainAlternativeNamesExtensions(extensions));

        JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA");
        ContentSigner signer = csBuilder.build(keyPair.getPrivate());
        PKCS10CertificationRequest csr = p10Builder.build(signer);
        JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(new FileWriter(new File("D:\Titans\cert\test.csr")));
        jcaPEMWriter.writeObject(csr);
        jcaPEMWriter.close();
    }

    public static Extensions createDomainAlternativeNamesExtensions(List<String> domainAlternativeNames)
                    throws IOException {
        List<GeneralName> namesList = new ArrayList<>();
        if (domainAlternativeNames != null) {
            for (String alternativeName : domainAlternativeNames) {
                namesList.add(new GeneralName(
                                IPAddress.isValid(alternativeName) ? GeneralName.iPAddress : GeneralName.dNSName,
                                alternativeName));
            }
        }
        GeneralNames subjectAltNames = new GeneralNames(namesList.toArray(new GeneralName[] {}));
        ExtensionsGenerator extGen = new ExtensionsGenerator();
        extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
        return extGen.generate();
    }
}