Firme CSR usando Bouncy Castle

No puedo encontrar ningún código / documento que describa cómo firmar un CSR usando BC. Como entrada, tengo una CSR como matriz de bytes y me gustaría obtener el certificado en formato PEM y / o DER.

He llegado hasta aquí

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) 

Tengo problemas para averiguar cómo obtener un generador de certificados para almacenar esto en formato PEM o DER.

¿O voy por el camino equivocado todos juntos?

Ok … Estaba buscando hacer lo mismo y por mi vida no pude entender cómo. Las API hablan de generar los pares de claves y luego generar el certificado, pero no cómo firmar un CSR. De alguna manera, por casualidad, esto es lo que encontré.

Como PKCS10 representa el formato de la solicitud (de la CSR), primero debe colocar su CSR en un PKCS10Holder. Luego, lo pasa a un CertificateBuilder (ya que CertificateGenerator está en desuso). La forma de pasarlo es llamar a getSubject en el titular.

Aquí está el código (Java, por favor, adáptelo como lo necesite):

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

Como puede ver, he generado la solicitud fuera de este método, pero se lo pasé. Entonces, tengo el PKCS10CertificationRequestHolder para aceptar esto como un argumento constructor.

A continuación, en los argumentos X509v3CertificateBuilder, verá pk10Holder.getSubject: ¿esto es aparentemente todo lo que necesita? Si algo falta, ¡¡¡házmelo saber también !!! Funcionó para mí El cert que generé correctamente tenía la información de DN que necesitaba.

Wikipedia tiene una sección asesina sobre PKCS – http://en.wikipedia.org/wiki/PKCS

El siguiente código se basa en las respuestas anteriores pero se comstackrá y, dado un CSR codificado en PEM (del tipo exportado por keytool), devolverá un objeto signedData codificado en PEM válido que contiene una cadena de certificados firmada (del tipo que se puede importar) por keytool).

Ah, y está en contra de 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"); } 

Archie gracias!

Hice algunos cambios en tu código, mira a continuación.

Los cambios principales son pasar el nombre del emisor y usar la clave pública del 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)) 

Al final, esto es lo que funcionó para mí:

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

Dónde está el código

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

Y esto se puede convertir a 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(); } 

@Mike B: ¿has probado tu ejemplo a fondo? Obtuve un comportamiento extraño con tu código: estoy usando la versión bc15on. Cuando firmo la solicitud del cliente con una CA autofirmada, la importo en IE y muestra el certificado como válido con la CA en la cadena enter image description here

Sin embargo, puede ver que cuando se importan en FF las imágenes de la derecha, falta la CA en la cadena y ff no puede verificarla en una Autoridad de Confianza. También con IE o FF al intentar autenticarse en el servidor web, falla, ya que http no puede verificarlo ante una autoridad de confianza también.

He hecho algunos cambios en tu código para satisfacer mis necesidades, pero en general debería ser el mismo, ¿alguien puede darme algunos consejos sobre lo que estoy haciendo mal aquí?

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

En la versión anterior de mi código donde utilicé Bouncy Castle 1.4 usamos el X509V3CertificateGenerator y justo antes de devolver el contenido que utilizamos para construir la cadena, así:

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

ACTUALIZACIÓN : OK Caso resuelto. Gracias a este hilo Obviamente al usar:

cacert.getSubjectX500Principal (). getName ()

Obtuve los nombres del emisor a la inversa, lo que rompió la cadena, utilizando en su lugar:

cert.getSubjectX500Principal (). getEncoded () me lo resolvió! Entonces, cuando su CA no se verifique hasta la autoridad de confianza, asegúrese de obtener los nombres correctamente.