X509Certificate

From Crypto++ Wiki
Jump to navigation Jump to search

Digital certificates are used to bind identities and public keys using a cryptographic signature. The certificates are used in protocols such as IPSec, TLS and SSH. X509Certificate is a class that allows the library to load X.509 v3 certificates and access values in the certificate, like names and the public key. The class is based on earlier work by Geoff Beier. You can find Geoff's work at X.509 certificate example.

The X509Certificate class is a read-only implementation. The class loads a DER encoded X.509 v3 certificate and provides accessors for the common fields, like signature, signature algorithm, toBeSigned, serialNumber, issuerDistinguishedName, subjectDistinguishedName, and subjectPublicKeyInfo. More functionality will be added over time as use cases are determined.

The class does not allow you to edit a certificate. The class allows you to Save, but it is the same certificate that was Loaded. It is assumed another tool is used to create and issue certificates. This functionality may be added over time if the need arises.

X509Certificate is fairly robust and can load all of the certificates distributed by Mozilla as packaged in cacert.pem (packaging courtesy of Daniel Stenberg and cURL). X509Certificate does not load static Diffie-Hellman keys because they are no longer in favor.

The Crypto++ library received several modifications to allow better integration of X509Certificate. For example, the functions BERDecodeTextString, BERDecodeDate and BERDecodePeekLength were added to the library's ASN.1 functions. Additionally, some useful OID's were added to the ASN1 namespace, like sha256WithRSAEncryption.

The class is not part of the Crypto++ library. Rather, it is an add-on that you download and compile yourself. The source code is available as part of the PEM Pack. The PEM Pack also adds PEM support on top of X509Certificate's DER support.

Compiling and Testing

To compile the source files, simply drop them in your cryptopp folder and then execute Crypto++'s GNUmakefile. The library's makefile will automatically pick them up. Windows users should add the header and source files to the cryptlib project in their respective folders.

There are two C++ source files to add the X509Certificate class to Crypto++. The files are:

  • x509cert.h - the include file for the X509Certificate class used by applications.
  • x509cert.cpp - the source file for the X509Certificate class.

The class can be tested using the PEM Pack's pem_test.cxx. pem_test.cxx loads each certificate in Mozilla's cacert.pem, calls Validate on each certificate, and then prints each certificate to std::ostringstream to ensure the accessor functions are working as expected.

The default behavior is to validate keys and parameters after reading them in Debug builds (but not Release builds). If you want the keys and parameters validated after reading them, then open pem_common.h and uncomment the define PEM_KEY_OR_PARAMETER_VALIDATION. Once compiled, changing PEM_KEY_OR_PARAMETER_VALIDATION has no effect.

When using PEM_KEY_OR_PARAMETER_VALIDATION, an OS random number generator must be available, like AutoSeededRandomPool. Do not build the library and the PEM Pack with -DNO_OS_DEPENDENCE because the define removes the OS random number generators.

The easiest way to test the PEM source files is drop them in your cryptopp directory, and then issue the following commands. The scripts will build and test for you.

$ cd cryptopp
$ make distclean
$ ./pem_create.sh && ./pem_verify.sh

Verify Certificate

There are three techniques used to verify a certificate. The first is the basic verification using a self-signed certificate with a RSA key. It is the easiest case, and the subject's public key can be used to verify the certificate.

The second case is a chain, and it differs from the first case because the issuer's public key is used and it is located in a different certificate. The third case is a signature using ECDSA. The interesting point for this case is, the ASN.1/DER signature must be converted to P1363.

Self Signed Certificate

The code below verifies the signature over a self-signed certificate. Since the certificate is self-signed, the subjectPublicKey and issuerPublicKey are the same. You simply use the public key in the certificate to verify the TBSCertificate. The recipe to create the self-signed certificate is below at OpenSSL x509.

#include "cryptlib.h"
#include "x509cert.h"
#include "secblock.h"
#include "filters.h"
#include "rsa.h"
#include "sha.h"
#include "hex.h"
#include "pem.h"

#include <iostream>
#include <string>

