git: 8aeeab433170 - stable/14 - pkg: finish adding the ECC signer and signature type bits

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Sat, 11 Jan 2025 02:49:00 UTC
The branch stable/14 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=8aeeab4331704e6ce64b7c312932e1a4ac1ce7ec

commit 8aeeab4331704e6ce64b7c312932e1a4ac1ce7ec
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-01-01 21:10:28 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2025-01-11 02:48:26 +0000

    pkg: finish adding the ECC signer and signature type bits
    
    Signature types need to be parsed out of the key/signature information
    that we are presented with from the files we download.  We use that to
    understand whicher signer we need to dispatch to.
    
    The ECC signer is more-or-less lifted from pkg(8), with some changes to
    slim it down for pkg(7).
    
    Reviewed by:    bapt
    
    (cherry picked from commit 3d0a0dda3a7d57bbd4eaf65ba8da0f2a36089c0e)
---
 usr.sbin/pkg/Makefile |   7 +-
 usr.sbin/pkg/ecc.c    | 606 ++++++++++++++++++++++++++++++++++++++++++++++++++
 usr.sbin/pkg/pkg.c    | 115 ++++++++--
 usr.sbin/pkg/pkg.h    |   3 +
 4 files changed, 711 insertions(+), 20 deletions(-)

diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile
index af0a4d57ee90..b44905ee4976 100644
--- a/usr.sbin/pkg/Makefile
+++ b/usr.sbin/pkg/Makefile
@@ -22,11 +22,14 @@ CONFSNAME_${PKGCONF}=	${PKGCONF:C/\.conf.+$/.conf/}
 CONFSDIR=	/etc/pkg
 CONFSMODE=	644
 PROG=	pkg
-SRCS=	pkg.c rsa.c dns_utils.c config.c hash.c
+SRCS=	pkg.c rsa.c dns_utils.c config.c ecc.c hash.c
 MAN=	pkg.7
 
 CFLAGS+=-I${SRCTOP}/contrib/libucl/include
 .PATH:	${SRCTOP}/contrib/libucl/include
-LIBADD=	archive fetch ucl crypto ssl util md
+LIBADD=	archive der fetch pkgecc ucl crypto ssl util md
+
+CFLAGS+=-I${SRCTOP}/contrib/libder/libder
+CFLAGS+=-I${SRCTOP}/crypto/libecc/include
 
 .include <bsd.prog.mk>
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,
+};
diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c
index c39e210e1040..9aa8d7dfe774 100644
--- a/usr.sbin/pkg/pkg.c
+++ b/usr.sbin/pkg/pkg.c
@@ -56,6 +56,8 @@
 #include "config.h"
 #include "hash.h"
 
+#define	PKGSIGN_MARKER	"$PKGSIGN:"
+
 static const struct pkgsign_impl {
 	const char			*pi_name;
 	const struct pkgsign_ops	*pi_ops;
@@ -64,6 +66,18 @@ static const struct pkgsign_impl {
 		.pi_name = "rsa",
 		.pi_ops = &pkgsign_rsa,
 	},
+	{
+		.pi_name = "ecc",
+		.pi_ops = &pkgsign_ecc,
+	},
+	{
+		.pi_name = "ecdsa",
+		.pi_ops = &pkgsign_ecc,
+	},
+	{
+		.pi_name = "eddsa",
+		.pi_ops = &pkgsign_ecc,
+	},
 };
 
 typedef enum {
@@ -489,11 +503,41 @@ pkg_read_fd(int fd, size_t *osz)
 	return (obuf);
 }
 
+/*
+ * Returns a copy of the signature type stored on the heap, and advances *bufp
+ * past the type.
+ */
+static char *
+parse_sigtype(char **bufp, size_t *bufszp)
+{
+	char *buf = *bufp;
+	char *endp;
+	char *sigtype;
+	size_t bufsz = *bufszp;
+
+	if (bufsz <= sizeof(PKGSIGN_MARKER) - 1 ||
+	    strncmp(buf, PKGSIGN_MARKER, sizeof(PKGSIGN_MARKER) - 1) != 0)
+		goto dflt;
+
+	buf += sizeof(PKGSIGN_MARKER) - 1;
+	endp = strchr(buf, '$');
+	if (endp == NULL)
+		goto dflt;
+
+	sigtype = strndup(buf, endp - buf);
+	*bufp = endp + 1;
+	*bufszp -= *bufp - buf;
+
+	return (sigtype);
+dflt:
+	return (strdup("rsa"));
+}
+
 static struct pubkey *
 read_pubkey(int fd)
 {
 	struct pubkey *pk;
-	char *sigb;
+	char *osigb, *sigb, *sigtype;
 	size_t sigsz;
 
 	if (lseek(fd, 0, 0) == -1) {
@@ -501,13 +545,15 @@ read_pubkey(int fd)
 		return (NULL);
 	}
 
-	sigb = pkg_read_fd(fd, &sigsz);
+	osigb = sigb = pkg_read_fd(fd, &sigsz);
+	sigtype = parse_sigtype(&sigb, &sigsz);
 
 	pk = calloc(1, sizeof(struct pubkey));
 	pk->siglen = sigsz;
 	pk->sig = calloc(1, pk->siglen);
 	memcpy(pk->sig, sigb, pk->siglen);
-	free(sigb);
+	pk->sigtype = sigtype;
+	free(osigb);
 
 	return (pk);
 }
