Software Development

Signing Digital Certificates with OpenSSL Library

While working on the pgopenssltypes extension I realized that I haven’t discussed how to sign digital certificates using the OpenSSL library. (At least I don’t recall doing so – I might have discussed this in the early days of the blog. I’m pretty sure I’ve already discussed signing digital certificates with the BouncyCastle (java) library.)

My pgopenssltypes extension will have the ability to sign digital certificates for testing purposes but the real work will be done in a possible pgca extension. Being a CA requires a lot more than the ability to sign simple certs.

The basic code to sign a certificate is straightforward.

 

BIGNUM *serialNumber;
X509_NAME *issuerName;
X509_NAME *subjectName;
time_t notBefore;
time_t notAfter;
EVP_PKEY *issuerKey; // private key
EVP_PKEY *subjectKey; // public key

// allocate memory for a cert.
X509 cert = X509_new();

// this is standard
X509_set_version(cert, 3);

// set the the mandatory fields.
X509_set_serialNumber(cert, BN_to_ASN1_INTEGER(serialNumber, NULL));
X509_set_issuer_name(cert, issuerName);
X509_set_subject_name(cert, subjectName);
X509_set_notBefore(cert, ASN1_TIME_set(NULL, notBefore));
X509_set_notAfter(cert, ASN1_TIME_set(NULL, notAfter));
X509_set_pubkey(cert, subjectKey);

// sign the certificate. In this case I'm using SHA256 for the hash.
if ((r = X509_sign(cert, issuerKey, EVP_sha256())) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    X509_free(cert);
    return NULL;
}

// release the memory
PKCS8_PRIV_KEY_INFO_free(p8);
EVP_PKEY_free(pkey);

// write certificate to file
// FILE *fp = fopen(...)
// PEM_write_X509(fp, cert);

return cert;

There is a variant using X509_REQ instead of an X509 object but it’s a trivial change. (As I recall an X509_REQ is a special self-signed X509 certificate mostly used by full Certificate Authorities (CA).)

The issuer is normally associated with its own digital certificate, ad infinitum. This forms a “certificate chain” where the issuer of each certificate is the subject of the next. The chain ends at either a “trusted” certificate (where it is up to the user to decide what is “trusted”), or a self-signed certificate (where again it is up to user to decide how much that subject/issuer is “trusted”).

Certificates by themselves have no value beyond being a convenient way to track public keys. Certificate chains can be used to authenticate identity only to the extent that each intermediate signer can be trusted.

Finally the code above will happily allow any key to be used to sign a certificate. In a well-behaved application the issuer cert will be checked for the “basic constraint” – it is a number that specifies how many additional levels can be signed and should be dropped by at least one in each generation.

Data Types

The code contains several unfamiliar data types.

A BIGNUM is the OpenSSL implementation of an arbitrary length integer. Some CAs use 128 or 160 bit serial numbers, not coincidently the length of an MD5 or SHA1 hash value. However this is just a convention and there’s no reason why an attacker can’t use a 10k byte serial number in an attempt to cause a buffer overflow when poorly written code attempts to read it.

There is a loose convention that certificates with higher serial numbers were issued later than certificates with lower serial numbers. Many CAs do not follow this convention.

Some CAs are concerned that a simple sequence leaks sensitive information, namely how many certificates have been issued. A common countermeasure is to use a date-based serial number, e.g., the hex value may be 0x20141230NNNNNN where NNNNNN is the time, a pseudorandom number, or some combination.

Finally cautious CAs may encode information in the serial number itself. For instance it might quietly ensure that the serial number is always 3 mod 17, or that analogous values reflect information present elsewhere in the certificate. An attacker with access to the CA’s private key is probably unaware of these checks and finding bad serial numbers in the wild might be the first indication of a breach.

A EVP_PKEY is a generic private or public key. It can contain an RSA, ECC, DSA or DH key. I think we’re only concerned about RSA keys when creating digital certificates, possibly ECC with low-power devices.

We can convert an RSA key to an EVP_PKEY with:

RSA *rsa;
EVP_PKEY *pkey;

pkey = EVP_PKEY_new();
if (EVP_PKEY_set1_RSA(pkey, rsa) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    EVP_PKEY_free(pkey);
    return NULL;
}

return pkey;

It is also easy to convert a .p8 (PKCS8) key to an EVP_PKEY:

PKCS8_PRIV_KEY_INFO *p8;
EVP_PKEY *pkey;

pkey = EVP_PKCS82PKEY(p8);
if (pkey == null) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    return NULL;
}

return pkey;

A X509_NAME is the same as an LDAP Distingushed Name. Literally – they come from the same X.500 standards. (Hence X509.) Many architectures take advantage of this – it’s a good way to tie user’s digital certificate to enterprise directory services.

A X509_NAME is a stack of X509_NAME_ENTRY values. Each entry is a key-value pair. As an example “CN=Bear Giles, C=US, ST=Colorado” would have three X509_NAME_ENTRY values – (“CN”, “Bear Giles”), (“C”, “US”), (“ST”, “Colorado”). There are several dozen standard keys, some of them can be repeated. (E.g., “DC” for domain component. The server at invariantproperties.com would be “DC=invariantproperties, DC=com”.) For more details see RFC 4519 attribute types.

(Sidenote: LDAP usually has the format shown above. OpenSSL tends to replace the commas with slashes, e.g., “CN=Bear Giles/C=US/ST=Colorado”.)

You can print an X509_NAME with:

X509_NAME *name;
char buf[BUF_LEN];

X509_NAME_oneline(name, buf, BUF_LEN);

or:

BIO *bio;  // can point to memory buffer, file, etc.
X509_NAME *name;
int obase; // indentation on multi-line output.

X509_NAME_print(bio, name, obase);

Creating X509_NAME objects is a bit more complicated.

char *name;              // e.g., "C" for Country
unsigned char *value;    // e.g., "US"
int type = MBSTRING_ASC; // or MBSTRING_UTF8
X509_NAME *name;

// value is null-terminated string.
int len = -1;

// these values add entry to end of X509_NAME.
int loc = -1;
int set = 0;

name = X509_NAME_new();
if (X509_NAME_add_entry_by_txt(name, key, type, value, len, loc, set) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    return NULL;
}

Extensions

The true power in X509v3 certificates is that they can be extended with arbitrary information. There are a dozen or so widely used extensions but anyone can get a new ASN1 OID and add their own information to a digital certificate. (A new OID must be explicitly requested and registered – it will cause problems if the same OID is used for different purposes.) Only the root OID must be registed, sub-OIDs can be freely created.

A detailed discussion of the standard extensions is beyond the scope of this article. It will need to suffice that they can provide information about key usage (e.g., the cert should be used to encrypt email but not on a server), additional aliases for the certificate, or even restrictions on certificates signed by this certificate. (e.g., it must be for a server under the “example.com” domain).

Non-standard extensions could include internal userid, biometrics to further authenticate the user, photographs, etc.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button