├── tals ├── ripe.tal ├── afrinic.tal ├── lacnic.tal └── apnic.tal ├── LICENSE.md ├── TODO.md ├── output-csv.c ├── output-bgpd.c ├── output-json.c ├── test-tal.c ├── test-mft.c ├── test-roa.c ├── log.c ├── crl.c ├── output-bird.c ├── test-cert.c ├── rsync.c ├── Makefile ├── output.c ├── io.c ├── as.c ├── cms.c ├── rpki-client.8 ├── validate.c ├── tal.c ├── x509.c ├── ip.c ├── extern.h ├── roa.c ├── tests.c ├── README.md ├── mft.c └── cert.c /tals/ripe.tal: -------------------------------------------------------------------------------- 1 | rsync://rpki.ripe.net/ta/ripe-ncc-ta.cer 2 | 3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0URYSGqUz2myBsOzeW1j 4 | Q6NsxNvlLMyhWknvnl8NiBCs/T/S2XuNKQNZ+wBZxIgPPV2pFBFeQAvoH/WK83Hw 5 | A26V2siwm/MY2nKZ+Olw+wlpzlZ1p3Ipj2eNcKrmit8BwBC8xImzuCGaV0jkRB0G 6 | Z0hoH6Ml03umLprRsn6v0xOP0+l6Qc1ZHMFVFb385IQ7FQQTcVIxrdeMsoyJq9eM 7 | kE6DoclHhF/NlSllXubASQ9KUWqJ0+Ot3QCXr4LXECMfkpkVR2TZT+v5v658bHVs 8 | 6ZxRD1b6Uk1uQKAyHUbn/tXvP8lrjAibGzVsXDT2L0x4Edx+QdixPgOji3gBMyL2 9 | VwIDAQAB 10 | -------------------------------------------------------------------------------- /tals/afrinic.tal: -------------------------------------------------------------------------------- 1 | rsync://rpki.afrinic.net/repository/AfriNIC.cer 2 | 3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsAqAhWIO+ON2Ef9oRDM 4 | pKxv+AfmSLIdLWJtjrvUyDxJPBjgR+kVrOHUeTaujygFUp49tuN5H2C1rUuQavTH 5 | vve6xNF5fU3OkTcqEzMOZy+ctkbde2SRMVdvbO22+TH9gNhKDc9l7Vu01qU4LeJH 6 | k3X0f5uu5346YrGAOSv6AaYBXVgXxa0s9ZvgqFpim50pReQe/WI3QwFKNgpPzfQL 7 | 6Y7fDPYdYaVOXPXSKtx7P4s4KLA/ZWmRL/bobw/i2fFviAGhDrjqqqum+/9w1hEl 8 | L/vqihVnV18saKTnLvkItA/Bf5i11Yhw2K7qv573YWxyuqCknO/iYLTR1DToBZcZ 9 | UQIDAQAB 10 | -------------------------------------------------------------------------------- /tals/lacnic.tal: -------------------------------------------------------------------------------- 1 | rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer 2 | 3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZEzhYK0+PtDOPfub/KR 4 | c3MeWx3neXx4/wbnJWGbNAtbYqXg3uU5J4HFzPgk/VIppgSKAhlO0H60DRP48by9 5 | gr5/yDHu2KXhOmnMg46sYsUIpfgtBS9+VtrqWziJfb+pkGtuOWeTnj6zBmBNZKK+ 6 | 5AlMCW1WPhrylIcB+XSZx8tk9GS/3SMQ+YfMVwwAyYjsex14Uzto4GjONALE5oh1 7 | M3+glRQduD6vzSwOD+WahMbc9vCOTED+2McLHRKgNaQf0YJ9a1jG9oJIvDkKXEqd 8 | fqDRktwyoD74cV57bW3tBAexB7GglITbInyQAsmdngtfg2LUMrcROHHP86QPZINj 9 | DQIDAQAB 10 | -------------------------------------------------------------------------------- /tals/apnic.tal: -------------------------------------------------------------------------------- 1 | rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer 2 | 3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8 4 | qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI 5 | 7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr 6 | LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf 7 | i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H 8 | cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou 9 | 0wIDAQAB 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Kristaps Dzonsons 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | The following are unclear to me. 3 | 4 | - Following up on validating AS numbers for certificates. The 5 | specification is not clear on what happens with empty AS extensions in 6 | a chain of certificates. Do we consider that inheritence? If so, 7 | what's the point of having an inheritence clause? 8 | 9 | - I get that ASid 0 has special meaning for ROAs (see RFC 6483 sec 4), 10 | but it doesn't make sense that some top-level certificates (e.g., 11 | Afrinic) have a range inclusive of zero, since it's reserved. In this 12 | system, I let the range through but don't let a specific ASid of 0 in 13 | certificates---only ROAs. 14 | 15 | - VRP duplication. When run as-is, there are duplicate VRPs and 16 | that doesn't seem right. It happens when two ROAs have their validity 17 | period overlap. I need to see if there's a more programmatic way to 18 | check before commiting the routes to output. 19 | 20 | - (Not a particular helpful security measure, but...) The validators 21 | should all be run in their own process: the syntax parser should not 22 | be performing the route validation. This is a mechanical step, as all 23 | the logic to do so is in place. 24 | 25 | - (**Important**.) Stipulating `X509_V_FLAG_IGNORE_CRITICAL` might be 26 | dangerous. Which extensions are being ignored should be 27 | double-checked. 28 | -------------------------------------------------------------------------------- /output-csv.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD$ */ 2 | /* 3 | * Copyright (c) 2019 Claudio Jeker 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "extern.h" 24 | 25 | int 26 | output_csv(FILE *out, struct vrp_tree *vrps, void *arg) 27 | { 28 | char buf[64]; 29 | struct vrp *v; 30 | 31 | if (fprintf(out, "ASN,IP Prefix,Max Length,Trust Anchor\n") < 0) 32 | return -1; 33 | 34 | RB_FOREACH(v, vrp_tree, vrps) { 35 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 36 | if (fprintf(out, "AS%u,%s,%u,%s\n", v->asid, buf, v->maxlength, 37 | v->tal) < 0) 38 | return -1; 39 | } 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /output-bgpd.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "extern.h" 24 | 25 | int 26 | output_bgpd(FILE *out, struct vrp_tree *vrps, void *arg) 27 | { 28 | char buf1[64], buf2[32]; 29 | struct vrp *v; 30 | 31 | if (fprintf(out, "roa-set {\n") < 0) 32 | return -1; 33 | 34 | RB_FOREACH(v, vrp_tree, vrps) { 35 | ip_addr_print(&v->addr, v->afi, buf1, sizeof(buf1)); 36 | if (v->maxlength > v->addr.prefixlen) 37 | snprintf(buf2, sizeof(buf2), "maxlen %u ", 38 | v->maxlength); 39 | else 40 | buf2[0] = '\0'; 41 | if (fprintf(out, "\t%s %ssource-as %u\n", buf1, buf2, v->asid) < 0) 42 | return -1; 43 | } 44 | 45 | if (fprintf(out, "}\n") < 0) 46 | return -1; 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /output-json.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD$ */ 2 | /* 3 | * Copyright (c) 2019 Claudio Jeker 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "extern.h" 24 | 25 | int 26 | output_json(FILE *out, struct vrp_tree *vrps, void *arg) 27 | { 28 | char buf[64]; 29 | struct vrp *v; 30 | int first = 1; 31 | 32 | if (fprintf(out, "{\n\t\"roas\": [\n") < 0) 33 | return -1; 34 | 35 | RB_FOREACH(v, vrp_tree, vrps) { 36 | if (first) 37 | first = 0; 38 | else { 39 | if (fprintf(out, ",\n") < 0) 40 | return -1; 41 | } 42 | 43 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 44 | 45 | if (fprintf(out, "\t\t{ \"asn\": \"AS%u\", \"prefix\": \"%s\", " 46 | "\"maxLength\": %u, \"ta\": \"%s\" }", 47 | v->asid, buf, v->maxlength, v->tal) < 0) 48 | return -1; 49 | } 50 | 51 | if (fprintf(out, "\n\t]\n}\n") < 0) 52 | return -1; 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /test-tal.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "extern.h" 31 | 32 | static void 33 | tal_print(const struct tal *p) 34 | { 35 | size_t i; 36 | 37 | assert(p != NULL); 38 | 39 | for (i = 0; i < p->urisz; i++) 40 | printf("%5zu: URI: %s\n", i + 1, p->uri[i]); 41 | } 42 | 43 | int 44 | main(int argc, char *argv[]) 45 | { 46 | int c; 47 | struct tal *tal; 48 | size_t i; 49 | char *f; 50 | 51 | SSL_library_init(); 52 | SSL_load_error_strings(); 53 | 54 | while (-1 != (c = getopt(argc, argv, "v"))) 55 | switch (c) { 56 | case 'v': 57 | verbose++; 58 | break; 59 | default: 60 | return EXIT_FAILURE; 61 | } 62 | 63 | argv += optind; 64 | argc -= optind; 65 | 66 | for (i = 0; i < (size_t)argc; i++) { 67 | f = tal_read_file(argv[i]); 68 | assert(f != NULL); 69 | tal = tal_parse(argv[i], f); 70 | free(f); 71 | if (tal == NULL) 72 | break; 73 | if (verbose) 74 | tal_print(tal); 75 | tal_free(tal); 76 | } 77 | 78 | EVP_cleanup(); 79 | CRYPTO_cleanup_all_ex_data(); 80 | ERR_free_strings(); 81 | return i < (size_t)argc ? EXIT_FAILURE : EXIT_SUCCESS; 82 | } 83 | -------------------------------------------------------------------------------- /test-mft.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "extern.h" 31 | 32 | static void 33 | mft_print(const struct mft *p) 34 | { 35 | size_t i; 36 | 37 | assert(p != NULL); 38 | 39 | printf("Subject key identifier: %s\n", p->ski); 40 | printf("Authority key identifier: %s\n", p->aki); 41 | for (i = 0; i < p->filesz; i++) 42 | printf("%5zu: %s\n", i + 1, p->files[i].file); 43 | } 44 | 45 | 46 | int 47 | main(int argc, char *argv[]) 48 | { 49 | int c, force = 0; 50 | size_t i; 51 | struct mft *p; 52 | X509 *xp = NULL; 53 | 54 | SSL_library_init(); 55 | SSL_load_error_strings(); 56 | 57 | while (-1 != (c = getopt(argc, argv, "fv"))) 58 | switch (c) { 59 | case 'f': 60 | force = 1; 61 | break; 62 | case 'v': 63 | verbose++; 64 | break; 65 | default: 66 | return EXIT_FAILURE; 67 | } 68 | 69 | argv += optind; 70 | argc -= optind; 71 | 72 | for (i = 0; i < (size_t)argc; i++) { 73 | if ((p = mft_parse(&xp, argv[i], force)) == NULL) 74 | break; 75 | if (verbose) 76 | mft_print(p); 77 | mft_free(p); 78 | X509_free(xp); 79 | } 80 | 81 | EVP_cleanup(); 82 | CRYPTO_cleanup_all_ex_data(); 83 | ERR_free_strings(); 84 | return i < (size_t)argc ? EXIT_FAILURE : EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /test-roa.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "extern.h" 31 | 32 | static void 33 | roa_print(const struct roa *p) 34 | { 35 | char buf[128]; 36 | size_t i; 37 | 38 | assert(p != NULL); 39 | 40 | printf("Subject key identifier: %s\n", p->ski); 41 | printf("Authority key identifier: %s\n", p->aki); 42 | printf("asID: %" PRIu32 "\n", p->asid); 43 | for (i = 0; i < p->ipsz; i++) { 44 | ip_addr_print(&p->ips[i].addr, 45 | p->ips[i].afi, buf, sizeof(buf)); 46 | printf("%5zu: %s (max: %zu)\n", i + 1, 47 | buf, p->ips[i].maxlength); 48 | } 49 | } 50 | 51 | int 52 | main(int argc, char *argv[]) 53 | { 54 | int c; 55 | size_t i; 56 | X509 *xp = NULL; 57 | struct roa *p; 58 | 59 | SSL_library_init(); 60 | SSL_load_error_strings(); 61 | 62 | while ((c = getopt(argc, argv, "v")) != -1) 63 | switch (c) { 64 | case 'v': 65 | verbose++; 66 | break; 67 | default: 68 | return EXIT_FAILURE; 69 | } 70 | 71 | argv += optind; 72 | argc -= optind; 73 | 74 | for (i = 0; i < (size_t)argc; i++) { 75 | if ((p = roa_parse(&xp, argv[i], NULL)) == NULL) 76 | break; 77 | if (verbose) 78 | roa_print(p); 79 | roa_free(p); 80 | X509_free(xp); 81 | } 82 | 83 | EVP_cleanup(); 84 | CRYPTO_cleanup_all_ex_data(); 85 | ERR_free_strings(); 86 | return i < (size_t)argc ? EXIT_FAILURE : EXIT_SUCCESS; 87 | } 88 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "extern.h" 28 | 29 | /* 30 | * 0 for no output but results, 1 for status, 2 for detailed debugging 31 | * messages during operation. 32 | */ 33 | int verbose; 34 | 35 | /* 36 | * Log a message to stderr if and only if "verbose" is non-zero. 37 | * This uses the err(3) functionality. 38 | */ 39 | void 40 | logx(const char *fmt, ...) 41 | { 42 | va_list ap; 43 | 44 | if (verbose && fmt != NULL) { 45 | va_start(ap, fmt); 46 | vwarnx(fmt, ap); 47 | va_end(ap); 48 | } 49 | } 50 | 51 | /* 52 | * Print the chain of openssl errors that led to the current one. 53 | * This should only be invoked in the event that OpenSSL fails with 54 | * something. 55 | * It's followed by the (optional) given error message, then terminates. 56 | */ 57 | void 58 | cryptoerrx(const char *fmt, ...) 59 | { 60 | unsigned long er; 61 | char buf[BUFSIZ]; 62 | va_list ap; 63 | 64 | while ((er = ERR_get_error()) > 0) { 65 | ERR_error_string_n(er, buf, sizeof(buf)); 66 | warnx(" ...trace: %s", buf); 67 | } 68 | 69 | if (fmt != NULL) { 70 | va_start(ap, fmt); 71 | vwarnx(fmt, ap); 72 | va_end(ap); 73 | } 74 | 75 | exit(1); 76 | } 77 | 78 | /* 79 | * Like cryptoerrx(), but without exiting. 80 | */ 81 | void 82 | cryptowarnx(const char *fmt, ...) 83 | { 84 | unsigned long er; 85 | char buf[BUFSIZ]; 86 | va_list ap; 87 | 88 | while ((er = ERR_get_error()) > 0) { 89 | ERR_error_string_n(er, buf, sizeof(buf)); 90 | warnx(" ...trace: %s", buf); 91 | } 92 | 93 | if (fmt != NULL) { 94 | va_start(ap, fmt); 95 | vwarnx(fmt, ap); 96 | va_end(ap); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crl.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "extern.h" 33 | 34 | X509_CRL * 35 | crl_parse(const char *fn, const unsigned char *dgst) 36 | { 37 | int rc = 0, sz; 38 | X509_CRL *x = NULL; 39 | BIO *bio = NULL, *shamd; 40 | FILE *f; 41 | EVP_MD *md; 42 | char mdbuf[EVP_MAX_MD_SIZE]; 43 | 44 | if ((f = fopen(fn, "rb")) == NULL) { 45 | warn("%s", fn); 46 | return NULL; 47 | } 48 | 49 | if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) { 50 | if (verbose > 0) 51 | cryptowarnx("%s: BIO_new_file", fn); 52 | return NULL; 53 | } 54 | 55 | /* 56 | * If we have a digest specified, create an MD chain that will 57 | * automatically compute a digest during the X509 creation. 58 | */ 59 | 60 | if (dgst != NULL) { 61 | if ((shamd = BIO_new(BIO_f_md())) == NULL) 62 | cryptoerrx("BIO_new"); 63 | if (!BIO_set_md(shamd, EVP_sha256())) 64 | cryptoerrx("BIO_set_md"); 65 | if ((bio = BIO_push(shamd, bio)) == NULL) 66 | cryptoerrx("BIO_push"); 67 | } 68 | 69 | if ((x = d2i_X509_CRL_bio(bio, NULL)) == NULL) { 70 | cryptowarnx("%s: d2i_X509_CRL_bio", fn); 71 | goto out; 72 | } 73 | 74 | /* 75 | * If we have a digest, find it in the chain (we'll already have 76 | * made it, so assert otherwise) and verify it. 77 | */ 78 | 79 | if (dgst != NULL) { 80 | shamd = BIO_find_type(bio, BIO_TYPE_MD); 81 | assert(shamd != NULL); 82 | 83 | if (!BIO_get_md(shamd, &md)) 84 | cryptoerrx("BIO_get_md"); 85 | assert(EVP_MD_type(md) == NID_sha256); 86 | 87 | if ((sz = BIO_gets(shamd, mdbuf, EVP_MAX_MD_SIZE)) < 0) 88 | cryptoerrx("BIO_gets"); 89 | assert(sz == SHA256_DIGEST_LENGTH); 90 | 91 | if (memcmp(mdbuf, dgst, SHA256_DIGEST_LENGTH)) { 92 | if (verbose > 0) 93 | warnx("%s: bad message digest", fn); 94 | goto out; 95 | } 96 | } 97 | 98 | rc = 1; 99 | out: 100 | BIO_free_all(bio); 101 | if (rc == 0) { 102 | X509_CRL_free(x); 103 | x = NULL; 104 | } 105 | return x; 106 | } 107 | 108 | static inline int 109 | crlcmp(struct crl *a, struct crl *b) 110 | { 111 | return strcmp(a->aki, b->aki); 112 | } 113 | 114 | RB_GENERATE(crl_tree, crl, entry, crlcmp); 115 | 116 | void 117 | free_crl(struct crl *crl) 118 | { 119 | free(crl->aki); 120 | X509_CRL_free(crl->x509_crl); 121 | free(crl); 122 | } 123 | -------------------------------------------------------------------------------- /output-bird.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD$ */ 2 | /* 3 | * Copyright (c) 2019 Claudio Jeker 4 | * Copyright (c) 2020 Robert Scheck 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include "config.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "extern.h" 25 | 26 | int 27 | output_bird1v4(FILE *out, struct vrp_tree *vrps, void *arg) 28 | { 29 | const char *bird_tablename = arg; 30 | char buf[64]; 31 | struct vrp *v; 32 | 33 | if (fprintf(out, "roa table %s {\n", bird_tablename) < 0) 34 | return -1; 35 | 36 | RB_FOREACH(v, vrp_tree, vrps) { 37 | if (v->afi == AFI_IPV4) { 38 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 39 | if (fprintf(out, "\troa %s max %u as %u;\n", buf, 40 | v->maxlength, v->asid) < 0) 41 | return -1; 42 | } 43 | } 44 | 45 | if (fprintf(out, "}\n") < 0) 46 | return -1; 47 | return 0; 48 | } 49 | 50 | int 51 | output_bird1v6(FILE *out, struct vrp_tree *vrps, void *arg) 52 | { 53 | const char *bird_tablename = arg; 54 | char buf[64]; 55 | struct vrp *v; 56 | 57 | if (fprintf(out, "roa table %s {\n", bird_tablename) < 0) 58 | return -1; 59 | 60 | RB_FOREACH(v, vrp_tree, vrps) { 61 | if (v->afi == AFI_IPV6) { 62 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 63 | if (fprintf(out, "\troa %s max %u as %u;\n", buf, 64 | v->maxlength, v->asid) < 0) 65 | return -1; 66 | } 67 | } 68 | 69 | if (fprintf(out, "}\n") < 0) 70 | return -1; 71 | return 0; 72 | } 73 | 74 | int 75 | output_bird2(FILE *out, struct vrp_tree *vrps, void *arg) 76 | { 77 | const char *bird_tablename = arg; 78 | char buf[64]; 79 | struct vrp *v; 80 | time_t now = time(NULL); 81 | 82 | if (fprintf(out, "define force_roa_table_update = %lld;\n\n" 83 | "roa4 table %s4;\nroa6 table %s6;\n\n" 84 | "protocol static {\n\troa4 { table %s4; };\n\n", 85 | (long long) now, bird_tablename, bird_tablename, 86 | bird_tablename) < 0) 87 | return -1; 88 | 89 | RB_FOREACH(v, vrp_tree, vrps) { 90 | if (v->afi == AFI_IPV4) { 91 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 92 | if (fprintf(out, "\troute %s max %u as %u;\n", buf, 93 | v->maxlength, v->asid) < 0) 94 | return -1; 95 | } 96 | } 97 | 98 | if (fprintf(out, "}\n\nprotocol static {\n\troa6 { table %s6; };\n\n", 99 | bird_tablename) < 0) 100 | return -1; 101 | 102 | RB_FOREACH(v, vrp_tree, vrps) { 103 | if (v->afi == AFI_IPV6) { 104 | ip_addr_print(&v->addr, v->afi, buf, sizeof(buf)); 105 | if (fprintf(out, "\troute %s max %u as %u;\n", buf, 106 | v->maxlength, v->asid) < 0) 107 | return -1; 108 | } 109 | } 110 | 111 | if (fprintf(out, "}\n") < 0) 112 | return -1; 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /test-cert.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #include "extern.h" 34 | 35 | static void 36 | cert_print(const struct cert *p) 37 | { 38 | size_t i; 39 | char buf1[64], buf2[64]; 40 | int sockt; 41 | 42 | assert(p != NULL); 43 | 44 | printf("Manifest: %s\n", p->mft); 45 | if (p->crl != NULL) 46 | printf("Revocation list: %s\n", p->crl); 47 | printf("Subject key identifier: %s\n", p->ski); 48 | if (p->aki != NULL) 49 | printf("Authority key identifier: %s\n", p->aki); 50 | 51 | for (i = 0; i < p->asz; i++) 52 | switch (p->as[i].type) { 53 | case CERT_AS_ID: 54 | printf("%5zu: AS: %" 55 | PRIu32 "\n", i + 1, p->as[i].id); 56 | break; 57 | case CERT_AS_INHERIT: 58 | printf("%5zu: AS: inherit\n", i + 1); 59 | break; 60 | case CERT_AS_RANGE: 61 | printf("%5zu: AS: %" 62 | PRIu32 "--%" PRIu32 "\n", i + 1, 63 | p->as[i].range.min, p->as[i].range.max); 64 | break; 65 | } 66 | 67 | for (i = 0; i < p->ipsz; i++) 68 | switch (p->ips[i].type) { 69 | case CERT_IP_INHERIT: 70 | printf("%5zu: IP: inherit\n", i + 1); 71 | break; 72 | case CERT_IP_ADDR: 73 | ip_addr_print(&p->ips[i].ip, 74 | p->ips[i].afi, buf1, sizeof(buf1)); 75 | printf("%5zu: IP: %s\n", i + 1, buf1); 76 | break; 77 | case CERT_IP_RANGE: 78 | sockt = (p->ips[i].afi == AFI_IPV4) ? 79 | AF_INET : AF_INET6; 80 | inet_ntop(sockt, p->ips[i].min, buf1, sizeof(buf1)); 81 | inet_ntop(sockt, p->ips[i].max, buf2, sizeof(buf2)); 82 | printf("%5zu: IP: %s--%s\n", i + 1, buf1, buf2); 83 | break; 84 | } 85 | } 86 | 87 | int 88 | main(int argc, char *argv[]) 89 | { 90 | int c, ta = 0; 91 | size_t i; 92 | X509 *xp = NULL; 93 | struct cert *p; 94 | 95 | SSL_library_init(); 96 | SSL_load_error_strings(); 97 | 98 | while ((c = getopt(argc, argv, "tv")) != -1) 99 | switch (c) { 100 | case 't': 101 | ta = 1; 102 | break; 103 | case 'v': 104 | verbose++; 105 | break; 106 | default: 107 | return EXIT_FAILURE; 108 | } 109 | 110 | argv += optind; 111 | argc -= optind; 112 | 113 | for (i = 0; i < (size_t)argc; i++) { 114 | p = ta ? 115 | ta_parse(&xp, argv[i], NULL, 0) : 116 | cert_parse(&xp, argv[i], NULL); 117 | if (p == NULL) 118 | break; 119 | if (verbose) 120 | cert_print(p); 121 | cert_free(p); 122 | X509_free(xp); 123 | } 124 | 125 | EVP_cleanup(); 126 | CRYPTO_cleanup_all_ex_data(); 127 | ERR_free_strings(); 128 | return i < (size_t)argc ? EXIT_FAILURE : EXIT_SUCCESS; 129 | } 130 | -------------------------------------------------------------------------------- /rsync.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "extern.h" 30 | 31 | /* 32 | * Conforms to RFC 5781. 33 | * Note that "Source" is broken down into the module, path, and also 34 | * file type relevant to RPKI. 35 | * Any of the pointers (except "uri") may be NULL. 36 | * Returns zero on failure, non-zero on success. 37 | */ 38 | int 39 | rsync_uri_parse(const char **hostp, size_t *hostsz, 40 | const char **modulep, size_t *modulesz, 41 | const char **pathp, size_t *pathsz, 42 | enum rtype *rtypep, const char *uri) 43 | { 44 | const char *host, *module, *path; 45 | size_t sz; 46 | 47 | /* Initialise all output values to NULL or 0. */ 48 | 49 | if (hostsz != NULL) 50 | *hostsz = 0; 51 | if (modulesz != NULL) 52 | *modulesz = 0; 53 | if (pathsz != NULL) 54 | *pathsz = 0; 55 | if (hostp != NULL) 56 | *hostp = 0; 57 | if (modulep != NULL) 58 | *modulep = 0; 59 | if (pathp != NULL) 60 | *pathp = 0; 61 | if (rtypep != NULL) 62 | *rtypep = RTYPE_EOF; 63 | 64 | /* Case-insensitive rsync URI. */ 65 | 66 | if (strncasecmp(uri, "rsync://", 8)) { 67 | warnx("%s: not using rsync schema", uri); 68 | return 0; 69 | } 70 | 71 | /* Parse the non-zero-length hostname. */ 72 | 73 | host = uri + 8; 74 | 75 | if ((module = strchr(host, '/')) == NULL) { 76 | warnx("%s: missing rsync module", uri); 77 | return 0; 78 | } else if (module == host) { 79 | warnx("%s: zero-length rsync host", uri); 80 | return 0; 81 | } 82 | 83 | if (hostp != NULL) 84 | *hostp = host; 85 | if (hostsz != NULL) 86 | *hostsz = module - host; 87 | 88 | /* The non-zero-length module follows the hostname. */ 89 | 90 | if (module[1] == '\0') { 91 | warnx("%s: zero-length rsync module", uri); 92 | return 0; 93 | } 94 | 95 | module++; 96 | 97 | /* The path component is optional. */ 98 | 99 | if ((path = strchr(module, '/')) == NULL) { 100 | assert(*module != '\0'); 101 | if (modulep != NULL) 102 | *modulep = module; 103 | if (modulesz != NULL) 104 | *modulesz = strlen(module); 105 | return 1; 106 | } else if (path == module) { 107 | warnx("%s: zero-length module", uri); 108 | return 0; 109 | } 110 | 111 | if (modulep != NULL) 112 | *modulep = module; 113 | if (modulesz != NULL) 114 | *modulesz = path - module; 115 | 116 | path++; 117 | sz = strlen(path); 118 | 119 | if (pathp != NULL) 120 | *pathp = path; 121 | if (pathsz != NULL) 122 | *pathsz = sz; 123 | 124 | if (rtypep != NULL && sz > 4) { 125 | if (strcasecmp(path + sz - 4, ".roa") == 0) 126 | *rtypep = RTYPE_ROA; 127 | else if (strcasecmp(path + sz - 4, ".mft") == 0) 128 | *rtypep = RTYPE_MFT; 129 | else if (strcasecmp(path + sz - 4, ".cer") == 0) 130 | *rtypep = RTYPE_CER; 131 | else if (strcasecmp(path + sz - 4, ".crl") == 0) 132 | *rtypep = RTYPE_CRL; 133 | } 134 | 135 | return 1; 136 | } 137 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.configure 2 | 3 | # If set to zero, privilege-dropping is disabled and RPKI_PRIVDROP_USER 4 | # is not used. Otherwise, privileges are dropped. 5 | 6 | RPKI_PRIVDROP = 1 7 | RPKI_PRIVDROP_USER = "_rpki-client" 8 | 9 | # Command to invoke for rsync. Must be in user's path. 10 | 11 | RPKI_RSYNC_COMMAND = "openrsync" 12 | 13 | # Where to place output files. 14 | 15 | RPKI_PATH_OUT_DIR = "/var/db/rpki-client" 16 | 17 | # Where repositories are stored. 18 | 19 | RPKI_PATH_BASE_DIR = "/var/cache/rpki-client" 20 | 21 | # Where TAL files are found. 22 | 23 | RPKI_TAL_DIR = "/etc/rpki" 24 | 25 | # OpenBSD uses the "eopenssl11" pkg-config package. 26 | # Most other operating systems use just "openssl". 27 | 28 | PKGNAME_OPENSSL != if [ "`uname -s`" = "OpenBSD" ]; then echo "eopenssl11"; else echo "openssl" ; fi 29 | PKG_OPENSSL ?= $(PKGNAME_OPENSSL) 30 | 31 | # You don't want to change anything after this. 32 | 33 | OBJS = as.o \ 34 | cert.o \ 35 | cms.o \ 36 | compats.o \ 37 | crl.o \ 38 | io.o \ 39 | ip.o \ 40 | log.o \ 41 | mft.o \ 42 | output.o \ 43 | output-bird.o \ 44 | output-bgpd.o \ 45 | output-csv.o \ 46 | output-json.o \ 47 | roa.o \ 48 | rsync.o \ 49 | tal.o \ 50 | validate.o \ 51 | x509.o 52 | ALLOBJS = $(OBJS) \ 53 | main.o \ 54 | test-cert.o \ 55 | test-mft.o \ 56 | test-roa.o \ 57 | test-tal.o 58 | BINS = rpki-client \ 59 | test-cert \ 60 | test-mft \ 61 | test-roa \ 62 | test-tal 63 | CFLAGS_OPENSSL != pkg-config --cflags $(PKG_OPENSSL) 2>/dev/null || echo "" 64 | LIBS_OPENSSL != pkg-config --libs $(PKG_OPENSSL) 2>/dev/null || echo "-lssl -lcrypto" 65 | CFLAGS += $(CFLAGS_OPENSSL) 66 | LDADD += $(LIBS_OPENSSL) 67 | 68 | # Linux needs its -lresolv for b64_* functions. 69 | # IllumOS needs its socket library. 70 | 71 | LDADD += $(LDADD_B64_NTOP) $(LDADD_LIB_SOCKET) 72 | 73 | all: $(BINS) rpki-client.install.8 74 | 75 | site.h: Makefile 76 | (echo "#define RPKI_RSYNC_COMMAND \"${RPKI_RSYNC_COMMAND}\"" ; \ 77 | echo "#define RPKI_PATH_OUT_DIR \"${RPKI_PATH_OUT_DIR}\"" ; \ 78 | echo "#define RPKI_PATH_BASE_DIR \"${RPKI_PATH_BASE_DIR}\"" ; \ 79 | echo "#define RPKI_PRIVDROP ${RPKI_PRIVDROP}" ; \ 80 | echo "#define RPKI_PRIVDROP_USER \"${RPKI_PRIVDROP_USER}\"" ; \ 81 | echo "#define RPKI_TAL_DIR \"${RPKI_TAL_DIR}\"" ; ) >$@ 82 | 83 | site.sed: Makefile 84 | (echo "s!@RPKI_RSYNC_COMMAND@!${RPKI_RSYNC_COMMAND}!g" ; \ 85 | echo "s!@RPKI_PATH_OUT_DIR@!${RPKI_PATH_OUT_DIR}!g" ; \ 86 | echo "s!@RPKI_PATH_BASE_DIR@!${RPKI_PATH_BASE_DIR}!g" ; \ 87 | echo "s!@RPKI_PRIVDROP_USER@!${RPKI_PRIVDROP_USER}!g" ; \ 88 | echo "s!@RPKI_TAL_DIR@!${RPKI_TAL_DIR}!g" ; ) >$@ 89 | 90 | install: all 91 | mkdir -p $(DESTDIR)$(BINDIR) 92 | mkdir -p $(DESTDIR)$(MANDIR)/man8 93 | $(INSTALL_PROGRAM) rpki-client $(DESTDIR)$(BINDIR) 94 | $(INSTALL_MAN) rpki-client.install.8 $(DESTDIR)$(MANDIR)/man8/rpki-client.8 95 | 96 | uninstall: 97 | rm -f $(DESTDIR)$(BINDIR)/rpki-client 98 | rm -f $(DESTDIR)$(MANDIR)/man8/rpki-client.8 99 | 100 | rpki-client: $(OBJS) main.o 101 | $(CC) -o $@ main.o $(OBJS) $(LDFLAGS) $(LDADD) 102 | 103 | test-tal: $(OBJS) test-tal.o 104 | $(CC) -o $@ test-tal.o $(OBJS) $(LDFLAGS) $(LDADD) 105 | 106 | test-mft: $(OBJS) test-mft.o 107 | $(CC) -o $@ test-mft.o $(OBJS) $(LDFLAGS) $(LDADD) 108 | 109 | test-roa: $(OBJS) test-roa.o 110 | $(CC) -o $@ test-roa.o $(OBJS) $(LDFLAGS) $(LDADD) 111 | 112 | test-cert: $(OBJS) test-cert.o 113 | $(CC) -o $@ test-cert.o $(OBJS) $(LDFLAGS) $(LDADD) 114 | 115 | clean: 116 | rm -f $(BINS) $(ALLOBJS) rpki-client.install.8 site.sed site.h 117 | 118 | distclean: clean 119 | rm -f config.h config.log Makefile.configure 120 | 121 | distcheck: 122 | mandoc -Tlint -Werror rpki-client.8 123 | rm -rf .distcheck 124 | mkdir .distcheck 125 | cp *.c extern.h rpki-client.8 configure Makefile .distcheck 126 | ( cd .distcheck && ./configure PREFIX=prefix ) 127 | ( cd .distcheck && $(MAKE) ) 128 | ( cd .distcheck && $(MAKE) install ) 129 | rm -rf .distcheck 130 | 131 | regress: 132 | # Do nothing. 133 | 134 | $(ALLOBJS): extern.h config.h site.h 135 | 136 | rpki-client.install.8: rpki-client.8 site.sed 137 | sed -f site.sed rpki-client.8 >$@ 138 | -------------------------------------------------------------------------------- /output.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD$ */ 2 | /* 3 | * Copyright (c) 2019 Theo de Raadt 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include "extern.h" 32 | 33 | char *outputdir; 34 | int outformats; 35 | 36 | static char output_tmpname[PATH_MAX]; 37 | static char output_name[PATH_MAX]; 38 | 39 | static const struct outputs { 40 | int format; 41 | char *name; 42 | int (*fn)(FILE *, struct vrp_tree *, void *); 43 | } outputs[] = { 44 | { FORMAT_OPENBGPD, "openbgpd", output_bgpd }, 45 | { FORMAT_BIRD, "bird1v4", output_bird1v4 }, 46 | { FORMAT_BIRD, "bird1v6", output_bird1v6 }, 47 | { FORMAT_BIRD, "bird", output_bird2 }, 48 | { FORMAT_CSV, "csv", output_csv }, 49 | { FORMAT_JSON, "json", output_json }, 50 | { 0, NULL, NULL } 51 | }; 52 | 53 | static FILE *output_createtmp(char *); 54 | static void output_cleantmp(void); 55 | static int output_finish(FILE *); 56 | static void sig_handler(int); 57 | static void set_signal_handler(void); 58 | 59 | int 60 | outputfiles(struct vrp_tree *v, const char *bird_output) 61 | { 62 | int i, rc = 0; 63 | void *arg; 64 | FILE *fout; 65 | 66 | atexit(output_cleantmp); 67 | set_signal_handler(); 68 | 69 | for (i = 0; outputs[i].name; i++) { 70 | if (!(outformats & outputs[i].format)) 71 | continue; 72 | 73 | arg = (outputs[i].format & FORMAT_BIRD) ? 74 | (void *)bird_output : NULL; 75 | 76 | fout = output_createtmp(outputs[i].name); 77 | if (fout == NULL) { 78 | warn("cannot create %s", outputs[i].name); 79 | rc = 1; 80 | continue; 81 | } 82 | if ((*outputs[i].fn)(fout, v, arg) != 0) { 83 | warn("output for %s format failed", outputs[i].name); 84 | fclose(fout); 85 | output_cleantmp(); 86 | rc = 1; 87 | continue; 88 | } 89 | if (output_finish(fout) != 0) { 90 | warn("finish for %s format failed", outputs[i].name); 91 | output_cleantmp(); 92 | rc = 1; 93 | continue; 94 | } 95 | } 96 | 97 | return rc; 98 | } 99 | 100 | static FILE * 101 | output_createtmp(char *name) 102 | { 103 | FILE *f; 104 | int fd, r; 105 | 106 | r = snprintf(output_name, sizeof output_name, 107 | "%s/%s", outputdir, name); 108 | if (r < 0 || r > (int)sizeof(output_name)) 109 | err(1, "path too long"); 110 | r = snprintf(output_tmpname, sizeof output_tmpname, 111 | "%s.XXXXXXXXXXX", output_name); 112 | if (r < 0 || r > (int)sizeof(output_tmpname)) 113 | err(1, "path too long"); 114 | fd = mkostemp(output_tmpname, O_CLOEXEC); 115 | if (fd == -1) 116 | err(1, "mkostemp"); 117 | (void) fchmod(fd, 0644); 118 | f = fdopen(fd, "w"); 119 | if (f == NULL) 120 | err(1, "fdopen"); 121 | return f; 122 | } 123 | 124 | static int 125 | output_finish(FILE *out) 126 | { 127 | if (fclose(out) != 0) 128 | return -1; 129 | if (rename(output_tmpname, output_name) == -1) 130 | return -1; 131 | output_tmpname[0] = '\0'; 132 | return 0; 133 | } 134 | 135 | static void 136 | output_cleantmp(void) 137 | { 138 | if (*output_tmpname) 139 | unlink(output_tmpname); 140 | output_tmpname[0] = '\0'; 141 | } 142 | 143 | /* 144 | * Signal handler that clears the temporary files. 145 | */ 146 | static void 147 | sig_handler(int sig) 148 | { 149 | output_cleantmp(); 150 | _exit(2); 151 | } 152 | 153 | /* 154 | * Set signal handler on panic signals. 155 | */ 156 | static void 157 | set_signal_handler(void) 158 | { 159 | struct sigaction sa; 160 | int i, signals[] = {SIGTERM, SIGHUP, SIGINT, SIGUSR1, SIGUSR2, 161 | SIGPIPE, SIGXCPU, SIGXFSZ, 0}; 162 | 163 | memset(&sa, 0, sizeof(sa)); 164 | sigfillset(&sa.sa_mask); 165 | sa.sa_flags = SA_RESTART; 166 | sa.sa_handler = sig_handler; 167 | 168 | for (i = 0; signals[i] != 0; i++) { 169 | if (sigaction(signals[i], &sa, NULL) == -1) { 170 | warn("sigaction(%s)", strsignal(signals[i])); 171 | continue; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /io.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include "extern.h" 34 | 35 | void 36 | io_socket_blocking(int fd) 37 | { 38 | int fl; 39 | 40 | if ((fl = fcntl(fd, F_GETFL, 0)) == -1) 41 | err(1, "fcntl"); 42 | if (fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1) 43 | err(1, "fcntl"); 44 | } 45 | 46 | void 47 | io_socket_nonblocking(int fd) 48 | { 49 | int fl; 50 | 51 | if ((fl = fcntl(fd, F_GETFL, 0)) == -1) 52 | err(1, "fcntl"); 53 | if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) == -1) 54 | err(1, "fcntl"); 55 | } 56 | 57 | /* 58 | * Blocking write of a binary buffer. 59 | * Buffers of length zero are simply ignored. 60 | */ 61 | void 62 | io_simple_write(int fd, const void *res, size_t sz) 63 | { 64 | ssize_t ssz; 65 | 66 | if (sz == 0) 67 | return; 68 | if ((ssz = write(fd, res, sz)) == -1) 69 | err(1, "write"); 70 | else if ((size_t)ssz != sz) 71 | errx(1, "write: short write"); 72 | } 73 | 74 | /* 75 | * Like io_simple_write() but into a buffer. 76 | */ 77 | void 78 | io_simple_buffer(char **b, size_t *bsz, 79 | size_t *bmax, const void *res, size_t sz) 80 | { 81 | 82 | if (*bsz + sz > *bmax) { 83 | if ((*b = realloc(*b, *bsz + sz)) == NULL) 84 | err(1, NULL); 85 | *bmax = *bsz + sz; 86 | } 87 | 88 | memcpy(*b + *bsz, res, sz); 89 | *bsz += sz; 90 | } 91 | 92 | /* 93 | * Like io_buf_write() but into a buffer. 94 | */ 95 | void 96 | io_buf_buffer(char **b, size_t *bsz, 97 | size_t *bmax, const void *p, size_t sz) 98 | { 99 | 100 | io_simple_buffer(b, bsz, bmax, &sz, sizeof(size_t)); 101 | if (sz > 0) 102 | io_simple_buffer(b, bsz, bmax, p, sz); 103 | } 104 | 105 | /* 106 | * Write a binary buffer of the given size, which may be zero. 107 | */ 108 | void 109 | io_buf_write(int fd, const void *p, size_t sz) 110 | { 111 | 112 | io_simple_write(fd, &sz, sizeof(size_t)); 113 | io_simple_write(fd, p, sz); 114 | } 115 | 116 | /* 117 | * Like io_str_write() but into a buffer. 118 | */ 119 | void 120 | io_str_buffer(char **b, size_t *bsz, size_t *bmax, const char *p) 121 | { 122 | size_t sz = (p == NULL) ? 0 : strlen(p); 123 | 124 | io_buf_buffer(b, bsz, bmax, p, sz); 125 | } 126 | 127 | /* 128 | * Write a NUL-terminated string, which may be zero-length. 129 | */ 130 | void 131 | io_str_write(int fd, const char *p) 132 | { 133 | size_t sz = (p == NULL) ? 0 : strlen(p); 134 | 135 | io_buf_write(fd, p, sz); 136 | } 137 | 138 | /* 139 | * Read of a binary buffer that must be on a blocking descriptor. 140 | * Does nothing if "sz" is zero. 141 | * This will fail and exit on EOF. 142 | */ 143 | void 144 | io_simple_read(int fd, void *res, size_t sz) 145 | { 146 | ssize_t ssz; 147 | char *tmp; 148 | 149 | tmp = res; /* arithmetic on a pointer to void is a GNU extension */ 150 | again: 151 | if (sz == 0) 152 | return; 153 | if ((ssz = read(fd, tmp, sz)) == -1) 154 | err(1, "read"); 155 | else if (ssz == 0) 156 | errx(1, "read: unexpected end of file"); 157 | else if ((size_t)ssz == sz) 158 | return; 159 | sz -= ssz; 160 | tmp += ssz; 161 | goto again; 162 | } 163 | 164 | /* 165 | * Read a binary buffer, allocating space for it. 166 | * If the buffer is zero-sized, this won't allocate "res", but 167 | * will still initialise it to NULL. 168 | */ 169 | void 170 | io_buf_read_alloc(int fd, void **res, size_t *sz) 171 | { 172 | 173 | *res = NULL; 174 | io_simple_read(fd, sz, sizeof(size_t)); 175 | if (*sz == 0) 176 | return; 177 | if ((*res = malloc(*sz)) == NULL) 178 | err(1, NULL); 179 | io_simple_read(fd, *res, *sz); 180 | } 181 | 182 | /* 183 | * Read a string (which may just be \0 and zero-length), allocating 184 | * space for it. 185 | */ 186 | void 187 | io_str_read(int fd, char **res) 188 | { 189 | size_t sz; 190 | 191 | io_simple_read(fd, &sz, sizeof(size_t)); 192 | if ((*res = calloc(sz + 1, 1)) == NULL) 193 | err(1, NULL); 194 | io_simple_read(fd, *res, sz); 195 | } 196 | -------------------------------------------------------------------------------- /as.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include "extern.h" 32 | 33 | /* 34 | * Parse a uint32_t AS identifier from an ASN1_INTEGER. 35 | * This relies on the specification for ASN1_INTEGER itself, which is 36 | * essentially a series of big-endian bytes in the unsigned case. 37 | * All we do here is check if the number is negative then start copying 38 | * over bytes. 39 | * This is necessary because ASN1_INTEGER_get() on a 32-bit machine 40 | * (e.g., i386) will fail for AS numbers of UINT32_MAX. 41 | */ 42 | int 43 | as_id_parse(const ASN1_INTEGER *v, uint32_t *out) 44 | { 45 | int i; 46 | uint32_t res = 0; 47 | 48 | /* If the negative bit is set, this is wrong. */ 49 | 50 | if (v->type & V_ASN1_NEG) 51 | return 0; 52 | 53 | /* Too many bytes for us to consider. */ 54 | 55 | if ((size_t)v->length > sizeof(uint32_t)) 56 | return 0; 57 | 58 | /* Stored as big-endian bytes. */ 59 | 60 | for (i = 0; i < v->length; i++) { 61 | res <<= 8; 62 | res |= v->data[i]; 63 | } 64 | 65 | *out = res; 66 | return 1; 67 | } 68 | 69 | /* 70 | * Given a newly-parsed AS number or range "a", make sure that "a" does 71 | * not overlap with any other numbers or ranges in the "as" array. 72 | * This is defined by RFC 3779 section 3.2.3.4. 73 | * Returns zero on failure, non-zero on success. 74 | */ 75 | int 76 | as_check_overlap(const struct cert_as *a, const char *fn, 77 | const struct cert_as *as, size_t asz) 78 | { 79 | size_t i; 80 | 81 | /* We can have only one inheritence statement. */ 82 | 83 | if (asz && 84 | (a->type == CERT_AS_INHERIT || as[0].type == CERT_AS_INHERIT)) { 85 | warnx("%s: RFC 3779 section 3.2.3.3: " 86 | "cannot have inheritence and multiple ASnum or " 87 | "multiple inheritence", fn); 88 | return 0; 89 | } 90 | 91 | /* Now check for overlaps between singletons/ranges. */ 92 | 93 | for (i = 0; i < asz; i++) 94 | switch (as[i].type) { 95 | case CERT_AS_ID: 96 | switch (a->type) { 97 | case CERT_AS_ID: 98 | if (a->id != as[i].id) 99 | break; 100 | warnx("%s: RFC 3779 section 3.2.3.4: " 101 | "cannot have overlapping ASnum", fn); 102 | return 0; 103 | case CERT_AS_RANGE: 104 | if (as->range.min > as[i].id || 105 | as->range.max < as[i].id) 106 | break; 107 | warnx("%s: RFC 3779 section 3.2.3.4: " 108 | "cannot have overlapping ASnum", fn); 109 | return 0; 110 | default: 111 | abort(); 112 | } 113 | break; 114 | case CERT_AS_RANGE: 115 | switch (a->type) { 116 | case CERT_AS_ID: 117 | if (as[i].range.min > a->id || 118 | as[i].range.max < a->id) 119 | break; 120 | warnx("%s: RFC 3779 section 3.2.3.4: " 121 | "cannot have overlapping ASnum", fn); 122 | return 0; 123 | case CERT_AS_RANGE: 124 | if (a->range.max < as[i].range.min || 125 | a->range.min > as[i].range.max) 126 | break; 127 | warnx("%s: RFC 3779 section 3.2.3.4: " 128 | "cannot have overlapping ASnum", fn); 129 | return 0; 130 | default: 131 | abort(); 132 | } 133 | break; 134 | default: 135 | abort(); 136 | } 137 | 138 | return 1; 139 | } 140 | 141 | /* 142 | * See if a given AS range (which may be the same number, in the case of 143 | * singleton AS identifiers) is covered by the AS numbers or ranges 144 | * specified in the "as" array. 145 | * Return <0 if there is no cover, 0 if we're inheriting, >0 if there is. 146 | */ 147 | int 148 | as_check_covered(uint32_t min, uint32_t max, 149 | const struct cert_as *as, size_t asz) 150 | { 151 | size_t i; 152 | uint32_t amin, amax; 153 | 154 | for (i = 0; i < asz; i++) { 155 | if (as[i].type == CERT_AS_INHERIT) 156 | return 0; 157 | amin = as[i].type == CERT_AS_RANGE ? 158 | as[i].range.min : as[i].id; 159 | amax = as[i].type == CERT_AS_RANGE? 160 | as[i].range.max : as[i].id; 161 | if (min >= amin && max <= amax) 162 | return 1; 163 | } 164 | 165 | return -1; 166 | } 167 | -------------------------------------------------------------------------------- /cms.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "extern.h" 30 | 31 | /* 32 | * Parse and validate a self-signed CMS message, where the signing X509 33 | * certificate has been hashed to dgst (optional). 34 | * Conforms to RFC 6488. 35 | * The eContentType of the message must be an oid object. 36 | * Return the eContent as a string and set "rsz" to be its length. 37 | */ 38 | unsigned char * 39 | cms_parse_validate(X509 **xp, const char *fn, 40 | const char *oid, const unsigned char *dgst, size_t *rsz) 41 | { 42 | const ASN1_OBJECT *obj; 43 | ASN1_OCTET_STRING **os = NULL; 44 | BIO *bio = NULL, *shamd; 45 | CMS_ContentInfo *cms; 46 | FILE *f; 47 | char buf[128], mdbuf[EVP_MAX_MD_SIZE]; 48 | int rc = 0, sz; 49 | STACK_OF(X509) *certs = NULL; 50 | EVP_MD *md; 51 | unsigned char *res = NULL; 52 | 53 | *rsz = 0; 54 | *xp = NULL; 55 | 56 | /* 57 | * This is usually fopen() failure, so let it pass through to 58 | * the handler, which will in turn ignore the entity. 59 | */ 60 | if ((f = fopen(fn, "rb")) == NULL) { 61 | warn("%s", fn); 62 | return NULL; 63 | } 64 | 65 | if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) { 66 | cryptowarnx("%s: BIO_new_fp", fn); 67 | return NULL; 68 | } 69 | 70 | /* 71 | * If we have a digest specified, create an MD chain that will 72 | * automatically compute a digest during the CMS creation. 73 | */ 74 | 75 | if (dgst != NULL) { 76 | if ((shamd = BIO_new(BIO_f_md())) == NULL) 77 | cryptoerrx("BIO_new"); 78 | if (!BIO_set_md(shamd, EVP_sha256())) 79 | cryptoerrx("BIO_set_md"); 80 | if ((bio = BIO_push(shamd, bio)) == NULL) 81 | cryptoerrx("BIO_push"); 82 | } 83 | 84 | if ((cms = d2i_CMS_bio(bio, NULL)) == NULL) { 85 | cryptowarnx("%s: RFC 6488: failed CMS parse", fn); 86 | goto out; 87 | } 88 | 89 | /* 90 | * If we have a digest, find it in the chain (we'll already have 91 | * made it, so assert otherwise) and verify it. 92 | */ 93 | 94 | if (dgst != NULL) { 95 | shamd = BIO_find_type(bio, BIO_TYPE_MD); 96 | assert(shamd != NULL); 97 | 98 | if (!BIO_get_md(shamd, &md)) 99 | cryptoerrx("BIO_get_md"); 100 | assert(EVP_MD_type(md) == NID_sha256); 101 | 102 | if ((sz = BIO_gets(shamd, mdbuf, EVP_MAX_MD_SIZE)) < 0) 103 | cryptoerrx("BIO_gets"); 104 | assert(sz == SHA256_DIGEST_LENGTH); 105 | 106 | if (memcmp(mdbuf, dgst, SHA256_DIGEST_LENGTH)) { 107 | warnx("%s: RFC 6488: bad message digest", fn); 108 | goto out; 109 | } 110 | } 111 | 112 | /* 113 | * The CMS is self-signed with a signing certifiate. 114 | * Verify that the self-signage is correct. 115 | */ 116 | 117 | if (!CMS_verify(cms, NULL, NULL, 118 | NULL, NULL, CMS_NO_SIGNER_CERT_VERIFY)) { 119 | cryptowarnx("%s: RFC 6488: CMS not self-signed", fn); 120 | goto out; 121 | } 122 | 123 | /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 124 | 125 | obj = CMS_get0_eContentType(cms); 126 | if ((sz = OBJ_obj2txt(buf, sizeof(buf), obj, 1)) < 0) 127 | cryptoerrx("OBJ_obj2txt"); 128 | 129 | if ((size_t)sz >= sizeof(buf)) { 130 | warnx("%s: RFC 6488 section 2.1.3.1: " 131 | "eContentType: OID too long", fn); 132 | goto out; 133 | } else if (strcmp(buf, oid)) { 134 | warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 135 | "unknown OID: %s, want %s", fn, buf, oid); 136 | goto out; 137 | } 138 | 139 | /* 140 | * The self-signing certificate is further signed by the input 141 | * signing authority according to RFC 6488, 2.1.4. 142 | * We extract that certificate now for later verification. 143 | */ 144 | 145 | certs = CMS_get0_signers(cms); 146 | if (certs == NULL || sk_X509_num(certs) != 1) { 147 | warnx("%s: RFC 6488 section 2.1.4: eContent: " 148 | "want 1 signer, have %d", fn, sk_X509_num(certs)); 149 | goto out; 150 | } 151 | *xp = X509_dup(sk_X509_value(certs, 0)); 152 | 153 | /* Verify that we have eContent to disseminate. */ 154 | 155 | if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { 156 | warnx("%s: RFC 6488 section 2.1.4: " 157 | "eContent: zero-length content", fn); 158 | goto out; 159 | } 160 | 161 | /* 162 | * Extract and duplicate the eContent. 163 | * The CMS framework offers us no other way of easily managing 164 | * this information; and since we're going to d2i it anyway, 165 | * simply pass it as the desired underlying types. 166 | */ 167 | 168 | if ((res = malloc((*os)->length)) == NULL) 169 | err(1, NULL); 170 | memcpy(res, (*os)->data, (*os)->length); 171 | *rsz = (*os)->length; 172 | 173 | rc = 1; 174 | out: 175 | BIO_free_all(bio); 176 | sk_X509_free(certs); 177 | CMS_ContentInfo_free(cms); 178 | 179 | if (rc == 0) { 180 | X509_free(*xp); 181 | *xp = NULL; 182 | } 183 | 184 | return res; 185 | } 186 | -------------------------------------------------------------------------------- /rpki-client.8: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD$ 2 | .\" 3 | .\" Copyright (c) 2019 Kristaps Dzonsons 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt RPKI-CLIENT 8 19 | .Os 20 | .Sh NAME 21 | .Nm rpki-client 22 | .Nd RPKI validator to support BGP Origin Validation 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Fl Bcfjnov 26 | .Op Fl b Ar sourceaddr 27 | .Op Fl d Ar cachedir 28 | .Op Fl e Ar rsync_prog 29 | .Op Fl T Ar table 30 | .Op Fl t Ar tal 31 | .Op Ar outputdir 32 | .Sh DESCRIPTION 33 | The 34 | .Nm 35 | utility queries the RPKI repository system with 36 | .Xr @RPKI_RSYNC_COMMAND@ 1 37 | to fetch all X.509 certificates, manifests, and revocation lists under a given 38 | .Em Trust Anchor . 39 | .Nm 40 | subsequently validates each 41 | .Em Route Origin Authorization Pq ROA 42 | by constructing and verifying a certification path for the certificate 43 | associated with the ROA (including checking relevant CRLs). 44 | .Nm 45 | produces lists of the 46 | .Em Validated ROA Payloads Pq VRPs 47 | in various formats. 48 | .Pp 49 | The options are as follows: 50 | .Bl -tag -width Ds 51 | .It Fl B 52 | Create output in the file 53 | .Pa bird 54 | in the output directory which is suitable for the BIRD internet routing daemon. 55 | .It Fl b Ar sourceaddr 56 | Tell the rsync client to use 57 | .Ar sourceaddr 58 | as the source address for connections, which is useful on machines 59 | with multiple interfaces. 60 | .It Fl c 61 | Create output in the file 62 | .Pa csv 63 | in the output directory as comma-separated values of the prefix in slash notation, 64 | the maximum prefix length, the autonomous system number, and an abbreviation 65 | for the trust anchor the entry is derived from. 66 | .It Fl d Ar cachedir 67 | The directory where 68 | .Nm 69 | will store the cached repository data. 70 | Defaults to 71 | .Pa @RPKI_PATH_BASE_DIR@ . 72 | .It Fl e Ar rsync_prog 73 | Use 74 | .Ar rsync_prog 75 | instead of 76 | .Xr @RPKI_RSYNC_COMMAND@ 1 77 | to fetch repositories. 78 | It must accept the 79 | .Fl rlt , 80 | .Fl -address 81 | and 82 | .Fl -delete 83 | flags and connect with rsync-protocol locations. 84 | .It Fl f 85 | Accept out-of-date manifests. 86 | This will still report if a manifest has expired. 87 | .It Fl j 88 | Create output in the file 89 | .Pa json 90 | in the output directory as JSON object. 91 | This format is identical to that 92 | produced by the RIPE NCC RPKI Validator and NLnet Labs routinator. 93 | .It Fl n 94 | Assume that all requested repositories exist: don't update. 95 | .It Fl o 96 | Create output in the file 97 | .Pa openbgpd 98 | in the output directory as 99 | .Xr bgpd 8 100 | compatible input. 101 | If the 102 | .Fl B , 103 | .Fl c , 104 | and 105 | .Fl j 106 | options are not specified this is the default. 107 | .It Fl T Ar table 108 | For BIRD output generated with the 109 | .Fl B 110 | option use 111 | .Ar table 112 | as roa table name instead of the default 'ROAS'. 113 | .It Fl t Ar tal 114 | Specify a 115 | .Em Trust Anchor Location Pq TAL 116 | file to be used. 117 | This option can be used multiple times to load multiple TALs. 118 | By default 119 | .Nm 120 | will load all TAL files in 121 | .Pa @RPKI_TAL_DIR@ . 122 | .It Fl v 123 | Specified once, prints information about status. 124 | Twice, prints each filename as it's processed. 125 | .It Ar outputdir 126 | The directory where 127 | .Nm 128 | will write the output files. 129 | Defaults to 130 | .Pa @RPKI_PATH_OUT_DIR@ . 131 | .El 132 | .Pp 133 | By default 134 | .Nm 135 | produces a list of unique 136 | .Li roa-set 137 | statements in 138 | .Fl o 139 | (OpenBGPD compatible) output. 140 | .\" .Sh ENVIRONMENT 141 | .\" For sections 1, 6, 7, and 8 only. 142 | .Sh FILES 143 | .Bl -tag -width "@RPKI_PATH_OUT_DIR@/openbgpd" -compact 144 | .It Pa @RPKI_TAL_DIR@/*.tal 145 | default TAL files used unless 146 | .Fl t Ar tal 147 | is specified. 148 | .It Pa @RPKI_PATH_BASE_DIR@ 149 | cached repository data. 150 | .It Pa @RPKI_PATH_OUT_DIR@/openbgpd 151 | default roa-set output file. 152 | .El 153 | .Sh EXIT STATUS 154 | .Ex -std 155 | .\" For sections 1, 6, and 8 only. 156 | .\" .Sh EXAMPLES 157 | .\" .Sh DIAGNOSTICS 158 | .\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. 159 | .Sh SEE ALSO 160 | .Xr @RPKI_RSYNC_COMMAND@ 1 , 161 | .Xr bgpd.conf 5 162 | .Sh STANDARDS 163 | The following standards are used or referenced in 164 | .Nm : 165 | .Bl -tag -width -Ds 166 | .It RFC 3370 167 | Cryptographic Message Syntax (CMS) Algorithms. 168 | .It RFC 3779 169 | X.509 Extensions for IP Addresses and AS Identifiers. 170 | .It RFC 4291 171 | IP Version 6 Addressing Architecture. 172 | .It RFC 4631 173 | Classless Inter-domain Routing (CIDR): The Internet Address Assignment 174 | and Aggregation Plan. 175 | .It RFC 5280 176 | Internet X.509 Public Key Infrastructure Certificate and Certificate 177 | Revocation List (CRL) Profile. 178 | .It RFC 5652 179 | Cryptographic Message Syntax (CMS). 180 | .It RFC 5781 181 | The rsync URI Scheme. 182 | .It RFC 5952 183 | A Recommendation for IPv6 Address Text Representation. 184 | .It RFC 6480 185 | An Infrastructure to Support Secure Internet Routing. 186 | .It RFC 6482 187 | A Profile for Route Origin Authorizations (ROAs). 188 | .It RFC 6485 189 | The Profile for Algorithms and Key Sizes for Use in the Resource Public Key 190 | Infrastructure (RPKI). 191 | .It RFC 6486 192 | Manifests for the Resource Public Key Infrastructure (RPKI). 193 | .It RFC 6487 194 | A Profile for X.509 PKIX Resource Certificates. 195 | .It RFC 6488 196 | Signed Object Template for the Resource Public Key Infrastructure 197 | (RPKI). 198 | .It RFC 7730 199 | Resource Public Key Infrastructure (RPKI) Trust Anchor Locator. 200 | .El 201 | .\" .Sh HISTORY 202 | .Sh AUTHORS 203 | The 204 | .Nm 205 | utility was written by 206 | .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv . 207 | .\" .Sh CAVEATS 208 | .\" .Sh BUGS 209 | -------------------------------------------------------------------------------- /validate.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "extern.h" 33 | 34 | static void 35 | tracewarn(const struct auth *a) 36 | { 37 | 38 | for (; a != NULL; a = a->parent) 39 | warnx(" ...inheriting from: %s", a->fn); 40 | } 41 | 42 | /* 43 | * Walk up the chain of certificates trying to match our AS number to 44 | * one of the allocations in that chain. 45 | * Returns 1 if covered or 0 if not. 46 | */ 47 | static int 48 | valid_as(struct auth *a, uint32_t min, uint32_t max) 49 | { 50 | int c; 51 | 52 | if (a == NULL) 53 | return 0; 54 | 55 | /* Does this certificate cover our AS number? */ 56 | if (a->cert->asz) { 57 | c = as_check_covered(min, max, 58 | a->cert->as, a->cert->asz); 59 | if (c > 0) 60 | return 1; 61 | else if (c < 0) 62 | return 0; 63 | } 64 | 65 | /* If it doesn't, walk up the chain. */ 66 | return valid_as(a->parent, min, max); 67 | } 68 | 69 | /* 70 | * Walk up the chain of certificates (really just the last one, but in 71 | * the case of inheritence, the ones before) making sure that our IP 72 | * prefix is covered in the first non-inheriting specification. 73 | * Returns 1 if covered or 0 if not. 74 | */ 75 | static int 76 | valid_ip(struct auth *a, enum afi afi, 77 | const unsigned char *min, const unsigned char *max) 78 | { 79 | int c; 80 | 81 | if (a == NULL) 82 | return 0; 83 | 84 | /* Does this certificate cover our IP prefix? */ 85 | c = ip_addr_check_covered(afi, min, max, 86 | a->cert->ips, a->cert->ipsz); 87 | if (c > 0) 88 | return 1; 89 | else if (c < 0) 90 | return 0; 91 | 92 | /* If it doesn't, walk up the chain. */ 93 | return valid_ip(a->parent, afi, min, max); 94 | } 95 | 96 | /* 97 | * Make sure that the SKI doesn't already exist and return the parent by 98 | * its AKI. 99 | * Returns the parent auth or NULL on failure. 100 | */ 101 | struct auth * 102 | valid_ski_aki(const char *fn, struct auth_tree *auths, 103 | const char *ski, const char *aki) 104 | { 105 | struct auth *a; 106 | 107 | if (auth_find(auths, ski) != NULL) { 108 | warnx("%s: RFC 6487: duplicate SKI", fn); 109 | return NULL; 110 | } 111 | 112 | a = auth_find(auths, aki); 113 | if (a == NULL) 114 | warnx("%s: RFC 6487: unknown AKI", fn); 115 | 116 | return a; 117 | } 118 | 119 | /* 120 | * Authenticate a trust anchor by making sure its resources are not 121 | * inheriting and that the SKI is unique. 122 | * Returns 1 if valid, 0 otherwise. 123 | */ 124 | int 125 | valid_ta(const char *fn, struct auth_tree *auths, const struct cert *cert) 126 | { 127 | size_t i; 128 | 129 | /* AS and IP resources must not inherit. */ 130 | if (cert->asz && cert->as[0].type == CERT_AS_INHERIT) { 131 | warnx("%s: RFC 6487 (trust anchor): " 132 | "inheriting AS resources", fn); 133 | return 0; 134 | } 135 | for (i = 0; i < cert->ipsz; i++) 136 | if (cert->ips[i].type == CERT_IP_INHERIT) { 137 | warnx("%s: RFC 6487 (trust anchor): " 138 | "inheriting IP resources", fn); 139 | return 0; 140 | } 141 | 142 | /* SKI must not be a dupe. */ 143 | if (auth_find(auths, cert->ski) != NULL) { 144 | warnx("%s: RFC 6487: duplicate SKI", fn); 145 | return 0; 146 | } 147 | 148 | return 1; 149 | } 150 | 151 | /* 152 | * Validate a non-TA certificate: make sure its IP and AS resources are 153 | * fully covered by those in the authority key (which must exist). 154 | * Returns 1 if valid, 0 otherwise. 155 | */ 156 | int 157 | valid_cert(const char *fn, struct auth_tree *auths, const struct cert *cert) 158 | { 159 | struct auth *a; 160 | size_t i; 161 | uint32_t min, max; 162 | char buf1[64], buf2[64]; 163 | 164 | a = valid_ski_aki(fn, auths, cert->ski, cert->aki); 165 | if (a == NULL) 166 | return 0; 167 | 168 | for (i = 0; i < cert->asz; i++) { 169 | if (cert->as[i].type == CERT_AS_INHERIT) 170 | continue; 171 | min = cert->as[i].type == CERT_AS_ID ? 172 | cert->as[i].id : cert->as[i].range.min; 173 | max = cert->as[i].type == CERT_AS_ID ? 174 | cert->as[i].id : cert->as[i].range.max; 175 | if (valid_as(a, min, max)) 176 | continue; 177 | warnx("%s: RFC 6487: uncovered AS: %" 178 | PRIu32 "--%" PRIu32, fn, min, max); 179 | tracewarn(a); 180 | return 0; 181 | } 182 | 183 | for (i = 0; i < cert->ipsz; i++) { 184 | if (valid_ip(a, cert->ips[i].afi, cert->ips[i].min, 185 | cert->ips[i].max)) 186 | continue; 187 | switch (cert->ips[i].type) { 188 | case CERT_IP_RANGE: 189 | ip_addr_print(&cert->ips[i].range.min, 190 | cert->ips[i].afi, buf1, sizeof(buf1)); 191 | ip_addr_print(&cert->ips[i].range.max, 192 | cert->ips[i].afi, buf2, sizeof(buf2)); 193 | warnx("%s: RFC 6487: uncovered IP: " 194 | "%s--%s", fn, buf1, buf2); 195 | break; 196 | case CERT_IP_ADDR: 197 | ip_addr_print(&cert->ips[i].ip, 198 | cert->ips[i].afi, buf1, sizeof(buf1)); 199 | warnx("%s: RFC 6487: uncovered IP: " 200 | "%s", fn, buf1); 201 | break; 202 | case CERT_IP_INHERIT: 203 | warnx("%s: RFC 6487: uncovered IP: " 204 | "(inherit)", fn); 205 | break; 206 | } 207 | tracewarn(a); 208 | return 0; 209 | } 210 | 211 | return 1; 212 | } 213 | 214 | /* 215 | * Validate our ROA: check that the SKI is unique, the AKI exists, and 216 | * the IP prefix is also contained. 217 | * Returns 1 if valid, 0 otherwise. 218 | */ 219 | int 220 | valid_roa(const char *fn, struct auth_tree *auths, struct roa *roa) 221 | { 222 | struct auth *a; 223 | size_t i; 224 | char buf[64]; 225 | 226 | a = valid_ski_aki(fn, auths, roa->ski, roa->aki); 227 | if (a == NULL) 228 | return 0; 229 | 230 | if ((roa->tal = strdup(a->tal)) == NULL) 231 | err(1, NULL); 232 | 233 | for (i = 0; i < roa->ipsz; i++) { 234 | if (valid_ip(a, roa->ips[i].afi, roa->ips[i].min, 235 | roa->ips[i].max)) 236 | continue; 237 | ip_addr_print(&roa->ips[i].addr, 238 | roa->ips[i].afi, buf, sizeof(buf)); 239 | warnx("%s: RFC 6482: uncovered IP: " 240 | "%s", fn, buf); 241 | tracewarn(a); 242 | return 0; 243 | } 244 | 245 | return 1; 246 | } 247 | -------------------------------------------------------------------------------- /tal.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #ifdef __FreeBSD__ 20 | # define _WITH_GETLINE /* getline() */ 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #include "extern.h" 36 | 37 | /* 38 | * Inner function for parsing RFC 7730 from a buffer. 39 | * Returns a valid pointer on success, NULL otherwise. 40 | * The pointer must be freed with tal_free(). 41 | */ 42 | static struct tal * 43 | tal_parse_buffer(const char *fn, char *buf) 44 | { 45 | char *nl, *line; 46 | unsigned char *b64 = NULL; 47 | size_t sz; 48 | int rc = 0, b64sz; 49 | struct tal *tal = NULL; 50 | enum rtype rp; 51 | EVP_PKEY *pkey = NULL; 52 | 53 | if ((tal = calloc(1, sizeof(struct tal))) == NULL) 54 | err(1, NULL); 55 | 56 | /* Begin with the URI section, comment section already removed. */ 57 | while ((nl = strchr(buf, '\n')) != NULL) { 58 | line = buf; 59 | *nl = '\0'; 60 | 61 | /* advance buffer to next line */ 62 | buf = nl + 1; 63 | 64 | /* Zero-length line is end of section. */ 65 | if (*line == '\0') 66 | break; 67 | 68 | /* ignore https URI for now. */ 69 | if (strncasecmp(line, "https://", 8) == 0) { 70 | warnx("%s: https schema ignored", line); 71 | continue; 72 | } 73 | 74 | /* Append to list of URIs. */ 75 | tal->uri = reallocarray(tal->uri, 76 | tal->urisz + 1, sizeof(char *)); 77 | if (tal->uri == NULL) 78 | err(1, NULL); 79 | 80 | tal->uri[tal->urisz] = strdup(line); 81 | if (tal->uri[tal->urisz] == NULL) 82 | err(1, NULL); 83 | tal->urisz++; 84 | 85 | /* Make sure we're a proper rsync URI. */ 86 | if (!rsync_uri_parse(NULL, NULL, 87 | NULL, NULL, NULL, NULL, &rp, line)) { 88 | warnx("%s: RFC 7730 section 2.1: " 89 | "failed to parse URL: %s", fn, line); 90 | goto out; 91 | } 92 | if (rp != RTYPE_CER) { 93 | warnx("%s: RFC 7730 section 2.1: " 94 | "not a certificate URL: %s", fn, line); 95 | goto out; 96 | } 97 | 98 | } 99 | 100 | if (tal->urisz == 0) { 101 | warnx("%s: no URIs in manifest part", fn); 102 | goto out; 103 | } else if (tal->urisz > 1) 104 | warnx("%s: multiple URIs: using the first", fn); 105 | /* XXX no support for TAL files with multiple TALs yet */ 106 | 107 | sz = strlen(buf); 108 | if (sz == 0) { 109 | warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: " 110 | "zero-length public key", fn); 111 | goto out; 112 | } 113 | 114 | /* Now the BASE64-encoded public key. */ 115 | sz = ((sz + 3) / 4) * 3 + 1; 116 | if ((b64 = malloc(sz)) == NULL) 117 | err(1, NULL); 118 | if ((b64sz = b64_pton(buf, b64, sz)) < 0) 119 | errx(1, "b64_pton"); 120 | 121 | tal->pkey = b64; 122 | tal->pkeysz = b64sz; 123 | 124 | /* Make sure it's a valid public key. */ 125 | pkey = d2i_PUBKEY(NULL, (const unsigned char **)&b64, b64sz); 126 | if (pkey == NULL) { 127 | cryptowarnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: " 128 | "failed public key parse", fn); 129 | goto out; 130 | } 131 | rc = 1; 132 | out: 133 | if (rc == 0) { 134 | tal_free(tal); 135 | tal = NULL; 136 | } 137 | EVP_PKEY_free(pkey); 138 | return tal; 139 | } 140 | 141 | /* 142 | * Parse a TAL from "buf" conformant to RFC 7730 originally from a file 143 | * named "fn". 144 | * Returns the encoded data or NULL on syntax failure. 145 | */ 146 | struct tal * 147 | tal_parse(const char *fn, char *buf) 148 | { 149 | struct tal *p; 150 | const char *d; 151 | size_t dlen; 152 | 153 | p = tal_parse_buffer(fn, buf); 154 | if (p == NULL) 155 | return NULL; 156 | 157 | /* extract the TAL basename (without .tal suffix) */ 158 | d = strrchr(fn, '/'); 159 | if (d == NULL) 160 | d = fn; 161 | else 162 | d++; 163 | dlen = strlen(d); 164 | if (strcasecmp(d + dlen - 4, ".tal") == 0) 165 | dlen -= 4; 166 | if ((p->descr = malloc(dlen + 1)) == NULL) 167 | err(1, NULL); 168 | memcpy(p->descr, d, dlen); 169 | p->descr[dlen] = '\0'; 170 | 171 | return p; 172 | } 173 | 174 | /* 175 | * Read the file named "file" into a returned, NUL-terminated buffer. 176 | * This replaces CRLF terminators with plain LF, if found, and also 177 | * elides document-leading comment lines starting with "#". 178 | * Files may not exceeds 4096 bytes. 179 | * This function exits on failure, so it always returns a buffer with 180 | * TAL data. 181 | */ 182 | char * 183 | tal_read_file(const char *file) 184 | { 185 | char *nbuf, *line = NULL, *buf = NULL; 186 | FILE *in; 187 | ssize_t n, i; 188 | size_t sz = 0, bsz = 0; 189 | int optcomment = 1; 190 | 191 | if ((in = fopen(file, "r")) == NULL) 192 | err(1, "fopen: %s", file); 193 | 194 | while ((n = getline(&line, &sz, in)) != -1) { 195 | /* replace CRLF with just LF */ 196 | if (n > 1 && line[n - 1] == '\n' && line[n - 2] == '\r') { 197 | line[n - 2] = '\n'; 198 | line[n - 1] = '\0'; 199 | n--; 200 | } 201 | if (optcomment) { 202 | /* if this is comment, just eat the line */ 203 | if (line[0] == '#') 204 | continue; 205 | optcomment = 0; 206 | /* 207 | * Empty line is end of section and needs 208 | * to be eaten as well. 209 | */ 210 | if (line[0] == '\n') 211 | continue; 212 | } 213 | 214 | /* make sure every line is valid ascii */ 215 | for (i = 0; i < n; i++) 216 | if (!isprint((unsigned char)line[i]) && 217 | !isspace((unsigned char)line[i])) 218 | errx(1, "getline: %s: " 219 | "invalid content", file); 220 | 221 | /* concat line to buf */ 222 | if ((nbuf = realloc(buf, bsz + n + 1)) == NULL) 223 | err(1, NULL); 224 | if (buf == NULL) 225 | nbuf[0] = '\0'; /* initialize buffer */ 226 | buf = nbuf; 227 | bsz += n + 1; 228 | if (strlcat(buf, line, bsz) >= bsz) 229 | errx(1, "strlcat overflow"); 230 | /* limit the buffer size */ 231 | if (bsz > 4096) 232 | errx(1, "%s: file too big", file); 233 | } 234 | 235 | free(line); 236 | if (ferror(in)) 237 | err(1, "getline: %s", file); 238 | fclose(in); 239 | if (buf == NULL) 240 | errx(1, "%s: no data", file); 241 | return buf; 242 | } 243 | 244 | /* 245 | * Free a TAL pointer. 246 | * Safe to call with NULL. 247 | */ 248 | void 249 | tal_free(struct tal *p) 250 | { 251 | size_t i; 252 | 253 | if (p == NULL) 254 | return; 255 | 256 | if (p->uri != NULL) 257 | for (i = 0; i < p->urisz; i++) 258 | free(p->uri[i]); 259 | 260 | free(p->pkey); 261 | free(p->uri); 262 | free(p->descr); 263 | free(p); 264 | } 265 | 266 | /* 267 | * Buffer TAL parsed contents for writing. 268 | * See tal_read() for the other side of the pipe. 269 | */ 270 | void 271 | tal_buffer(char **b, size_t *bsz, size_t *bmax, const struct tal *p) 272 | { 273 | size_t i; 274 | 275 | io_buf_buffer(b, bsz, bmax, p->pkey, p->pkeysz); 276 | io_str_buffer(b, bsz, bmax, p->descr); 277 | io_simple_buffer(b, bsz, bmax, &p->urisz, sizeof(size_t)); 278 | 279 | for (i = 0; i < p->urisz; i++) 280 | io_str_buffer(b, bsz, bmax, p->uri[i]); 281 | } 282 | 283 | /* 284 | * Read parsed TAL contents from descriptor. 285 | * See tal_buffer() for the other side of the pipe. 286 | * A returned pointer must be freed with tal_free(). 287 | */ 288 | struct tal * 289 | tal_read(int fd) 290 | { 291 | size_t i; 292 | struct tal *p; 293 | 294 | if ((p = calloc(1, sizeof(struct tal))) == NULL) 295 | err(1, NULL); 296 | 297 | io_buf_read_alloc(fd, (void **)&p->pkey, &p->pkeysz); 298 | assert(p->pkeysz > 0); 299 | io_str_read(fd, &p->descr); 300 | io_simple_read(fd, &p->urisz, sizeof(size_t)); 301 | assert(p->urisz > 0); 302 | 303 | if ((p->uri = calloc(p->urisz, sizeof(char *))) == NULL) 304 | err(1, NULL); 305 | 306 | for (i = 0; i < p->urisz; i++) 307 | io_str_read(fd, &p->uri[i]); 308 | 309 | return p; 310 | } 311 | -------------------------------------------------------------------------------- /x509.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "extern.h" 33 | 34 | /* 35 | * Wrapper around ASN1_get_object() that preserves the current start 36 | * state and returns a more meaningful value. 37 | * Return zero on failure, non-zero on success. 38 | */ 39 | static int 40 | ASN1_frame(const char *fn, size_t sz, 41 | const unsigned char **cnt, long *cntsz, int *tag) 42 | { 43 | int ret, pcls; 44 | 45 | assert(cnt != NULL && *cnt != NULL); 46 | assert(sz > 0); 47 | ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz); 48 | if ((ret & 0x80)) { 49 | cryptowarnx("%s: ASN1_get_object", fn); 50 | return 0; 51 | } 52 | return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag); 53 | } 54 | 55 | /* 56 | * Parse X509v3 authority key identifier (AKI), RFC 6487 sec. 4.8.3. 57 | * Returns the AKI or NULL if it could not be parsed. 58 | * The AKI is formatted as aa:bb:cc:dd, with each being a hex value. 59 | */ 60 | char * 61 | x509_get_aki_ext(X509_EXTENSION *ext, const char *fn) 62 | { 63 | const unsigned char *d; 64 | const ASN1_TYPE *t; 65 | const ASN1_OCTET_STRING *os = NULL; 66 | ASN1_SEQUENCE_ANY *seq = NULL; 67 | int dsz, ptag; 68 | long i, plen; 69 | char buf[4]; 70 | char *res = NULL; 71 | 72 | assert(NID_authority_key_identifier == 73 | OBJ_obj2nid(X509_EXTENSION_get_object(ext))); 74 | os = X509_EXTENSION_get_data(ext); 75 | assert(os != NULL); 76 | 77 | d = os->data; 78 | dsz = os->length; 79 | 80 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 81 | cryptowarnx("%s: RFC 6487 section 4.8.3: AKI: " 82 | "failed ASN.1 sub-sequence parse", fn); 83 | goto out; 84 | } 85 | if (sk_ASN1_TYPE_num(seq) != 1) { 86 | warnx("%s: RFC 6487 section 4.8.3: AKI: " 87 | "want 1 element, have %d", fn, sk_ASN1_TYPE_num(seq)); 88 | goto out; 89 | } 90 | 91 | t = sk_ASN1_TYPE_value(seq, 0); 92 | if (t->type != V_ASN1_OTHER) { 93 | warnx("%s: RFC 6487 section 4.8.3: AKI: " 94 | "want ASN.1 external, have %s (NID %d)", 95 | fn, ASN1_tag2str(t->type), t->type); 96 | goto out; 97 | } 98 | 99 | d = t->value.asn1_string->data; 100 | dsz = t->value.asn1_string->length; 101 | 102 | if (!ASN1_frame(fn, dsz, &d, &plen, &ptag)) 103 | goto out; 104 | 105 | /* Make room for [hex1, hex2, ":"]*, NUL. */ 106 | 107 | if ((res = calloc(plen * 3 + 1, 1)) == NULL) 108 | err(1, NULL); 109 | 110 | for (i = 0; i < plen; i++) { 111 | snprintf(buf, sizeof(buf), "%02X:", d[i]); 112 | strlcat(res, buf, plen * 3 + 1); 113 | } 114 | res[plen * 3 - 1] = '\0'; 115 | out: 116 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 117 | return res; 118 | } 119 | 120 | /* 121 | * Parse X509v3 subject key identifier (SKI), RFC 6487 sec. 4.8.2. 122 | * Returns the SKI or NULL if it could not be parsed. 123 | * The SKI is formatted as aa:bb:cc:dd, with each being a hex value. 124 | */ 125 | char * 126 | x509_get_ski_ext(X509_EXTENSION *ext, const char *fn) 127 | { 128 | const unsigned char *d; 129 | const ASN1_OCTET_STRING *os; 130 | ASN1_OCTET_STRING *oss = NULL; 131 | int i, dsz; 132 | char buf[4]; 133 | char *res = NULL; 134 | 135 | assert(NID_subject_key_identifier == 136 | OBJ_obj2nid(X509_EXTENSION_get_object(ext))); 137 | 138 | os = X509_EXTENSION_get_data(ext); 139 | assert(os != NULL); 140 | d = os->data; 141 | dsz = os->length; 142 | 143 | if ((oss = d2i_ASN1_OCTET_STRING(NULL, &d, dsz)) == NULL) { 144 | cryptowarnx("%s: RFC 6487 section 4.8.2: SKI: " 145 | "failed ASN.1 octet string parse", fn); 146 | goto out; 147 | } 148 | 149 | d = oss->data; 150 | dsz = oss->length; 151 | 152 | if (dsz != 20) { 153 | warnx("%s: RFC 6487 section 4.8.2: SKI: " 154 | "want 20 B SHA1 hash, have %d B", fn, dsz); 155 | goto out; 156 | } 157 | 158 | /* Make room for [hex1, hex2, ":"]*, NUL. */ 159 | 160 | if ((res = calloc(dsz * 3 + 1, 1)) == NULL) 161 | err(1, NULL); 162 | 163 | for (i = 0; i < dsz; i++) { 164 | snprintf(buf, sizeof(buf), "%02X:", d[i]); 165 | strlcat(res, buf, dsz * 3 + 1); 166 | } 167 | res[dsz * 3 - 1] = '\0'; 168 | out: 169 | ASN1_OCTET_STRING_free(oss); 170 | return res; 171 | } 172 | 173 | /* 174 | * Wraps around x509_get_ski_ext and x509_get_aki_ext. 175 | * Returns zero on failure (out pointers are NULL) or non-zero on 176 | * success (out pointers must be freed). 177 | */ 178 | int 179 | x509_get_ski_aki(X509 *x, const char *fn, char **ski, char **aki) 180 | { 181 | X509_EXTENSION *ext = NULL; 182 | const ASN1_OBJECT *obj; 183 | int extsz, i; 184 | 185 | *ski = *aki = NULL; 186 | 187 | if ((extsz = X509_get_ext_count(x)) < 0) 188 | cryptoerrx("X509_get_ext_count"); 189 | 190 | for (i = 0; i < extsz; i++) { 191 | ext = X509_get_ext(x, i); 192 | assert(ext != NULL); 193 | obj = X509_EXTENSION_get_object(ext); 194 | assert(obj != NULL); 195 | switch (OBJ_obj2nid(obj)) { 196 | case NID_subject_key_identifier: 197 | free(*ski); 198 | *ski = x509_get_ski_ext(ext, fn); 199 | break; 200 | case NID_authority_key_identifier: 201 | free(*aki); 202 | *aki = x509_get_aki_ext(ext, fn); 203 | break; 204 | } 205 | } 206 | 207 | if (*aki == NULL) { 208 | cryptowarnx("%s: RFC 6487 section 4.8.3: AKI: " 209 | "missing AKI X509 extension", fn); 210 | free(*ski); 211 | *ski = NULL; 212 | return 0; 213 | } 214 | if (*ski == NULL) { 215 | cryptowarnx("%s: RFC 6487 section 4.8.2: AKI: " 216 | "missing SKI X509 extension", fn); 217 | free(*aki); 218 | *aki = NULL; 219 | return 0; 220 | } 221 | 222 | return 1; 223 | } 224 | 225 | /* 226 | * Parse the very specific subset of information in the CRL distribution 227 | * point extension. 228 | * See RFC 6487, sectoin 4.8.6 for details. 229 | * Returns NULL on failure, the crl URI on success which has to be freed 230 | * after use. 231 | */ 232 | char * 233 | x509_get_crl(X509 *x, const char *fn) 234 | { 235 | STACK_OF(DIST_POINT) *crldp; 236 | DIST_POINT *dp; 237 | GENERAL_NAME *name; 238 | char *crl; 239 | 240 | crldp = X509_get_ext_d2i(x, NID_crl_distribution_points, NULL, NULL); 241 | if (crldp == NULL) { 242 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 243 | "no CRL distribution point extension", fn); 244 | return NULL; 245 | } 246 | 247 | if (sk_DIST_POINT_num(crldp) != 1) { 248 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 249 | "want 1 element, have %d", fn, 250 | sk_DIST_POINT_num(crldp)); 251 | return NULL; 252 | } 253 | 254 | dp = sk_DIST_POINT_value(crldp, 0); 255 | if (dp->distpoint == NULL) { 256 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 257 | "no distribution point name", fn); 258 | return NULL; 259 | } 260 | if (dp->distpoint->type != 0) { 261 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 262 | "expected GEN_OTHERNAME, have %d", fn, dp->distpoint->type); 263 | return NULL; 264 | } 265 | 266 | if (sk_GENERAL_NAME_num(dp->distpoint->name.fullname) != 1) { 267 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 268 | "want 1 full name, have %d", fn, 269 | sk_GENERAL_NAME_num(dp->distpoint->name.fullname)); 270 | return NULL; 271 | } 272 | 273 | name = sk_GENERAL_NAME_value(dp->distpoint->name.fullname, 0); 274 | if (name->type != GEN_URI) { 275 | warnx("%s: RFC 6487 section 4.8.6: CRL: " 276 | "want URI type, have %d", fn, name->type); 277 | return NULL; 278 | } 279 | 280 | crl = strndup(ASN1_STRING_get0_data(name->d.uniformResourceIdentifier), 281 | ASN1_STRING_length(name->d.uniformResourceIdentifier)); 282 | if (crl == NULL) 283 | err(1, NULL); 284 | 285 | return crl; 286 | } 287 | 288 | char * 289 | x509_crl_get_aki(X509_CRL *crl) 290 | { 291 | X509_EXTENSION *ext; 292 | int loc; 293 | 294 | loc = X509_CRL_get_ext_by_NID(crl, NID_authority_key_identifier, -1); 295 | if (loc == -1) { 296 | warnx("%s: CRL without AKI extension", __func__); 297 | return NULL; 298 | } 299 | ext = X509_CRL_get_ext(crl, loc); 300 | 301 | return x509_get_aki_ext(ext, "x509_crl_get_aki"); 302 | } 303 | -------------------------------------------------------------------------------- /ip.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include "extern.h" 32 | 33 | /* 34 | * Length of the binary IPV4 or IPV6 prefix, i.e., without the unused bits. 35 | */ 36 | #define PREFIX_SIZE(x) (((x) + 7) / 8) 37 | 38 | /* 39 | * Parse an IP address family. 40 | * This is defined in different places in the ROA/X509 standards, but 41 | * it's the same thing. 42 | * We prohibit all but IPv4 and IPv6, without SAFI. 43 | * Return zero on failure, non-zero on success. 44 | */ 45 | int 46 | ip_addr_afi_parse(const char *fn, const ASN1_OCTET_STRING *p, enum afi *afi) 47 | { 48 | uint16_t v; 49 | 50 | if (p->length == 0 || p->length > 3) { 51 | warnx("%s: invalid field length, want 1--3, have %d", 52 | fn, p->length); 53 | return 0; 54 | } 55 | 56 | memcpy(&v, p->data, sizeof(v)); 57 | v = ntohs(v); 58 | 59 | /* Only accept IPv4 and IPv6 AFIs. */ 60 | 61 | if (v != AFI_IPV4 && v != AFI_IPV6) { 62 | warnx("%s: only AFI for IPV4 (1) and IPV6 (2) allowed: " 63 | "have %hd", fn, v); 64 | return 0; 65 | } 66 | 67 | /* Disallow the optional SAFI. */ 68 | 69 | if (p->length == 3) { 70 | warnx("%s: SAFI not allowed", fn); 71 | return 0; 72 | } 73 | 74 | *afi = v; 75 | return 1; 76 | } 77 | 78 | /* 79 | * See if a given IP prefix is covered by the IP prefixes or ranges 80 | * specified in the "ips" array. 81 | * This means that the IP prefix must be strictly within the ranges or 82 | * singletons given in the array. 83 | * Return 0 if we're inheriting from the parent, >0 if we're covered, 84 | * or <0 if we're not covered. 85 | */ 86 | int 87 | ip_addr_check_covered(enum afi afi, 88 | const unsigned char *min, const unsigned char *max, 89 | const struct cert_ip *ips, size_t ipsz) 90 | { 91 | size_t i, sz = AFI_IPV4 == afi ? 4 : 16; 92 | 93 | for (i = 0; i < ipsz; i++) { 94 | if (ips[i].afi != afi) 95 | continue; 96 | if (ips[i].type == CERT_IP_INHERIT) 97 | return 0; 98 | if (memcmp(ips[i].min, min, sz) <= 0 && 99 | memcmp(ips[i].max, max, sz) >= 0) 100 | return 1; 101 | } 102 | 103 | return -1; 104 | } 105 | 106 | /* 107 | * Given a newly-parsed IP address or range "ip", make sure that "ip" 108 | * does not overlap with any addresses or ranges in the "ips" array. 109 | * This is defined by RFC 3779 section 2.2.3.6. 110 | * Returns zero on failure, non-zero on success. 111 | */ 112 | int 113 | ip_addr_check_overlap(const struct cert_ip *ip, const char *fn, 114 | const struct cert_ip *ips, size_t ipsz) 115 | { 116 | size_t i, sz = ip->afi == AFI_IPV4 ? 4 : 16; 117 | int inherit_v4 = 0, inherit_v6 = 0; 118 | int has_v4 = 0, has_v6 = 0, socktype; 119 | char buf[64]; 120 | 121 | /* 122 | * FIXME: cache this by having a flag on the cert_ip, else we're 123 | * going to need to do a lot of scanning for big allocations. 124 | */ 125 | 126 | for (i = 0; i < ipsz; i++) 127 | if (ips[i].type == CERT_IP_INHERIT) { 128 | if (ips[i].afi == AFI_IPV4) 129 | inherit_v4 = 1; 130 | else 131 | inherit_v6 = 1; 132 | } else { 133 | if (ips[i].afi == AFI_IPV4) 134 | has_v4 = 1; 135 | else 136 | has_v6 = 1; 137 | } 138 | 139 | /* Disallow multiple inheritence per type. */ 140 | 141 | if ((inherit_v4 && ip->afi == AFI_IPV4) || 142 | (inherit_v6 && ip->afi == AFI_IPV6) || 143 | (has_v4 && ip->afi == AFI_IPV4 && 144 | ip->type == CERT_IP_INHERIT) || 145 | (has_v6 && ip->afi == AFI_IPV6 && 146 | ip->type == CERT_IP_INHERIT)) { 147 | warnx("%s: RFC 3779 section 2.2.3.5: " 148 | "cannot have multiple inheritence or inheritence and " 149 | "addresses of the same class", fn); 150 | return 0; 151 | } 152 | 153 | /* Check our ranges. */ 154 | 155 | for (i = 0; i < ipsz; i++) { 156 | if (ips[i].afi != ip->afi) 157 | continue; 158 | if (memcmp(ips[i].max, ip->min, sz) <= 0 || 159 | memcmp(ips[i].min, ip->max, sz) >= 0) 160 | continue; 161 | socktype = (ips[i].afi == AFI_IPV4) ? AF_INET : AF_INET6, 162 | warnx("%s: RFC 3779 section 2.2.3.5: " 163 | "cannot have overlapping IP addresses", fn); 164 | ip_addr_print(&ip->ip, ip->afi, buf, sizeof(buf)); 165 | warnx("%s: certificate IP: %s", fn, buf); 166 | inet_ntop(socktype, ip->min, buf, sizeof(buf)); 167 | warnx("%s: certificate IP minimum: %s", fn, buf); 168 | inet_ntop(socktype, ip->max, buf, sizeof(buf)); 169 | warnx("%s: certificate IP maximum: %s", fn, buf); 170 | inet_ntop(socktype, ips[i].min, buf, sizeof(buf)); 171 | warnx("%s: offending IP minimum: %s", fn, buf); 172 | inet_ntop(socktype, ips[i].max, buf, sizeof(buf)); 173 | warnx("%s: offending IP maximum: %s", fn, buf); 174 | return 0; 175 | } 176 | 177 | return 1; 178 | } 179 | 180 | /* 181 | * Parse an IP address, RFC 3779, 2.2.3.8. 182 | * Return zero on failure, non-zero on success. 183 | */ 184 | int 185 | ip_addr_parse(const ASN1_BIT_STRING *p, 186 | enum afi afi, const char *fn, struct ip_addr *addr) 187 | { 188 | long unused = 0; 189 | 190 | /* Weird OpenSSL-ism to get unused bit count. */ 191 | 192 | if ((p->flags & ASN1_STRING_FLAG_BITS_LEFT)) 193 | unused = p->flags & ~ASN1_STRING_FLAG_BITS_LEFT; 194 | 195 | if (unused < 0) { 196 | warnx("%s: RFC 3779 section 2.2.3.8: " 197 | "unused bit count must be non-negative", fn); 198 | return 0; 199 | } else if (unused >= 8) { 200 | warnx("%s: RFC 3779 section 2.2.3.8: " 201 | "unused bit count must mask an unsigned char", fn); 202 | return 0; 203 | } else if (p->length == 0 && unused != 0) { 204 | warnx("%s: RFC 3779 section 2.2.3.8: " 205 | "unused bit count must be zero if length is zero", fn); 206 | return 0; 207 | } 208 | 209 | /* 210 | * Check that the unused bits are set to zero. 211 | * If we don't do this, stray bits will corrupt our composition 212 | * of the [minimum] address ranges. 213 | */ 214 | 215 | if (p->length != 0 && 216 | (p->data[p->length - 1] & ((1 << unused) - 1))) { 217 | warnx("%s: RFC 3779 section 2.2.3.8: " 218 | "unused bits must be set to zero", fn); 219 | return 0; 220 | } 221 | 222 | /* Limit possible sizes of addresses. */ 223 | 224 | if ((afi == AFI_IPV4 && p->length > 4) || 225 | (afi == AFI_IPV6 && p->length > 16)) { 226 | warnx("%s: RFC 3779 section 2.2.3.8: " 227 | "IP address too long", fn); 228 | return 0; 229 | } 230 | 231 | memset (addr, 0, sizeof(struct ip_addr)); 232 | addr->prefixlen = p->length * 8 - unused; 233 | memcpy(addr->addr, p->data, p->length); 234 | return 1; 235 | } 236 | 237 | /* 238 | * Convert the IPv4 address into CIDR notation conforming to RFC 4632. 239 | * Buffer should be able to hold xxx.yyy.zzz.www/nn. 240 | */ 241 | static void 242 | ip4_addr2str(const struct ip_addr *addr, char *b, size_t bsz) 243 | { 244 | char buf[16]; 245 | 246 | snprintf(b, bsz, "%s/%hhu", inet_ntop(AF_INET, addr->addr, buf, 247 | sizeof(buf)), addr->prefixlen); 248 | } 249 | 250 | /* 251 | * Convert the IPv6 address into CIDR notation conforming to RFC 4291. 252 | * See also RFC 5952. 253 | * Must hold 0000:0000:0000:0000:0000:0000:0000:0000/nn. 254 | */ 255 | static void 256 | ip6_addr2str(const struct ip_addr *addr, char *b, size_t bsz) 257 | { 258 | char buf[44]; 259 | 260 | snprintf(b, bsz, "%s/%hhu", inet_ntop(AF_INET6, addr->addr, buf, 261 | sizeof(buf)), addr->prefixlen); 262 | } 263 | 264 | /* 265 | * Convert a ip_addr into a NUL-terminated CIDR notation string 266 | * conforming to RFC 4632 or 4291. 267 | * The size of the buffer must be at least 64 (inclusive). 268 | */ 269 | void 270 | ip_addr_print(const struct ip_addr *addr, 271 | enum afi afi, char *buf, size_t bufsz) 272 | { 273 | 274 | if (afi == AFI_IPV4) 275 | ip4_addr2str(addr, buf, bufsz); 276 | else 277 | ip6_addr2str(addr, buf, bufsz); 278 | } 279 | 280 | /* 281 | * Serialise an ip_addr for sending over the wire. 282 | * Matched with ip_addr_read(). 283 | */ 284 | void 285 | ip_addr_buffer(char **b, size_t *bsz, size_t *bmax, const struct ip_addr *p) 286 | { 287 | size_t sz = PREFIX_SIZE(p->prefixlen); 288 | 289 | assert(sz <= 16); 290 | io_simple_buffer(b, bsz, bmax, &p->prefixlen, sizeof(unsigned char)); 291 | io_simple_buffer(b, bsz, bmax, p->addr, sz); 292 | } 293 | 294 | /* 295 | * Serialise an ip_addr_range for sending over the wire. 296 | * Matched with ip_addr_range_read(). 297 | */ 298 | void 299 | ip_addr_range_buffer(char **b, size_t *bsz, size_t *bmax, 300 | const struct ip_addr_range *p) 301 | { 302 | 303 | ip_addr_buffer(b, bsz, bmax, &p->min); 304 | ip_addr_buffer(b, bsz, bmax, &p->max); 305 | } 306 | 307 | /* 308 | * Read an ip_addr from the wire. 309 | * Matched with ip_addr_buffer(). 310 | */ 311 | void 312 | ip_addr_read(int fd, struct ip_addr *p) 313 | { 314 | size_t sz; 315 | 316 | io_simple_read(fd, &p->prefixlen, sizeof(unsigned char)); 317 | sz = PREFIX_SIZE(p->prefixlen); 318 | assert(sz <= 16); 319 | io_simple_read(fd, p->addr, sz); 320 | } 321 | 322 | /* 323 | * Read an ip_addr_range from the wire. 324 | * Matched with ip_addr_range_buffer(). 325 | */ 326 | void 327 | ip_addr_range_read(int fd, struct ip_addr_range *p) 328 | { 329 | 330 | ip_addr_read(fd, &p->min); 331 | ip_addr_read(fd, &p->max); 332 | } 333 | 334 | /* 335 | * Given the addresses (range or IP) in cert_ip, fill in the "min" and 336 | * "max" fields with the minimum and maximum possible IP addresses given 337 | * those ranges (or singleton prefixed range). 338 | * This does nothing if CERT_IP_INHERIT. 339 | * Returns zero on failure (misordered ranges), non-zero on success. 340 | */ 341 | int 342 | ip_cert_compose_ranges(struct cert_ip *p) 343 | { 344 | size_t sz; 345 | 346 | switch (p->type) { 347 | case CERT_IP_ADDR: 348 | sz = PREFIX_SIZE(p->ip.prefixlen); 349 | memset(p->min, 0x0, sizeof(p->min)); 350 | memcpy(p->min, p->ip.addr, sz); 351 | memset(p->max, 0xff, sizeof(p->max)); 352 | memcpy(p->max, p->ip.addr, sz); 353 | if (sz > 0 && p->ip.prefixlen % 8 != 0) 354 | p->max[sz - 1] |= (1 << (8 - p->ip.prefixlen % 8)) - 1; 355 | break; 356 | case CERT_IP_RANGE: 357 | memset(p->min, 0x0, sizeof(p->min)); 358 | sz = PREFIX_SIZE(p->range.min.prefixlen); 359 | memcpy(p->min, p->range.min.addr, sz); 360 | memset(p->max, 0xff, sizeof(p->max)); 361 | sz = PREFIX_SIZE(p->range.max.prefixlen); 362 | memcpy(p->max, p->range.max.addr, sz); 363 | if (sz > 0 && p->range.max.prefixlen % 8 != 0) 364 | p->max[sz - 1] |= 365 | (1 << (8 - p->range.max.prefixlen % 8)) - 1; 366 | break; 367 | default: 368 | return 1; 369 | } 370 | 371 | sz = AFI_IPV4 == p->afi ? 4 : 16; 372 | return memcmp(p->min, p->max, sz) <= 0; 373 | } 374 | 375 | /* 376 | * Given the ROA's acceptable prefix, compute the minimum and maximum 377 | * address accepted by the prefix. 378 | */ 379 | void 380 | ip_roa_compose_ranges(struct roa_ip *p) 381 | { 382 | size_t sz = PREFIX_SIZE(p->addr.prefixlen); 383 | 384 | memset(p->min, 0x0, sizeof(p->min)); 385 | memcpy(p->min, p->addr.addr, sz); 386 | memset(p->max, 0xff, sizeof(p->max)); 387 | memcpy(p->max, p->addr.addr, sz); 388 | if (sz > 0 && p->addr.prefixlen % 8 != 0) 389 | p->max[sz - 1] |= (1 << (8 - p->addr.prefixlen % 8)) - 1; 390 | } 391 | -------------------------------------------------------------------------------- /extern.h: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #ifndef EXTERN_H 18 | #define EXTERN_H 19 | 20 | #if HAVE_SYS_TREE 21 | # include 22 | #endif 23 | #if !HAVE_PLEDGE 24 | # define pledge(x, y) (1) 25 | #endif 26 | #if !HAVE_UNVEIL 27 | # define unveil(x, y) (1) 28 | #endif 29 | 30 | enum cert_as_type { 31 | CERT_AS_ID, /* single identifier */ 32 | CERT_AS_INHERIT, /* inherit from parent */ 33 | CERT_AS_RANGE, /* range of identifiers */ 34 | }; 35 | 36 | /* 37 | * An AS identifier range. 38 | * The maximum AS identifier is an unsigned 32 bit integer (RFC 6793). 39 | */ 40 | struct cert_as_range { 41 | uint32_t min; /* minimum non-zero */ 42 | uint32_t max; /* maximum */ 43 | }; 44 | 45 | /* 46 | * An autonomous system (AS) object. 47 | * AS identifiers are unsigned 32 bit integers (RFC 6793). 48 | */ 49 | struct cert_as { 50 | enum cert_as_type type; /* type of AS specification */ 51 | union { 52 | uint32_t id; /* singular identifier */ 53 | struct cert_as_range range; /* range */ 54 | }; 55 | }; 56 | 57 | /* 58 | * AFI values are assigned by IANA. 59 | * In rpki-client, we only accept the IPV4 and IPV6 AFI values. 60 | */ 61 | enum afi { 62 | AFI_IPV4 = 1, 63 | AFI_IPV6 = 2 64 | }; 65 | 66 | /* 67 | * An IP address as parsed from RFC 3779, section 2.2.3.8. 68 | * This is either in a certificate or an ROA. 69 | * It may either be IPv4 or IPv6. 70 | */ 71 | struct ip_addr { 72 | unsigned char addr[16]; /* binary address prefix */ 73 | unsigned char prefixlen; /* number of valid bits in address */ 74 | }; 75 | 76 | /* 77 | * An IP address (IPv4 or IPv6) range starting at the minimum and making 78 | * its way to the maximum. 79 | */ 80 | struct ip_addr_range { 81 | struct ip_addr min; /* minimum ip */ 82 | struct ip_addr max; /* maximum ip */ 83 | }; 84 | 85 | enum cert_ip_type { 86 | CERT_IP_ADDR, /* IP address range w/shared prefix */ 87 | CERT_IP_INHERIT, /* inherited IP address */ 88 | CERT_IP_RANGE /* range of IP addresses */ 89 | }; 90 | 91 | /* 92 | * A single IP address family (AFI, address or range) as defined in RFC 93 | * 3779, 2.2.3.2. 94 | * The RFC specifies multiple address or ranges per AFI; this structure 95 | * encodes both the AFI and a single address or range. 96 | */ 97 | struct cert_ip { 98 | enum afi afi; /* AFI value */ 99 | enum cert_ip_type type; /* type of IP entry */ 100 | unsigned char min[16]; /* full range minimum */ 101 | unsigned char max[16]; /* full range maximum */ 102 | union { 103 | struct ip_addr ip; /* singular address */ 104 | struct ip_addr_range range; /* range */ 105 | }; 106 | }; 107 | 108 | /* 109 | * Parsed components of a validated X509 certificate stipulated by RFC 110 | * 6847 and further (within) by RFC 3779. 111 | * All AS numbers are guaranteed to be non-overlapping and properly 112 | * inheriting. 113 | */ 114 | struct cert { 115 | struct cert_ip *ips; /* list of IP address ranges */ 116 | size_t ipsz; /* length of "ips" */ 117 | struct cert_as *as; /* list of AS numbers and ranges */ 118 | size_t asz; /* length of "asz" */ 119 | char *mft; /* manifest (rsync:// uri) */ 120 | char *crl; /* CRL location (rsync:// or NULL) */ 121 | char *aki; /* AKI (or NULL, for trust anchor) */ 122 | char *ski; /* SKI */ 123 | int valid; /* validated resources */ 124 | X509 *x509; /* the cert */ 125 | }; 126 | 127 | /* 128 | * The TAL file conforms to RFC 7730. 129 | * It is the top-level structure of RPKI and defines where we can find 130 | * certificates for TAs (trust anchors). 131 | * It also includes the public key for verifying those trust anchor 132 | * certificates. 133 | */ 134 | struct tal { 135 | char **uri; /* well-formed rsync URIs */ 136 | size_t urisz; /* number of URIs */ 137 | unsigned char *pkey; /* DER-encoded public key */ 138 | size_t pkeysz; /* length of pkey */ 139 | char *descr; /* basename of tal file */ 140 | }; 141 | 142 | /* 143 | * Files specified in an MFT have their bodies hashed with SHA256. 144 | */ 145 | struct mftfile { 146 | char *file; /* filename (CER/ROA/CRL, no path) */ 147 | unsigned char hash[SHA256_DIGEST_LENGTH]; /* sha256 of body */ 148 | }; 149 | 150 | /* 151 | * A manifest, RFC 6486. 152 | * This consists of a bunch of files found in the same directory as the 153 | * manifest file. 154 | */ 155 | struct mft { 156 | char *file; /* full path of MFT file */ 157 | struct mftfile *files; /* file and hash */ 158 | size_t filesz; /* number of filenames */ 159 | int stale; /* if a stale manifest */ 160 | char *ski; /* SKI */ 161 | char *aki; /* AKI */ 162 | }; 163 | 164 | /* 165 | * An IP address prefix for a given ROA. 166 | * This encodes the maximum length, AFI (v6/v4), and address. 167 | * FIXME: are the min/max necessary or just used in one place? 168 | */ 169 | struct roa_ip { 170 | enum afi afi; /* AFI value */ 171 | size_t maxlength; /* max length or zero */ 172 | unsigned char min[16]; /* full range minimum */ 173 | unsigned char max[16]; /* full range maximum */ 174 | struct ip_addr addr; /* the address prefix itself */ 175 | }; 176 | 177 | /* 178 | * An ROA, RFC 6482. 179 | * This consists of the concerned ASID and its IP prefixes. 180 | */ 181 | struct roa { 182 | uint32_t asid; /* asID of ROA (if 0, RFC 6483 sec 4) */ 183 | struct roa_ip *ips; /* IP prefixes */ 184 | size_t ipsz; /* number of IP prefixes */ 185 | int valid; /* validated resources */ 186 | char *ski; /* SKI */ 187 | char *aki; /* AKI */ 188 | char *tal; /* basename of TAL for this cert */ 189 | }; 190 | 191 | /* 192 | * A single VRP element (including ASID) 193 | */ 194 | struct vrp { 195 | RB_ENTRY(vrp) entry; 196 | struct ip_addr addr; 197 | uint32_t asid; 198 | char *tal; /* basename of TAL for this cert */ 199 | enum afi afi; 200 | unsigned char maxlength; 201 | }; 202 | /* 203 | * Tree of VRP sorted by afi, addr, maxlength and asid 204 | */ 205 | RB_HEAD(vrp_tree, vrp); 206 | RB_PROTOTYPE(vrp_tree, vrp, entry, vrpcmp); 207 | 208 | /* 209 | * A single CRL 210 | */ 211 | struct crl { 212 | RB_ENTRY(crl) entry; 213 | char *aki; 214 | X509_CRL *x509_crl; 215 | }; 216 | /* 217 | * Tree of CRLs sorted by uri 218 | */ 219 | RB_HEAD(crl_tree, crl); 220 | RB_PROTOTYPE(crl_tree, crl, entry, crlcmp); 221 | 222 | /* 223 | * An authentication tuple. 224 | * This specifies a public key and a subject key identifier used to 225 | * verify children nodes in the tree of entities. 226 | */ 227 | struct auth { 228 | RB_ENTRY(auth) entry; 229 | struct cert *cert; /* owner information */ 230 | struct auth *parent; /* pointer to parent or NULL for TA cert */ 231 | char *tal; /* basename of TAL for this cert */ 232 | char *fn; /* FIXME: debugging */ 233 | }; 234 | /* 235 | * Tree of auth sorted by ski 236 | */ 237 | RB_HEAD(auth_tree, auth); 238 | RB_PROTOTYPE(auth_tree, auth, entry, authcmp); 239 | 240 | struct auth *auth_find(struct auth_tree *, const char *); 241 | 242 | /* 243 | * Resource types specified by the RPKI profiles. 244 | * There are others (e.g., gbr) that we don't consider. 245 | */ 246 | enum rtype { 247 | RTYPE_EOF = 0, 248 | RTYPE_TAL, 249 | RTYPE_MFT, 250 | RTYPE_ROA, 251 | RTYPE_CER, 252 | RTYPE_CRL 253 | }; 254 | 255 | /* global variables */ 256 | extern int verbose; 257 | 258 | /* Routines for RPKI entities. */ 259 | 260 | void tal_buffer(char **, size_t *, size_t *, const struct tal *); 261 | void tal_free(struct tal *); 262 | struct tal *tal_parse(const char *, char *); 263 | char *tal_read_file(const char *); 264 | struct tal *tal_read(int); 265 | 266 | void cert_buffer(char **, size_t *, size_t *, const struct cert *); 267 | void cert_free(struct cert *); 268 | struct cert *cert_parse(X509 **, const char *, const unsigned char *); 269 | struct cert *ta_parse(X509 **, const char *, const unsigned char *, size_t); 270 | struct cert *cert_read(int); 271 | 272 | void mft_buffer(char **, size_t *, size_t *, const struct mft *); 273 | void mft_free(struct mft *); 274 | struct mft *mft_parse(X509 **, const char *, int); 275 | int mft_check(const char *, struct mft *); 276 | struct mft *mft_read(int); 277 | 278 | void roa_buffer(char **, size_t *, size_t *, const struct roa *); 279 | void roa_free(struct roa *); 280 | struct roa *roa_parse(X509 **, const char *, const unsigned char *); 281 | struct roa *roa_read(int); 282 | void roa_insert_vrps(struct vrp_tree *, struct roa *, size_t *, 283 | size_t *); 284 | 285 | /* crl.c */ 286 | X509_CRL *crl_parse(const char *, const unsigned char *); 287 | void free_crl(struct crl *); 288 | 289 | /* Validation of our objects. */ 290 | 291 | struct auth *valid_ski_aki(const char *, struct auth_tree *, 292 | const char *, const char *); 293 | int valid_ta(const char *, struct auth_tree *, 294 | const struct cert *); 295 | int valid_cert(const char *, struct auth_tree *, 296 | const struct cert *); 297 | int valid_roa(const char *, struct auth_tree *, struct roa *); 298 | 299 | /* Working with CMS files. */ 300 | 301 | unsigned char *cms_parse_validate(X509 **, const char *, 302 | const char *, const unsigned char *, size_t *); 303 | 304 | /* Work with RFC 3779 IP addresses, prefixes, ranges. */ 305 | 306 | int ip_addr_afi_parse(const char *, const ASN1_OCTET_STRING *, 307 | enum afi *); 308 | int ip_addr_parse(const ASN1_BIT_STRING *, 309 | enum afi, const char *, struct ip_addr *); 310 | void ip_addr_print(const struct ip_addr *, enum afi, char *, 311 | size_t); 312 | void ip_addr_buffer(char **, size_t *, size_t *, 313 | const struct ip_addr *); 314 | void ip_addr_range_buffer(char **, size_t *, size_t *, 315 | const struct ip_addr_range *); 316 | void ip_addr_read(int, struct ip_addr *); 317 | void ip_addr_range_read(int, struct ip_addr_range *); 318 | int ip_addr_cmp(const struct ip_addr *, const struct ip_addr *); 319 | int ip_addr_check_overlap(const struct cert_ip *, 320 | const char *, const struct cert_ip *, size_t); 321 | int ip_addr_check_covered(enum afi, const unsigned char *, 322 | const unsigned char *, const struct cert_ip *, size_t); 323 | int ip_cert_compose_ranges(struct cert_ip *); 324 | void ip_roa_compose_ranges(struct roa_ip *); 325 | 326 | /* Work with RFC 3779 AS numbers, ranges. */ 327 | 328 | int as_id_parse(const ASN1_INTEGER *, uint32_t *); 329 | int as_check_overlap(const struct cert_as *, const char *, 330 | const struct cert_as *, size_t); 331 | int as_check_covered(uint32_t, uint32_t, 332 | const struct cert_as *, size_t); 333 | 334 | /* Rsync-specific. */ 335 | 336 | int rsync_uri_parse(const char **, size_t *, 337 | const char **, size_t *, const char **, size_t *, 338 | enum rtype *, const char *); 339 | 340 | /* Logging (though really used for OpenSSL errors). */ 341 | 342 | void cryptowarnx(const char *, ...) 343 | __attribute__((format(printf, 1, 2))); 344 | void cryptoerrx(const char *, ...) 345 | __attribute__((format(printf, 1, 2))) 346 | __attribute__((noreturn)); 347 | 348 | /* Functions for moving data between processes. */ 349 | 350 | void io_socket_blocking(int); 351 | void io_socket_nonblocking(int); 352 | void io_simple_buffer(char **, size_t *, size_t *, const void *, 353 | size_t); 354 | void io_simple_read(int, void *, size_t); 355 | void io_simple_write(int, const void *, size_t); 356 | void io_buf_buffer(char **, size_t *, size_t *, const void *, 357 | size_t); 358 | void io_buf_read_alloc(int, void **, size_t *); 359 | void io_buf_write(int, const void *, size_t); 360 | void io_str_buffer(char **, size_t *, size_t *, const char *); 361 | void io_str_read(int, char **); 362 | void io_str_write(int, const char *); 363 | 364 | /* X509 helpers. */ 365 | 366 | char *x509_get_aki_ext(X509_EXTENSION *, const char *); 367 | char *x509_get_ski_ext(X509_EXTENSION *, const char *); 368 | int x509_get_ski_aki(X509 *, const char *, char **, char **); 369 | char *x509_get_crl(X509 *, const char *); 370 | char *x509_crl_get_aki(X509_CRL *); 371 | 372 | /* Output! */ 373 | 374 | extern int outformats; 375 | #define FORMAT_OPENBGPD 0x01 376 | #define FORMAT_BIRD 0x02 377 | #define FORMAT_CSV 0x04 378 | #define FORMAT_JSON 0x08 379 | extern char* outputdir; 380 | 381 | int outputfiles(struct vrp_tree *v, const char *); 382 | int output_bgpd(FILE *, struct vrp_tree *, void *arg); 383 | int output_bird1v4(FILE *, struct vrp_tree *, void *arg); 384 | int output_bird1v6(FILE *, struct vrp_tree *, void *arg); 385 | int output_bird2(FILE *, struct vrp_tree *, void *arg); 386 | int output_csv(FILE *, struct vrp_tree *, void *arg); 387 | int output_json(FILE *, struct vrp_tree *, void *arg); 388 | 389 | void logx(const char *fmt, ...) 390 | __attribute__((format(printf, 1, 2))); 391 | 392 | #endif /* ! EXTERN_H */ 393 | -------------------------------------------------------------------------------- /roa.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "extern.h" 30 | 31 | /* 32 | * Parse results and data of the manifest file. 33 | */ 34 | struct parse { 35 | const char *fn; /* manifest file name */ 36 | struct roa *res; /* results */ 37 | }; 38 | 39 | /* 40 | * Parse IP address (ROAIPAddress), RFC 6482, section 3.3. 41 | * Returns zero on failure, non-zero on success. 42 | */ 43 | static int 44 | roa_parse_addr(const ASN1_OCTET_STRING *os, enum afi afi, struct parse *p) 45 | { 46 | ASN1_SEQUENCE_ANY *seq; 47 | const unsigned char *d = os->data; 48 | size_t dsz = os->length; 49 | int rc = 0; 50 | const ASN1_TYPE *t; 51 | const ASN1_INTEGER *maxlength = NULL; 52 | struct ip_addr addr; 53 | struct roa_ip *res; 54 | 55 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 56 | cryptowarnx("%s: RFC 6482 section 3.3: address: " 57 | "failed ASN.1 sequence parse", p->fn); 58 | goto out; 59 | } 60 | 61 | /* ROAIPAddress has the address and optional maxlength. */ 62 | 63 | if (sk_ASN1_TYPE_num(seq) != 1 && 64 | sk_ASN1_TYPE_num(seq) != 2) { 65 | warnx("%s: RFC 6482 section 3.3: adddress: " 66 | "want 1 or 2 elements, have %d", 67 | p->fn, sk_ASN1_TYPE_num(seq)); 68 | goto out; 69 | } 70 | 71 | t = sk_ASN1_TYPE_value(seq, 0); 72 | if (t->type != V_ASN1_BIT_STRING) { 73 | warnx("%s: RFC 6482 section 3.3: address: " 74 | "want ASN.1 bit string, have %s (NID %d)", 75 | p->fn, ASN1_tag2str(t->type), t->type); 76 | goto out; 77 | } 78 | if (!ip_addr_parse(t->value.bit_string, afi, p->fn, &addr)) { 79 | warnx("%s: RFC 6482 section 3.3: address: " 80 | "invalid IP address", p->fn); 81 | goto out; 82 | } 83 | 84 | /* 85 | * RFC 6482, section 3.3 doesn't ever actually state that the 86 | * maximum length can't be negative, but it needs to be >=0. 87 | */ 88 | 89 | if (sk_ASN1_TYPE_num(seq) == 2) { 90 | t = sk_ASN1_TYPE_value(seq, 1); 91 | if (t->type != V_ASN1_INTEGER) { 92 | warnx("%s: RFC 6482 section 3.1: maxLength: " 93 | "want ASN.1 integer, have %s (NID %d)", 94 | p->fn, ASN1_tag2str(t->type), t->type); 95 | goto out; 96 | } 97 | maxlength = t->value.integer; 98 | 99 | /* 100 | * It's safe to use ASN1_INTEGER_get() here 101 | * because we're not going to have more than signed 32 102 | * bit maximum of length. 103 | */ 104 | 105 | if (ASN1_INTEGER_get(maxlength) < 0) { 106 | warnx("%s: RFC 6482 section 3.2: maxLength: " 107 | "want positive integer, have %ld", 108 | p->fn, ASN1_INTEGER_get(maxlength)); 109 | goto out; 110 | } 111 | /* FIXME: maximum check. */ 112 | } 113 | 114 | p->res->ips = reallocarray(p->res->ips, 115 | p->res->ipsz + 1, sizeof(struct roa_ip)); 116 | if (p->res->ips == NULL) 117 | err(1, NULL); 118 | res = &p->res->ips[p->res->ipsz++]; 119 | memset(res, 0, sizeof(struct roa_ip)); 120 | 121 | res->addr = addr; 122 | res->afi = afi; 123 | res->maxlength = (maxlength == NULL) ? addr.prefixlen : 124 | ASN1_INTEGER_get(maxlength); 125 | ip_roa_compose_ranges(res); 126 | 127 | rc = 1; 128 | out: 129 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 130 | return rc; 131 | } 132 | 133 | /* 134 | * Parse IP address family, RFC 6482, section 3.3. 135 | * Returns zero on failure, non-zero on success. 136 | */ 137 | static int 138 | roa_parse_ipfam(const ASN1_OCTET_STRING *os, struct parse *p) 139 | { 140 | ASN1_SEQUENCE_ANY *seq, *sseq = NULL; 141 | const unsigned char *d = os->data; 142 | size_t dsz = os->length; 143 | int i, rc = 0; 144 | const ASN1_TYPE *t; 145 | enum afi afi; 146 | 147 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 148 | cryptowarnx("%s: RFC 6482 section 3.3: ROAIPAddressFamily: " 149 | "failed ASN.1 sequence parse", p->fn); 150 | goto out; 151 | } else if (sk_ASN1_TYPE_num(seq) != 2) { 152 | warnx("%s: RFC 6482 section 3.3: ROAIPAddressFamily: " 153 | "want 2 elements, have %d", 154 | p->fn, sk_ASN1_TYPE_num(seq)); 155 | goto out; 156 | } 157 | 158 | t = sk_ASN1_TYPE_value(seq, 0); 159 | if (t->type != V_ASN1_OCTET_STRING) { 160 | warnx("%s: RFC 6482 section 3.3: addressFamily: " 161 | "want ASN.1 octet string, have %s (NID %d)", 162 | p->fn, ASN1_tag2str(t->type), t->type); 163 | goto out; 164 | } 165 | if (!ip_addr_afi_parse(p->fn, t->value.octet_string, &afi)) { 166 | warnx("%s: RFC 6482 section 3.3: addressFamily: " 167 | "invalid", p->fn); 168 | goto out; 169 | } 170 | 171 | t = sk_ASN1_TYPE_value(seq, 1); 172 | if (t->type != V_ASN1_SEQUENCE) { 173 | warnx("%s: RFC 6482 section 3.3: addresses: " 174 | "want ASN.1 sequence, have %s (NID %d)", 175 | p->fn, ASN1_tag2str(t->type), t->type); 176 | goto out; 177 | } 178 | 179 | d = t->value.octet_string->data; 180 | dsz = t->value.octet_string->length; 181 | 182 | if ((sseq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 183 | cryptowarnx("%s: RFC 6482 section 3.3: addresses: " 184 | "failed ASN.1 sequence parse", p->fn); 185 | goto out; 186 | } 187 | 188 | for (i = 0; i < sk_ASN1_TYPE_num(sseq); i++) { 189 | t = sk_ASN1_TYPE_value(sseq, i); 190 | if (t->type != V_ASN1_SEQUENCE) { 191 | warnx("%s: RFC 6482 section 3.3: ROAIPAddress: " 192 | "want ASN.1 sequence, have %s (NID %d)", 193 | p->fn, ASN1_tag2str(t->type), t->type); 194 | goto out; 195 | } 196 | if (!roa_parse_addr(t->value.octet_string, afi, p)) 197 | goto out; 198 | } 199 | 200 | rc = 1; 201 | out: 202 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 203 | sk_ASN1_TYPE_pop_free(sseq, ASN1_TYPE_free); 204 | return rc; 205 | } 206 | 207 | /* 208 | * Parse IP blocks, RFC 6482, section 3.3. 209 | * Returns zero on failure, non-zero on success. 210 | */ 211 | static int 212 | roa_parse_ipblocks(const ASN1_OCTET_STRING *os, struct parse *p) 213 | { 214 | ASN1_SEQUENCE_ANY *seq; 215 | const unsigned char *d = os->data; 216 | size_t dsz = os->length; 217 | int i, rc = 0; 218 | const ASN1_TYPE *t; 219 | 220 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 221 | cryptowarnx("%s: RFC 6482 section 3.3: ipAddrBlocks: " 222 | "failed ASN.1 sequence parse", p->fn); 223 | goto out; 224 | } 225 | 226 | for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 227 | t = sk_ASN1_TYPE_value(seq, i); 228 | if (t->type != V_ASN1_SEQUENCE) { 229 | warnx("%s: RFC 6482 section 3.3: ROAIPAddressFamily: " 230 | "want ASN.1 sequence, have %s (NID %d)", 231 | p->fn, ASN1_tag2str(t->type), t->type); 232 | goto out; 233 | } else if (!roa_parse_ipfam(t->value.octet_string, p)) 234 | goto out; 235 | } 236 | 237 | rc = 1; 238 | out: 239 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 240 | return rc; 241 | } 242 | 243 | /* 244 | * Parses the eContent section of an ROA file, RFC 6482, section 3. 245 | * Returns zero on failure, non-zero on success. 246 | */ 247 | static int 248 | roa_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 249 | { 250 | ASN1_SEQUENCE_ANY *seq; 251 | int i = 0, rc = 0, sz; 252 | const ASN1_TYPE *t; 253 | 254 | /* RFC 6482, section 3. */ 255 | 256 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 257 | cryptowarnx("%s: RFC 6482 section 3: RouteOriginAttestation: " 258 | "failed ASN.1 sequence parse", p->fn); 259 | goto out; 260 | } 261 | 262 | if ((sz = sk_ASN1_TYPE_num(seq)) != 2 && sz != 3) { 263 | warnx("%s: RFC 6482 section 3: RouteOriginAttestation: " 264 | "want 2 or 3 elements, have %d", 265 | p->fn, sk_ASN1_TYPE_num(seq)); 266 | goto out; 267 | } 268 | 269 | /* RFC 6482, section 3.1. */ 270 | 271 | if (sz == 3) { 272 | t = sk_ASN1_TYPE_value(seq, i++); 273 | 274 | /* 275 | * This check with ASN1_INTEGER_get() is fine since 276 | * we're looking for a value of zero anyway, so any 277 | * overflowing number will be definition be wrong. 278 | */ 279 | 280 | if (t->type != V_ASN1_INTEGER) { 281 | warnx("%s: RFC 6482 section 3.1: version: " 282 | "want ASN.1 integer, have %s (NID %d)", 283 | p->fn, ASN1_tag2str(t->type), t->type); 284 | goto out; 285 | } else if (ASN1_INTEGER_get(t->value.integer) != 0) { 286 | warnx("%s: RFC 6482 section 3.1: version: " 287 | "want version 0, have %ld", 288 | p->fn, ASN1_INTEGER_get(t->value.integer)); 289 | goto out; 290 | } 291 | } 292 | 293 | /* 294 | * RFC 6482, section 3.2. 295 | * It doesn't ever actually state that AS numbers can't be 296 | * negative, but...? 297 | */ 298 | 299 | t = sk_ASN1_TYPE_value(seq, i++); 300 | if (t->type != V_ASN1_INTEGER) { 301 | warnx("%s: RFC 6482 section 3.2: asID: " 302 | "want ASN.1 integer, have %s (NID %d)", 303 | p->fn, ASN1_tag2str(t->type), t->type); 304 | goto out; 305 | } else if (!as_id_parse(t->value.integer, &p->res->asid)) { 306 | warnx("%s: RFC 6482 section 3.2: asID: " 307 | "malformed AS identifier", p->fn); 308 | goto out; 309 | } 310 | 311 | /* RFC 6482, section 3.3. */ 312 | 313 | t = sk_ASN1_TYPE_value(seq, i++); 314 | if (t->type != V_ASN1_SEQUENCE) { 315 | warnx("%s: RFC 6482 section 3.3: ipAddrBlocks: " 316 | "want ASN.1 sequence, have %s (NID %d)", 317 | p->fn, ASN1_tag2str(t->type), t->type); 318 | goto out; 319 | } else if (!roa_parse_ipblocks(t->value.octet_string, p)) 320 | goto out; 321 | 322 | rc = 1; 323 | out: 324 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 325 | return rc; 326 | } 327 | 328 | /* 329 | * Parse a full RFC 6482 file with a SHA256 digest "dgst" and signed by 330 | * the certificate "cacert" (the latter two are optional and may be 331 | * passed as NULL to disable). 332 | * Returns the ROA or NULL if the document was malformed. 333 | */ 334 | struct roa * 335 | roa_parse(X509 **x509, const char *fn, const unsigned char *dgst) 336 | { 337 | struct parse p; 338 | size_t cmsz; 339 | unsigned char *cms; 340 | int rc = 0; 341 | 342 | memset(&p, 0, sizeof(struct parse)); 343 | p.fn = fn; 344 | 345 | /* OID from section 2, RFC 6482. */ 346 | 347 | cms = cms_parse_validate(x509, fn, 348 | "1.2.840.113549.1.9.16.1.24", dgst, &cmsz); 349 | if (cms == NULL) 350 | return NULL; 351 | 352 | if ((p.res = calloc(1, sizeof(struct roa))) == NULL) 353 | err(1, NULL); 354 | if (!x509_get_ski_aki(*x509, fn, &p.res->ski, &p.res->aki)) 355 | goto out; 356 | if (!roa_parse_econtent(cms, cmsz, &p)) 357 | goto out; 358 | 359 | rc = 1; 360 | out: 361 | if (rc == 0) { 362 | roa_free(p.res); 363 | p.res = NULL; 364 | X509_free(*x509); 365 | *x509 = NULL; 366 | } 367 | free(cms); 368 | return p.res; 369 | 370 | } 371 | 372 | /* 373 | * Free an ROA pointer. 374 | * Safe to call with NULL. 375 | */ 376 | void 377 | roa_free(struct roa *p) 378 | { 379 | 380 | if (p == NULL) 381 | return; 382 | free(p->aki); 383 | free(p->ski); 384 | free(p->ips); 385 | free(p->tal); 386 | free(p); 387 | } 388 | 389 | /* 390 | * Serialise parsed ROA content. 391 | * See roa_read() for reader. 392 | */ 393 | void 394 | roa_buffer(char **b, size_t *bsz, size_t *bmax, const struct roa *p) 395 | { 396 | size_t i; 397 | 398 | io_simple_buffer(b, bsz, bmax, &p->valid, sizeof(int)); 399 | io_simple_buffer(b, bsz, bmax, &p->asid, sizeof(uint32_t)); 400 | io_simple_buffer(b, bsz, bmax, &p->ipsz, sizeof(size_t)); 401 | 402 | for (i = 0; i < p->ipsz; i++) { 403 | io_simple_buffer(b, bsz, bmax, 404 | &p->ips[i].afi, sizeof(enum afi)); 405 | io_simple_buffer(b, bsz, bmax, 406 | &p->ips[i].maxlength, sizeof(size_t)); 407 | io_simple_buffer(b, bsz, bmax, 408 | p->ips[i].min, sizeof(p->ips[i].min)); 409 | io_simple_buffer(b, bsz, bmax, 410 | p->ips[i].max, sizeof(p->ips[i].max)); 411 | ip_addr_buffer(b, bsz, bmax, &p->ips[i].addr); 412 | } 413 | 414 | io_str_buffer(b, bsz, bmax, p->aki); 415 | io_str_buffer(b, bsz, bmax, p->ski); 416 | io_str_buffer(b, bsz, bmax, p->tal); 417 | } 418 | 419 | /* 420 | * Read parsed ROA content from descriptor. 421 | * See roa_buffer() for writer. 422 | * Result must be passed to roa_free(). 423 | */ 424 | struct roa * 425 | roa_read(int fd) 426 | { 427 | struct roa *p; 428 | size_t i; 429 | 430 | if ((p = calloc(1, sizeof(struct roa))) == NULL) 431 | err(1, NULL); 432 | 433 | io_simple_read(fd, &p->valid, sizeof(int)); 434 | io_simple_read(fd, &p->asid, sizeof(uint32_t)); 435 | io_simple_read(fd, &p->ipsz, sizeof(size_t)); 436 | 437 | if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL) 438 | err(1, NULL); 439 | 440 | for (i = 0; i < p->ipsz; i++) { 441 | io_simple_read(fd, &p->ips[i].afi, sizeof(enum afi)); 442 | io_simple_read(fd, &p->ips[i].maxlength, sizeof(size_t)); 443 | io_simple_read(fd, &p->ips[i].min, sizeof(p->ips[i].min)); 444 | io_simple_read(fd, &p->ips[i].max, sizeof(p->ips[i].max)); 445 | ip_addr_read(fd, &p->ips[i].addr); 446 | } 447 | 448 | io_str_read(fd, &p->aki); 449 | io_str_read(fd, &p->ski); 450 | io_str_read(fd, &p->tal); 451 | return p; 452 | } 453 | 454 | /* 455 | * Add each IP address in the ROA into the VRP tree. 456 | * Updates "vrps" to be the number of VRPs and "uniqs" to be the unique 457 | * number of addresses. 458 | */ 459 | void 460 | roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, size_t *vrps, 461 | size_t *uniqs) 462 | { 463 | struct vrp *v; 464 | size_t i; 465 | 466 | for (i = 0; i < roa->ipsz; i++) { 467 | if ((v = malloc(sizeof(*v))) == NULL) 468 | err(1, NULL); 469 | v->afi = roa->ips[i].afi; 470 | v->addr = roa->ips[i].addr; 471 | v->maxlength = roa->ips[i].maxlength; 472 | v->asid = roa->asid; 473 | if ((v->tal = strdup(roa->tal)) == NULL) 474 | err(1, NULL); 475 | if (RB_INSERT(vrp_tree, tree, v) == NULL) 476 | (*uniqs)++; 477 | else /* already exists */ 478 | free(v); 479 | (*vrps)++; 480 | } 481 | } 482 | 483 | static inline int 484 | vrpcmp(struct vrp *a, struct vrp *b) 485 | { 486 | int rv; 487 | 488 | if (a->afi > b->afi) 489 | return 1; 490 | if (a->afi < b->afi) 491 | return -1; 492 | switch (a->afi) { 493 | case AFI_IPV4: 494 | rv = memcmp(&a->addr.addr, &b->addr.addr, 4); 495 | if (rv) 496 | return rv; 497 | break; 498 | case AFI_IPV6: 499 | rv = memcmp(&a->addr.addr, &b->addr.addr, 16); 500 | if (rv) 501 | return rv; 502 | break; 503 | } 504 | /* a smaller prefixlen is considered bigger, e.g. /8 vs /10 */ 505 | if (a->addr.prefixlen < b->addr.prefixlen) 506 | return 1; 507 | if (a->addr.prefixlen > b->addr.prefixlen) 508 | return -1; 509 | if (a->maxlength < b->maxlength) 510 | return 1; 511 | if (a->maxlength > b->maxlength) 512 | return -1; 513 | 514 | if (a->asid > b->asid) 515 | return 1; 516 | if (a->asid < b->asid) 517 | return -1; 518 | 519 | return 0; 520 | } 521 | 522 | RB_GENERATE(vrp_tree, vrp, entry, vrpcmp); 523 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #if TEST___PROGNAME 2 | int 3 | main(void) 4 | { 5 | extern char *__progname; 6 | 7 | return !__progname; 8 | } 9 | #endif /* TEST___PROGNAME */ 10 | #if TEST_ARC4RANDOM 11 | #include 12 | 13 | int 14 | main(void) 15 | { 16 | return (arc4random() + 1) ? 0 : 1; 17 | } 18 | #endif /* TEST_ARC4RANDOM */ 19 | #if TEST_B64_NTOP 20 | #include 21 | #include 22 | 23 | int 24 | main(void) 25 | { 26 | const char *src = "hello world"; 27 | char output[1024]; 28 | 29 | return b64_ntop((const unsigned char *)src, 11, output, sizeof(output)) > 0 ? 0 : 1; 30 | } 31 | #endif /* TEST_B64_NTOP */ 32 | #if TEST_CAPSICUM 33 | #include 34 | 35 | int 36 | main(void) 37 | { 38 | cap_enter(); 39 | return(0); 40 | } 41 | #endif /* TEST_CAPSICUM */ 42 | #if TEST_CRYPT 43 | #if defined(__linux__) 44 | # define _GNU_SOURCE /* old glibc */ 45 | # define _DEFAULT_SOURCE /* new glibc */ 46 | #endif 47 | #if defined(__sun) 48 | # ifndef _XOPEN_SOURCE /* SunOS already defines */ 49 | # define _XOPEN_SOURCE /* XPGx */ 50 | # endif 51 | # define _XOPEN_SOURCE_EXTENDED 1 /* XPG4v2 */ 52 | # ifndef __EXTENSIONS__ /* SunOS already defines */ 53 | # define __EXTENSIONS__ /* reallocarray, etc. */ 54 | # endif 55 | #endif 56 | #include 57 | 58 | int main(void) 59 | { 60 | char *v; 61 | 62 | v = crypt("this_is_a_key", "123455"); 63 | return v == NULL; 64 | } 65 | #endif /* TEST_CRYPT */ 66 | #if TEST_ENDIAN_H 67 | #ifdef __linux__ 68 | # define _DEFAULT_SOURCE 69 | #endif 70 | #include 71 | 72 | int 73 | main(void) 74 | { 75 | return !htole32(23); 76 | } 77 | #endif /* TEST_ENDIAN_H */ 78 | #if TEST_ERR 79 | /* 80 | * Copyright (c) 2015 Ingo Schwarze 81 | * 82 | * Permission to use, copy, modify, and distribute this software for any 83 | * purpose with or without fee is hereby granted, provided that the above 84 | * copyright notice and this permission notice appear in all copies. 85 | * 86 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 87 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 88 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 89 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 90 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 91 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 92 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 93 | */ 94 | 95 | #include 96 | 97 | int 98 | main(void) 99 | { 100 | warnx("%d. warnx", 1); 101 | warn("%d. warn", 2); 102 | err(0, "%d. err", 3); 103 | /* NOTREACHED */ 104 | return 1; 105 | } 106 | #endif /* TEST_ERR */ 107 | #if TEST_EXPLICIT_BZERO 108 | #include 109 | 110 | int 111 | main(void) 112 | { 113 | char foo[10]; 114 | 115 | explicit_bzero(foo, sizeof(foo)); 116 | return(0); 117 | } 118 | #endif /* TEST_EXPLICIT_BZERO */ 119 | #if TEST_FTS 120 | #include 121 | #include 122 | #include 123 | 124 | int 125 | main(void) 126 | { 127 | const char *argv[2]; 128 | FTS *ftsp; 129 | FTSENT *entry; 130 | 131 | argv[0] = "."; 132 | argv[1] = (char *)NULL; 133 | 134 | ftsp = fts_open((char * const *)argv, 135 | FTS_PHYSICAL | FTS_NOCHDIR, NULL); 136 | 137 | if (ftsp == NULL) 138 | return 1; 139 | 140 | entry = fts_read(ftsp); 141 | 142 | if (entry == NULL) 143 | return 1; 144 | 145 | if (fts_set(ftsp, entry, FTS_SKIP) != 0) 146 | return 1; 147 | 148 | if (fts_close(ftsp) != 0) 149 | return 1; 150 | 151 | return 0; 152 | } 153 | #endif /* TEST_FTS */ 154 | #if TEST_GETEXECNAME 155 | #include 156 | 157 | int 158 | main(void) 159 | { 160 | const char * progname; 161 | 162 | progname = getexecname(); 163 | return progname == NULL; 164 | } 165 | #endif /* TEST_GETEXECNAME */ 166 | #if TEST_GETPROGNAME 167 | #include 168 | 169 | int 170 | main(void) 171 | { 172 | const char * progname; 173 | 174 | progname = getprogname(); 175 | return progname == NULL; 176 | } 177 | #endif /* TEST_GETPROGNAME */ 178 | #if TEST_INFTIM 179 | /* 180 | * Linux doesn't (always?) have this. 181 | */ 182 | 183 | #include 184 | #include 185 | 186 | int 187 | main(void) 188 | { 189 | printf("INFTIM is defined to be %ld\n", (long)INFTIM); 190 | return 0; 191 | } 192 | #endif /* TEST_INFTIM */ 193 | #if TEST_LIB_SOCKET 194 | #include 195 | 196 | int 197 | main(void) 198 | { 199 | int fds[2], c; 200 | 201 | c = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); 202 | return c == -1; 203 | } 204 | #endif /* TEST_LIB_SOCKET */ 205 | #if TEST_MD5 206 | #include 207 | #include 208 | 209 | int main(void) 210 | { 211 | MD5_CTX ctx; 212 | char result[MD5_DIGEST_STRING_LENGTH]; 213 | 214 | MD5Init(&ctx); 215 | MD5Update(&ctx, (const unsigned char *)"abcd", 4); 216 | MD5End(&ctx, result); 217 | 218 | return 0; 219 | } 220 | #endif /* TEST_MD5 */ 221 | #if TEST_MEMMEM 222 | #define _GNU_SOURCE 223 | #include 224 | 225 | int 226 | main(void) 227 | { 228 | char *a = memmem("hello, world", strlen("hello, world"), "world", strlen("world")); 229 | return(NULL == a); 230 | } 231 | #endif /* TEST_MEMMEM */ 232 | #if TEST_MEMRCHR 233 | #if defined(__linux__) || defined(__MINT__) 234 | #define _GNU_SOURCE /* See test-*.c what needs this. */ 235 | #endif 236 | #include 237 | 238 | int 239 | main(void) 240 | { 241 | const char *buf = "abcdef"; 242 | void *res; 243 | 244 | res = memrchr(buf, 'a', strlen(buf)); 245 | return(NULL == res ? 1 : 0); 246 | } 247 | #endif /* TEST_MEMRCHR */ 248 | #if TEST_MEMSET_S 249 | #include 250 | 251 | int main(void) 252 | { 253 | char buf[10]; 254 | memset_s(buf, 0, 'c', sizeof(buf)); 255 | return 0; 256 | } 257 | #endif /* TEST_MEMSET_S */ 258 | #if TEST_MKFIFOAT 259 | #include 260 | #include 261 | 262 | int main(void) { 263 | mkfifoat(AT_FDCWD, "this/path/should/not/exist", 0600); 264 | return 0; 265 | } 266 | #endif /* TEST_MKFIFOAT */ 267 | #if TEST_MKNODAT 268 | #include 269 | #include 270 | 271 | int main(void) { 272 | mknodat(AT_FDCWD, "this/path/should/not/exist", S_IFIFO | 0600, 0); 273 | return 0; 274 | } 275 | #endif /* TEST_MKNODAT */ 276 | #if TEST_OSBYTEORDER_H 277 | #include 278 | 279 | int 280 | main(void) 281 | { 282 | return !OSSwapHostToLittleInt32(23); 283 | } 284 | #endif /* TEST_OSBYTEORDER_H */ 285 | #if TEST_PATH_MAX 286 | /* 287 | * POSIX allows PATH_MAX to not be defined, see 288 | * http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html; 289 | * the GNU Hurd is an example of a system not having it. 290 | * 291 | * Arguably, it would be better to test sysconf(_SC_PATH_MAX), 292 | * but since the individual *.c files include "config.h" before 293 | * , overriding an excessive value of PATH_MAX from 294 | * "config.h" is impossible anyway, so for now, the simplest 295 | * fix is to provide a value only on systems not having any. 296 | * So far, we encountered no system defining PATH_MAX to an 297 | * impractically large value, even though POSIX explicitly 298 | * allows that. 299 | * 300 | * The real fix would be to replace all static buffers of size 301 | * PATH_MAX by dynamically allocated buffers. But that is 302 | * somewhat intrusive because it touches several files and 303 | * because it requires changing struct mlink in mandocdb.c. 304 | * So i'm postponing that for now. 305 | */ 306 | 307 | #include 308 | #include 309 | 310 | int 311 | main(void) 312 | { 313 | printf("PATH_MAX is defined to be %ld\n", (long)PATH_MAX); 314 | return 0; 315 | } 316 | #endif /* TEST_PATH_MAX */ 317 | #if TEST_PLEDGE 318 | #include 319 | 320 | int 321 | main(void) 322 | { 323 | return !!pledge("stdio", NULL); 324 | } 325 | #endif /* TEST_PLEDGE */ 326 | #if TEST_PROGRAM_INVOCATION_SHORT_NAME 327 | #define _GNU_SOURCE /* See feature_test_macros(7) */ 328 | #include 329 | 330 | int 331 | main(void) 332 | { 333 | 334 | return !program_invocation_short_name; 335 | } 336 | #endif /* TEST_PROGRAM_INVOCATION_SHORT_NAME */ 337 | #if TEST_READPASSPHRASE 338 | #include 339 | #include 340 | 341 | int 342 | main(void) 343 | { 344 | return !!readpassphrase("prompt: ", NULL, 0, 0); 345 | } 346 | #endif /* TEST_READPASSPHRASE */ 347 | #if TEST_REALLOCARRAY 348 | #ifdef __NetBSD__ 349 | # define _OPENBSD_SOURCE 350 | #endif 351 | #include 352 | 353 | int 354 | main(void) 355 | { 356 | return !reallocarray(NULL, 2, 2); 357 | } 358 | #endif /* TEST_REALLOCARRAY */ 359 | #if TEST_RECALLOCARRAY 360 | #include 361 | 362 | int 363 | main(void) 364 | { 365 | return !recallocarray(NULL, 0, 2, 2); 366 | } 367 | #endif /* TEST_RECALLOCARRAY */ 368 | #if TEST_SANDBOX_INIT 369 | #include 370 | 371 | int 372 | main(void) 373 | { 374 | char *ep; 375 | int rc; 376 | 377 | rc = sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, &ep); 378 | if (-1 == rc) 379 | sandbox_free_error(ep); 380 | return(-1 == rc); 381 | } 382 | #endif /* TEST_SANDBOX_INIT */ 383 | #if TEST_SECCOMP_FILTER 384 | #include 385 | #include 386 | #include 387 | 388 | int 389 | main(void) 390 | { 391 | 392 | prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0); 393 | return(EFAULT == errno ? 0 : 1); 394 | } 395 | #endif /* TEST_SECCOMP_FILTER */ 396 | #if TEST_SETRESGID 397 | #define _GNU_SOURCE /* linux */ 398 | #include 399 | #include 400 | 401 | int 402 | main(void) 403 | { 404 | return setresgid(-1, -1, -1) == -1; 405 | } 406 | #endif /* TEST_SETRESGID */ 407 | #if TEST_SETRESUID 408 | #define _GNU_SOURCE /* linux */ 409 | #include 410 | #include 411 | 412 | int 413 | main(void) 414 | { 415 | return setresuid(-1, -1, -1) == -1; 416 | } 417 | #endif /* TEST_SETRESUID */ 418 | #if TEST_SHA2_H 419 | #include 420 | #include 421 | 422 | int main(void) 423 | { 424 | SHA2_CTX ctx; 425 | char result[SHA256_DIGEST_STRING_LENGTH]; 426 | 427 | SHA256Init(&ctx); 428 | SHA256Update(&ctx, (const unsigned char *)"abcd", 4); 429 | SHA256End(&ctx, result); 430 | 431 | return 0; 432 | } 433 | #endif /* TEST_SHA2_H */ 434 | #if TEST_SOCK_NONBLOCK 435 | /* 436 | * Linux doesn't (always?) have this. 437 | */ 438 | 439 | #include 440 | 441 | int 442 | main(void) 443 | { 444 | int fd[2]; 445 | socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0, fd); 446 | return 0; 447 | } 448 | #endif /* TEST_SOCK_NONBLOCK */ 449 | #if TEST_STATIC 450 | int 451 | main(void) 452 | { 453 | return 0; /* not meant to do anything */ 454 | } 455 | #endif /* TEST_STATIC */ 456 | #if TEST_STRLCAT 457 | #include 458 | 459 | int 460 | main(void) 461 | { 462 | char buf[3] = "a"; 463 | return ! (strlcat(buf, "b", sizeof(buf)) == 2 && 464 | buf[0] == 'a' && buf[1] == 'b' && buf[2] == '\0'); 465 | } 466 | #endif /* TEST_STRLCAT */ 467 | #if TEST_STRLCPY 468 | #include 469 | 470 | int 471 | main(void) 472 | { 473 | char buf[2] = ""; 474 | return ! (strlcpy(buf, "a", sizeof(buf)) == 1 && 475 | buf[0] == 'a' && buf[1] == '\0'); 476 | } 477 | #endif /* TEST_STRLCPY */ 478 | #if TEST_STRNDUP 479 | #include 480 | 481 | int 482 | main(void) 483 | { 484 | const char *foo = "bar"; 485 | char *baz; 486 | 487 | baz = strndup(foo, 1); 488 | return(0 != strcmp(baz, "b")); 489 | } 490 | #endif /* TEST_STRNDUP */ 491 | #if TEST_STRNLEN 492 | #include 493 | 494 | int 495 | main(void) 496 | { 497 | const char *foo = "bar"; 498 | size_t sz; 499 | 500 | sz = strnlen(foo, 1); 501 | return(1 != sz); 502 | } 503 | #endif /* TEST_STRNLEN */ 504 | #if TEST_STRTONUM 505 | /* 506 | * Copyright (c) 2015 Ingo Schwarze 507 | * 508 | * Permission to use, copy, modify, and distribute this software for any 509 | * purpose with or without fee is hereby granted, provided that the above 510 | * copyright notice and this permission notice appear in all copies. 511 | * 512 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 513 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 514 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 515 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 516 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 517 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 518 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 519 | */ 520 | #ifdef __NetBSD__ 521 | # define _OPENBSD_SOURCE 522 | #endif 523 | #include 524 | 525 | int 526 | main(void) 527 | { 528 | const char *errstr; 529 | 530 | if (strtonum("1", 0, 2, &errstr) != 1) 531 | return 1; 532 | if (errstr != NULL) 533 | return 2; 534 | if (strtonum("1x", 0, 2, &errstr) != 0) 535 | return 3; 536 | if (errstr == NULL) 537 | return 4; 538 | if (strtonum("2", 0, 1, &errstr) != 0) 539 | return 5; 540 | if (errstr == NULL) 541 | return 6; 542 | if (strtonum("0", 1, 2, &errstr) != 0) 543 | return 7; 544 | if (errstr == NULL) 545 | return 8; 546 | return 0; 547 | } 548 | #endif /* TEST_STRTONUM */ 549 | #if TEST_SYS_BYTEORDER_H 550 | #include 551 | 552 | int 553 | main(void) 554 | { 555 | return !LE_32(23); 556 | } 557 | #endif /* TEST_SYS_BYTEORDER_H */ 558 | #if TEST_SYS_ENDIAN_H 559 | #include 560 | 561 | int 562 | main(void) 563 | { 564 | return !htole32(23); 565 | } 566 | #endif /* TEST_SYS_ENDIAN_H */ 567 | #if TEST_SYS_MKDEV_H 568 | #include 569 | #include 570 | 571 | int 572 | main(void) 573 | { 574 | return !minor(0); 575 | } 576 | #endif /* TEST_SYS_MKDEV_H */ 577 | #if TEST_SYS_QUEUE 578 | #include 579 | #include 580 | 581 | struct foo { 582 | int bar; 583 | TAILQ_ENTRY(foo) entries; 584 | }; 585 | 586 | TAILQ_HEAD(fooq, foo); 587 | 588 | int 589 | main(void) 590 | { 591 | struct fooq foo_q; 592 | struct foo *p, *tmp; 593 | int i = 0; 594 | 595 | TAILQ_INIT(&foo_q); 596 | 597 | /* 598 | * Use TAILQ_FOREACH_SAFE because some systems (e.g., Linux) 599 | * have TAILQ_FOREACH but not the safe variant. 600 | */ 601 | 602 | TAILQ_FOREACH_SAFE(p, &foo_q, entries, tmp) 603 | p->bar = i++; 604 | return 0; 605 | } 606 | #endif /* TEST_SYS_QUEUE */ 607 | #if TEST_SYS_SYSMACROS_H 608 | #include 609 | 610 | int 611 | main(void) 612 | { 613 | return !minor(0); 614 | } 615 | #endif /* TEST_SYS_SYSMACROS_H */ 616 | #if TEST_SYS_TREE 617 | #include 618 | #include 619 | 620 | struct node { 621 | RB_ENTRY(node) entry; 622 | int i; 623 | }; 624 | 625 | static int 626 | intcmp(struct node *e1, struct node *e2) 627 | { 628 | return (e1->i < e2->i ? -1 : e1->i > e2->i); 629 | } 630 | 631 | RB_HEAD(inttree, node) head = RB_INITIALIZER(&head); 632 | RB_PROTOTYPE(inttree, node, entry, intcmp) 633 | RB_GENERATE(inttree, node, entry, intcmp) 634 | 635 | int testdata[] = { 636 | 20, 16, 17, 13, 3, 6, 1, 8, 2, 4 637 | }; 638 | 639 | int 640 | main(void) 641 | { 642 | size_t i; 643 | struct node *n; 644 | 645 | for (i = 0; i < sizeof(testdata) / sizeof(testdata[0]); i++) { 646 | if ((n = malloc(sizeof(struct node))) == NULL) 647 | return 1; 648 | n->i = testdata[i]; 649 | RB_INSERT(inttree, &head, n); 650 | } 651 | 652 | return 0; 653 | } 654 | 655 | #endif /* TEST_SYS_TREE */ 656 | #if TEST_UNVEIL 657 | #include 658 | 659 | int 660 | main(void) 661 | { 662 | return -1 != unveil(NULL, NULL); 663 | } 664 | #endif /* TEST_UNVEIL */ 665 | #if TEST_WAIT_ANY 666 | #include 667 | 668 | int 669 | main(void) 670 | { 671 | int st; 672 | 673 | return waitpid(WAIT_ANY, &st, WNOHANG) != -1; 674 | } 675 | #endif /* TEST_WAIT_ANY */ 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **This system has been merged into OpenBSD base. If you'd like to 4 | contribute to rpki-client, please mail your patches to tech@openbsd.org. 5 | This repository is simply the OpenBSD version plus some glue for 6 | portability. It is updated from time to time to keep in sync with 7 | OpenBSD's version.** 8 | 9 | **rpki-client** is an implementation of RPKI (resource public key 10 | infrastructure) described in [RFC 11 | 6480](https://tools.ietf.org/html/rfc6480). 12 | It implements the *client* side of RPKI, which is responsible for 13 | downloading and validating route origin statements. 14 | For usage, please read [rpki-client(8)](rpki-client.8). 15 | 16 | The design focus of **rpki-client** is simplicity and security. 17 | To wit, it implements RPKI components necessary for validating route 18 | statements and omits superfluities (such as, for example, which X509 19 | certificate sections must be labelled "Critical"). 20 | 21 | The system runs on most modern UNIX operating systems with the the 22 | [OpenSSL](https://www.openssl.org) external library installed, version 23 | 1.1.1c and above. 24 | Beyond that it requires only 25 | [BSD make](https://man.openbsd.org/make), usually called `bmake` on systems 26 | where [GNU make](https://www.gnu.org/software/make) is the default. 27 | See [Portability](#portability) for details. 28 | 29 | The reference operating system is [OpenBSD](https://www.openbsd.org), 30 | which we strongly suggest for all installations for security reasons. 31 | It will support [LibreSSL](https://www.libressl.org/) once the library 32 | gains CMS parsing. 33 | 34 | It has been tested on OpenBSD, FreeBSD, Linux (glibc and musl), and 35 | IllumOS. Solaris, NetBSD, and Mac OS X require some portability work. 36 | 37 | See the [TODO](TODO.md) file for open questions regarding RPKI operation 38 | in general. 39 | 40 | ## Project background 41 | 42 | **rpki-client** is written as part of the 43 | [rpki-client(8)](https://medium.com/@jobsnijders/a-proposal-for-a-new-rpki-validator-openbsd-rpki-client-1-15b74e7a3f65) 44 | project, an 45 | [RPKI](https://en.wikipedia.org/wiki/Resource_Public_Key_Infrastructure) 46 | validator for OpenBSD. 47 | It was funded by [NetNod](https://www.netnod.se), 48 | [IIS.SE](https://www.iis.se), [SUNET](https://www.sunet.se) and 49 | [6connect](https://www.6connect.com). 50 | 51 | # Installation 52 | 53 | First, you'll need a recent [OpenSSL](https://www.openssl.org/) library 54 | (version 1.1.1c and above) on your operating system. At this point, just 55 | run the following. The installation rule will install into `PREFIX`, 56 | defaulting to */usr/local*, which you may override: 57 | 58 | ``` 59 | % ./configure PREFIX=/opt/local 60 | % make 61 | # make install 62 | ``` 63 | 64 | If your `pkg-config` for OpenSSL 1.1.1c and above isn't `openssl` (or 65 | `eopenssl11` for OpenBSD), pass the proper name as a build option. 66 | For example, using `openssl111`: 67 | 68 | ``` 69 | % ./configure 70 | % make PKG_OPENSSL=openssl111 71 | ``` 72 | 73 | This value may also be hard-coded in the 74 | [Makefile](https://github.com/kristapsdz/rpki-client/blob/master/Makefile). 75 | **If your system consistently uses a different package name, please 76 | raise an issue to let us know.** 77 | 78 | Next, you'll need the */var/cache/rpki-client* directory in place. 79 | It must be writable by the operator of **rpki-client**. The default 80 | output directory is */var/db/rpki-client*, which must also be writable 81 | (if not overriden). 82 | 83 | You'll also need TAL ("trust anchor locator") files. 84 | There are some in the [tals](tals) directory of this system, but you can 85 | download them on your own. 86 | For default operation, load these into */etc/rpki*. 87 | 88 | These default paths are set as 89 | `RPKI_PATH_BASE_DIR`, `RPKI_PATH_OUT_DIR`, and `RPKI_TAL_DIR`, 90 | respectively, in the 91 | [Makefile](https://github.com/kristapsdz/rpki-client/blob/master/Makefile). 92 | Alternatively, override the variables when invoking `make`, e.g., 93 | 94 | ``` 95 | % make RPKI_TAL_DIR=/etc/tals 96 | ``` 97 | 98 | You'll also need [openrsync(1)](https://man.openbsd.org/openrsync.1) or 99 | [rsync](https://rsync.samba.org/) as specified with the **-e** argument. 100 | To hardcode an alternate rsync implementation, set the 101 | `RPKI_RSYNC_COMMAND` value in the 102 | [Makefile](https://github.com/kristapsdz/rpki-client/blob/master/Makefile). 103 | 104 | In the following, the first uses a custom TAL file, while the second 105 | loads all TAL files from their default location. Output for the first 106 | is written into *./openbgpd* and */var/db/rpki-client/openbgpd* for the 107 | second. 108 | 109 | ``` 110 | % ./rpki-client -v -t sometal.tal . 111 | % ./rpki-client -v 112 | ``` 113 | 114 | If you later want to uninstall the system, simply run 115 | 116 | ``` 117 | # make uninstall 118 | ``` 119 | 120 | If the manpages in the install root have already been indexed, you may 121 | need to re-run [makewhatis(8)](https://man.openbsd.org/makewhatis.8) to 122 | purge the system's manpage. 123 | 124 | # Architecture 125 | 126 | The **rpki-client** run-time is split into at least three processes 127 | which pass data back and forth. 128 | "At least" since the system will dynamically spawn additional process in 129 | addition to the three core processes. 130 | Most of the architecture is implemented in [main.c](main.c). 131 | 132 | The master process orchestrates all other process. 133 | It also formats and outputs valid route data. 134 | 135 | The first subordinate process is responsible for obtaining certificates, 136 | route announcements, manifests, and so on. 137 | It waits for the master process to give it a repository and destination, 138 | then executes [openrsync(1)](https://man.openbsd.org/openrsync.1) and 139 | waits for termination. 140 | It executes child openrsync(1) processes asynchronously for maximum 141 | efficiency. 142 | 143 | *Side note*: although **rpki-client** can use [rsync](https://rsync.samba.org/) 144 | instead of openrsync(1), this is not recommended for security reasons: 145 | the latter has been designed to make maximum use of OpenBSD's security 146 | framework, as has **rpki-client**. 147 | 148 | The second subordinate process parses and validates data files. 149 | It is given filenames by the master process, parses them in-order, and 150 | returns the results. 151 | The files are assumed to exist on disc by virtue of being downloaded 152 | earlier by the first subordinate process. 153 | This process performs the bulk of the work. 154 | 155 | The master process is responsible for orchestrating this pipeline. 156 | It seeds the parser process with the TAL files, retrieves TAL output, 157 | then begins parsing certificates (X509), manifests (MFT), revocation 158 | lists (CRL), and Route Origin Authorizations (ROAs). 159 | If any of these files sits in a repository not yet fetched, that 160 | repository is fetched or refreshed. 161 | When the repository is fetched, those pending entries are flushed into 162 | the parser. 163 | 164 | The master process also outputs valid routes. At this time, it does so 165 | in [bgpd.conf(5)](https://man.openbsd.org/bgpd.conf.5), 166 | [BIRD](https://bird.network.cz), RIPE NCC RPKI JSON, or CSV formats. 167 | 168 | ## Future security 169 | 170 | It's trivially possible to put each type of file parse into its own 171 | process, but it's not clear whether this adds any security since the 172 | file-system available to a parser consists of all file types. 173 | 174 | Alternatively, each repository might have its own parser that's 175 | restricted to files only within the repository. 176 | This would allow [unveil(2)](https://man.openbsd.org/unveil.2) to limit 177 | the parser only to those in its repository. 178 | The repository cache would need to be redesigned to nest repositories so 179 | that a top-level repository would be able to access its children. 180 | 181 | The latter is not difficult to implement. 182 | 183 | # Algorithm 184 | 185 | At its heart, **rpki-client** is a tool for validating hierarchical 186 | statements. 187 | The terminals of this hierarchy consist of IP address prefix and 188 | numerical AS identifier components. 189 | The non-terminal statements provide both acceptable ranges of both 190 | components and links to further terminal and non-terminal nodes in the 191 | tree. 192 | 193 | Terminal nodes are ROA (route origin authorisation) and CRL (certificate 194 | revocation list) files. Non-terminal nodes are X509 (certificate) and 195 | MFT (manifest) files. The root node (there may be multiple roots) is a 196 | TAL (trust anchor locator) file. 197 | 198 | The validation algorithm is a breadth-first (though whether depth or 199 | breadth first is irrelevant) tree walk. 200 | 201 | Most of the certificate validation in RPKI comes from the `X509_STORE` 202 | functionality of OpenSSL. This covers CRL revocation, expiration dates, 203 | and so on. 204 | 205 | ## TAL validation 206 | 207 | It begins by parsing a TAL file, [RFC 208 | 7730](https://tools.ietf.org/html/rfc7730), which specifies a trust 209 | anchor certificate address and its public key. 210 | The parsing and validation of the TAL file occurs in [tal.c](tal.c). 211 | 212 | *Side note*: the TAL file may technically specify multiple top-level 213 | certificates; but in the case of **rpki-client**, only the first is 214 | processed. 215 | 216 | ## Trust anchor validation 217 | 218 | A trust anchor is an X509 ([RFC 219 | 6487](https://tools.ietf.org/html/rfc6487)) certificate given by the TAL 220 | file. 221 | Beyond the usual certificate parsing in [cert.c](cert.c), the trust 222 | anchor files also have a number of additional constraints imposed in 223 | [validate.c](validate.c): 224 | 225 | - the certificate must be self-signed 226 | - the public key must match the one given in the TAL file 227 | - it must have an SKI (subject key identifier) 228 | - the SKI must be unique in the set of all parsed certificates (trust 229 | anchors and otherwise) 230 | - must not specify a CRL resource 231 | 232 | Furthermore: 233 | 234 | - it may only contain non-inheritance AS identifiers 235 | - it may only contain non-inheritance IP blocks 236 | 237 | Each trust anchor (inheriting from the X509 validation) contains a 238 | reference to a manifest file that's used for further parsing. 239 | 240 | ## Manifest validation 241 | 242 | Manifests ([RFC 6486](https://tools.ietf.org/html/rfc6487)) contain 243 | links to more resources. 244 | They are parsed in [mft.c](mft.c), with the CMS ([RFC 245 | 6488](https://tools.ietf.org/html/rfc6488)) envelope parsed in 246 | [cms.c](cms.c), and additional checks implemented in 247 | [validate.c](validate.c). 248 | 249 | - self-signed CMS envelope 250 | - CMS envelope self-signed certificate is signed by the AKI's 251 | certificate 252 | - manifest time window has not expired 253 | 254 | Manifests contain a list of files they manage that must be ROA, CRL, or 255 | X509 (`roa`, `crl`, or `cer` suffixes, respectively). 256 | Each file is associated with a hash. 257 | 258 | Stale manifests---those whose validity period has elapsed---are 259 | accepted (and noted), but will contain zero members. 260 | 261 | ## Route origin validation 262 | 263 | ROA (route origin authorisation, [RFC 264 | 6482](https://tools.ietf.org/html/rfc6482)) files are stipulated in 265 | manifests. 266 | These are the focus of RPKI: those that pass validation are emitted as 267 | valid routes. 268 | ROA files consist of data wrapped in a CMS envelope. 269 | They are parsed in [roa.c](roa.c), with the CMS ([RFC 270 | 6488](https://tools.ietf.org/html/rfc6488)) envelope parsed in 271 | [cms.c](cms.c), and additional checks implemented in 272 | [validate.c](validate.c). 273 | 274 | - computed digest matches that given by the manifest 275 | - self-signed CMS envelope 276 | - CMS envelope self-signed certificate is signed by the AKI's 277 | certificate 278 | - IP blocks must be within the ranges allocated by the *nearest* 279 | non-inheriting certificate in the chain to the trust anchor 280 | 281 | An ROA may technically contain zero IP prefixes. 282 | If this is the case, it is merely skipped. 283 | 284 | A "stale" ROA (time validity has elapsed) is also ignored. 285 | 286 | ## Certificate validation 287 | 288 | X509 certificates ([RFC 6487](https://tools.ietf.org/html/rfc6487)) certificate 289 | are the mainstay of RPKI's validation. 290 | They are parsed in [cert.c](cert.c) with further validation being 291 | performed in [validate.c](validate.c). 292 | 293 | - computed digest matches that given by the manifest (if applicable) 294 | - the certificate must be signed by the AKI's certificate 295 | - the SKI must be unique in the set of all parsed certificates (trust 296 | anchors and otherwise) 297 | - must specify a CRL resource 298 | - AS identifiers/ranges must be within the ranges allocated by the 299 | nearest non-inheriting certificate in the chain to the trust anchor 300 | (see [TODO](TODO.md) for notes) 301 | - IP blocks must be within the ranges allocated by the nearest 302 | non-inheriting certificate in the chain to the trust anchor 303 | 304 | ## Revocation list validation 305 | 306 | **rpki-client** imposes no specific checks on CRL than those provided by 307 | OpenSSL's `X509_STORE` functionality. 308 | 309 | Some repositories, however, contain enormous CRL files with thousands 310 | and thousands of entries. These take quite some time to parse. 311 | 312 | # Portability 313 | 314 | The **rpki-client** is portable to the extent that it will compile and 315 | run on most modern UNIX systems. 316 | To date it is known to compile on GNU/Linux (musl, glibc), FreeBSD, 317 | OpenBSD, and IllumOS (OmniOS). Portability efforst are underway to NetBSD, 318 | Darwin (Mac OS X), and Solaris, all of which are missing 319 | [ppoll(2)](https://man.openbsd.org/ppoll) and in the latter case, 320 | [mkostemp](https://man.openbsd.org/mkostemp). 321 | 322 | It uses [oconfigure](https://github.com/kristapsdz/oconfigure) for its 323 | compatibility layer and 324 | [minci](https://kristaps.bsd.lv/cgi-bin/minci.cgi?project-name=rpki-client) 325 | for continuous integration. 326 | 327 | However, the system depends heavily on OpenBSD's security mechanisms 328 | (only enabled on OpenBSD installations) to safely and securely parse 329 | untrusted content. 330 | Those running on a non-OpenBSD operating system should be aware that 331 | this additional protection is not available. 332 | 333 | ## Privilege dropping 334 | 335 | If the `RPKI_PRIVDROP` macro evaluates to 1 as set in the 336 | [Makefile](https://github.com/kristapsdz/rpki-client/blob/master/Makefile), 337 | the `RPKI_PRIVDROP_USER` is used as the username into which to 338 | privilege-drop. 339 | On OpenBSD, this is *_rpki-client*. 340 | Privilege dropping only occurs when running the utility as root. 341 | 342 | If `RPKI_PRIVDROP` is set to 0, no privilege dropping occurs. 343 | 344 | ## Pledge 345 | 346 | **rpki-client** makes significant use of 347 | [pledge(2)](https://man.openbsd.org/pledge.2) to constrain resources 348 | available to the running process. 349 | On FreeBSD, the same (or similar) may be effected by judicious use of 350 | Capsicum. 351 | On Linux, seccomp, although it's an unholy mess. 352 | 353 | This function is used in [main.c](main.c). 354 | On non-OpenBSD systems it is redefined to be empty in [extern.h](extern.h). 355 | 356 | ## Unveil 357 | 358 | Once TAL files have been parsed (these may sit anywhere on the 359 | file-system), the parsing process restricts file-system access to the 360 | local repository directory with 361 | [unveil(2)](https://man.openbsd.org/unveil.2). 362 | 363 | It's not trivial to port this to FreeBSD or Linux. 364 | First, calls to `BIO_new_file` would need to use `BIO_new_fp` with a 365 | separate `fdopen` call. 366 | This descriptor would need to be opened with `openat` and the input 367 | paths stripped of their common prefix. 368 | This way, calls directly to `open` could be filtered. 369 | 370 | This function is used in [main.c](main.c). 371 | On non-OpenBSD systems it is redefined to be empty in [extern.h](extern.h). 372 | -------------------------------------------------------------------------------- /mft.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include "extern.h" 32 | 33 | /* 34 | * Parse results and data of the manifest file. 35 | */ 36 | struct parse { 37 | const char *fn; /* manifest file name */ 38 | struct mft *res; /* result object */ 39 | }; 40 | 41 | static const char * 42 | gentime2str(const ASN1_GENERALIZEDTIME *time) 43 | { 44 | static char buf[64]; 45 | BIO *mem; 46 | 47 | if ((mem = BIO_new(BIO_s_mem())) == NULL) 48 | cryptoerrx("BIO_new"); 49 | if (!ASN1_GENERALIZEDTIME_print(mem, time)) 50 | cryptoerrx("ASN1_GENERALIZEDTIME_print"); 51 | if (BIO_gets(mem, buf, sizeof(buf)) < 0) 52 | cryptoerrx("BIO_gets"); 53 | 54 | BIO_free(mem); 55 | return buf; 56 | } 57 | 58 | /* 59 | * Validate and verify the time validity of the mft. 60 | * Returns 1 if all is good, 0 if mft is stale, any other case -1. 61 | * XXX should use ASN1_time_tm_cmp() once libressl is used. 62 | */ 63 | static time_t 64 | check_validity(const ASN1_GENERALIZEDTIME *from, 65 | const ASN1_GENERALIZEDTIME *until, const char *fn, int force) 66 | { 67 | time_t now = time(NULL); 68 | 69 | if (!ASN1_GENERALIZEDTIME_check(from) || 70 | !ASN1_GENERALIZEDTIME_check(until)) { 71 | warnx("%s: embedded time format invalid", fn); 72 | return -1; 73 | } 74 | /* check that until is not before from */ 75 | if (ASN1_STRING_cmp(until, from) < 0) { 76 | warnx("%s: bad update interval", fn); 77 | return -1; 78 | } 79 | /* check that now is not before from */ 80 | if (X509_cmp_time(from, &now) > 0) { 81 | warnx("%s: mft not yet valid %s", fn, gentime2str(from)); 82 | return -1; 83 | } 84 | /* check that now is not after until */ 85 | if (X509_cmp_time(until, &now) < 0) { 86 | warnx("%s: mft expired on %s%s", fn, gentime2str(until), 87 | force ? " (ignoring)" : ""); 88 | if (!force) 89 | return 0; 90 | } 91 | 92 | return 1; 93 | } 94 | 95 | /* 96 | * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 97 | * Return zero on failure, non-zero on success. 98 | */ 99 | static int 100 | mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os) 101 | { 102 | ASN1_SEQUENCE_ANY *seq; 103 | const ASN1_TYPE *file, *hash; 104 | char *fn = NULL; 105 | const unsigned char *d = os->data; 106 | size_t dsz = os->length, sz; 107 | int rc = 0; 108 | struct mftfile *fent; 109 | 110 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 111 | cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 112 | "failed ASN.1 sequence parse", p->fn); 113 | goto out; 114 | } else if (sk_ASN1_TYPE_num(seq) != 2) { 115 | warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 116 | "want 2 elements, have %d", p->fn, 117 | sk_ASN1_TYPE_num(seq)); 118 | goto out; 119 | } 120 | 121 | /* First is the filename itself. */ 122 | 123 | file = sk_ASN1_TYPE_value(seq, 0); 124 | if (file->type != V_ASN1_IA5STRING) { 125 | warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 126 | "want ASN.1 IA5 string, have %s (NID %d)", 127 | p->fn, ASN1_tag2str(file->type), file->type); 128 | goto out; 129 | } 130 | fn = strndup((const char *)file->value.ia5string->data, 131 | file->value.ia5string->length); 132 | if (fn == NULL) 133 | err(1, NULL); 134 | 135 | /* 136 | * Make sure we're just a pathname and either an ROA or CER. 137 | * I don't think that the RFC specifically mentions this, but 138 | * it's in practical use and would really screw things up 139 | * (arbitrary filenames) otherwise. 140 | */ 141 | 142 | if (strchr(fn, '/') != NULL) { 143 | warnx("%s: path components disallowed in filename: %s", 144 | p->fn, fn); 145 | goto out; 146 | } else if ((sz = strlen(fn)) <= 4) { 147 | warnx("%s: filename must be large enough for suffix part: %s", 148 | p->fn, fn); 149 | goto out; 150 | } 151 | 152 | if (strcasecmp(fn + sz - 4, ".roa") && 153 | strcasecmp(fn + sz - 4, ".crl") && 154 | strcasecmp(fn + sz - 4, ".cer")) { 155 | /* ignore unknown files */ 156 | free(fn); 157 | fn = NULL; 158 | rc = 1; 159 | goto out; 160 | } 161 | 162 | /* Now hash value. */ 163 | 164 | hash = sk_ASN1_TYPE_value(seq, 1); 165 | if (hash->type != V_ASN1_BIT_STRING) { 166 | warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 167 | "want ASN.1 bit string, have %s (NID %d)", 168 | p->fn, ASN1_tag2str(hash->type), hash->type); 169 | goto out; 170 | } 171 | 172 | if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 173 | warnx("%s: RFC 6486 section 4.2.1: hash: " 174 | "invalid SHA256 length, have %d", 175 | p->fn, hash->value.bit_string->length); 176 | goto out; 177 | } 178 | 179 | /* Insert the filename and hash value. */ 180 | 181 | p->res->files = reallocarray(p->res->files, p->res->filesz + 1, 182 | sizeof(struct mftfile)); 183 | if (p->res->files == NULL) 184 | err(1, NULL); 185 | 186 | fent = &p->res->files[p->res->filesz++]; 187 | memset(fent, 0, sizeof(struct mftfile)); 188 | 189 | fent->file = fn; 190 | fn = NULL; 191 | memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 192 | 193 | rc = 1; 194 | out: 195 | free(fn); 196 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 197 | return rc; 198 | } 199 | 200 | /* 201 | * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 202 | * Return zero on failure, non-zero on success. 203 | */ 204 | static int 205 | mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 206 | { 207 | ASN1_SEQUENCE_ANY *seq; 208 | const ASN1_TYPE *t; 209 | const unsigned char *d = os->data; 210 | size_t dsz = os->length; 211 | int i, rc = 0; 212 | 213 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 214 | cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 215 | "failed ASN.1 sequence parse", p->fn); 216 | goto out; 217 | } 218 | 219 | for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 220 | t = sk_ASN1_TYPE_value(seq, i); 221 | if (t->type != V_ASN1_SEQUENCE) { 222 | warnx("%s: RFC 6486 section 4.2: fileList: " 223 | "want ASN.1 sequence, have %s (NID %d)", 224 | p->fn, ASN1_tag2str(t->type), t->type); 225 | goto out; 226 | } else if (!mft_parse_filehash(p, t->value.octet_string)) 227 | goto out; 228 | } 229 | 230 | rc = 1; 231 | out: 232 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 233 | return rc; 234 | } 235 | 236 | /* 237 | * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 238 | * Returns <0 on failure, 0 on stale, >0 on success. 239 | */ 240 | static int 241 | mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p, int force) 242 | { 243 | ASN1_SEQUENCE_ANY *seq; 244 | const ASN1_TYPE *t; 245 | const ASN1_GENERALIZEDTIME *from, *until; 246 | int i, rc = -1, validity; 247 | 248 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 249 | cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 250 | "failed ASN.1 sequence parse", p->fn); 251 | goto out; 252 | } 253 | 254 | /* The version is optional. */ 255 | 256 | if (sk_ASN1_TYPE_num(seq) != 5 && 257 | sk_ASN1_TYPE_num(seq) != 6) { 258 | warnx("%s: RFC 6486 section 4.2: Manifest: " 259 | "want 5 or 6 elements, have %d", p->fn, 260 | sk_ASN1_TYPE_num(seq)); 261 | goto out; 262 | } 263 | 264 | /* Start with optional version. */ 265 | 266 | i = 0; 267 | if (sk_ASN1_TYPE_num(seq) == 6) { 268 | t = sk_ASN1_TYPE_value(seq, i++); 269 | if (t->type != V_ASN1_INTEGER) { 270 | warnx("%s: RFC 6486 section 4.2.1: version: " 271 | "want ASN.1 integer, have %s (NID %d)", 272 | p->fn, ASN1_tag2str(t->type), t->type); 273 | goto out; 274 | } 275 | } 276 | 277 | /* Now the manifest sequence number. */ 278 | 279 | t = sk_ASN1_TYPE_value(seq, i++); 280 | if (t->type != V_ASN1_INTEGER) { 281 | warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 282 | "want ASN.1 integer, have %s (NID %d)", 283 | p->fn, ASN1_tag2str(t->type), t->type); 284 | goto out; 285 | } 286 | 287 | /* 288 | * Timestamps: this and next update time. 289 | * Validate that the current date falls into this interval. 290 | * This is required by section 4.4, (3). 291 | * If we're after the given date, then the MFT is stale. 292 | * This is made super complicated because it usees OpenSSL's 293 | * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 294 | * compare against the current time trivially. 295 | */ 296 | 297 | t = sk_ASN1_TYPE_value(seq, i++); 298 | if (t->type != V_ASN1_GENERALIZEDTIME) { 299 | warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 300 | "want ASN.1 generalised time, have %s (NID %d)", 301 | p->fn, ASN1_tag2str(t->type), t->type); 302 | goto out; 303 | } 304 | from = t->value.generalizedtime; 305 | 306 | t = sk_ASN1_TYPE_value(seq, i++); 307 | if (t->type != V_ASN1_GENERALIZEDTIME) { 308 | warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 309 | "want ASN.1 generalised time, have %s (NID %d)", 310 | p->fn, ASN1_tag2str(t->type), t->type); 311 | goto out; 312 | } 313 | until = t->value.generalizedtime; 314 | 315 | validity = check_validity(from, until, p->fn, force); 316 | if (validity != 1) 317 | goto out; 318 | 319 | /* File list algorithm. */ 320 | 321 | t = sk_ASN1_TYPE_value(seq, i++); 322 | if (t->type != V_ASN1_OBJECT) { 323 | warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 324 | "want ASN.1 object time, have %s (NID %d)", 325 | p->fn, ASN1_tag2str(t->type), t->type); 326 | goto out; 327 | } else if (OBJ_obj2nid(t->value.object) != NID_sha256) { 328 | warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 329 | "want SHA256 object, have %s (NID %d)", p->fn, 330 | ASN1_tag2str(OBJ_obj2nid(t->value.object)), 331 | OBJ_obj2nid(t->value.object)); 332 | goto out; 333 | } 334 | 335 | /* Now the sequence. */ 336 | 337 | t = sk_ASN1_TYPE_value(seq, i++); 338 | if (t->type != V_ASN1_SEQUENCE) { 339 | warnx("%s: RFC 6486 section 4.2.1: fileList: " 340 | "want ASN.1 sequence, have %s (NID %d)", 341 | p->fn, ASN1_tag2str(t->type), t->type); 342 | goto out; 343 | } else if (!mft_parse_flist(p, t->value.octet_string)) 344 | goto out; 345 | 346 | rc = validity; 347 | out: 348 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 349 | return rc; 350 | } 351 | 352 | /* 353 | * Parse the objects that have been published in the manifest. 354 | * This conforms to RFC 6486. 355 | * Note that if the MFT is stale, all referenced objects are stripped 356 | * from the parsed content. 357 | * The MFT content is otherwise returned. 358 | */ 359 | struct mft * 360 | mft_parse(X509 **x509, const char *fn, int force) 361 | { 362 | struct parse p; 363 | int c, rc = 0; 364 | size_t i, cmsz; 365 | unsigned char *cms; 366 | 367 | memset(&p, 0, sizeof(struct parse)); 368 | p.fn = fn; 369 | 370 | cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26", 371 | NULL, &cmsz); 372 | if (cms == NULL) 373 | return NULL; 374 | assert(*x509 != NULL); 375 | 376 | if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 377 | err(1, NULL); 378 | if ((p.res->file = strdup(fn)) == NULL) 379 | err(1, NULL); 380 | if (!x509_get_ski_aki(*x509, fn, &p.res->ski, &p.res->aki)) 381 | goto out; 382 | 383 | /* 384 | * If we're stale, then remove all of the files that the MFT 385 | * references as well as marking it as stale. 386 | */ 387 | 388 | if ((c = mft_parse_econtent(cms, cmsz, &p, force)) == 0) { 389 | /* 390 | * FIXME: it should suffice to just mark this as stale 391 | * and have the logic around mft_read() simply ignore 392 | * the contents of stale entries, just like it does for 393 | * invalid ROAs or certificates. 394 | */ 395 | 396 | p.res->stale = 1; 397 | if (p.res->files != NULL) 398 | for (i = 0; i < p.res->filesz; i++) 399 | free(p.res->files[i].file); 400 | free(p.res->files); 401 | p.res->filesz = 0; 402 | p.res->files = NULL; 403 | } else if (c == -1) 404 | goto out; 405 | 406 | rc = 1; 407 | out: 408 | if (rc == 0) { 409 | mft_free(p.res); 410 | p.res = NULL; 411 | X509_free(*x509); 412 | *x509 = NULL; 413 | } 414 | free(cms); 415 | return p.res; 416 | } 417 | 418 | /* 419 | * Check the hash value of a file. 420 | * Return zero on failure, non-zero on success. 421 | */ 422 | static int 423 | mft_validfilehash(const char *fn, const struct mftfile *m) 424 | { 425 | char filehash[SHA256_DIGEST_LENGTH]; 426 | char buffer[8192]; 427 | char *cp, *path = NULL; 428 | SHA256_CTX ctx; 429 | ssize_t nr; 430 | int fd; 431 | 432 | /* Check hash of file now, but first build path for it */ 433 | cp = strrchr(fn, '/'); 434 | assert(cp != NULL); 435 | if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn, m->file) == -1) 436 | err(1, "asprintf"); 437 | 438 | if ((fd = open(path, O_RDONLY)) == -1) { 439 | warn("%s: referenced file %s", fn, m->file); 440 | free(path); 441 | return 0; 442 | } 443 | free(path); 444 | 445 | SHA256_Init(&ctx); 446 | while ((nr = read(fd, buffer, sizeof(buffer))) > 0) { 447 | SHA256_Update(&ctx, buffer, nr); 448 | } 449 | close(fd); 450 | 451 | SHA256_Final(filehash, &ctx); 452 | if (memcmp(m->hash, filehash, SHA256_DIGEST_LENGTH) != 0) { 453 | warnx("%s: bad message digest for %s", fn, m->file); 454 | return 0; 455 | } 456 | 457 | return 1; 458 | } 459 | 460 | /* 461 | * Check all files and their hashes in a MFT structure. 462 | * Return zero on failure, non-zero on success. 463 | */ 464 | int 465 | mft_check(const char *fn, struct mft *p) 466 | { 467 | size_t i; 468 | int rc = 1; 469 | 470 | for (i = 0; i < p->filesz; i++) 471 | if (!mft_validfilehash(fn, &p->files[i])) 472 | rc = 0; 473 | 474 | return rc; 475 | } 476 | 477 | /* 478 | * Free an MFT pointer. 479 | * Safe to call with NULL. 480 | */ 481 | void 482 | mft_free(struct mft *p) 483 | { 484 | size_t i; 485 | 486 | if (p == NULL) 487 | return; 488 | 489 | if (p->files != NULL) 490 | for (i = 0; i < p->filesz; i++) 491 | free(p->files[i].file); 492 | 493 | free(p->aki); 494 | free(p->ski); 495 | free(p->file); 496 | free(p->files); 497 | free(p); 498 | } 499 | 500 | /* 501 | * Serialise MFT parsed content into the given buffer. 502 | * See mft_read() for the other side of the pipe. 503 | */ 504 | void 505 | mft_buffer(char **b, size_t *bsz, size_t *bmax, const struct mft *p) 506 | { 507 | size_t i; 508 | 509 | io_simple_buffer(b, bsz, bmax, &p->stale, sizeof(int)); 510 | io_str_buffer(b, bsz, bmax, p->file); 511 | io_simple_buffer(b, bsz, bmax, &p->filesz, sizeof(size_t)); 512 | 513 | for (i = 0; i < p->filesz; i++) { 514 | io_str_buffer(b, bsz, bmax, p->files[i].file); 515 | io_simple_buffer(b, bsz, bmax, 516 | p->files[i].hash, SHA256_DIGEST_LENGTH); 517 | } 518 | 519 | io_str_buffer(b, bsz, bmax, p->aki); 520 | io_str_buffer(b, bsz, bmax, p->ski); 521 | } 522 | 523 | /* 524 | * Read an MFT structure from the file descriptor. 525 | * Result must be passed to mft_free(). 526 | */ 527 | struct mft * 528 | mft_read(int fd) 529 | { 530 | struct mft *p = NULL; 531 | size_t i; 532 | 533 | if ((p = calloc(1, sizeof(struct mft))) == NULL) 534 | err(1, NULL); 535 | 536 | io_simple_read(fd, &p->stale, sizeof(int)); 537 | io_str_read(fd, &p->file); 538 | io_simple_read(fd, &p->filesz, sizeof(size_t)); 539 | 540 | if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 541 | err(1, NULL); 542 | 543 | for (i = 0; i < p->filesz; i++) { 544 | io_str_read(fd, &p->files[i].file); 545 | io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH); 546 | } 547 | 548 | io_str_read(fd, &p->aki); 549 | io_str_read(fd, &p->ski); 550 | return p; 551 | } 552 | -------------------------------------------------------------------------------- /cert.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2019 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include /* DIST_POINT */ 32 | 33 | #include "extern.h" 34 | 35 | /* 36 | * Type of ASIdentifier (RFC 3779, 3.2.3). 37 | */ 38 | #define ASID_TYPE_ASNUM 0x00 39 | #define ASID_TYPE_RDI 0x01 40 | #define ASID_TYPE_MAX ASID_TYPE_RDI 41 | 42 | /* 43 | * A parsing sequence of a file (which may just be ). 44 | */ 45 | struct parse { 46 | struct cert *res; /* result */ 47 | const char *fn; /* currently-parsed file */ 48 | }; 49 | 50 | /* 51 | * Wrapper around ASN1_get_object() that preserves the current start 52 | * state and returns a more meaningful value. 53 | * Return zero on failure, non-zero on success. 54 | */ 55 | static int 56 | ASN1_frame(struct parse *p, size_t sz, 57 | const unsigned char **cnt, long *cntsz, int *tag) 58 | { 59 | int ret, pcls; 60 | 61 | assert(cnt != NULL && *cnt != NULL); 62 | assert(sz > 0); 63 | ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz); 64 | if ((ret & 0x80)) { 65 | cryptowarnx("%s: ASN1_get_object", p->fn); 66 | return 0; 67 | } 68 | return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag); 69 | } 70 | 71 | /* 72 | * Append an IP address structure to our list of results. 73 | * This will also constrain us to having at most one inheritence 74 | * statement per AFI and also not have overlapping rages (as prohibited 75 | * in section 2.2.3.6). 76 | * It does not make sure that ranges can't coalesce, that is, that any 77 | * two ranges abut each other. 78 | * This is warned against in section 2.2.3.6, but doesn't change the 79 | * semantics of the system. 80 | * Return zero on failure (IP overlap) non-zero on success. 81 | */ 82 | static int 83 | append_ip(struct parse *p, const struct cert_ip *ip) 84 | { 85 | struct cert *res = p->res; 86 | 87 | if (!ip_addr_check_overlap(ip, p->fn, p->res->ips, p->res->ipsz)) 88 | return 0; 89 | res->ips = reallocarray(res->ips, res->ipsz + 1, 90 | sizeof(struct cert_ip)); 91 | if (res->ips == NULL) 92 | err(1, NULL); 93 | res->ips[res->ipsz++] = *ip; 94 | return 1; 95 | } 96 | 97 | /* 98 | * Append an AS identifier structure to our list of results. 99 | * Makes sure that the identifiers do not overlap or improperly inherit 100 | * as defined by RFC 3779 section 3.3. 101 | */ 102 | static int 103 | append_as(struct parse *p, const struct cert_as *as) 104 | { 105 | 106 | if (!as_check_overlap(as, p->fn, p->res->as, p->res->asz)) 107 | return 0; 108 | p->res->as = reallocarray(p->res->as, p->res->asz + 1, 109 | sizeof(struct cert_as)); 110 | if (p->res->as == NULL) 111 | err(1, NULL); 112 | p->res->as[p->res->asz++] = *as; 113 | return 1; 114 | } 115 | 116 | /* 117 | * Construct a RFC 3779 2.2.3.8 range by its bit string. 118 | * Return zero on failure, non-zero on success. 119 | */ 120 | static int 121 | sbgp_addr(struct parse *p, 122 | struct cert_ip *ip, const ASN1_BIT_STRING *bs) 123 | { 124 | 125 | if (!ip_addr_parse(bs, ip->afi, p->fn, &ip->ip)) { 126 | warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: " 127 | "invalid IP address", p->fn); 128 | return 0; 129 | } 130 | if (!ip_cert_compose_ranges(ip)) { 131 | warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: " 132 | "IP address range reversed", p->fn); 133 | return 0; 134 | } 135 | return append_ip(p, ip); 136 | } 137 | 138 | /* 139 | * Parse the SIA manifest, 4.8.8.1. 140 | * There may be multiple different resources at this location, so throw 141 | * out all but the matching resource type. 142 | * Returns zero on failure, non-zero on success. 143 | */ 144 | static int 145 | sbgp_sia_resource_mft(struct parse *p, 146 | const unsigned char *d, size_t dsz) 147 | { 148 | ASN1_SEQUENCE_ANY *seq; 149 | const ASN1_TYPE *t; 150 | int rc = 0, ptag; 151 | char buf[128]; 152 | long plen; 153 | enum rtype rt; 154 | 155 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 156 | cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " 157 | "failed ASN.1 sequence parse", p->fn); 158 | goto out; 159 | } 160 | if (sk_ASN1_TYPE_num(seq) != 2) { 161 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 162 | "want 2 elements, have %d", 163 | p->fn, sk_ASN1_TYPE_num(seq)); 164 | goto out; 165 | } 166 | 167 | /* Composed of an OID and its continuation. */ 168 | 169 | t = sk_ASN1_TYPE_value(seq, 0); 170 | if (t->type != V_ASN1_OBJECT) { 171 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 172 | "want ASN.1 object, have %s (NID %d)", 173 | p->fn, ASN1_tag2str(t->type), t->type); 174 | goto out; 175 | } 176 | OBJ_obj2txt(buf, sizeof(buf), t->value.object, 1); 177 | 178 | /* 179 | * Ignore all but manifest. 180 | * Things we may want to consider later: 181 | * - 1.3.6.1.5.5.7.48.13 (rpkiNotify) 182 | * - 1.3.6.1.5.5.7.48.5 (CA repository) 183 | */ 184 | 185 | if (strcmp(buf, "1.3.6.1.5.5.7.48.10")) { 186 | rc = 1; 187 | goto out; 188 | } 189 | if (p->res->mft != NULL) { 190 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 191 | "MFT location already specified", p->fn); 192 | goto out; 193 | } 194 | 195 | t = sk_ASN1_TYPE_value(seq, 1); 196 | if (t->type != V_ASN1_OTHER) { 197 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 198 | "want ASN.1 external, have %s (NID %d)", 199 | p->fn, ASN1_tag2str(t->type), t->type); 200 | goto out; 201 | } 202 | 203 | /* FIXME: there must be a way to do this without ASN1_frame. */ 204 | 205 | d = t->value.asn1_string->data; 206 | dsz = t->value.asn1_string->length; 207 | if (!ASN1_frame(p, dsz, &d, &plen, &ptag)) 208 | goto out; 209 | 210 | if ((p->res->mft = strndup((const char *)d, plen)) == NULL) 211 | err(1, NULL); 212 | 213 | /* Make sure it's an MFT rsync address. */ 214 | 215 | if (!rsync_uri_parse(NULL, NULL, NULL, 216 | NULL, NULL, NULL, &rt, p->res->mft)) { 217 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 218 | "failed to parse rsync URI", p->fn); 219 | free(p->res->mft); 220 | p->res->mft = NULL; 221 | goto out; 222 | } 223 | if (rt != RTYPE_MFT) { 224 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 225 | "invalid rsync URI suffix", p->fn); 226 | free(p->res->mft); 227 | p->res->mft = NULL; 228 | goto out; 229 | } 230 | 231 | rc = 1; 232 | out: 233 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 234 | return rc; 235 | } 236 | 237 | /* 238 | * Multiple locations as defined in RFC 6487, 4.8.8.1. 239 | * Returns zero on failure, non-zero on success. 240 | */ 241 | static int 242 | sbgp_sia_resource(struct parse *p, const unsigned char *d, size_t dsz) 243 | { 244 | ASN1_SEQUENCE_ANY *seq; 245 | const ASN1_TYPE *t; 246 | int rc = 0, i; 247 | 248 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 249 | cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " 250 | "failed ASN.1 sequence parse", p->fn); 251 | goto out; 252 | } 253 | 254 | for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 255 | t = sk_ASN1_TYPE_value(seq, i); 256 | if (t->type != V_ASN1_SEQUENCE) { 257 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 258 | "want ASN.1 sequence, have %s (NID %d)", 259 | p->fn, ASN1_tag2str(t->type), t->type); 260 | goto out; 261 | } 262 | d = t->value.asn1_string->data; 263 | dsz = t->value.asn1_string->length; 264 | if (!sbgp_sia_resource_mft(p, d, dsz)) 265 | goto out; 266 | } 267 | 268 | rc = 1; 269 | out: 270 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 271 | return rc; 272 | } 273 | 274 | /* 275 | * Parse "Subject Information Access" extension, RFC 6487 4.8.8. 276 | * Returns zero on failure, non-zero on success. 277 | */ 278 | static int 279 | sbgp_sia(struct parse *p, X509_EXTENSION *ext) 280 | { 281 | unsigned char *sv = NULL; 282 | const unsigned char *d; 283 | ASN1_SEQUENCE_ANY *seq = NULL; 284 | const ASN1_TYPE *t; 285 | int dsz, rc = 0; 286 | 287 | if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { 288 | cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " 289 | "failed extension parse", p->fn); 290 | goto out; 291 | } 292 | d = sv; 293 | 294 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 295 | cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " 296 | "failed ASN.1 sequence parse", p->fn); 297 | goto out; 298 | } 299 | if (sk_ASN1_TYPE_num(seq) != 2) { 300 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 301 | "want 2 elements, have %d", p->fn, 302 | sk_ASN1_TYPE_num(seq)); 303 | goto out; 304 | } 305 | 306 | t = sk_ASN1_TYPE_value(seq, 0); 307 | if (t->type != V_ASN1_OBJECT) { 308 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 309 | "want ASN.1 object, have %s (NID %d)", 310 | p->fn, ASN1_tag2str(t->type), t->type); 311 | goto out; 312 | } 313 | if (OBJ_obj2nid(t->value.object) != NID_sinfo_access) { 314 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 315 | "incorrect OID, have %s (NID %d)", p->fn, 316 | ASN1_tag2str(OBJ_obj2nid(t->value.object)), 317 | OBJ_obj2nid(t->value.object)); 318 | goto out; 319 | } 320 | 321 | t = sk_ASN1_TYPE_value(seq, 1); 322 | if (t->type != V_ASN1_OCTET_STRING) { 323 | warnx("%s: RFC 6487 section 4.8.8: SIA: " 324 | "want ASN.1 octet string, have %s (NID %d)", 325 | p->fn, ASN1_tag2str(t->type), t->type); 326 | goto out; 327 | } 328 | 329 | d = t->value.octet_string->data; 330 | dsz = t->value.octet_string->length; 331 | if (!sbgp_sia_resource(p, d, dsz)) 332 | goto out; 333 | 334 | rc = 1; 335 | out: 336 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 337 | free(sv); 338 | return rc; 339 | } 340 | 341 | /* 342 | * Parse a range of addresses as in 3.2.3.8. 343 | * Returns zero on failure, non-zero on success. 344 | */ 345 | static int 346 | sbgp_asrange(struct parse *p, const unsigned char *d, size_t dsz) 347 | { 348 | struct cert_as as; 349 | ASN1_SEQUENCE_ANY *seq; 350 | const ASN1_TYPE *t; 351 | int rc = 0; 352 | 353 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 354 | cryptowarnx("%s: RFC 3779 section 3.2.3.8: ASRange: " 355 | "failed ASN.1 sequence parse", p->fn); 356 | goto out; 357 | } 358 | if (sk_ASN1_TYPE_num(seq) != 2) { 359 | warnx("%s: RFC 3779 section 3.2.3.8: ASRange: " 360 | "want 2 elements, have %d", p->fn, 361 | sk_ASN1_TYPE_num(seq)); 362 | goto out; 363 | } 364 | 365 | memset(&as, 0, sizeof(struct cert_as)); 366 | as.type = CERT_AS_RANGE; 367 | 368 | t = sk_ASN1_TYPE_value(seq, 0); 369 | if (t->type != V_ASN1_INTEGER) { 370 | warnx("%s: RFC 3779 section 3.2.3.8: ASRange: " 371 | "want ASN.1 integer, have %s (NID %d)", 372 | p->fn, ASN1_tag2str(t->type), t->type); 373 | goto out; 374 | } 375 | if (!as_id_parse(t->value.integer, &as.range.min)) { 376 | warnx("%s: RFC 3770 section 3.2.3.8 (via RFC 1930): " 377 | "malformed AS identifier", p->fn); 378 | return 0; 379 | } 380 | 381 | t = sk_ASN1_TYPE_value(seq, 1); 382 | if (t->type != V_ASN1_INTEGER) { 383 | warnx("%s: RFC 3779 section 3.2.3.8: ASRange: " 384 | "want ASN.1 integer, have %s (NID %d)", 385 | p->fn, ASN1_tag2str(t->type), t->type); 386 | goto out; 387 | } 388 | if (!as_id_parse(t->value.integer, &as.range.max)) { 389 | warnx("%s: RFC 3770 section 3.2.3.8 (via RFC 1930): " 390 | "malformed AS identifier", p->fn); 391 | return 0; 392 | } 393 | 394 | if (as.range.max == as.range.min) { 395 | warnx("%s: RFC 3379 section 3.2.3.8: ASRange: " 396 | "range is singular", p->fn); 397 | goto out; 398 | } else if (as.range.max < as.range.min) { 399 | warnx("%s: RFC 3379 section 3.2.3.8: ASRange: " 400 | "range is out of order", p->fn); 401 | goto out; 402 | } 403 | 404 | if (!append_as(p, &as)) 405 | goto out; 406 | rc = 1; 407 | out: 408 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 409 | return rc; 410 | } 411 | 412 | /* 413 | * Parse an entire 3.2.3.10 integer type. 414 | */ 415 | static int 416 | sbgp_asid(struct parse *p, const ASN1_INTEGER *i) 417 | { 418 | struct cert_as as; 419 | 420 | memset(&as, 0, sizeof(struct cert_as)); 421 | as.type = CERT_AS_ID; 422 | 423 | if (!as_id_parse(i, &as.id)) { 424 | warnx("%s: RFC 3770 section 3.2.3.10 (via RFC 1930): " 425 | "malformed AS identifier", p->fn); 426 | return 0; 427 | } 428 | if (as.id == 0) { 429 | warnx("%s: RFC 3770 section 3.2.3.10 (via RFC 1930): " 430 | "AS identifier zero is reserved", p->fn); 431 | return 0; 432 | } 433 | 434 | return append_as(p, &as); 435 | } 436 | 437 | /* 438 | * Parse one of RFC 3779 3.2.3.2. 439 | * Returns zero on failure, non-zero on success. 440 | */ 441 | static int 442 | sbgp_asnum(struct parse *p, const unsigned char *d, size_t dsz) 443 | { 444 | struct cert_as as; 445 | ASN1_TYPE *t, *tt; 446 | ASN1_SEQUENCE_ANY *seq = NULL; 447 | int i, rc = 0; 448 | const unsigned char *sv = d; 449 | 450 | /* We can either be a null (inherit) or sequence. */ 451 | 452 | if ((t = d2i_ASN1_TYPE(NULL, &d, dsz)) == NULL) { 453 | cryptowarnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " 454 | "failed ASN.1 type parse", p->fn); 455 | goto out; 456 | } 457 | 458 | /* 459 | * Section 3779 3.2.3.3 is to inherit with an ASN.1 NULL type, 460 | * which is the easy case. 461 | */ 462 | 463 | switch (t->type) { 464 | case V_ASN1_NULL: 465 | memset(&as, 0, sizeof(struct cert_as)); 466 | as.type = CERT_AS_INHERIT; 467 | if (!append_as(p, &as)) 468 | goto out; 469 | rc = 1; 470 | goto out; 471 | case V_ASN1_SEQUENCE: 472 | break; 473 | default: 474 | warnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " 475 | "want ASN.1 sequence or null, have %s (NID %d)", 476 | p->fn, ASN1_tag2str(t->type), t->type); 477 | goto out; 478 | } 479 | 480 | /* This is RFC 3779 3.2.3.4. */ 481 | 482 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &sv, dsz)) == NULL) { 483 | cryptowarnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " 484 | "failed ASN.1 sequence parse", p->fn); 485 | goto out; 486 | } 487 | 488 | /* Accepts RFC 3779 3.2.3.6 or 3.2.3.7 (sequence). */ 489 | 490 | for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 491 | tt = sk_ASN1_TYPE_value(seq, i); 492 | switch (tt->type) { 493 | case V_ASN1_INTEGER: 494 | if (!sbgp_asid(p, tt->value.integer)) 495 | goto out; 496 | break; 497 | case V_ASN1_SEQUENCE: 498 | d = tt->value.asn1_string->data; 499 | dsz = tt->value.asn1_string->length; 500 | if (!sbgp_asrange(p, d, dsz)) 501 | goto out; 502 | break; 503 | default: 504 | warnx("%s: RFC 3779 section 3.2.3.4: IPAddressOrRange: " 505 | "want ASN.1 sequence or integer, have %s (NID %d)", 506 | p->fn, ASN1_tag2str(tt->type), tt->type); 507 | goto out; 508 | } 509 | } 510 | 511 | rc = 1; 512 | out: 513 | ASN1_TYPE_free(t); 514 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 515 | return rc; 516 | } 517 | 518 | /* 519 | * Parse RFC 6487 4.8.11 X509v3 extension, with syntax documented in RFC 520 | * 3779 starting in section 3.2. 521 | * Returns zero on failure, non-zero on success. 522 | */ 523 | static int 524 | sbgp_assysnum(struct parse *p, X509_EXTENSION *ext) 525 | { 526 | unsigned char *sv = NULL; 527 | const unsigned char *d; 528 | ASN1_SEQUENCE_ANY *seq = NULL, *sseq = NULL; 529 | const ASN1_TYPE *t; 530 | int dsz, rc = 0, i, ptag; 531 | long plen; 532 | 533 | if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { 534 | cryptowarnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 535 | "failed extension parse", p->fn); 536 | goto out; 537 | } 538 | 539 | /* Start with RFC 3779, section 3.2 top-level. */ 540 | 541 | d = sv; 542 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 543 | cryptowarnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 544 | "failed ASN.1 sequence parse", p->fn); 545 | goto out; 546 | } 547 | if (sk_ASN1_TYPE_num(seq) != 3) { 548 | warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 549 | "want 3 elements, have %d", p->fn, 550 | sk_ASN1_TYPE_num(seq)); 551 | goto out; 552 | } 553 | 554 | t = sk_ASN1_TYPE_value(seq, 0); 555 | if (t->type != V_ASN1_OBJECT) { 556 | warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 557 | "want ASN.1 object, have %s (NID %d)", 558 | p->fn, ASN1_tag2str(t->type), t->type); 559 | goto out; 560 | } 561 | 562 | /* FIXME: verify OID. */ 563 | 564 | t = sk_ASN1_TYPE_value(seq, 1); 565 | if (t->type != V_ASN1_BOOLEAN) { 566 | warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 567 | "want ASN.1 boolean, have %s (NID %d)", 568 | p->fn, ASN1_tag2str(t->type), t->type); 569 | goto out; 570 | } 571 | 572 | t = sk_ASN1_TYPE_value(seq, 2); 573 | if (t->type != V_ASN1_OCTET_STRING) { 574 | warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " 575 | "want ASN.1 octet string, have %s (NID %d)", 576 | p->fn, ASN1_tag2str(t->type), t->type); 577 | goto out; 578 | } 579 | 580 | /* Within RFC 3779 3.2.3, check 3.2.3.1. */ 581 | 582 | d = t->value.octet_string->data; 583 | dsz = t->value.octet_string->length; 584 | 585 | if ((sseq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 586 | cryptowarnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " 587 | "failed ASN.1 sequence parse", p->fn); 588 | goto out; 589 | } 590 | 591 | /* Scan through for private 3.2.3.2 classes. */ 592 | 593 | for (i = 0; i < sk_ASN1_TYPE_num(sseq); i++) { 594 | t = sk_ASN1_TYPE_value(sseq, i); 595 | if (t->type != V_ASN1_OTHER) { 596 | warnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " 597 | "want ASN.1 explicit, have %s (NID %d)", p->fn, 598 | ASN1_tag2str(t->type), t->type); 599 | goto out; 600 | } 601 | 602 | /* Use the low-level ASN1_frame. */ 603 | 604 | d = t->value.asn1_string->data; 605 | dsz = t->value.asn1_string->length; 606 | if (!ASN1_frame(p, dsz, &d, &plen, &ptag)) 607 | goto out; 608 | 609 | /* Ignore bad AS identifiers and RDI entries. */ 610 | 611 | if (ptag > ASID_TYPE_MAX) { 612 | warnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " 613 | "unknown explicit tag 0x%02x", p->fn, ptag); 614 | goto out; 615 | } else if (ptag == ASID_TYPE_RDI) 616 | continue; 617 | 618 | if (!sbgp_asnum(p, d, plen)) 619 | goto out; 620 | } 621 | 622 | rc = 1; 623 | out: 624 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 625 | sk_ASN1_TYPE_pop_free(sseq, ASN1_TYPE_free); 626 | free(sv); 627 | return rc; 628 | } 629 | 630 | /* 631 | * Parse RFC 3779 2.2.3.9 range of addresses. 632 | * Return zero on failure, non-zero on success. 633 | */ 634 | static int 635 | sbgp_addr_range(struct parse *p, struct cert_ip *ip, 636 | const unsigned char *d, size_t dsz) 637 | { 638 | ASN1_SEQUENCE_ANY *seq; 639 | const ASN1_TYPE *t; 640 | int rc = 0; 641 | 642 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 643 | cryptowarnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 644 | "failed ASN.1 sequence parse", p->fn); 645 | goto out; 646 | } 647 | if (sk_ASN1_TYPE_num(seq) != 2) { 648 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 649 | "want 2 elements, have %d", p->fn, sk_ASN1_TYPE_num(seq)); 650 | goto out; 651 | } 652 | 653 | t = sk_ASN1_TYPE_value(seq, 0); 654 | if (t->type != V_ASN1_BIT_STRING) { 655 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 656 | "want ASN.1 bit string, have %s (NID %d)", 657 | p->fn, ASN1_tag2str(t->type), t->type); 658 | goto out; 659 | } 660 | if (!ip_addr_parse(t->value.bit_string, 661 | ip->afi, p->fn, &ip->range.min)) { 662 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 663 | "invalid IP address", p->fn); 664 | goto out; 665 | } 666 | 667 | t = sk_ASN1_TYPE_value(seq, 1); 668 | if (t->type != V_ASN1_BIT_STRING) { 669 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 670 | "want ASN.1 bit string, have %s (NID %d)", 671 | p->fn, ASN1_tag2str(t->type), t->type); 672 | goto out; 673 | } 674 | if (!ip_addr_parse(t->value.bit_string, 675 | ip->afi, p->fn, &ip->range.max)) { 676 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 677 | "invalid IP address", p->fn); 678 | goto out; 679 | } 680 | 681 | if (!ip_cert_compose_ranges(ip)) { 682 | warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " 683 | "IP address range reversed", p->fn); 684 | return 0; 685 | } 686 | 687 | rc = append_ip(p, ip); 688 | out: 689 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 690 | return rc; 691 | } 692 | 693 | /* 694 | * Parse an IP address or range, RFC 3779 2.2.3.7. 695 | * We don't constrain this parse (as specified in section 2.2.3.6) to 696 | * having any kind of order. 697 | * Returns zero on failure, non-zero on success. 698 | */ 699 | static int 700 | sbgp_addr_or_range(struct parse *p, struct cert_ip *ip, 701 | const unsigned char *d, size_t dsz) 702 | { 703 | struct cert_ip nip; 704 | ASN1_SEQUENCE_ANY *seq; 705 | const ASN1_TYPE *t; 706 | int i, rc = 0; 707 | 708 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 709 | cryptowarnx("%s: RFC 3779 section 2.2.3.7: IPAddressOrRange: " 710 | "failed ASN.1 sequence parse", p->fn); 711 | goto out; 712 | } 713 | 714 | /* Either RFC 3779 2.2.3.8 or 2.2.3.9. */ 715 | 716 | for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 717 | nip = *ip; 718 | t = sk_ASN1_TYPE_value(seq, i); 719 | switch (t->type) { 720 | case V_ASN1_BIT_STRING: 721 | nip.type = CERT_IP_ADDR; 722 | if (!sbgp_addr(p, &nip, t->value.bit_string)) 723 | goto out; 724 | break; 725 | case V_ASN1_SEQUENCE: 726 | nip.type = CERT_IP_RANGE; 727 | d = t->value.asn1_string->data; 728 | dsz = t->value.asn1_string->length; 729 | if (!sbgp_addr_range(p, &nip, d, dsz)) 730 | goto out; 731 | break; 732 | default: 733 | warnx("%s: RFC 3779 section 2.2.3.7: IPAddressOrRange: " 734 | "want ASN.1 sequence or bit string, have %s (NID %d)", 735 | p->fn, ASN1_tag2str(t->type), t->type); 736 | goto out; 737 | } 738 | } 739 | 740 | rc = 1; 741 | out: 742 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 743 | return rc; 744 | } 745 | 746 | /* 747 | * Parse a sequence of address families as in RFC 3779 sec. 2.2.3.2. 748 | * Ignore several stipulations of the RFC (2.2.3.3). 749 | * Namely, we don't require entries to be ordered in any way (type, AFI 750 | * or SAFI group, etc.). 751 | * This is because it doesn't matter for our purposes: we're going to 752 | * validate in the same way regardless. 753 | * Returns zero no failure, non-zero on success. 754 | */ 755 | static int 756 | sbgp_ipaddrfam(struct parse *p, const unsigned char *d, size_t dsz) 757 | { 758 | struct cert_ip ip; 759 | ASN1_SEQUENCE_ANY *seq; 760 | const ASN1_TYPE *t; 761 | int rc = 0; 762 | 763 | memset(&ip, 0, sizeof(struct cert_ip)); 764 | 765 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 766 | cryptowarnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " 767 | "failed ASN.1 sequence parse", p->fn); 768 | goto out; 769 | } 770 | if (sk_ASN1_TYPE_num(seq) != 2) { 771 | warnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " 772 | "want 2 elements, have %d", 773 | p->fn, sk_ASN1_TYPE_num(seq)); 774 | goto out; 775 | } 776 | 777 | /* Get address family, RFC 3779, 2.2.3.3. */ 778 | 779 | t = sk_ASN1_TYPE_value(seq, 0); 780 | if (t->type != V_ASN1_OCTET_STRING) { 781 | warnx("%s: RFC 3779 section 2.2.3.2: addressFamily: " 782 | "want ASN.1 octet string, have %s (NID %d)", 783 | p->fn, ASN1_tag2str(t->type), t->type); 784 | goto out; 785 | } 786 | 787 | if (!ip_addr_afi_parse(p->fn, t->value.octet_string, &ip.afi)) { 788 | warnx("%s: RFC 3779 section 2.2.3.2: addressFamily: " 789 | "invalid AFI", p->fn); 790 | goto out; 791 | } 792 | 793 | /* Either sequence or null (inherit), RFC 3779 sec. 2.2.3.4. */ 794 | 795 | t = sk_ASN1_TYPE_value(seq, 1); 796 | switch (t->type) { 797 | case V_ASN1_SEQUENCE: 798 | d = t->value.asn1_string->data; 799 | dsz = t->value.asn1_string->length; 800 | if (!sbgp_addr_or_range(p, &ip, d, dsz)) 801 | goto out; 802 | break; 803 | case V_ASN1_NULL: 804 | ip.type = CERT_IP_INHERIT; 805 | if (!append_ip(p, &ip)) 806 | goto out; 807 | break; 808 | default: 809 | warnx("%s: RFC 3779 section 2.2.3.2: IPAddressChoice: " 810 | "want ASN.1 sequence or null, have %s (NID %d)", 811 | p->fn, ASN1_tag2str(t->type), t->type); 812 | goto out; 813 | } 814 | 815 | rc = 1; 816 | out: 817 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 818 | return rc; 819 | } 820 | 821 | /* 822 | * Parse an sbgp-ipAddrBlock X509 extension, RFC 6487 4.8.10, with 823 | * syntax documented in RFC 3779 starting in section 2.2. 824 | * Returns zero on failure, non-zero on success. 825 | */ 826 | static int 827 | sbgp_ipaddrblk(struct parse *p, X509_EXTENSION *ext) 828 | { 829 | int dsz, rc = 0; 830 | unsigned char *sv = NULL; 831 | const unsigned char *d; 832 | ASN1_SEQUENCE_ANY *seq = NULL, *sseq = NULL; 833 | const ASN1_TYPE *t = NULL; 834 | int i; 835 | 836 | if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { 837 | cryptowarnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 838 | "failed extension parse", p->fn); 839 | goto out; 840 | } 841 | d = sv; 842 | 843 | if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 844 | cryptowarnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 845 | "failed ASN.1 sequence parse", p->fn); 846 | goto out; 847 | } 848 | if (sk_ASN1_TYPE_num(seq) != 3) { 849 | warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 850 | "want 3 elements, have %d", 851 | p->fn, sk_ASN1_TYPE_num(seq)); 852 | goto out; 853 | } 854 | 855 | t = sk_ASN1_TYPE_value(seq, 0); 856 | if (t->type != V_ASN1_OBJECT) { 857 | warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 858 | "want ASN.1 object, have %s (NID %d)", 859 | p->fn, ASN1_tag2str(t->type), t->type); 860 | goto out; 861 | } 862 | 863 | /* FIXME: verify OID. */ 864 | 865 | t = sk_ASN1_TYPE_value(seq, 1); 866 | if (t->type != V_ASN1_BOOLEAN) { 867 | warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 868 | "want ASN.1 boolean, have %s (NID %d)", 869 | p->fn, ASN1_tag2str(t->type), t->type); 870 | goto out; 871 | } 872 | 873 | t = sk_ASN1_TYPE_value(seq, 2); 874 | if (t->type != V_ASN1_OCTET_STRING) { 875 | warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " 876 | "want ASN.1 octet string, have %s (NID %d)", 877 | p->fn, ASN1_tag2str(t->type), t->type); 878 | goto out; 879 | } 880 | 881 | /* The blocks sequence, RFC 3779 2.2.3.1. */ 882 | 883 | d = t->value.octet_string->data; 884 | dsz = t->value.octet_string->length; 885 | 886 | if ((sseq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 887 | cryptowarnx("%s: RFC 3779 section 2.2.3.1: IPAddrBlocks: " 888 | "failed ASN.1 sequence parse", p->fn); 889 | goto out; 890 | } 891 | 892 | /* Each sequence element contains RFC 3779 sec. 2.2.3.2. */ 893 | 894 | for (i = 0; i < sk_ASN1_TYPE_num(sseq); i++) { 895 | t = sk_ASN1_TYPE_value(sseq, i); 896 | if (t->type != V_ASN1_SEQUENCE) { 897 | warnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " 898 | "want ASN.1 sequence, have %s (NID %d)", 899 | p->fn, ASN1_tag2str(t->type), t->type); 900 | goto out; 901 | } 902 | d = t->value.asn1_string->data; 903 | dsz = t->value.asn1_string->length; 904 | if (!sbgp_ipaddrfam(p, d, dsz)) 905 | goto out; 906 | } 907 | 908 | rc = 1; 909 | out: 910 | sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 911 | sk_ASN1_TYPE_pop_free(sseq, ASN1_TYPE_free); 912 | free(sv); 913 | return rc; 914 | } 915 | 916 | /* 917 | * Parse and partially validate an RPKI X509 certificate (either a trust 918 | * anchor or a certificate) as defined in RFC 6487. 919 | * If "ta" is set, this is a trust anchor and must be self-signed. 920 | * Returns the parse results or NULL on failure ("xp" will be NULL too). 921 | * On success, free the pointer with cert_free() and make sure that "xp" 922 | * is also dereferenced. 923 | */ 924 | static struct cert * 925 | cert_parse_inner(X509 **xp, const char *fn, const unsigned char *dgst, int ta) 926 | { 927 | int rc = 0, extsz, c, sz; 928 | size_t i; 929 | X509 *x = NULL; 930 | X509_EXTENSION *ext = NULL; 931 | ASN1_OBJECT *obj; 932 | struct parse p; 933 | BIO *bio = NULL, *shamd; 934 | FILE *f; 935 | EVP_MD *md; 936 | char mdbuf[EVP_MAX_MD_SIZE]; 937 | 938 | *xp = NULL; 939 | 940 | if ((f = fopen(fn, "rb")) == NULL) { 941 | warn("%s", fn); 942 | return NULL; 943 | } 944 | 945 | if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) { 946 | if (verbose > 0) 947 | cryptowarnx("%s: BIO_new_file", fn); 948 | return NULL; 949 | } 950 | 951 | memset(&p, 0, sizeof(struct parse)); 952 | p.fn = fn; 953 | if ((p.res = calloc(1, sizeof(struct cert))) == NULL) 954 | err(1, NULL); 955 | 956 | /* 957 | * If we have a digest specified, create an MD chain that will 958 | * automatically compute a digest during the X509 creation. 959 | */ 960 | 961 | if (dgst != NULL) { 962 | if ((shamd = BIO_new(BIO_f_md())) == NULL) 963 | cryptoerrx("BIO_new"); 964 | if (!BIO_set_md(shamd, EVP_sha256())) 965 | cryptoerrx("BIO_set_md"); 966 | if ((bio = BIO_push(shamd, bio)) == NULL) 967 | cryptoerrx("BIO_push"); 968 | } 969 | 970 | if ((x = *xp = d2i_X509_bio(bio, NULL)) == NULL) { 971 | cryptowarnx("%s: d2i_X509_bio", p.fn); 972 | goto out; 973 | } 974 | 975 | /* 976 | * If we have a digest, find it in the chain (we'll already have 977 | * made it, so assert otherwise) and verify it. 978 | */ 979 | 980 | if (dgst != NULL) { 981 | shamd = BIO_find_type(bio, BIO_TYPE_MD); 982 | assert(shamd != NULL); 983 | 984 | if (!BIO_get_md(shamd, &md)) 985 | cryptoerrx("BIO_get_md"); 986 | assert(EVP_MD_type(md) == NID_sha256); 987 | 988 | if ((sz = BIO_gets(shamd, mdbuf, EVP_MAX_MD_SIZE)) < 0) 989 | cryptoerrx("BIO_gets"); 990 | assert(sz == SHA256_DIGEST_LENGTH); 991 | 992 | if (memcmp(mdbuf, dgst, SHA256_DIGEST_LENGTH)) { 993 | if (verbose > 0) 994 | warnx("%s: bad message digest", p.fn); 995 | goto out; 996 | } 997 | } 998 | 999 | /* Look for X509v3 extensions. */ 1000 | 1001 | if ((extsz = X509_get_ext_count(x)) < 0) 1002 | cryptoerrx("X509_get_ext_count"); 1003 | 1004 | for (i = 0; i < (size_t)extsz; i++) { 1005 | ext = X509_get_ext(x, i); 1006 | assert(ext != NULL); 1007 | obj = X509_EXTENSION_get_object(ext); 1008 | assert(obj != NULL); 1009 | c = 1; 1010 | 1011 | switch (OBJ_obj2nid(obj)) { 1012 | case NID_sbgp_ipAddrBlock: 1013 | c = sbgp_ipaddrblk(&p, ext); 1014 | break; 1015 | case NID_sbgp_autonomousSysNum: 1016 | c = sbgp_assysnum(&p, ext); 1017 | break; 1018 | case NID_sinfo_access: 1019 | c = sbgp_sia(&p, ext); 1020 | break; 1021 | case NID_crl_distribution_points: 1022 | /* ignored here, handled later */ 1023 | break; 1024 | case NID_authority_key_identifier: 1025 | free(p.res->aki); 1026 | p.res->aki = x509_get_aki_ext(ext, p.fn); 1027 | c = (p.res->aki != NULL); 1028 | break; 1029 | case NID_subject_key_identifier: 1030 | free(p.res->ski); 1031 | p.res->ski = x509_get_ski_ext(ext, p.fn); 1032 | c = (p.res->ski != NULL); 1033 | break; 1034 | default: 1035 | /* { 1036 | char objn[64]; 1037 | OBJ_obj2txt(objn, sizeof(objn), obj, 0); 1038 | warnx("%s: ignoring %s (NID %d)", 1039 | p.fn, objn, OBJ_obj2nid(obj)); 1040 | } */ 1041 | break; 1042 | } 1043 | if (c == 0) 1044 | goto out; 1045 | } 1046 | 1047 | if (!ta) 1048 | p.res->crl = x509_get_crl(x, p.fn); 1049 | 1050 | /* Validation on required fields. */ 1051 | 1052 | if (p.res->ski == NULL) { 1053 | warnx("%s: RFC 6487 section 8.4.2: " 1054 | "missing SKI", p.fn); 1055 | goto out; 1056 | } 1057 | 1058 | if (ta && p.res->aki != NULL && strcmp(p.res->aki, p.res->ski)) { 1059 | warnx("%s: RFC 6487 section 8.4.2: " 1060 | "trust anchor AKI, if specified, must match SKI", p.fn); 1061 | goto out; 1062 | } 1063 | 1064 | if (!ta && p.res->aki == NULL) { 1065 | warnx("%s: RFC 6487 section 8.4.2: " 1066 | "non-trust anchor missing AKI", p.fn); 1067 | goto out; 1068 | } else if (!ta && strcmp(p.res->aki, p.res->ski) == 0) { 1069 | warnx("%s: RFC 6487 section 8.4.2: " 1070 | "non-trust anchor AKI may not match SKI", p.fn); 1071 | goto out; 1072 | } 1073 | 1074 | if (ta && p.res->crl != NULL) { 1075 | warnx("%s: RFC 6487 section 8.4.2: " 1076 | "trust anchor may not specify CRL resource", p.fn); 1077 | goto out; 1078 | } 1079 | 1080 | if (p.res->asz == 0 && p.res->ipsz == 0) { 1081 | warnx("%s: RFC 6487 section 4.8.10 and 4.8.11: " 1082 | "missing IP or AS resources", p.fn); 1083 | goto out; 1084 | } 1085 | 1086 | if (p.res->mft == NULL) { 1087 | warnx("%s: RFC 6487 section 4.8.8: " 1088 | "missing SIA", p.fn); 1089 | goto out; 1090 | } 1091 | if (X509_up_ref(x) == 0) 1092 | errx(1, "king bula"); 1093 | 1094 | p.res->x509 = x; 1095 | 1096 | rc = 1; 1097 | out: 1098 | BIO_free_all(bio); 1099 | if (rc == 0) { 1100 | cert_free(p.res); 1101 | X509_free(x); 1102 | *xp = NULL; 1103 | } 1104 | return (rc == 0) ? NULL : p.res; 1105 | } 1106 | 1107 | struct cert * 1108 | cert_parse(X509 **xp, const char *fn, const unsigned char *dgst) 1109 | { 1110 | 1111 | return cert_parse_inner(xp, fn, dgst, 0); 1112 | } 1113 | 1114 | struct cert * 1115 | ta_parse(X509 **xp, const char *fn, const unsigned char *pkey, size_t pkeysz) 1116 | { 1117 | EVP_PKEY *pk = NULL, *opk = NULL; 1118 | struct cert *p; 1119 | int rc = 0; 1120 | 1121 | if ((p = cert_parse_inner(xp, fn, NULL, 1)) == NULL) 1122 | return NULL; 1123 | 1124 | if (pkey != NULL) { 1125 | assert(*xp != NULL); 1126 | pk = d2i_PUBKEY(NULL, &pkey, pkeysz); 1127 | assert(pk != NULL); 1128 | 1129 | if ((opk = X509_get_pubkey(*xp)) == NULL) 1130 | cryptowarnx("%s: RFC 6487 (trust anchor): " 1131 | "missing pubkey", fn); 1132 | else if (!EVP_PKEY_cmp(pk, opk)) 1133 | cryptowarnx("%s: RFC 6487 (trust anchor): " 1134 | "pubkey does not match TAL pubkey", fn); 1135 | else 1136 | rc = 1; 1137 | 1138 | EVP_PKEY_free(pk); 1139 | EVP_PKEY_free(opk); 1140 | } else 1141 | rc = 1; 1142 | 1143 | if (rc == 0) { 1144 | cert_free(p); 1145 | p = NULL; 1146 | X509_free(*xp); 1147 | *xp = NULL; 1148 | } 1149 | 1150 | return p; 1151 | } 1152 | 1153 | /* 1154 | * Free parsed certificate contents. 1155 | * Passing NULL is a noop. 1156 | */ 1157 | void 1158 | cert_free(struct cert *p) 1159 | { 1160 | 1161 | if (p == NULL) 1162 | return; 1163 | 1164 | free(p->crl); 1165 | free(p->mft); 1166 | free(p->ips); 1167 | free(p->as); 1168 | free(p->aki); 1169 | free(p->ski); 1170 | X509_free(p->x509); 1171 | free(p); 1172 | } 1173 | 1174 | static void 1175 | cert_ip_buffer(char **b, size_t *bsz, 1176 | size_t *bmax, const struct cert_ip *p) 1177 | { 1178 | 1179 | io_simple_buffer(b, bsz, bmax, &p->afi, sizeof(enum afi)); 1180 | io_simple_buffer(b, bsz, bmax, &p->type, sizeof(enum cert_ip_type)); 1181 | 1182 | if (p->type != CERT_IP_INHERIT) { 1183 | io_simple_buffer(b, bsz, bmax, &p->min, sizeof(p->min)); 1184 | io_simple_buffer(b, bsz, bmax, &p->max, sizeof(p->max)); 1185 | } 1186 | 1187 | if (p->type == CERT_IP_RANGE) 1188 | ip_addr_range_buffer(b, bsz, bmax, &p->range); 1189 | else if (p->type == CERT_IP_ADDR) 1190 | ip_addr_buffer(b, bsz, bmax, &p->ip); 1191 | } 1192 | 1193 | static void 1194 | cert_as_buffer(char **b, size_t *bsz, 1195 | size_t *bmax, const struct cert_as *p) 1196 | { 1197 | 1198 | io_simple_buffer(b, bsz, bmax, &p->type, sizeof(enum cert_as_type)); 1199 | if (p->type == CERT_AS_RANGE) { 1200 | io_simple_buffer(b, bsz, bmax, &p->range.min, sizeof(uint32_t)); 1201 | io_simple_buffer(b, bsz, bmax, &p->range.max, sizeof(uint32_t)); 1202 | } else if (p->type == CERT_AS_ID) 1203 | io_simple_buffer(b, bsz, bmax, &p->id, sizeof(uint32_t)); 1204 | } 1205 | 1206 | /* 1207 | * Write certificate parsed content into buffer. 1208 | * See cert_read() for the other side of the pipe. 1209 | */ 1210 | void 1211 | cert_buffer(char **b, size_t *bsz, size_t *bmax, const struct cert *p) 1212 | { 1213 | size_t i; 1214 | int has_crl, has_aki; 1215 | 1216 | io_simple_buffer(b, bsz, bmax, &p->valid, sizeof(int)); 1217 | io_simple_buffer(b, bsz, bmax, &p->ipsz, sizeof(size_t)); 1218 | for (i = 0; i < p->ipsz; i++) 1219 | cert_ip_buffer(b, bsz, bmax, &p->ips[i]); 1220 | 1221 | io_simple_buffer(b, bsz, bmax, &p->asz, sizeof(size_t)); 1222 | for (i = 0; i < p->asz; i++) 1223 | cert_as_buffer(b, bsz, bmax, &p->as[i]); 1224 | 1225 | io_str_buffer(b, bsz, bmax, p->mft); 1226 | 1227 | has_crl = (p->crl != NULL); 1228 | io_simple_buffer(b, bsz, bmax, &has_crl, sizeof(int)); 1229 | if (has_crl) 1230 | io_str_buffer(b, bsz, bmax, p->crl); 1231 | has_aki = (p->aki != NULL); 1232 | io_simple_buffer(b, bsz, bmax, &has_aki, sizeof(int)); 1233 | if (has_aki) 1234 | io_str_buffer(b, bsz, bmax, p->aki); 1235 | io_str_buffer(b, bsz, bmax, p->ski); 1236 | } 1237 | 1238 | static void 1239 | cert_ip_read(int fd, struct cert_ip *p) 1240 | { 1241 | 1242 | io_simple_read(fd, &p->afi, sizeof(enum afi)); 1243 | io_simple_read(fd, &p->type, sizeof(enum cert_ip_type)); 1244 | 1245 | if (p->type != CERT_IP_INHERIT) { 1246 | io_simple_read(fd, &p->min, sizeof(p->min)); 1247 | io_simple_read(fd, &p->max, sizeof(p->max)); 1248 | } 1249 | 1250 | if (p->type == CERT_IP_RANGE) 1251 | ip_addr_range_read(fd, &p->range); 1252 | else if (p->type == CERT_IP_ADDR) 1253 | ip_addr_read(fd, &p->ip); 1254 | } 1255 | 1256 | static void 1257 | cert_as_read(int fd, struct cert_as *p) 1258 | { 1259 | 1260 | io_simple_read(fd, &p->type, sizeof(enum cert_as_type)); 1261 | if (p->type == CERT_AS_RANGE) { 1262 | io_simple_read(fd, &p->range.min, sizeof(uint32_t)); 1263 | io_simple_read(fd, &p->range.max, sizeof(uint32_t)); 1264 | } else if (p->type == CERT_AS_ID) 1265 | io_simple_read(fd, &p->id, sizeof(uint32_t)); 1266 | } 1267 | 1268 | /* 1269 | * Allocate and read parsed certificate content from descriptor. 1270 | * The pointer must be freed with cert_free(). 1271 | * Always returns a valid pointer. 1272 | */ 1273 | struct cert * 1274 | cert_read(int fd) 1275 | { 1276 | struct cert *p; 1277 | size_t i; 1278 | int has_crl, has_aki; 1279 | 1280 | if ((p = calloc(1, sizeof(struct cert))) == NULL) 1281 | err(1, NULL); 1282 | 1283 | io_simple_read(fd, &p->valid, sizeof(int)); 1284 | io_simple_read(fd, &p->ipsz, sizeof(size_t)); 1285 | p->ips = calloc(p->ipsz, sizeof(struct cert_ip)); 1286 | if (p->ips == NULL) 1287 | err(1, NULL); 1288 | for (i = 0; i < p->ipsz; i++) 1289 | cert_ip_read(fd, &p->ips[i]); 1290 | 1291 | io_simple_read(fd, &p->asz, sizeof(size_t)); 1292 | p->as = calloc(p->asz, sizeof(struct cert_as)); 1293 | if (p->as == NULL) 1294 | err(1, NULL); 1295 | for (i = 0; i < p->asz; i++) 1296 | cert_as_read(fd, &p->as[i]); 1297 | 1298 | io_str_read(fd, &p->mft); 1299 | io_simple_read(fd, &has_crl, sizeof(int)); 1300 | if (has_crl) 1301 | io_str_read(fd, &p->crl); 1302 | io_simple_read(fd, &has_aki, sizeof(int)); 1303 | if (has_aki) 1304 | io_str_read(fd, &p->aki); 1305 | io_str_read(fd, &p->ski); 1306 | 1307 | return p; 1308 | } 1309 | 1310 | struct auth * 1311 | auth_find(struct auth_tree *auths, const char *aki) 1312 | { 1313 | struct auth a; 1314 | struct cert c; 1315 | 1316 | /* we look up the cert where the ski == aki */ 1317 | c.ski = (char *)aki; 1318 | a.cert = &c; 1319 | 1320 | return RB_FIND(auth_tree, auths, &a); 1321 | } 1322 | 1323 | static inline int 1324 | authcmp(struct auth *a, struct auth *b) 1325 | { 1326 | return strcmp(a->cert->ski, b->cert->ski); 1327 | } 1328 | 1329 | RB_GENERATE(auth_tree, auth, entry, authcmp); 1330 | --------------------------------------------------------------------------------