Processing X.509v3 Certificates and CRLs

This section contains code samples illustrating the use of the cdk::Cert class to process X.509v3 certificates. The following examples are provided:

Note:
The certificates and CRLs in all of the examples below are represented as base64-encoded octet strings so that they could be included as string literals in the source code. If you are loading a certificate and/or CRL from a file and are unsure whether the data is base64-encoded or not, you should test for the encoding wrapper and, if one is present, remove it before attempting to load the data.

To decode a possibly base64-encoded PDU stored in a cdk::str object x, use a statement of the form:
    if(x[0]!=0x30) x = x.tobin64()
as shown below. (Note that the predicate (x[0]!=0x30) is true when the PDU begins with the character 'M' as it does when it is base64-encoded, whereas all binary ASN.1 encoded PDUs under consideration here start with the tag '\x30' for an ASN.1 BER- or DER-encoded SEQUENCE.)

Parsing Certificates

To parse an X.509v3 certificate, you must first load it into a cdk::Cert object as illustrated in the first example below. Subsequent examples show how to process various certificate components.

Loading a Certificate

In this sample a base64-encoded certificate is decoded and then loaded into a cdk::Cert object.

// put a sample base64-encoded X.509 certificate into a CDK string object
cdk::str strCertificate = 
  "MIICJzCCAZCgAwIBAgIBGDANBgkqhkiG9w0BAQUFADBAMQswCQYDVQQGEwJVUzEM"
  "MAoGA1UEChMDSVNDMSMwIQYDVQQDExpJU0MgQ0RLIFNhbXBsZSBDZXJ0aWZpY2F0"
  "ZTAeFw0wMzA3MTcwMDAwMDBaFw0wNDA3MTcwMDAwMDBaMEAxCzAJBgNVBAYTAlVT"
  "MQwwCgYDVQQKEwNJU0MxIzAhBgNVBAMTGklTQyBDREsgU2FtcGxlIENlcnRpZmlj"
  "YXRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9GQTkukn+153rATR8dh2H"
  "m8ixF7f7Y7bI0VFJnJAQCKqta4/IhFwQIK5F2Gn8j9tITBiXCF7F6XSvaF8bivN1"
  "0zR0pvI11NflEm2kwh7Yw0jZJB17Y3FHg183qYegmm/UwqX5zKUa4xw+cE8XSEqU"
  "uwjg0roBMGhAMzFEihHzLwIDAQABozEwLzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB"
  "/wQEAwIAYDAPBgNVHQ4ECHJzYS0xMDI0MA0GCSqGSIb3DQEBBQUAA4GBALWGxxo5"
  "5ScpLfECnqEUixFwrzftQGD2ISda7EWp/d7k23fOXgHC7Za18OpvlBUZ3sC2Fg4f"
  "infRHd2J4mXONk5OEdjhJILd58GErcCECg4J2uJPz77/zk+giiXldQEPtG+YOaAb"
  "ZC2SFbdfyYDKiSPhgzdy0/b4cElf4+VzegRM";

// decode the certificate if it's base64-encoded
if (strCertificate[0] != 0x30) strCertificate = strCertificate.tobin64();

// instantiate a Cert object and load the certificate string
cdk::Cert c1;
c1.load(strCertificate);     // non-zero return value indicates error

Obtaining Keys, Algorithm IDs, and Validity Periods

Once a certificate has been loaded into a cdk::Cert object its component parts can be extracted. Here the subject public key, algorithm ID, and validity period dates are accessed and pretty-printed:

// assume certificate has been loaded into c1 as above
printf("Subject public key: %s\n", c1.subject_pub.tohex().c_str());
printf("Subject algorithm ID: %s\n", c1.subject_oid.tohex().c_str());
printf("notBefore: %f\n", c1.notBefore);
printf("notAfter: %f\n", c1.notAfter);

Note:
The main reason to access the subject_oid and subject_pub components of a cdk::Cert object is to load them into a cdk::Key object for key wrapping or signature validation (see Processing ASN.1 Encoded Keys).

Processing Certificate Extensions

Certificate extensions can be processed serially by calling the getext() method as many times as necessary:

// assume certificate has been loaded into c1 as in first example above
for (int j = 0; ; j++)
{
  cdk::str oid, value;
  int i = c1.getext(j, oid, value);
  if ( (i != 0) && (i != CDK_EXTENSION_CRITICAL) )
  {
    break;     // no more extensions; we're done
  }
  if (i == CDK_EXTENSION_CRITICAL)
  {
    printf("(critical) ");
  }
  printf("Extension OID: %s, Value: %s\n", oid.tohex().c_str(), value.tohex().c_str());
}

