Constructing an X.509 Certificate Using ASN.1

Digital certificates (also called X.509 certificates) are defined using ASN.1 and encoded using Distinguished Encoding Rules (DER). A number of cryptography libraries (Bouncy Castle, NSS etc.) provide high-level APIs which can be used to create digital certificates. Behind the scenes, however, they are using ASN.1 structures. To gain a deeper understanding of X.509 certificates, I think it’s instructive to look at how to create them using ASN.1 by following the construct presented in RFC 5280 – Internet X.509 Public Key Infrastructure and Certificate Revocation List (CRL) Profile.

RFC 5280 describes the content of X.509 certificates and CRL. We are only going to consider X.509v3 certificates (refer to RFC 5280 for differences between X.509 certificate versions).

An X.509v3 certificate is defined as:

Certificate ::= SEQUENCE {
     tbsCertificate TBSCertificate,
     signatureAlgorithm AlgorithmIdentifier,
     signatureValue BIT STRING }

I am not going to explain ASN.1 syntax in detail, but the syntax above defines a Certificate, a SEQUENCE of tbsCertificate, signatureAlgorithm and a signatureValue, where tbsCertificate and signatureAlgorithm are complex structures (to be defined later) and signatureValue is a BIT STRING primitive structure.

Let’s construct each structure piece-by-piece. We start with TBSCertificate, where “TBS” stands for “To Be Signed”. The signature will be computed over this data structure and placed in the signatureValue field and .

The TBSCertificate structure is defined as:

 TBSCertificate ::= SEQUENCE {
     version [0] EXPLICIT Version DEFAULT v1,
     serialNumber CertificateSerialNumber,
     signature AlgorithmIdentifier,
     issuer Name,
     validity Validity,
     subject Name,
     subjectPublicKeyInfo SubjectPublicKeyInfo,
     issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
     subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
     extensions [3] EXPLICIT Extensions OPTIONAL }

As you can see TBSCertificate is composed of several complex structures.

To create the ASN.1 structures I will use Java and Bouncy Castle 1.48 (BC 1.48) library. The certificate will be self-signed and will contain a number of extensions. I don’t construct all the extensions defined in RFC 5280. Once you get the hang of constructing complex ASN.1 structures, creating the extensions I left out shouldn’t be too difficult.

The first item in TBSCertificate is Version. As I mentioned previously, we focus solely on X.509 v3 certificates. Version is “tagged” (tags helps in resolving ambiguities during decoding ASN.1) with “0” and is also marked EXPLICIT. We use DERTaggedObject, mark the first argument true which sets it as EXPLICIT and then assign it the tag 0. The third argument is encoded as DERInteger and is assigned the value 2, which indicates that this is an X.509v3 certificate.

//Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
DERTaggedObject Version = new DERTaggedObject(true, 0, new DERInteger(2));

The next item in TBSCertificate is CertificateSerialNumber. This is the serial number of the certificate. Each Certificate Authority (CA) is supposed to assign an unique serial number to every certificate it issues. The certificate serial number should be positive.

//CertificateSerialNumber  ::=  INTEGER
DERInteger CertificateSerialNumber = new DERInteger(BigInteger.valueOf(Math.abs(new Random().nextInt())));

The next item in TBSCertificate is AlgorithmIdentifier. This value indicates the algorithm used by the CA to sign the certificate. A signature involves both a hashing algorithm (SHA-1) and an asymmetric encryption algorithm (RSA). AlgorithmIdentifier is defined as an ASN.1 SEQUENCE. ASN.1 structures are composed of either simple types (INTEGER, BOOLEAN, OCTET STRING etc.) or structured types (SEQUENCE, SET, SEQUENCE OF, SET OF etc.) or a combination of both. The first object in AlgorithmIdentifier, algorithm, is an Object Identifier (OID) which is assigned the identifier for “SHA-1 With RSA Encryption” and the second object, parameters, is set to NULL (for this particular algorithm). In our code, the two ASN.1 objects are created separately and then wrapped in a ASN.1 SEQUENCE.

/*AlgorithmIdentifier  ::=  SEQUENCE  {
        algorithm OBJECT IDENTIFIER,
        parameters ANY DEFINED BY algorithm OPTIONAL  }*/