extern const std::string pemCertificate;

int main(int argc, char* argv[])
{
    using namespace CryptoPP;

    StringSource ss(pemCertificate, true);
    X509Certificate cert;
    PEM_Load(ss, cert);

    const SecByteBlock& signature = cert.GetCertificateSignature();
    const SecByteBlock& toBeSigned = cert.GetToBeSigned();
    const X509PublicKey& publicKey = cert.GetSubjectPublicKey();
    
    RSASS<PKCS1v15, SHA256>::Verifier verifier(publicKey);
    bool result = verifier.VerifyMessage(toBeSigned, toBeSigned.size(), signature, signature.size());

    if (result)
        std::cout << "Verified certificate" << std::endl;
    else
        std::cout << "Failed to verify certificate" << std::endl;

    std::cout << "Signature: ";
    size_t size = std::min(signature.size(), (size_t)30);
    StringSource(signature, size, true, new HexEncoder(new FileSink(std::cout)));
    std::cout << "..." << std::endl;

    std::cout << "To Be Signed: ";
    size = std::min(signature.size(), (size_t)30);
    StringSource(toBeSigned, size, true, new HexEncoder(new FileSink(std::cout)));
    std::cout << "..." << std::endl;

    return 0;
}

const std::string pemCertificate =
    "-----BEGIN CERTIFICATE-----\r\n"
    "MIIEZTCCA02gAwIBAgIUTrRCySQFQNRYcWKYxhHVNsult3cwDQYJKoZIhvcNAQEL\r\n"
    "BQAwfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y\r\n"
    "azEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRgwFgYDVQQDDA9FeGFtcGxlIENvbXBh\r\n"
    "bnkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTkxMDAxMDYx\r\n"
    "NzE0WhcNMjAwOTMwMDYxNzE0WjB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkx\r\n"
    "ETAPBgNVBAcMCE5ldyBZb3JrMRUwEwYDVQQKDAxFeGFtcGxlLCBMTEMxGDAWBgNV\r\n"
    "BAMMD0V4YW1wbGUgQ29tcGFueTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl\r\n"
    "LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL1tC7yUK8h7L/dg\r\n"
    "THkoQGYLhBI/jNIoN+HJUP6fnEIrhaYnH3bbFoXKcarOqZusKmhhRIsgGeeT2NG6\r\n"
    "0nWgkRbBUH2Ic1gNqzIhQsF8eirUGchaCyXuuueBvQUrnkJjVG9yyJ5XFdjjx4kX\r\n"
    "y9IMxAM80W3GmMxXkKlS1vYVqKmRNf/NUne5h/U/kRtkGqjDQpIG/y9et8+mY3CV\r\n"
    "vjh4AiFAIswPB5beUqSVuq+vx+VCo3vZw9KptuEwqphZMC8YVuSHi3/hQXuaBlG1\r\n"
    "sAfVR05KIl3tKVp428tQPZZZjreVZTBfWCwI/marlFFxkC9bWuIAzpy8tTPsB21r\r\n"
    "LDvXof8CAwEAAaOB2DCB1TAdBgNVHQ4EFgQUgrdpzgQ4EeZk2VRdMDXPeSPCvGsw\r\n"
    "HwYDVR0jBBgwFoAUgrdpzgQ4EeZk2VRdMDXPeSPCvGswDAYDVR0TAQH/BAIwADAL\r\n"
    "BgNVHQ8EBAMCBaAwSgYDVR0RBEMwQYILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxl\r\n"
    "LmNvbYIQbWFpbC5leGFtcGxlLmNvbYIPZnRwLmV4YW1wbGUuY29tMCwGCWCGSAGG\r\n"
    "+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0B\r\n"
    "AQsFAAOCAQEAn5fSk9UK+N4MDAFytzIpfUoSobiVvvNT//+dticgJyySyPThXeZ+\r\n"
    "+I+C6FSykkr0+wq4DZidygpHydS1/E2Dvlsa2XHQbgTyfiBdpEcbu6bVNeBRAtyP\r\n"
    "kWe0pO7/rha94dcFMDN88d4qMIragWh+yJk0rIofLxQe5qWounTYBetutz5dFOiJ\r\n"
    "lwvGeY1HTnElkxaXULtoz+QPcgidQX8sEKhHNwKiae5gj0YeWowVoAnaHhwYiRMa\r\n"
    "VdUKKD1CiSkFNaKSUW0ee8dpVr3rWtt+X1K0+B46lUPGUG5QtN33dtisqrY3X8q7\r\n"
    "g0NwwUKAWL9DE1uadKjJI+X1AL0ft6Nj4Q==\r\n"
    "-----END CERTIFICATE-----\r\n";

