Certificate authority module in CzechIdM, part2 – the execution engine

In the first part of our series, we discussed important features the company’s certificate authority should have. In today’s post, we will present the CAW, our execution engine. Read on.

The CAW engine is our implementation of the CA atop the OpenSSL certificate authority engine. Actually, there already exist many CA implementations but none of them really suited our needs.

A little research

At the end of the previous post we did put down the functional and non-functional requirements for the certificate authority (CA) implementation. First, let’s take a look why we did not use some already existing solution.

CFSSL / BoulderCA

The CFSSL is a certificate authority written in Go language which looks pretty good at the first sight, it even has RESTful API. But it is unusable for us because:

  • Does not store private keys. This is perfectly understandable, but it does not fit our previously-stated requirements. We would have to modify application code. Which brings us to…
  • Poor documentation. The docs are more about how to use the API, nothing about the code itself. Some things even work in a bit funny way: Your private key uses 4096 bits? Good, here, have SHA-512 signature on your certificate. Your private key uses 2048 bits? Well, you have to do with SHA-256. There is so way to influence this behavior. After a while looking around the code, we found there this:
    case *rsa.PublicKey:
    keySize := pub.N.BitLen()
    switch {
      case keySize >= 4096:
        return x509.SHA512WithRSA
      case keySize >= 3072:
        return x509.SHA384WithRSA
      case keySize >= 2048:
        return x509.SHA256WithRSA
      default:
        return x509.SHA1WithRSA
    }
  • CFSSL should have the PKCS#11 support which was, unfortunately, dropped few versions before we did our research. It is still possible to merge it over from the Boulder repository. But without a programmers doc, we doubt this should be a foundation for production-ready CA.

All this boils down to an image of a software we would not be able to actively maintain, if we ever decided to build some infrastructure above it. And that would be a suicide. So – no for CFSSL.

Similar case is the Boulder CA, which is a fork of CFSSL. This CA actually has support for PKCS#11 but its API is tailored for the use of Let’sEncrypt certificate authority, its usage and processes. Major modifications would thus be needed. So – no for Boulder.

OpenCA

OpenCA seems to have good-written documentation (although some parts are missing). But its webpage looks like it wasn’t update for a while. Wiki is returning HTTP/500, the last release of the core is from 2013 (although the OCSP responder has last release date of 2015). That does not look like actively maintained project. There ain’t no time for necromancy – no for OpenCA.

FreeIPA (DogTag)

FreeIPA is a software stack basically aiming to become the Active Directory of the Linux world. It is actively developed, documented and is backed by the RedHat guys. This looked good so far, so the idea was to use just the DogTag – the CA part of it.

But there, user cannot generate his/hers own certificate at one click. The certificate signing part builds on the user/administrator supplying the CSR. Not usable for the ordinary user.

The complexity of the deployment would probably not be worth it either. The stack itself constitutes of many services (LDAP, HTTPd, DogTag, BIND, …) that are authenticated to each other by certificates and Kerberos keytabs. They are also tightly coupled and managed through the ipa tool.

When installing FreeIPA, it generates its own certificate authority (via DogTag) and issues plenty of certificates with given names. We are sure there is a way to make it work (say by using standalone DogTag instance), but that would probably cost a lot of time to do it. One (a bit undecisive) no for FreeIPA and DogTag.

EasyRSA

The EasyRSA is a simple CA written as a BASH script and is actively maintained as a part of the OpenVPN project. It internally uses OpenSSL executable.

Actually, EasyRSA is closest to our idea of how our certificate authority should function. There are few things missing: a PKCS#11 support (but actually the older pkitool supported it), searching through the certificates, input parameters (EasyRSA is made to work out of the command line so you have to specify files with certificates, etc.). We, for example, would need to specify only certificate’s serial number. This would mean changing the original code in many places, that we would end up with our custom (and no longer compatible) fork.

Having a need for simple, quickly usable, CA, the EasyRSA is a way to go. We did not choose it only because of the need for code changes. Their scale would be so big that the boon of using existing CA code would be small.

We have a winner (sort-of)

We did not choose the EasyRSA, but the idea of it remained. Thus we decided to write custom implementation of Certificate Authority Wrapper – hence, CAW. In the core of the CAW is an OpenSSL certificate authority which does the crypto work. CAW deals with three main (and many small) problems:

  • You cannot specify everything in the openssl.cnf and some parameters just do not work if not specified on the command line (and in correct order).
  • We need to store intermediary products of certificate generation – private keys and certificate requests. OpenSSL does not do that for us.
  • When validating SubjectDN, openssl also considers data types in CSRs. Using policy_match is therefore impossible, because “Czech Republic” in UTF-8 is not the same as “Czech Republic” in PrintableString. Used data type depends on the application, which we used for creating the CSR so the authority cannot influence it. That’s really not going to work in heterogenous infrastructure.