ASN1EncodableVector SignatureAlgorithmIdentifier_ASN = new ASN1EncodableVector();
SignatureAlgorithmIdentifier_ASN.add(new DERObjectIdentifier("1.2.840.113549.1.1.5"));
DERSequence SignatureAlgorithm = new DERSequence(SignatureAlgorithmIdentifier_ASN);

Next, we consider the issuer name. This is typically the distinguished name (DN) of the CA. Since this is a self-signed certificate it will be the same as the subject name (which is defined later).

The Name structure is more involved than the ones we have seen previously. Let’s break it down. As seen below, the Name structure is a RDNSequence structure. The RDNSequence, as the name indicates, is a SEQUENCE of RelativeDistinguishedNames. The RelativeDistinguishedNames structure is a SET of AttributeTypeAndValue, which in turn is a SEQUENCE composed of an AttributeType and AttributeValue. The AttributeType is an OID, while the AttributeValue is a DirectoryString (specifically, a CHOICE between TeletexString, PrintableString, UniversalString, UTF8String and BMPString). We choose to encode each component of the DN as a PrintableString. Ultimately, the DN will look like CN=SecureCA,OU=PKI,O=Cyberdyne,C=US. If you are confused by the syntax of the issuer name, look up Lightweight Directory Access Protocol (LDAP) naming conventions.

Don’t worry if this is confusing. It takes a while to sink in. When writing the code it’s easier to construct the innermost structure first (AttributeTypeAndValue) and build it up to the outermost structure (Name). Remember that Name is a SEQUENCE, so ultimately it will be an ASN.1 SEQUENCE.

/*Name ::= CHOICE { rdnSequence  RDNSequence }
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::=
     SET SIZE (1..MAX) OF AttributeTypeAndValue*/

/*AttributeTypeAndValue ::= SEQUENCE {
	type     AttributeType,
	value    AttributeValue }*/

ASN1EncodableVector countryNameATV_ASN = new ASN1EncodableVector();
countryNameATV_ASN.add(new DERObjectIdentifier(""));
countryNameATV_ASN.add(new DERPrintableString("US"));
DERSequence countryNameATV = new DERSequence(countryNameATV_ASN);

ASN1EncodableVector organizationNameATV_ASN = new ASN1EncodableVector();
organizationNameATV_ASN.add(new DERObjectIdentifier(""));
organizationNameATV_ASN.add(new DERPrintableString("Cyberdyne"));
DERSequence organizationNameATV = new DERSequence(organizationNameATV_ASN);

ASN1EncodableVector organizationalUnitNameATV_ASN = new ASN1EncodableVector();
organizationalUnitNameATV_ASN.add(new DERObjectIdentifier(""));
organizationalUnitNameATV_ASN.add(new DERPrintableString("PKI"));
DERSequence organizationalUnitNameATV = new DERSequence(organizationalUnitNameATV_ASN);

ASN1EncodableVector issuerCommonNameATV_ASN = new ASN1EncodableVector();
issuerCommonNameATV_ASN.add(new DERObjectIdentifier(""));
issuerCommonNameATV_ASN.add(new DERPrintableString("SecureCA"));
DERSequence issuerCommonNameATV = new DERSequence(issuerCommonNameATV_ASN);

//RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
DERSet countryName = new DERSet(countryNameATV);
DERSet organizationName = new DERSet(organizationNameATV);
DERSet organizationalUnitName = new DERSet(organizationalUnitNameATV);
DERSet issuerCommonName = new DERSet(issuerCommonNameATV);

ASN1EncodableVector IssuerRelativeDistinguishedName = new ASN1EncodableVector();

//RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
DERSequence IssuerName = new DERSequence(IssuerRelativeDistinguishedName);

The validity field defines the time frame in which the certificate is valid. We use Java’s Date class to create a three year validity period. Validity is composed of two Time objects, notBefore and notAfter. The names should be self-explanatory. The Time objects are created separately and then included in the Validity SEQUENCE object.

//Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime }
DERUTCTime notBefore = new DERUTCTime(new Date(System.currentTimeMillis()));
DERUTCTime notAfter = new DERUTCTime(new Date(System.currentTimeMillis() + (((1000L*60*60*24*30))*12)*3));
ASN1EncodableVector Time = new ASN1EncodableVector();

/*Validity ::= SEQUENCE {
     notBefore      Time,
     notAfter       Time } */

DERSequence Validity = new DERSequence(Time);