Running the program results in the following.

$ ./test.exe 
Verified certificate
Signature: 9F97D293D50AF8DE0C0C0172B732297D4A12A1B895BEF353FFFF9DB62720...
To Be Signed: 3082034DA00302010202144EB442C9240540D458716298C611D536CBA5B7...

Certificate Chain

If you have a certificate chain, then you use the issuerPublicKey to verify the signature on the server's certificate. You can find the issuerPublicKey in the CA certificate. The code to verify the signature on a server's certificate in a chain would look similar to below.

StringSource ss1(pemServerCert, true);
X509Certificate serverCert;
PEM_Load(ss1, serverCert);

StringSource ss2(pemCaCert, true);
X509Certificate caCert;
PEM_Load(ss2, caCert);

const SecByteBlock& signature = serverCert.GetCertificateSignature();
const SecByteBlock& toBeSigned = serverCert.GetToBeSigned();
const X509PublicKey& publicKey = caCert.GetSubjectPublicKey();

RSASS<PKCS1v15, SHA256>::Verifier verifier(publicKey);
bool result = verifier.VerifyMessage(toBeSigned, toBeSigned.size(), signature, signature.size());

if (result)
    std::cout << "Verified certificate" << std::endl;
else
    std::cout << "Failed to verify certificate" << std::endl;

ECDSA Certificates

A certificate signed using ECDSA needs the signature converted from ASN.1/DER to P1363. The X509Certificate class converts them automatically for id_ecdsaWithSHA1 through id_ecdsaWithSHA512. If a different signature algorithm specifies ECDSA, then the signature may need to be manually converted until it is added to the class.

The code below performs the conversion and verifies the signature. The code was taken from VerifySignature in x509cert.cpp. The conversion is covered in DSAConvertSignatureFormat on the wiki.

// If the certificate is self-signed, then this certificate can be verified
// using the subjectPublicKey. Otherwise, the publicKey is the CA's publicKey.
const X509PublicKey &publicKey = GetSubjectPublicKey();
const OID &algorithm = GetCertificateSignatureAlgorithm();

// Get a verifier object based on the algorithm and public key.
member_ptr<PK_Verifier> verifier(GetPK_VerifierObject(algorithm, publicKey));

size_t size = verifier->SignatureLength();
SecByteBlock p1363Signature(size);

size = DSAConvertSignatureFormat(
           p1363Signature, p1363Signature.size(), DSA_P1363,
           signature, signature.size(), DSA_DER);
p1363Signature.resize(size);

bool result = verifier->VerifyMessage(toBeSigned, toBeSigned.size(),
                            p1363Signature, p1363Signature.size());

if (result)
    std::cout << "Verified certificate" << std::endl;
else
    std::cout << "Failed to verify certificate" << std::endl;

Certificate Identities

A certificate's sole purpose in life is to bind a public key to an identity using a cryptographic signature. X509Certificate makes it easier to discover the bindings by providing GetSubjectPublicKey to obtain the certificate's public key; and GetSubjectIdentities to obtain the subject identities in the certificate.

The identity is an array of IdentityValue, and the collection includes X.520 attributes and values, like RFC 822 email, PKCS #9 email, Distinguished Name (DN), subjectAltName (SAN), and uniqueId (UID). It is up to the caller to determine what is acceptable.

You can retrieve and display the identity using the following code.

