Add support for using external signing application and .pem private key files to vbutil_keyblock.
This allows signing using a .pem file using an external program.
It is assumed that the external program reads input from stdin, and outputs signed data on stdout. It takes one argument - the file name for the .pem private key reference. See external_rsa_signer.sh for an example external program.
Example usage:
vbutil_keyblock --pack 4096.keyblock \
--datapubkey 4096.vbpubk \
--signprivate_pem 4096.pem \
--pem_algorithm 8 \
--externalsigner "external_rsa_signer.sh"
I have tried to make the change such that it doesn't impact existing tools/interfaces (since these are used at various places). That said, I am aware of the places where we could just extend an old interface an avoid code duplication but thought I'd put that re-factoring in as a TODO for now. Let me know if you disagree and I can merge them (and changing the existing interface).
BUG=7576
TEST=Extended run_vbutil_tests.sh to test vbutil_keyblock packing using an external signer.
To test, make && make runtests (or just run tests/gen_test_keys.sh; tests/run_vbutils_tests.sh)
Review URL: http://codereview.chromium.org/4194003
Change-Id: I7cc52c8293c04ef9ba074794d046c9a4f19f6bdd
diff --git a/host/include/host_keyblock.h b/host/include/host_keyblock.h
index 1da251e..ea88f19 100644
--- a/host/include/host_keyblock.h
+++ b/host/include/host_keyblock.h
@@ -13,6 +13,18 @@
/* Create a key block header containing [data_key] and [flags], signed
+ * by private key the file [signing_key_pem_file] and algorithm [algorithm]
+ * using the external signer program [external_signer] for all private key
+ * operations.
+ * Caller owns the returned pointer, and must free
+ * it with Free(). */
+VbKeyBlockHeader* KeyBlockCreate_external(const VbPublicKey* data_key,
+ const char* signing_key_pem_file,
+ uint64_t algorithm,
+ uint64_t flags,
+ const char* external_signer);
+
+/* Create a key block header containing [data_key] and [flags], signed
* by [signing_key]. Caller owns the returned pointer, and must free
* it with Free(). */
VbKeyBlockHeader* KeyBlockCreate(const VbPublicKey* data_key,
diff --git a/host/include/host_signature.h b/host/include/host_signature.h
index 4bbee7c..f08547c 100644
--- a/host/include/host_signature.h
+++ b/host/include/host_signature.h
@@ -32,15 +32,25 @@
/* Calculates a SHA-512 checksum.
* Caller owns the returned pointer, and must free it with Free().
*
- * Returns NULL if error. */
+ * Returns NULL on error. */
VbSignature* CalculateChecksum(const uint8_t* data, uint64_t size);
/* Calculates a signature for the data using the specified key.
* Caller owns the returned pointer, and must free it with Free().
*
- * Returns NULL if error. */
+ * Returns NULL on error. */
VbSignature* CalculateSignature(const uint8_t* data, uint64_t size,
const VbPrivateKey* key);
+/* Calculates a signature for the data using the specified key and
+ * an external program.
+ * Caller owns the returned pointer, and must free it with Free().
+ *
+ * Returns NULL on error. */
+VbSignature* CalculateSignature_external(const uint8_t* data, uint64_t size,
+ const char* key_file,
+ uint64_t key_algorithm,
+ const char* external_signer);
+
#endif /* VBOOT_REFERENCE_HOST_SIGNATURE_H_ */
diff --git a/host/lib/host_keyblock.c b/host/lib/host_keyblock.c
index 2ad62b0..97a5d12 100644
--- a/host/lib/host_keyblock.c
+++ b/host/lib/host_keyblock.c
@@ -70,6 +70,65 @@
return h;
}
+/* TODO(gauravsh): This could easily be integrated into KeyBlockCreate()
+ * since the code is almost a mirror - I have kept it as such to avoid changing
+ * the existing interface. */
+VbKeyBlockHeader* KeyBlockCreate_external(const VbPublicKey* data_key,
+ const char* signing_key_pem_file,
+ uint64_t algorithm,
+ uint64_t flags,
+ const char* external_signer) {
+ VbKeyBlockHeader* h;
+ uint64_t signed_size = sizeof(VbKeyBlockHeader) + data_key->key_size;
+ uint64_t block_size = (signed_size + SHA512_DIGEST_SIZE +
+ siglen_map[algorithm]);
+ uint8_t* data_key_dest;
+ uint8_t* block_sig_dest;
+ uint8_t* block_chk_dest;
+ VbSignature *sigtmp;
+
+ /* Allocate key block */
+ h = (VbKeyBlockHeader*)Malloc(block_size);
+ if (!h)
+ return NULL;
+ if (!signing_key_pem_file || !data_key || !external_signer)
+ return NULL;
+
+ data_key_dest = (uint8_t*)(h + 1);
+ block_chk_dest = data_key_dest + data_key->key_size;
+ block_sig_dest = block_chk_dest + SHA512_DIGEST_SIZE;
+
+ Memcpy(h->magic, KEY_BLOCK_MAGIC, KEY_BLOCK_MAGIC_SIZE);
+ h->header_version_major = KEY_BLOCK_HEADER_VERSION_MAJOR;
+ h->header_version_minor = KEY_BLOCK_HEADER_VERSION_MINOR;
+ h->key_block_size = block_size;
+ h->key_block_flags = flags;
+
+ /* Copy data key */
+ PublicKeyInit(&h->data_key, data_key_dest, data_key->key_size);
+ PublicKeyCopy(&h->data_key, data_key);
+
+ /* Set up signature structs so we can calculate the signatures */
+ SignatureInit(&h->key_block_checksum, block_chk_dest,
+ SHA512_DIGEST_SIZE, signed_size);
+ SignatureInit(&h->key_block_signature, block_sig_dest,
+ siglen_map[algorithm], signed_size);
+
+ /* Calculate checksum */
+ sigtmp = CalculateChecksum((uint8_t*)h, signed_size);
+ SignatureCopy(&h->key_block_checksum, sigtmp);
+ Free(sigtmp);
+
+ /* Calculate signature */
+ sigtmp = CalculateSignature_external((uint8_t*)h, signed_size,
+ signing_key_pem_file, algorithm,
+ external_signer);
+ SignatureCopy(&h->key_block_signature, sigtmp);
+ Free(sigtmp);
+
+ /* Return the header */
+ return h;
+}
/* Read a key block from a .keyblock file. Caller owns the returned
* pointer, and must free it with Free().
diff --git a/host/lib/host_signature.c b/host/lib/host_signature.c
index c0d0f64..e00824f 100644
--- a/host/lib/host_signature.c
+++ b/host/lib/host_signature.c
@@ -14,6 +14,8 @@
#include <stdio.h>
#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include <unistd.h>
#include "cryptolib.h"
@@ -36,7 +38,7 @@
void SignatureInit(VbSignature* sig, uint8_t* sig_data,
- uint64_t sig_size, uint64_t data_size) {
+ uint64_t sig_size, uint64_t data_size) {
sig->sig_offset = OffsetOf(sig, sig_data);
sig->sig_size = sig_size;
sig->data_size = data_size;
@@ -77,7 +79,6 @@
return sig;
}
-
VbSignature* CalculateSignature(const uint8_t* data, uint64_t size,
const VbPrivateKey* key) {
@@ -133,3 +134,150 @@
/* Return the signature */
return sig;
}
+
+/* Invoke [external_signer] command with [pem_file] as
+ * an argument, contents of [inbuf] passed redirected to stdin,
+ * and the stdout of the command is put back into [outbuf].
+ * Returns -1 on error, 0 on success.
+ */
+int InvokeExternalSigner(uint64_t size,
+ const uint8_t* inbuf,
+ uint8_t* outbuf,
+ uint64_t outbufsize,
+ const char* pem_file,
+ const char* external_signer) {
+
+ int rv = 0, n;
+ int p_to_c[2], c_to_p[2]; /* pipe descriptors */
+ pid_t pid;
+
+ VBDEBUG(("Will invoke \"%s %s\" to perform signing.\n"
+ "Input to the signer will be provided on standard in.\n"
+ "Output of the signer will be read from standard out.\n",
+ external_signer, pem_file));
+
+ /* Need two pipes since we want to invoke the external_signer as
+ * a co-process writing to its stdin and reading from its stdout. */
+ if (pipe(p_to_c) < 0 || pipe(c_to_p) < 0) {
+ VBDEBUG(("pipe() error\n"));
+ return -1;
+ }
+ if ((pid = fork()) < 0) {
+ VBDEBUG(("fork() error"));
+ return -1;
+ }
+ else if (pid > 0) { /* Parent. */
+ close(p_to_c[STDIN_FILENO]);
+ close(c_to_p[STDOUT_FILENO]);
+
+ /* We provide input to the child process (external signer). */
+ if (write(p_to_c[STDOUT_FILENO], inbuf, size) != size) {
+ VBDEBUG(("write() error while providing input to external signer\n"));
+ rv = -1;
+ } else {
+ close(p_to_c[STDOUT_FILENO]); /* Send EOF to child (signer process). */
+ do {
+ n = read(c_to_p[STDIN_FILENO], outbuf, outbufsize);
+ outbuf += n;
+ outbufsize -= n;
+ } while (n > 0 && outbufsize);
+
+ if (n < 0) {
+ VBDEBUG(("read() error while reading output from external signer\n"));
+ rv = -1;
+ }
+ }
+ if (waitpid(pid, NULL, 0) < 0) {
+ VBDEBUG(("waitpid() error\n"));
+ rv = -1;
+ }
+ } else { /* Child. */
+ close (p_to_c[STDOUT_FILENO]);
+ close (c_to_p[STDIN_FILENO]);
+ /* Map the stdin to the first pipe (this pipe gets input
+ * from the parent) */
+ if (STDIN_FILENO != p_to_c[STDIN_FILENO]) {
+ if (dup2(p_to_c[STDIN_FILENO], STDIN_FILENO) != STDIN_FILENO) {
+ VBDEBUG(("stdin dup2() failed (external signer)\n"));
+ close(p_to_c[0]);
+ return -1;
+ }
+ }
+ /* Map the stdout to the second pipe (this pipe sends back
+ * signer output to the parent) */
+ if (STDOUT_FILENO != c_to_p[STDOUT_FILENO]) {
+ if (dup2(c_to_p[STDOUT_FILENO], STDOUT_FILENO) != STDOUT_FILENO) {
+ VBDEBUG(("stdout dup2() failed (external signer)\n"));
+ close(c_to_p[STDOUT_FILENO]);
+ return -1;
+ }
+ }
+ /* External signer is invoked here. */
+ if (execl(external_signer, external_signer, pem_file, (char *) 0) < 0) {
+ VBDEBUG(("execl() of external signer failed\n"));
+ }
+ }
+ return rv;
+}
+
+/* TODO(gauravsh): This could easily be integrated into CalculateSignature()
+ * since the code is almost a mirror - I have kept it as such to avoid changing
+ * the existing interface. */
+VbSignature* CalculateSignature_external(const uint8_t* data, uint64_t size,
+ const char* key_file,
+ uint64_t key_algorithm,
+ const char* external_signer) {
+ uint8_t* digest;
+ uint64_t digest_size = hash_size_map[key_algorithm];
+
+ const uint8_t* digestinfo = hash_digestinfo_map[key_algorithm];
+ uint64_t digestinfo_size = digestinfo_size_map[key_algorithm];
+
+ uint8_t* signature_digest;
+ uint64_t signature_digest_len = digest_size + digestinfo_size;
+
+ VbSignature* sig;
+ int rv;
+
+ /* Calculate the digest */
+ /* TODO: rename param 3 of DigestBuf to hash_type */
+ digest = DigestBuf(data, size, hash_type_map[key_algorithm]);
+ if (!digest)
+ return NULL;
+
+ /* Prepend the digest info to the digest */
+ signature_digest = Malloc(signature_digest_len);
+ if (!signature_digest) {
+ Free(digest);
+ return NULL;
+ }
+ Memcpy(signature_digest, digestinfo, digestinfo_size);
+ Memcpy(signature_digest + digestinfo_size, digest, digest_size);
+ Free(digest);
+
+ /* Allocate output signature */
+ sig = SignatureAlloc(siglen_map[key_algorithm], size);
+ if (!sig) {
+ Free(signature_digest);
+ return NULL;
+ }
+
+ /* Sign the signature_digest into our output buffer */
+ rv = InvokeExternalSigner(signature_digest_len, /* Input length */
+ signature_digest, /* Input data */
+ GetSignatureData(sig), /* Output sig */
+ (sizeof(VbSignature) + /* Max Output sig size. */
+ siglen_map[key_algorithm]) ,
+ key_file, /* Key file to use */
+ external_signer); /* External cmd to invoke */
+ Free(signature_digest);
+
+ if (-1 == rv) {
+ VBDEBUG(("SignatureBuf(): RSA_private_encrypt() failed.\n"));
+ Free(sig);
+ return NULL;
+ }
+
+ /* Return the signature */
+ return sig;
+}
diff --git a/tests/external_rsa_signer.sh b/tests/external_rsa_signer.sh
new file mode 100755
index 0000000..0724b87
--- /dev/null
+++ b/tests/external_rsa_signer.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+if [ $# -ne 1 ]; then
+ echo "Usage: $0 <private_key_pem_file>"
+ echo "Reads data to sign from stdin, encrypted data is output to stdout"
+ exit 1
+fi
+
+openssl rsautl -sign -inkey $1
diff --git a/tests/run_vbutil_tests.sh b/tests/run_vbutil_tests.sh
index b881130..b56f4c1 100755
--- a/tests/run_vbutil_tests.sh
+++ b/tests/run_vbutil_tests.sh
@@ -124,6 +124,46 @@
exit 1
fi
+ echo -e "${COL_YELLOW}Testing keyblock creation using \
+external signer.${COL_STOP}"
+ # Pack using external signer
+ # Pack
+ ${UTIL_DIR}/vbutil_keyblock --pack ${keyblockfile} \
+ --datapubkey \
+ ${TESTKEY_SCRATCH_DIR}/key_alg${data_algorithmcounter}.vbpubk \
+ --signprivate_pem \
+ ${TESTKEY_DIR}/key_rsa${signing_keylen}.pem \
+ --pem_algorithm "${signing_algorithmcounter}" \
+ --externalsigner "${SCRIPT_DIR}/external_rsa_signer.sh"
+
+ if [ $? -ne 0 ]
+ then
+ echo -e "${COL_RED}Pack${COL_STOP}"
+ return_code=255
+ fi
+
+ # Unpack
+ ${UTIL_DIR}/vbutil_keyblock --unpack ${keyblockfile} \
+ --datapubkey \
+ ${TESTKEY_SCRATCH_DIR}/key_alg${data_algorithmcounter}.vbpubk2 \
+ --signpubkey \
+ ${TESTKEY_SCRATCH_DIR}/key_alg${signing_algorithmcounter}.vbpubk
+ if [ $? -ne 0 ]
+ then
+ echo -e "${COL_RED}Unpack${COL_STOP}"
+ return_code=255
+ fi
+
+ # Check
+ if ! cmp -s \
+ ${TESTKEY_SCRATCH_DIR}/key_alg${data_algorithmcounter}.vbpubk \
+ ${TESTKEY_SCRATCH_DIR}/key_alg${data_algorithmcounter}.vbpubk2
+ then
+ echo -e "${COL_RED}Check${COL_STOP}"
+ return_code=255
+ exit 1
+ fi
+
let data_algorithmcounter=data_algorithmcounter+1
done
done
diff --git a/utility/vbutil_keyblock.c b/utility/vbutil_keyblock.c
index dd13eb3..c919221 100644
--- a/utility/vbutil_keyblock.c
+++ b/utility/vbutil_keyblock.c
@@ -6,7 +6,7 @@
*/
#include <getopt.h>
-#include <inttypes.h> /* For PRIu64 */
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -23,6 +23,9 @@
OPT_DATAPUBKEY,
OPT_SIGNPUBKEY,
OPT_SIGNPRIVATE,
+ OPT_SIGNPRIVATE_PEM,
+ OPT_PEM_ALGORITHM,
+ OPT_EXTERNAL_SIGNER,
OPT_FLAGS,
};
@@ -32,6 +35,9 @@
{"datapubkey", 1, 0, OPT_DATAPUBKEY },
{"signpubkey", 1, 0, OPT_SIGNPUBKEY },
{"signprivate", 1, 0, OPT_SIGNPRIVATE },
+ {"signprivate_pem", 1, 0, OPT_SIGNPRIVATE_PEM },
+ {"pem_algorithm", 1, 0, OPT_PEM_ALGORITHM },
+ {"externalsigner", 1, 0, OPT_EXTERNAL_SIGNER },
{"flags", 1, 0, OPT_FLAGS },
{NULL, 0, 0, 0}
};
@@ -49,24 +55,34 @@
"\n"
"Optional OPTIONS are:\n"
" --signprivate <file>"
- " Signing private key in .vbprivk format. Without this arg,\n"
- " the keyblock will not be signed.\n"
+ " Signing private key in .vbprivk format.\n"
+ "OR\n"
+ " --signprivate_pem <file>\n"
+ " --pem_algorithm <algo>\n"
+ " Signing private key in .pem format and algorithm id.\n"
+ "(If one of the above arguments is not specified, the keyblock will\n"
+ "not be signed.)\n"
+ "\n"
" --flags <number> Specifies allowed use conditions.\n"
+ " --externalsigner \"cmd\""
+ " Use an external program cmd to calculate the signatures.\n"
"\n"
"For '--unpack <file>', optional OPTIONS are:\n"
" --signpubkey <file>"
- " Signing public key in .vbpubk format. This is required to\n"
+ " Signing public key in .vbpubk format. This is required to\n"
" verify a signed keyblock.\n"
" --datapubkey <file>"
- " Write the data public key to this file.\n",
+ " Write the data public key to this file.\n",
progname);
return 1;
}
-
/* Pack a .keyblock */
static int Pack(const char* outfile, const char* datapubkey,
- const char* signprivate, uint64_t flags) {
+ const char* signprivate,
+ const char* signprivate_pem, uint64_t pem_algorithm,
+ uint64_t flags,
+ const char* external_signer) {
VbPublicKey* data_key;
VbPrivateKey* signing_key = NULL;
VbKeyBlockHeader* block;
@@ -86,15 +102,37 @@
return 1;
}
- if (signprivate) {
- signing_key = PrivateKeyRead(signprivate);
- if (!signing_key) {
- fprintf(stderr, "vbutil_keyblock: Error reading signing key.\n");
+ if (signprivate_pem) {
+ if (pem_algorithm >= kNumAlgorithms) {
+ fprintf(stderr, "vbutil_keyblock: Invalid --pem_algorithm %" PRIu64 "\n",
+ pem_algorithm);
return 1;
}
+ if (external_signer) {
+ /* External signing uses the PEM file directly. */
+ block = KeyBlockCreate_external(data_key,
+ signprivate_pem, pem_algorithm,
+ flags,
+ external_signer);
+ } else {
+ signing_key = PrivateKeyReadPem(signprivate, pem_algorithm);
+ if (!signing_key) {
+ fprintf(stderr, "vbutil_keyblock: Error reading signing key.\n");
+ return 1;
+ }
+ block = KeyBlockCreate(data_key, signing_key, flags);
+ }
+ } else {
+ if (signprivate) {
+ signing_key = PrivateKeyRead(signprivate);
+ if (!signing_key) {
+ fprintf(stderr, "vbutil_keyblock: Error reading signing key.\n");
+ return 1;
+ }
+ }
+ block = KeyBlockCreate(data_key, signing_key, flags);
}
- block = KeyBlockCreate(data_key, signing_key, flags);
Free(data_key);
if (signing_key)
Free(signing_key);
@@ -107,7 +145,6 @@
return 0;
}
-
static int Unpack(const char* infile, const char* datapubkey,
const char* signpubkey) {
VbPublicKey* data_key;
@@ -181,7 +218,11 @@
char* datapubkey = NULL;
char* signpubkey = NULL;
char* signprivate = NULL;
+ char* signprivate_pem = NULL;
+ char* external_signer = NULL;
uint64_t flags = 0;
+ uint64_t pem_algorithm = 0;
+ int is_pem_algorithm = 0;
int mode = 0;
int parse_error = 0;
char* e;
@@ -219,22 +260,61 @@
signprivate = optarg;
break;
+ case OPT_SIGNPRIVATE_PEM:
+ signprivate_pem = optarg;
+ break;
+
+ case OPT_PEM_ALGORITHM:
+ pem_algorithm = strtoul(optarg, &e, 0);
+ if (!*optarg || (e && *e)) {
+ fprintf(stderr, "Invalid --pem_algorithm\n");
+ parse_error = 1;
+ } else {
+ is_pem_algorithm = 1;
+ }
+ break;
+
+ case OPT_EXTERNAL_SIGNER:
+ external_signer = optarg;
+ break;
+
case OPT_FLAGS:
flags = strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
- printf("Invalid --flags\n");
+ fprintf(stderr, "Invalid --flags\n");
parse_error = 1;
}
break;
}
}
+ /* Check if the right combination of options was provided. */
+ if (signprivate && signprivate_pem) {
+ fprintf(stderr, "Only one of --signprivate or --signprivate_pem must"
+ " be specified\n");
+ parse_error = 1;
+ }
+
+ if (signprivate_pem && !is_pem_algorithm) {
+ fprintf(stderr, "--pem_algorithm must be used with --signprivate_pem\n");
+ parse_error = 1;
+ }
+
+ if (external_signer && !signprivate_pem) {
+ fprintf(stderr, "--externalsigner must be used with --signprivate_pem"
+ "\n");
+ parse_error = 1;
+ }
+
if (parse_error)
return PrintHelp(progname);
switch(mode) {
case OPT_MODE_PACK:
- return Pack(filename, datapubkey, signprivate, flags);
+ return Pack(filename, datapubkey, signprivate,
+ signprivate_pem, pem_algorithm,
+ flags,
+ external_signer);
case OPT_MODE_UNPACK:
return Unpack(filename, datapubkey, signpubkey);
default: