From a739b59efe7996e3bdcbe8b17743dc05ac7c110a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 6 Aug 2019 12:49:28 +0200 Subject: mkfs.ubifs: Add authentication support This adds support for authenticated UBIFS images. In authenticated images all UBIFS nodes are hashed as described in the UBIFS authentication whitepaper. Additionally the superblock node contains a hash of the master node and itself is cryptographically signed in a node following the superblock node. The signature is in PKCS #7 CMS format. To generate an authenticated image these options are necessary: --hash-algo=NAME hash algorithm to use for signed images (Valid options include sha1, sha256, sha512) --auth-key=FILE filename or PKCS #11 uri containing the authentication key for signing --auth-cert=FILE Authentication certificate filename for signing. Unused when certificate is provided via PKCS #11 Signed-off-by: Sascha Hauer Signed-off-by: David Oberhollenzer --- ubifs-utils/mkfs.ubifs/sign.c | 409 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 ubifs-utils/mkfs.ubifs/sign.c (limited to 'ubifs-utils/mkfs.ubifs/sign.c') diff --git a/ubifs-utils/mkfs.ubifs/sign.c b/ubifs-utils/mkfs.ubifs/sign.c new file mode 100644 index 0000000..b7ad7ef --- /dev/null +++ b/ubifs-utils/mkfs.ubifs/sign.c @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2018 Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Sascha Hauer + */ + +#include "mkfs.ubifs.h" +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct ubifs_info *c = &info_; + +EVP_MD_CTX *hash_md; +const EVP_MD *md; + +int authenticated(void) +{ + return c->hash_algo_name != NULL; +} + +static int match_string(const char * const *array, size_t n, const char *string) +{ + int index; + const char *item; + + for (index = 0; index < n; index++) { + item = array[index]; + if (!item) + break; + if (!strcmp(item, string)) + return index; + } + + return -EINVAL; +} + +#include + +const char *const hash_algo_name[HASH_ALGO__LAST] = { + [HASH_ALGO_MD4] = "md4", + [HASH_ALGO_MD5] = "md5", + [HASH_ALGO_SHA1] = "sha1", + [HASH_ALGO_RIPE_MD_160] = "rmd160", + [HASH_ALGO_SHA256] = "sha256", + [HASH_ALGO_SHA384] = "sha384", + [HASH_ALGO_SHA512] = "sha512", + [HASH_ALGO_SHA224] = "sha224", + [HASH_ALGO_RIPE_MD_128] = "rmd128", + [HASH_ALGO_RIPE_MD_256] = "rmd256", + [HASH_ALGO_RIPE_MD_320] = "rmd320", + [HASH_ALGO_WP_256] = "wp256", + [HASH_ALGO_WP_384] = "wp384", + [HASH_ALGO_WP_512] = "wp512", + [HASH_ALGO_TGR_128] = "tgr128", + [HASH_ALGO_TGR_160] = "tgr160", + [HASH_ALGO_TGR_192] = "tgr192", + [HASH_ALGO_SM3_256] = "sm3-256", +}; + +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 ssl_err_msg(fmt, ...) ({ \ + display_openssl_errors(__LINE__); \ + err_msg(fmt, ## __VA_ARGS__); \ + -1; \ +}) + +static const char *key_pass; + +static int pem_pw_cb(char *buf, int len, __attribute__((unused)) int w, + __attribute__((unused)) 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; +} + +static EVP_PKEY *read_private_key(const char *private_key_name, X509 **cert) +{ + EVP_PKEY *private_key = NULL; + int err; + + *cert = NULL; + + if (!strncmp(private_key_name, "pkcs11:", 7)) { + ENGINE *e; + struct { + const char *url; + X509 *cert; + } parms = { + .url = private_key_name, + }; + + ENGINE_load_builtin_engines(); + drain_openssl_errors(); + e = ENGINE_by_id("pkcs11"); + if (!e) { + ssl_err_msg("Load PKCS#11 ENGINE"); + return NULL; + } + + if (ENGINE_init(e)) { + drain_openssl_errors(); + } else { + ssl_err_msg("ENGINE_init"); + return NULL; + } + + if (key_pass) + if (!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0)) { + ssl_err_msg("Set PKCS#11 PIN"); + return NULL; + } + + private_key = ENGINE_load_private_key(e, private_key_name, + NULL, NULL); + + err = ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &parms, NULL, 0); + if (!err || !parms.cert) { + ssl_err_msg("Load certificate"); + } + *cert = parms.cert; + fprintf(stderr, "Using cert %p\n", *cert); + } else { + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + if (!b) + goto out; + + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + BIO_free(b); + } +out: + if (!private_key) + ssl_err_msg("failed opening private key %s", private_key_name); + + return private_key; +} + +static X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509 = NULL; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + if (!b) + goto out; + + /* Look at the first two bytes of the file to determine the encoding */ + n = BIO_read(b, buf, 2); + if (n != 2) { + if (BIO_should_retry(b)) + err_msg("%s: Read wanted retry", x509_name); + if (n >= 0) + err_msg("%s: Short read", x509_name); + goto out; + } + + if (BIO_reset(b)) + goto out; + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509 = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); + + BIO_free(b); + +out: + if (!x509) { + ssl_err_msg("%s", x509_name); + return NULL; + } + + return x509; +} + +int sign_superblock_node(void *node) +{ + EVP_PKEY *private_key; + CMS_ContentInfo *cms = NULL; + X509 *cert = NULL; + BIO *bd, *bm; + void *obuf; + long len; + int ret; + void *pret; + struct ubifs_sig_node *sig = node + UBIFS_SB_NODE_SZ; + + if (!authenticated()) + return 0; + + ERR_load_crypto_strings(); + ERR_clear_error(); + + key_pass = getenv("MKFS_UBIFS_SIGN_PIN"); + + bm = BIO_new_mem_buf(node, UBIFS_SB_NODE_SZ); + + private_key = read_private_key(c->auth_key_filename, &cert); + if (!private_key) + return -1; + + if (!cert) { + if (!c->auth_cert_filename) + return err_msg("authentication certificate not provided (--auth-cert)"); + cert = read_x509(c->auth_cert_filename); + } + + if (!cert) + return -1; + + OpenSSL_add_all_digests(); + display_openssl_errors(__LINE__); + + cms = CMS_sign(NULL, NULL, NULL, NULL, + CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | + CMS_DETACHED | CMS_STREAM); + if (!cms) + return err_msg("CMS_sign failed"); + + pret = CMS_add1_signer(cms, cert, private_key, md, + CMS_NOCERTS | CMS_BINARY | + CMS_NOSMIMECAP | CMS_NOATTR); + if (!pret) + return err_msg("CMS_add1_signer failed"); + + ret = CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY); + if (!ret) + return err_msg("CMS_final failed"); + + bd = BIO_new(BIO_s_mem()); + + ret = i2d_CMS_bio_stream(bd, cms, NULL, 0); + if (!ret) + return err_msg("i2d_CMS_bio_stream failed"); + + len = BIO_get_mem_data(bd, &obuf); + + sig->type = UBIFS_SIGNATURE_TYPE_PKCS7; + sig->len = cpu_to_le32(len); + sig->ch.node_type = UBIFS_SIG_NODE; + + memcpy(sig + 1, obuf, len); + + BIO_free(bd); + BIO_free(bm); + + return 0; +} + +/** + * ubifs_node_calc_hash - calculate the hash of a UBIFS node + * @c: UBIFS file-system description object + * @node: the node to calculate a hash for + * @hash: the returned hash + */ +void ubifs_node_calc_hash(const void *node, uint8_t *hash) +{ + const struct ubifs_ch *ch = node; + unsigned int md_len; + + if (!authenticated()) + return; + + EVP_DigestInit_ex(hash_md, md, NULL); + EVP_DigestUpdate(hash_md, node, le32_to_cpu(ch->len)); + EVP_DigestFinal_ex(hash_md, hash, &md_len); +} + +/** + * mst_node_calc_hash - calculate the hash of a UBIFS master node + * @c: UBIFS file-system description object + * @node: the node to calculate a hash for + * @hash: the returned hash + */ +void mst_node_calc_hash(const void *node, uint8_t *hash) +{ + unsigned int md_len; + + if (!authenticated()) + return; + + EVP_DigestInit_ex(hash_md, md, NULL); + EVP_DigestUpdate(hash_md, node + sizeof(struct ubifs_ch), + UBIFS_MST_NODE_SZ - sizeof(struct ubifs_ch)); + EVP_DigestFinal_ex(hash_md, hash, &md_len); +} + +void hash_digest_init(void) +{ + if (!authenticated()) + return; + + EVP_DigestInit_ex(hash_md, md, NULL); +} + +void hash_digest_update(const void *buf, int len) +{ + if (!authenticated()) + return; + + EVP_DigestUpdate(hash_md, buf, len); +} + +void hash_digest_final(void *hash, unsigned int *len) +{ + if (!authenticated()) + return; + + EVP_DigestFinal_ex(hash_md, hash, len); +} + +int init_authentication(void) +{ + int hash_algo; + + if (!c->auth_key_filename && !c->auth_cert_filename && !c->hash_algo_name) + return 0; + + if (!c->auth_key_filename) + return err_msg("authentication key not given (--auth-key)"); + + if (!c->hash_algo_name) + return err_msg("Hash algorithm not given (--hash-algo)"); + + OPENSSL_no_config(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + md = EVP_get_digestbyname(c->hash_algo_name); + if (!md) + return err_msg("Unknown message digest %s", c->hash_algo_name); + + hash_md = EVP_MD_CTX_create(); + c->hash_len = EVP_MD_size(md); + + hash_algo = match_string(hash_algo_name, HASH_ALGO__LAST, c->hash_algo_name); + if (hash_algo < 0) + return err_msg("Unsupported message digest %s", c->hash_algo_name); + + c->hash_algo = hash_algo; + + return 0; +} -- cgit v1.2.3