std::cout << "\nDumping identities:" << std::endl;
std::cout << cert.GetSubjectIdentities() << std::endl;

Running a program using the test certificate results in output similar to the following.

Dumping identities:

DN: C=US; ST=NY; L="New York"; O="Example, LLC"; CN="Example Company"; email=support@example.com
CN: Example Company
email: support@example.com
SPKI: 82B769CE043811E664D9545D3035CF7923C2BC6B
SAN: example.com
SAN: www.example.com
SAN: mail.example.com
SAN: ftp.example.com
SAN: 127.0.0.1
SAN: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01
SAN: webmaster@example.com
SAN: ftpmaster@example.com
SAN: hostmaster@example.com

Note that some information is presented more than once. For example, the email address and the Common Name (CN) are part of the distinguished name, and also provided as stand-alone values. This by design to allow the applications to do something like below.

bool FindIdentityByName(const std::string name,
          const IdentityValueArray &values,
          IdentityValueArray::const_iterator &loc)
{
    IdentityValueArray::const_iterator first = values.begin();
    IdentityValueArray::const_iterator last = values.end();

    while (first != last)
    {
        if (name == first->m_text)
        {
            loc = first;
            return true;
        }
        ++first;
    }

    loc = last;
    return false;
}

SubjectAltNames

Subject Alternate Names (SAN) is one of the primary places to find identities. GetSubjectIdentities attempts to collect identities from the SAN, but it only loads some of the GeneralNames.

The RFCs define the SAN as follows.

SubjectAltName ::= GeneralNames

GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName

GeneralName ::= CHOICE {
     otherName                 [0]  AnotherName,
     rfc822Name                [1]  IA5String,
     dNSName                   [2]  IA5String,
     x400Address               [3]  ORAddress,
     directoryName             [4]  Name,
     ediPartyName              [5]  EDIPartyName,
     uniformResourceIdentifier [6]  IA5String,
     iPAddress                 [7]  OCTET STRING,
     registeredID              [8]  OBJECT IDENTIFIER }

The X509Certificate implementation has full support for rfc822Name, dNSName, iPAddress and uniformResourceIdentifier. There is partial support for otherName, but we need a test cert to finish the implementation. There is no support for the remaining GeneralNames. If you have a certificate for testing, then please send an email to the mailing list.

Certificate output

X509Certificate::operator<< is fairly boring at the moment. It calls the member function X509Certificate::Print, which outputs some of the certificate's fields. A typical output is shown below.

Dumping certificate:

Version: 2
Serial Number: 0x446e47feeb7b36a0b2c5acf334770c2a1c0dd477h
Not Before: 191001220622Z
Not After: 200930220622Z
Issuer DN: C=US; ST=NY; L="New York"; O="Example, LLC"; CN="Example Company"; email=test@example.com
Subject DN: C=US; ST=NY; L="New York"; O="Example, LLC"; CN="Example Company"; email=test@example.com
Authority KeyId: hash: 82B769CE043811E664D9545D3035CF7923C2BC6B
Subject KeyId: hash: 82B769CE043811E664D9545D3035CF7923C2BC6B
Key Usage: digitalSignature, keyEncipherment, serverAuth, clientAuth, secureShellServer
CA Certificate: 0, Self Signed: 1
Signature Alg: sha256WithRSAEncryption (1.2.840.113549.1.1.11)
To Be Signed: 3082034DA0030201020214446E47FEEB7B36A0B2C5ACF334770C2A1C0DD4...
Signature: B7779D4A9341C452FA58E1D8E97E291ED21917DCCCB54AAFDA87D6C19E7A...

You can modify X509Certificate::Print to suit your taste.

Certificate Validation

If you call Validate on a X509Certificate, then the call validates the subject's public key. The signature on a self signed certificate is verified at level 1 or above.

bool X509Certificate::Validate(RandomNumberGenerator &rng, unsigned int level) const
{
    // TODO: add more tests
    bool valid = true, pass;

    pass = m_subjectPublicKey->Validate(rng, level);
    valid = pass && valid;

    if (IsSelfSigned() && level >= 1)
    {
        const X509PublicKey &publicKey = GetSubjectPublicKey();
        pass = ValidateSignature(rng, publicKey);
        valid = pass && valid;
    }

    return valid;
}

