diff options
Diffstat (limited to 'utils/find_roa/find_roa.c')
-rw-r--r-- | utils/find_roa/find_roa.c | 422 |
1 files changed, 421 insertions, 1 deletions
diff --git a/utils/find_roa/find_roa.c b/utils/find_roa/find_roa.c index 10297296..8ab5e98c 100644 --- a/utils/find_roa/find_roa.c +++ b/utils/find_roa/find_roa.c @@ -16,4 +16,424 @@ /* $Id$ */ -int main (int argc, char *argv[]) { return 1; } +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <limits.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/safestack.h> +#include <openssl/conf.h> +#include <openssl/rand.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/cms.h> + +#ifndef FILENAME_MAX +#define FILENAME_MAX 1024 +#endif + +#ifndef ADDR_RAW_BUF_LEN +#define ADDR_RAW_BUF_LEN 16 +#endif + +static int verbose = 1; + + + +/* + * ASN.1 templates. Not sure that ASN1_EXP_OPT() is the right macro + * for these defaulted "version" fields, but it's what the examples + * for this construction use. Probably doesn't matter since this + * program only decodes manifests, never encodes them. + */ + +typedef struct ROAIPAddress_st { + ASN1_BIT_STRING *IPAddress; + ASN1_INTEGER *maxLength; +} ROAIPAddress; + +DECLARE_STACK_OF(ROAIPAddress) + +ASN1_SEQUENCE(ROAIPAddress) = { + ASN1_SIMPLE(ROAIPAddress, IPAddress, ASN1_BIT_STRING), + ASN1_EXP_OPT(ROAIPAddress, maxLength, ASN1_INTEGER, 0) +} ASN1_SEQUENCE_END(ROAIPAddress) + +typedef struct ROAIPAddressFamily_st { + ASN1_OCTET_STRING *addressFamily; + STACK_OF(ROAIPAddress) *addresses; +} ROAIPAddressFamily; + +DECLARE_STACK_OF(ROAIPAddressFamily) + +ASN1_SEQUENCE(ROAIPAddressFamily) = { + ASN1_SIMPLE(ROAIPAddressFamily, addressFamily, ASN1_OCTET_STRING), + ASN1_SEQUENCE_OF(ROAIPAddressFamily, addresses, ROAIPAddress) +} ASN1_SEQUENCE_END(ROAIPAddressFamily) + +typedef struct ROA_st { + ASN1_INTEGER *version, *asID; + STACK_OF(ROAIPAddressFamily) *ipAddrBlocks; +} ROA; + +ASN1_SEQUENCE(ROA) = { + ASN1_EXP_OPT(ROA, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ROA, asID, ASN1_INTEGER), + ASN1_SEQUENCE_OF(ROA, ipAddrBlocks, ROAIPAddressFamily) +} ASN1_SEQUENCE_END(ROA) + +DECLARE_ASN1_FUNCTIONS(ROAIPAddress) +DECLARE_ASN1_FUNCTIONS(ROAIPAddressFamily) +DECLARE_ASN1_FUNCTIONS(ROA) + +IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddress) +IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddressFamily) +IMPLEMENT_ASN1_FUNCTIONS(ROA) + +#define sk_ROAIPAddress_new(st) SKM_sk_new(ROAIPAddress, (st)) +#define sk_ROAIPAddress_new_null() SKM_sk_new_null(ROAIPAddress) +#define sk_ROAIPAddress_free(st) SKM_sk_free(ROAIPAddress, (st)) +#define sk_ROAIPAddress_num(st) SKM_sk_num(ROAIPAddress, (st)) +#define sk_ROAIPAddress_value(st, i) SKM_sk_value(ROAIPAddress, (st), (i)) +#define sk_ROAIPAddress_set(st, i, val) SKM_sk_set(ROAIPAddress, (st), (i), (val)) +#define sk_ROAIPAddress_zero(st) SKM_sk_zero(ROAIPAddress, (st)) +#define sk_ROAIPAddress_push(st, val) SKM_sk_push(ROAIPAddress, (st), (val)) +#define sk_ROAIPAddress_unshift(st, val) SKM_sk_unshift(ROAIPAddress, (st), (val)) +#define sk_ROAIPAddress_find(st, val) SKM_sk_find(ROAIPAddress, (st), (val)) +#define sk_ROAIPAddress_find_ex(st, val) SKM_sk_find_ex(ROAIPAddress, (st), (val)) +#define sk_ROAIPAddress_delete(st, i) SKM_sk_delete(ROAIPAddress, (st), (i)) +#define sk_ROAIPAddress_delete_ptr(st, ptr) SKM_sk_delete_ptr(ROAIPAddress, (st), (ptr)) +#define sk_ROAIPAddress_insert(st, val, i) SKM_sk_insert(ROAIPAddress, (st), (val), (i)) +#define sk_ROAIPAddress_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ROAIPAddress, (st), (cmp)) +#define sk_ROAIPAddress_dup(st) SKM_sk_dup(ROAIPAddress, st) +#define sk_ROAIPAddress_pop_free(st, free_func) SKM_sk_pop_free(ROAIPAddress, (st), (free_func)) +#define sk_ROAIPAddress_shift(st) SKM_sk_shift(ROAIPAddress, (st)) +#define sk_ROAIPAddress_pop(st) SKM_sk_pop(ROAIPAddress, (st)) +#define sk_ROAIPAddress_sort(st) SKM_sk_sort(ROAIPAddress, (st)) +#define sk_ROAIPAddress_is_sorted(st) SKM_sk_is_sorted(ROAIPAddress, (st)) + +#define sk_ROAIPAddressFamily_new(st) SKM_sk_new(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_new_null() SKM_sk_new_null(ROAIPAddressFamily) +#define sk_ROAIPAddressFamily_free(st) SKM_sk_free(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_num(st) SKM_sk_num(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_value(st, i) SKM_sk_value(ROAIPAddressFamily, (st), (i)) +#define sk_ROAIPAddressFamily_set(st, i, val) SKM_sk_set(ROAIPAddressFamily, (st), (i), (val)) +#define sk_ROAIPAddressFamily_zero(st) SKM_sk_zero(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_push(st, val) SKM_sk_push(ROAIPAddressFamily, (st), (val)) +#define sk_ROAIPAddressFamily_unshift(st, val) SKM_sk_unshift(ROAIPAddressFamily, (st), (val)) +#define sk_ROAIPAddressFamily_find(st, val) SKM_sk_find(ROAIPAddressFamily, (st), (val)) +#define sk_ROAIPAddressFamily_find_ex(st, val) SKM_sk_find_ex(ROAIPAddressFamily, (st), (val)) +#define sk_ROAIPAddressFamily_delete(st, i) SKM_sk_delete(ROAIPAddressFamily, (st), (i)) +#define sk_ROAIPAddressFamily_delete_ptr(st, ptr) SKM_sk_delete_ptr(ROAIPAddressFamily, (st), (ptr)) +#define sk_ROAIPAddressFamily_insert(st, val, i) SKM_sk_insert(ROAIPAddressFamily, (st), (val), (i)) +#define sk_ROAIPAddressFamily_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ROAIPAddressFamily, (st), (cmp)) +#define sk_ROAIPAddressFamily_dup(st) SKM_sk_dup(ROAIPAddressFamily, st) +#define sk_ROAIPAddressFamily_pop_free(st, free_func) SKM_sk_pop_free(ROAIPAddressFamily, (st), (free_func)) +#define sk_ROAIPAddressFamily_shift(st) SKM_sk_shift(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_pop(st) SKM_sk_pop(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_sort(st) SKM_sk_sort(ROAIPAddressFamily, (st)) +#define sk_ROAIPAddressFamily_is_sorted(st) SKM_sk_is_sorted(ROAIPAddressFamily, (st)) + + + +/* + * Error handling. + */ + +#define _lose(_msg_, _file_) \ + do { \ + if (_file_) \ + fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, _msg_, _file_); \ + else \ + fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, _msg_); \ + fprintf(stderr, "%s: %s\n", _msg_, _file_); \ + } while (0) + +#define lose(_msg_, _file_) \ + do { \ + _lose(_msg_, _file_); \ + goto done; \ + } while (0) + +#define lose_errno(_msg_, _file_) \ + do { \ + _lose(_msg_, _file_); \ + perror(NULL); \ + goto done; \ + } while (0) + +#define lose_openssl(_msg_, _file_) \ + do { \ + _lose(_msg_, _file_); \ + ERR_print_errors_fp(stderr); \ + goto done; \ + } while (0) + + +/* + * Extract a ROA prefix from the ASN.1 bitstring encoding. + */ +static int extract_roa_prefix(unsigned char *addr, + unsigned *prefixlen, + const ASN1_BIT_STRING *bs, + const unsigned afi) +{ + unsigned length; + + switch (afi) { + case IANA_AFI_IPV4: length = 4; break; + case IANA_AFI_IPV6: length = 16; break; + default: return 0; + } + + if (bs->length < 0 || bs->length > length) + return 0; + + if (bs->length > 0) { + memcpy(addr, bs->data, bs->length); + if ((bs->flags & 7) != 0) { + unsigned char mask = 0xFF >> (8 - (bs->flags & 7)); + addr[bs->length - 1] &= ~mask; + } + } + + memset(addr + bs->length, 0, length - bs->length); + + *prefixlen = (bs->length * 8) - (bs->flags & 7); + + return 1; +} + +/* + * Check str for a trailing suffix. + */ +static int has_suffix(const char *str, const char *suffix) +{ + size_t len_str, len_suffix; + assert(str != NULL && suffix != NULL); + len_str = strlen(str); + len_suffix = strlen(suffix); + return len_str >= len_suffix && !strcmp(str + len_str - len_suffix, suffix); +} + +/* + * Handle one object. + */ +static void file_handler(const char *filename, const unsigned prefix_afi, const unsigned char *prefix, const unsigned long prefixlen) +{ + unsigned char roa_prefix[ADDR_RAW_BUF_LEN]; + unsigned roa_prefixlen, roa_maxprefixlen, plen; + CMS_ContentInfo *cms = NULL; + BIO *b = NULL; + ROA *r = NULL; + int i, j, k; + long asid; + + if (verbose) + printf("Reading %s\n", filename); + + if (!(b = BIO_new_file(filename, "rb"))) + lose_openssl("Couldn't open CMS file", filename); + + if ((cms = d2i_CMS_bio(b, NULL)) == NULL) + lose_openssl("Couldn't read CMS file", filename); + + BIO_free(b); + + if ((b = BIO_new(BIO_s_mem())) == NULL) + lose_openssl("Couldn't open ROA", filename); + + if (CMS_verify(cms, NULL, NULL, NULL, b, CMS_NOCRL | CMS_NO_SIGNER_CERT_VERIFY | CMS_NO_ATTR_VERIFY | CMS_NO_CONTENT_VERIFY) <= 0) + lose_openssl("Couldn't parse ROA CMS", filename); + + if ((r = ASN1_item_d2i_bio(ASN1_ITEM_rptr(ROA), b, NULL)) == NULL) + lose_openssl("Couldn't parse ROA", filename); + + asid = ASN1_INTEGER_get(r->asID); + + for (i = 0; i < sk_ROAIPAddressFamily_num(r->ipAddrBlocks); i++) { + ROAIPAddressFamily *f = sk_ROAIPAddressFamily_value(r->ipAddrBlocks, i); + + /* + * AFI must match, SAFI must be null + */ + if (f->addressFamily->length != 2 || + prefix_afi != ((f->addressFamily->data[0] << 8) | (f->addressFamily->data[1]))) + continue; + + for (j = 0; j < sk_ROAIPAddress_num(f->addresses); j++) { + ROAIPAddress *a = sk_ROAIPAddress_value(f->addresses, j); + + if (!extract_roa_prefix(roa_prefix, &roa_prefixlen, a->IPAddress, prefix_afi)) + lose("Malformed ROA", filename); + + /* + * If the prefix we're looking for is bigger than the ROA + * prefix, the ROA can't possibly cover. + */ + if (prefixlen < roa_prefixlen) + continue; + + if (a->maxLength) + roa_maxprefixlen = ASN1_INTEGER_get(a->maxLength); + else + roa_maxprefixlen = roa_prefixlen; + + /* + * If the prefix we're looking for is smaller than the smallest + * allowed slice of the ROA prefix, the ROA can't possibly + * cover. + */ + if (prefixlen > roa_maxprefixlen) + continue; + + /* + * If we get this far, we have to compare prefixes. + */ + assert(roa_prefixlen <= ADDR_RAW_BUF_LEN * 8); + plen = prefixlen < roa_prefixlen ? prefixlen : roa_prefixlen; + k = 0; + while (plen >= 8 && prefix[k] == roa_prefix[k]) { + plen -= 8; + k++; + } + if (plen > 8 || ((prefix[k] ^ roa_prefix[k]) & (0xFF << (8 - plen))) != 0) + continue; + + /* + * If we get here, we have a match. + */ + printf("Match: %s\n", filename); + } + } + + done: + BIO_free(b); + CMS_ContentInfo_free(cms); + ROA_free(r); +} + +/* + * Walk a directory tree + */ +static int handle_directory(const char *name, const unsigned prefix_afi, const unsigned char *prefix, const unsigned long prefixlen) +{ + char path[FILENAME_MAX]; + struct dirent *d; + size_t len; + DIR *dir; + int ret = 0, need_slash; + + assert(name); + len = strlen(name); + assert(len > 0 && len < sizeof(path)); + need_slash = name[len - 1] != '/'; + + if ((dir = opendir(name)) == NULL) + lose_errno("Couldn't open directory", name); + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (len + strlen(d->d_name) + need_slash >= sizeof(path)) + lose("Constructed path name too long", d->d_name); + strcpy(path, name); + if (need_slash) + strcat(path, "/"); + strcat(path, d->d_name); + switch (d->d_type) { + case DT_DIR: + if (!handle_directory(path, prefix_afi, prefix, prefixlen)) + lose("Directory walk failed", path); + continue; + default: + if (has_suffix(path, ".roa")) + file_handler(path, prefix_afi, prefix, prefixlen); + continue; + } + } + + ret = 1; + + done: + if (dir) + closedir(dir); + return ret; +} + +int main (int argc, char *argv[]) +{ + unsigned char prefix[ADDR_RAW_BUF_LEN]; + unsigned long prefixlen; + unsigned afi; + char *s = NULL, *p; + int len, ret = 1; + + if (argc < 3) { + fprintf(stderr, "usage: %s prefix authtree\n", argv[0]); + goto done; + } + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + if ((s = strdup(argv[1])) == NULL) + lose("Couldn't strdup()", argv[1]); + + if ((p = strchr(s, '/')) != NULL) + *p++ = '\0'; + + len = a2i_ipadd(prefix, s); + + switch (len) { + case 4: afi = IANA_AFI_IPV4; break; + case 16: afi = IANA_AFI_IPV6; break; + default: lose("Unknown AFI", argv[1]); + } + + if (p) { + if (*p == '\0' || + (prefixlen = strtoul(p, &p, 10)) == ULONG_MAX || + *p != '\0' || + prefixlen > ADDR_RAW_BUF_LEN * 8) + lose("Bad prefix length", argv[1]); + } else { + prefixlen = len * 8; + } + + assert(prefixlen <= ADDR_RAW_BUF_LEN * 8); + + ret = !handle_directory(argv[2], afi, prefix, prefixlen); + +#if 0 + switch (afi) { + case IANA_AFI_IPV4: + printf("%u.%u.%u.%u", prefix[0], prefix[1], prefix[2], prefix[3]); + break; + case IANA_AFI_IPV6: + for (n = 16; n > 1 && prefix[n-1] == 0x00 && prefix[n-2] == 0x00; n -= 2) + ; + for (i = 0; i < n; i += 2) + printf("%x%s", (prefix[i] << 8) | prefix[i+1], (i < 14 ? ":" : "")); + if (i < 16) + printf(":"); + break; + } + printf("/%lu\n", prefixlen); +#endif + + + done: + if (s) + free(s); + return ret; +} |