From 8d5c48b86182df194b4a9f77e08d5ff9988c45b6 Mon Sep 17 00:00:00 2001 From: Juan RP Date: Sat, 5 Oct 2013 11:38:04 +0200 Subject: [PATCH] Added support for the long awaited feature: RSA signed repositories. --- NEWS | 17 +++ TODO | 5 +- bin/xbps-install/defs.h | 2 +- bin/xbps-install/state_cb.c | 20 ++- bin/xbps-query/list.c | 11 +- bin/xbps-query/ownedby.c | 2 +- bin/xbps-query/search.c | 3 + bin/xbps-reconfigure/main.c | 4 +- bin/xbps-remove/main.c | 6 +- bin/xbps-rindex/Makefile | 2 + bin/xbps-rindex/defs.h | 54 ++++--- bin/xbps-rindex/index-add.c | 24 ++- bin/xbps-rindex/index-clean.c | 31 ++-- bin/xbps-rindex/main.c | 35 ++++- bin/xbps-rindex/readpassphrase.c | 123 +++++++++++++++ bin/xbps-rindex/remove-obsoletes.c | 2 +- bin/xbps-rindex/repoflush.c | 80 +++++----- bin/xbps-rindex/sign.c | 231 ++++++++++++++++++++++++++++ bin/xbps-rindex/xbps-rindex.8 | 13 +- include/xbps.h.in | 80 ++++++++-- include/xbps_api_impl.h | 12 +- lib/Makefile | 4 +- lib/cb_util.c | 8 +- lib/pubkey2fp.c | 143 ++++++++++++++++++ lib/repo.c | 44 +++--- lib/repo_keys.c | 233 +++++++++++++++++++++++++++++ lib/rpool.c | 68 +++++++-- lib/util.c | 17 +++ mk/prog.mk | 2 +- 29 files changed, 1121 insertions(+), 155 deletions(-) create mode 100644 bin/xbps-rindex/readpassphrase.c create mode 100644 bin/xbps-rindex/sign.c create mode 100644 lib/pubkey2fp.c create mode 100644 lib/repo_keys.c diff --git a/NEWS b/NEWS index 6973242b..caa51152 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,20 @@ +xbps-0.27 (???): + + * Support for RSA signed repositories. A repository can be signed with your + preferred RSA key (any ssh key works) as follows: + + $ xbps-rindex -s --signedby "foobar " --privkey /priv/key /path/to/repo + + The first time xbps-install(8) access to a signed repository it will ask you + to import its public key to verify the signature. Please double-check the + hex fingerprint of the public key is the real one! + + Once the public key has been imported it's not expected to change, hence if the + repository index has been modified or signed with another key, it will be ignored. + + Starting from now on all remote repositories must be signed and verified. + Local repos do not need to be signed and they will work as before. + xbps-0.26 (2013-09-25): * xbps-query(8): the `-D --defrepo' argument has been superseded by diff --git a/TODO b/TODO index d08cd061..1d94a688 100644 --- a/TODO +++ b/TODO @@ -7,9 +7,6 @@ xbps-create: - Move all configuration files to /share//conf/. - Add -i --installed option to create binpkg from an installed version. -All: - - Add support to sign with PGP or RSA the repository index files. - -Issues listed at http://code.google.com/p/xbps/issues/list +Issues listed at https://github.com/xtraeme/xbps/issues Surely more stuff... diff --git a/bin/xbps-install/defs.h b/bin/xbps-install/defs.h index dff4a746..dbd4f7a5 100644 --- a/bin/xbps-install/defs.h +++ b/bin/xbps-install/defs.h @@ -48,7 +48,7 @@ bool noyes(const char *, ...); void fetch_file_progress_cb(struct xbps_fetch_cb_data *, void *); /* from state_cb.c */ -void state_cb(struct xbps_state_cb_data *, void *); +int state_cb(struct xbps_state_cb_data *, void *); /* From util.c */ void print_package_line(const char *, int, bool); diff --git a/bin/xbps-install/state_cb.c b/bin/xbps-install/state_cb.c index ddf850f8..f7bcfdfd 100644 --- a/bin/xbps-install/state_cb.c +++ b/bin/xbps-install/state_cb.c @@ -31,13 +31,14 @@ #include #include "defs.h" -void +int state_cb(struct xbps_state_cb_data *xscd, void *cbdata _unused) { xbps_dictionary_t pkgd; const char *instver, *newver; char *pkgname; bool syslog_enabled = false; + int rv = 0; if (xscd->xhp->flags & XBPS_FLAG_SYSLOG) { syslog_enabled = true; @@ -46,6 +47,19 @@ state_cb(struct xbps_state_cb_data *xscd, void *cbdata _unused) switch (xscd->state) { /* notifications */ + case XBPS_STATE_REPO_KEY_IMPORT: + printf("%s\n", xscd->desc); + printf("Fingerprint: "); + xbps_print_hexfp(xscd->arg); + printf("\n"); + rv = noyes("Do you want to import this public key?"); + break; + case XBPS_STATE_REPO_SIGVERIFIED: + printf("[*] RSA signature verified correctly\n"); + break; + case XBPS_STATE_REPO_SIGUNVERIFIED: + printf("[*] RSA signature UNVERIFIED! ignoring...\n"); + break; case XBPS_STATE_TRANS_DOWNLOAD: printf("\n[*] Downloading binary packages\n"); break; @@ -141,7 +155,7 @@ state_cb(struct xbps_state_cb_data *xscd, void *cbdata _unused) case XBPS_STATE_REMOVE_FILE_OBSOLETE_FAIL: /* Ignore errors due to not empty directories */ if (xscd->err == ENOTEMPTY) - return; + return 0; xbps_error_printf("%s\n", xscd->desc); if (syslog_enabled) @@ -152,4 +166,6 @@ state_cb(struct xbps_state_cb_data *xscd, void *cbdata _unused) "%s: unknown state %d\n", xscd->arg, xscd->state); break; } + + return rv; } diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index 6ef69ea7..6941dc04 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -156,8 +156,15 @@ list_pkgs_pkgdb(struct xbps_handle *xhp) static int repo_list_uri_cb(struct xbps_repo *repo, void *arg _unused, bool *done _unused) { - printf("%5zd %s\n", repo->idx ? (ssize_t)xbps_dictionary_count(repo->idx) : -1, repo->uri); - + printf("%5zd %s", + repo->idx ? (ssize_t)xbps_dictionary_count(repo->idx) : -1, + repo->uri); + if (xbps_repository_is_remote(repo->uri)) { + printf(" (RSA %s, %s)", + repo->is_signed ? "signed" : "unsigned", + repo->is_verified ? "verified" : "unverified"); + } + printf("\n"); return 0; } diff --git a/bin/xbps-query/ownedby.c b/bin/xbps-query/ownedby.c index 7f0049af..42259b9b 100644 --- a/bin/xbps-query/ownedby.c +++ b/bin/xbps-query/ownedby.c @@ -162,7 +162,7 @@ repo_ownedby_cb(struct xbps_repo *repo, void *arg, bool *done _unused) struct ffdata *ffd = arg; int rv; - filesd = xbps_repo_get_plist(repo, XBPS_PKGINDEX_FILES); + filesd = xbps_repo_get_plist(repo, XBPS_REPOIDX_FILES); if (filesd == NULL) return 0; diff --git a/bin/xbps-query/search.c b/bin/xbps-query/search.c index e978bad8..d2358cc2 100644 --- a/bin/xbps-query/search.c +++ b/bin/xbps-query/search.c @@ -131,6 +131,9 @@ search_pkgs_cb(struct xbps_repo *repo, void *arg, bool *done _unused) struct search_data *sd = arg; int rv; + if (repo->idx == NULL) + return 0; + allkeys = xbps_dictionary_all_keys(repo->idx); rv = xbps_array_foreach_cb(repo->xhp, allkeys, repo->idx, search_array_cb, sd); xbps_object_release(allkeys); diff --git a/bin/xbps-reconfigure/main.c b/bin/xbps-reconfigure/main.c index 4e170c05..96b76590 100644 --- a/bin/xbps-reconfigure/main.c +++ b/bin/xbps-reconfigure/main.c @@ -50,7 +50,7 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } -static void +static int state_cb(struct xbps_state_cb_data *xscd, void *cbd _unused) { bool syslog_enabled = false; @@ -84,6 +84,8 @@ state_cb(struct xbps_state_cb_data *xscd, void *cbd _unused) "%s: unknown state %d\n", xscd->arg, xscd->state); break; } + + return 0; } int diff --git a/bin/xbps-remove/main.c b/bin/xbps-remove/main.c index 7abf1c63..24ff086d 100644 --- a/bin/xbps-remove/main.c +++ b/bin/xbps-remove/main.c @@ -62,7 +62,7 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } -static void +static int state_cb_rm(struct xbps_state_cb_data *xscd, void *cbdata _unused) { bool syslog_enabled = false; @@ -101,7 +101,7 @@ state_cb_rm(struct xbps_state_cb_data *xscd, void *cbdata _unused) case XBPS_STATE_REMOVE_FILE_OBSOLETE_FAIL: /* Ignore errors due to not empty directories */ if (xscd->err == ENOTEMPTY) - return; + return 0; xbps_error_printf("%s\n", xscd->desc); if (syslog_enabled) @@ -112,6 +112,8 @@ state_cb_rm(struct xbps_state_cb_data *xscd, void *cbdata _unused) "%s: unknown state %d\n", xscd->arg, xscd->state); break; } + + return 0; } static int diff --git a/bin/xbps-rindex/Makefile b/bin/xbps-rindex/Makefile index 20e7714c..29ebf790 100644 --- a/bin/xbps-rindex/Makefile +++ b/bin/xbps-rindex/Makefile @@ -3,5 +3,7 @@ TOPDIR = ../.. BIN = xbps-rindex OBJS = main.o index-add.o index-clean.o remove-obsoletes.o repoflush.o +OBJS += readpassphrase.o sign.o +EXTRA_CFLAGS = -Wno-unused-result include $(TOPDIR)/mk/prog.mk diff --git a/bin/xbps-rindex/defs.h b/bin/xbps-rindex/defs.h index 74e78218..b6377a4e 100644 --- a/bin/xbps-rindex/defs.h +++ b/bin/xbps-rindex/defs.h @@ -28,23 +28,6 @@ #include -/* From index-add.c */ -int index_add(struct xbps_handle *, int, char **, bool); - -/* From index-clean.c */ -int index_clean(struct xbps_handle *, const char *); - -/* From index-files.c */ -int index_files_add(struct xbps_handle *, int, char **); -int index_files_clean(struct xbps_handle *, const char *); - -/* From remove-obsoletes.c */ -int remove_obsoletes(struct xbps_handle *, const char *); - -/* From repoflush.c */ -int repodata_flush(struct xbps_handle *, const char *, - xbps_dictionary_t, xbps_dictionary_t); - /* libarchive compat */ #if ARCHIVE_VERSION_NUMBER >= 3000000 @@ -77,4 +60,41 @@ int repodata_flush(struct xbps_handle *, const char *, #endif +#ifndef __UNCONST +#define __UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#endif + + +struct repodata { + struct archive *ar; + char *repofile; + char *tname; + int repofd; +}; + +/* From index-add.c */ +int index_add(struct xbps_handle *, int, char **, bool); + +/* From index-clean.c */ +int index_clean(struct xbps_handle *, const char *); + +/* From index-files.c */ +int index_files_add(struct xbps_handle *, int, char **); +int index_files_clean(struct xbps_handle *, const char *); + +/* From remove-obsoletes.c */ +int remove_obsoletes(struct xbps_handle *, const char *); + +/* From sign.c */ +int sign_repo(struct xbps_handle *, const char *, const char *, + const char *); + +/* From readpass.c */ +char *readpassphrase(const char *, char *, size_t, int); + +/* From repoflush.c */ +struct repodata *repodata_init(struct xbps_handle *xhp, const char *); +int repodata_add_buf(struct repodata *, const char *, const char *); +void repodata_flush(struct repodata *); + #endif /* !_XBPS_RINDEX_DEFS_H_ */ diff --git a/bin/xbps-rindex/index-add.c b/bin/xbps-rindex/index-add.c index b84f2741..9b8799fa 100644 --- a/bin/xbps-rindex/index-add.c +++ b/bin/xbps-rindex/index-add.c @@ -52,7 +52,7 @@ index_add(struct xbps_handle *xhp, int argc, char **argv, bool force) const char *oldpkgver, *arch, *oldarch; char *pkgver, *pkgname, *sha256, *repodir, *buf; char *tmprepodir; - int rv, ret = 0; + int rv = 0, ret = 0; bool flush = false, found = false; idx = idxfiles = newpkgd = newpkgfilesd = curpkgd = NULL; @@ -67,8 +67,8 @@ index_add(struct xbps_handle *xhp, int argc, char **argv, bool force) repo = xbps_repo_open(xhp, repodir); if (repo != NULL) { - idx = xbps_repo_get_plist(repo, XBPS_PKGINDEX); - idxfiles = xbps_repo_get_plist(repo, XBPS_PKGINDEX_FILES); + idx = xbps_repo_get_plist(repo, XBPS_REPOIDX); + idxfiles = xbps_repo_get_plist(repo, XBPS_REPOIDX_FILES); } if (idx == NULL) idx = xbps_dictionary_create(); @@ -267,9 +267,19 @@ index_add(struct xbps_handle *xhp, int argc, char **argv, bool force) * Generate repository data file. */ if (flush) { - rv = repodata_flush(xhp, repodir, idx, idxfiles); - if (rv != 0) - return rv; + struct repodata *rd; + char *xml; + + rd = repodata_init(xhp, repodir); + xml = xbps_dictionary_externalize(idx); + assert(idx); + rv = repodata_add_buf(rd, xml, XBPS_REPOIDX); + free(xml); + xml = xbps_dictionary_externalize(idxfiles); + assert(idx); + rv = repodata_add_buf(rd, xml, XBPS_REPOIDX_FILES); + free(xml); + repodata_flush(rd); } printf("index: %u packages registered.\n", xbps_dictionary_count(idx)); @@ -279,5 +289,5 @@ index_add(struct xbps_handle *xhp, int argc, char **argv, bool force) xbps_object_release(idx); xbps_object_release(idxfiles); - return 0; + return rv; } diff --git a/bin/xbps-rindex/index-clean.c b/bin/xbps-rindex/index-clean.c index d8ad3edb..52efd9d8 100644 --- a/bin/xbps-rindex/index-clean.c +++ b/bin/xbps-rindex/index-clean.c @@ -71,7 +71,7 @@ idx_cleaner_cb(struct xbps_handle *xhp, * File can be read; check its hash. */ xbps_dictionary_get_cstring_nocopy(obj, - "filename-sha256", &sha256); + "filename-sha256", &sha256); if (xbps_file_hash_check(filen, sha256) != 0) xbps_array_add_cstring_nocopy(cbd->array, pkgver); } @@ -80,7 +80,7 @@ idx_cleaner_cb(struct xbps_handle *xhp, } /* - * Removes stalled pkg entries in repository's index.plist file, if any + * Removes stalled pkg entries in repository's XBPS_REPOIDX file, if any * binary package cannot be read (unavailable, not enough perms, etc). */ int @@ -102,8 +102,8 @@ index_clean(struct xbps_handle *xhp, const char *repodir) fprintf(stderr, "index: cannot read repository data: %s\n", strerror(errno)); return -1; } - idx = xbps_repo_get_plist(repo, XBPS_PKGINDEX); - idxfiles = xbps_repo_get_plist(repo, XBPS_PKGINDEX_FILES); + idx = xbps_repo_get_plist(repo, XBPS_REPOIDX); + idxfiles = xbps_repo_get_plist(repo, XBPS_REPOIDX_FILES); xbps_repo_close(repo); if (idx == NULL || idxfiles == NULL) { fprintf(stderr, "incomplete repository data file!"); @@ -111,7 +111,7 @@ index_clean(struct xbps_handle *xhp, const char *repodir) } if (chdir(repodir) == -1) { fprintf(stderr, "index: cannot chdir to %s: %s\n", - repodir, strerror(errno)); + repodir, strerror(errno)); return -1; } printf("Cleaning `%s' index, please wait...\n", repodir); @@ -134,14 +134,25 @@ index_clean(struct xbps_handle *xhp, const char *repodir) xbps_object_release(cbd.array); if (flush) { - rv = repodata_flush(xhp, repodir, idx, idxfiles); - if (rv != 0) - return rv; + struct repodata *rd; + char *xml; + + rd = repodata_init(xhp, repodir); + xml = xbps_dictionary_externalize(idx); + assert(idx); + rv = repodata_add_buf(rd, xml, XBPS_REPOIDX); + free(xml); + xml = xbps_dictionary_externalize(idxfiles); + assert(idx); + rv = repodata_add_buf(rd, xml, XBPS_REPOIDX_FILES); + free(xml); + repodata_flush(rd); + } printf("index: %u packages registered.\n", - xbps_dictionary_count(idx)); + xbps_dictionary_count(idx)); printf("index-files: %u packages registered.\n", - xbps_dictionary_count(idxfiles)); + xbps_dictionary_count(idxfiles)); xbps_object_release(idx); xbps_object_release(idxfiles); diff --git a/bin/xbps-rindex/main.c b/bin/xbps-rindex/main.c index 80f265d7..727c0b67 100644 --- a/bin/xbps-rindex/main.c +++ b/bin/xbps-rindex/main.c @@ -39,11 +39,14 @@ usage(bool fail) "OPTIONS\n" " -f --force Force mode to overwrite entry in add mode\n" " -h --help Show help usage\n" - " -V --version Show XBPS version\n\n" + " -V --version Show XBPS version\n" + " --privkey Path to the private key for signing\n" + " --signedby Signature details, i.e \"name \"\n\n" "MODE\n" " -a --add ... Add package(s) to repository index\n" " -c --clean Cleans obsolete entries in repository index\n" - " -r --remove-obsoletes Removes obsolete packages from repository\n\n"); + " -r --remove-obsoletes Removes obsolete packages from repository\n" + " -s --sign Sign repository index\n\n"); exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } @@ -58,14 +61,26 @@ main(int argc, char **argv) { "help", no_argument, NULL, 'h' }, { "remove-obsoletes", no_argument, NULL, 'r' }, { "version", no_argument, NULL, 'V' }, + { "privkey", required_argument, NULL, 0}, + { "signedby", required_argument, NULL, 1}, + { "sign", no_argument, NULL, 's'}, { NULL, 0, NULL, 0 } }; struct xbps_handle xh; + const char *privkey = NULL, *signedby = NULL; int rv, c; - bool clean_mode = false, add_mode = false, rm_mode = false, force = false; + bool clean_mode, add_mode, rm_mode, sign_mode, force; + + clean_mode = add_mode = rm_mode = sign_mode = force = false; while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { switch (c) { + case 0: + privkey = optarg; + break; + case 1: + signedby = optarg; + break; case 'a': add_mode = true; break; @@ -81,18 +96,22 @@ main(int argc, char **argv) case 'r': rm_mode = true; break; + case 's': + sign_mode = true; + break; case 'V': printf("%s\n", XBPS_RELVER); exit(EXIT_SUCCESS); } } - if ((argc == optind) || (!add_mode && !clean_mode && !rm_mode)) { + if ((argc == optind) || (!add_mode && !clean_mode && !rm_mode && !sign_mode)) { usage(true); } else if ((add_mode && (clean_mode || rm_mode)) || (clean_mode && (add_mode || rm_mode)) || - (rm_mode && (add_mode || clean_mode))) { - fprintf(stderr, "Only one mode can be specified: add, clean " - "or remove-obsoletes.\n"); + (rm_mode && (add_mode || clean_mode)) || + (sign_mode && (add_mode || clean_mode || rm_mode))) { + fprintf(stderr, "Only one mode can be specified: add, clean, " + "remove-obsoletes or sign.\n"); exit(EXIT_FAILURE); } @@ -110,6 +129,8 @@ main(int argc, char **argv) rv = index_clean(&xh, argv[optind]); else if (rm_mode) rv = remove_obsoletes(&xh, argv[optind]); + else if (sign_mode) + rv = sign_repo(&xh, argv[optind], privkey, signedby); exit(rv ? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/bin/xbps-rindex/readpassphrase.c b/bin/xbps-rindex/readpassphrase.c new file mode 100644 index 00000000..edc7bb6a --- /dev/null +++ b/bin/xbps-rindex/readpassphrase.c @@ -0,0 +1,123 @@ +/* $NetBSD: readpassphrase.c,v 1.1 2009/06/07 22:38:47 christos Exp $ */ +/* + * Copyright (c) 2000 Todd C. Miller + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" + +#define RPP_ECHO_OFF 0x00 /* Turn off echo (default). */ +#define RPP_ECHO_ON 0x01 /* Leave echo on. */ +#define RPP_REQUIRE_TTY 0x02 /* Fail if there is no tty. */ +#define RPP_FORCELOWER 0x04 /* Force input to lower case. */ +#define RPP_FORCEUPPER 0x08 /* Force input to upper case. */ +#define RPP_SEVENBIT 0x10 /* Strip the high bit from input. */ + +char * +readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags) +{ + struct termios term, oterm; + char ch, *p, *end; + int input, output; + sigset_t oset, nset; + + /* I suppose we could alloc on demand in this case (XXX). */ + if (bufsiz == 0) { + errno = EINVAL; + return(NULL); + } + + /* + * Read and write to /dev/tty if available. If not, read from + * stdin and write to stderr unless a tty is required. + */ + if ((input = output = open(_PATH_TTY, O_RDWR)) == -1) { + if (flags & RPP_REQUIRE_TTY) { + errno = ENOTTY; + return(NULL); + } + input = STDIN_FILENO; + output = STDERR_FILENO; + } + + /* + * We block SIGINT and SIGTSTP so the terminal is not left + * in an inconsistent state (ie: no echo). It would probably + * be better to simply catch these though. + */ + sigemptyset(&nset); + sigaddset(&nset, SIGINT); + sigaddset(&nset, SIGTSTP); + (void)sigprocmask(SIG_BLOCK, &nset, &oset); + + /* Turn off echo if possible. */ + if (tcgetattr(input, &oterm) == 0) { + memcpy(&term, &oterm, sizeof(term)); + if (!(flags & RPP_ECHO_ON) && (term.c_lflag & ECHO)) + term.c_lflag &= ~ECHO; + (void)tcsetattr(input, TCSAFLUSH, &term); + } else { + memset(&term, 0, sizeof(term)); + memset(&oterm, 0, sizeof(oterm)); + } + + (void)write(output, prompt, strlen(prompt)); + end = buf + bufsiz - 1; + for (p = buf; read(input, &ch, 1) == 1 && ch != '\n' && ch != '\r';) { + if (p < end) { + if ((flags & RPP_SEVENBIT)) + ch &= 0x7f; + if (isalpha((unsigned char)ch)) { + if ((flags & RPP_FORCELOWER)) + ch = tolower((unsigned char)ch); + if ((flags & RPP_FORCEUPPER)) + ch = toupper((unsigned char)ch); + } + *p++ = ch; + } + } + *p = '\0'; + if (!(term.c_lflag & ECHO)) + (void)write(output, "\n", 1); + + /* Restore old terminal settings and signal mask. */ + if (memcmp(&term, &oterm, sizeof(term)) != 0) + (void)tcsetattr(input, TCSAFLUSH, &oterm); + (void)sigprocmask(SIG_SETMASK, &oset, NULL); + if (input != STDIN_FILENO) + (void)close(input); + + return(buf); +} diff --git a/bin/xbps-rindex/remove-obsoletes.c b/bin/xbps-rindex/remove-obsoletes.c index 02884456..9017c22d 100644 --- a/bin/xbps-rindex/remove-obsoletes.c +++ b/bin/xbps-rindex/remove-obsoletes.c @@ -119,7 +119,7 @@ remove_obsoletes(struct xbps_handle *xhp, const char *repodir) } return 0; } - if ((repo->idx = xbps_repo_get_plist(repo, XBPS_PKGINDEX)) == NULL) { + if ((repo->idx = xbps_repo_get_plist(repo, XBPS_REPOIDX)) == NULL) { xbps_repo_close(repo); return -1; } diff --git a/bin/xbps-rindex/repoflush.c b/bin/xbps-rindex/repoflush.c index 6067f6ba..77d1abfa 100644 --- a/bin/xbps-rindex/repoflush.c +++ b/bin/xbps-rindex/repoflush.c @@ -37,58 +37,54 @@ #include #include "defs.h" -int -repodata_flush(struct xbps_handle *xhp, const char *repodir, - xbps_dictionary_t idx, xbps_dictionary_t idxfiles) +struct repodata * +repodata_init(struct xbps_handle *xhp, const char *repodir) { - struct archive *ar; - mode_t myumask; - char *repofile, *tname, *xml; - int repofd; + struct repodata *rd; + + rd = malloc(sizeof(struct repodata)); + assert(rd); /* Create a tempfile for our repository archive */ - repofile = xbps_repo_path(xhp, repodir); - tname = xbps_xasprintf("%s.XXXXXXXXXX", repofile); - if ((repofd = mkstemp(tname)) == -1) - return errno; + rd->repofile = xbps_repo_path(xhp, repodir); + rd->tname = xbps_xasprintf("%s.XXXXXXXXXX", rd->repofile); + if ((rd->repofd = mkstemp(rd->tname)) == -1) { + free(rd); + return NULL; + } /* Create and write our repository archive */ - ar = archive_write_new(); - assert(ar); - archive_write_set_compression_gzip(ar); - archive_write_set_format_pax_restricted(ar); - archive_write_set_options(ar, "compression-level=9"); - archive_write_open_fd(ar, repofd); + rd->ar = archive_write_new(); + assert(rd->ar); + archive_write_set_compression_gzip(rd->ar); + archive_write_set_format_pax_restricted(rd->ar); + archive_write_set_options(rd->ar, "compression-level=9"); + archive_write_open_fd(rd->ar, rd->repofd); - xml = xbps_dictionary_externalize(idx); - assert(xml); - if (xbps_archive_append_buf(ar, xml, strlen(xml), - XBPS_PKGINDEX, 0644, "root", "root") != 0) { - free(xml); - return -1; - } - free(xml); + return rd; +} - xml = xbps_dictionary_externalize(idxfiles); - assert(xml); - if (xbps_archive_append_buf(ar, xml, strlen(xml), - XBPS_PKGINDEX_FILES, 0644, "root", "root") != 0) { - free(xml); - return -1; - } - free(xml); +int +repodata_add_buf(struct repodata *rd, const char *buf, const char *filename) +{ + return xbps_archive_append_buf(rd->ar, buf, strlen(buf), + filename, 0644, "root", "root"); +} - archive_write_finish(ar); +void +repodata_flush(struct repodata *rd) +{ + mode_t myumask; /* Write data to tempfile and rename */ - fdatasync(repofd); + archive_write_finish(rd->ar); + fdatasync(rd->repofd); myumask = umask(0); (void)umask(myumask); - assert(fchmod(repofd, 0666 & ~myumask) != -1); - close(repofd); - rename(tname, repofile); - free(repofile); - free(tname); - - return 0; + assert(fchmod(rd->repofd, 0666 & ~myumask) != -1); + close(rd->repofd); + rename(rd->tname, rd->repofile); + free(rd->repofile); + free(rd->tname); + free(rd); } diff --git a/bin/xbps-rindex/sign.c b/bin/xbps-rindex/sign.c new file mode 100644 index 00000000..0292f201 --- /dev/null +++ b/bin/xbps-rindex/sign.c @@ -0,0 +1,231 @@ +/*- + * Copyright (c) 2013 Juan Romero Pardines. + * 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 ``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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "defs.h" + +static int +password_cb(char *buf, int size) +{ + int len = 0; + char pass[BUFSIZ]; + + if (readpassphrase("Enter passphrase: ", pass, BUFSIZ, 0) == NULL) + return 0; + + len = strlen(pass); + + if (len <= 0) + return 0; + if (len > size) + len = size; + + memset(buf, '\0', size); + memcpy(buf, pass, len); + memset(&pass, 0, BUFSIZ); + + return len; +} + +static RSA * +load_rsa_privkey(const char *path) +{ + FILE *fp; + RSA *rsa = NULL; + + if ((fp = fopen(path, "r")) == 0) + return NULL; + + if ((rsa = RSA_new()) == NULL) { + fclose(fp); + return NULL; + } + + rsa = PEM_read_RSAPrivateKey(fp, 0, + (pem_password_cb *)password_cb, + __UNCONST(path)); + fclose(fp); + return rsa; +} + +static char * +pubkey_from_privkey(RSA *rsa) +{ + BIO *bp; + char *buf; + + bp = BIO_new(BIO_s_mem()); + assert(bp); + + if (!PEM_write_bio_RSA_PUBKEY(bp, rsa)) { + fprintf(stderr, "error writing public key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + BIO_free(bp); + return NULL; + } + /* XXX (xtraeme) 8192 should be always enough? */ + buf = malloc(8192); + assert(buf); + BIO_read(bp, buf, 8192); + BIO_free(bp); + ERR_free_strings(); + + return buf; +} + +static RSA * +rsa_sign_buf(const char *privkey, const char *buf, + unsigned char **sigret, unsigned int *siglen) +{ + SHA256_CTX context; + RSA *rsa; + unsigned char sha256[SHA256_DIGEST_LENGTH]; + + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + + rsa = load_rsa_privkey(privkey); + if (rsa == NULL) { + fprintf(stderr, "can't load private key from %s\n", privkey); + return NULL; + } + + SHA256_Init(&context); + SHA256_Update(&context, buf, strlen(buf)); + SHA256_Final(sha256, &context); + + *sigret = calloc(1, RSA_size(rsa) + 1); + if (RSA_sign(NID_sha1, sha256, sizeof(sha256), + *sigret, siglen, rsa) == 0) { + fprintf(stderr, "%s: %s\n", privkey, + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + return rsa; +} + +int +sign_repo(struct xbps_handle *xhp, const char *repodir, + const char *privkey, const char *signedby) +{ + RSA *rsa = NULL; + struct repodata *rd; + struct xbps_repo *repo; + xbps_dictionary_t idx, meta; + xbps_data_t data; + unsigned int siglen; + unsigned char *sig; + char *buf, *xml, *defprivkey = NULL; + int rv = 0; + + if (signedby == NULL) { + fprintf(stderr, "--signedby unset! cannot sign repository\n"); + return -1; + } + /* + * Check that repository index exists and not empty, otherwise bail out. + */ + repo = xbps_repo_open(xhp, repodir); + if (repo == NULL) { + fprintf(stderr, "cannot read repository data: %s\n", strerror(errno)); + return -1; + } + idx = xbps_repo_get_plist(repo, XBPS_REPOIDX); + xbps_repo_close(repo); + if (xbps_dictionary_count(idx) == 0) { + fprintf(stderr, "invalid number of objects in repository index!\n"); + return -1; + } + /* + * Externalize the index and then sign it. + */ + xml = xbps_dictionary_externalize(idx); + if (xml == NULL) { + fprintf(stderr, "failed to externalize repository index: %s\n", strerror(errno)); + xbps_object_release(idx); + return -1; + } + /* + * If privkey not set, default to ~/.ssh/id_rsa. + */ + if (privkey == NULL) + defprivkey = xbps_xasprintf("%s/.ssh/id_rsa", getenv("HOME")); + else + defprivkey = strdup(privkey); + + rsa = rsa_sign_buf(defprivkey, xml, &sig, &siglen); + if (rsa == NULL) { + free(xml); + return -1; + } + /* + * Prepare the XBPS_REPOMETA for our repository data. + */ + meta = xbps_dictionary_create(); + xbps_dictionary_set_cstring_nocopy(meta, "signature-by", signedby); + xbps_dictionary_set_cstring_nocopy(meta, "signature-type", "rsa"); + data = xbps_data_create_data_nocopy(sig, siglen); + xbps_dictionary_set(meta, "signature", data); + + buf = pubkey_from_privkey(rsa); + assert(buf); + data = xbps_data_create_data_nocopy(buf, strlen(buf)); + xbps_dictionary_set(meta, "public-key", data); + xbps_dictionary_set_uint16(meta, "public-key-size", RSA_size(rsa) * 8); + + /* + * and finally write our repodata file! + */ + rd = repodata_init(xhp, repodir); + assert(rd); + xml = xbps_dictionary_externalize(idx); + assert(xml); + rv = repodata_add_buf(rd, xml, XBPS_REPOIDX); + free(xml); + xml = xbps_dictionary_externalize(meta); + assert(xml); + rv = repodata_add_buf(rd, xml, XBPS_REPOMETA); + free(xml); + repodata_flush(rd); + free(buf); + RSA_free(rsa); + + return rv; +} diff --git a/bin/xbps-rindex/xbps-rindex.8 b/bin/xbps-rindex/xbps-rindex.8 index afde2707..380fc954 100644 --- a/bin/xbps-rindex/xbps-rindex.8 +++ b/bin/xbps-rindex/xbps-rindex.8 @@ -1,4 +1,4 @@ -.Dd August 18, 2013 +.Dd October 5, 2013 .Os Void Linux .Dt xbps-rindex 8 .Sh NAME @@ -38,6 +38,17 @@ Removes obsolete packages from .Ar repository . Packages that are not currently registered in repository's index will be removed (out of date, invalid archives, etc). +.It Sy -s, --sign +Signs a repository with your specified RSA key. If +.Fl --privkey +argument not set, it defaults to +.Sy ~/.ssh/id_rsa . +.It Sy --signedby Ar string +This is required to sign a repository, use something like +.Ar name . +.It Sy --privkey Ar key +Path to the private RSA key to sign the repository. If unset, defaults to +.Sy ~/.ssh/id_rsa . .Sh ENVIRONMENT .Bl -tag -width XBPS_TARGET_ARCH .It Sy XBPS_TARGET_ARCH diff --git a/include/xbps.h.in b/include/xbps.h.in index 6703c53e..7a2003c1 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -46,7 +46,7 @@ * * This header documents the full API for the XBPS Library. */ -#define XBPS_API_VERSION "20130918" +#define XBPS_API_VERSION "20131005" #ifndef XBPS_VERSION #define XBPS_VERSION "UNSET" @@ -81,6 +81,12 @@ #define XBPS_PKGDB "pkgdb-0.21.plist" /** + * @def XBPS_REPOKEYS + * Filename for the repository keys. + */ +#define XBPS_REPOKEYS "repokeys.plist" + +/** * @def XBPS_PKGPROPS * Filename for package metadata property list. */ @@ -93,16 +99,22 @@ #define XBPS_PKGFILES "files.plist" /** - * @def XBPS_PKGINDEX - * Filename for the repository package index property list. + * @def XBPS_REPOIDX + * Filename for the repository index property list. */ -#define XBPS_PKGINDEX "index.plist" +#define XBPS_REPOIDX "index.plist" /** - * @def XBPS_PKGINDEX_FILES - * Filename for the repository package index files property list. + * @def XBPS_REPOIDX_FILES + * Filename for the repository index files property list. */ -#define XBPS_PKGINDEX_FILES "index-files.plist" +#define XBPS_REPOIDX_FILES "index-files.plist" + +/** + * @def XBPS_REPOMETA + * Filename for the repository metadata property list. + */ +#define XBPS_REPOMETA "meta.plist" /** * @def XBPS_SYSCONF_PATH @@ -238,6 +250,9 @@ extern "C" { * - XBPS_STATE_UPDATE_FAIL: package update has failed. * - XBPS_STATE_UNPACK_FAIL: package unpack has failed. * - XBPS_STATE_REPOSYNC_FAIL: syncing remote repositories has failed. + * - XBPS_STATE_REPO_KEY_IMPORT: repository is signed and needs to import pubkey. + * - XBPS_STATE_REPO_SIGVERIFIED: repository is signed and verified. + * - XBPS_STATE_REPO_SIGUNVERIFIED: repository is signed and UNVERIFIED. */ typedef enum xbps_state { XBPS_STATE_UNKNOWN = 0, @@ -274,7 +289,10 @@ typedef enum xbps_state { XBPS_STATE_UPDATE_FAIL, XBPS_STATE_UNPACK_FAIL, XBPS_STATE_REPOSYNC_FAIL, - XBPS_STATE_CONFIGURE_DONE + XBPS_STATE_CONFIGURE_DONE, + XBPS_STATE_REPO_KEY_IMPORT, + XBPS_STATE_REPO_SIGVERIFIED, + XBPS_STATE_REPO_SIGUNVERIFIED } xbps_state_t; /** @@ -485,7 +503,7 @@ struct xbps_handle { * Pointer to the supplifed function callback to be used * in the XBPS possible states. */ - void (*state_cb)(struct xbps_state_cb_data *, void *); + int (*state_cb)(struct xbps_state_cb_data *, void *); /** * @var state_cb_data * @@ -1086,6 +1104,7 @@ xbps_dictionary_t xbps_get_pkg_plist_from_binpkg(const char *fname, /** @addtogroup repopool */ /*@{*/ + /** * @struct xbps_repo xbps.h "xbps.h" * @brief Repository structure @@ -1110,17 +1129,36 @@ struct xbps_repo { * Proplib dictionary associated with the repository index files. */ xbps_dictionary_t idxfiles; + /** + * @var meta + * + * Proplib dictionary associated with the repository metadata. + */ + xbps_dictionary_t meta; /** * @var uri * * URI string associated with repository. */ const char *uri; + /** + * var is_signed + * + * True if this repository has been signed, false otherwise. + * (read-only). + */ + bool is_signed; + /** + * var is_verified + * + * True if this repository has been signed and verified against its public key. + * False if the stored public key did not match its signature. + */ + bool is_verified; /** * @var xhp * * Pointer to our xbps_handle struct passed to xbps_rpool_foreach. - * (read-only). */ struct xbps_handle *xhp; }; @@ -1154,8 +1192,8 @@ int xbps_rpool_sync(struct xbps_handle *xhp, const char *uri); * @return 0 on success, otherwise an errno value. */ int xbps_rpool_foreach(struct xbps_handle *xhp, - int (*fn)(struct xbps_repo *, void *, bool *), - void *arg); + int (*fn)(struct xbps_repo *, void *, bool *), + void *arg); /** * Finds a package dictionary in the repository pool by specifying a @@ -1617,6 +1655,24 @@ int xbps_humanize_number(char *buf, int64_t bytes); */ int xbps_cmpver(const char *pkg1, const char *pkg2); +/** + * Converts a RSA public key in PEM format to a hex fingerprint. + * + * @param[in] xhp The pointer to an xbps_handle struct. + * @param[in] pubkey The public-key in PEM format as xbps_data_t. + * + * @return The hex fingerprint. The returned buffer must be free(3)d + * when necessary. + */ +unsigned char *xbps_pubkey2fp(struct xbps_handle *xhp, xbps_data_t pubkey); + +/** + * Prints to stdout the hex fingerprint of a public key. + * + * @param[in] fp String returned by xbps_pubkey2fp(); + */ +void xbps_print_hexfp(const char *fp); + /*@}*/ #ifdef __cplusplus diff --git a/include/xbps_api_impl.h b/include/xbps_api_impl.h index c62f5a2c..43e4e0eb 100644 --- a/include/xbps_api_impl.h +++ b/include/xbps_api_impl.h @@ -159,6 +159,14 @@ int HIDDEN xbps_entry_install_conf_file(struct xbps_handle *, const char *, const char *, const char *); + +/** + * @private + * From lib/repo_keys.c + */ +int HIDDEN xbps_repo_key_import(struct xbps_repo *); +int HIDDEN xbps_repo_key_verify(struct xbps_repo *); + /** * @private * From lib/repo_pkgdeps.c @@ -225,8 +233,8 @@ int HIDDEN xbps_file_exec(struct xbps_handle *, const char *, ...); */ void HIDDEN xbps_set_cb_fetch(struct xbps_handle *, off_t, off_t, off_t, const char *, bool, bool, bool); -void HIDDEN xbps_set_cb_state(struct xbps_handle *, xbps_state_t, int, - const char *, const char *, ...); +int HIDDEN xbps_set_cb_state(struct xbps_handle *, xbps_state_t, int, + const char *, const char *, ...); /** * @private diff --git a/lib/Makefile b/lib/Makefile index b8772260..7012d185 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -38,11 +38,11 @@ OBJS += package_remove.o package_find_obsoletes.o package_state.o OBJS += package_unpack.o package_register.o package_script.o OBJS += transaction_commit.o transaction_package_replace.o OBJS += transaction_dictionary.o transaction_sortdeps.o transaction_ops.o -OBJS += transaction_revdeps.o +OBJS += transaction_revdeps.o pubkey2fp.o OBJS += download.o initend.o pkgdb.o package_conflicts.o OBJS += plist.o plist_find.o plist_match.o archive.o OBJS += plist_remove.o plist_fetch.o util.o util_hash.o -OBJS += repo.o repo_pkgdeps.o repo_sync.o +OBJS += repo.o repo_pkgdeps.o repo_sync.o repo_keys.o OBJS += rpool.o rpool_get.o cb_util.o proplib_wrapper.o OBJS += $(EXTOBJS) $(COMPAT_SRCS) diff --git a/lib/cb_util.c b/lib/cb_util.c index a42f0f36..9cf65aca 100644 --- a/lib/cb_util.c +++ b/lib/cb_util.c @@ -70,7 +70,7 @@ xbps_set_cb_fetch(struct xbps_handle *xhp, (*xhp->fetch_cb)(&xfcd, xhp->fetch_cb_data); } -void HIDDEN +int HIDDEN xbps_set_cb_state(struct xbps_handle *xhp, xbps_state_t state, int err, @@ -84,7 +84,7 @@ xbps_set_cb_state(struct xbps_handle *xhp, int retval; if (xhp->state_cb == NULL) - return; + return 0; xscd.xhp = xhp; xscd.state = state; @@ -99,7 +99,9 @@ xbps_set_cb_state(struct xbps_handle *xhp, else xscd.desc = buf; } - (*xhp->state_cb)(&xscd, xhp->state_cb_data); + retval = (*xhp->state_cb)(&xscd, xhp->state_cb_data); if (buf != NULL) free(buf); + + return retval; } diff --git a/lib/pubkey2fp.c b/lib/pubkey2fp.c new file mode 100644 index 00000000..b7fa9f19 --- /dev/null +++ b/lib/pubkey2fp.c @@ -0,0 +1,143 @@ +/* + * An implementation of convertion from OpenSSL to OpenSSH public key format + * + * Copyright (c) 2008 Mounir IDRASSI . All rights reserved. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "xbps_api_impl.h" + +static unsigned char pSshHeader[11] = { + 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2D, 0x72, 0x73, 0x61 +}; + +static +int SshEncodeBuffer(unsigned char *pEncoding, int bufferLen, + unsigned char *pBuffer) +{ + int adjustedLen = bufferLen, index; + + if (*pBuffer & 0x80) { + adjustedLen++; + pEncoding[4] = 0; + index = 5; + } else { + index = 4; + } + pEncoding[0] = (unsigned char) (adjustedLen >> 24); + pEncoding[1] = (unsigned char) (adjustedLen >> 16); + pEncoding[2] = (unsigned char) (adjustedLen >> 8); + pEncoding[3] = (unsigned char) (adjustedLen ); + memcpy(&pEncoding[index], pBuffer, bufferLen); + return index + bufferLen; +} + +unsigned char * +xbps_pubkey2fp(struct xbps_handle *xhp, xbps_data_t pubkey) +{ + EVP_MD_CTX mdctx; + EVP_PKEY *pPubKey = NULL; + RSA *pRsa = NULL; + BIO *bio = NULL; + const void *pubkeydata; + unsigned char *fpstr = NULL, md_value[EVP_MAX_MD_SIZE]; + unsigned char *nBytes = NULL, *eBytes = NULL, *pEncoding = NULL; + unsigned int md_len = 0; + int index = 0, nLen = 0, eLen = 0, encodingLength = 0; + + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + pubkeydata = xbps_data_data_nocopy(pubkey); + bio = BIO_new_mem_buf(__UNCONST(pubkeydata), xbps_data_size(pubkey)); + assert(bio); + + pPubKey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (!pPubKey) { + xbps_dbg_printf(xhp, + "unable to decode public key from the given file: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + if (EVP_PKEY_type(pPubKey->type) != EVP_PKEY_RSA) { + xbps_dbg_printf(xhp, "only RSA public keys are currently supported\n"); + goto error; + } + + pRsa = EVP_PKEY_get1_RSA(pPubKey); + if (!pRsa) { + xbps_dbg_printf(xhp, "failed to get RSA public key : %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + // reading the modulus + nLen = BN_num_bytes(pRsa->n); + nBytes = (unsigned char*) malloc(nLen); + BN_bn2bin(pRsa->n, nBytes); + + // reading the public exponent + eLen = BN_num_bytes(pRsa->e); + eBytes = (unsigned char*) malloc(eLen); + BN_bn2bin(pRsa->e, eBytes); + + encodingLength = 11 + 4 + eLen + 4 + nLen; + // correct depending on the MSB of e and N + if (eBytes[0] & 0x80) + encodingLength++; + if (nBytes[0] & 0x80) + encodingLength++; + + pEncoding = malloc(encodingLength); + memcpy(pEncoding, pSshHeader, 11); + + index = SshEncodeBuffer(&pEncoding[11], eLen, eBytes); + index = SshEncodeBuffer(&pEncoding[11 + index], nLen, nBytes); + + /* + * Compute the RSA fingerprint (MD5). + */ + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, EVP_md5(), NULL); + assert(EVP_DigestUpdate(&mdctx, pEncoding, encodingLength) != -1); + assert(EVP_DigestFinal_ex(&mdctx, md_value, &md_len) != -1); + EVP_MD_CTX_cleanup(&mdctx); + + fpstr = malloc(md_len+1); + for (unsigned int i = 0; i < md_len; i++) + fpstr[i] = md_value[i]; + + fpstr[md_len] = '\0'; + +error: + if (bio) + BIO_free_all(bio); + if (pRsa) + RSA_free(pRsa); + if (pPubKey) + EVP_PKEY_free(pPubKey); + if (nBytes) + free(nBytes); + if (eBytes) + free(eBytes); + if (pEncoding) + free(pEncoding); + + EVP_cleanup(); + ERR_free_strings(); + + return fpstr; +} diff --git a/lib/repo.c b/lib/repo.c index 7a3d591c..e1cf93e3 100644 --- a/lib/repo.c +++ b/lib/repo.c @@ -88,16 +88,17 @@ xbps_repo_open(struct xbps_handle *xhp, const char *url) archive_read_support_format_tar(repo->ar); if (stat(repofile, &st) == -1) { - xbps_dbg_printf(xhp, "[repo] cannot stat repository file %s: %s\n", - repofile, strerror(errno)); + xbps_dbg_printf(xhp, "[repo] `%s' missing repodata %s: %s\n", + url, repofile, strerror(errno)); archive_read_finish(repo->ar); free(repo); repo = NULL; goto out; } if (archive_read_open_filename(repo->ar, repofile, st.st_blksize) == ARCHIVE_FATAL) { - xbps_dbg_printf(xhp, "[repo] cannot open repository file %s: %s\n", - repofile, strerror(archive_errno(repo->ar))); + xbps_dbg_printf(xhp, + "[repo] `%s' failed to open repodata archive %s: %s\n", + url, repofile, strerror(archive_errno(repo->ar))); archive_read_finish(repo->ar); free(repo); repo = NULL; @@ -152,15 +153,21 @@ xbps_repo_close(struct xbps_repo *repo) { assert(repo); - if (repo->ar == NULL) - return; + if (repo->ar != NULL) + archive_read_finish(repo->ar); - archive_read_finish(repo->ar); - if (xbps_object_type(repo->idx) == XBPS_TYPE_DICTIONARY) + if (repo->meta != NULL) { + xbps_object_release(repo->meta); + repo->meta = NULL; + } + if (repo->idx != NULL) { xbps_object_release(repo->idx); - if (xbps_object_type(repo->idxfiles) == XBPS_TYPE_DICTIONARY) + repo->idx = NULL; + } + if (repo->idxfiles != NULL) { xbps_object_release(repo->idxfiles); - free(repo); + repo->idxfiles = NULL; + } } xbps_dictionary_t @@ -171,14 +178,9 @@ xbps_repo_get_virtualpkg(struct xbps_repo *repo, const char *pkg) assert(repo); assert(pkg); - if (repo->ar == NULL) + if (repo->ar == NULL || repo->idx == NULL) return NULL; - if (xbps_object_type(repo->idx) != XBPS_TYPE_DICTIONARY) { - repo->idx = xbps_repo_get_plist(repo, XBPS_PKGINDEX); - if (repo->idx == NULL) - return NULL; - } pkgd = xbps_find_virtualpkg_in_dict(repo->xhp, repo->idx, pkg); if (pkgd) { xbps_dictionary_set_cstring_nocopy(pkgd, @@ -196,14 +198,9 @@ xbps_repo_get_pkg(struct xbps_repo *repo, const char *pkg) assert(repo); assert(pkg); - if (repo->ar == NULL) + if (repo->ar == NULL || repo->idx == NULL) return NULL; - if (xbps_object_type(repo->idx) != XBPS_TYPE_DICTIONARY) { - repo->idx = xbps_repo_get_plist(repo, XBPS_PKGINDEX); - if (repo->idx == NULL) - return NULL; - } pkgd = xbps_find_pkg_in_dict(repo->idx, pkg); if (pkgd) { xbps_dictionary_set_cstring_nocopy(pkgd, @@ -336,6 +333,9 @@ xbps_repo_get_pkg_revdeps(struct xbps_repo *repo, const char *pkg) char *buf = NULL; bool match = false; + if (repo->idx == NULL) + return NULL; + if (((pkgd = xbps_rpool_get_pkg(repo->xhp, pkg)) == NULL) && ((pkgd = xbps_rpool_get_virtualpkg(repo->xhp, pkg)) == NULL)) { errno = ENOENT; diff --git a/lib/repo_keys.c b/lib/repo_keys.c new file mode 100644 index 00000000..258acff1 --- /dev/null +++ b/lib/repo_keys.c @@ -0,0 +1,233 @@ +/*- + * Copyright (c) 2013 Juan Romero Pardines. + * 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 ``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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "xbps_api_impl.h" + +int HIDDEN +xbps_repo_key_import(struct xbps_repo *repo) +{ + xbps_dictionary_t repokeyd, rkeysd = NULL, newmetad = NULL; + xbps_data_t rpubkey; + const char *signedby; + unsigned char *fp; + char *rkeypath = NULL; + int import, rv = 0; + + assert(repo); + /* + * If repository does not have required metadata plist, ignore it. + */ + if (xbps_dictionary_count(repo->meta) == 0) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s' missing required metadata, ignoring.\n", repo->uri); + repo->is_verified = false; + repo->is_signed = false; + return 0; + } + /* + * Check the repository provides a working public-key data object. + */ + rpubkey = xbps_dictionary_get(repo->meta, "public-key"); + if (xbps_object_type(rpubkey) != XBPS_TYPE_DATA) { + rv = EINVAL; + xbps_dbg_printf(repo->xhp, + "[repo] `%s' invalid public-key object!\n", repo->uri); + repo->is_verified = false; + repo->is_signed = false; + goto out; + } + repo->is_signed = true; + /* + * Check if the public key has been stored for this repository. + */ + rkeypath = xbps_xasprintf("%s/%s", repo->xhp->metadir, XBPS_REPOKEYS); + rkeysd = xbps_dictionary_internalize_from_file(rkeypath); + if (xbps_object_type(rkeysd) != XBPS_TYPE_DICTIONARY) + rkeysd = xbps_dictionary_create(); + + repokeyd = xbps_dictionary_get(rkeysd, repo->uri); + if (xbps_object_type(repokeyd) == XBPS_TYPE_DICTIONARY) { + if (xbps_dictionary_get(repokeyd, "public-key")) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s' public key already stored.\n", + repo->uri); + goto out; + } + } + /* + * Notify the client and take appropiate action to import + * the repository public key. Pass back the public key openssh fingerprint + * to the client. + */ + fp = xbps_pubkey2fp(repo->xhp, rpubkey); + xbps_dictionary_get_cstring_nocopy(repo->meta, "signature-by", &signedby); + import = xbps_set_cb_state(repo->xhp, XBPS_STATE_REPO_KEY_IMPORT, + 0, (const char *)fp, + "This repository is RSA signed by \"%s\"", + signedby); + free(fp); + if (import <= 0) + goto out; + /* + * Add the meta dictionary into XBPS_REPOKEYS and externalize it. + */ + newmetad = xbps_dictionary_copy_mutable(repo->meta); + xbps_dictionary_remove(newmetad, "signature"); + xbps_dictionary_set(rkeysd, repo->uri, newmetad); + + if (access(repo->xhp->metadir, R_OK|W_OK) == -1) { + if (errno == ENOENT) { + xbps_mkpath(repo->xhp->metadir, 0755); + } else { + rv = errno; + xbps_dbg_printf(repo->xhp, + "[repo] `%s' cannot create metadir: %s\n", + repo->uri, strerror(errno)); + goto out; + } + } + if (!xbps_dictionary_externalize_to_file(rkeysd, rkeypath)) { + rv = errno; + xbps_dbg_printf(repo->xhp, + "[repo] `%s' failed to externalize %s: %s\n", + repo->uri, XBPS_REPOKEYS, strerror(rv)); + } + +out: + if (newmetad) + xbps_object_release(newmetad); + if (xbps_object_type(rkeysd) == XBPS_TYPE_DICTIONARY) + xbps_object_release(rkeysd); + free(rkeypath); + return rv; +} + +static int +rsa_verify_buf(struct xbps_repo *repo, xbps_data_t sigdata, + xbps_data_t pubkey, const char *buf) +{ + SHA256_CTX context; + BIO *bio; + RSA *rsa; + unsigned char sha256[SHA256_DIGEST_LENGTH]; + int rv = 0; + + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + + bio = BIO_new_mem_buf(__UNCONST(xbps_data_data_nocopy(pubkey)), + xbps_data_size(pubkey)); + assert(bio); + + rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + if (rsa == NULL) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s' error reading public key: %s\n", + repo->uri, ERR_error_string(ERR_get_error(), NULL)); + return EINVAL; + } + + SHA256_Init(&context); + SHA256_Update(&context, buf, strlen(buf)); + SHA256_Final(sha256, &context); + + if (RSA_verify(NID_sha1, sha256, sizeof(sha256), + xbps_data_data_nocopy(sigdata), + xbps_data_size(sigdata), rsa) == 0) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s' failed to verify signature: %s\n", + repo->uri, ERR_error_string(ERR_get_error(), NULL)); + rv = EPERM; + } + RSA_free(rsa); + BIO_free(bio); + ERR_free_strings(); + + return rv; +} + +int HIDDEN +xbps_repo_key_verify(struct xbps_repo *repo) +{ + xbps_dictionary_t rkeysd, repokeyd; + xbps_data_t sigdata, pubkey; + char *rkeyspath, *idx_xml; + bool verified = false; + + /* unsigned repo */ + if (!repo->is_signed) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s' ignoring unsigned repository.\n", repo->uri); + return 0; + } + rkeyspath = xbps_xasprintf("%s/%s", repo->xhp->metadir, XBPS_REPOKEYS); + rkeysd = xbps_dictionary_internalize_from_file(rkeyspath); + if (xbps_dictionary_count(rkeysd) == 0) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s': failed to internalize %s: %s\n", + repo->uri, rkeyspath, strerror(errno)); + free(rkeyspath); + return ENODEV; + } + free(rkeyspath); + + repokeyd = xbps_dictionary_get(rkeysd, repo->uri); + if (xbps_dictionary_count(repokeyd) == 0) { + xbps_dbg_printf(repo->xhp, + "[repo] `%s': empty %s dictionary\n", + repo->uri, XBPS_REPOKEYS); + xbps_object_release(rkeysd); + return ENOENT; + } + + idx_xml = xbps_dictionary_externalize(repo->idx); + assert(idx_xml); + + sigdata = xbps_dictionary_get(repo->meta, "signature"); + assert(xbps_object_type(sigdata) == XBPS_TYPE_DATA); + pubkey = xbps_dictionary_get(repokeyd, "public-key"); + assert(xbps_object_type(pubkey) == XBPS_TYPE_DATA); + /* XXX ignore 'signature-type' for now */ + if (rsa_verify_buf(repo, sigdata, pubkey, idx_xml) == 0) { + repo->is_verified = true; + verified = true; + } + free(idx_xml); + + return verified ? 0 : EPERM; +} diff --git a/lib/rpool.c b/lib/rpool.c index 30b2d224..450b8b9a 100644 --- a/lib/rpool.c +++ b/lib/rpool.c @@ -53,7 +53,7 @@ xbps_rpool_init(struct xbps_handle *xhp) struct rpool *rp; const char *repouri; bool foundrepo = false; - int rv = 0; + int retval, rv = 0; assert(xhp); @@ -65,15 +65,59 @@ xbps_rpool_init(struct xbps_handle *xhp) assert(rp); xbps_array_get_cstring_nocopy(xhp->repositories, i, &repouri); if ((rp->repo = xbps_repo_open(xhp, repouri)) == NULL) { - rp->repo = calloc(1, sizeof(struct xbps_repo)); + rp->repo = malloc(sizeof(struct xbps_repo)); assert(rp->repo); + rp->repo->ar = NULL; + rp->repo->is_verified = false; + rp->repo->is_signed = false; } - rp->repo->idx = xbps_repo_get_plist(rp->repo, XBPS_PKGINDEX); + rp->repo->idx = xbps_repo_get_plist(rp->repo, XBPS_REPOIDX); + if (xbps_object_type(rp->repo->idx) == XBPS_TYPE_DICTIONARY) + xbps_dictionary_make_immutable(rp->repo->idx); + + rp->repo->meta = xbps_repo_get_plist(rp->repo, XBPS_REPOMETA); + if (xbps_object_type(rp->repo->meta) == XBPS_TYPE_DICTIONARY) + xbps_dictionary_make_immutable(rp->repo->meta); + rp->repo->uri = repouri; rp->repo->xhp = xhp; + + if (xbps_repository_is_remote(repouri)) { + /* + * Import the RSA public key (if it's signed). + */ + retval = xbps_repo_key_import(rp->repo); + if (retval != 0) { + /* any error */ + xbps_dbg_printf(xhp, "[rpool] %s: key_import %s\n", + rp->repo->uri, strerror(retval)); + } + /* + * Check the repository signature against stored public key. + */ + retval = xbps_repo_key_verify(rp->repo); + if (retval == 0) { + /* signed, verified */ + xbps_set_cb_state(xhp, XBPS_STATE_REPO_SIGVERIFIED, 0, NULL, NULL); + } else if (retval == EPERM) { + /* signed, unverified */ + xbps_set_cb_state(xhp, XBPS_STATE_REPO_SIGUNVERIFIED, 0, NULL, NULL); + xbps_repo_close(rp->repo); + } else { + /* any error */ + xbps_dbg_printf(xhp, "[rpool] %s: key_verify %s\n", + rp->repo->uri, strerror(retval)); + xbps_repo_close(rp->repo); + } + } + /* + * If repository has passed signature checks, add it to the pool. + */ SIMPLEQ_INSERT_TAIL(&rpool_queue, rp, entries); foundrepo = true; - xbps_dbg_printf(xhp, "[rpool] `%s' registered.\n", repouri); + xbps_dbg_printf(xhp, "[rpool] `%s' registered (%s, %s).\n", + repouri, rp->repo->is_signed ? "signed" : "unsigned", + rp->repo->is_verified ? "verified" : "unverified"); } if (!foundrepo) { /* no repositories available, error out */ @@ -101,6 +145,7 @@ xbps_rpool_release(struct xbps_handle *xhp) while ((rp = SIMPLEQ_FIRST(&rpool_queue))) { SIMPLEQ_REMOVE(&rpool_queue, rp, rpool, entries); xbps_repo_close(rp->repo); + free(rp->repo); free(rp); } xhp->rpool_initialized = false; @@ -131,8 +176,8 @@ xbps_rpool_sync(struct xbps_handle *xhp, const char *uri) int xbps_rpool_foreach(struct xbps_handle *xhp, - int (*fn)(struct xbps_repo *, void *, bool *), - void *arg) + int (*fn)(struct xbps_repo *, void *, bool *), + void *arg) { struct rpool *rp; int rv = 0; @@ -142,21 +187,14 @@ xbps_rpool_foreach(struct xbps_handle *xhp, /* Initialize repository pool */ if ((rv = xbps_rpool_init(xhp)) != 0) { if (rv == ENOTSUP) { - xbps_dbg_printf(xhp, - "[rpool] empty repository list.\n"); + xbps_dbg_printf(xhp, "[rpool] empty repository list.\n"); } else if (rv != ENOENT && rv != ENOTSUP) { - xbps_dbg_printf(xhp, - "[rpool] couldn't initialize: %s\n", - strerror(rv)); + xbps_dbg_printf(xhp, "[rpool] couldn't initialize: %s\n", strerror(rv)); } return rv; } /* Iterate over repository pool */ SIMPLEQ_FOREACH(rp, &rpool_queue, entries) { - /* ignore invalid repos */ - if (rp->repo->idx == NULL) - continue; - rv = (*fn)(rp->repo, arg, &done); if (rv != 0 || done) break; diff --git a/lib/util.c b/lib/util.c index f7bd1141..0f41098a 100644 --- a/lib/util.c +++ b/lib/util.c @@ -297,3 +297,20 @@ xbps_humanize_number(char *buf, int64_t bytes) return humanize_number(buf, 7, bytes, "B", HN_AUTOSCALE, HN_DECIMAL|HN_NOSPACE); } + +void +xbps_print_hexfp(const char *fp) +{ + unsigned char *fpstr; + unsigned int i, c, len; + + fpstr = (unsigned char *)(void *)(unsigned long)(const void *)fp; + len = strlen(fp); + + for (i = 0; i < len; i++) { + printf("%02x", fpstr[i]); + c = i + 1; + if (c < len) + putchar(':'); + } +} diff --git a/mk/prog.mk b/mk/prog.mk index 2e69d096..a8c1b61d 100644 --- a/mk/prog.mk +++ b/mk/prog.mk @@ -41,7 +41,7 @@ endif %.o: %.c @printf " [CC]\t\t$@\n" - ${SILENT}$(CC) $(CPPFLAGS) $(CFLAGS) -c $< + ${SILENT}$(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) -c $< $(BIN).static: $(OBJS) @printf " [CCLD]\t\t$@\n"