使用Bouncy Castle签署CSR

时间:2011-08-29 12:52:32

标签: java scala bouncycastle

我找不到任何描述如何使用BC签署CSR的代码/文档。作为输入,我有一个CSR作为字节数组,并希望获得PEM和/或DER格式的证书。

我已经走到了这一步

def signCSR(csrData:Array[Byte], ca:CACertificate, caPassword:String) = {
  val csr = new PKCS10CertificationRequestHolder(csrData)
  val spi = csr.getSubjectPublicKeyInfo

  val ks = new java.security.spec.X509EncodedKeySpec(spi.getDEREncoded())
  val kf = java.security.KeyFactory.getInstance("RSA")
  val pk = kf.generatePublic(ks)

  val (caCert, caPriv) = parsePKCS12(ca.pkcs12data, caPassword)

  val fromDate : java.util.Date = new java.util.Date // FixMe
  val toDate = fromDate // FixMe
  val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
  val contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(caPriv)
  val serial = BigInt(CertSerialnumber.nextSerialNumber)
  val certgen = new JcaX509v3CertificateBuilder(new X500Name(issuer.getName), serial.bigInteger, fromDate, toDate, csr.getSubject, pk)

我无法确定从证书生成器获取以PEM或DER格式存储它。

还是我一起走错了路?

5 个答案:

答案 0 :(得分:33)

好的......我一直在寻找同样的东西,对于我的生活,我无法弄明白。 API都讨论生成密钥对,然后生成证书,而不是如何签署CSR。不知怎的,很偶然 - 这就是我发现的。

由于PKCS10代表(CSR)请求的格式,因此首先需要将CSR放入PKCS10Holder中。然后,将其传递给CertificateBuilder(因为不推荐使用CertificateGenerator)。传递它的方法是在持有者上调用getSubject。

这是代码(Java,请根据需要进行调整):

public static X509Certificate sign(PKCS10CertificationRequest inputCSR, PrivateKey caPrivate, KeyPair pair)
        throws InvalidKeyException, NoSuchAlgorithmException,
        NoSuchProviderException, SignatureException, IOException,
        OperatorCreationException, CertificateException {   

    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
            .find("SHA1withRSA");
    AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder()
            .find(sigAlgId);

    AsymmetricKeyParameter foo = PrivateKeyFactory.createKey(caPrivate
            .getEncoded());
    SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pair
            .getPublic().getEncoded());

    PKCS10CertificationRequestHolder pk10Holder = new PKCS10CertificationRequestHolder(inputCSR);
    //in newer version of BC such as 1.51, this is 
    //PKCS10CertificationRequest pk10Holder = new PKCS10CertificationRequest(inputCSR);

    X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(
            new X500Name("CN=issuer"), new BigInteger("1"), new Date(
                    System.currentTimeMillis()), new Date(
                    System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60
                            * 1000), pk10Holder.getSubject(), keyInfo);

    ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
            .build(foo);        

    X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
    X509CertificateStructure eeX509CertificateStructure = holder.toASN1Structure(); 
    //in newer version of BC such as 1.51, this is 
    //org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = holder.toASN1Structure(); 

    CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");

    // Read Certificate
    InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
    X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);
    is1.close();
    return theCert;
    //return null;
}

正如您所看到的,我已经在此方法之外生成了请求,但是将其传入。然后,我将PKCS10CertificationRequestHolder作为构造函数arg接受。

接下来,在X509v3CertificateBuilder参数中,您将看到pk10Holder.getSubject - 这显然是您所需要的一切?如果遗漏了什么,请告诉我!它对我有用。我正确生成的证书有我需要的DN信息。

维基百科有一个关于PKCS的杀手部分 - http://en.wikipedia.org/wiki/PKCS

答案 1 :(得分:7)

