diff options
Diffstat (limited to 'usr.sbin/pkg')
| -rw-r--r-- | usr.sbin/pkg/FreeBSD.conf.latest | 23 | ||||
| -rw-r--r-- | usr.sbin/pkg/FreeBSD.conf.quarterly | 23 | ||||
| -rw-r--r-- | usr.sbin/pkg/FreeBSD.conf.quarterly-release | 23 | ||||
| -rw-r--r-- | usr.sbin/pkg/Makefile | 34 | ||||
| -rw-r--r-- | usr.sbin/pkg/Makefile.depend | 23 | ||||
| -rw-r--r-- | usr.sbin/pkg/config.c | 708 | ||||
| -rw-r--r-- | usr.sbin/pkg/config.h | 90 | ||||
| -rw-r--r-- | usr.sbin/pkg/dns_utils.c | 223 | ||||
| -rw-r--r-- | usr.sbin/pkg/dns_utils.h | 46 | ||||
| -rw-r--r-- | usr.sbin/pkg/ecc.c | 606 | ||||
| -rw-r--r-- | usr.sbin/pkg/hash.c | 47 | ||||
| -rw-r--r-- | usr.sbin/pkg/hash.h | 32 | ||||
| -rw-r--r-- | usr.sbin/pkg/pkg.7 | 336 | ||||
| -rw-r--r-- | usr.sbin/pkg/pkg.c | 1264 | ||||
| -rw-r--r-- | usr.sbin/pkg/pkg.h | 75 | ||||
| -rw-r--r-- | usr.sbin/pkg/rsa.c | 175 |
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, +}; |
