aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/pkg')
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.latest23
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.quarterly23
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.quarterly-release23
-rw-r--r--usr.sbin/pkg/Makefile34
-rw-r--r--usr.sbin/pkg/Makefile.depend23
-rw-r--r--usr.sbin/pkg/config.c708
-rw-r--r--usr.sbin/pkg/config.h90
-rw-r--r--usr.sbin/pkg/dns_utils.c223
-rw-r--r--usr.sbin/pkg/dns_utils.h46
-rw-r--r--usr.sbin/pkg/ecc.c606
-rw-r--r--usr.sbin/pkg/hash.c47
-rw-r--r--usr.sbin/pkg/hash.h32
-rw-r--r--usr.sbin/pkg/pkg.7336
-rw-r--r--usr.sbin/pkg/pkg.c1264
-rw-r--r--usr.sbin/pkg/pkg.h75
-rw-r--r--usr.sbin/pkg/rsa.c175
16 files changed, 3728 insertions, 0 deletions
diff --git a/usr.sbin/pkg/FreeBSD.conf.latest b/usr.sbin/pkg/FreeBSD.conf.latest
new file mode 100644
index 000000000000..ac1636386942
--- /dev/null
+++ b/usr.sbin/pkg/FreeBSD.conf.latest
@@ -0,0 +1,23 @@
+#
+# To disable a repository, instead of modifying or removing this file,
+# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
+#
+# mkdir -p /usr/local/etc/pkg/repos
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+#
+
+FreeBSD-ports: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
+FreeBSD-ports-kmods: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly b/usr.sbin/pkg/FreeBSD.conf.quarterly
new file mode 100644
index 000000000000..4e26582c6981
--- /dev/null
+++ b/usr.sbin/pkg/FreeBSD.conf.quarterly
@@ -0,0 +1,23 @@
+#
+# To disable a repository, instead of modifying or removing this file,
+# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
+#
+# mkdir -p /usr/local/etc/pkg/repos
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+#
+
+FreeBSD-ports: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
+FreeBSD-ports-kmods: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly-release b/usr.sbin/pkg/FreeBSD.conf.quarterly-release
new file mode 100644
index 000000000000..b4a78009f7d2
--- /dev/null
+++ b/usr.sbin/pkg/FreeBSD.conf.quarterly-release
@@ -0,0 +1,23 @@
+#
+# To disable a repository, instead of modifying or removing this file,
+# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
+#
+# mkdir -p /usr/local/etc/pkg/repos
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+#
+
+FreeBSD-ports: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
+FreeBSD-ports-kmods: {
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly_${VERSION_MINOR}",
+ mirror_type: "srv",
+ signature_type: "fingerprints",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile
new file mode 100644
index 000000000000..0420065bb7eb
--- /dev/null
+++ b/usr.sbin/pkg/Makefile
@@ -0,0 +1,34 @@
+PACKAGE= pkg-bootstrap
+
+_BRANCH!= ${MAKE} -C ${SRCTOP}/release -V BRANCH
+BRANCH?= ${_BRANCH}
+.if ${BRANCH:MCURRENT} != ""
+PKGCONFBRANCH?= latest
+.else
+. if ${BRANCH:MBETA*} || ${BRANCH:MRC*} || ${BRANCH:MRELEASE*}
+PKGCONFBRANCH?= quarterly-release
+. else
+. if ${MACHINE} != "amd64" && ${MACHINE} != "i386" && ${MACHINE} != "arm64"
+PKGCONFBRANCH?= quarterly
+. else
+PKGCONFBRANCH?= latest
+. endif
+. endif
+.endif
+PKGCONF?= FreeBSD.conf.${PKGCONFBRANCH}
+CONFS= ${PKGCONF}
+CONFSNAME_${PKGCONF}= ${PKGCONF:C/\.conf.+$/.conf/}
+CONFSDIR= /etc/pkg
+CONFSMODE= 644
+PROG= pkg
+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 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/Makefile.depend b/usr.sbin/pkg/Makefile.depend
new file mode 100644
index 000000000000..5b6639f85f7f
--- /dev/null
+++ b/usr.sbin/pkg/Makefile.depend
@@ -0,0 +1,23 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libarchive \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libfetch \
+ lib/libmd \
+ lib/libucl \
+ lib/libutil \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c
new file mode 100644
index 000000000000..6649e75b7f6b
--- /dev/null
+++ b/usr.sbin/pkg/config.c
@@ -0,0 +1,708 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014-2025 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/param.h>
+#include <sys/queue.h>
+#include <sys/utsname.h>
+#include <sys/sysctl.h>
+
+#include <dirent.h>
+#include <ucl.h>
+#include <err.h>
+#include <errno.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "config.h"
+
+struct config_value {
+ char *value;
+ STAILQ_ENTRY(config_value) next;
+};
+
+struct config_entry {
+ uint8_t type;
+ const char *key;
+ const char *val;
+ char *value;
+ STAILQ_HEAD(, config_value) *list;
+ bool envset;
+ bool main_only; /* Only set in pkg.conf. */
+};
+
+static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories);
+
+static struct config_entry c[] = {
+ [PACKAGESITE] = {
+ PKG_CONFIG_STRING,
+ "PACKAGESITE",
+ URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [ABI] = {
+ PKG_CONFIG_STRING,
+ "ABI",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [MIRROR_TYPE] = {
+ PKG_CONFIG_STRING,
+ "MIRROR_TYPE",
+ "SRV",
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [ASSUME_ALWAYS_YES] = {
+ PKG_CONFIG_BOOL,
+ "ASSUME_ALWAYS_YES",
+ "NO",
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [SIGNATURE_TYPE] = {
+ PKG_CONFIG_STRING,
+ "SIGNATURE_TYPE",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [FINGERPRINTS] = {
+ PKG_CONFIG_STRING,
+ "FINGERPRINTS",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [REPOS_DIR] = {
+ PKG_CONFIG_LIST,
+ "REPOS_DIR",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [PUBKEY] = {
+ PKG_CONFIG_STRING,
+ "PUBKEY",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false
+ },
+ [PKG_ENV] = {
+ PKG_CONFIG_OBJECT,
+ "PKG_ENV",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false,
+ }
+};
+
+static char *
+pkg_get_myabi(void)
+{
+ struct utsname uts;
+ char machine_arch[255];
+ char *abi;
+ size_t len;
+ int error;
+
+ error = uname(&uts);
+ if (error)
+ return (NULL);
+
+ len = sizeof(machine_arch);
+ error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
+ if (error)
+ return (NULL);
+ machine_arch[len] = '\0';
+
+ /*
+ * Use __FreeBSD_version rather than kernel version (uts.release) for
+ * use in jails. This is equivalent to the value of uname -U.
+ */
+ error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
+ machine_arch);
+ if (error < 0)
+ return (NULL);
+
+ return (abi);
+}
+
+static void
+subst_packagesite(const char *abi)
+{
+ char *newval;
+ const char *variable_string;
+ const char *oldval;
+
+ if (c[PACKAGESITE].value != NULL)
+ oldval = c[PACKAGESITE].value;
+ else
+ oldval = c[PACKAGESITE].val;
+
+ if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
+ return;
+
+ asprintf(&newval, "%.*s%s%s",
+ (int)(variable_string - oldval), oldval, abi,
+ variable_string + strlen("${ABI}"));
+ if (newval == NULL)
+ errx(EXIT_FAILURE, "asprintf");
+
+ free(c[PACKAGESITE].value);
+ c[PACKAGESITE].value = newval;
+}
+
+static int
+boolstr_to_bool(const char *str)
+{
+ if (str != NULL && (strcasecmp(str, "true") == 0 ||
+ strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
+ str[0] == '1'))
+ return (true);
+
+ return (false);
+}
+
+static void
+config_parse(const ucl_object_t *obj)
+{
+ FILE *buffp;
+ char *buf = NULL;
+ size_t bufsz = 0;
+ const ucl_object_t *cur, *seq, *tmp;
+ ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL;
+ struct config_entry *temp_config;
+ struct config_value *cv;
+ const char *key, *evkey;
+ int i;
+ size_t j;
+
+ /* Temporary config for configs that may be disabled. */
+ temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
+ buffp = open_memstream(&buf, &bufsz);
+ if (buffp == NULL)
+ err(EXIT_FAILURE, "open_memstream()");
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+ if (key == NULL)
+ continue;
+ if (buf != NULL)
+ memset(buf, 0, bufsz);
+ rewind(buffp);
+
+ for (j = 0; j < strlen(key); ++j)
+ fputc(toupper(key[j]), buffp);
+ fflush(buffp);
+
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ if (strcmp(buf, c[i].key) == 0)
+ break;
+ }
+
+ /* Silently skip unknown keys to be future compatible. */
+ if (i == CONFIG_SIZE)
+ continue;
+
+ /* env has priority over config file */
+ if (c[i].envset)
+ continue;
+
+ /* Parse sequence value ["item1", "item2"] */
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ if (cur->type != UCL_ARRAY) {
+ warnx("Skipping invalid array "
+ "value for %s.\n", c[i].key);
+ continue;
+ }
+ temp_config[i].list =
+ malloc(sizeof(*temp_config[i].list));
+ STAILQ_INIT(temp_config[i].list);
+
+ while ((seq = ucl_iterate_object(cur, &itseq, true))) {
+ if (seq->type != UCL_STRING)
+ continue;
+ cv = malloc(sizeof(struct config_value));
+ cv->value =
+ strdup(ucl_object_tostring(seq));
+ STAILQ_INSERT_TAIL(temp_config[i].list, cv,
+ next);
+ }
+ break;
+ case PKG_CONFIG_BOOL:
+ temp_config[i].value =
+ strdup(ucl_object_toboolean(cur) ? "yes" : "no");
+ break;
+ case PKG_CONFIG_OBJECT:
+ if (strcmp(c[i].key, "PKG_ENV") == 0) {
+ while ((tmp =
+ ucl_iterate_object(cur, &it_obj, true))) {
+ evkey = ucl_object_key(tmp);
+ if (evkey != NULL && *evkey != '\0') {
+ setenv(evkey, ucl_object_tostring_forced(tmp), 1);
+ }
+ }
+ }
+ break;
+ default:
+ /* Normal string value. */
+ temp_config[i].value = strdup(ucl_object_tostring(cur));
+ break;
+ }
+ }
+
+ /* Repo is enabled, copy over all settings from temp_config. */
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ if (c[i].envset)
+ continue;
+ /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
+ if (c[i].main_only == true)
+ continue;
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ c[i].list = temp_config[i].list;
+ break;
+ default:
+ c[i].value = temp_config[i].value;
+ break;
+ }
+ }
+
+ free(temp_config);
+ fclose(buffp);
+ free(buf);
+}
+
+
+static void
+parse_mirror_type(struct repository *r, const char *mt)
+{
+ if (strcasecmp(mt, "srv") == 0)
+ r->mirror_type = MIRROR_SRV;
+ r->mirror_type = MIRROR_NONE;
+}
+
+static void
+repo_free(struct repository *r)
+{
+ free(r->name);
+ free(r->url);
+ free(r->fingerprints);
+ free(r->pubkey);
+ free(r);
+}
+
+static bool
+parse_signature_type(struct repository *repo, const char *st)
+{
+ if (strcasecmp(st, "FINGERPRINTS") == 0)
+ repo->signature_type = SIGNATURE_FINGERPRINT;
+ else if (strcasecmp(st, "PUBKEY") == 0)
+ repo->signature_type = SIGNATURE_PUBKEY;
+ else if (strcasecmp(st, "NONE") == 0)
+ repo->signature_type = SIGNATURE_NONE;
+ else {
+ warnx("Signature type %s is not supported for bootstrapping,"
+ " ignoring repository %s", st, repo->name);
+ return (false);
+ }
+ return (true);
+}
+
+static struct repository *
+find_repository(const char *name)
+{
+ struct repository *repo;
+ STAILQ_FOREACH(repo, &repositories, next) {
+ if (strcmp(repo->name, name) == 0)
+ return (repo);
+ }
+ return (NULL);
+}
+
+static void
+parse_repo(const ucl_object_t *o)
+{
+ const ucl_object_t *cur;
+ const char *key, *reponame;
+ ucl_object_iter_t it = NULL;
+ bool newrepo = false;
+ struct repository *repo;
+
+ reponame = ucl_object_key(o);
+ repo = find_repository(reponame);
+ if (repo == NULL) {
+ repo = calloc(1, sizeof(struct repository));
+ if (repo == NULL)
+ err(EXIT_FAILURE, "calloc");
+
+ repo->name = strdup(reponame);
+ if (repo->name == NULL)
+ err(EXIT_FAILURE, "strdup");
+ newrepo = true;
+ }
+ while ((cur = ucl_iterate_object(o, &it, true))) {
+ key = ucl_object_key(cur);
+ if (key == NULL)
+ continue;
+ if (strcasecmp(key, "url") == 0) {
+ free(repo->url);
+ repo->url = strdup(ucl_object_tostring(cur));
+ if (repo->url == NULL)
+ err(EXIT_FAILURE, "strdup");
+ } else if (strcasecmp(key, "mirror_type") == 0) {
+ parse_mirror_type(repo, ucl_object_tostring(cur));
+ } else if (strcasecmp(key, "signature_type") == 0) {
+ if (!parse_signature_type(repo, ucl_object_tostring(cur))) {
+ if (newrepo)
+ repo_free(repo);
+ else
+ STAILQ_REMOVE(&repositories, repo, repository, next);
+ return;
+ }
+ } else if (strcasecmp(key, "fingerprints") == 0) {
+ free(repo->fingerprints);
+ repo->fingerprints = strdup(ucl_object_tostring(cur));
+ if (repo->fingerprints == NULL)
+ err(EXIT_FAILURE, "strdup");
+ } else if (strcasecmp(key, "pubkey") == 0) {
+ free(repo->pubkey);
+ repo->pubkey = strdup(ucl_object_tostring(cur));
+ if (repo->pubkey == NULL)
+ err(EXIT_FAILURE, "strdup");
+ } else if (strcasecmp(key, "enabled") == 0) {
+ if ((cur->type != UCL_BOOLEAN) ||
+ !ucl_object_toboolean(cur)) {
+ if (newrepo)
+ repo_free(repo);
+ else
+ STAILQ_REMOVE(&repositories, repo, repository, next);
+ return;
+ }
+ }
+ }
+ /* At least we need an url */
+ if (repo->url == NULL) {
+ repo_free(repo);
+ return;
+ }
+ if (newrepo)
+ STAILQ_INSERT_TAIL(&repositories, repo, next);
+ return;
+}
+
+/*-
+ * Parse new repo style configs in style:
+ * Name:
+ * URL:
+ * MIRROR_TYPE:
+ * etc...
+ */
+static void
+parse_repo_file(ucl_object_t *obj, const char *requested_repo)
+{
+ ucl_object_iter_t it = NULL;
+ const ucl_object_t *cur;
+ const char *key;
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+
+ if (key == NULL)
+ continue;
+
+ if (cur->type != UCL_OBJECT)
+ continue;
+
+ if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
+ continue;
+ parse_repo(cur);
+ }
+}
+
+
+static int
+read_conf_file(const char *confpath, const char *requested_repo,
+ pkg_conf_file_t conftype)
+{
+ struct ucl_parser *p;
+ ucl_object_t *obj = NULL;
+ char *abi = pkg_get_myabi(), *major, *minor;
+ struct utsname uts;
+ int ret;
+
+ if (uname(&uts))
+ err(EXIT_FAILURE, "uname");
+ if (abi == NULL)
+ errx(EXIT_FAILURE, "Failed to determine ABI");
+
+ p = ucl_parser_new(0);
+ asprintf(&major, "%d", __FreeBSD_version/100000);
+ if (major == NULL)
+ err(EXIT_FAILURE, "asprintf");
+ asprintf(&minor, "%d", (__FreeBSD_version / 1000) % 100);
+ if (minor == NULL)
+ err(EXIT_FAILURE, "asprintf");
+ ucl_parser_register_variable(p, "ABI", abi);
+ ucl_parser_register_variable(p, "OSNAME", uts.sysname);
+ ucl_parser_register_variable(p, "RELEASE", major);
+ ucl_parser_register_variable(p, "VERSION_MAJOR", major);
+ ucl_parser_register_variable(p, "VERSION_MINOR", minor);
+
+ if (!ucl_parser_add_file(p, confpath)) {
+ if (errno != ENOENT)
+ errx(EXIT_FAILURE, "Unable to parse configuration "
+ "file %s: %s", confpath, ucl_parser_get_error(p));
+ /* no configuration present */
+ ret = 1;
+ goto out;
+ }
+
+ obj = ucl_parser_get_object(p);
+ if (obj->type != UCL_OBJECT)
+ warnx("Invalid configuration format, ignoring the "
+ "configuration file %s", confpath);
+ else {
+ if (conftype == CONFFILE_PKG)
+ config_parse(obj);
+ else if (conftype == CONFFILE_REPO)
+ parse_repo_file(obj, requested_repo);
+ }
+ ucl_object_unref(obj);
+
+ ret = 0;
+out:
+ ucl_parser_free(p);
+ free(abi);
+ free(major);
+ free(minor);
+
+ return (ret);
+}
+
+static void
+load_repositories(const char *repodir, const char *requested_repo)
+{
+ struct dirent *ent;
+ DIR *d;
+ char *p;
+ size_t n;
+ char path[MAXPATHLEN];
+
+ if ((d = opendir(repodir)) == NULL)
+ return;
+
+ while ((ent = readdir(d))) {
+ /* Trim out 'repos'. */
+ if ((n = strlen(ent->d_name)) <= 5)
+ continue;
+ p = &ent->d_name[n - 5];
+ if (strcmp(p, ".conf") == 0) {
+ snprintf(path, sizeof(path), "%s%s%s",
+ repodir,
+ repodir[strlen(repodir) - 1] == '/' ? "" : "/",
+ ent->d_name);
+ if (access(path, F_OK) != 0)
+ continue;
+ if (read_conf_file(path, requested_repo,
+ CONFFILE_REPO)) {
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ closedir(d);
+}
+
+int
+config_init(const char *requested_repo)
+{
+ char *val;
+ int i;
+ const char *localbase;
+ char *abi, *env_list_item;
+ char confpath[MAXPATHLEN];
+ struct config_value *cv;
+
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ val = getenv(c[i].key);
+ if (val != NULL) {
+ c[i].envset = true;
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ /* Split up comma-separated items from env. */
+ c[i].list = malloc(sizeof(*c[i].list));
+ STAILQ_INIT(c[i].list);
+ for (env_list_item = strtok(val, ",");
+ env_list_item != NULL;
+ env_list_item = strtok(NULL, ",")) {
+ cv =
+ malloc(sizeof(struct config_value));
+ cv->value =
+ strdup(env_list_item);
+ STAILQ_INSERT_TAIL(c[i].list, cv,
+ next);
+ }
+ break;
+ default:
+ c[i].val = val;
+ break;
+ }
+ }
+ }
+
+ /* Read LOCALBASE/etc/pkg.conf first. */
+ localbase = getlocalbase();
+ snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
+
+ if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
+ CONFFILE_PKG))
+ goto finalize;
+
+ /* Then read in all repos from REPOS_DIR list of directories. */
+ if (c[REPOS_DIR].list == NULL) {
+ c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
+ STAILQ_INIT(c[REPOS_DIR].list);
+ cv = malloc(sizeof(struct config_value));
+ cv->value = strdup("/etc/pkg");
+ STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
+ cv = malloc(sizeof(struct config_value));
+ if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
+ goto finalize;
+ STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
+ }
+
+ STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
+ load_repositories(cv->value, requested_repo);
+
+finalize:
+ if (c[ABI].val == NULL && c[ABI].value == NULL) {
+ abi = pkg_get_myabi();
+ if (abi == NULL)
+ errx(EXIT_FAILURE, "Failed to determine the system "
+ "ABI");
+ c[ABI].val = abi;
+ }
+
+ return (0);
+}
+
+int
+config_string(pkg_config_key k, const char **val)
+{
+ if (c[k].type != PKG_CONFIG_STRING)
+ return (-1);
+
+ if (c[k].value != NULL)
+ *val = c[k].value;
+ else
+ *val = c[k].val;
+
+ return (0);
+}
+
+int
+config_bool(pkg_config_key k, bool *val)
+{
+ const char *value;
+
+ if (c[k].type != PKG_CONFIG_BOOL)
+ return (-1);
+
+ *val = false;
+
+ if (c[k].value != NULL)
+ value = c[k].value;
+ else
+ value = c[k].val;
+
+ if (boolstr_to_bool(value))
+ *val = true;
+
+ return (0);
+}
+
+struct repositories *
+config_get_repositories(void)
+{
+ if (STAILQ_EMPTY(&repositories)) {
+ /* Fall back to PACKAGESITE - deprecated - */
+ struct repository *r = calloc(1, sizeof(*r));
+ if (r == NULL)
+ err(EXIT_FAILURE, "calloc");
+ r->name = strdup("fallback");
+ if (r->name == NULL)
+ err(EXIT_FAILURE, "strdup");
+ subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
+ r->url = c[PACKAGESITE].value;
+ if (c[SIGNATURE_TYPE].value != NULL)
+ if (!parse_signature_type(r, c[SIGNATURE_TYPE].value))
+ exit(EXIT_FAILURE);
+ if (c[MIRROR_TYPE].value != NULL)
+ parse_mirror_type(r, c[MIRROR_TYPE].value);
+ if (c[PUBKEY].value != NULL)
+ r->pubkey = c[PUBKEY].value;
+ if (c[FINGERPRINTS].value != NULL)
+ r->fingerprints = c[FINGERPRINTS].value;
+ STAILQ_INSERT_TAIL(&repositories, r, next);
+ }
+ return (&repositories);
+}
+
+void
+config_finish(void) {
+ int i;
+
+ for (i = 0; i < CONFIG_SIZE; i++)
+ free(c[i].value);
+}
diff --git a/usr.sbin/pkg/config.h b/usr.sbin/pkg/config.h
new file mode 100644
index 000000000000..26f3ff79541b
--- /dev/null
+++ b/usr.sbin/pkg/config.h
@@ -0,0 +1,90 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Baptiste Daroussin <bapt@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#ifndef _PKG_CONFIG_H
+#define _PKG_CONFIG_H
+
+#include <paths.h>
+#include <sys/queue.h>
+
+#define URL_SCHEME_PREFIX "pkg+"
+
+typedef enum {
+ PACKAGESITE = 0,
+ ABI,
+ MIRROR_TYPE,
+ ASSUME_ALWAYS_YES,
+ SIGNATURE_TYPE,
+ FINGERPRINTS,
+ REPOS_DIR,
+ PUBKEY,
+ PKG_ENV,
+ CONFIG_SIZE
+} pkg_config_key;
+
+typedef enum {
+ PKG_CONFIG_STRING=0,
+ PKG_CONFIG_BOOL,
+ PKG_CONFIG_LIST,
+ PKG_CONFIG_OBJECT
+} pkg_config_t;
+
+typedef enum {
+ CONFFILE_PKG=0,
+ CONFFILE_REPO,
+} pkg_conf_file_t;
+
+typedef enum {
+ SIGNATURE_NONE = 0,
+ SIGNATURE_FINGERPRINT,
+ SIGNATURE_PUBKEY,
+} signature_t;
+
+typedef enum {
+ MIRROR_NONE = 0,
+ MIRROR_SRV,
+} mirror_t;
+
+struct repository {
+ char *name;
+ char *url;
+ mirror_t mirror_type;
+ signature_t signature_type;
+ char *fingerprints;
+ char *pubkey;
+ STAILQ_ENTRY(repository) next;
+};
+STAILQ_HEAD(repositories, repository);
+
+int config_init(const char *);
+void config_finish(void);
+int config_string(pkg_config_key, const char **);
+int config_bool(pkg_config_key, bool *);
+struct repositories *config_get_repositories(void);
+
+#endif
diff --git a/usr.sbin/pkg/dns_utils.c b/usr.sbin/pkg/dns_utils.c
new file mode 100644
index 000000000000..dcf4b5599532
--- /dev/null
+++ b/usr.sbin/pkg/dns_utils.c
@@ -0,0 +1,223 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2013 Baptiste Daroussin <bapt@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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,
+ * without modification, immediately at the beginning of the file.
+ * 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 ``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 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/cdefs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+
+#include "dns_utils.h"
+
+typedef union {
+ HEADER hdr;
+ unsigned char buf[1024];
+} dns_query;
+
+static int
+srv_priority_cmp(const void *a, const void *b)
+{
+ const struct dns_srvinfo *da, *db;
+ unsigned int r, l;
+
+ da = *(struct dns_srvinfo * const *)a;
+ db = *(struct dns_srvinfo * const *)b;
+
+ l = da->priority;
+ r = db->priority;
+
+ return ((l > r) - (l < r));
+}
+
+static int
+srv_final_cmp(const void *a, const void *b)
+{
+ const struct dns_srvinfo *da, *db;
+ unsigned int r, l, wr, wl;
+ int res;
+
+ da = *(struct dns_srvinfo * const *)a;
+ db = *(struct dns_srvinfo * const *)b;
+
+ l = da->priority;
+ r = db->priority;
+
+ res = ((l > r) - (l < r));
+
+ if (res == 0) {
+ wl = da->finalweight;
+ wr = db->finalweight;
+ res = ((wr > wl) - (wr < wl));
+ }
+
+ return (res);
+}
+
+static void
+compute_weight(struct dns_srvinfo **d, int first, int last)
+{
+ int i, j, totalweight;
+ int *chosen;
+
+ totalweight = 0;
+
+ for (i = 0; i <= last; i++)
+ totalweight += d[i]->weight;
+
+ if (totalweight == 0)
+ return;
+
+ chosen = malloc(sizeof(int) * (last - first + 1));
+
+ for (i = 0; i <= last; i++) {
+ for (;;) {
+ chosen[i] = arc4random_uniform(d[i]->weight * 100 /
+ totalweight);
+ for (j = 0; j < i; j++) {
+ if (chosen[i] == chosen[j])
+ break;
+ }
+ if (j == i) {
+ d[i]->finalweight = chosen[i];
+ break;
+ }
+ }
+ }
+
+ free(chosen);
+}
+
+struct dns_srvinfo *
+dns_getsrvinfo(const char *zone)
+{
+ struct dns_srvinfo **res, *first;
+ unsigned char *end, *p;
+ char host[MAXHOSTNAMELEN];
+ dns_query q;
+ int len, qdcount, ancount, n, i, f, l;
+ unsigned int type, class, ttl, priority, weight, port;
+
+ if ((len = res_query(zone, C_IN, T_SRV, q.buf, sizeof(q.buf))) == -1 ||
+ len < (int)sizeof(HEADER))
+ return (NULL);
+
+ qdcount = ntohs(q.hdr.qdcount);
+ ancount = ntohs(q.hdr.ancount);
+
+ end = q.buf + len;
+ p = q.buf + sizeof(HEADER);
+
+ while(qdcount > 0 && p < end) {
+ qdcount--;
+ if((len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN)) < 0)
+ return (NULL);
+ p += len + NS_QFIXEDSZ;
+ }
+
+ res = calloc(ancount, sizeof(struct dns_srvinfo *));
+ if (res == NULL)
+ return (NULL);
+
+ n = 0;
+ while (ancount > 0 && p < end) {
+ ancount--;
+ len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN);
+ if (len < 0) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return NULL;
+ }
+
+ p += len;
+
+ NS_GET16(type, p);
+ NS_GET16(class, p);
+ NS_GET32(ttl, p);
+ NS_GET16(len, p);
+
+ if (type != T_SRV) {
+ p += len;
+ continue;
+ }
+
+ NS_GET16(priority, p);
+ NS_GET16(weight, p);
+ NS_GET16(port, p);
+
+ len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN);
+ if (len < 0) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return (NULL);
+ }
+
+ res[n] = malloc(sizeof(struct dns_srvinfo));
+ if (res[n] == NULL) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return (NULL);
+ }
+ res[n]->type = type;
+ res[n]->class = class;
+ res[n]->ttl = ttl;
+ res[n]->priority = priority;
+ res[n]->weight = weight;
+ res[n]->port = port;
+ res[n]->next = NULL;
+ strlcpy(res[n]->host, host, MAXHOSTNAMELEN);
+
+ p += len;
+ n++;
+ }
+
+ qsort(res, n, sizeof(res[0]), srv_priority_cmp);
+
+ priority = f = l = 0;
+ for (i = 0; i < n; i++) {
+ if (res[i]->priority != priority) {
+ if (f != l)
+ compute_weight(res, f, l);
+ f = i;
+ priority = res[i]->priority;
+ }
+ l = i;
+ }
+
+ qsort(res, n, sizeof(res[0]), srv_final_cmp);
+
+ for (i = 0; i < n - 1; i++)
+ res[i]->next = res[i + 1];
+
+ first = res[0];
+ free(res);
+
+ return (first);
+}
diff --git a/usr.sbin/pkg/dns_utils.h b/usr.sbin/pkg/dns_utils.h
new file mode 100644
index 000000000000..23b8a30140ab
--- /dev/null
+++ b/usr.sbin/pkg/dns_utils.h
@@ -0,0 +1,46 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 Baptiste Daroussin <bapt@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#ifndef DNS_UTILS_H
+#define DNS_UTILS_H
+struct dns_srvinfo {
+ unsigned int type;
+ unsigned int class;
+ unsigned int ttl;
+ unsigned int priority;
+ unsigned int weight;
+ unsigned int port;
+ unsigned int finalweight;
+ char host[MAXHOSTNAMELEN];
+ struct dns_srvinfo *next;
+};
+
+struct dns_srvinfo *
+ dns_getsrvinfo(const char *zone);
+
+#endif
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/hash.c b/usr.sbin/pkg/hash.c
new file mode 100644
index 000000000000..b3e6334da6a2
--- /dev/null
+++ b/usr.sbin/pkg/hash.c
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 <sha256.h>
+#include <unistd.h>
+
+#include "hash.h"
+
+char *
+sha256_buf(char *buf, size_t len)
+{
+
+ return (SHA256_Data(buf, len, NULL));
+}
+
+char *
+sha256_fd(int fd)
+{
+
+ return (SHA256_Fd(fd, NULL));
+}
diff --git a/usr.sbin/pkg/hash.h b/usr.sbin/pkg/hash.h
new file mode 100644
index 000000000000..2ade4b7e7a1f
--- /dev/null
+++ b/usr.sbin/pkg/hash.h
@@ -0,0 +1,32 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 Baptiste Daroussin <bapt@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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ *
+ */
+
+#pragma once
+
+char *sha256_buf(char *buf, size_t len);
+char *sha256_fd(int fd);
diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7
new file mode 100644
index 000000000000..d2246f74a3fc
--- /dev/null
+++ b/usr.sbin/pkg/pkg.7
@@ -0,0 +1,336 @@
+.\" Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" 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.
+.\" 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.Dd April 29, 2025
+.Dt PKG 7
+.Os
+.Sh NAME
+.Nm pkg
+.Nd a utility for manipulating packages
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Ar command ...
+.Nm
+.Op Fl d
+.Cm add
+.Op Fl fy
+.Op Fl r Ar reponame
+.Ar pkg.pkg
+.Nm
+.Fl N
+.Nm
+.Op Fl 46d
+.Cm bootstrap
+.Op Fl fy
+.Op Fl r Ar reponame
+.Sh DESCRIPTION
+.Nm
+is the package management tool.
+It is used to manage local packages installed from
+.Xr ports 7
+and install/upgrade packages from remote repositories.
+.Pp
+To avoid backwards incompatibility issues, the actual
+.Xr pkg 8
+tool is not installed in the base system.
+The first time invoked,
+.Nm
+will bootstrap the real
+.Xr pkg 8
+from a remote repository.
+.Bl -tag
+.It Nm Ar command ...
+If
+.Xr pkg 8
+is not installed yet, it will be fetched, have its signature verified,
+installed, and then have the original command forwarded to it.
+If already installed, the command requested will be forwarded to the real
+.Xr pkg 8 .
+.It Nm Cm add Oo Fl fy Oc Oo Fl r Ar reponame Oc Ar pkg.pkg
+Install
+.Xr pkg 8
+from a local package instead of fetching from remote.
+If signature checking is enabled, then the correct signature file
+must exist and the signature valid before the package will be installed.
+If the
+.Fl f
+flag is specified, then
+.Xr pkg 8
+will be installed regardless if it is already installed.
+If the
+.Fl y
+flag is specified, no confirmation will be asked when bootstrapping
+.Xr pkg 8 .
+.Pp
+If a
+.Ar reponame
+has been specified, then the signature configuration for that repository will be
+used.
+.It Nm Fl N
+Do not bootstrap, just determine if
+.Xr pkg 8
+is actually installed or not.
+Returns 0 and the number of packages installed
+if it is, otherwise 1.
+.It Nm Oo Fl 46 Oc Cm bootstrap Oo Fl fy Oc \
+Oo Fl r Ar reponame Oc
+Attempt to bootstrap and do not forward anything to
+.Xr pkg 8
+after it is installed.
+With
+.Fl 4
+and
+.Fl 6 ,
+.Nm
+will force IPv4 or IPv6 respectively to fetch
+.Xr pkg 8
+and its signatures as needed.
+If the
+.Fl f
+flag is specified, then
+.Xr pkg 8
+will be fetched and installed regardless if it is already installed.
+If the
+.Fl y
+flag is specified, no confirmation will be asked when bootstrapping
+.Xr pkg 8 .
+.Pp
+If a
+.Ar reponame
+has been specified, then the configuration for that repository will be used.
+.El
+.Sh OPTIONS
+The following options are supported by
+.Nm :
+.Bl -tag -width indent
+.It Fl d, Fl -debug
+Show debug information.
+May be specified more than once to increase the level of detail.
+When specified twice,
+.Xr fetch 3
+debug output is enabled.
+.El
+.Sh CONFIGURATION
+Configuration varies in whether it is in a repository configuration file
+or the global configuration file.
+The default repository configuration for
+.Fx
+is stored in
+.Pa /etc/pkg/FreeBSD.conf ,
+and additional repository configuration files will be searched for in
+.Ev REPOS_DIR ,
+or
+.Pa /usr/local/etc/pkg/repos
+if it is unset.
+.Pp
+For bootstrapping,
+.Nm
+will process all repositories that it finds and use the last enabled repository
+by default.
+.Pp
+Repository configuration is stored in the following format:
+.Bd -literal -offset indent
+FreeBSD: {
+ url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
+ mirror_type: "srv",
+ signature_type: "none",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
+.Ed
+.Pp
+.Bl -tag -width signature_type -compact
+.It url
+Refer to
+.Dv PACKAGESITE
+in
+.Sx ENVIRONMENT
+.It mirror_type
+Refer to
+.Dv MIRROR_TYPE
+in
+.Sx ENVIRONMENT
+.It signature_type
+Refer to
+.Dv SIGNATURE_TYPE
+in
+.Sx ENVIRONMENT
+.It fingerprints
+Refer to
+.Dv FINGERPRINTS
+in
+.Sx ENVIRONMENT
+.It enabled
+Defines whether this repository should be used or not.
+Valid values are
+.Dv yes ,
+.Dv true ,
+.Dv 1 ,
+.Dv no ,
+.Dv false ,
+.Dv 0 .
+.El
+.Pp
+Global configuration can be stored in
+.Pa /usr/local/etc/pkg.conf
+in the following format:
+.Bd -literal -offset indent
+PACKAGESITE: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
+MIRROR_TYPE: "srv",
+SIGNATURE_TYPE: "none",
+FINGERPRINTS: "/usr/share/keys/pkg",
+ASSUME_ALWAYS_YES: "yes"
+REPOS_DIR: ["/etc/pkg", "/usr/local/etc/pkg/repos"]
+.Ed
+.Pp
+Reference
+.Sx ENVIRONMENT
+for each variable.
+.Sh ENVIRONMENT
+The following environment variables can be set to override the settings
+from the
+.Pa pkg.conf
+file used.
+.Bl -tag -width "ASSUME_ALWAYS_YES"
+.It Ev MIRROR_TYPE
+This defines which mirror type should be used.
+Valid values are
+.Dv SRV ,
+.Dv HTTP ,
+.Dv NONE .
+.It Ev ABI
+This defines the ABI for the package to be installed.
+Default ABI is determined from
+.Pa /bin/sh .
+.It Ev ASSUME_ALWAYS_YES
+If set, no confirmation will be asked when bootstrapping
+.Xr pkg 8 .
+.It Ev SIGNATURE_TYPE
+If set to
+.Dv FINGERPRINTS
+then a signature will be required and validated against known
+certificate fingerprints when bootstrapping
+.Xr pkg 8 .
+.It Ev FINGERPRINTS
+If
+.Sy SIGNATURE_TYPE
+is set to
+.Dv FINGERPRINTS
+this value should be set to the directory path where known fingerprints are
+located.
+.It Ev PACKAGESITE
+The URL that
+.Xr pkg 8
+and other packages
+will be fetched from.
+.It Ev REPOS_DIR
+Comma-separated list of directories that should be searched for repository
+configuration files.
+.El
+.Sh FILES
+Configuration is read from the files in the listed order.
+This path can be changed by setting
+.Ev REPOS_DIR .
+The last enabled repository is the one used for bootstrapping
+.Xr pkg 8 .
+.Bl -tag -width "/usr/local/etc/pkg/repos/*.conf"
+.It Pa /usr/local/etc/pkg.conf
+.It Pa /etc/pkg/FreeBSD.conf
+.It Pa /usr/local/etc/pkg/repos/*.conf
+.El
+.Sh EXAMPLES
+Some examples are listed here.
+The full list of available commands are available in
+.Xr pkg 8
+once it is bootstrapped.
+.Pp
+Search for a package:
+.Dl $ pkg search perl
+.Pp
+Install a package:
+.Dl % pkg install perl
+.Pp
+List installed packages:
+.Dl $ pkg info
+.Pp
+Upgrade from remote repository:
+.Dl % pkg upgrade
+.Pp
+List non-automatic packages:
+.Dl $ pkg query -e '%a = 0' %o
+.Pp
+List automatic packages:
+.Dl $ pkg query -e '%a = 1' %o
+.Pp
+Delete an installed package:
+.Dl % pkg delete perl
+.Pp
+Remove unneeded dependencies:
+.Dl % pkg autoremove
+.Pp
+Change a package from automatic to non-automatic, which will prevent
+.Xr pkg-autoremove 8
+from removing it:
+.Dl % pkg set -A 0 perl
+.Pp
+Change a package from non-automatic to automatic, which will make
+.Xr pkg-autoremove 8
+allow it be removed once nothing depends on it:
+.Dl % pkg set -A 1 perl
+.Pp
+Create package file from an installed package:
+.Dl % pkg create -o /usr/ports/packages/All perl
+.Pp
+Determine which package installed a file:
+.Dl $ pkg which /usr/local/bin/perl
+.Pp
+Audit installed packages for security advisories:
+.Dl $ pkg audit
+.Pp
+Check installed packages for checksum mismatches:
+.Dl # pkg check -s -a
+.Pp
+Check for missing dependencies:
+.Dl # pkg check -d -a
+.Pp
+Fetch a package for a different
+.Fx
+version, along with all its dependencies:
+.Dl # pkg -o ABI=FreeBSD:15:amd64 -o IGNORE_OSVERSION=yes fetch -o destdir -d perl
+.Sh SEE ALSO
+.Xr ports 7 ,
+.Xr pkg 8
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Fx 9.1 .
+It became the default package tool in
+.Fx 10.0 ,
+replacing the
+pkg_install suite of tools
+.Xr pkg_add 1 ,
+.Xr pkg_info 1 and
+.Xr pkg_create 1 .
diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c
new file mode 100644
index 000000000000..7b0a67e69a4c
--- /dev/null
+++ b/usr.sbin/pkg/pkg.c
@@ -0,0 +1,1264 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <archive.h>
+#include <archive_entry.h>
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fetch.h>
+#include <getopt.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ucl.h>
+
+#include "pkg.h"
+
+#include "dns_utils.h"
+#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;
+} pkgsign_builtins[] = {
+ {
+ .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 {
+ HASH_UNKNOWN,
+ HASH_SHA256,
+} hash_t;
+
+struct fingerprint {
+ hash_t type;
+ char *name;
+ char hash[BUFSIZ];
+ STAILQ_ENTRY(fingerprint) next;
+};
+
+static const char *bootstrap_name = "pkg.pkg";
+
+STAILQ_HEAD(fingerprint_list, fingerprint);
+
+static int debug;
+
+static int
+pkgsign_new(const char *name, struct pkgsign_ctx **ctx)
+{
+ const struct pkgsign_impl *impl;
+ const struct pkgsign_ops *ops;
+ struct pkgsign_ctx *nctx;
+ size_t ctx_size;
+ int ret;
+
+ assert(*ctx == NULL);
+ ops = NULL;
+ for (size_t i = 0; i < nitems(pkgsign_builtins); i++) {
+ impl = &pkgsign_builtins[i];
+
+ if (strcmp(name, impl->pi_name) == 0) {
+ ops = impl->pi_ops;
+ break;
+ }
+ }
+
+ if (ops == NULL)
+ return (ENOENT);
+
+ ctx_size = ops->pkgsign_ctx_size;
+ if (ctx_size == 0)
+ ctx_size = sizeof(*nctx);
+ assert(ctx_size >= sizeof(*nctx));
+
+ nctx = calloc(1, ctx_size);
+ if (nctx == NULL)
+ err(EXIT_FAILURE, "calloc");
+ nctx->impl = impl;
+
+ ret = 0;
+ if (ops->pkgsign_new != NULL)
+ ret = (*ops->pkgsign_new)(name, nctx);
+
+ if (ret != 0) {
+ free(nctx);
+ return (ret);
+ }
+
+ *ctx = nctx;
+ return (0);
+}
+
+static bool
+pkgsign_verify_cert(const struct pkgsign_ctx *ctx, int fd, const char *sigfile,
+ const unsigned char *key, int keylen, unsigned char *sig, int siglen)
+{
+
+ return ((*ctx->impl->pi_ops->pkgsign_verify_cert)(ctx, fd, sigfile,
+ key, keylen, sig, siglen));
+}
+
+static bool
+pkgsign_verify_data(const struct pkgsign_ctx *ctx, const char *data,
+ size_t datasz, const char *sigfile, const unsigned char *key, int keylen,
+ unsigned char *sig, int siglen)
+{
+
+ return ((*ctx->impl->pi_ops->pkgsign_verify_data)(ctx, data, datasz,
+ sigfile, key, keylen, sig, siglen));
+}
+
+
+static int
+extract_pkg_static(int fd, char *p, int sz)
+{
+ struct archive *a;
+ struct archive_entry *ae;
+ char *end;
+ int ret, r;
+
+ ret = -1;
+ a = archive_read_new();
+ if (a == NULL) {
+ warn("archive_read_new");
+ return (ret);
+ }
+ archive_read_support_filter_all(a);
+ archive_read_support_format_tar(a);
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ goto cleanup;
+ }
+
+ if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) {
+ warnx("archive_read_open_fd: %s", archive_error_string(a));
+ goto cleanup;
+ }
+
+ ae = NULL;
+ while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) {
+ end = strrchr(archive_entry_pathname(ae), '/');
+ if (end == NULL)
+ continue;
+
+ if (strcmp(end, "/pkg-static") == 0) {
+ r = archive_read_extract(a, ae,
+ ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM |
+ ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL |
+ ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR);
+ strlcpy(p, archive_entry_pathname(ae), sz);
+ break;
+ }
+ }
+
+ if (r == ARCHIVE_OK)
+ ret = 0;
+ else
+ warnx("failed to extract pkg-static: %s",
+ archive_error_string(a));
+
+cleanup:
+ archive_read_free(a);
+ return (ret);
+
+}
+
+static int
+install_pkg_static(const char *path, const char *pkgpath, bool force)
+{
+ int pstat;
+ pid_t pid;
+
+ switch ((pid = fork())) {
+ case -1:
+ return (-1);
+ case 0:
+ if (force)
+ execl(path, "pkg-static", "add", "-f", pkgpath,
+ (char *)NULL);
+ else
+ execl(path, "pkg-static", "add", pkgpath,
+ (char *)NULL);
+ _exit(1);
+ default:
+ break;
+ }
+
+ while (waitpid(pid, &pstat, 0) == -1)
+ if (errno != EINTR)
+ return (-1);
+
+ if (WEXITSTATUS(pstat))
+ return (WEXITSTATUS(pstat));
+ else if (WIFSIGNALED(pstat))
+ return (128 & (WTERMSIG(pstat)));
+ return (pstat);
+}
+
+static int
+fetch_to_fd(struct repository *repo, const char *url, char *path, const char *fetchOpts)
+{
+ struct url *u;
+ struct dns_srvinfo *mirrors, *current;
+ struct url_stat st;
+ FILE *remote;
+ /* To store _https._tcp. + hostname + \0 */
+ int fd;
+ int retry, max_retry;
+ ssize_t r;
+ char buf[10240];
+ char zone[MAXHOSTNAMELEN + 13];
+
+ max_retry = 3;
+ current = mirrors = NULL;
+ remote = NULL;
+
+ if ((fd = mkstemp(path)) == -1) {
+ warn("mkstemp()");
+ return (-1);
+ }
+
+ retry = max_retry;
+
+ if ((u = fetchParseURL(url)) == NULL) {
+ warn("fetchParseURL('%s')", url);
+ return (-1);
+ }
+
+ while (remote == NULL) {
+ if (retry == max_retry) {
+ if (strcmp(u->scheme, "file") != 0 &&
+ repo->mirror_type == MIRROR_SRV) {
+ snprintf(zone, sizeof(zone),
+ "_%s._tcp.%s", u->scheme, u->host);
+ mirrors = dns_getsrvinfo(zone);
+ current = mirrors;
+ }
+ }
+
+ if (mirrors != NULL) {
+ strlcpy(u->host, current->host, sizeof(u->host));
+ u->port = current->port;
+ }
+
+ remote = fetchXGet(u, &st, fetchOpts);
+ if (remote == NULL) {
+ --retry;
+ if (retry <= 0)
+ goto fetchfail;
+ if (mirrors != NULL) {
+ current = current->next;
+ if (current == NULL)
+ current = mirrors;
+ }
+ }
+ }
+
+ while ((r = fread(buf, 1, sizeof(buf), remote)) > 0) {
+ if (write(fd, buf, r) != r) {
+ warn("write()");
+ goto fetchfail;
+ }
+ }
+
+ if (r != 0) {
+ warn("An error occurred while fetching pkg(8)");
+ goto fetchfail;
+ }
+
+ if (ferror(remote))
+ goto fetchfail;
+
+ goto cleanup;
+
+fetchfail:
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ unlink(path);
+ }
+
+cleanup:
+ if (remote != NULL)
+ fclose(remote);
+
+ return fd;
+}
+
+static struct fingerprint *
+parse_fingerprint(ucl_object_t *obj)
+{
+ const ucl_object_t *cur;
+ ucl_object_iter_t it = NULL;
+ const char *function, *fp, *key;
+ struct fingerprint *f;
+ hash_t fct = HASH_UNKNOWN;
+
+ function = fp = NULL;
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+ if (cur->type != UCL_STRING)
+ continue;
+ if (strcasecmp(key, "function") == 0) {
+ function = ucl_object_tostring(cur);
+ continue;
+ }
+ if (strcasecmp(key, "fingerprint") == 0) {
+ fp = ucl_object_tostring(cur);
+ continue;
+ }
+ }
+
+ if (fp == NULL || function == NULL)
+ return (NULL);
+
+ if (strcasecmp(function, "sha256") == 0)
+ fct = HASH_SHA256;
+
+ if (fct == HASH_UNKNOWN) {
+ warnx("Unsupported hashing function: %s", function);
+ return (NULL);
+ }
+
+ f = calloc(1, sizeof(struct fingerprint));
+ f->type = fct;
+ strlcpy(f->hash, fp, sizeof(f->hash));
+
+ return (f);
+}
+
+static void
+free_fingerprint_list(struct fingerprint_list* list)
+{
+ struct fingerprint *fingerprint, *tmp;
+
+ STAILQ_FOREACH_SAFE(fingerprint, list, next, tmp) {
+ free(fingerprint->name);
+ free(fingerprint);
+ }
+ free(list);
+}
+
+static struct fingerprint *
+load_fingerprint(const char *dir, const char *filename)
+{
+ ucl_object_t *obj = NULL;
+ struct ucl_parser *p = NULL;
+ struct fingerprint *f;
+ char path[MAXPATHLEN];
+
+ f = NULL;
+
+ snprintf(path, MAXPATHLEN, "%s/%s", dir, filename);
+
+ p = ucl_parser_new(0);
+ if (!ucl_parser_add_file(p, path)) {
+ warnx("%s: %s", path, ucl_parser_get_error(p));
+ ucl_parser_free(p);
+ return (NULL);
+ }
+
+ obj = ucl_parser_get_object(p);
+
+ if (obj->type == UCL_OBJECT)
+ f = parse_fingerprint(obj);
+
+ if (f != NULL)
+ f->name = strdup(filename);
+
+ ucl_object_unref(obj);
+ ucl_parser_free(p);
+
+ return (f);
+}
+
+static struct fingerprint_list *
+load_fingerprints(const char *path, int *count)
+{
+ DIR *d;
+ struct dirent *ent;
+ struct fingerprint *finger;
+ struct fingerprint_list *fingerprints;
+
+ *count = 0;
+
+ fingerprints = calloc(1, sizeof(struct fingerprint_list));
+ if (fingerprints == NULL)
+ return (NULL);
+ STAILQ_INIT(fingerprints);
+
+ if ((d = opendir(path)) == NULL) {
+ free(fingerprints);
+
+ return (NULL);
+ }
+
+ while ((ent = readdir(d))) {
+ if (strcmp(ent->d_name, ".") == 0 ||
+ strcmp(ent->d_name, "..") == 0)
+ continue;
+ finger = load_fingerprint(path, ent->d_name);
+ if (finger != NULL) {
+ STAILQ_INSERT_TAIL(fingerprints, finger, next);
+ ++(*count);
+ }
+ }
+
+ closedir(d);
+
+ return (fingerprints);
+}
+
+char *
+pkg_read_fd(int fd, size_t *osz)
+{
+ char *obuf;
+ char buf[4096];
+ FILE *fp;
+ ssize_t r;
+
+ obuf = NULL;
+ *osz = 0;
+ fp = open_memstream(&obuf, osz);
+ if (fp == NULL)
+ err(EXIT_FAILURE, "open_memstream()");
+
+ while ((r = read(fd, buf, sizeof(buf))) >0) {
+ fwrite(buf, 1, r, fp);
+ }
+
+ if (ferror(fp))
+ errx(EXIT_FAILURE, "reading file");
+
+ fclose(fp);
+
+ 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 *osigb, *sigb, *sigtype;
+ size_t sigsz;
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ return (NULL);
+ }
+
+ 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);
+ pk->sigtype = sigtype;
+ free(osigb);
+
+ return (pk);
+}
+
+static struct sig_cert *
+parse_cert(int fd) {
+ int my_fd;
+ struct sig_cert *sc;
+ FILE *fp, *sigfp, *certfp, *tmpfp, *typefp;
+ char *line;
+ char *sig, *cert, *type;
+ size_t linecap, sigsz, certsz, typesz;
+ ssize_t linelen;
+ bool end_seen;
+
+ sc = NULL;
+ line = NULL;
+ linecap = 0;
+ sig = cert = type = NULL;
+ sigfp = certfp = tmpfp = typefp = NULL;
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ return (NULL);
+ }
+
+ /* Duplicate the fd so that fclose(3) does not close it. */
+ if ((my_fd = dup(fd)) == -1) {
+ warnx("dup");
+ return (NULL);
+ }
+
+ if ((fp = fdopen(my_fd, "rb")) == NULL) {
+ warn("fdopen");
+ close(my_fd);
+ return (NULL);
+ }
+
+ 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)
+ fwrite(line, 1, linelen, tmpfp);
+ }
+
+ 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;
+
+ return (sc);
+}
+
+static bool
+verify_pubsignature(int fd_pkg, int fd_sig, struct repository *r)
+{
+ struct pubkey *pk;
+ char *data;
+ struct pkgsign_ctx *sctx;
+ size_t datasz;
+ bool ret;
+ const char *pubkey;
+
+ pk = NULL;
+ sctx = NULL;
+ data = NULL;
+ ret = false;
+
+ if (r != NULL) {
+ if (r->pubkey == NULL) {
+ warnx("No CONFIG_PUBKEY defined for %s", r->name);
+ goto cleanup;
+ }
+ pubkey = r->pubkey;
+ } else {
+ if (config_string(PUBKEY, &pubkey) != 0) {
+ warnx("No CONFIG_PUBKEY defined");
+ goto cleanup;
+ }
+ }
+
+ if ((pk = read_pubkey(fd_sig)) == NULL) {
+ warnx("Error reading signature");
+ goto cleanup;
+ }
+
+ if (lseek(fd_pkg, 0, SEEK_SET) == -1) {
+ warn("lseek");
+ 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);
+ } else {
+ if ((data = pkg_read_fd(fd_pkg, &datasz)) == NULL) {
+ warnx("Failed to read package data");
+ goto cleanup;
+ }
+ }
+
+ if (pkgsign_new(pk->sigtype, &sctx) != 0) {
+ warnx("Failed to fetch '%s' signer", pk->sigtype);
+ goto cleanup;
+ }
+
+ /* Verify the signature. */
+ printf("Verifying signature with public key %s.a.. ", r->pubkey);
+ if (pkgsign_verify_data(sctx, data, datasz, r->pubkey, NULL, 0, pk->sig,
+ pk->siglen) == false) {
+ fprintf(stderr, "Signature is not valid\n");
+ goto cleanup;
+ }
+
+ ret = true;
+
+cleanup:
+ free(data);
+ if (pk) {
+ free(pk->sig);
+ free(pk);
+ }
+
+ return (ret);
+}
+
+static bool
+verify_signature(int fd_pkg, int fd_sig, struct repository *r)
+{
+ struct fingerprint_list *trusted, *revoked;
+ struct fingerprint *fingerprint;
+ struct sig_cert *sc;
+ struct pkgsign_ctx *sctx;
+ bool ret;
+ int trusted_count, revoked_count;
+ const char *fingerprints;
+ char path[MAXPATHLEN];
+ char *hash;
+
+ hash = NULL;
+ sc = NULL;
+ sctx = NULL;
+ trusted = revoked = NULL;
+ ret = false;
+
+ /* Read and parse fingerprints. */
+ if (r != NULL) {
+ if (r->fingerprints == NULL) {
+ warnx("No FINGERPRINTS defined for %s", r->name);
+ goto cleanup;
+ }
+ fingerprints = r->fingerprints;
+ } else {
+ if (config_string(FINGERPRINTS, &fingerprints) != 0) {
+ warnx("No FINGERPRINTS defined");
+ goto cleanup;
+ }
+ }
+
+ snprintf(path, MAXPATHLEN, "%s/trusted", fingerprints);
+ if ((trusted = load_fingerprints(path, &trusted_count)) == NULL) {
+ warnx("Error loading trusted certificates");
+ goto cleanup;
+ }
+
+ if (trusted_count == 0 || trusted == NULL) {
+ fprintf(stderr, "No trusted certificates found.\n");
+ goto cleanup;
+ }
+
+ snprintf(path, MAXPATHLEN, "%s/revoked", fingerprints);
+ if ((revoked = load_fingerprints(path, &revoked_count)) == NULL) {
+ warnx("Error loading revoked certificates");
+ goto cleanup;
+ }
+
+ /* Read certificate and signature in. */
+ if ((sc = parse_cert(fd_sig)) == NULL) {
+ warnx("Error parsing certificate");
+ goto cleanup;
+ }
+ /* Explicitly mark as non-trusted until proven otherwise. */
+ sc->trusted = false;
+
+ /* Parse signature and pubkey out of the certificate */
+ hash = sha256_buf(sc->cert, sc->certlen);
+
+ /* Check if this hash is revoked */
+ if (revoked != NULL) {
+ STAILQ_FOREACH(fingerprint, revoked, next) {
+ if (strcasecmp(fingerprint->hash, hash) == 0) {
+ fprintf(stderr, "The package was signed with "
+ "revoked certificate %s\n",
+ fingerprint->name);
+ goto cleanup;
+ }
+ }
+ }
+
+ STAILQ_FOREACH(fingerprint, trusted, next) {
+ if (strcasecmp(fingerprint->hash, hash) == 0) {
+ sc->trusted = true;
+ sc->name = strdup(fingerprint->name);
+ break;
+ }
+ }
+
+ if (sc->trusted == false) {
+ fprintf(stderr, "No trusted fingerprint found matching "
+ "package's certificate\n");
+ goto cleanup;
+ }
+
+ if (pkgsign_new(sc->type, &sctx) != 0) {
+ fprintf(stderr, "Failed to fetch 'rsa' signer\n");
+ goto cleanup;
+ }
+
+ /* Verify the signature. */
+ printf("Verifying signature with trusted certificate %s... ", sc->name);
+ if (pkgsign_verify_cert(sctx, fd_pkg, NULL, sc->cert, sc->certlen,
+ sc->sig, sc->siglen) == false) {
+ fprintf(stderr, "Signature is not valid\n");
+ goto cleanup;
+ }
+
+ ret = true;
+
+cleanup:
+ free(hash);
+ if (trusted)
+ free_fingerprint_list(trusted);
+ if (revoked)
+ free_fingerprint_list(revoked);
+ if (sc) {
+ free(sc->cert);
+ free(sc->sig);
+ free(sc->name);
+ free(sc);
+ }
+
+ return (ret);
+}
+
+static int
+bootstrap_pkg(bool force, const char *fetchOpts, struct repository *repo)
+{
+ int fd_pkg, fd_sig;
+ int ret;
+ char url[MAXPATHLEN];
+ char tmppkg[MAXPATHLEN];
+ char tmpsig[MAXPATHLEN];
+ const char *packagesite;
+ char pkgstatic[MAXPATHLEN];
+
+ fd_sig = -1;
+ ret = -1;
+
+ printf("Bootstrapping pkg from %s, please wait...\n", repo->url);
+
+ /* Support pkg+http:// for PACKAGESITE which is the new format
+ in 1.2 to avoid confusion on why http://pkg.FreeBSD.org has
+ no A record. */
+ packagesite = repo->url;
+ if (strncmp(URL_SCHEME_PREFIX, packagesite,
+ strlen(URL_SCHEME_PREFIX)) == 0)
+ packagesite += strlen(URL_SCHEME_PREFIX);
+
+ snprintf(url, MAXPATHLEN, "%s/Latest/%s", packagesite, bootstrap_name);
+ snprintf(tmppkg, MAXPATHLEN, "%s/%s.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP,
+ bootstrap_name);
+ if ((fd_pkg = fetch_to_fd(repo, url, tmppkg, fetchOpts)) == -1)
+ goto fetchfail;
+
+ if (repo->signature_type == SIGNATURE_FINGERPRINT) {
+ snprintf(tmpsig, MAXPATHLEN, "%s/%s.sig.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP,
+ bootstrap_name);
+ snprintf(url, MAXPATHLEN, "%s/Latest/%s.sig",
+ packagesite, bootstrap_name);
+
+ if ((fd_sig = fetch_to_fd(repo, url, tmpsig, fetchOpts)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto fetchfail;
+ }
+
+ if (verify_signature(fd_pkg, fd_sig, repo) == false)
+ goto cleanup;
+ } else if (repo->signature_type == SIGNATURE_PUBKEY) {
+ snprintf(tmpsig, MAXPATHLEN,
+ "%s/%s.pubkeysig.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP,
+ bootstrap_name);
+ snprintf(url, MAXPATHLEN, "%s/Latest/%s.pubkeysig",
+ packagesite, bootstrap_name);
+
+ if ((fd_sig = fetch_to_fd(repo, url, tmpsig, fetchOpts)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto fetchfail;
+ }
+
+ if (verify_pubsignature(fd_pkg, fd_sig, repo) == false)
+ goto cleanup;
+ }
+
+ if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0)
+ ret = install_pkg_static(pkgstatic, tmppkg, force);
+
+ goto cleanup;
+
+fetchfail:
+ warnx("Error fetching %s: %s", url, fetchLastErrString);
+ if (fetchLastErrCode == FETCH_RESOLV) {
+ fprintf(stderr, "Address resolution failed for %s.\n", packagesite);
+ } else {
+ fprintf(stderr, "A pre-built version of pkg could not be found for "
+ "your system.\n");
+ }
+
+cleanup:
+ if (fd_sig != -1) {
+ close(fd_sig);
+ unlink(tmpsig);
+ }
+
+ if (fd_pkg != -1) {
+ close(fd_pkg);
+ unlink(tmppkg);
+ }
+
+ return (ret);
+}
+
+static const char confirmation_message[] =
+"The package management tool is not yet installed on your system.\n"
+"Do you want to fetch and install it now? [y/N]: ";
+
+static const char non_interactive_message[] =
+"The package management tool is not yet installed on your system.\n"
+"Please set ASSUME_ALWAYS_YES=yes environment variable to be able to bootstrap "
+"in non-interactive (stdin not being a tty)\n";
+
+static const char args_bootstrap_message[] =
+"Too many arguments\n"
+"Usage: pkg [-4|-6] bootstrap [-f] [-y]\n";
+
+static int
+pkg_query_yes_no(void)
+{
+ int ret, c;
+
+ fflush(stdout);
+ c = getchar();
+
+ if (c == 'y' || c == 'Y')
+ ret = 1;
+ else
+ ret = 0;
+
+ while (c != '\n' && c != EOF)
+ c = getchar();
+
+ return (ret);
+}
+
+static int
+bootstrap_pkg_local(const char *pkgpath, bool force)
+{
+ char path[MAXPATHLEN];
+ char pkgstatic[MAXPATHLEN];
+ const char *signature_type;
+ int fd_pkg, fd_sig, ret;
+
+ fd_sig = -1;
+ ret = -1;
+
+ fd_pkg = open(pkgpath, O_RDONLY);
+ if (fd_pkg == -1)
+ err(EXIT_FAILURE, "Unable to open %s", pkgpath);
+
+ if (config_string(SIGNATURE_TYPE, &signature_type) != 0) {
+ warnx("Error looking up SIGNATURE_TYPE");
+ goto cleanup;
+ }
+ if (signature_type != NULL &&
+ strcasecmp(signature_type, "NONE") != 0) {
+ if (strcasecmp(signature_type, "FINGERPRINTS") == 0) {
+
+ snprintf(path, sizeof(path), "%s.sig", pkgpath);
+
+ if ((fd_sig = open(path, O_RDONLY)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto cleanup;
+ }
+
+ if (verify_signature(fd_pkg, fd_sig, NULL) == false)
+ goto cleanup;
+
+ } else if (strcasecmp(signature_type, "PUBKEY") == 0) {
+
+ snprintf(path, sizeof(path), "%s.pubkeysig", pkgpath);
+
+ if ((fd_sig = open(path, O_RDONLY)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto cleanup;
+ }
+
+ if (verify_pubsignature(fd_pkg, fd_sig, NULL) == false)
+ goto cleanup;
+
+ } else {
+ warnx("Signature type %s is not supported for "
+ "bootstrapping.", signature_type);
+ goto cleanup;
+ }
+ }
+
+ if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0)
+ ret = install_pkg_static(pkgstatic, pkgpath, force);
+
+cleanup:
+ close(fd_pkg);
+ if (fd_sig != -1)
+ close(fd_sig);
+
+ return (ret);
+}
+
+#define PKG_NAME "pkg"
+#define PKG_DEVEL_NAME PKG_NAME "-devel"
+#define PKG_PKG PKG_NAME "."
+
+static bool
+pkg_is_pkg_pkg(const char *pkg)
+{
+ char *vstart, *basename;
+ size_t namelen;
+
+ /* Strip path. */
+ if ((basename = strrchr(pkg, '/')) != NULL)
+ pkg = basename + 1;
+
+ /*
+ * Chop off the final "-" (version delimiter) and check the name that
+ * precedes it. If we didn't have a version delimiter, it must be the
+ * pkg.$archive short form but we'll check it anyways. pkg-devel short
+ * form will look like a pkg archive with 'devel' version, but that's
+ * OK. We otherwise assumed that non-pkg packages will always have a
+ * version component.
+ */
+ vstart = strrchr(pkg, '-');
+ if (vstart == NULL) {
+ return (strlen(pkg) > sizeof(PKG_PKG) - 1 &&
+ strncmp(pkg, PKG_PKG, sizeof(PKG_PKG) - 1) == 0);
+ }
+
+ namelen = vstart - pkg;
+ if (namelen == sizeof(PKG_NAME) - 1 &&
+ strncmp(pkg, PKG_NAME, sizeof(PKG_NAME) - 1) == 0)
+ return (true);
+ if (namelen == sizeof(PKG_DEVEL_NAME) - 1 &&
+ strncmp(pkg, PKG_DEVEL_NAME, sizeof(PKG_DEVEL_NAME) - 1) == 0)
+ return (true);
+ return (false);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char pkgpath[MAXPATHLEN];
+ char **original_argv;
+ const char *pkgarg, *repo_name;
+ bool activation_test, add_pkg, bootstrap_only, force, yes;
+ signed char ch;
+ const char *fetchOpts;
+ struct repositories *repositories;
+
+ activation_test = false;
+ add_pkg = false;
+ bootstrap_only = false;
+ fetchOpts = "";
+ force = false;
+ original_argv = argv;
+ pkgarg = NULL;
+ repo_name = NULL;
+ yes = false;
+
+ struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "only-ipv4", no_argument, NULL, '4' },
+ { "only-ipv6", no_argument, NULL, '6' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase());
+
+ while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'd':
+ debug++;
+ break;
+ case 'N':
+ activation_test = true;
+ break;
+ case '4':
+ fetchOpts = "4";
+ break;
+ case '6':
+ fetchOpts = "6";
+ break;
+ default:
+ break;
+ }
+ }
+ if (debug > 1)
+ fetchDebug = 1;
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc >= 1) {
+ if (strcmp(argv[0], "bootstrap") == 0) {
+ bootstrap_only = true;
+ } else if (strcmp(argv[0], "add") == 0) {
+ add_pkg = true;
+ }
+
+ optreset = 1;
+ optind = 1;
+ if (bootstrap_only || add_pkg) {
+ struct option sub_longopts[] = {
+ { "force", no_argument, NULL, 'f' },
+ { "yes", no_argument, NULL, 'y' },
+ { NULL, 0, NULL, 0 },
+ };
+ while ((ch = getopt_long(argc, argv, "+:fr:y",
+ sub_longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'f':
+ force = true;
+ break;
+ case 'r':
+ repo_name = optarg;
+ break;
+ case 'y':
+ yes = true;
+ break;
+ case ':':
+ fprintf(stderr, "Option -%c requires an argument\n", optopt);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ /*
+ * Parse -y and --yes regardless of the pkg subcommand
+ * specified. This is necessary to make, for example,
+ * `pkg install -y foobar` work as expected when pkg is
+ * not yet bootstrapped.
+ */
+ struct option sub_longopts[] = {
+ { "yes", no_argument, NULL, 'y' },
+ { NULL, 0, NULL, 0 },
+ };
+ while ((ch = getopt_long(argc, argv, "+:y",
+ sub_longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'y':
+ yes = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (bootstrap_only && argc > 0) {
+ fprintf(stderr, args_bootstrap_message);
+ exit(EXIT_FAILURE);
+ }
+
+ if (add_pkg) {
+ if (argc < 1) {
+ fprintf(stderr, "Path to pkg.pkg required\n");
+ exit(EXIT_FAILURE);
+ } else if (argc == 1 && pkg_is_pkg_pkg(argv[0])) {
+ pkgarg = argv[0];
+ } else {
+ /*
+ * If the target package is not pkg.pkg
+ * or there is more than one target package,
+ * this is not a local bootstrap request.
+ */
+ add_pkg = false;
+ }
+ }
+ }
+
+ if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) {
+ struct repository *repo;
+ int ret = 0;
+ /*
+ * To allow 'pkg -N' to be used as a reliable test for whether
+ * a system is configured to use pkg, don't bootstrap pkg
+ * when that option is passed.
+ */
+ if (activation_test)
+ errx(EXIT_FAILURE, "pkg is not installed");
+
+ config_init(repo_name);
+
+ if (add_pkg) {
+ assert(pkgarg != NULL);
+ if (access(pkgarg, R_OK) == -1) {
+ fprintf(stderr, "No such file: %s\n", pkgarg);
+ exit(EXIT_FAILURE);
+ }
+ if (bootstrap_pkg_local(pkgarg, force) != 0)
+ exit(EXIT_FAILURE);
+ exit(EXIT_SUCCESS);
+ }
+ /*
+ * Do not ask for confirmation if either of stdin or stdout is
+ * not tty. Check the environment to see if user has answer
+ * tucked in there already.
+ */
+ if (!yes)
+ config_bool(ASSUME_ALWAYS_YES, &yes);
+ if (!yes) {
+ if (!isatty(fileno(stdin))) {
+ fprintf(stderr, non_interactive_message);
+ exit(EXIT_FAILURE);
+ }
+
+ printf("%s", confirmation_message);
+ if (pkg_query_yes_no() == 0)
+ exit(EXIT_FAILURE);
+ }
+ repositories = config_get_repositories();
+ STAILQ_FOREACH(repo, repositories, next) {
+ if ((ret = bootstrap_pkg(force, fetchOpts, repo)) == 0)
+ break;
+ }
+ if (ret != 0)
+ exit(EXIT_FAILURE);
+ config_finish();
+
+ if (bootstrap_only)
+ exit(EXIT_SUCCESS);
+ } else if (bootstrap_only) {
+ printf("pkg already bootstrapped at %s\n", pkgpath);
+ exit(EXIT_SUCCESS);
+ }
+
+ execv(pkgpath, original_argv);
+
+ /* NOT REACHED */
+ return (EXIT_FAILURE);
+}
diff --git a/usr.sbin/pkg/pkg.h b/usr.sbin/pkg/pkg.h
new file mode 100644
index 000000000000..f74f97ce795b
--- /dev/null
+++ b/usr.sbin/pkg/pkg.h
@@ -0,0 +1,75 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#ifndef _PKG_H
+#define _PKG_H
+
+#include <stdbool.h>
+
+struct pkgsign_ctx {
+ const struct pkgsign_impl *impl;
+};
+
+/* Tentatively won't be needing to free any state, all allocated in the ctx. */
+typedef int pkgsign_new_cb(const char *, struct pkgsign_ctx *);
+typedef bool pkgsign_verify_cert_cb(const struct pkgsign_ctx *, int,
+ const char *, const unsigned char *, int, unsigned char *, int);
+typedef bool pkgsign_verify_data_cb(const struct pkgsign_ctx *,
+ const char *, size_t, const char *, const unsigned char *, int,
+ unsigned char *, int);
+
+struct pkgsign_ops {
+ size_t pkgsign_ctx_size;
+ pkgsign_new_cb *pkgsign_new;
+ pkgsign_verify_cert_cb *pkgsign_verify_cert;
+ 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;
+ int certlen;
+ bool trusted;
+};
+
+struct pubkey {
+ char *sigtype;
+ unsigned char *sig;
+ int siglen;
+};
+
+char *pkg_read_fd(int fd, size_t *osz);
+
+#endif /* _PKG_H */
diff --git a/usr.sbin/pkg/rsa.c b/usr.sbin/pkg/rsa.c
new file mode 100644
index 000000000000..0056ccb595a9
--- /dev/null
+++ b/usr.sbin/pkg/rsa.c
@@ -0,0 +1,175 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/param.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include "pkg.h"
+
+#include "config.h"
+#include "hash.h"
+
+static EVP_PKEY *
+load_public_key_file(const char *file)
+{
+ EVP_PKEY *pkey;
+ BIO *bp;
+ char errbuf[1024];
+
+ bp = BIO_new_file(file, "r");
+ if (!bp)
+ errx(EXIT_FAILURE, "Unable to read %s", file);
+
+ if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL)
+ warnx("ici: %s", ERR_error_string(ERR_get_error(), errbuf));
+
+ BIO_free(bp);
+
+ return (pkey);
+}
+
+static EVP_PKEY *
+load_public_key_buf(const unsigned char *cert, int certlen)
+{
+ EVP_PKEY *pkey;
+ BIO *bp;
+ char errbuf[1024];
+
+ bp = BIO_new_mem_buf(__DECONST(void *, cert), certlen);
+
+ if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL)
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+
+ BIO_free(bp);
+
+ return (pkey);
+}
+
+static bool
+rsa_verify_data(const struct pkgsign_ctx *ctx __unused,
+ const char *data, size_t datasz, const char *sigfile,
+ const unsigned char *key, int keylen, unsigned char *sig, int siglen)
+{
+ EVP_MD_CTX *mdctx;
+ EVP_PKEY *pkey;
+ char errbuf[1024];
+ bool ret;
+
+ pkey = NULL;
+ mdctx = NULL;
+ ret = false;
+ SSL_load_error_strings();
+
+ if (sigfile != NULL) {
+ if ((pkey = load_public_key_file(sigfile)) == NULL) {
+ warnx("Error reading public key");
+ goto cleanup;
+ }
+ } else {
+ if ((pkey = load_public_key_buf(key, keylen)) == NULL) {
+ warnx("Error reading public key");
+ goto cleanup;
+ }
+ }
+
+ /* Verify signature of the SHA256(pkg) is valid. */
+ if ((mdctx = EVP_MD_CTX_create()) == NULL) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+ if (EVP_DigestVerifyUpdate(mdctx, data, datasz) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ if (EVP_DigestVerifyFinal(mdctx, sig, siglen) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ ret = true;
+ printf("done\n");
+ goto cleanup;
+
+error:
+ printf("failed\n");
+
+cleanup:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (mdctx)
+ EVP_MD_CTX_destroy(mdctx);
+ ERR_free_strings();
+
+ return (ret);
+}
+
+static bool
+rsa_verify_cert(const struct pkgsign_ctx *ctx __unused, int fd,
+ const char *sigfile, const unsigned char *key, int keylen,
+ unsigned char *sig, int siglen)
+{
+ char *sha256;
+ bool ret;
+
+ sha256 = NULL;
+
+ /* Compute SHA256 of the package. */
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ return (false);
+ }
+ if ((sha256 = sha256_fd(fd)) == NULL) {
+ warnx("Error creating SHA256 hash for package");
+ return (false);
+ }
+
+ ret = rsa_verify_data(ctx, sha256, strlen(sha256), sigfile, key, keylen,
+ sig, siglen);
+ free(sha256);
+
+ return (ret);
+}
+
+const struct pkgsign_ops pkgsign_rsa = {
+ .pkgsign_verify_cert = rsa_verify_cert,
+ .pkgsign_verify_data = rsa_verify_data,
+};