Implementing CAW

Caw is simply a BASH script accompanied by two configuration files and a few folders. Everything is preconfigured in the way that duplicating/migrating/installing the certificate authority means merely copying the folder to another location and changing file ownership.

When installing the CAW, it is necessary to set starting serial number for certificates and to create the CA certificate itself. CAW already contains template for PKCS#11 token, the administrator needs only to test his/hers token and to give proper paths to dynamic libraries and such. All instructions are also contained in the configuration files.

The CAW can be used from the command line and also from the special application module. The application module handles conversion from the RESTful API requests to the CAW invocations and back. The command line interface can be used by administrator directly (the similar way as in EasyRSA) but is also meant as a first-choice tool when issues arise.

CAW has two configuration files – ca_openssl.cnf which holds most of the CA configuration, and caw_settings.source with the wrapper’s configuration itself. Main goal of the wrapper is to shield the others from the specialities of the openssl invocation.

The policy_match

In the openssl configuration file, we can define policies for handling SubjectDN components. Those components are then evaluated against the CA certificate. One of the policies is policy_match which forces the CSR to have the same values in the SubjectDN (ST,C,OU,…) as the CA certificate. The default policy_match:

[ policy_match ]
countryName = match # Must be the same as the CA
stateOrProvinceName = match # Must be the same as the CA
localityName = match # Must be the same as the CA
organizationName = match # Must be the same as the CA
organizationalUnitName = optional # not required
commonName = supplied
emailAddress = optional

Openssl, however, does not do the text matching, it also considers datatypes. You can therefore have two lookalike text strings that, according to the openssl binary, do not match – because they have different data type representation in the CSR/CRT. The only way to find out is to use openssl asn1parse:

#this is a user's CSR
[root@ca ~]# openssl asn1parse -in user.csr | grep -A1 stateOrProvinceName
...
 249:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
 254:d=5 hl=2 l= 14 prim: PRINTABLESTRING :Czech Republic
...

# this is a CA certificate
[root@ca ~]# openssl asn1parse -in ca.crt | grep -A1 stateOrProvinceName
...
 67:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
 72:d=5 hl=2 l= 14 prim: UTF8STRING :Czech Republic
...

# When using policy_match, you will not be able to sign 
# the CSR although the value of the fields is equal and makes sense.

The way to make such CSR work with openssl is to loose the policy – use something like optional (meaning the field does not even need to be here) or supplied (field is taken from CSR without checking). As is easy to see, there is no way to make that work in practice with some form of validation.

CAW solves this by not enforcing check on the level of openssl but does it itself. The administrator can define a regular expression that does the validation:

[root@ca caw]# cat caw_settings.source
...
# Those regexes validate the subject dn components either in "create all"
# or in "sign CSR". Validated string is constructed as follows:
# "${VALIDATED_STRING_PREFIX}${string_to_be_checked}"
# Check is done by grep and regex is as follows (note added ^ and $):
# "^${VALIDATED_STRING_PREFIX}${regex_template}$" .
VALIDATED_STRING_PREFIX="x"
# regex templates
COUNTRY_VALIDATION_REGEX="CZ"
STATE_VALIDATION_REGEX="Czech Republic"
LOCALITY_VALIDATION_REGEX="Prague"
ORG_VALIDATION_REGEX="BCV"
OU_VALIDATION_REGEX=".*"
CN_VALIDATION_REGEX=".*[a-zA-Z0-9]+.*"
MAIL_VALIDATION_REGEX=".*"
PASS_VALIDATION_REGEX=".*[a-zA-Z0-9]+.*"
...

Storing private keys and certificate requests

Storing clients’ private keys is never a good idea. But for the sake of user’s comfort, the CAW does exactly that. When asking for a key and a certificate, however, the user has to specify a passphrase. After the key and CSR is generated, the key is immediatelly encrypted with the passphrase and then stored in the authority. This ensures that nobody except the legitimate user can use it. The same way as ve validate SubjectDN, we can specify basic requirements for the passphrase.

Legitimate user can then, provided he knows the passphrase, download the certificate as many times as he wants. He can also request the certificate prolongation without supplying any additional information about himself – using CSR, the authority can issue a new version of the certificate using the same private key. As long as the user is validated in the Registration Authority (RA) and the to-be-prolonged certificate is valid, the request is granted.

Working with crypto devices

The CAW has built-in support for the PKCS#11 openssl engines. All you have to do is to configure your crypto token into CAW – that is the easy part. You also have to test it against the openssl itself – that is the hard part.

