/*- * Copyright (c) 2011-2013 Baptiste Daroussin * Copyright (c) 2011-2012 Julien Laffaye * All rights reserved. * Copyright (c) 2021 Kyle Evans * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #define WITH_STDLIB #include #undef WITH_STDLIB #include "pkg.h" #include "hash.h" /* libpkg shim */ #define STREQ(l, r) (strcmp(l, r) == 0) struct ecc_sign_ctx { struct pkgsign_ctx sctx; ec_params params; ec_key_pair keypair; ec_alg_type sig_alg; hash_alg_type sig_hash; bool loaded; }; /* Grab the ossl context from a pkgsign_ctx. */ #define ECC_CCTX(c) (__containerof(c, const struct ecc_sign_ctx, sctx)) #define ECC_CTX(c) (__containerof(c, struct ecc_sign_ctx, sctx)) #define PUBKEY_UNCOMPRESSED 0x04 #ifndef MAX #define MAX(a,b) (((a)>(b))?(a):(b)) #endif static const uint8_t oid_ecpubkey[] = \ { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 }; static const uint8_t oid_secp[] = \ { 0x2b, 0x81, 0x04, 0x00 }; static const uint8_t oid_secp256k1[] = \ { 0x2b, 0x81, 0x04, 0x00, 0x0a }; static const uint8_t oid_brainpoolP[] = \ { 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01 }; #define ENTRY(name, params) { #name, sizeof(#name) - 1, params } static const struct pkgkey_map_entry { const char *name; size_t namesz; const ec_str_params *params; } pkgkey_map[] = { ENTRY(WEI25519, &wei25519_str_params), ENTRY(SECP256K1, &secp256k1_str_params), ENTRY(SECP384R1, &secp384r1_str_params), ENTRY(SECP512R1, &secp521r1_str_params), ENTRY(BRAINPOOLP256R1, &brainpoolp256r1_str_params), ENTRY(BRAINPOOLP256T1, &brainpoolp256t1_str_params), ENTRY(BRAINPOOLP320R1, &brainpoolp320r1_str_params), ENTRY(BRAINPOOLP320T1, &brainpoolp320t1_str_params), ENTRY(BRAINPOOLP384R1, &brainpoolp384r1_str_params), ENTRY(BRAINPOOLP384T1, &brainpoolp384t1_str_params), ENTRY(BRAINPOOLP512R1, &brainpoolp512r1_str_params), ENTRY(BRAINPOOLP512T1, &brainpoolp512t1_str_params), }; static const char pkgkey_app[] = "pkg"; static const char pkgkey_signer[] = "ecc"; static const ec_str_params * ecc_pkgkey_params(const uint8_t *curve, size_t curvesz) { const struct pkgkey_map_entry *entry; for (size_t i = 0; i < nitems(pkgkey_map); i++) { entry = &pkgkey_map[i]; if (curvesz != entry->namesz) continue; if (memcmp(curve, entry->name, curvesz) == 0) return (entry->params); } return (NULL); } static int ecc_read_pkgkey(struct libder_object *root, ec_params *params, int public, uint8_t *rawkey, size_t *rawlen) { struct libder_object *obj; const uint8_t *data; const ec_str_params *sparams; size_t datasz; int ret; if (libder_obj_type_simple(root) != BT_SEQUENCE) return (1); /* Application */ obj = libder_obj_child(root, 0); if (obj == NULL || libder_obj_type_simple(obj) != BT_UTF8STRING) return (1); data = libder_obj_data(obj, &datasz); if (datasz != sizeof(pkgkey_app) - 1 || memcmp(data, pkgkey_app, datasz) != 0) return (1); /* Version */ obj = libder_obj_child(root, 1); if (obj == NULL || libder_obj_type_simple(obj) != BT_INTEGER) return (1); data = libder_obj_data(obj, &datasz); if (datasz != 1 || *data != 1 /* XXX */) return (1); /* Signer */ obj = libder_obj_child(root, 2); if (obj == NULL || libder_obj_type_simple(obj) != BT_UTF8STRING) return (1); data = libder_obj_data(obj, &datasz); if (datasz != sizeof(pkgkey_signer) - 1 || memcmp(data, pkgkey_signer, datasz) != 0) return (1); /* KeyType (curve) */ obj = libder_obj_child(root, 3); if (obj == NULL || libder_obj_type_simple(obj) != BT_UTF8STRING) return (1); data = libder_obj_data(obj, &datasz); sparams = ecc_pkgkey_params(data, datasz); if (sparams == NULL) return (1); ret = import_params(params, sparams); if (ret != 0) return (1); /* Public? */ obj = libder_obj_child(root, 4); if (obj == NULL || libder_obj_type_simple(obj) != BT_BOOLEAN) return (1); data = libder_obj_data(obj, &datasz); if (datasz != 1 || !data[0] != !public) return (1); /* Key */ obj = libder_obj_child(root, 5); if (obj == NULL || libder_obj_type_simple(obj) != BT_BITSTRING) return (1); data = libder_obj_data(obj, &datasz); if (datasz <= 2 || data[0] != 0 || data[1] != PUBKEY_UNCOMPRESSED) return (1); data += 2; datasz -= 2; if (datasz > *rawlen) return (1); memcpy(rawkey, data, datasz); *rawlen = datasz; return (0); } static int ecc_extract_signature(const uint8_t *sig, size_t siglen, uint8_t *rawsig, size_t rawlen) { struct libder_ctx *ctx; struct libder_object *obj, *root; const uint8_t *sigdata; size_t compsz, datasz, sigoff; int rc; ctx = libder_open(); if (ctx == NULL) return (1); rc = 1; root = libder_read(ctx, sig, &siglen); if (root == NULL || libder_obj_type_simple(root) != BT_SEQUENCE) goto out; /* Descend into the sequence's payload, extract both numbers. */ compsz = rawlen / 2; sigoff = 0; for (int i = 0; i < 2; i++) { obj = libder_obj_child(root, i); if (libder_obj_type_simple(obj) != BT_INTEGER) goto out; sigdata = libder_obj_data(obj, &datasz); if (datasz < 2 || datasz > compsz + 1) goto out; /* * We may see an extra lead byte if our high bit of the first * byte was set, since these numbers are positive by definition. */ if (sigdata[0] == 0 && (sigdata[1] & 0x80) != 0) { sigdata++; datasz--; } /* Sanity check: don't overflow the output. */ if (sigoff + datasz > rawlen) goto out; /* Padding to the significant end if we're too small. */ if (datasz < compsz) { memset(&rawsig[sigoff], 0, compsz - datasz); sigoff += compsz - datasz; } memcpy(&rawsig[sigoff], sigdata, datasz); sigoff += datasz; } /* Sanity check: must have exactly the required # of signature bits. */ rc = (sigoff == rawlen) ? 0 : 1; out: libder_obj_free(root); libder_close(ctx); return (rc); } static int ecc_extract_pubkey_string(const uint8_t *data, size_t datalen, uint8_t *rawkey, size_t *rawlen) { uint8_t prefix, usebit; if (datalen <= 2) return (1); usebit = *data++; datalen--; if (usebit != 0) return (1); prefix = *data++; datalen--; if (prefix != PUBKEY_UNCOMPRESSED) return (1); if (datalen > *rawlen) return (1); memcpy(rawkey, data, datalen); *rawlen = datalen; return (0); } static int ecc_extract_key_params(const uint8_t *oid, size_t oidlen, ec_params *rawparams) { int ret; if (oidlen >= sizeof(oid_secp) && memcmp(oid, oid_secp, sizeof(oid_secp)) >= 0) { oid += sizeof(oid_secp); oidlen -= sizeof(oid_secp); if (oidlen != 1) return (1); ret = -1; switch (*oid) { case 0x0a: /* secp256k1 */ ret = import_params(rawparams, &secp256k1_str_params); break; case 0x22: /* secp384r1 */ ret = import_params(rawparams, &secp384r1_str_params); break; case 0x23: /* secp521r1 */ ret = import_params(rawparams, &secp521r1_str_params); break; default: return (1); } if (ret == 0) return (0); return (1); } if (oidlen >= sizeof(oid_brainpoolP) && memcmp(oid, oid_brainpoolP, sizeof(oid_brainpoolP)) >= 0) { oid += sizeof(oid_brainpoolP); oidlen -= sizeof(oid_brainpoolP); if (oidlen != 1) return (1); ret = -1; switch (*oid) { case 0x07: /* brainpoolP256r1 */ ret = import_params(rawparams, &brainpoolp256r1_str_params); break; case 0x08: /* brainpoolP256t1 */ ret = import_params(rawparams, &brainpoolp256t1_str_params); break; case 0x09: /* brainpoolP320r1 */ ret = import_params(rawparams, &brainpoolp320r1_str_params); break; case 0x0a: /* brainpoolP320t1 */ ret = import_params(rawparams, &brainpoolp320t1_str_params); break; case 0x0b: /* brainpoolP384r1 */ ret = import_params(rawparams, &brainpoolp384r1_str_params); break; case 0x0c: /* brainpoolP384t1 */ ret = import_params(rawparams, &brainpoolp384t1_str_params); break; case 0x0d: /* brainpoolP512r1 */ ret = import_params(rawparams, &brainpoolp512r1_str_params); break; case 0x0e: /* brainpoolP512t1 */ ret = import_params(rawparams, &brainpoolp512t1_str_params); break; default: return (1); } if (ret == 0) return (0); return (1); } #ifdef ECC_DEBUG for (size_t i = 0; i < oidlen; i++) { fprintf(stderr, "%.02x ", oid[i]); } fprintf(stderr, "\n"); #endif return (1); } /* * On entry, *rawparams should point to an ec_params that we can import the * key parameters to. We'll either do that, or we'll set it to NULL if we could * not deduce the curve. */ static int ecc_extract_pubkey(FILE *keyfp, const uint8_t *key, size_t keylen, uint8_t *rawkey, size_t *rawlen, ec_params *rawparams) { const uint8_t *oidp; struct libder_ctx *ctx; struct libder_object *keydata, *oid, *params, *root; size_t oidsz; int rc; ctx = libder_open(); if (ctx == NULL) return (1); rc = 1; assert((keyfp != NULL) ^ (key != NULL)); if (keyfp != NULL) { root = libder_read_file(ctx, keyfp, &keylen); } else { root = libder_read(ctx, key, &keylen); } if (root == NULL || libder_obj_type_simple(root) != BT_SEQUENCE) goto out; params = libder_obj_child(root, 0); if (params == NULL) { goto out; } else if (libder_obj_type_simple(params) != BT_SEQUENCE) { rc = ecc_read_pkgkey(root, rawparams, 1, rawkey, rawlen); goto out; } /* Is a sequence */ keydata = libder_obj_child(root, 1); if (keydata == NULL || libder_obj_type_simple(keydata) != BT_BITSTRING) goto out; /* Key type */ oid = libder_obj_child(params, 0); if (oid == NULL || libder_obj_type_simple(oid) != BT_OID) goto out; oidp = libder_obj_data(oid, &oidsz); if (oidsz != sizeof(oid_ecpubkey) || memcmp(oidp, oid_ecpubkey, oidsz) != 0) return (1); /* Curve */ oid = libder_obj_child(params, 1); if (oid == NULL || libder_obj_type_simple(oid) != BT_OID) goto out; oidp = libder_obj_data(oid, &oidsz); if (ecc_extract_key_params(oidp, oidsz, rawparams) != 0) goto out; /* Finally, peel off the key material */ key = libder_obj_data(keydata, &keylen); if (ecc_extract_pubkey_string(key, keylen, rawkey, rawlen) != 0) goto out; rc = 0; out: libder_obj_free(root); libder_close(ctx); return (rc); } struct ecc_verify_cbdata { const struct pkgsign_ctx *sctx; FILE *keyfp; const unsigned char *key; size_t keylen; unsigned char *sig; size_t siglen; }; static int ecc_verify_internal(struct ecc_verify_cbdata *cbdata, const uint8_t *hash, size_t hashsz) { ec_pub_key pubkey; ec_params derparams; const struct ecc_sign_ctx *keyinfo = ECC_CCTX(cbdata->sctx); uint8_t keybuf[EC_PUB_KEY_MAX_SIZE]; uint8_t rawsig[EC_MAX_SIGLEN]; size_t keysz; int ret; uint8_t ecsiglen; keysz = MIN(sizeof(keybuf), cbdata->keylen / 2); keysz = sizeof(keybuf); if (ecc_extract_pubkey(cbdata->keyfp, cbdata->key, cbdata->keylen, keybuf, &keysz, &derparams) != 0) { warnx("failed to parse key"); return (1); } ret = ec_get_sig_len(&derparams, keyinfo->sig_alg, keyinfo->sig_hash, &ecsiglen); if (ret != 0) return (1); /* * Signatures are DER-encoded, whether by OpenSSL or pkg. */ if (ecc_extract_signature(cbdata->sig, cbdata->siglen, rawsig, ecsiglen) != 0) { warnx("failed to decode signature"); return (1); } ret = ec_pub_key_import_from_aff_buf(&pubkey, &derparams, keybuf, keysz, keyinfo->sig_alg); if (ret != 0) { warnx("failed to import key"); return (1); } ret = ec_verify(rawsig, ecsiglen, &pubkey, hash, hashsz, keyinfo->sig_alg, keyinfo->sig_hash, NULL, 0); if (ret != 0) { warnx("failed to verify signature"); return (1); } return (0); } static bool ecc_verify_data(const struct pkgsign_ctx *sctx, const char *data, size_t datasz, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { int ret; struct ecc_verify_cbdata cbdata; ret = 1; if (sigfile != NULL) { cbdata.keyfp = fopen(sigfile, "r"); if (cbdata.keyfp == NULL) { warn("fopen: %s", sigfile); return (false); } } else { cbdata.keyfp = NULL; cbdata.key = key; cbdata.keylen = keylen; } cbdata.sctx = sctx; cbdata.sig = sig; cbdata.siglen = siglen; ret = ecc_verify_internal(&cbdata, data, datasz); if (cbdata.keyfp != NULL) fclose(cbdata.keyfp); return (ret == 0); } static bool ecc_verify_cert(const struct pkgsign_ctx *sctx, int fd, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { bool ret; char *sha256; ret = false; if (lseek(fd, 0, SEEK_SET) == -1) { warn("lseek"); return (false); } if ((sha256 = sha256_fd(fd)) != NULL) { ret = ecc_verify_data(sctx, sha256, strlen(sha256), sigfile, key, keylen, sig, siglen); free(sha256); } return (ret); } static int ecc_new(const char *name __unused, struct pkgsign_ctx *sctx) { struct ecc_sign_ctx *keyinfo = ECC_CTX(sctx); int ret; ret = 1; if (STREQ(name, "ecc") || STREQ(name, "eddsa")) { keyinfo->sig_alg = EDDSA25519; keyinfo->sig_hash = SHA512; ret = import_params(&keyinfo->params, &wei25519_str_params); } else if (STREQ(name, "ecdsa")) { keyinfo->sig_alg = ECDSA; keyinfo->sig_hash = SHA256; ret = import_params(&keyinfo->params, &secp256k1_str_params); } if (ret != 0) return (1); return (0); } const struct pkgsign_ops pkgsign_ecc = { .pkgsign_ctx_size = sizeof(struct ecc_sign_ctx), .pkgsign_new = ecc_new, .pkgsign_verify_cert = ecc_verify_cert, .pkgsign_verify_data = ecc_verify_data, };