mkimage: Add support for signing with pkcs11

Add support for signing with the pkcs11 engine. This allows FIT images
to be signed with keys securely stored on a smartcard, hardware security
module, etc without exposing the keys.

Support for other engines can be added in the future by modifying
rsa_engine_get_pub_key() and rsa_engine_get_priv_key() to construct
correct key_id strings.

Signed-off-by: George McCollister <george.mccollister@gmail.com>
This commit is contained in:
George McCollister 2017-01-06 13:14:17 -06:00 committed by Tom Rini
parent b1c6a54a53
commit f1ca1fdebf
7 changed files with 408 additions and 28 deletions

View File

@ -385,6 +385,149 @@ Test Verified Boot Run: signed config with bad hash: OK
Test passed
Hardware Signing with PKCS#11
-----------------------------
Securely managing private signing keys can challenging, especially when the
keys are stored on the file system of a computer that is connected to the
Internet. If an attacker is able to steal the key, they can sign malicious FIT
images which will appear genuine to your devices.
An alternative solution is to keep your signing key securely stored on hardware
device like a smartcard, USB token or Hardware Security Module (HSM) and have
them perform the signing. PKCS#11 is standard for interfacing with these crypto
device.
Requirements:
Smartcard/USB token/HSM which can work with the pkcs11 engine
openssl
libp11 (provides pkcs11 engine)
p11-kit (recommended to simplify setup)
opensc (for smartcards and smartcard like USB devices)
gnutls (recommended for key generation, p11tool)
The following examples use the Nitrokey Pro. Instructions for other devices may vary.
Notes on pkcs11 engine setup:
Make sure p11-kit, opensc are installed and that p11-kit is setup to use opensc.
/usr/share/p11-kit/modules/opensc.module should be present on your system.
Generating Keys On the Nitrokey:
$ gpg --card-edit
Reader ...........: Nitrokey Nitrokey Pro (xxxxxxxx0000000000000000) 00 00
Application ID ...: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Version ..........: 2.1
Manufacturer .....: ZeitControl
Serial number ....: xxxxxxxx
Name of cardholder: [not set]
Language prefs ...: de
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 32 32 32
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]
gpg/card> generate
Make off-card backup of encryption key? (Y/n) n
Please note that the factory settings of the PINs are
PIN = '123456' Admin PIN = '12345678'
You should change them using the command --change-pin
What keysize do you want for the Signature key? (2048) 4096
The card will now be re-configured to generate a key of 4096 bits
Note: There is no guarantee that the card supports the requested size.
If the key generation does not succeed, please check the
documentation of your card to see what sizes are allowed.
What keysize do you want for the Encryption key? (2048) 4096
The card will now be re-configured to generate a key of 4096 bits
What keysize do you want for the Authentication key? (2048) 4096
The card will now be re-configured to generate a key of 4096 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: John Doe
Email address: john.doe@email.com
Comment:
You selected this USER-ID:
"John Doe <john.doe@email.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
Using p11tool to get the token URL:
Depending on system configuration, gpg-agent may need to be killed first.
$ p11tool --provider /usr/lib/opensc-pkcs11.so --list-tokens
Token 0:
URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29
Label: OpenPGP card (User PIN (sig))
Type: Hardware token
Manufacturer: ZeitControl
Model: PKCS#15 emulated
Serial: 000xxxxxxxxx
Module: (null)
Token 1:
URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%29
Label: OpenPGP card (User PIN)
Type: Hardware token
Manufacturer: ZeitControl
Model: PKCS#15 emulated
Serial: 000xxxxxxxxx
Module: (null)
Use the portion of the signature token URL after "pkcs11:" as the keydir argument (-k) to mkimage below.
Use the URL of the token to list the private keys:
$ p11tool --login --provider /usr/lib/opensc-pkcs11.so --list-privkeys \
"pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29"
Token 'OpenPGP card (User PIN (sig))' with URL 'pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29' requires user PIN
Enter PIN:
Object 0:
URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29;id=%01;object=Signature%20key;type=private
Type: Private key
Label: Signature key
Flags: CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
ID: 01
Use the label, in this case "Signature key" as the key-name-hint in your FIT.
Create the fitImage:
$ ./tools/mkimage -f fit-image.its fitImage
Sign the fitImage with the hardware key:
$ ./tools/mkimage -F -k \
"model=PKCS%2315%20emulated;manufacturer=ZeitControl;serial=000xxxxxxxxx;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29" \
-K u-boot.dtb -N pkcs11 -r fitImage
Future Work
-----------
- Roll-back protection using a TPM is done using the tpm command. This can

