|
34 | 34 | #include <stdlib.h> |
35 | 35 | #include <string.h> |
36 | 36 |
|
| 37 | +#include <openssl/sha.h> |
| 38 | + |
37 | 39 | #include <archive.h> |
38 | 40 | #include <archive_entry.h> |
39 | 41 |
|
@@ -164,6 +166,99 @@ repo_read_index(struct xbps_repo *repo, struct archive *ar) |
164 | 166 | return 0; |
165 | 167 | } |
166 | 168 |
|
| 169 | +static int |
| 170 | +repo_load_key(struct xbps_repo *repo) |
| 171 | +{ |
| 172 | + char keypath[PATH_MAX]; |
| 173 | + xbps_dictionary_t trusted_keyd; |
| 174 | + xbps_data_t pubkey, trusted_pubkey; |
| 175 | + uint16_t pubkey_size, trusted_pubkey_size; |
| 176 | + char *hexfp; |
| 177 | + int r; |
| 178 | + |
| 179 | + if (!repo->idxmeta) |
| 180 | + return 0; |
| 181 | + |
| 182 | + pubkey = xbps_dictionary_get(repo->idxmeta, "public-key"); |
| 183 | + if (xbps_object_type(pubkey) != XBPS_TYPE_DATA) |
| 184 | + return 0; |
| 185 | + |
| 186 | + if (!xbps_dictionary_get_uint16( |
| 187 | + repo->idxmeta, "public-key-size", &pubkey_size)) { |
| 188 | + xbps_error_printf( |
| 189 | + "%s: failed to load public key: missing public key size\n", |
| 190 | + repo->uri); |
| 191 | + return -EINVAL; |
| 192 | + } |
| 193 | + |
| 194 | + hexfp = xbps_pubkey2fp(pubkey); |
| 195 | + if (!hexfp) { |
| 196 | + r = errno ? -errno : -EINVAL; |
| 197 | + xbps_error_printf( |
| 198 | + "%s: failed to create public key fingerprint: %s\n", |
| 199 | + repo->uri, strerror(-r)); |
| 200 | + return r; |
| 201 | + } |
| 202 | + |
| 203 | + repo->is_signed = true; |
| 204 | + |
| 205 | + r = snprintf(keypath, sizeof(keypath), "%s/keys/%s.plist", |
| 206 | + repo->xhp->metadir, hexfp); |
| 207 | + if (r < 0 || (size_t)r >= sizeof(keypath)) { |
| 208 | + r = -ENAMETOOLONG; |
| 209 | + xbps_error_printf("%s: failed get public key path: %s\n", |
| 210 | + repo->uri, strerror(-r)); |
| 211 | + return r; |
| 212 | + } |
| 213 | + |
| 214 | + trusted_keyd = xbps_plist_dictionary_from_file(keypath); |
| 215 | + if (!trusted_keyd) { |
| 216 | + if (errno == ENOENT) |
| 217 | + return 0; |
| 218 | + r = errno ? -errno : -EINVAL; |
| 219 | + xbps_error_printf("failed to read public key: %s: %s\n", |
| 220 | + keypath, strerror(-r)); |
| 221 | + return r; |
| 222 | + } |
| 223 | + |
| 224 | + trusted_pubkey = xbps_dictionary_get(trusted_keyd, "public-key"); |
| 225 | + if (xbps_object_type(trusted_pubkey) != XBPS_TYPE_DATA) { |
| 226 | + r = -EINVAL; |
| 227 | + xbps_error_printf( |
| 228 | + "failed to read public key: %s: missing public-key\n", |
| 229 | + keypath); |
| 230 | + return r; |
| 231 | + } |
| 232 | + if (!xbps_dictionary_get_uint16( |
| 233 | + trusted_keyd, "public-key-size", &trusted_pubkey_size)) { |
| 234 | + r = -EINVAL; |
| 235 | + xbps_error_printf( |
| 236 | + "failed to read public key: %s: missing public-key-size\n", |
| 237 | + keypath); |
| 238 | + xbps_object_release(trusted_keyd); |
| 239 | + return r; |
| 240 | + } |
| 241 | + |
| 242 | + if (pubkey_size != trusted_pubkey_size || |
| 243 | + !xbps_data_equals(pubkey, trusted_pubkey)) { |
| 244 | + r = -ERANGE; |
| 245 | + xbps_error_printf("%s: repository public-key does not " |
| 246 | + "match trusted public key: %s\n", |
| 247 | + repo->uri, keypath); |
| 248 | + xbps_object_release(trusted_keyd); |
| 249 | + return r; |
| 250 | + } |
| 251 | + |
| 252 | + repo->pubkey = pubkey_load_rsa(trusted_pubkey); |
| 253 | + if (!repo->pubkey) { |
| 254 | + xbps_object_release(trusted_keyd); |
| 255 | + return -errno; |
| 256 | + } |
| 257 | + |
| 258 | + xbps_object_release(trusted_keyd); |
| 259 | + return 0; |
| 260 | +} |
| 261 | + |
167 | 262 | static int |
168 | 263 | repo_read_meta(struct xbps_repo *repo, struct archive *ar) |
169 | 264 | { |
@@ -224,7 +319,6 @@ repo_read_meta(struct xbps_repo *repo, struct archive *ar) |
224 | 319 | return r; |
225 | 320 | } |
226 | 321 |
|
227 | | - repo->is_signed = true; |
228 | 322 | xbps_dictionary_make_immutable(repo->idxmeta); |
229 | 323 | return 0; |
230 | 324 | } |
@@ -511,6 +605,13 @@ xbps_repo_open(struct xbps_handle *xhp, const char *url) |
511 | 605 | return NULL; |
512 | 606 | } |
513 | 607 |
|
| 608 | + r = repo_load_key(repo); |
| 609 | + if (r < 0) { |
| 610 | + xbps_repo_release(repo); |
| 611 | + errno = -r; |
| 612 | + return NULL; |
| 613 | + } |
| 614 | + |
514 | 615 | if (xbps_dictionary_count(repo->stage) == 0 || |
515 | 616 | (repo->is_remote && !(xhp->flags & XBPS_FLAG_USE_STAGE))) { |
516 | 617 | repo->idx = repo->index; |
@@ -550,6 +651,10 @@ xbps_repo_release(struct xbps_repo *repo) |
550 | 651 | xbps_object_release(repo->stage); |
551 | 652 | repo->idx = NULL; |
552 | 653 | } |
| 654 | + if (repo->pubkey) { |
| 655 | + pubkey_free(repo->pubkey); |
| 656 | + repo->pubkey = NULL; |
| 657 | + } |
553 | 658 | if (repo->idxmeta) { |
554 | 659 | xbps_object_release(repo->idxmeta); |
555 | 660 | repo->idxmeta = NULL; |
@@ -861,3 +966,90 @@ xbps_repo_key_import(struct xbps_repo *repo) |
861 | 966 | free(rkeyfile); |
862 | 967 | return rv; |
863 | 968 | } |
| 969 | + |
| 970 | +static int |
| 971 | +read_signature(const char *path, unsigned char *buf, size_t bufsz) |
| 972 | +{ |
| 973 | + size_t used = 0; |
| 974 | + int fd; |
| 975 | + int r; |
| 976 | + |
| 977 | + fd = open(path, O_RDONLY); |
| 978 | + if (fd == -1) { |
| 979 | + r = -errno; |
| 980 | + xbps_error_printf("failed to read signature file: could not " |
| 981 | + "open: %s: %s\n", |
| 982 | + path, strerror(-r)); |
| 983 | + return r; |
| 984 | + } |
| 985 | + |
| 986 | + for (;;) { |
| 987 | + ssize_t rd; |
| 988 | + |
| 989 | + rd = read(fd, buf + used, bufsz - used); |
| 990 | + if (rd == -1) { |
| 991 | + if (errno == EINTR || errno == EAGAIN) |
| 992 | + continue; |
| 993 | + r = -errno; |
| 994 | + xbps_error_printf("failed to read signature file: %s: " |
| 995 | + "could not read: %s\n", |
| 996 | + path, strerror(-r)); |
| 997 | + close(fd); |
| 998 | + return r; |
| 999 | + } |
| 1000 | + used += rd; |
| 1001 | + if (rd == 0 || used == bufsz) |
| 1002 | + break; |
| 1003 | + } |
| 1004 | + |
| 1005 | + close(fd); |
| 1006 | + |
| 1007 | + if (used < bufsz) { |
| 1008 | + xbps_error_printf( |
| 1009 | + "failed to read signature: file too short\n"); |
| 1010 | + return -EINVAL; |
| 1011 | + } |
| 1012 | + return 0; |
| 1013 | +} |
| 1014 | + |
| 1015 | +int HIDDEN |
| 1016 | +repo_sig_verify_digest(struct xbps_repo *repo, const char *sigfile, |
| 1017 | + const unsigned char *md, size_t mdlen) |
| 1018 | +{ |
| 1019 | + unsigned char sigbuf[512]; |
| 1020 | + int r; |
| 1021 | + |
| 1022 | + if (!repo->pubkey) { |
| 1023 | + xbps_error_printf("failed to verify file: repository has no " |
| 1024 | + "trusted public key\n"); |
| 1025 | + return -EPERM; |
| 1026 | + } |
| 1027 | + |
| 1028 | + r = read_signature(sigfile, sigbuf, sizeof(sigbuf)); |
| 1029 | + if (r < 0) |
| 1030 | + return r; |
| 1031 | + |
| 1032 | + return pubkey_verify(repo->pubkey, sigbuf, sizeof(sigbuf), md, mdlen); |
| 1033 | +} |
| 1034 | + |
| 1035 | +int HIDDEN |
| 1036 | +repo_sig_verify_file(struct xbps_repo *repo, const char *file, const char *sigfile) |
| 1037 | +{ |
| 1038 | + unsigned char md[SHA256_DIGEST_LENGTH]; |
| 1039 | + |
| 1040 | + if (!repo->pubkey) { |
| 1041 | + xbps_error_printf("failed to verify file: repository has no " |
| 1042 | + "trusted public key\n"); |
| 1043 | + return -EPERM; |
| 1044 | + } |
| 1045 | + |
| 1046 | + if (!xbps_file_sha256_raw(md, sizeof(md), file)) { |
| 1047 | + int r = -errno; |
| 1048 | + xbps_error_printf("failed to verify file: failed to " |
| 1049 | + "generate sha256 checksum: %s\n", |
| 1050 | + strerror(-r)); |
| 1051 | + return r; |
| 1052 | + } |
| 1053 | + |
| 1054 | + return repo_sig_verify_digest(repo, sigfile, md, sizeof(md)); |
| 1055 | +} |
0 commit comments