I have successfully used the X509v3CertificateBuilder Java class from Bouncy Castle to create X509 certificates with standard V3 extensions. I am now trying to create certificates with custom extensions.
I can create a custom extension using the addExtension(...) method, however, the resulting value in the certificate is not what I want. For example, I would like these exact octets listed in the certificate under a custom OID 1.2.3.4: "00 00 00 00 FF FF FF FF". Everything I try wraps that octet string in ASN1 encoding and it ends up as "04 08 00 00 00 00 FF FF FF FF".
Basically, I would like to create a certificate in Java with a custom extension that looks identical to how a certificate would look when created with OpenSSL using an extensions file that had this configuration:
1.2.3.4=DER:00:00:00:00:FF:FF:FF:FF
Is this possible to do in a clean way with the X509v3CertificateBuilder class?
Below is a snippet of code that creates the "incorrect" value.
// Raw value to place in cert for OID 1.2.3.4.
byte[] bytearray = {0, 0, 0, 0, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
ASN1ObjectIdentifier asn1oid = new ASN1ObjectIdentifier("1.2.3.4");
Extension ext = new Extension(asn1oid, false, bytearray);
X509v3CertificateBuilder certBldr =
new JcaX509v3CertificateBuilder(
caCert,
serial,
startDate,
endDate,
dn,
pubKey)
.addExtension(
new ASN1ObjectIdentifier("2.5.29.19"),
false,
new BasicConstraints(false))
.addExtension(
new ASN1ObjectIdentifier("2.5.29.15"),
true,
new X509KeyUsage(
X509KeyUsage.digitalSignature |
X509KeyUsage.nonRepudiation |
X509KeyUsage.keyEncipherment |
X509KeyUsage.dataEncipherment))
.addExtension(
new ASN1ObjectIdentifier("1.2.3.4"),
false,
ext.getExtnValue());
// Create and sign the certificate.
X509CertificateHolder certHolder = certBldr.build(sigGen);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
.getCertificate(certHolder);
After trying a lot of different options, I don't think it's possible to use X509v3CertificateBuilder to create an extension with a raw (non-ASN.1 encoded) value. The addExtension() method expects or changes an input value to be ASN.1 encoded.
However, after looking at the Bouncy Castle source code for the methods that X509v3CertificateBuilder uses behind the scenes, I found a way to do it with other classes. There are more lines of code involved, but it is fairly straightforward and gives the results needed.
Here is the code that will allow a custom extension with a raw value.
// Raw value to place in cert for OID 1.2.3.4.
byte[] bytearray = {0, 0, 0, 0, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
// Start creating the certificate beginning with the TBS certificate.
V3TBSCertificateGenerator tbsGen = new V3TBSCertificateGenerator();
tbsGen.setSerialNumber(new ASN1Integer(serialNum));
tbsGen.setIssuer(issuer);
tbsGen.setStartDate(new Time(new Date(startDate)));
tbsGen.setEndDate(new Time(new Date(endDate)));
tbsGen.setSubject(new X500Name(dn));
tbsGen.setSubjectPublicKeyInfo(SubjectPublicKeyInfo.getInstance(certPubKey.getEncoded()));
tbsGen.setSignature(sigGen.getAlgorithmIdentifier());
// The Key Usage extension:
X509KeyUsage keyuse = new X509KeyUsage(
X509KeyUsage.digitalSignature |
X509KeyUsage.nonRepudiation |
X509KeyUsage.keyEncipherment |
X509KeyUsage.dataEncipherment);
Extension keyUsageExt =
new Extension(
Extension.keyUsage,
true,
keyuse.getEncoded());
// The Basic Constraints extension:
BasicConstraints basic = new BasicConstraints(false);
Extension basicExt =
new Extension(
Extension.basicConstraints,
false,
basic.getEncoded());
// The Custom extension:
ASN1ObjectIdentifier asn1iod =
new ASN1ObjectIdentifier("1.2.3.4");
Extension customExt =
new Extension(
asn1iod,
false,
bytearray);
Extension[] extArray = {keyUsageExt, basicExt, customExt};
tbsGen.setExtensions(new Extensions(extArray));
// Create the TBS certificate.
TBSCertificate tbsCert = tbsGen.generateTBSCertificate();
// Sign the certificate.
OutputStream ostream = sigGen.getOutputStream();
DEROutputStream derOstream = new DEROutputStream(ostream);
derOstream.writeObject(tbsCert);
ostream.close();
byte[] tbsSig = sigGen.getSignature();
// Assemble the full X509 certificate. (TBS + Sig Alg + Sig)
ASN1EncodableVector asnVector = new ASN1EncodableVector();
asnVector.add(tbsCert);
asnVector.add(sigGen.getAlgorithmIdentifier());
asnVector.add(new DERBitString(tbsSig));
X509CertificateHolder certHolder =
new X509CertificateHolder(
org.bouncycastle.asn1.x509.Certificate.getInstance(new DERSequence(asnVector)));
X509Certificate cert =
new JcaX509CertificateConverter()
.setProvider(BC).getCertificate(certHolder);
Certificate is ASN.1 encoded, so extension values should also be ASN.1 encoded. 04 is OCTET STRING type, 08 - length of this octet string. BouncyCastle doesn't know anything about format of extension data, most likely this is the reason why it doesn't strip tag and length, and you should decoded that data manually.