Note:
ISC can make available additional code for performing ASN.1 encoding and decoding operations. If you would like this code for processing certificate extensions, please contact us.

Processing Distinguished Names

You can use cdk::parsedname() to convert distinguished names into human readable (ASCII) text, as shown here:

// assume certificate has been loaded into c1 as in first example above
cdk::str strSubjectDN, strIssuerDN;           // output strings

// convert subject and issuer DNs to ASCII text and print them
cdk::parsedname(c1.subject, strSubjectDN, 2); // see function reference for sort order info
cdk::parsedname(c1.issuer, strIssuerDN, 2);

printf("SubjectDN: %s\n", strSubjectDN.c_str());
printf("IssuerDN: %s\n", strIssuerDN.c_str());


Creating A Certificate

The following code shows how to generate an RSA key pair (see RSA Key Generation) and turn its public key into a self-signed certificate.

// generate a 1024-bit RSA key pair based on a 40-byte random seed
cdk::Key k;                              // instantiate a new Key object
k.RSAkeygen(cdk::getrand2(40), 1024);    // non-zero return value indicates error

// build the new tbsCertificate (i.e., the certificate body)
cdk::Cert c;                             // instantiate a new Cert object
c.version = V3;                          // X.509 version 3
c.serial = cdk::num(HASH_SHA(c.subject_pub, 1)); 
                                         // one way to produce a unique serial number
c.issuer_oid = k.asn1parameters(0,1);    // issuer signature algorithm ID
c.issuer = c.subject;                    // issuer DN
c.notBefore = time(NULL);                // validity period
c.notAfter = time(NULL) + 31536000;      // (one year)

cdk::DName dn;                           // create subject DN
dn.cname = "John Doe";
dn.org = "XYZ Corp.";
dn.country = "US"; 
c.subject = dn.toasn1();

c.subject_oid = k.asn1parameters(1,0);   // subject key algorithm ID (RSA)
c.subject_pub = k.asn1public();          // subject public key
cdk::str body = c.makebody();            // ASN.1 encode the body of the certificate

// sign the tbsCertificate to produce a self-signed certificate
cdk::str cert;                           // output buffer for the certificate
cdk::num r;                              // random seed required for DSA/ECDSA, ignored for RSA
if (k.isRSA())
    k.SetPadding(cdk::RSA::pkcs1);       // specify padding mechanism for RSA, or
else
{
    PRNG rand;                           // supply random seed if non-deterministic
    r = (num) rand.gens(20);
}
k.asn1sign(body,r,cert);                 // sign the certificate; non-zero indicates error 

See RFC 3280 for details.


Creating A CRL

Creating a CRL is similar to creating a certificate:

Cert caCert;                       // assume caCert and caKey have already been initalized
Key caKey;
k.hashtype = hSHA1;                // specify desired hash function

//  build the new tbsCertList (i.e., the CRL body)
cdk::CRL c;                        // instantiate a new CRL object
c.oid = caCert.subject_oid;        // one could also use k.asn1parameters(0,1);
c.issuer = caCert.subject;
c.thisUpdate = cdk::timegmt();
c.nextUpdate = cdk::timegmt() + 365.25 * 24 * 60 * 60;

// create list of revoked certificates and reason codes (see below)
cdk::str crllist = "";
crllist += mkCRLEntry("0x0000001", c.thisUpdate, CRL::certificateHold, CRL::reject);
crllist += mkCRLEntry("0x0000002", c.thisUpdate, CRL::cessationOfOperation);
crllist += mkCRLEntry("0x0000003", c.thisUpdate);
c.list = crllist;

// gather together desired extensions (see below)
cdk::str crlexts = "";
crlexts += asn1::mkAuthKeyIDExt(caCert);  // required for conformance with RFC 3280
crlexts += asn1::mkCRLNumberExt(2163);
c.extra = crlexts;

cdk::str body = c.makebody();      // put it all together as a tbsCertList

// sign the body to produce the CRL
cdk::str crl;                      // output buffer for the CRL
cdk::num r;                        // random seed required for DSA/ECDSA, ignored for RSA
if (k.isRSA())
  k.SetPadding(cdk::RSA::pkcs1); // specify padding mechanism for RSA, or
else
{
  PRNG rand;                     // supply random seed if non-deterministic
  r = (num) rand.gens(20);
}
k.asn1sign(body,r,crl);            // sign the CRL; non-zero indicates error

The following auxiliary function can be used to create the individual entries in the certificate revocation list (i.e., the items in the sequence of revokedCertificates):