以下代码基于上述答案,但将进行编译,并且在给定PEM编码的CSR(由keytool导出的类型)的情况下,将返回包含签名证书链的有效PEM编码的signedData对象(类型为可以通过keytool导入。

哦,这是反对BouncyCastle 1.49。

import java.security.*;
import java.io.*;
import java.util.Date;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x500.*;
import org.bouncycastle.asn1.pkcs.*;
import org.bouncycastle.openssl.*;
import org.bouncycastle.pkcs.*;
import org.bouncycastle.cert.*;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.*;
import org.bouncycastle.crypto.util.*;
import org.bouncycastle.operator.*;
import org.bouncycastle.operator.bc.*;
import org.bouncycastle.operator.jcajce.*;
import org.bouncycastle.util.encoders.Base64;

/**
 * Given a Keystore containing a private key and certificate and a Reader containing a PEM-encoded
 * Certificiate Signing Request (CSR), sign the CSR with that private key and return the signed
 * certificate as a PEM-encoded PKCS#7 signedData object. The returned value can be written to a file
 * and imported into a Java KeyStore with "keytool -import -trustcacerts -alias subjectalias -file file.pem"
 *
 * @param pemcsr a Reader from which will be read a PEM-encoded CSR (begins "-----BEGIN NEW CERTIFICATE REQUEST-----")
 * @param validity the number of days to sign the Certificate for
 * @param keystore the KeyStore containing the CA signing key
 * @param alias the alias of the CA signing key in the KeyStore
 * @param password the password of the CA signing key in the KeyStore
 *
 * @return a String containing the PEM-encoded signed Certificate (begins "-----BEGIN PKCS #7 SIGNED DATA-----")
 */
public static String signCSR(Reader pemcsr, int validity, KeyStore keystore, String alias, char[] password) throws Exception {
    PrivateKey cakey = (PrivateKey)keystore.getKey(alias, password);
    X509Certificate cacert = (X509Certificate)keystore.getCertificate(alias);
    PEMReader reader = new PEMReader(pemcsr);
    PKCS10CertificationRequest csr = new PKCS10CertificationRequest((CertificationRequest)reader.readObject());

    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
    AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
    X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName());
    BigInteger serial = new BigInteger(32, new SecureRandom());
    Date from = new Date();
    Date to = new Date(System.currentTimeMillis() + (validity * 86400000L));

    X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
    certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
    certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
    certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(cacert.getSubjectX500Principal().getName()))), cacert.getSerialNumber()));

    ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(cakey.getEncoded()));
    X509CertificateHolder holder = certgen.build(signer);
    byte[] certencoded = holder.toASN1Structure().getEncoded();

    CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
    signer = new JcaContentSignerBuilder("SHA1withRSA").build(cakey);
    generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cacert));
    generator.addCertificate(new X509CertificateHolder(certencoded));
    generator.addCertificate(new X509CertificateHolder(cacert.getEncoded()));
    CMSTypedData content = new CMSProcessableByteArray(certencoded);
    CMSSignedData signeddata = generator.generate(content, true);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write("-----BEGIN PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
    out.write(Base64.encode(signeddata.getEncoded()));
    out.write("\n-----END PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
    out.close();
    return new String(out.toByteArray(), "ISO-8859-1");
}

答案 2 :(得分:4)

阿奇谢谢!

我对您的代码进行了一些更改,请参阅下文。

主要更改是传递发布者的名称并使用CSR中的公钥。

val caCert = PEMToCert(issuerPEM).get
val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
val csr = new PKCS10CertificationRequestHolder(csrData)
val serial = BigInt(CertSerialNumber.nextSerialNumber)
val spi = csr.getSubjectPublicKeyInfo();

val certgen = new X509v3CertificateBuilder(
    new X500Name(issuer.getName),
    serial.bigInteger,
    new java.util.Date(),
    new Date(System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60 * 1000),
    csr.getSubject,
    csr.getSubjectPublicKeyInfo())

certgen.addExtension(
    X509Extension.subjectKeyIdentifier,
    false,
    spi
)

val issuerPK = PEMToPK(issuerPKPEM, caPassword).get
val contentSigner = new JcaContentSignerBuilder(contentSignerAlg).setProvider(BC).build(issuerPK.getPrivate())
val x509 = (new JcaX509CertificateConverter).setProvider(BC).getCertificate(certgen.build(contentSigner))

答案 3 :(得分:2)

最后,这对我有用:

KeyPair serverKeyPair = keyPairLoader.getKeyPair(); //my own class
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate serverCertificate = getServerCertificate(certificateFactory);

org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = signCertificateSigningRequest(
  jcaPKCS10CertificationRequest, keyPair, serverCertificate);

java.security.cert.X509Certificate signedCertificate = readCertificateFromASN1Certificate(
  eeX509CertificateStructure, certificateFactory);

代码在哪里

  private org.spongycastle.asn1.x509.Certificate signCertificateSigningRequest(
    JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest,
    KeyPair keyPair, X509Certificate serverCertificate)
      throws IOException, OperatorCreationException, NoSuchAlgorithmException, InvalidKeyException
  {
    // Signing CSR
    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
      .find("SHA1withRSA");

    X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
        serverCertificate, 
        new BigInteger("1"), //serial
        new Date(System.currentTimeMillis()),
        new Date(System.currentTimeMillis() + 30L * 365L * 24L * 60L * 60L * 1000L),
        jcaPKCS10CertificationRequest.getSubject(),
        jcaPKCS10CertificationRequest.getPublicKey()
    /*).addExtension(
        new ASN1ObjectIdentifier("2.5.29.35"),
        false,
        new AuthorityKeyIdentifier(keyPair.getPublic().getEncoded())*/
    ).addExtension(
            new ASN1ObjectIdentifier("2.5.29.19"),
            false,
            new BasicConstraints(false) // true if it is allowed to sign other certs
    ).addExtension(
            new ASN1ObjectIdentifier("2.5.29.15"),
            true,
            new X509KeyUsage(
                X509KeyUsage.digitalSignature |
                    X509KeyUsage.nonRepudiation   |
                    X509KeyUsage.keyEncipherment  |
                    X509KeyUsage.dataEncipherment));

    AsymmetricKeyParameter asymmetricKeyParameter =
          PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
    //ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(asymmetricKeyParameter);
    ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").build(keyPair.getPrivate());


    X509CertificateHolder x509CertificateHolder = certificateBuilder.build(sigGen);
    org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure =
      x509CertificateHolder.toASN1Structure();
    return eeX509CertificateStructure;
  }

  private X509Certificate readCertificateFromASN1Certificate(
    org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure,
    CertificateFactory certificateFactory)
    throws IOException, CertificateException {
    // Read Certificate
    InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
    X509Certificate signedCertificate =
      (X509Certificate) certificateFactory.generateCertificate(is1);
    return signedCertificate;
  }