More tests can be added from the following documents. In practice certificate validation is the wild, wild west. It will probably be a good idea to allow the user to select the tests they are interested in by setting a mask before calling Validate.

OpenSSL x509

You can use OpenSSL and the openssl x509 subcommand to create a self-signed certificate. There are two parts to creating the certificate. First is a custom conf file, and the second is the command. The OpenSSL man pages provide information for both the config file and x509 subcommand.

Your OpenSSL conf file should look similar to below. The conf file creates a self-signed server certificate for the domain example.com. The conf file has several options that probably should not be present for a real server certificate, like keyEncipherment. secureShellServer requires OpenSSL 1.1.0 or above. prompt = no means no prompts are provided, like confirming country or organization name.

[ req ]
prompt               = no
default_bits         = 2048
default_keyfile      = server-key.pem
distinguished_name   = subject
req_extensions       = req_ext
x509_extensions      = x509_ext
string_mask          = utf8only

[ subject ]
countryName          = US
stateOrProvinceName  = NY
localityName         = New York
organizationName     = Example, LLC
commonName           = Example Company
emailAddress         = test@example.com

[ x509_ext ]
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid,issuer
basicConstraints        = critical,CA:FALSE
keyUsage                = digitalSignature, keyEncipherment
extendedKeyUsage        = clientAuth, serverAuth, secureShellServer
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated Certificate"

[ req_ext ]
subjectKeyIdentifier    = hash
basicConstraints        = critical,CA:FALSE
keyUsage                = digitalSignature, keyEncipherment
extendedKeyUsage        = clientAuth, serverAuth, secureShellServer
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated Certificate"

[ alternate_names ]
DNS.1  = example.com
DNS.2  = www.example.com
DNS.3  = mail.example.com
DNS.4  = ftp.example.com
IP.1   = 127.0.0.1
IP.2   = ::1
email.1  = webmaster@example.com
email.2  = ftpmaster@example.com
email.3  = hostmaster@example.com

After creating the conf file you can create the certificate with the following command. The command creates a new key pair and the certificate using the conf file. The private key does not have a password.

openssl req -config example-com.conf -new -x509 -sha256 -newkey rsa:2048 -nodes \
    -keyout example-com.key.pem -days 365 -out example-com.cert.pem

You can convert the PEM certificate to ASN.1/DER with the following command.

openssl x509 -in example-com.cert.pem -inform PEM -out example-com.cert.der -outform DER

You can view the certificate with the following OpenSSL command.

$ openssl x509 -in example-com.cert.pem -inform PEM -text -noout

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            4e:b4:42:c9:24:05:40:d4:58:71:62:98:c6:11:d5:36:cb:a5:b7:77
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = NY, L = New York, O = "Example, LLC", CN = Example Company, emailAddress = test@example.com
        Validity
            Not Before: Oct  1 06:17:14 2019 GMT
            Not After : Sep 30 06:17:14 2020 GMT
        Subject: C = US, ST = NY, L = New York, O = "Example, LLC", CN = Example Company, emailAddress = test@example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
        ...

If you prefer you can view the certificate with Peter Gutmann's dumpasn1.

