From f1ca1fdebf1cde1c37c91b3d85f8b7af111112ea Mon Sep 17 00:00:00 2001 From: George McCollister Date: Fri, 6 Jan 2017 13:14:17 -0600 Subject: 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 diff --git a/doc/uImage.FIT/signature.txt b/doc/uImage.FIT/signature.txt index e487401..7cdb7bf 100644 --- a/doc/uImage.FIT/signature.txt +++ b/doc/uImage.FIT/signature.txt @@ -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 + = key expires in n days + w = key expires in n weeks + m = key expires in n months + 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 " + +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 diff --git a/include/image.h b/include/image.h index 0537678..6207d62 100644 --- a/include/image.h +++ b/include/image.h @@ -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 */ diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c index 9a09280..8c6637e 100644 --- a/lib/rsa/rsa-sign.c +++ b/lib/rsa/rsa-sign.c @@ -14,6 +14,7 @@ #include #include #include +#include #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; } diff --git a/tools/fit_image.c b/tools/fit_image.c index efd8a97..4dc8bd8 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -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) { diff --git a/tools/image-host.c b/tools/image-host.c index c1a0122..5e4d690 100644 --- a/tools/image-host.c +++ b/tools/image-host.c @@ -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; } diff --git a/tools/imagetool.h b/tools/imagetool.h index 15c2a0c..a8d5054 100644 --- a/tools/imagetool.h +++ b/tools/imagetool.h @@ -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 */ }; /* diff --git a/tools/mkimage.c b/tools/mkimage.c index f48135f..b0c98f6 100644 --- a/tools/mkimage.c +++ b/tools/mkimage.c @@ -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 ] [-p addr] [-r]\n" + "Signing / verified boot options: [-E] [-k keydir] [-K dtb] [ -c ] [-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) { -- cgit v0.10.2