Next, we create the subject name. A certificate is basically a binding between the public key and its holder (also called subscriber in literature). The subject name, like the issuer name, is defined in the form of a DN and identifies the holder of the certificate. Usually, only the common name is different for the subject name. The relative DN (RDN) (i.e. OU=PKI,O=Cyberdyne,C=US) is the same as the issuer’s, so we reuse the code we wrote earlier. For our example, since it is a self-signed certificate, the common is also the same.

//SubjectName - only need to change the common name
ASN1EncodableVector subjCommonNameATV_ASN = new ASN1EncodableVector();
subjCommonNameATV_ASN.add(new DERObjectIdentifier(""));
subjCommonNameATV_ASN.add(new DERPrintableString("SecureCA"));
DERSequence subjectCommonNameATV = new DERSequence(subjCommonNameATV_ASN);

//RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
DERSet subjectCommonName = new DERSet(subjectCommonNameATV);
ASN1EncodableVector SubjectRelativeDistinguishedName = new ASN1EncodableVector();

//RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
DERSequence SubjectName = new DERSequence(SubjectRelativeDistinguishedName);

As I mentioned previously, a certificate is a binding between an identity and the public key. The public key in a X.509 certificate is contained in the SubjectPublicKeyInfo structure, which is an ASN.1 SEQUENCE composed of the algorithm identifier (RSA) and the public key encoded as a BIT STRING. We generate the key pair using the KeyPairGenerator class and save the public key by calling the getPublic() method. Since this is a key pair, the private key can also be obtained by calling getPrivate(). If you read the documentation for the Java Key interface, you will notice that if you call the getEncoded() method on PublicKey, it returns the key in the SubjectPublicKeyInfo form (as a byte array), which the documentation calls the “standard format”. So all you have to do is form it into an ASN.1 SEQUENCE by using the ASN1Sequence.getInstance() static method.

/*SubjectPublicKeyInfo  ::=  SEQUENCE  {
	algorithm            AlgorithmIdentifier,
	subjectPublicKey     BIT STRING  }*/

///Generate the 2048-bit RSA Public Key  - PublicKey returns SubjectPublicKeyInfo by default (X.509 format)
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(2048, random);
KeyPair keyPair = kpGen.generateKeyPair();
PublicKey RSAPubKey = keyPair.getPublic();

//Convert public key bytes (in SubjectPublicKeyInfo format) to ASN1Sequence
byte[] RSAPubKeyBytes = RSAPubKey.getEncoded();
ASN1Sequence SubjectPublicKeyInfo = ASN1Sequence.getInstance(RSAPubKeyBytes);

We are done with the major portion of the TBSCertificate structure as the next three ASN.1 structures are marked OPTIONAL. The issuerUniqueID and subjectUniqueID are not usually used so let’s focus on extensions. RFC 5280 defines a number of extensions but we only focus on the following: Subject Key Identifier, Authority Key Identifier, Key Usage, Extended Key Usage, Basic Constraints, Certificate Policies, Subject Alternative Names, Authority Information Access and CRL Distribution Points.

The first two extensions we consider are Subject Key Identifier and Authority Key Identifier. These extensions are helpful during certificate path construction. Both involve a keyIdentifier value which is a SHA-1 hash of the value of the BIT STRING subjectPublicKey (from the SubjectPublicKeyInfo structure). The subjectKeyIdentifier is the hash over the subject’s public key (i.e. the public key in the certificate). The authorityKeyIdentifier is the hash over the issuer’s public key. Since we are constructing a self-signed certificate, both values are the same. To calculate the key Identifier, we extract the public key value from SubjectPublicKeyInfo and save it as a BIT STRING. Then we create a SHA-1 digest and calculate the hash over this value. The last step is to construct the ASN.1 structure as defined in RFC 5280.

//Get the subjectPublicKey from SubjectPublicKeyInfo to calculate the keyIdentifier
DERBitString subjectPublicKey = (DERBitString)SubjectPublicKeyInfo.getObjectAt(1).toASN1Primitive();

//Calculate the keyIdentifier
byte[] pubKeyBitStringBytes = subjectPublicKey.getBytes();
Digest sha1 = new SHA1Digest();
byte[] pubKeydigestBytes = new byte[sha1.getDigestSize()];
DEROctetString keyIdentifier = new DEROctetString(pubKeydigestBytes);