View File

@ -965,6 +965,7 @@ int fit_set_timestamp(void *fit, int noffset, time_t timestamp);
* @fit: Pointer to the FIT format image header
* @comment: Comment to add to signature nodes
* @require_keys: Mark all keys as 'required'
* @engine_id: Engine to use for signing
*
* Adds hash values for all component images in the FIT blob.
* Hashes are calculated for all component images which have hash subnodes
@ -977,7 +978,8 @@ int fit_set_timestamp(void *fit, int noffset, time_t timestamp);
* libfdt error code, on failure
*/
int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
const char *comment, int require_keys);
const char *comment, int require_keys,
const char *engine_id);
int fit_image_verify(const void *fit, int noffset);
int fit_config_verify(const void *fit, int conf_noffset);
@ -1057,6 +1059,7 @@ struct image_sign_info {
const void *fdt_blob; /* FDT containing public keys */
int required_keynode; /* Node offset of key to use: -1=any */
const char *require_keys; /* Value for 'required' property */
const char *engine_id; /* Engine to use for signing */
};
#endif /* Allow struct image_region to always be defined for rsa.h */

View File

@ -14,6 +14,7 @@
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
#define HAVE_ERR_REMOVE_THREAD_STATE
@ -31,14 +32,14 @@ static int rsa_err(const char *msg)
}
/**
* rsa_get_pub_key() - read a public key from a .crt file
* rsa_pem_get_pub_key() - read a public key from a .crt file
*
* @keydir: Directory containins the key
* @name Name of key file (will have a .crt extension)
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_get_pub_key(const char *keydir, const char *name, RSA **rsap)
static int rsa_pem_get_pub_key(const char *keydir, const char *name, RSA **rsap)
{
char path[1024];
EVP_PKEY *key;
@ -96,14 +97,90 @@ err_cert:
}
/**
* rsa_get_priv_key() - read a private key from a .key file
* rsa_engine_get_pub_key() - read a public key from given engine
*
* @keydir: Directory containins the key
* @keydir: Key prefix
* @name Name of key
* @engine Engine to use
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_engine_get_pub_key(const char *keydir, const char *name,
ENGINE *engine, RSA **rsap)
{
const char *engine_id;
char key_id[1024];
EVP_PKEY *key;
RSA *rsa;
int ret;
*rsap = NULL;
engine_id = ENGINE_get_id(engine);
if (engine_id && !strcmp(engine_id, "pkcs11")) {
if (keydir)
snprintf(key_id, sizeof(key_id),
"pkcs11:%s;object=%s;type=public",
keydir, name);
else
snprintf(key_id, sizeof(key_id),
"pkcs11:object=%s;type=public",
name);
} else {
fprintf(stderr, "Engine not supported\n");
return -ENOTSUP;
}
key = ENGINE_load_public_key(engine, key_id, NULL, NULL);
if (!key)
return rsa_err("Failure loading public key from engine");
/* Convert to a RSA_style key. */
rsa = EVP_PKEY_get1_RSA(key);
if (!rsa) {
rsa_err("Couldn't convert to a RSA style key");
ret = -EINVAL;
goto err_rsa;
}
EVP_PKEY_free(key);
*rsap = rsa;
return 0;
err_rsa:
EVP_PKEY_free(key);
return ret;
}
/**
* rsa_get_pub_key() - read a public key
*
* @keydir: Directory containing the key (PEM file) or key prefix (engine)
* @name Name of key file (will have a .crt extension)
* @engine Engine to use
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_get_pub_key(const char *keydir, const char *name,
ENGINE *engine, RSA **rsap)
{
if (engine)
return rsa_engine_get_pub_key(keydir, name, engine, rsap);
return rsa_pem_get_pub_key(keydir, name, rsap);
}
/**
* rsa_pem_get_priv_key() - read a private key from a .key file
*
* @keydir: Directory containing the key
* @name Name of key file (will have a .key extension)
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_get_priv_key(const char *keydir, const char *name, RSA **rsap)
static int rsa_pem_get_priv_key(const char *keydir, const char *name,
RSA **rsap)
{
char path[1024];
RSA *rsa;
@ -130,6 +207,81 @@ static int rsa_get_priv_key(const char *keydir, const char *name, RSA **rsap)
return 0;
}
/**
* rsa_engine_get_priv_key() - read a private key from given engine
*
* @keydir: Key prefix
* @name Name of key
* @engine Engine to use
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_engine_get_priv_key(const char *keydir, const char *name,
ENGINE *engine, RSA **rsap)
{
const char *engine_id;
char key_id[1024];
EVP_PKEY *key;
RSA *rsa;
int ret;
*rsap = NULL;
engine_id = ENGINE_get_id(engine);
if (engine_id && !strcmp(engine_id, "pkcs11")) {
if (keydir)
snprintf(key_id, sizeof(key_id),
"pkcs11:%s;object=%s;type=private",
keydir, name);
else
snprintf(key_id, sizeof(key_id),
"pkcs11:object=%s;type=private",
name);
} else {
fprintf(stderr, "Engine not supported\n");
return -ENOTSUP;
}
key = ENGINE_load_private_key(engine, key_id, NULL, NULL);
if (!key)
return rsa_err("Failure loading private key from engine");
/* Convert to a RSA_style key. */
rsa = EVP_PKEY_get1_RSA(key);
if (!rsa) {
rsa_err("Couldn't convert to a RSA style key");
ret = -EINVAL;
goto err_rsa;
}
EVP_PKEY_free(key);
*rsap = rsa;
return 0;
err_rsa:
EVP_PKEY_free(key);
return ret;
}
/**
* rsa_get_priv_key() - read a private key
*
* @keydir: Directory containing the key (PEM file) or key prefix (engine)
* @name Name of key
* @engine Engine to use for signing
* @rsap Returns RSA object, or NULL on failure
* @return 0 if ok, -ve on error (in which case *rsap will be set to NULL)
*/
static int rsa_get_priv_key(const char *keydir, const char *name,
ENGINE *engine, RSA **rsap)
{
if (engine)
return rsa_engine_get_priv_key(keydir, name, engine, rsap);
return rsa_pem_get_priv_key(keydir, name, rsap);
}
static int rsa_init(void)
{
int ret;
@ -148,6 +300,45 @@ static int rsa_init(void)
return 0;
}
static int rsa_engine_init(const char *engine_id, ENGINE **pe)
{
ENGINE *e;
int ret;
ENGINE_load_builtin_engines();
e = ENGINE_by_id(engine_id);
if (!e) {
fprintf(stderr, "Engine isn't available\n");
ret = -1;
goto err_engine_by_id;
}
if (!ENGINE_init(e)) {
fprintf(stderr, "Couldn't initialize engine\n");
ret = -1;
goto err_engine_init;
}
if (!ENGINE_set_default_RSA(e)) {
fprintf(stderr, "Couldn't set engine as default for RSA\n");
ret = -1;
goto err_set_rsa;
}
*pe = e;
return 0;
err_set_rsa:
ENGINE_finish(e);
err_engine_init:
ENGINE_free(e);
err_engine_by_id:
ENGINE_cleanup();
return ret;
}
static void rsa_remove(void)
{
CRYPTO_cleanup_all_ex_data();
@ -160,6 +351,14 @@ static void rsa_remove(void)
EVP_cleanup();
}
static void rsa_engine_remove(ENGINE *e)
{
if (e) {
ENGINE_finish(e);
ENGINE_free(e);
}
}
static int rsa_sign_with_key(RSA *rsa, struct checksum_algo *checksum_algo,
const struct image_region region[], int region_count,
uint8_t **sigp, uint *sig_size)
@ -235,13 +434,20 @@ int rsa_sign(struct image_sign_info *info,
uint8_t **sigp, uint *sig_len)
{
RSA *rsa;
ENGINE *e = NULL;
int ret;
ret = rsa_init();
if (ret)
return ret;
ret = rsa_get_priv_key(info->keydir, info->keyname, &rsa);
if (info->engine_id) {
ret = rsa_engine_init(info->engine_id, &e);
if (ret)
goto err_engine;
}
ret = rsa_get_priv_key(info->keydir, info->keyname, e, &rsa);
if (ret)
goto err_priv;
ret = rsa_sign_with_key(rsa, info->checksum, region,
@ -250,6 +456,8 @@ int rsa_sign(struct image_sign_info *info,
goto err_sign;
RSA_free(rsa);
if (info->engine_id)
rsa_engine_remove(e);
rsa_remove();
return ret;
@ -257,6 +465,9 @@ int rsa_sign(struct image_sign_info *info,
err_sign:
RSA_free(rsa);
err_priv:
if (info->engine_id)
rsa_engine_remove(e);
err_engine:
rsa_remove();
return ret;
}
@ -446,14 +657,20 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest)
int ret;
int bits;
RSA *rsa;
ENGINE *e = NULL;
debug("%s: Getting verification data\n", __func__);
ret = rsa_get_pub_key(info->keydir, info->keyname, &rsa);
if (info->engine_id) {
ret = rsa_engine_init(info->engine_id, &e);
if (ret)
return ret;
}
ret = rsa_get_pub_key(info->keydir, info->keyname, e, &rsa);
if (ret)
return ret;
goto err_get_pub_key;
ret = rsa_get_params(rsa, &exponent, &n0_inv, &modulus, &r_squared);
if (ret)
return ret;
goto err_get_params;
bits = BN_num_bits(modulus);
parent = fdt_subnode_offset(keydest, 0, FIT_SIG_NODENAME);
if (parent == -FDT_ERR_NOTFOUND) {
@ -518,7 +735,12 @@ done:
BN_free(modulus);
BN_free(r_squared);
if (ret)
return ret == -FDT_ERR_NOSPACE ? -ENOSPC : -EIO;
ret = ret == -FDT_ERR_NOSPACE ? -ENOSPC : -EIO;
err_get_params:
RSA_free(rsa);
err_get_pub_key:
if (info->engine_id)
rsa_engine_remove(e);
return 0;
return ret;
}

View File

@ -59,7 +59,8 @@ static int fit_add_file_data(struct image_tool_params *params, size_t size_inc,
if (!ret) {
ret = fit_add_verification_data(params->keydir, dest_blob, ptr,
params->comment,
params->require_keys);
params->require_keys,
params->engine_id);
}
if (dest_blob) {

View File

@ -149,7 +149,7 @@ static int fit_image_write_sig(void *fit, int noffset, uint8_t *value,
static int fit_image_setup_sig(struct image_sign_info *info,
const char *keydir, void *fit, const char *image_name,
int noffset, const char *require_keys)
int noffset, const char *require_keys, const char *engine_id)
{
const char *node_name;
char *algo_name;
@ -170,6 +170,7 @@ static int fit_image_setup_sig(struct image_sign_info *info,
info->checksum = image_get_checksum_algo(algo_name);
info->crypto = image_get_crypto_algo(algo_name);
info->require_keys = require_keys;
info->engine_id = engine_id;
if (!info->checksum || !info->crypto) {
printf("Unsupported signature algorithm (%s) for '%s' signature node in '%s' image node\n",
algo_name, node_name, image_name);
@ -194,12 +195,13 @@ static int fit_image_setup_sig(struct image_sign_info *info,
* @size: size of data in bytes
* @comment: Comment to add to signature nodes
* @require_keys: Mark all keys as 'required'
* @engine_id: Engine to use for signing
* @return 0 if ok, -1 on error
*/
static int fit_image_process_sig(const char *keydir, void *keydest,
void *fit, const char *image_name,
int noffset, const void *data, size_t size,
const char *comment, int require_keys)
const char *comment, int require_keys, const char *engine_id)
{
struct image_sign_info info;
struct image_region region;
@ -209,7 +211,7 @@ static int fit_image_process_sig(const char *keydir, void *keydest,
int ret;
if (fit_image_setup_sig(&info, keydir, fit, image_name, noffset,
require_keys ? "image" : NULL))
require_keys ? "image" : NULL, engine_id))
return -1;
node_name = fit_get_name(fit, noffset, NULL);
@ -288,11 +290,12 @@ static int fit_image_process_sig(const char *keydir, void *keydest,
* @image_noffset: Requested component image node
* @comment: Comment to add to signature nodes
* @require_keys: Mark all keys as 'required'
* @engine_id: Engine to use for signing
* @return: 0 on success, <0 on failure
*/
int fit_image_add_verification_data(const char *keydir, void *keydest,
void *fit, int image_noffset, const char *comment,
int require_keys)
int require_keys, const char *engine_id)
{
const char *image_name;
const void *data;
@ -329,7 +332,7 @@ int fit_image_add_verification_data(const char *keydir, void *keydest,
strlen(FIT_SIG_NODENAME))) {
ret = fit_image_process_sig(keydir, keydest,
fit, image_name, noffset, data, size,
comment, require_keys);
comment, require_keys, engine_id);
}
if (ret)
return ret;
@ -569,7 +572,8 @@ static int fit_config_get_data(void *fit, int conf_noffset, int noffset,
static int fit_config_process_sig(const char *keydir, void *keydest,
void *fit, const char *conf_name, int conf_noffset,
int noffset, const char *comment, int require_keys)
int noffset, const char *comment, int require_keys,
const char *engine_id)
{
struct image_sign_info info;
const char *node_name;
@ -587,7 +591,7 @@ static int fit_config_process_sig(const char *keydir, void *keydest,
return -1;
if (fit_image_setup_sig(&info, keydir, fit, conf_name, noffset,
require_keys ? "conf" : NULL))
require_keys ? "conf" : NULL, engine_id))
return -1;
ret = info.crypto->sign(&info, region, region_count, &value,
@ -635,7 +639,7 @@ static int fit_config_process_sig(const char *keydir, void *keydest,
static int fit_config_add_verification_data(const char *keydir, void *keydest,
void *fit, int conf_noffset, const char *comment,
int require_keys)
int require_keys, const char *engine_id)
{
const char *conf_name;
int noffset;
@ -654,7 +658,7 @@ static int fit_config_add_verification_data(const char *keydir, void *keydest,
strlen(FIT_SIG_NODENAME))) {
ret = fit_config_process_sig(keydir, keydest,
fit, conf_name, conf_noffset, noffset, comment,
require_keys);
require_keys, engine_id);
}
if (ret)
return ret;
@ -664,7 +668,8 @@ static int fit_config_add_verification_data(const char *keydir, void *keydest,
}
int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
const char *comment, int require_keys)
const char *comment, int require_keys,
const char *engine_id)
{
int images_noffset, confs_noffset;
int noffset;
@ -687,7 +692,7 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
* i.e. component image node.
*/
ret = fit_image_add_verification_data(keydir, keydest,
fit, noffset, comment, require_keys);
fit, noffset, comment, require_keys, engine_id);
if (ret)
return ret;
}
@ -710,7 +715,8 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit,
noffset = fdt_next_subnode(fit, noffset)) {
ret = fit_config_add_verification_data(keydir, keydest,
fit, noffset, comment,
require_keys);
require_keys,
engine_id);
if (ret)
return ret;
}

