Sign CSR using Bouncy Castle

2019-01-05 09:39发布

I cannot find any code/doc describing how to sign a CSR using BC. As input I have a CSR as a byte array and would like to get the cert in PEM and/or DER format.

I have gotten this far

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)

I have trouble figuring out get from a certificate generator to store this in PEM or DER format.

Or am I going down the wrong path all together?

5条回答
beautiful°
2楼-- · 2019-01-05 09:41

In the end, this is what worked for me:

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);

Where code is

  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;
  }

And this can be converted to 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();
  }
查看更多
疯言疯语
3楼-- · 2019-01-05 09:50

The following code is based on the above answers but will compile and, given a PEM encoded CSR (of the kind exported by keytool), will return a valid PEM-encoded signedData object containing a signed Certificate chain (of the type that can be imported by keytool).

Oh and it's against 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");
}
查看更多
姐就是有狂的资本
4楼-- · 2019-01-05 10:01

Ok ... I was looking to do the same stuff and for the life of me I couldn't figure out how. The APIs all talk about generating the key pairs and then generating the cert but not how to sign a CSR. Somehow, quite by chance - here's what I found.

Since PKCS10 represents the format of the request (of the CSR), you first need to put your CSR into a PKCS10Holder. Then, you pass it to a CertificateBuilder (since CertificateGenerator is deprecated). The way you pass it is to call getSubject on the holder.

Here's the code (Java, please adapt as you need):

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;
}

As you can see, I've generated the request outside this method, but passed it in. Then, I have the PKCS10CertificationRequestHolder to accept this as a constructor arg.

Next, in the X509v3CertificateBuilder arguments, you'll see the pk10Holder.getSubject - this is apparently all you need? If something is missing, please let me know too!!! It worked for me. The cert I generated correctly had the DN info I needed.

Wikipedia has a killer section on PKCS - http://en.wikipedia.org/wiki/PKCS

查看更多
The star\"
5楼-- · 2019-01-05 10:06

@Mike B - have you tested your example thoroughly ? I get a strange behavior with your code: Im using bc15on version. When I sign the client request with a self signed CA I import it in IE and it shows the certificate as valid with the CA in the chainenter image description here

However you can see that when imported in FF the images on the right the CA in the chain is missing and ff cannot verify it to a Trusted Authority. Also with IE or FF when attempting to authenticate to the web server with it it fails as http cannot verify it to a trusted authority too.

Ive made some changes to your code just to suit my needs but in general it should be the same, can anyone give me some pointers onto what Im doing wrong here:

    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;
}

In the old version of my code where I used bouncy castle 1.4 we used the X509V3CertificateGenerator and just before returning the content we used to build the chain like so:

            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"));

UPDATE: OK Case solved. Thanks to this thread Obviously when using:

cacert.getSubjectX500Principal().getName()

I got the names of the issuer in reverse, which broke the chain, using instead:

cert.getSubjectX500Principal().getEncoded() solved it for me! So when your CA does not get verified upto trusted authority make sure you are getting the names correctly.

查看更多
再贱就再见
6楼-- · 2019-01-05 10:08

Archie thanks!

I made some changes to you code, see below.

The main changes are to pass the issuer's name and use the public key from the 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))
查看更多
登录 后发表回答