//Subject Key Identifier
ASN1EncodableVector subjectKeyIdentifier_ASN = new ASN1EncodableVector();
subjectKeyIdentifier_ASN.add(new DERObjectIdentifier(""));
subjectKeyIdentifier_ASN.add(new DEROctetString(keyIdentifier));
DERSequence subjectKeyIdentifier = new DERSequence(subjectKeyIdentifier_ASN);

//Authority Key Identifier
DERTaggedObject aki = new DERTaggedObject(false,0,keyIdentifier);
ASN1EncodableVector akiVec = new ASN1EncodableVector();
DERSequence akiSeq = new DERSequence(akiVec);
ASN1EncodableVector authorityKeyIdentifier_ASN = new ASN1EncodableVector();
authorityKeyIdentifier_ASN.add(new DERObjectIdentifier(""));
authorityKeyIdentifier_ASN.add(new DEROctetString(akiSeq));
DERSequence authorityKeyIdentifier = new DERSequence(authorityKeyIdentifier_ASN);

Next, we consider the Key Usage extension. This extension defines the purpose of the key contained in the certificate. For example, in an email signing certificate you will likely see digitalSignature and nonRepudiation key usages, while in an email encryption certificate you will see keyEncipherment (RSA). For our example, we assert digitalSignature, keyCertSign and cRLSign. The ASN.1 structure is fairly simple. We define the different key usages from RFC 5280 and then form the BIT STRING by using bitwise OR.

final int  digitalSignature = (1 << 7);
final int  nonRepudiation   = (1 << 6);
final int  keyEncipherment  = (1 << 5);
final int  dataEncipherment = (1 << 4);
final int  keyAgreement     = (1 << 3);
final int  keyCertSign      = (1 << 2);
final int  cRLSign          = (1 << 1);
final int  encipherOnly     = (1 << 0);
final int  decipherOnly     = (1 << 15);

//Set digitalSignature, keyCertSign and cRLSign
DERBitString keyUsageBitString = new DERBitString(digitalSignature | keyCertSign | cRLSign);
ASN1EncodableVector keyUsage_ASN = new ASN1EncodableVector();
keyUsage_ASN.add(new DERObjectIdentifier(""));
keyUsage_ASN.add(new DEROctetString(keyUsageBitString));
DERSequence KeyUsage = new DERSequence(keyUsage_ASN);

Related to Key Usage is the Extended Key Usage (EKU) extension. This extension indicates one or more purposes (for specific applications) for which the public key in the certificate can be used. For example, in a SSL server certificate you will likely see the Server Authentication EKU and in a certificate used for code signing you will see a Code Signing EKU. The EKUs are identified by specific OIDs. For our example we assert the Server Authentication EKU and the Email Protection EKU.

//Extended Key Usage
DERObjectIdentifier  serverAuthEKU = new DERObjectIdentifier("");
DERObjectIdentifier  emailProtectionEKU = new DERObjectIdentifier("");
ASN1EncodableVector EKU_ASN = new ASN1EncodableVector();
DERSequence EKUSeq = new DERSequence(EKU_ASN);

ASN1EncodableVector ExtendedKeyUsage_ASN = new ASN1EncodableVector();
ExtendedKeyUsage_ASN.add(new DERObjectIdentifier(""));
ExtendedKeyUsage_ASN.add(new DEROctetString(EKUSeq));
DERSequence ExtendedKeyUsage = new DERSequence(ExtendedKeyUsage_ASN);

Next, we create the Basic Constraints extension. This extension is defined as a SEQUENCE composed of two objects, a BOOLEAN value specifying whether or not the certificate is a CA certificate and a path length constraint which gives the number of non self-issued intermediate certificates that can follow this certificate in a valid certificate path. In a CA certificate the BOOLEAN value is set to true, however, sometimes you may see this extension in an end-entity certificate set to false. For our example we assert that the certificate is a CA certificate and set the path length constraint to 0, indicating that the only certificate which may follow this certificate in a certificate path is an end-entity certificate. We also mark this extension as critical.

//Basic Constraints
DERBoolean isCA = DERBoolean.getInstance(ASN1Boolean.TRUE);
DERInteger pathLenConstraint = new DERInteger(0);
ASN1EncodableVector basicConstraintStructure_ASN = new ASN1EncodableVector();
DERSequence basicConstraintSeq = new DERSequence(basicConstraintStructure_ASN);

ASN1EncodableVector basicConstraintExtension = new ASN1EncodableVector();
basicConstraintExtension.add(new DERObjectIdentifier(""));
basicConstraintExtension.add(ASN1Boolean.TRUE); //Mark critical
basicConstraintExtension.add(new DEROctetString(basicConstraintSeq));
DERSequence BasicConstraints = new DERSequence(basicConstraintExtension);