View File

@ -76,6 +76,7 @@ struct image_tool_params {
bool external_data; /* Store data outside the FIT */
bool quiet; /* Don't output text in normal operation */
unsigned int external_offset; /* Add padding to external data */
const char *engine_id; /* Engine to use for signing */
};
/*

View File

@ -98,14 +98,15 @@ static void usage(const char *msg)
" -i => input filename for ramdisk file\n");
#ifdef CONFIG_FIT_SIGNATURE
fprintf(stderr,
"Signing / verified boot options: [-E] [-k keydir] [-K dtb] [ -c <comment>] [-p addr] [-r]\n"
"Signing / verified boot options: [-E] [-k keydir] [-K dtb] [ -c <comment>] [-p addr] [-r] [-N engine]\n"
" -E => place data outside of the FIT structure\n"
" -k => set directory containing private keys\n"
" -K => write public keys to this .dtb file\n"
" -c => add comment in signature node\n"
" -F => re-sign existing FIT image\n"
" -p => place external data at a static position\n"
" -r => mark keys used as 'required' in dtb\n");
" -r => mark keys used as 'required' in dtb\n"
" -N => engine to use for signing (pkcs11)\n");
#else
fprintf(stderr,
"Signing / verified boot not supported (CONFIG_FIT_SIGNATURE undefined)\n");
@ -143,7 +144,7 @@ static void process_args(int argc, char **argv)
int opt;
while ((opt = getopt(argc, argv,
"a:A:b:c:C:d:D:e:Ef:Fk:i:K:ln:p:O:rR:qsT:vVx")) != -1) {
"a:A:b:c:C:d:D:e:Ef:Fk:i:K:ln:N:p:O:rR:qsT:vVx")) != -1) {
switch (opt) {
case 'a':
params.addr = strtoull(optarg, &ptr, 16);
@ -224,6 +225,9 @@ static void process_args(int argc, char **argv)
case 'n':
params.imagename = optarg;
break;
case 'N':
params.engine_id = optarg;
break;
case 'O':
params.os = genimg_get_os_id(optarg);
if (params.os < 0) {