Wednesday, March 12, 2014

Using CMP with BouncyCastle Java API

Introduction

A convenient method to enroll for certificates and do automatic certificate renewal, is to use the BouncyCastle API to implement a client that uses the CMP (RFC4210) protocol. In this example the implementation is done using java code and you need a CA that supports the CMP protocol on the server side.

Using the CMP protocol is a good way to integrate clients and RAs in a PKI, and BouncyCastle API is a great tool for this task. By the way, BouncyCastle needs your support to fund a FIPS certification. Help spread the word.

A short background on CMP

The open source CA EJBCA, implements many standard PKI protocols, CMP being one of them.

In the early days of CMP there were mainly commercial SDK implementations, and only a single open source instance (written in C). As always, that slowed down adoption. Nowadays, there is full support for CMP in the governing de-facto standard java crypto API BouncyCastle. No more excuses not to use CMP! :-)

Described more in detail in a previous blog post is CMP and its implementation in EJBCA.

Examples on how to use BouncyCastle CMP API

Here, I will show two different enrollment types (with source code) for the client implementation: 1) an RA using a shared secret authentication and 2) a client using certificate authentication.
These are by no means everything you can do. The RA can also use certificate authentication, or you can do nested messages with multiple layers of authentication. And many, many more! Only your imagination sets the limits :-).

All mentioned configurations can be active at the same time when using EJBCA 6. For complete documentation, see the Admin Guide at EJBCA.org.

Enrollment type 1) RA with shared secret authentication

Using an RA means the RA is configured to order certificates for its clients. The clients themselves will not be pre-registered by the CA, but will be added by the RA when it enrolls for the client.

To create a CMP enrollment message for an RA, using BouncyCastle:

CertificateRequestMessageBuilder msgbuilder = new CertificateRequestMessageBuilder(BigInteger.valueOf(certReqId));
X509NameEntryConverter dnconverter = new X509DefaultEntryConverter();
X500Name issuerDN = X500Name.getInstance(new X509Name("CN=AdminCA1").toASN1Object());
X500Name subjectDN = X500Name.getInstance(new X509Name("CN=user", dnconverter).toASN1Object());
msgbuilder.setIssuer(issuerDN);
msgbuilder.setSubject(subjectDN);
final byte[]                  bytes = keyPair.getPublic().getEncoded();
final ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
final ASN1InputStream         dIn = new ASN1InputStream(bIn);
final SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject());
msgbuilder.setPublicKey(keyInfo);
GeneralName sender = new GeneralName(subjectDN);
msgbuilder.setAuthInfoSender(sender);
// RAVerified POP
msgbuilder.setProofOfPossessionRaVerified();
CertificateRequestMessage msg = msgbuilder.build();
GeneralName recipient = new GeneralName(issuerDN);
ProtectedPKIMessageBuilder pbuilder = new ProtectedPKIMessageBuilder(sender, recipient);
pbuilder.setMessageTime(new Date());
// senderNonce
pbuilder.setSenderNonce(senderNonce);
// TransactionId
pbuilder.setTransactionID(transactionId);
// Key Id used (required) by the recipient to do a lot of stuff
pbuilder.setSenderKID("KeyId".getBytes());
org.bouncycastle.asn1.crmf.CertReqMessages msgs = new org.bouncycastle.asn1.crmf.CertReqMessages(msg.toASN1Structure());
org.bouncycastle.asn1.cmp.PKIBody pkibody = new org.bouncycastle.asn1.cmp.PKIBody(org.bouncycastle.asn1.cmp.PKIBody.TYPE_INIT_REQ, msgs);
pbuilder.setBody(pkibody);
JcePKMACValuesCalculator jcePkmacCalc = new JcePKMACValuesCalculator();
final AlgorithmIdentifier digAlg = new AlgorithmIdentifier("1.3.14.3.2.26"); // SHA1
final AlgorithmIdentifier macAlg = new AlgorithmIdentifier("1.2.840.113549.2.7"); // HMAC/SHA1
jcePkmacCalc.setup(digAlg, macAlg);
PKMACBuilder macbuilder = new PKMACBuilder(jcePkmacCalc);
MacCalculator macCalculator = macbuilder.build("password".toCharArray());
ProtectedPKIMessage message = pbuilder.build(macCalculator);
       
The above requires a CMP alias with approximately the following EJBCA configuration:
  • RA mode.
  • HMAC authentication module.
  • Specified secret 'password1'.
  • DN parts with CN as RA name generation scheme.

Enrollment type 2) Pre-registered client with certificate authentication

Using a client means that the client is already registered and present on the CA, able to authenticate itself with a certificate. The certificate can be generated by other means than the CA, or be imported into EJBCA.

To generate a signature protected CMP enrollment message using BouncyCastle:
       
CertificateRequestMessageBuilder msgbuilder = new CertificateRequestMessageBuilder(BigInteger.valueOf(certReqId));
X509NameEntryConverter dnconverter = new X509DefaultEntryConverter();
X500Name issuerDN = X500Name.getInstance(new X509Name("CN=AdminCA1").toASN1Object());
X500Name subjectDN = X500Name.getInstance(new X509Name("CN=user", dnconverter).toASN1Object());
msgbuilder.setIssuer(issuerDN);
msgbuilder.setSubject(subjectDN);
final byte[]                  bytes = keyPair.getPublic().getEncoded();
final ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
final ASN1InputStream         dIn = new ASN1InputStream(bIn);
final SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo((ASN1Sequence)dIn.readObject());
msgbuilder.setPublicKey(keyInfo);
GeneralName sender = new GeneralName(subjectDN);
msgbuilder.setAuthInfoSender(sender);
Control control = new RegTokenControl("foo123");
msgbuilder.addControl(control);
Provider prov = Security.getProvider("BC");
ContentSigner popsigner = new JcaContentSignerBuilder("SHA1withRSA").setProvider(prov).build(keyPair.getPrivate());
msgbuilder.setProofOfPossessionSigningKeySigner(popsigner);
CertificateRequestMessage msg = msgbuilder.build();
GeneralName recipient = new GeneralName(issuerDN);
ProtectedPKIMessageBuilder pbuilder = new ProtectedPKIMessageBuilder(sender, recipient);
pbuilder.setMessageTime(new Date());
// senderNonce
pbuilder.setSenderNonce(senderNonce);
// TransactionId
pbuilder.setTransactionID(transactionId);
org.bouncycastle.asn1.crmf.CertReqMessages msgs = new org.bouncycastle.asn1.crmf.CertReqMessages(msg.toASN1Structure());
org.bouncycastle.asn1.cmp.PKIBody pkibody = new org.bouncycastle.asn1.cmp.PKIBody(org.bouncycastle.asn1.cmp.PKIBody.TYPE_INIT_REQ, msgs);
pbuilder.setBody(pkibody);
ContentSigner msgsigner = new JcaContentSignerBuilder("SHA1withRSA").setProvider(prov).build(keyPair.getPrivate());
ProtectedPKIMessage message = pbuilder.build(msgsigner);
       

The above requires a CMP alias with approximately the following EJBCA configuration (use a new CMP alias so you can run this and the previous config in parallell):
  • Client mode.
  • EndEntityCertificate authentication module.
  • CN as extract username component.

About the author

Tomas Gustavsson, CTO of PrimeKey, founder of EJBCA
Contact me at tomas(at)primekey.se.
Follow me on Twitter.

2 comments:

Jaime Hablutzel said...

I think there is a typo in "de-factor"

tomas said...

Thanks, updated.