The Certificate Policies extension indicates the policy under which this certificate was issued. The policy is usually defined in a separate certificate policies document. The extension is defined as a SEQUENCE of certificate policies where each policy is identified by an OID and may contain other information regarding the policy. For our example we specify two policies (using policy OIDs I found online).

//Certificate Policies
DERObjectIdentifier policyIdentifierOne = new DERObjectIdentifier("2.16.840.");
DERObjectIdentifier policyIdentifierTwo = new DERObjectIdentifier("2.16.840.");

ASN1EncodableVector policyInformationOne_ASN = new ASN1EncodableVector();
DERSequence policyInformationSeqOne = new DERSequence(policyInformationOne_ASN);

ASN1EncodableVector policyInformationTwo_ASN = new ASN1EncodableVector();
DERSequence policyInformationSeqTwo = new DERSequence(policyInformationTwo_ASN);

ASN1EncodableVector certificatePolicies_ASN = new ASN1EncodableVector();
DERSequence certificatePoliciesSeq = new DERSequence(certificatePolicies_ASN);

ASN1EncodableVector certificatePoliciesExtension = new ASN1EncodableVector();
certificatePoliciesExtension.add(new DERObjectIdentifier(""));
certificatePoliciesExtension.add(new DEROctetString(certificatePoliciesSeq));
DERSequence CertificatePolicies = new DERSequence(certificatePoliciesExtension);

The Subject Alternative Name extension specifies more information about the identity asserted in the subject name. For an email certificate this extension may specify an email address, while for server certificates this extension may specify a DNS name. We specify an email address (rfc822Name) and a subject name (directoryName).

//Subject Alternative Name
DERTaggedObject rfc822Name = new DERTaggedObject(false, 1, new DERIA5String(""));
DERTaggedObject directoryName = new DERTaggedObject(true, 4, SubjectName); //directoryName explicitly tagged
ASN1EncodableVector GeneralNamesVec = new ASN1EncodableVector();
DERSequence GeneralNamesSeq = new DERSequence(GeneralNamesVec);

ASN1EncodableVector subjectAltname_ASN = new ASN1EncodableVector();
subjectAltname_ASN.add(new DERObjectIdentifier(""));
subjectAltname_ASN.add(new DEROctetString(GeneralNamesSeq));
DERSequence SubjectAlternativeName = new DERSequence(subjectAltname_ASN);

The Authority Information Access (AIA) extension provides information on how to access CA information. It defines two access descriptors: caIssuers and Online Certificate Status Protocol (OCSP). The caIssuers access descriptor provides information (location of the CA certificates) about the CA which issued the certificate which contains the AIA extension. It is often used to aid certificate path construction. The OCSP access descriptor provides the OCSP responder location which can be used to query the revocation status of the certificate.

//Authority Information Access
DERTaggedObject caIssuers= new DERTaggedObject(false, 6, new DERIA5String(""));
DERTaggedObject ocspURL= new DERTaggedObject(false, 6, new DERIA5String(""));
ASN1EncodableVector caIssuers_ASN = new ASN1EncodableVector();
caIssuers_ASN.add(new DERObjectIdentifier(""));
DERSequence caIssuersSeq = new DERSequence(caIssuers_ASN);
ASN1EncodableVector ocsp_ASN = new ASN1EncodableVector();
ocsp_ASN.add(new DERObjectIdentifier(""));
DERSequence ocspSeq = new DERSequence(ocsp_ASN);

ASN1EncodableVector accessSyn_ASN = new ASN1EncodableVector();
DERSequence AIASyntaxSeq = new DERSequence(accessSyn_ASN);

ASN1EncodableVector AIA_ASN = new ASN1EncodableVector();
AIA_ASN.add(new DERObjectIdentifier(""));
AIA_ASN.add(new DEROctetString(AIASyntaxSeq));
DERSequence AuthorityInformationAccess = new DERSequence(AIA_ASN);

The last extension we consider is the CRL Distribution Points extension. As the name suggests, this extension provides information on how to access the CRL for the certificate. While OCSP is the preferred method of obtaining revocation information, it’s useful to provide this extension for applications which may not support OCSP. We can define multiple distribution points in this extension. For our example, we define two CRL distribution points.

