summaryrefslogtreecommitdiff
path: root/ubifs-utils/mkfs.ubifs/sign.c
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2019-08-06 12:49:28 +0200
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2019-08-19 09:27:00 +0200
commita739b59efe7996e3bdcbe8b17743dc05ac7c110a (patch)
tree863ad65c25a5bcb9be3e5a170465ddc384d7b87b /ubifs-utils/mkfs.ubifs/sign.c
parent3ef262739a826deb27262929b88f36db6f923e7f (diff)
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 <s.hauer@pengutronix.de> Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'ubifs-utils/mkfs.ubifs/sign.c')
-rw-r--r--ubifs-utils/mkfs.ubifs/sign.c409
1 files changed, 409 insertions, 0 deletions
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 <openssl/evp.h>
+#include <openssl/opensslv.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/engine.h>
+#include <openssl/cms.h>
+#include <openssl/conf.h>
+#include <err.h>
+
+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 <linux/hash_info.h>
+
+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;
+}