$ openssl x509 -in example-com.cert.pem -inform PEM -out example-com.cert.der -outform DER
$ dumpasn1 example-com.cert.der 
   0 1125: SEQUENCE {
   4  845:   SEQUENCE {
   8    3:     [0] {
  10    1:       INTEGER 2
         :       }
  13   20:     INTEGER 4E B4 42 C9 24 05 40 D4 58 71 62 98 C6 11 D5 36 CB A5 B7 77
  35   13:     SEQUENCE {
  37    9:       OBJECT IDENTIFIER
         :         sha256WithRSAEncryption (1 2 840 113549 1 1 11)
  48    0:       NULL
         :       }
  ...

Certificate Basics

A X.509 certificate binds a public key to an identity using a cryptographic signature. That's all it does.

There are two types of certificates. The first type is a CA certificate. A CA certificate has CA = true in basic_constraints, and can be used to issue, manage and revoke other certificates. The second type of certificate is an end entity certificate. An end entity has CA = false in basic_constraints, and is issued to users, machines, web servers, VPN servers, SSH servers and other end users. If you create a self-signed certificate for your web server, then you created an end entity certificate because CA = false.

Certificates are issued by an issuer or authority. Certificates can be issued for intermediate/subordinate CA certificates and can be issued to end users. The entity for whom the certificate was issued is called the subject. The issuer sign's the subject's certificate.

A certificate can be self-signed. A self-signed certificate means the issuer and subject are the same entity. If the certificate is self-signed, then you issued your own certificate. In the RFCs, like RFC 4158, Certificate Path Building, it also means things like the authorityKeyIdentifier = subjectKeyIdentifier and issuerDistinguishedName = subjectDistinguishedName.

The rules governing the way certificates are issued, managed and revoked is codified in a Certification Practice Statement (CPS). All public Certification Authorities provide them. Most of CPS'es are obscene for the user, and make statements like the certificates are not fit for their purpose (no kidding, read one sometime).

Certification Practice Statements are legally binding documents. They are part of the documentation for a Public Key Infrastructure (PKI). A CA that does not follow its CPS risks being dis-trusted, like Symmantec for chronically breaking its own rules. Symantec owns/operates Thawte, VeriSign, Equifax, GeoTrust, and RapidSSL, so it was a deep cut and the stock price fell 35%.

People who trust certificates and certificate chains are called Relying Parties. A relying party can chose to trust a commercial CA like Comodo or Verisign. They can also chose to trust a self-signed end entity certificate issued by an individual. Relying parties can base their decision on a Certification Practice Statement for a public CA, or base their decision on friendship like in Web of Trust schemes. A common collection of trusted issuers can be found in Mozilla's cacert.pem.

The two most popular PKI's are (1) the Internet's PKI is called PKIX, and (2) the CA/Browser Forum (CA/B) PKI. The Internet's PKI is used by tools like cURL, OpenSSL and Wget and codified in various RFCs. The CA/Browser Forum PKI is used by browsers, and their PKI is codified in the CA/B Baseline Requirements. The CA/B is sometimes called the "ca cartel" because they are a closed group.

PKIX and the CA/B have different issuing policies, but they mostly overlap. An example of them differing is, PKIX allows SubjectAltName (SAN) dnsNames of *.com, *.net and *.org, while the CA/B BR does not. Another difference is, the CA/B requires the Common Name (CN) provide the domain, like example.com, while PKIX does not.

Public CA's sell "extended validation" certificates. There is nothing special about them, and they don't provide any additional technical or security controls. Extended validation certificates are more expensive than standard validation certificates, and they helped restore public CA profits after the race to the bottom cut into business profits. Also see Peter Gutmann's book Engineering Security.

Various governments and organizations run their own PKIs. The US government's PKI documents are over 2500 pages. A spectacular PKI failure from the Dutch government was Diginotar. Diginotar was caught by Public Key Pinning. Here is the Google Product Support message from 2011 that exposed Diginotar: https://productforums.google.com/forum/#!category-topic/gmail/share-and-discuss-with-others/3J3r2JqFNTw

You can run a private PKI at your house or in your organization. You can make up any rules you like for your PKI. For example, you can have a rule that says you will only issue certificates on Wednesday, and a certificate issued on another day is invalid. For a private PKI you don't need to publish Certification Practice Statement. If your rules diverge too much from PKIX and CA/B Forums, then tools like cURL, OpenSSL and Wget and browsers will have a tough time conforming.

Downloads

cryptopp-pem - Additional source files which allow you to read and write PEM encoded keys, including encrypted private keys and X.509 certificates. The collection includes a script to build test keys and certificates with OpenSSL, a small C++ test program to test reading and writing the keys and certificates, and a script to verify the keys written by Crypto++ using OpenSSL.