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: