bcgit / bc-java

Bouncy Castle Java Distribution (Mirror)
https://www.bouncycastle.org/java.html
MIT License
2.31k stars 1.14k forks source link

X509v3CertificateBuilder generates invalid ASN.1 RSA public key structure #1175

Closed jpstotz closed 2 years ago

jpstotz commented 2 years ago

I am using BouncyCastle 1.70 to generate and sign a server certificate with an RSA key. The problem is that the ASN.1 structure of the generated X509 certificate contains an invalid RSA public key.

The main problem is e.g. discussed here: https://github.com/zmap/zlint/issues/283:

The sequence containing the OID is generated by BouncyCastle this way:

     SEQUENCE (1 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption (PKCS #1)

But correct would be:

      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption (PKCS #1)
        NULL 

Most implementations don't care for the missing NULL value but some do. And according to the linked discussion the specification is clear that the NULL value has to be included.

Sample code used for generating RSA key and certificate:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keysize);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();

AlgorithmIdentifier rsaAlgId = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null);

ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGO).build(caPrivateKey);

org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPublicKey;
rsaPublicKey = new org.bouncycastle.asn1.pkcs.RSAPublicKey(pubKey.getModulus(), pubKey.getPublicExponent());
SubjectPublicKeyInfo subPubKeyInfo = new SubjectPublicKeyInfo(rsaAlgId, rsaPublicKey);

Calendar startDate = Calendar.getInstance();
Calendar endDate = Calendar.getInstance();
endDate.add(Calendar.YEAR, 5);

X500Name subjectX500 = new X500Name(RFC4519Style.INSTANCE, subject);
X500Name issuerX500 = new X500Name(RFC4519Style.INSTANCE, caCert.getSubjectDN().getName());

X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(issuerX500, BigInteger.valueOf(serial),
        startDate.getTime(), endDate.getTime(), subjectX500, subPubKeyInfo);
v3CertGen.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
v3CertGen.addExtension(Extension.keyUsage, false, new KeyUsage( KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment));
v3CertGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames("example.org"));

X509CertificateHolder certHolder = v3CertGen.build(sigGen);
byte[] certData = certHolder.getEncoded();
peterdettman commented 2 years ago

Your code is actually creating the AlgorithmIdentifier with null instead of DERNull.INSTANCE. That goes into the SubjectPublicKeyInfo and is preserved into the generated certificate.

If you would let BC generate the SubjectPublicKeyInfo it would be created correctly AFAICT. e.g:

SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded());
jpstotz commented 2 years ago

@peterdettman Thanks for pointing that out. It is pretty hard to use that classes as there is nearly no documentation (JavaDoc) and the classes itself do not seem to validate their arguments.

Using new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE); works.

SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()); also works and allows to delete a lot of other code as well.

Just for the records (in case somebody else needs to do the same) the updated code is:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keysize);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();

ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGO).build(caPrivateKey);
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded());

Calendar startDate = Calendar.getInstance();
Calendar endDate = Calendar.getInstance();
endDate.add(Calendar.YEAR, 5);

X500Name subjectX500 = new X500Name(RFC4519Style.INSTANCE, subject);
X500Name issuerX500 = new X500Name(RFC4519Style.INSTANCE, caCert.getSubjectDN().getName());

X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(issuerX500, BigInteger.valueOf(serial),
        startDate.getTime(), endDate.getTime(), subjectX500, subPubKeyInfo);
v3CertGen.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
v3CertGen.addExtension(Extension.keyUsage, false, new KeyUsage( KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment));
v3CertGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames("example.org"));

X509CertificateHolder certHolder = v3CertGen.build(sigGen);
byte[] certData = certHolder.getEncoded();