The Bouncy Castle Cryptography Java APIs are an excellent set of APIs that act as a provider for JCE and JCA. Additionally, they take care of the mundane and tedious (some would say overly complicated) details involved in reading and creating the data structures associated with the X.500 and PKCS standards. (The APIs are also available in C#, for .NET developers out there)
One thing they handle well is the concept of certificate extensions. X.509 v3 certificates introduced the concept of these extensions, which are basically additional (potentially optional) fields containing information not contained in the older original X.509 specifications. Each extension is specified by an OID (Object Identifier); a good list of these extensions is available.
While it’s easy to read these extensions from an existing X.509 v3 certificate using the Bouncy Castle APIs it is a bit more involved to read these extensions from a Certificate Signing Request, or CSR; this is the data structure that is sent to a CA to request a certificate. The CA then reads the data from this and creates a signed certificate issued to the requester. In this guide I’ll present a brief way to extract X.509 extensions request from a CSR so that they may be included in the resulting issued certificate.
Code: The good stuff
Assuming you have added the Bouncy Castle JARs to your classpath, you should have access to the classes used here.
You must first have the CSR in the format of a Bouncy Castle data object, namely the PKCS10CertificationRequest
. If all you have is the PEM-format of the CSR (i.e. Base64-encoded contents delimited by headers like ----- BEGIN CERTIFICATE REQUEST -----
and ----- END CERTIFICATE REQUEST -----
) then you will need to convert this to the proper data structure using something like
PEMUtil from Commons-SSL like I have done below. (BC has a PEMUtil class as well, but it appears to be only for internal use)
// NOTE: Commons-SSL doesn't support generics.
final List pemItems = PEMUtil.decode( csrContent.getBytes() );
// Verify list isn't empty - uses Apache Commons Lang.
Validate.isTrue( !pemItems.isEmpty() );
// No support for generics, so have to cast. (Could have cast the entire List)
final PEMItem csrPemFormat = (PEMItem) pemItems.get( 0 );
// Verify the type.
Validate.isTrue( csrPemFormat.pemType.equals( "CERTIFICATE REQUEST" ),
"This is not a CSR" );
final PKCS10CertificationRequest csr = new PKCS10CertificationRequest(
csrPemFormat.getDerBytes() );
We first decode the PEM (Base64) CSR into List
of PEMItem
s. Note that Commons-SSL doesn’t support generics, so you are going to get a cast warning somewhere in the code, no matter what. When calling getBytes()
on the CSR string, you may want to specify the US-ASCII
character set, since the no-arg method uses the platform default character set, which might give inconsistent results across different systems when converting from characters to bytes.
We then grab the first entry in the list, checking if it is a CSR. We can now convert this into the proper data structure by supplying the raw bytes (i.e. the DER-encoded format) to the constructor of PKCS10CertificationRequest
.
The method to extract the X509Extensions
structure from the PKCS10CertificationRequest
is shown below.
/**
* Gets the X509 Extensions contained in a CSR (Certificate Signing Request).
*
* @param certificateSigningRequest the CSR.
* @return the X509 Extensions in the request.
* @throws CertificateException if the extensions could not be found.
*/
X509Extensions getX509ExtensionsFromCsr(
final PKCS10CertificationRequest certificateSigningRequest ) throws CertificateException
{
final CertificationRequestInfo certificationRequestInfo = certificateSigningRequest
.getCertificationRequestInfo();
final ASN1Set attributesAsn1Set = certificationRequestInfo.getAttributes();
// The `Extension Request` attribute is contained within an ASN.1 Set,
// usually as the first element.
X509Extensions certificateRequestExtensions = null;
for (int i = 0; i < attributesAsn1Set.size(); ++i)
{
// There should be only only one attribute in the set. (that is, only
// the `Extension Request`, but loop through to find it properly)
final DEREncodable derEncodable = attributesAsn1Set.getObjectAt( i );
if (derEncodable instanceof DERSequence)
{
final Attribute attribute = new Attribute( (DERSequence) attributesAsn1Set
.getObjectAt( i ) );
if (attribute.getAttrType().equals( PKCSObjectIdentifiers.pkcs_9_at_extensionRequest ))
{
// The `Extension Request` attribute is present.
final ASN1Set attributeValues = attribute.getAttrValues();
// The X509Extensions are contained as a value of the ASN.1 Set.
// Assume that it is the first value of the set.
if (attributeValues.size() >= 1)
{
certificateRequestExtensions = new X509Extensions( (ASN1Sequence) attributeValues
.getObjectAt( 0 ) );
// No need to search any more.
break;
}
}
}
}
if (null == certificateRequestExtensions)
{
throw new CertificateException( "Could not obtain X509 Extensions from the CSR" );
}
return certificateRequestExtensions;
}
Basically, we get the certificate request info from the CSR structure and then extract attributes from it. Then, we loop through to find the attribute with the “Extension Request” OID.
After that, I make an assumption that the actual extensions are contained in the first value of the place of the ASN.1 Set that makes up the “Extensions Request” structure – not a big assumption, and in my testing I haven’t encountered a situation where this wasn’t the case. It’s worthwhile to keep in mind that ASN.1 often prescribes Set or multi-value structures in places where the underlying data can only be single-valued.
After running through that code, we’ll have either found the extensions, and be returning them in a X509Extensions
structure, or an exception will be thrown. You could modify the code to return null
if that suits your style or purpose better.
A few more notes
Once you have the X509Extensions
structure you can use the extensions contained within to create/issue a certificate with them. Check out the Bouncy Castle Guide on Certificate Generation for more details.
Note that a CA is not required to use any of the extension requests present in a CSR – hence the name “requests”. It is entirely up to the CA to decide what extensions are appropriate, along with their values, for the certificates that it issues.
Code Review
The code is a little complicated and could probably benefit from some refactoring. However, a lot of the complexity derives from the fact that the X.509 and associated standards are quite complex themselves. This is a reflection on the vision that the designers of X.509 had for the future of the standard. However, the complexity of X.509 is another topic for another article.
I hope you found this article useful, as while I found lots of information for generating CSRs, information on parsing and working with them was a little sparse. Please feel free to leave your comments below!
Hello do you have any C# sample for decoding CSR?
Thanks a lot.
@hemant
Unfortunately, I don’t really have any practical C# experience so I wouldn’t be able to provide a straight code example. However, the Bouncy Castle libraries are also available in C#, so the same basic procedure outlined here should work in C# using the available BC libraries for .NET.
Thanks man This example really help me.
Thanks you are life saver!!!!!
The C# version below… Cant believe how badly documented the BouncyCastle libraries are…
X509Extensions GetX509ExtensionsFromCsr( Pkcs10CertificationRequest certificateSigningRequest )
{
CertificationRequestInfo certificationRequestInfo = certificateSigningRequest.GetCertificationRequestInfo();
Asn1Set attributesAsn1Set = certificationRequestInfo.Attributes;
// The `Extension Request` attribute is contained within an ASN.1 Set,
// usually as the first element.
X509Extensions certificateRequestExtensions = null;
for (int i = 0; i = 1)
{
KeyValuePair pair = MakeX509Extensions((Asn1Sequence)attributeValues[0]);
certificateRequestExtensions = new X509Extensions(pair.Key, pair.Value);
// No need to search any more.
break;
}
}
}
else if (derEncodable is Org.BouncyCastle.Asn1.Cms.Attribute)
{
Org.BouncyCastle.Asn1.Cms.Attribute attribute = (Org.BouncyCastle.Asn1.Cms.Attribute)derEncodable;
if (attribute.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest))
{
// The `Extension Request` attribute is present.
Asn1Set attributeValues = attribute.AttrValues;
// The X509Extensions are contained as a value of the ASN.1 Set.
// Assume that it is the first value of the set.
if (attributeValues.Count >= 1)
{
certificateRequestExtensions = (X509Extensions)attributeValues[0];
// No need to search any more.
break;
}
}
}
}
if (null == certificateRequestExtensions)
{
throw new Exception("Could not obtain X509 Extensions from the CSR");
}
return certificateRequestExtensions;
}
No need for the 3rd party PEM tool, just use Org.BouncyCastle.OpenSsl.PemReader I’m not sure about the Java version, but the .Net takes in a TextReader so you can just reference the file and cast the returned object
Org.BouncyCastle.OpenSsl.PemReader pemGuy = new Org.BouncyCastle.OpenSsl.PemReader(tr);
object pemBack = pemGuy.ReadObject();
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest csr = (Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest)pemBack;
This was really helpfull
Do you have the full code for this ?