Hardware Security Modules and Smart Cards

Hardware Security Modules (HSMs) are specialized hardware intended to protect private keys on server systems. They store internally the private keys (e.g., RSA keys), and provide access to operations with the keys without exposing the keys. That access, is provided using a standardized API, which across Fedora is PKCS#11.

Smart cards are small cards with a micro processor, often combined with a USB reader resembling a USB stick. They are very similar in nature with HSMs as they can also be used to protect private keys and are almost universally accessed via the PKCS#11 API. The main distinguishers from HSMs is their inferior performance and often, the available hardware protection mechanisms.

Typically a smart card or HSM relies on a shared library to provide functionality. This shared library follows the PKCS#11 API and thus is often referred to as a PKCS#11 module. In Fedora the opensc shared module (opensc-pkcs11.so) can be used for the majority of smart cards available in the market. By convention these modules are located at /usr/lib64/pkcs11. They can be used directly, or via a higher level library.

All the major crypto libraries (NSS, GnuTLS and OpenSSL in Fedora) support hardware security modules and smart cards, by providing wrappers over the PKCS#11 API. However, the level of support varies, as well as the ease of use of such modules and its integration to the overall library API.

  • The PKCS#11 API does provide an API to access HSMs or smart cards, but does not provide any method of discovering which HSMs or smart cards are available in the system. In Fedora and modules are registered via p11-kit configuration files, stored at /etc/pkcs11/modules/. For applications using engine_pkcs11 or GnuTLS the registered modules are available without further configuration. Other applications will have to load the p11-kit-proxy.so module.

  • Most crypto libraries support the PKCS#11 URLs scheme to identify objects stored in an HSM, however that support is not yet universal. Some support transparent usage of PKCS#11 objects, e.g., specifying a PKCS#11 object instead of a file, while others require to use specialized APIs for such objects.

  • Objects stored in an HSM or smart card can be protected with a PIN. As such, libraries typically require to set a PIN handling function for accessing private keys, or the PIN can be passed along with a PKCS#11 URL and the pin-value parameter.

  • Obtaining a Hardware Security Module, or including it on a continuous integration testing is not always feasible. For testing purposes smart cards supported by the OpenSC project can be used, as well as software modules like softhsm which provides a tool to setup a software HSM, and a PKCS#11 library.

  • The PKCS#11 API requires applications that use fork to reinitialize the used PKCS#11 modules. This is an uncommon requirement, which has led to several bugs across applications in Fedora which used PKCS#11 directly. To make things more complicated software PKCS#11 module like softhsm do not require this re-initialization leading to applications working against software modules but failing with hardware modules or smart cards. The wrapper PKCS#11 APIs provided by NSS, GnuTLS and engine_pkcs11 (OpenSSL) handle the reinitialization after fork requirement transparently.

OpenSSL HSM Support

OpenSSL does not have native support for PKCS#11. It can provide PKCS#11 support through the OpenSC’s project pkcs11 engine (formerly known as engine_pkcs11). As such software intended to use HSMs, must utilize that engine.

Engine pkcs11 supports loading stored objects via PKCS#11 URLs. If no PKCS#11 module is specified the engine will use the system-wide registered modules via p11-kit-proxy.so.

The following example demonstrates the initialization of the pkcs11 engine and its usage to sign data.

Example 1. Signing data with HSM and OpenSSL
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
ERR_clear_error();
ENGINE_load_builtin_engines();

e = ENGINE_by_id("pkcs11");
if (!e) {
  display_openssl_errors(__LINE__);
  exit(1);
}

if (module_path) {
  fprintf(stderr, "loading: %s\n", module_path);
  if (!ENGINE_ctrl_cmd_string(e, "MODULE_PATH", module_path, 0)) {
    display_openssl_errors(__LINE__);
    exit(1);
  }
}

if (!ENGINE_init(e)) {
  display_openssl_errors(__LINE__);
  exit(1);
}

if (key_pass && !ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0)) {
  display_openssl_errors(__LINE__);
  exit(1);
}

private_key = ENGINE_load_private_key(e, private_key_name, NULL, NULL);
if (!private_key) {
  fprintf(stderr, "cannot load: %s\n", private_key_name);
  display_openssl_errors(__LINE__);
  exit(1);
}

display_openssl_errors(__LINE__);

digest_algo = EVP_get_digestbyname("sha256");

EVP_MD_CTX_init(&ctx);
if (EVP_DigestInit(&ctx, digest_algo) <= 0) {
  display_openssl_errors(__LINE__);
  exit(1);
}

EVP_SignInit(&ctx, digest_algo);

#define TEST_DATA "test data"
if (EVP_SignUpdate(&ctx, TEST_DATA, sizeof(TEST_DATA) - 1) <= 0) {
  display_openssl_errors(__LINE__);
  exit(1);
}

n = sizeof(buf);
if (EVP_SignFinal(&ctx, buf, &n, private_key) <= 0) {
  display_openssl_errors(__LINE__);
  exit(1);
}

EVP_PKEY_free(private_key);
ENGINE_finish(e);

GnuTLS HSM Support

GnuTLS supports PKCS#11 natively. Most of the API functions accepting certificate files, can also accept PKCS#11 URLs, thus requiring minor or no modifications to applications in order to support HSMs. In most cases applications must be modified to install a PIN callback function.

The following example demonstrates the initialization of the pkcs11 engine and its usage to sign data.

Example 2. Signing data with HSM and GnuTLS
if (module_path) {
  ret = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
  if (ret < 0) {
    fprintf(stderr, "error in %d: %s\n", __LINE__, gnutls_strerror(ret));
    exit(1);
  }

  ret = gnutls_pkcs11_add_provider(module_path, NULL);
  if (ret < 0) {
    fprintf(stderr, "error in %d: %s\n", __LINE__, gnutls_strerror(ret));
    exit(1);
  }
}