Main problem with using crypto devices is with their integration with OpenSSL and there things can get very weird. Consider this certificate signing done by-hand on openSUSE 42.1 (using SoftHSM token). The first signing with SHA-256 fails. The second signing (with SHA-1 which is the default) succeeds. The third signing (again with SHA-256) now succeeds as well.

OpenSSL> engine -t dynamic -pre SO_PATH:/usr/lib64/engines/engine_pkcs11.so -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:/usr/lib64/pkcs11/libsofthsm2.so
(dynamic) Dynamic engine loading support
[Success]: SO_PATH:/usr/lib64/engines/engine_pkcs11.so
[Success]: ID:pkcs11
[Success]: LIST_ADD:1
[Success]: LOAD
[Success]: MODULE_PATH:/usr/lib64/pkcs11/libsofthsm2.so
Loaded: (pkcs11) pkcs11 engine
 [ available ]
OpenSSL> x509 -req -engine pkcs11 -CAkeyform engine -in user.csr -out user.crt -days 100 -CA ca/ca.crt -CAkey id_A1B2 -set_serial 5 -sha256
# here openssl complains that -sha256 is an unknown option

OpenSSL> x509 -req -engine pkcs11 -CAkeyform engine -in user.csr -out user.crt -days 100 -CA ca/ca.crt -CAkey id_A1B2 -set_serial 5
# there is a success message - certificate with SHA-1 signature was issued correctly

OpenSSL> x509 -req -engine pkcs11 -CAkeyform engine -in user.csr -out user.crt -days 100 -CA ca/ca.crt -CAkey id_A1B2 -set_serial 5 -sha256
# there is a success message - certificate with SHA-256 signature was now also issued correctly

The funny thing there is that when we packed (with tar) the whole authority and moved it to CentOS, everything worked flawlessly. This, unfortunately, means that some crypto tokens may work and some of them may not and that also it is OS / openssl dependent.

CAW user interface

The CAW is made to be simply usable for both administrators and client applications. It is a command line utility which respects STDOUT and STDERR outputs, return codes and other things the one would expect from the *nix application. All user input is given in the form of command line parameters. The API is documented directly in the usage banner:

[root@ca caw]# ./caw [root@ca caw]# ./caw Unknown command '' specified.Usage: ./caw command [--param1 value1 --param2 value2 ...]
...
COMMAND create-key-and-cert - generates new private key, CSR and signs a certificateOUTPUT
    Success: Serial number of the issued certificate written onto STDOUT. Return code 0.
    Error: Error message on STDERR. Return code 1.
PARAMETERS
--country countryName. Mandatory.
--state stateOrProvinceName. Mandatory.
--locality localityName. Mandatory.
--org organizationName. Mandatory.
--ou organizationalUnitName. Mandatory.
--cn commonName. Mandatory.
--mail emailAddress. Mandatory.
--pass private key passphrase. Mandatory.

COMMAND sign-csr - signs supplied CSR, CSR has to be PEM-encodedOUTPUT
    Success: Serial number of the issued certificate written onto STDOUT. Return code 0.
    Error: Error message on STDERR. Return code 1.
...

Example of use

Adding to the API description, we briefly show how to use the CAW. We show how to create private key and certificate, download it, how to revoke certificate and refresh the CRL.

Create private key and certificate (CAW returns a serial number of the new certificate):

[root@ca ~]# ./caw create-key-and-cert --country CZ --state "Czech Republic" --locality Prague --org BCV --ou TEST --cn user.test.bcv --pass *****
0C0774BACDF2CA2A52BEEF68A0F1D411

Download (private key,certificate,certificate chain) bundle from the CA:

[root@ca ~]# ./caw get-cert --serial 0C0774BACDF2CA2A52BEEF68A0F1D411 --with-pkey --pass ***** --with-chain
MIIKoQIBAzCCCmcGCSqGSIb3DQEHAaCCClgEggpUMIIKUDCCBQcGCSqGSIb3DQEHBqC
...
FbAM6nS5jJYQ4s4VKDElMCMGCSqGSIb3DQEJFTEWBBRGj5/LUBZtcz/k+N96L7RzdleanDAxMCEwCQYFKw4DAhoFAAQUCqImx0Un2qmtSACpEWD4i2ivunMECFJnEuzDIEtHAgIIAA==

Revoke a certificate:

[root@ca ~]# ./caw revoke-cert --serial 0C0774BACDF2CA2A52BEEF68A0F1D411 --reason keyCompromise

Refresh the CRL

[root@ca ~]# ./caw create-crl

Conclusion

The CAW shields users and administrators from most nuances of the advanced openssl invocation. The only situation where it cannot do much is testing the crypto tokens which the administrator has to do himself.

Next time we will look into the certificate authority module and its realization. If you have any questions, do not hesitate to ask – send me an email to info@bcvsolutions.eu .

Leave a Reply