diff --git a/include/xbps.h.in b/include/xbps.h.in index dd9689a1d..55a004846 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -1465,17 +1465,17 @@ struct xbps_repo { */ const char *uri; /** - * var is_remote - * * True if repository is remote, false if it's a local repository. */ bool is_remote; /** - * var is_signed - * * True if this repository has been signed, false otherwise. */ bool is_signed; + /** + * Loaded trusted public-key. + */ + struct pubkey *pubkey; }; void xbps_rpool_release(struct xbps_handle *xhp); @@ -1910,31 +1910,6 @@ bool xbps_file_sha256_raw(unsigned char *dst, size_t len, const char *file); */ int xbps_file_sha256_check(const char *file, const char *sha256); -/** - * Verifies the RSA signature \a sigfile against \a digest with the - * RSA public-key associated in \a repo. - * - * @param[in] repo Repository to use with the RSA public key associated. - * @param[in] sigfile The signature file name used to verify \a digest. - * @param[in] digest The digest to verify. - * - * @return True if the signature is valid, false otherwise. - */ -bool xbps_verify_signature(struct xbps_repo *repo, const char *sigfile, - unsigned char *digest); - -/** - * Verifies the RSA signature of \a fname with the RSA public-key associated - * in \a repo. - * - * @param[in] repo Repository to use with the RSA public key associated. - * @param[in] fname The filename to verify, the signature file must have a .sig2 - * extension, i.e `.sig2`. - * - * @return True if the signature is valid, false otherwise. - */ -bool xbps_verify_file_signature(struct xbps_repo *repo, const char *fname); - /** * Checks if a package is currently installed in pkgdb by matching \a pkg. * To be installed, the pkg must be in "installed" or "unpacked" state. diff --git a/include/xbps_api_impl.h b/include/xbps_api_impl.h index 377c9a512..652ca6b97 100644 --- a/include/xbps_api_impl.h +++ b/include/xbps_api_impl.h @@ -127,4 +127,15 @@ struct xbps_repo HIDDEN *xbps_regget_repo(struct xbps_handle *, const char *); int HIDDEN xbps_conf_init(struct xbps_handle *); +struct pubkey; +struct pubkey HIDDEN *pubkey_load_rsa(xbps_data_t data); +void HIDDEN pubkey_free(struct pubkey *pubkey); +int pubkey_verify(const struct pubkey *pubkey, const unsigned char *sig, + unsigned int siglen, const unsigned char *md, size_t mdlen); + +int HIDDEN repo_sig_verify_file(struct xbps_repo *repo, const char *file, + const char *sigfile); +int HIDDEN repo_sig_verify_digest(struct xbps_repo *repo, const char *sigfile, + const unsigned char *md, size_t mdlen); + #endif /* !_XBPS_API_IMPL_H_ */ diff --git a/lib/repo.c b/lib/repo.c index 86c955e03..0b841e9e7 100644 --- a/lib/repo.c +++ b/lib/repo.c @@ -34,6 +34,8 @@ #include #include +#include + #include #include @@ -164,6 +166,99 @@ repo_read_index(struct xbps_repo *repo, struct archive *ar) return 0; } +static int +repo_load_key(struct xbps_repo *repo) +{ + char keypath[PATH_MAX]; + xbps_dictionary_t trusted_keyd; + xbps_data_t pubkey, trusted_pubkey; + uint16_t pubkey_size, trusted_pubkey_size; + char *hexfp; + int r; + + if (!repo->idxmeta) + return 0; + + pubkey = xbps_dictionary_get(repo->idxmeta, "public-key"); + if (xbps_object_type(pubkey) != XBPS_TYPE_DATA) + return 0; + + if (!xbps_dictionary_get_uint16( + repo->idxmeta, "public-key-size", &pubkey_size)) { + xbps_error_printf( + "%s: failed to load public key: missing public key size\n", + repo->uri); + return -EINVAL; + } + + hexfp = xbps_pubkey2fp(pubkey); + if (!hexfp) { + r = errno ? -errno : -EINVAL; + xbps_error_printf( + "%s: failed to create public key fingerprint: %s\n", + repo->uri, strerror(-r)); + return r; + } + + repo->is_signed = true; + + r = snprintf(keypath, sizeof(keypath), "%s/keys/%s.plist", + repo->xhp->metadir, hexfp); + if (r < 0 || (size_t)r >= sizeof(keypath)) { + r = -ENAMETOOLONG; + xbps_error_printf("%s: failed get public key path: %s\n", + repo->uri, strerror(-r)); + return r; + } + + trusted_keyd = xbps_plist_dictionary_from_file(keypath); + if (!trusted_keyd) { + if (errno == ENOENT) + return 0; + r = errno ? -errno : -EINVAL; + xbps_error_printf("failed to read public key: %s: %s\n", + keypath, strerror(-r)); + return r; + } + + trusted_pubkey = xbps_dictionary_get(trusted_keyd, "public-key"); + if (xbps_object_type(trusted_pubkey) != XBPS_TYPE_DATA) { + r = -EINVAL; + xbps_error_printf( + "failed to read public key: %s: missing public-key\n", + keypath); + return r; + } + if (!xbps_dictionary_get_uint16( + trusted_keyd, "public-key-size", &trusted_pubkey_size)) { + r = -EINVAL; + xbps_error_printf( + "failed to read public key: %s: missing public-key-size\n", + keypath); + xbps_object_release(trusted_keyd); + return r; + } + + if (pubkey_size != trusted_pubkey_size || + !xbps_data_equals(pubkey, trusted_pubkey)) { + r = -ERANGE; + xbps_error_printf("%s: repository public-key does not " + "match trusted public key: %s\n", + repo->uri, keypath); + xbps_object_release(trusted_keyd); + return r; + } + + repo->pubkey = pubkey_load_rsa(trusted_pubkey); + if (!repo->pubkey) { + xbps_object_release(trusted_keyd); + return -errno; + } + + xbps_object_release(trusted_keyd); + return 0; +} + static int repo_read_meta(struct xbps_repo *repo, struct archive *ar) { @@ -188,7 +283,7 @@ repo_read_meta(struct xbps_repo *repo, struct archive *ar) repo->uri, archive_error_string(ar)); return -archive_errno(ar); } - repo->idxmeta = xbps_dictionary_create(); + repo->idxmeta = NULL; return 0; } @@ -213,12 +308,14 @@ repo_read_meta(struct xbps_repo *repo, struct archive *ar) if (!repo->idxmeta) { if (!r) { - xbps_error_printf("failed to read repository metadata: %s: invalid dictionary\n", + xbps_error_printf("failed to read repository metadata: " + "%s: invalid dictionary\n", repo->uri); return -EINVAL; } - xbps_error_printf("failed to read repository metadata: %s: %s\n", - repo->uri, strerror(-r)); + xbps_error_printf( + "failed to read repository metadata: %s: %s\n", repo->uri, + strerror(-r)); return r; } @@ -508,6 +605,13 @@ xbps_repo_open(struct xbps_handle *xhp, const char *url) return NULL; } + r = repo_load_key(repo); + if (r < 0) { + xbps_repo_release(repo); + errno = -r; + return NULL; + } + if (xbps_dictionary_count(repo->stage) == 0 || (repo->is_remote && !(xhp->flags & XBPS_FLAG_USE_STAGE))) { repo->idx = repo->index; @@ -547,6 +651,10 @@ xbps_repo_release(struct xbps_repo *repo) xbps_object_release(repo->stage); repo->idx = NULL; } + if (repo->pubkey) { + pubkey_free(repo->pubkey); + repo->pubkey = NULL; + } if (repo->idxmeta) { xbps_object_release(repo->idxmeta); repo->idxmeta = NULL; @@ -858,3 +966,90 @@ xbps_repo_key_import(struct xbps_repo *repo) free(rkeyfile); return rv; } + +static int +read_signature(const char *path, unsigned char *buf, size_t bufsz) +{ + size_t used = 0; + int fd; + int r; + + fd = open(path, O_RDONLY); + if (fd == -1) { + r = -errno; + xbps_error_printf("failed to read signature file: could not " + "open: %s: %s\n", + path, strerror(-r)); + return r; + } + + for (;;) { + ssize_t rd; + + rd = read(fd, buf + used, bufsz - used); + if (rd == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + r = -errno; + xbps_error_printf("failed to read signature file: %s: " + "could not read: %s\n", + path, strerror(-r)); + close(fd); + return r; + } + used += rd; + if (rd == 0 || used == bufsz) + break; + } + + close(fd); + + if (used < bufsz) { + xbps_error_printf( + "failed to read signature: file too short\n"); + return -EINVAL; + } + return 0; +} + +int HIDDEN +repo_sig_verify_digest(struct xbps_repo *repo, const char *sigfile, + const unsigned char *md, size_t mdlen) +{ + unsigned char sigbuf[512]; + int r; + + if (!repo->pubkey) { + xbps_error_printf("failed to verify file: repository has no " + "trusted public key\n"); + return -EPERM; + } + + r = read_signature(sigfile, sigbuf, sizeof(sigbuf)); + if (r < 0) + return r; + + return pubkey_verify(repo->pubkey, sigbuf, sizeof(sigbuf), md, mdlen); +} + +int HIDDEN +repo_sig_verify_file(struct xbps_repo *repo, const char *file, const char *sigfile) +{ + unsigned char md[SHA256_DIGEST_LENGTH]; + + if (!repo->pubkey) { + xbps_error_printf("failed to verify file: repository has no " + "trusted public key\n"); + return -EPERM; + } + + if (!xbps_file_sha256_raw(md, sizeof(md), file)) { + int r = -errno; + xbps_error_printf("failed to verify file: failed to " + "generate sha256 checksum: %s\n", + strerror(-r)); + return r; + } + + return repo_sig_verify_digest(repo, sigfile, md, sizeof(md)); +} diff --git a/lib/transaction_fetch.c b/lib/transaction_fetch.c index 22182e16e..f7301621a 100644 --- a/lib/transaction_fetch.c +++ b/lib/transaction_fetch.c @@ -37,6 +37,7 @@ static int verify_binpkg(struct xbps_handle *xhp, xbps_dictionary_t pkgd) { char binfile[PATH_MAX]; + char sigfile[PATH_MAX]; struct xbps_repo *repo; const char *pkgver, *repoloc, *sha256; ssize_t l; @@ -49,6 +50,10 @@ verify_binpkg(struct xbps_handle *xhp, xbps_dictionary_t pkgd) if (l < 0) return -l; + l = snprintf(sigfile, sizeof(sigfile), "%s.sig2", binfile); + if (l < 0 || (size_t)l >= sizeof(sigfile)) + return ENAMETOOLONG; + /* * For pkgs in local repos check the sha256 hash. * For pkgs in remote repos check the RSA signature. @@ -60,11 +65,15 @@ verify_binpkg(struct xbps_handle *xhp, xbps_dictionary_t pkgd) return rv; } if (repo->is_remote) { + int r; /* remote repo */ xbps_set_cb_state(xhp, XBPS_STATE_VERIFY, 0, pkgver, "%s: verifying RSA signature...", pkgver); - if (!xbps_verify_file_signature(repo, binfile)) { + r = repo_sig_verify_file(repo, binfile, sigfile); + if (r < 0) { + return -r; + } else if (r == 0) { rv = EPERM; xbps_set_cb_state(xhp, XBPS_STATE_VERIFY_FAIL, rv, pkgver, "%s: the RSA signature is not valid!", pkgver); @@ -95,8 +104,8 @@ static int download_binpkg(struct xbps_handle *xhp, xbps_dictionary_t repo_pkgd) { struct xbps_repo *repo; - char buf[PATH_MAX]; - char *sigsuffix; + char file[PATH_MAX]; + char sigfile[PATH_MAX]; const char *pkgver, *arch, *fetchstr, *repoloc; unsigned char digest[XBPS_SHA256_DIGEST_SIZE] = {0}; int rv = 0; @@ -108,13 +117,18 @@ download_binpkg(struct xbps_handle *xhp, xbps_dictionary_t repo_pkgd) xbps_dictionary_get_cstring_nocopy(repo_pkgd, "pkgver", &pkgver); xbps_dictionary_get_cstring_nocopy(repo_pkgd, "architecture", &arch); - snprintf(buf, sizeof buf, "%s/%s.%s.xbps.sig2", repoloc, pkgver, arch); - sigsuffix = buf+(strlen(buf)-sizeof (".sig2")+1); + rv = snprintf(file, sizeof(file), "%s/%s.%s.xbps", repoloc, pkgver, arch); + if (rv < 0 || (size_t)rv >= sizeof(file)) + return ENAMETOOLONG; + + rv = snprintf(sigfile, sizeof(sigfile), "%s.sig2", file); + if (rv < 0 || (size_t)rv >= sizeof(sigfile)) + return ENAMETOOLONG; xbps_set_cb_state(xhp, XBPS_STATE_DOWNLOAD, 0, pkgver, "Downloading `%s' signature (from `%s')...", pkgver, repoloc); - if ((rv = xbps_fetch_file(xhp, buf, NULL)) == -1) { + if ((rv = xbps_fetch_file(xhp, sigfile, NULL)) == -1) { rv = fetchLastErrCode ? fetchLastErrCode : errno; fetchstr = xbps_fetch_error_string(); xbps_set_cb_state(xhp, XBPS_STATE_DOWNLOAD_FAIL, rv, @@ -124,12 +138,10 @@ download_binpkg(struct xbps_handle *xhp, xbps_dictionary_t repo_pkgd) } rv = 0; - *sigsuffix = '\0'; - xbps_set_cb_state(xhp, XBPS_STATE_DOWNLOAD, 0, pkgver, "Downloading `%s' package (from `%s')...", pkgver, repoloc); - if ((rv = xbps_fetch_file_sha256(xhp, buf, NULL, digest, + if ((rv = xbps_fetch_file_sha256(xhp, file, NULL, digest, sizeof digest)) == -1) { rv = fetchLastErrCode ? fetchLastErrCode : errno; fetchstr = xbps_fetch_error_string(); @@ -143,8 +155,14 @@ download_binpkg(struct xbps_handle *xhp, xbps_dictionary_t repo_pkgd) xbps_set_cb_state(xhp, XBPS_STATE_VERIFY, 0, pkgver, "%s: verifying RSA signature...", pkgver); - snprintf(buf, sizeof buf, "%s/%s.%s.xbps.sig2", xhp->cachedir, pkgver, arch); - sigsuffix = buf+(strlen(buf)-sizeof (".sig2")+1); + rv = snprintf(file, sizeof(file), "%s/%s.%s.xbps", xhp->cachedir, pkgver, arch); + if (rv < 0 || (size_t)rv >= sizeof(file)) + return ENAMETOOLONG; + + rv = snprintf(sigfile, sizeof(sigfile), "%s.sig2", file); + if (rv < 0 || (size_t)rv >= sizeof(sigfile)) + return ENAMETOOLONG; + if ((repo = xbps_rpool_get_repo(repoloc)) == NULL) { rv = errno; @@ -158,24 +176,31 @@ download_binpkg(struct xbps_handle *xhp, xbps_dictionary_t repo_pkgd) * i.e. 304 not modified, verify by file instead. */ if (fetchLastErrCode == FETCH_UNCHANGED) { - *sigsuffix = '\0'; - if (!xbps_verify_file_signature(repo, buf)) { + int r; + r = repo_sig_verify_file(repo, file, sigfile); + if (r < 0) { + return -r; + } else if (r == 0) { rv = EPERM; /* remove binpkg */ - (void)remove(buf); + (void)remove(file); /* remove signature */ - *sigsuffix = '.'; - (void)remove(buf); + (void)remove(sigfile); } + rv = 0; } else { - if (!xbps_verify_signature(repo, buf, digest)) { + int r; + r = repo_sig_verify_digest(repo, sigfile, digest, sizeof(digest)); + if (r < 0) { + return -r; + } else if (r == 0) { rv = EPERM; - /* remove signature */ - (void)remove(buf); /* remove binpkg */ - *sigsuffix = '\0'; - (void)remove(buf); + (void)remove(file); + /* remove signature */ + (void)remove(sigfile); } + rv = 0; } if (rv == EPERM) { diff --git a/lib/verifysig.c b/lib/verifysig.c index b8b8f4c2d..bffc2b916 100644 --- a/lib/verifysig.c +++ b/lib/verifysig.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2013-2014 Juan Romero Pardines. + * Copyright (c) 2025 Duncan Overbruck . * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,129 +24,161 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include + +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include #include +#include +#include #include "xbps_api_impl.h" -static bool -rsa_verify_hash(struct xbps_repo *repo, xbps_data_t pubkey, - unsigned char *sig, unsigned int siglen, - unsigned char *sha256) -{ - BIO *bio; - RSA *rsa; - int rv; +enum pubkey_type { + PUBKEY_RSA, +}; - ERR_load_crypto_strings(); - SSL_load_error_strings(); +struct pubkey { + enum pubkey_type type; +}; - bio = BIO_new_mem_buf(xbps_data_data_nocopy(pubkey), - xbps_data_size(pubkey)); - assert(bio); +struct pubkey_rsa { + struct pubkey common; + EVP_PKEY *pkey; +}; - rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); - if (rsa == NULL) { - xbps_dbg_printf("`%s' error reading public key: %s\n", - repo->uri, ERR_error_string(ERR_get_error(), NULL)); - return false; +struct pubkey HIDDEN * +pubkey_load_rsa(xbps_data_t data) +{ + EVP_PKEY *pkey; + BIO *bio; + int r; + struct pubkey_rsa *pubkey; + + pubkey = calloc(1, sizeof(*pubkey)); + if (!pubkey) { + r = -errno; + xbps_error_printf("failed to load pubkey: %s\n", strerror(-r)); + errno = -r; + return NULL; + } + + bio = + BIO_new_mem_buf(xbps_data_data_nocopy(data), xbps_data_size(data)); + if (!bio) { + r = -errno; + xbps_error_printf( + "failed to load pubkey: BIO_new_mem_buf: %s\n", + strerror(-r)); + free(pubkey); + errno = -r; + return NULL; + } + pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (!pkey) { + r = -EINVAL; + xbps_error_printf( + "failed to load pubkey: reading PEM failed\n"); + BIO_free(bio); + free(pubkey); + errno = -r; + return NULL; + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + r = -EINVAL; + xbps_error_printf( + "failed to load pubkey: unsupported key type\n"); + BIO_free(bio); + EVP_PKEY_free(pkey); + free(pubkey); + errno = -r; + return NULL; } - rv = RSA_verify(NID_sha256, sha256, SHA256_DIGEST_LENGTH, sig, siglen, rsa); - RSA_free(rsa); BIO_free(bio); - ERR_free_strings(); + pubkey->common.type = PUBKEY_RSA; + pubkey->pkey = pkey; + return (struct pubkey *)pubkey; +} - return rv ? true : false; +void HIDDEN +pubkey_free(struct pubkey *pubkey) +{ + struct pubkey_rsa *rsa; + if (!pubkey) + return; + switch (pubkey->type) { + case PUBKEY_RSA: + rsa = (struct pubkey_rsa *)pubkey; + EVP_PKEY_free(rsa->pkey); + free(rsa); + break; + } } -bool -xbps_verify_signature(struct xbps_repo *repo, const char *sigfile, - unsigned char *digest) +static int +pubkey_rsa_verify(const struct pubkey_rsa *pubkey, const unsigned char *sig, + unsigned int siglen, const unsigned char *md, size_t mdlen) { - xbps_dictionary_t repokeyd = NULL; - xbps_data_t pubkey; - char *hexfp = NULL; - unsigned char *sig_buf = NULL; - size_t sigbuflen, sigfilelen; - char *rkeyfile = NULL; - bool val = false; - - if (!xbps_dictionary_count(repo->idxmeta)) { - xbps_dbg_printf("%s: unsigned repository\n", repo->uri); - return false; + EVP_PKEY_CTX *ctx; + int r; + + ctx = EVP_PKEY_CTX_new(pubkey->pkey, NULL); + if (!ctx) { + xbps_error_printf( + "failed to verify signature: EVP_PKEY_CTX_new: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return errno ? -errno : -ENOTSUP; } - hexfp = xbps_pubkey2fp(xbps_dictionary_get(repo->idxmeta, "public-key")); - if (hexfp == NULL) { - xbps_dbg_printf("%s: incomplete signed repo, missing hexfp obj\n", repo->uri); - return false; + if (EVP_PKEY_verify_init(ctx) <= 0) { + xbps_error_printf( + "failed to verify signature: EVP_PKEY_verify_init: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; } - - /* - * Prepare repository RSA public key to verify fname signature. - */ - rkeyfile = xbps_xasprintf("%s/keys/%s.plist", repo->xhp->metadir, hexfp); - repokeyd = xbps_plist_dictionary_from_file(rkeyfile); - if (xbps_object_type(repokeyd) != XBPS_TYPE_DICTIONARY) { - xbps_dbg_printf("cannot read rkey data at %s: %s\n", - rkeyfile, strerror(errno)); - goto out; + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { + xbps_error_printf("failed to verify signature: " + "EVP_PKEY_CTX_set_rsa_padding: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; } - - pubkey = xbps_dictionary_get(repokeyd, "public-key"); - if (xbps_object_type(pubkey) != XBPS_TYPE_DATA) - goto out; - - if (!xbps_mmap_file(sigfile, (void *)&sig_buf, &sigbuflen, &sigfilelen)) { - xbps_dbg_printf("can't open signature file %s: %s\n", - sigfile, strerror(errno)); - goto out; + if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) { + xbps_error_printf("failed to verify signature: " + "EVP_PKEY_CTX_set_signature_md: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + r = EVP_PKEY_verify(ctx, sig, siglen, md, mdlen); + if (r < 0) { + xbps_error_printf( + "failed to verify signature: EVP_PKEY_verify: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; } - /* - * Verify fname RSA signature. - */ - if (rsa_verify_hash(repo, pubkey, sig_buf, sigfilelen, digest)) - val = true; - -out: - if (hexfp) - free(hexfp); - if (rkeyfile) - free(rkeyfile); - if (sig_buf) - (void)munmap(sig_buf, sigbuflen); - if (repokeyd) - xbps_object_release(repokeyd); - - return val; + + EVP_PKEY_CTX_free(ctx); + return r; +err: + EVP_PKEY_CTX_free(ctx); + return -EINVAL; } -bool -xbps_verify_file_signature(struct xbps_repo *repo, const char *fname) +int +pubkey_verify(const struct pubkey *pubkey, const unsigned char *sig, + unsigned int siglen, const unsigned char *md, size_t mdlen) { - char sig[PATH_MAX]; - unsigned char digest[XBPS_SHA256_DIGEST_SIZE]; - bool val = false; - - if (!xbps_file_sha256_raw(digest, sizeof digest, fname)) { - xbps_dbg_printf("can't open file %s: %s\n", fname, strerror(errno)); - return false; + switch (pubkey->type) { + case PUBKEY_RSA: + return pubkey_rsa_verify( + (const struct pubkey_rsa *)pubkey, sig, siglen, md, mdlen); } - - snprintf(sig, sizeof sig, "%s.sig2", fname); - val = xbps_verify_signature(repo, sig, digest); - - return val; + abort(); }