cdk::str mkCRLEntry(const cdk::str &serialno, cdk::TimeT revdate, CRL::Reasons reason, 
  CRL::Instructions instruction)
{
  str x,y = "";
  cdk::TimeT t = revdate; // cdkdatetounix(revdate);
  num serial(hex(serialno));
  x = asn::integer(serial) + asn::date(t);
  if ( reason == 6 && instruction > 0 )   
  {
    cdk::str strIns = "";
    switch (instruction)
    {
    case CRL::callissuer:
      strIns = Obj*CRL_hold_callissuer;
      break;
    case CRL::reject:
      strIns = Obj*CRL_hold_reject;
      break;
    case CRL::pickuptoken:
      strIns = Obj*CRL_hold_pickuptoken;
      break;
    case none:
    default:
      strIns = Obj*CRL_hold_none;
      break;
    }
    y = Seq*(Obj*id_ce_holdInstructionCode+Oct*(strIns));
  }
  if ( reason >= 0 ) x += Seq& Seq*(Obj*id_ce_cRLReasons+Oct*(x0A*single(reason))) + y;
  return Seq*x;
}

while the following functions can be used to create the extensions:

cdk::str mkAuthKeyIDExt(const cdk::Cert &cert)
{
  // compute SHA-1 hash of subject_pub
  cdk::str subPub = cert.subject_pub;
  cdk::str hash = HASH(SHA,strSubPub, 1);
  
  cdk::str oid;
  oid = Obj*id_ce_authorityKeyIdentifier;
  return Seq*(oid + Oct*(Seq*(x80*hash + xA1*(xA4*(Seq*(cert.subject))) 
    + x82*bigend(cert.serial))));
}

cdk::str mkCRLNumberExt(int crlNumber)
{
  cdk::str oid = Obj*id_ce_cRLNumber; 
  return Seq*(oid + Oct*(asn::integer(crlNumber)));
}

See RFC 3280 for details.


Certificate and CRL Checking

The following code shows how to check the validity of a certificate pair (i.e., one link in a certificate path).

cdk::str issuerCert =            // the purported issuer certificate
  "MIICwjCCAiugAwIBAgIBATANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJVUzEL"
  "MAkGA1UECBMCSUwxETAPBgNVBAcTCE9hayBQYXJrMQwwCgYDVQQKEwNJU0MxEDAO"
  "BgNVBAsTB0RlbW8gQ0ExHDAaBgNVBAMTE0NlcnRpZmljYXRlIE1hbmFnZXIwHhcN"
  "MDMwNjI0MDUwMDAwWhcNMDUwNjI0MDUwMDAwWjBrMQswCQYDVQQGEwJVUzELMAkG"
  "A1UECBMCSUwxETAPBgNVBAcTCE9hayBQYXJrMQwwCgYDVQQKEwNJU0MxEDAOBgNV"
  "BAsTB0RlbW8gQ0ExHDAaBgNVBAMTE0NlcnRpZmljYXRlIE1hbmFnZXIwgZ8wDQYJ"
  "KoZIhvcNAQEBBQADgY0AMIGJAoGBAMPYg8Up8s3CAJICbNyW/CTxbj4yzaA8ZbcX"
  "2t969MysICe0EI2/Z2xuTXuhY6hjFi6B9d/+yVLB17zHqpEpSbafCQMZvGc1pe7u"
  "bcf+4wFtt5yRo7WRz6y/bMUnpCB+TlT++w8ZKTLwXNNLgAAIRC8WtawKx/c7aCJo"
  "lekN65VXAgMBAAGjdjB0MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD"
  "AQH/MB0GA1UdDgQWBBSUr9rVPeS8n/o4spfRv3Kp+4PGCTAfBgNVHSMEGDAWgBSU"
  "r9rVPeS8n/o4spfRv3Kp+4PGCTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEF"
  "BQADgYEAnsGoKxDrTn+BGZNxUTjbVwv6Lkk9xr2R2y68JiUY48fGgu5IzO9QSsl7"
  "UmqLAZCryOY08lNxduVXyiwRHSt8088v+6qvCgAjhxTZIn8EFOECom6tKTV9Hp6h"
  "dU+z5aFhfSgJRKL0SzTrBg8P2/LIqMMotKBoQWqjnB+aP2d4jgk=";

