/* Sign a module file using the given key. * * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <getopt.h> #include <err.h> #include <arpa/inet.h> #include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/pem.h> #include <openssl/cms.h> #include <openssl/err.h> #include <openssl/engine.h> struct module_signature { uint8_t algo; /* Public-key crypto algorithm [0] */ uint8_t hash; /* Digest algorithm [0] */ uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */ uint8_t signer_len; /* Length of signer's name [0] */ uint8_t key_id_len; /* Length of key identifier [0] */ uint8_t __pad[3]; uint32_t sig_len; /* Length of signature data */ }; #define PKEY_ID_PKCS7 2 static char magic_number[] = "~Module signature appended~\n"; static __attribute__((noreturn)) void format(void) { fprintf(stderr, "Usage: scripts/sign-file [-dp] <hash algo> <key> <x509> <module> [<dest>]\n"); exit(2); } static void display_openssl_errors(int l) { const char *file; char buf[120]; int e, line; if (ERR_peek_error() == 0) return; fprintf(stderr, "At main.c:%d:\n", l); while ((e = ERR_get_error_line(&file, &line))) { ERR_error_string(e, buf); fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line); } } static void drain_openssl_errors(void) { const char *file; int line; if (ERR_peek_error() == 0) return; while (ERR_get_error_line(&file, &line)) {} } #define ERR(cond, fmt, ...) \ do { \ bool __cond = (cond); \ display_openssl_errors(__LINE__); \ if (__cond) { \ err(1, fmt, ## __VA_ARGS__); \ } \ } while(0) static const char *key_pass; static int pem_pw_cb(char *buf, int len, int w, void *v) { int pwlen; if (!key_pass) return -1; pwlen = strlen(key_pass); if (pwlen >= len) return -1; strcpy(buf, key_pass); /* If it's wrong, don't keep trying it. */ key_pass = NULL; return pwlen; } int main(int argc, char **argv) { struct module_signature sig_info = { .id_type = PKEY_ID_PKCS7 }; char *hash_algo = NULL; char *private_key_name, *x509_name, *module_name, *dest_name; bool save_cms = false, replace_orig; bool sign_only = false; unsigned char buf[4096]; unsigned long module_size, cms_size; unsigned int use_keyid = 0, use_signed_attrs = CMS_NOATTR; const EVP_MD *digest_algo; EVP_PKEY *private_key; CMS_ContentInfo *cms; X509 *x509; BIO *b, *bd = NULL, *bm; int opt, n; OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); ERR_clear_error(); key_pass = getenv("KBUILD_SIGN_PIN"); do { opt = getopt(argc, argv, "dpk"); switch (opt) { case 'p': save_cms = true; break; case 'd': sign_only = true; save_cms = true; break; case 'k': use_keyid = CMS_USE_KEYID; break; case -1: break; default: format(); } } while (opt != -1); argc -= optind; argv += optind; if (argc < 4 || argc > 5) format(); hash_algo = argv[0]; private_key_name = argv[1]; x509_name = argv[2]; module_name = argv[3]; if (argc == 5) { dest_name = argv[4]; replace_orig = false; } else { ERR(asprintf(&dest_name, "%s.~signed~", module_name) < 0, "asprintf"); replace_orig = true; } /* Read the private key and the X.509 cert the PKCS#7 message * will point to. */ if (!strncmp(private_key_name, "pkcs11:", 7)) { ENGINE *e; ENGINE_load_builtin_engines(); drain_openssl_errors(); e = ENGINE_by_id("pkcs11"); ERR(!e, "Load PKCS#11 ENGINE"); if (ENGINE_init(e)) drain_openssl_errors(); else ERR(1, "ENGINE_init"); if (key_pass) ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), "Set PKCS#11 PIN"); private_key = ENGINE_load_private_key(e, private_key_name, NULL, NULL); ERR(!private_key, "%s", private_key_name); } else { b = BIO_new_file(private_key_name, "rb"); ERR(!b, "%s", private_key_name); private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, NULL); ERR(!private_key, "%s", private_key_name); BIO_free(b); } b = BIO_new_file(x509_name, "rb"); ERR(!b, "%s", x509_name); x509 = d2i_X509_bio(b, NULL); /* Binary encoded X.509 */ if (!x509) { BIO_reset(b); x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); /* PEM encoded X.509 */ if (x509) drain_openssl_errors(); } BIO_free(b); ERR(!x509, "%s", x509_name); /* Open the destination file now so that we can shovel the module data * across as we read it. */ if (!sign_only) { bd = BIO_new_file(dest_name, "wb"); ERR(!bd, "%s", dest_name); } /* Digest the module data. */ OpenSSL_add_all_digests(); display_openssl_errors(__LINE__); digest_algo = EVP_get_digestbyname(hash_algo); ERR(!digest_algo, "EVP_get_digestbyname"); bm = BIO_new_file(module_name, "rb"); ERR(!bm, "%s", module_name); /* Load the CMS message from the digest buffer. */ cms = CMS_sign(NULL, NULL, NULL, NULL, CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED | CMS_STREAM); ERR(!cms, "CMS_sign"); ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP | use_keyid | use_signed_attrs), "CMS_sign_add_signer"); ERR(CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) < 0, "CMS_final"); if (save_cms) { char *cms_name; ERR(asprintf(&cms_name, "%s.p7s", module_name) < 0, "asprintf"); b = BIO_new_file(cms_name, "wb"); ERR(!b, "%s", cms_name); ERR(i2d_CMS_bio_stream(b, cms, NULL, 0) < 0, "%s", cms_name); BIO_free(b); } if (sign_only) return 0; /* Append the marker and the PKCS#7 message to the destination file */ ERR(BIO_reset(bm) < 0, "%s", module_name); while ((n = BIO_read(bm, buf, sizeof(buf))), n > 0) { ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); } ERR(n < 0, "%s", module_name); module_size = BIO_number_written(bd); ERR(i2d_CMS_bio_stream(bd, cms, NULL, 0) < 0, "%s", dest_name); cms_size = BIO_number_written(bd) - module_size; sig_info.sig_len = htonl(cms_size); ERR(BIO_write(bd, &sig_info, sizeof(sig_info)) < 0, "%s", dest_name); ERR(BIO_write(bd, magic_number, sizeof(magic_number) - 1) < 0, "%s", dest_name); ERR(BIO_free(bd) < 0, "%s", dest_name); /* Finally, if we're signing in place, replace the original. */ if (replace_orig) ERR(rename(dest_name, module_name) < 0, "%s", dest_name); return 0; }