//CRL Distribution Points
DERTaggedObject crlDPURL_One = new DERTaggedObject(false, 6, new DERIA5String(""));
ASN1EncodableVector crlDPURL_One_ASN = new ASN1EncodableVector();
DERSequence crlDPURL_OneSeq = new DERSequence(crlDPURL_One_ASN);

DERTaggedObject crlDPURL_Two = new DERTaggedObject(false, 6, new DERIA5String("ldap://;binary"));
ASN1EncodableVector crlDPURL_Two_ASN = new ASN1EncodableVector();
DERSequence crlDPURL_TwoSeq = new DERSequence(crlDPURL_Two_ASN);

DERTaggedObject DPName_One = new DERTaggedObject(false, 0, crlDPURL_OneSeq);
ASN1EncodableVector DPName_One_ASN = new ASN1EncodableVector();
DERSequence DPName_One_Seq = new DERSequence(DPName_One_ASN);

DERTaggedObject DPName_Two = new DERTaggedObject(false, 0, crlDPURL_TwoSeq);
ASN1EncodableVector DPName_Two_ASN = new ASN1EncodableVector();
DERSequence DPName_Two_Seq = new DERSequence(DPName_Two_ASN);

DERTaggedObject DPOne = new DERTaggedObject(false, 0, DPName_One_Seq);
ASN1EncodableVector DPOne_ASN = new ASN1EncodableVector();
DERSequence DistributionPointOne = new DERSequence(DPOne_ASN);

DERTaggedObject DPTwo = new DERTaggedObject(false, 0, DPName_Two_Seq);
ASN1EncodableVector DPTwo_ASN = new ASN1EncodableVector();
DERSequence DistributionPointTwo = new DERSequence(DPTwo_ASN);

ASN1EncodableVector CRLDistributionPoints_ASN = new ASN1EncodableVector();
DERSequence CRLDistributionPointsSeq = new DERSequence(CRLDistributionPoints_ASN);

ASN1EncodableVector CRLDP_ASN = new ASN1EncodableVector();
CRLDP_ASN.add(new DERObjectIdentifier(""));
CRLDP_ASN.add(new DEROctetString(CRLDistributionPointsSeq));
DERSequence CRLDistributionPoints = new DERSequence(CRLDP_ASN);

Now that we have constructed the extensions, we construct the ASN.1 SEQUENCE which will be included in the TBSCertificate structure.

//Create Extensions
ASN1EncodableVector Extensions_ASN = new ASN1EncodableVector();
DERSequence Extensions = new DERSequence(Extensions_ASN);

DERTaggedObject extensions = new DERTaggedObject(true, 3, Extensions);

The next step is to construct the final TBSCertificate structure

//TBSCertificate := SEQUENCE
ASN1EncodableVector TBSCertificate_ASN = new ASN1EncodableVector();

DERSequence TBSCertificate = new DERSequence(TBSCertificate_ASN);

Looking back at the Certificate structure, we still have two more objects we need to construct: signatureAlgorithm and signatureValue. We already constructed the signatureAlgorithm structure in TBSCertificate. The signatureValue structure contains the digital signature computed over the ASN.1 DER encoded tbsCertificate. A digital signature is an encryption operation over a hash value using a private key. Since this is a self-signed certificate, the private key is the private key of the subject. We get the private key from the key pair, use Java’s Signature class to construct the signature and encode the signature as a BIT STRING.

//Create the signature value
byte[] TBSCertificateBytes = TBSCertificate.getEncoded();
PrivateKey RSAPrivKey = keyPair.getPrivate();
Signature signer = Signature.getInstance("SHA1WithRSA","BC");
byte[] signature = signer.sign();
DERBitString signatureValue = new DERBitString(signature);

Finally we construct the actual certificate.

//Create the certificate structure
ASN1EncodableVector cert_ASN = new ASN1EncodableVector();
DERSequence Certificate = new DERSequence(cert_ASN);

To view the certificate we created I used Apache Common’s IO library to output the certificate as a file.

FileUtils.writeByteArrayToFile(new File("cert.crt"), Certificate.getEncoded());

As you can see from the image below, Windows parses the certificate and displays the extensions and other components. Normally you would never create an X.509 certificate by individually constructing the ASN.1 structures, but instead use higher level APIs provided by your cryptography library. In a future post I will create this same certificate using a higher level API with fewer lines of code.

Certificate Details


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s