cdk::str subjectCert =           // the subject certificate
  "MIID2DCCA0GgAwIBAgIBFzANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJVUzEL"
  "MAkGA1UECBMCSUwxETAPBgNVBAcTCE9hayBQYXJrMQwwCgYDVQQKEwNJU0MxEDAO"
  "BgNVBAsTB0RlbW8gQ0ExHDAaBgNVBAMTE0NlcnRpZmljYXRlIE1hbmFnZXIwHhcN"
  "MDMwNjI3MTk0ODU3WhcNMDQwNjI2MTk0ODU3WjBJMREwDwYDVQQMEwhNaXggQ2Fz"
  "ZTEOMAwGA1UEAxMFVGVTVDMxJDAiBgkqhkiG9w0BCQEWFXRlc3QzQGluZm9zZWNj"
  "b3JwLmNvbTCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCT6JZdr9nf7P0AtGa2j5Dq"
  "aK9dyf7ZFSeNGzoTdHHmVZbDf+0MeCn/j4Mx+BonAEOOzcwJRH3Dl8aF85cpT3Ir"
  "zEhK7fKL7SWqqzXTWmXbH9YsnXulWET+sflAHmcTQJM+5DxU5NxFlADXrWEki4Oi"
  "Ykg1sx//LZWVpbkLJ25E+QIVALtd/kLeEVwA217xsDiDRNbBVIWtAoGAaSefjrq4"
  "Do8k8BhGIZZjbEwiVGTwEz4PLyHnu+0dhT8G65SahoUKMKm1ss6oMOqtAOe99h68"
  "gcxB3rr/G1v9nyqksRMpsTw9xpURJ/vIa001M4I7+XpY3i/x+cmzF3ujSU7V4AA4"
  "Pb1qaZ1XiVSPzvUHNo5L5m7rDwh1c4y8/MUDgYQAAoGAAJevjq95fOwS9l3Cr1xq"
  "KIftJsWoT16u37VhiRfGEq+/JEkDjBkC22S10E7Iyv7p75Lv617N+2pOYQRWtcxp"
  "U169I60gxNvW6c2eLXHA+d9vKvaIqjmsYP9K/poqMJ2sdK50OQJxId4xOWiN9KLp"
  "BicXOLjcp1VHE1MaNST1fQWjgZUwgZIwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1Ud"
  "DwEB/wQEAwIF4DAYBgNVHREEETAPgQ1hbGljZUBpc2MuY29tMB8GA1UdIwQYMBaA"
  "FJSv2tU95Lyf+jiyl9G/cqn7g8YJMDIGA1UdCQQrMCkwEgYJYIZIAWUCAgFEMQUW"
  "A0FCQzATBgpghkgBhvhCAwEEMQUWA1ImRDANBgkqhkiG9w0BAQUFAAOBgQCq778P"
  "0yNeutqWu7pomHJ8yWZ7ZC2w8njvOLLJoECaaP5j9xt6hRdyCqIfzx3hVWxv1VOb"
  "dHgEfNViMFPaURYRJWbfX0Gn7+DJ9Lluby6prXPm0rTIZ4GdRCBA58dhn7ct5itc"
  "YorKfy79SaeumDvsjIvbe7iR6WO5rDxuVChaBA==";

// decode the certificates if they are base64-encoded
if (issuerCert[0] != 0x30) issuerCert = issuerCert.tobin64();
if (subjectCert[0] != 0x30) subjectCert = subjectCert.tobin64();

// check the signature in subjectCert using public key in issuerCert
int i = cdk::checkcert(issuerCert, subjectCert);
// checkcert() returns 0 if certificate is valid, non-zero otherwise
printf("Certificate is %s\n", i ? "INVALID!" : "valid");

The following code shows how to test a certificate against a CRL.

cdk::str strCertificate =        // a base64-encoded certificate
  "MIIChjCCAe+gAwIBAgIBNjANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJVUzEY"
  "MBYGA1UEChMPVS5TLiBHb3Zlcm5tZW50MQwwCgYDVQQLEwNEb2QxEDAOBgNVBAsT"
  "B1Rlc3RpbmcxFTATBgNVBAMTDENBMS1JQy4wNC4wMTAeFw05ODAxMDExMjAxMDBa"
  "Fw00ODAxMDExMjAxMDBaMGAxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9VLlMuIEdv"
  "dmVybm1lbnQxDDAKBgNVBAsTA0RvRDEQMA4GA1UECxMHVGVzdGluZzEXMBUGA1UE"
  "AxMOVXNlcjEtSUMuMDQuMDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM2d"
  "GkraKGdIi6EXxAu6/ekMqDloX5YSVBGh4Hp2faujr1u4j8Lp8afqjngRxFUpTqGb"
  "qH0ETgm4cVPXmc9rUvUzYTMdxTUmIZ+iW+ULZEvzNB712kxRPCD2kDFN2fH2ai8m"
  "iXr434w+weLm8VQN4jJGo4nswhSs2w1gsUmWyn/ZAgMBAAGjUjBQMA4GA1UdDwEB"
  "/wQEAwIF4DAWBgNVHSAEDzANMAsGCWCGSAFlAwEwATARBgNVHQ4ECgQITsLx/sO1"
  "edwwEwYDVR0jBAwwCoAIbMuZ73onuZswDQYJKoZIhvcNAQEFBQADgYEAeKft0RM8"
  "/b3zQodaKrTdWiFyLg5fzoOsTecSfdFPXoqz9J5ejLVkvJevSmfXJrIUhKXySzsQ"
  "i+GazuTh/hvWjwUTIvmupi+EiFudnMpXCro8bgi48+NkepNjXvjsSmOfzlrK3Sxt"
  "pH5dqonL6LHjGyg+Xp0Nor1m5g1rLHyrcEk=";