这可以转换为PEM:

  private String convertCertificateToPEM(X509Certificate signedCertificate) throws IOException {
    StringWriter signedCertificatePEMDataStringWriter = new StringWriter();
    JcaPEMWriter pemWriter = new JcaPEMWriter(signedCertificatePEMDataStringWriter);
    pemWriter.writeObject(signedCertificate);
    pemWriter.close();
    log.info("PEM data:");
    log.info("" + signedCertificatePEMDataStringWriter.toString());
    return signedCertificatePEMDataStringWriter.toString();
  }

答案 4 :(得分:1)

@Mike B - 你彻底测试了你的例子吗?我的代码有一个奇怪的行为: 我正在使用bc15on版本。当我使用自签名CA对客户端请求进行签名时,我将其导入IE,并将证书显示为对链中的CA enter image description here

有效

但是,您可以看到,在FF中导入右侧的图像时,链中的CA将丢失,并且ff无法将其验证到受信任的权限。当尝试使用IE或FF对Web服务器进行身份验证时,它也会失败,因为http也无法向受信任的机构验证它。

我已经对你的代码进行了一些更改,以满足我的需求,但总的来说它应该是相同的,任何人都可以给我一些关于我在这里做错的指示:

    public static String GenCert(long SerNum, int addYear, int addHours,
                             String reqText,
                             String reqName) throws Exception,
                                                    SQLException {

    String result = "";
    reqText = csr; // hard code base64 csr for testing purposes
    reqText =
        "-----BEGIN CERTIFICATE REQUEST-----\n" + reqText +
        "\n-----END CERTIFICATE REQUEST-----\n";

    try {


        String castr = ca + "\n"; // hard code base64 CA pub key for testing
        String strPriv = caPrivk + "\n"; // hard code base64 CA private key for testing


        byte[] encKey = castr.getBytes();
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate caCert =
            (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(encKey));

        PEMParser pr = new PEMParser(new StringReader(strPriv));
        Object obj = pr.readObject();
        JcaPEMKeyConverter converter =
            new JcaPEMKeyConverter().setProvider("BC");
        KeyPair kp;
        kp = converter.getKeyPair((PEMKeyPair)obj);

        PrivateKey privateKey = kp.getPrivate();

        // parse the request
        PEMParser pRd =
            new PEMParser(new InputStreamReader(new ByteArrayInputStream(reqText.getBytes())));
        PKCS10CertificationRequest csr =
            (PKCS10CertificationRequest)pRd.readObject();

        String strReq = csr.getSubject().toString();

        strReq = strReq.substring(strReq.indexOf("CN=") + 3).trim();
        if (strReq.indexOf(",") > 0)
            strReq = strReq.substring(0, strReq.indexOf(",")).trim();
        if (!strReq.equals(reqName)) {
            return "";
        }

        AlgorithmIdentifier sigAlgId =
            new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"); //SHA1withRSA
        AlgorithmIdentifier digAlgId =
            new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
        X500Name issuer =
            new X500Name(caCert.getSubjectX500Principal().getName());
        BigInteger serial = BigInteger.valueOf(SerNum);

        // The date object returns GMT format
        Date date = new Date(System.currentTimeMillis() - 180 * 1000);
        date.setHours(date.getHours() + addHours);
        Calendar cal = Calendar.getInstance();
        Date from = date;
        cal.setTime(date);
        cal.add(1, addYear);
        Date to = cal.getTime();

        SubjectPublicKeyInfo pkInfo = csr.getSubjectPublicKeyInfo();
        //SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded());
        RSAKeyParameters rsa =
            (RSAKeyParameters)PublicKeyFactory.createKey(pkInfo);
        RSAPublicKeySpec rsaSpec =
            new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey rsaPub = kf.generatePublic(rsaSpec);


        X509v3CertificateBuilder certgen =
            new X509v3CertificateBuilder(issuer, serial, from, to,
                                         csr.getSubject(),
                                         csr.getSubjectPublicKeyInfo());

        certgen.addExtension(X509Extension.basicConstraints, false,
                             new BasicConstraints(false));
        certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
                             new SubjectKeyIdentifier(pkInfo));
        //            certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
        //                                 new SubjectKeyIdentifierStructure(rsaPub)); // In old version done with much more extensive parsing
        certgen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                             new AuthorityKeyIdentifierStructure(caCert));
        //            certgen.addExtension(X509Extension.authorityKeyIdentifier, false,
        //                                 new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(caCert.getSubjectX500Principal().getName()))),
        //                                                            caCert.getSerialNumber()));

        // add certificate purposes
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.2"));
        vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.2"));
        vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.10.3.12"));
        vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.4"));


        DERSequence seq = new DERSequence(vector);
        certgen.addExtension(X509Extensions.ExtendedKeyUsage, false, seq);


        ContentSigner signer =
            new BcRSAContentSignerBuilder(sigAlgId,
                                          digAlgId).build(PrivateKeyFactory.createKey(privateKey.getEncoded()));
        X509CertificateHolder holder = certgen.build(signer);
        byte[] certencoded = holder.toASN1Structure().getEncoded();

        CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
        signer =
            new JcaContentSignerBuilder("SHA1withRSA").build(privateKey);
        generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer,
                                                                                                                                   caCert));
        generator.addCertificate(new X509CertificateHolder(certencoded));
        generator.addCertificate(new X509CertificateHolder(caCert.getEncoded()));
        CMSTypedData content = new CMSProcessableByteArray(certencoded);
        CMSSignedData signeddata = generator.generate(content, true);

        result = Base64Utils.base64Encode(signeddata.getEncoded());

    } catch (Exception e) {
        result = e.toString();
        getStackTrace(e);
    }
    return result;
}

在我使用bouncy castle 1.4的代码的旧版本中,我们使用了X509V3CertificateGenerator,并且在返回我们用于构建链的内容之前就是这样:

            X509Certificate newCert =
            certGen.generateX509Certificate(privateKey, "BC");
        //=============================
        List chain = new ArrayList();
        chain.add(newCert);
        //-------------------------------------------------
        //  create the CertPath with old BouncyCastle
        CertificateFactory fact =
            CertificateFactory.getInstance("X.509", "BC");
        CertPath path = fact.generateCertPath(chain);
        result = Base64Utils.base64Encode(path.getEncoded("PKCS7"));

更新:确定案例已解决。感谢this thread显然在使用时:

cacert.getSubjectX500Principal()。getName()

我反过来得到了发行人的名字,这打破了链条,改为:

cert.getSubjectX500Principal()。getEncoded()为我解决了!因此,当您的CA未经过验证时,请确保您正确获取名称。