if (key_pass)
  gnutls_pkcs11_set_pin_function(pin_function, key_pass);

ret = gnutls_privkey_init(&private_key);
if (ret < 0) {
  fprintf(stderr, "error in %d: %s\n", __LINE__, gnutls_strerror(ret));
  exit(1);
}

ret = gnutls_privkey_import_url(private_key, private_key_name, 0);
if (ret < 0) {
  fprintf(stderr, "error in %d: %s\n", __LINE__, gnutls_strerror(ret));
  exit(1);
}

ret = gnutls_privkey_sign_data(private_key, GNUTLS_DIG_SHA256, 0,
                               &testdata, &signature);
if (ret < 0) {
  fprintf(stderr, "error in %d: %s\n", __LINE__, gnutls_strerror(ret));
  exit(1);
}

gnutls_privkey_deinit(private_key);
gnutls_free(signature.data);

The PIN callback function can be either set globally as in the example above or locally by utilizing functions such as gnutls_privkey_set_pin_function. An example PIN callback function is shown below.

Example 3. An example PIN callback with GNUTLS
int pin_function(void *userdata, int attempt, const char *token_url,
                 const char *token_label, unsigned flags, char *pin, size_t pin_max)
{
  if (flags & GNUTLS_PIN_FINAL_TRY)
    printf("This is the final try before locking!\n");
  if (flags & GNUTLS_PIN_COUNT_LOW)
    printf("Only few tries left before locking!\n");
  if (flags & GNUTLS_PIN_WRONG)
    printf("Wrong PIN has been provided in the previous attempt\n");

  /* userdata is the second value passed to gnutls_pkcs11_set_pin_function()
   * in this example we passed the PIN as a null terminated value.
   */
  snprintf(pin, pin_max, "%s", (char*)userdata);
  return 0;
}

NSS HSM Support

NSS supports PKCS#11 natively. In fact all NSS crypto operations, including built-in operations, go through PKCS #11 modules. NSS provides its own software PKCS #11 module called softoken. NSS automatically loads any PKCS #11 module specified in its module database, which can be manipulated with the modutil command. NSS uses the PKCS #11 module that contains the requested keys to do the crypto operations. As long as the application opens an NSS database and properly sets a pin callback. If it runs with native NSS, it should be able to use HSMs that provide PKCS #11 modules. Modules can also be loaded programmatically, though this is less common.

The following example demonstrates a typical NSS application for signing.

Example 4. Signing data with HSM and NSS
SECStatus rv;
CERTCertificate *cert = NULL;
SECKEYPrivateKey *pvtkey = NULL;
SECItem signature = { siBuffer, NULL, 0 };
SECOidTag algTag;
int r = 1;
unsigned char buf[] = "test data to sign";
const char *cert_name;
unsigned i;

if (argc < 3) {
  fprintf(stderr, "usage: %s [cert name] [PIN]\n\n", argv[0]);
  exit(1);
}

cert_name = argv[1];
pin = argv[2];

PK11_SetPasswordFunc(passwdcb);
NSS_InitializePRErrorTable();
rv = NSS_Init(".");
if (rv != SECSuccess) {
  fprintf(stderr, "NSS initialization failed (err %d)\n", PR_GetError());
  goto cleanup;
}

cert = PK11_FindCertFromNickname(cert_name, NULL);
if (cert == NULL) {
  fprintf(stderr, "Couldn't find cert %s in NSS db (err %d: %s)\n",
	    cert_name, PR_GetError(), PORT_ErrorToString(PR_GetError()));
  goto cleanup;
}

fprintf(stderr, "Buffer being signed = \n%s\n", buf);

pvtkey = PK11_FindKeyByAnyCert(cert, NULL);
if (pvtkey == NULL) {
  fprintf(stderr, "Couldn't find private key for cert %s (err %d: %s)\n",
	    cert_name, PR_GetError(), PORT_ErrorToString(PR_GetError()));
  goto cleanup;
}

/* get the algtag. Pick the default hash algorithm */
algTag = SEC_GetSignatureAlgorithmOidTag(pvtkey->keyType, SEC_OID_UNKNOWN);

fprintf(stderr, "Signing with alg = %s (%d)\n",
	  SECOID_FindOIDTagDescription(algTag), algTag);

rv = SEC_SignData(&signature, buf, sizeof(buf)-1, pvtkey, algTag);
if (rv != SECSuccess) {
  fprintf(stderr, "sign with Private Key failed (err %d: %s)\n",
	    PR_GetError(), PORT_ErrorToString(PR_GetError()));
  goto cleanup;
}

To use the example above with an HSM or smart card you will need to do the following.

# add your HSM or token library to an NSS database (in the sample code the database is
# located in the current directory'.')
$ modutil -add "My HSM" -libfile ${path_to_pkcs11_file} -dbdir .
# Find the token name on your HSM
$ modutil -list -dbdir .
# find the cert on your token
$ certutil -L -h ${token_name} -d .
# pass the cert to your signing program
$ NSS_Sign_Example "${token_name}:${cert_name}"
Example 5. An example PIN callback with NSS
char *passwdcb(PK11SlotInfo * slot, PRBool retry, void *arg)
{
  if (!isatty(STDIN_FILENO) && retry) {
    /* we're just reading from a file, and the value is known to be wrong,
     * don't keep bounding the token with the wrong password. */
    return NULL;
  }

  if (retry) {
    printf("Warning: Wrong PIN has been provided in the previous attempt\n");
    if (PK11_IsHW(slot)) {
      printf
	  ("  NOTE: multiple pin failures could result in locking your device\n");
    }
  }

  if (pin == NULL)
    return pin;
  else
    return strdup(pin);
}