Added support for the long awaited feature: RSA signed repositories.

This commit is contained in:
Juan RP 2013-10-05 11:38:04 +02:00
parent ae2eea8937
commit 8d5c48b861
29 changed files with 1121 additions and 155 deletions

17
NEWS
View File

@ -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 <foo@bar>" --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

5
TODO
View File

@ -7,9 +7,6 @@ xbps-create:
- Move all configuration files to <prefix>/share/<pkgname>/conf/<cffile>.
- 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...

View File

@ -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);

View File

@ -31,13 +31,14 @@
#include <xbps.h>
#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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,23 +28,6 @@
#include <xbps.h>
/* 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_ */

View File

@ -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;
}

View File

@ -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);

View File

@ -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 <key> Path to the private key for signing\n"
" --signedby <string> Signature details, i.e \"name <email>\"\n\n"
"MODE\n"
" -a --add <repodir/pkg> ... Add package(s) to repository index\n"
" -c --clean <repodir> Cleans obsolete entries in repository index\n"
" -r --remove-obsoletes <repodir> Removes obsolete packages from repository\n\n");
" -r --remove-obsoletes <repodir> Removes obsolete packages from repository\n"
" -s --sign <repodir> 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);
}

View File

@ -0,0 +1,123 @@
/* $NetBSD: readpassphrase.c,v 1.1 2009/06/07 22:38:47 christos Exp $ */
/*
* Copyright (c) 2000 Todd C. Miller <Todd.Miller@courtesan.com>
* 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#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);
}

View File

@ -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;
}

View File

@ -37,58 +37,54 @@
#include <xbps.h>
#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);
}

231
bin/xbps-rindex/sign.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#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;
}

View File

@ -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 <email> .
.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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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;
}

143
lib/pubkey2fp.c Normal file
View File

@ -0,0 +1,143 @@
/*
* An implementation of convertion from OpenSSL to OpenSSH public key format
*
* Copyright (c) 2008 Mounir IDRASSI <mounir.idrassi@idrix.fr>. 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#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;
}

View File

@ -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;

233
lib/repo_keys.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#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;
}

View File

@ -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;

View File

@ -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(':');
}
}

View File

@ -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"