@@ -516,17 +562,18 @@ static struct sig_cert *
 parse_cert(int fd) {
 	int my_fd;
 	struct sig_cert *sc;
-	FILE *fp, *sigfp, *certfp, *tmpfp;
+	FILE *fp, *sigfp, *certfp, *tmpfp, *typefp;
 	char *line;
-	char *sig, *cert;
-	size_t linecap, sigsz, certsz;
+	char *sig, *cert, *type;
+	size_t linecap, sigsz, certsz, typesz;
 	ssize_t linelen;
+	bool end_seen;
 
 	sc = NULL;
 	line = NULL;
 	linecap = 0;
-	sig = cert = NULL;
-	sigfp = certfp = tmpfp = NULL;
+	sig = cert = type = NULL;
+	sigfp = certfp = tmpfp = typefp = NULL;
 
 	if (lseek(fd, 0, 0) == -1) {
 		warn("lseek");
@@ -545,22 +592,30 @@ parse_cert(int fd) {
 		return (NULL);
 	}
 
-	sigsz = certsz = 0;
+	sigsz = certsz = typesz = 0;
 	sigfp = open_memstream(&sig, &sigsz);
 	if (sigfp == NULL)
 		err(EXIT_FAILURE, "open_memstream()");
 	certfp = open_memstream(&cert, &certsz);
 	if (certfp == NULL)
 		err(EXIT_FAILURE, "open_memstream()");
+	typefp = open_memstream(&type, &typesz);
+	if (typefp == NULL)
+		err(EXIT_FAILURE, "open_memstream()");
 
+	end_seen = false;
 	while ((linelen = getline(&line, &linecap, fp)) > 0) {
 		if (strcmp(line, "SIGNATURE\n") == 0) {
 			tmpfp = sigfp;
 			continue;
+		} else if (strcmp(line, "TYPE\n") == 0) {
+			tmpfp = typefp;
+			continue;
 		} else if (strcmp(line, "CERT\n") == 0) {
 			tmpfp = certfp;
 			continue;
 		} else if (strcmp(line, "END\n") == 0) {
+			end_seen = true;
 			break;
 		}
 		if (tmpfp != NULL)
@@ -570,11 +625,28 @@ parse_cert(int fd) {
 	fclose(fp);
 	fclose(sigfp);
 	fclose(certfp);
+	fclose(typefp);
 
 	sc = calloc(1, sizeof(struct sig_cert));
 	sc->siglen = sigsz -1; /* Trim out unrelated trailing newline */
 	sc->sig = sig;
 
+	if (typesz == 0) {
+		sc->type = strdup("rsa");
+		free(type);
+	} else {
+		assert(type[typesz - 1] == '\n');
+		type[typesz - 1] = '\0';
+		sc->type = type;
+	}
+
+	/*
+	 * cert could be DER-encoded rather than PEM, so strip off any trailing
+	 * END marker if we ran over it.
+	 */
+	if (!end_seen && certsz > 4 &&
+	    strcmp(&cert[certsz - 4], "END\n") == 0)
+		certsz -= 4;
 	sc->certlen = certsz;
 	sc->cert = cert;
 
@@ -611,16 +683,23 @@ verify_pubsignature(int fd_pkg, int fd_sig)
 		goto cleanup;
 	}
 
-	/* Future types shouldn't do this. */
-	if ((data = sha256_fd(fd_pkg)) == NULL) {
-		warnx("Error creating SHA256 hash for package");
-		goto cleanup;
-	}
+	if (strcmp(pk->sigtype, "rsa") == 0) {
+		/* Future types shouldn't do this. */
+		if ((data = sha256_fd(fd_pkg)) == NULL) {
+			warnx("Error creating SHA256 hash for package");
+			goto cleanup;
+		}
 
-	datasz = strlen(data);
+		datasz = strlen(data);
+	} else {
+		if ((data = pkg_read_fd(fd_pkg, &datasz)) == NULL) {
+			warnx("Failed to read package data");
+			goto cleanup;
+		}
+	}
 
-	if (pkgsign_new("rsa", &sctx) != 0) {
-		warnx("Failed to fetch 'rsa' signer");
+	if (pkgsign_new(pk->sigtype, &sctx) != 0) {
+		warnx("Failed to fetch '%s' signer", pk->sigtype);
 		goto cleanup;
 	}
 
@@ -723,7 +802,7 @@ verify_signature(int fd_pkg, int fd_sig)
 		goto cleanup;
 	}
 
-	if (pkgsign_new("rsa", &sctx) != 0) {
+	if (pkgsign_new(sc->type, &sctx) != 0) {
 		fprintf(stderr, "Failed to fetch 'rsa' signer\n");
 		goto cleanup;
 	}
diff --git a/usr.sbin/pkg/pkg.h b/usr.sbin/pkg/pkg.h
index b9fe9b5fa566..f74f97ce795b 100644
--- a/usr.sbin/pkg/pkg.h
+++ b/usr.sbin/pkg/pkg.h
@@ -51,10 +51,12 @@ struct pkgsign_ops {
 	pkgsign_verify_data_cb	*pkgsign_verify_data;
 };
 
+extern const struct pkgsign_ops pkgsign_ecc;
 extern const struct pkgsign_ops pkgsign_rsa;
 
 struct sig_cert {
 	char *name;
+	char *type;
 	unsigned char *sig;
 	int siglen;
 	unsigned char *cert;
@@ -63,6 +65,7 @@ struct sig_cert {
 };
 
 struct pubkey {
+	char *sigtype;
 	unsigned char *sig;
 	int siglen;
 };