diff options
Diffstat (limited to 'usr.sbin/pkg/ecc.c')
| -rw-r--r-- | usr.sbin/pkg/ecc.c | 606 | 
1 files changed, 606 insertions, 0 deletions
| diff --git a/usr.sbin/pkg/ecc.c b/usr.sbin/pkg/ecc.c new file mode 100644 index 000000000000..01ce020bdba0 --- /dev/null +++ b/usr.sbin/pkg/ecc.c @@ -0,0 +1,606 @@ +/*- + * Copyright (c) 2011-2013 Baptiste Daroussin <bapt@FreeBSD.org> + * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org> + * All rights reserved. + * Copyright (c) 2021 Kyle Evans <kevans@FreeBSD.org> + * + * 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 <sys/stat.h> +#include <sys/param.h> + +#include <assert.h> +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +#include <libder.h> + +#define	WITH_STDLIB +#include <libecc/libsig.h> +#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, +}; | 