cdk::str strCRL =                // a base64-encoded CRL
  "MIIBSzCBtQIBATANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJVUzEYMBYGA1UE"
  "ChMPVS5TLiBHb3Zlcm5tZW50MQwwCgYDVQQLEwNEb2QxEDAOBgNVBAsTB1Rlc3Rp"
  "bmcxFTATBgNVBAMTDENBMS1JQy4wNC4wMRcNOTkwMTAxMTIwMTAwWhcNNDgwMTAx"
  "MTIwMTAwWqAjMCEwCgYDVR0UBAMCAQEwEwYDVR0jBAwwCoAIbMuZ73onuZswDQYJ"
  "KoZIhvcNAQEFBQADgYEAMk6DRztz1AyFnFr1KAlbjLLwxtQplf2eIc//zUkDFVUH"
  "tX5TrEC/ijUaItjdkOoPGQfpnL0w8xwyqWndMh593QPCqIJTtv/iACoiJNZ90ZJS"
  "0adcdZ+AEmQpa0Zv0e1JOqRrPoAfTq4HrOfRvhBwhvKQNtTExupW/EBudznKC6Q=";

// decode the certificate or CRL if either is base64-encoded
if (strCertificate[0] != 0x30) strCertificate = strCertificate.tobin64();
if (strCRL[0] != 0x30) strCRL = strCRL.tobin64();

cdk::CRL crl;           // instantiate a CRL object and load the
crl.load(strCRL);       // ASN.1 encoded CRL (test return code for error)

// check to see if the certificate has been revoked
cdk::Cert::Time tDate;  // output buffer for revocation date (if any)
int nReason;            // reason for revocation (if any)
int i = crl.isRevoked(strCertificate, tDate, nReason);
if (i) 
    printf("Certificate has been revoked: reason code = %d\n", nReason);
else
    printf("Certificate does not appear on this CRL\n");


Issuing an OCSP Status Request

The following function illustrates how to implement a simple OCSP client:

int OCSP_CheckCert(const str &caCert, const str &subCert, const str &svrURL, 
  const str &svrCert)
{
  /* issue an OCSP status request
     parameters:
        caCert   issuer certificate
        subCert  subject certificate whose validity is to be tested
        svrURL   URL of OCSP responder
        svrCert  responder certificate (for authentication of response)
     returns: 
        0 if certificate is valid
        1 if certificate has been revoked
        2 if certificate status is unknown (responder not authoritative for this CA)
        3 protocol error
        4 signature on response is invalid (reponse cannot be trusted)
  */
  // build the OCSP request
  cdk::str nonce = cdk::getrand2(16);
  cdk::str req = cdk::make_ocsp_req(caCert, subCert, nonce);
  
  // query the server
  CAmHttpSocket http;
  http.m_lpszContentType = "Content-Type: application/ocsp-request \r\n";
  std::string sResponse = http.GetPage(svrURL.c_str(), true, req.c_str(), req.length());
  cdk::str res = str_(sResponse);

  // check the response (and its signature)
  cdk::asn sinfo, certs, dn;
  cdk::TimeT revtime;
  int i = check_ocsp(req, res, sinfo, revtime, certs, dn);
  if ( (i == 0) || (i == 1) )   // if OK or revoked, check responder's signature
  {
    if ( check_signinfo(svrCert, sinfo) != 0 )
      i = 4;    //signature is invalid!
  }
  return i;
}

OCSP is specified in RFC 2560. The CAmHttpSocket class used here for the HTTP POST operation is available from the Code Project.


The next topic is Handling Public and Private Keys.


ISC Cryptographic Development Kit - User's Guide
ISC website
Questions? E-mail ISC technical support
Copyright© 2002-2006 Information Security Corp. All rights reserved.