2023-11-11 01:29:48 +00:00
|
|
|
/* $OpenBSD: validate.c,v 1.68 2023/10/19 17:05:55 job Exp $ */
|
2023-04-30 01:15:27 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <err.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "extern.h"
|
|
|
|
|
|
|
|
extern ASN1_OBJECT *certpol_oid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Walk up the chain of certificates trying to match our AS number to
|
|
|
|
* one of the allocations in that chain.
|
|
|
|
* Returns 1 if covered or 0 if not.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
valid_as(struct auth *a, uint32_t min, uint32_t max)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if (a == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Does this certificate cover our AS number? */
|
|
|
|
c = as_check_covered(min, max, a->cert->as, a->cert->asz);
|
|
|
|
if (c > 0)
|
|
|
|
return 1;
|
|
|
|
else if (c < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* If it inherits, walk up the chain. */
|
|
|
|
return valid_as(a->parent, min, max);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Walk up the chain of certificates (really just the last one, but in
|
|
|
|
* the case of inheritance, the ones before) making sure that our IP
|
|
|
|
* prefix is covered in the first non-inheriting specification.
|
|
|
|
* Returns 1 if covered or 0 if not.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
valid_ip(struct auth *a, enum afi afi,
|
|
|
|
const unsigned char *min, const unsigned char *max)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if (a == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Does this certificate cover our IP prefix? */
|
|
|
|
c = ip_addr_check_covered(afi, min, max, a->cert->ips, a->cert->ipsz);
|
|
|
|
if (c > 0)
|
|
|
|
return 1;
|
|
|
|
else if (c < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* If it inherits, walk up the chain. */
|
|
|
|
return valid_ip(a->parent, afi, min, max);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure the AKI is the same as the AKI listed on the Manifest,
|
|
|
|
* and that the SKI doesn't already exist.
|
|
|
|
* Return the parent by its AKI, or NULL on failure.
|
|
|
|
*/
|
|
|
|
struct auth *
|
|
|
|
valid_ski_aki(const char *fn, struct auth_tree *auths,
|
|
|
|
const char *ski, const char *aki, const char *mftaki)
|
|
|
|
{
|
|
|
|
struct auth *a;
|
|
|
|
|
|
|
|
if (mftaki != NULL) {
|
|
|
|
if (strcmp(aki, mftaki) != 0) {
|
|
|
|
warnx("%s: AKI doesn't match Manifest AKI", fn);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auth_find(auths, ski) != NULL) {
|
|
|
|
warnx("%s: RFC 6487: duplicate SKI", fn);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
a = auth_find(auths, aki);
|
|
|
|
if (a == NULL)
|
|
|
|
warnx("%s: RFC 6487: unknown AKI", fn);
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate a trust anchor by making sure that the SKI is unique.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_ta(const char *fn, struct auth_tree *auths, const struct cert *cert)
|
|
|
|
{
|
|
|
|
/* SKI must not be a dupe. */
|
|
|
|
if (auth_find(auths, cert->ski) != NULL) {
|
|
|
|
warnx("%s: RFC 6487: duplicate SKI", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate a non-TA certificate: make sure its IP and AS resources are
|
|
|
|
* fully covered by those in the authority key (which must exist).
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_cert(const char *fn, struct auth *a, const struct cert *cert)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
uint32_t min, max;
|
2023-09-28 08:40:30 +00:00
|
|
|
char buf[128];
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
for (i = 0; i < cert->asz; i++) {
|
|
|
|
if (cert->as[i].type == CERT_AS_INHERIT)
|
|
|
|
continue;
|
2023-09-28 08:40:30 +00:00
|
|
|
|
|
|
|
if (cert->as[i].type == CERT_AS_ID) {
|
|
|
|
min = cert->as[i].id;
|
|
|
|
max = cert->as[i].id;
|
|
|
|
} else {
|
|
|
|
min = cert->as[i].range.min;
|
|
|
|
max = cert->as[i].range.max;
|
|
|
|
}
|
|
|
|
|
2023-04-30 01:15:27 +00:00
|
|
|
if (valid_as(a, min, max))
|
|
|
|
continue;
|
2023-09-28 08:40:30 +00:00
|
|
|
|
|
|
|
switch (cert->as[i].type) {
|
|
|
|
case CERT_AS_ID:
|
|
|
|
warnx("%s: RFC 6487: uncovered AS: %u", fn, min);
|
|
|
|
break;
|
|
|
|
case CERT_AS_RANGE:
|
|
|
|
warnx("%s: RFC 6487: uncovered AS: %u--%u", fn,
|
|
|
|
min, max);
|
|
|
|
break;
|
|
|
|
case CERT_AS_INHERIT:
|
|
|
|
warnx("%s: RFC 6487: uncovered AS: (inherit)", fn);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-04-30 01:15:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < cert->ipsz; i++) {
|
2023-05-13 14:25:18 +00:00
|
|
|
if (cert->ips[i].type == CERT_IP_INHERIT)
|
|
|
|
continue;
|
2023-09-28 08:40:30 +00:00
|
|
|
|
2023-04-30 01:15:27 +00:00
|
|
|
if (valid_ip(a, cert->ips[i].afi, cert->ips[i].min,
|
|
|
|
cert->ips[i].max))
|
|
|
|
continue;
|
2023-09-28 08:40:30 +00:00
|
|
|
|
2023-04-30 01:15:27 +00:00
|
|
|
switch (cert->ips[i].type) {
|
|
|
|
case CERT_IP_ADDR:
|
|
|
|
ip_addr_print(&cert->ips[i].ip,
|
2023-09-28 08:40:30 +00:00
|
|
|
cert->ips[i].afi, buf, sizeof(buf));
|
|
|
|
warnx("%s: RFC 6487: uncovered IP: %s", fn, buf);
|
|
|
|
break;
|
|
|
|
case CERT_IP_RANGE:
|
|
|
|
ip_addr_range_print(&cert->ips[i].range,
|
|
|
|
cert->ips[i].afi, buf, sizeof(buf));
|
|
|
|
warnx("%s: RFC 6487: uncovered IP: %s", fn, buf);
|
2023-04-30 01:15:27 +00:00
|
|
|
break;
|
|
|
|
case CERT_IP_INHERIT:
|
2023-09-28 08:40:30 +00:00
|
|
|
warnx("%s: RFC 6487: uncovered IP: (inherit)", fn);
|
2023-04-30 01:15:27 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-09-28 08:40:30 +00:00
|
|
|
|
2023-04-30 01:15:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our ROA: check that the prefixes (ipAddrBlocks) are contained.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_roa(const char *fn, struct cert *cert, struct roa *roa)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
char buf[64];
|
|
|
|
|
|
|
|
for (i = 0; i < roa->ipsz; i++) {
|
|
|
|
if (ip_addr_check_covered(roa->ips[i].afi, roa->ips[i].min,
|
|
|
|
roa->ips[i].max, cert->ips, cert->ipsz) > 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ip_addr_print(&roa->ips[i].addr, roa->ips[i].afi, buf,
|
|
|
|
sizeof(buf));
|
|
|
|
warnx("%s: RFC 6482: uncovered IP: %s", fn, buf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate a file by verifying the SHA256 hash of that file.
|
|
|
|
* The file to check is passed as a file descriptor.
|
|
|
|
* Returns 1 if hash matched, 0 otherwise. Closes fd when done.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_filehash(int fd, const char *hash, size_t hlen)
|
|
|
|
{
|
|
|
|
SHA256_CTX ctx;
|
|
|
|
char filehash[SHA256_DIGEST_LENGTH];
|
|
|
|
char buffer[8192];
|
|
|
|
ssize_t nr;
|
|
|
|
|
|
|
|
if (hlen != sizeof(filehash))
|
|
|
|
errx(1, "bad hash size");
|
|
|
|
|
|
|
|
if (fd == -1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
SHA256_Init(&ctx);
|
|
|
|
while ((nr = read(fd, buffer, sizeof(buffer))) > 0)
|
|
|
|
SHA256_Update(&ctx, buffer, nr);
|
|
|
|
close(fd);
|
|
|
|
SHA256_Final(filehash, &ctx);
|
|
|
|
|
|
|
|
if (memcmp(hash, filehash, sizeof(filehash)) != 0)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Same as above but with a buffer instead of a fd.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_hash(unsigned char *buf, size_t len, const char *hash, size_t hlen)
|
|
|
|
{
|
|
|
|
char filehash[SHA256_DIGEST_LENGTH];
|
|
|
|
|
|
|
|
if (hlen != sizeof(filehash))
|
|
|
|
errx(1, "bad hash size");
|
|
|
|
|
|
|
|
if (buf == NULL || len == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!EVP_Digest(buf, len, filehash, NULL, EVP_sha256(), NULL))
|
|
|
|
errx(1, "EVP_Digest failed");
|
|
|
|
|
|
|
|
if (memcmp(hash, filehash, sizeof(filehash)) != 0)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate that a filename only contains characters from the POSIX portable
|
|
|
|
* filename character set [A-Za-z0-9._-], see IEEE Std 1003.1-2013, 3.278.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_filename(const char *fn, size_t len)
|
|
|
|
{
|
|
|
|
const unsigned char *c;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (c = fn, i = 0; i < len; i++, c++)
|
|
|
|
if (!isalnum(*c) && *c != '-' && *c != '_' && *c != '.')
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate a URI to make sure it is pure ASCII and does not point backwards
|
|
|
|
* or doing some other silly tricks. To enforce the protocol pass either
|
|
|
|
* https:// or rsync:// as proto, if NULL is passed no protocol is enforced.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_uri(const char *uri, size_t usz, const char *proto)
|
|
|
|
{
|
|
|
|
size_t s;
|
|
|
|
|
|
|
|
if (usz > MAX_URI_LENGTH)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (s = 0; s < usz; s++)
|
|
|
|
if (!isalnum((unsigned char)uri[s]) &&
|
|
|
|
!ispunct((unsigned char)uri[s]))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (proto != NULL) {
|
|
|
|
s = strlen(proto);
|
|
|
|
if (s >= usz)
|
|
|
|
return 0;
|
|
|
|
if (strncasecmp(uri, proto, s) != 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do not allow files or directories to start with a '.' */
|
|
|
|
if (strstr(uri, "/.") != NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate that a URI has the same host as the URI passed in proto.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_origin(const char *uri, const char *proto)
|
|
|
|
{
|
|
|
|
const char *to;
|
|
|
|
|
|
|
|
/* extract end of host from proto URI */
|
|
|
|
to = strstr(proto, "://");
|
|
|
|
if (to == NULL)
|
|
|
|
return 0;
|
|
|
|
to += strlen("://");
|
|
|
|
if ((to = strchr(to, '/')) == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* compare hosts including the / for the start of the path section */
|
|
|
|
if (strncasecmp(uri, proto, to - proto + 1) != 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2023-05-13 14:25:18 +00:00
|
|
|
* Walk the tree of known valid CA certificates until we find a certificate that
|
|
|
|
* doesn't inherit. Build a chain of intermediates and use the non-inheriting
|
|
|
|
* certificate as a trusted root by virtue of X509_V_FLAG_PARTIAL_CHAIN. The
|
|
|
|
* RFC 3779 path validation needs a non-inheriting trust root to ensure that
|
|
|
|
* all delegated resources are covered.
|
2023-04-30 01:15:27 +00:00
|
|
|
*/
|
|
|
|
static void
|
2023-05-13 14:25:18 +00:00
|
|
|
build_chain(const struct auth *a, STACK_OF(X509) **intermediates,
|
|
|
|
STACK_OF(X509) **root)
|
2023-04-30 01:15:27 +00:00
|
|
|
{
|
2023-05-13 14:25:18 +00:00
|
|
|
*intermediates = NULL;
|
|
|
|
*root = NULL;
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
if (a == NULL)
|
|
|
|
return;
|
|
|
|
|
2023-05-13 14:25:18 +00:00
|
|
|
if ((*intermediates = sk_X509_new_null()) == NULL)
|
|
|
|
err(1, "sk_X509_new_null");
|
|
|
|
if ((*root = sk_X509_new_null()) == NULL)
|
2023-04-30 01:15:27 +00:00
|
|
|
err(1, "sk_X509_new_null");
|
|
|
|
for (; a != NULL; a = a->parent) {
|
|
|
|
assert(a->cert->x509 != NULL);
|
2023-05-13 14:25:18 +00:00
|
|
|
if (!a->any_inherits) {
|
|
|
|
if (!sk_X509_push(*root, a->cert->x509))
|
|
|
|
errx(1, "sk_X509_push");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!sk_X509_push(*intermediates, a->cert->x509))
|
2023-04-30 01:15:27 +00:00
|
|
|
errx(1, "sk_X509_push");
|
|
|
|
}
|
2023-05-13 14:25:18 +00:00
|
|
|
assert(sk_X509_num(*root) == 1);
|
2023-04-30 01:15:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the CRL based on the certs SKI value.
|
|
|
|
* No need to insert any other CRL since those were already checked.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
build_crls(const struct crl *crl, STACK_OF(X509_CRL) **crls)
|
|
|
|
{
|
|
|
|
*crls = NULL;
|
|
|
|
|
|
|
|
if (crl == NULL)
|
|
|
|
return;
|
|
|
|
if ((*crls = sk_X509_CRL_new_null()) == NULL)
|
|
|
|
errx(1, "sk_X509_CRL_new_null");
|
|
|
|
if (!sk_X509_CRL_push(*crls, crl->x509_crl))
|
|
|
|
err(1, "sk_X509_CRL_push");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the X509 certificate. Returns 1 for valid certificates,
|
|
|
|
* returns 0 if there is a verify error and sets *errstr to the error
|
|
|
|
* returned by X509_verify_cert_error_string().
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_x509(char *file, X509_STORE_CTX *store_ctx, X509 *x509, struct auth *a,
|
|
|
|
struct crl *crl, const char **errstr)
|
|
|
|
{
|
|
|
|
X509_VERIFY_PARAM *params;
|
|
|
|
ASN1_OBJECT *cp_oid;
|
2023-05-13 14:25:18 +00:00
|
|
|
STACK_OF(X509) *intermediates, *root;
|
2023-04-30 01:15:27 +00:00
|
|
|
STACK_OF(X509_CRL) *crls = NULL;
|
|
|
|
unsigned long flags;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
*errstr = NULL;
|
2023-05-13 14:25:18 +00:00
|
|
|
build_chain(a, &intermediates, &root);
|
2023-04-30 01:15:27 +00:00
|
|
|
build_crls(crl, &crls);
|
|
|
|
|
|
|
|
assert(store_ctx != NULL);
|
|
|
|
assert(x509 != NULL);
|
|
|
|
if (!X509_STORE_CTX_init(store_ctx, NULL, x509, NULL))
|
2023-06-29 10:53:26 +00:00
|
|
|
err(1, "X509_STORE_CTX_init");
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
if ((params = X509_STORE_CTX_get0_param(store_ctx)) == NULL)
|
2023-06-29 10:53:26 +00:00
|
|
|
errx(1, "X509_STORE_CTX_get0_param");
|
2023-04-30 01:15:27 +00:00
|
|
|
if ((cp_oid = OBJ_dup(certpol_oid)) == NULL)
|
2023-06-29 10:53:26 +00:00
|
|
|
err(1, "OBJ_dup");
|
2023-04-30 01:15:27 +00:00
|
|
|
if (!X509_VERIFY_PARAM_add0_policy(params, cp_oid))
|
2023-06-29 10:53:26 +00:00
|
|
|
err(1, "X509_VERIFY_PARAM_add0_policy");
|
2023-06-07 21:20:56 +00:00
|
|
|
X509_VERIFY_PARAM_set_time(params, get_current_time());
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
flags = X509_V_FLAG_CRL_CHECK;
|
2023-05-13 14:25:18 +00:00
|
|
|
flags |= X509_V_FLAG_PARTIAL_CHAIN;
|
2023-04-30 01:15:27 +00:00
|
|
|
flags |= X509_V_FLAG_POLICY_CHECK;
|
|
|
|
flags |= X509_V_FLAG_EXPLICIT_POLICY;
|
|
|
|
flags |= X509_V_FLAG_INHIBIT_MAP;
|
|
|
|
X509_STORE_CTX_set_flags(store_ctx, flags);
|
|
|
|
X509_STORE_CTX_set_depth(store_ctx, MAX_CERT_DEPTH);
|
2023-05-13 14:25:18 +00:00
|
|
|
/*
|
|
|
|
* See the comment above build_chain() for details on what's happening
|
|
|
|
* here. The nomenclature in this API is dubious and poorly documented.
|
|
|
|
*/
|
|
|
|
X509_STORE_CTX_set0_untrusted(store_ctx, intermediates);
|
|
|
|
X509_STORE_CTX_set0_trusted_stack(store_ctx, root);
|
2023-04-30 01:15:27 +00:00
|
|
|
X509_STORE_CTX_set0_crls(store_ctx, crls);
|
|
|
|
|
|
|
|
if (X509_verify_cert(store_ctx) <= 0) {
|
|
|
|
error = X509_STORE_CTX_get_error(store_ctx);
|
|
|
|
*errstr = X509_verify_cert_error_string(error);
|
|
|
|
X509_STORE_CTX_cleanup(store_ctx);
|
2023-05-13 14:25:18 +00:00
|
|
|
sk_X509_free(intermediates);
|
|
|
|
sk_X509_free(root);
|
2023-04-30 01:15:27 +00:00
|
|
|
sk_X509_CRL_free(crls);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_STORE_CTX_cleanup(store_ctx);
|
2023-05-13 14:25:18 +00:00
|
|
|
sk_X509_free(intermediates);
|
|
|
|
sk_X509_free(root);
|
2023-04-30 01:15:27 +00:00
|
|
|
sk_X509_CRL_free(crls);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our RSC: check that all items in the ResourceBlock are contained.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_rsc(const char *fn, struct cert *cert, struct rsc *rsc)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
uint32_t min, max;
|
2023-09-28 08:40:30 +00:00
|
|
|
char buf[128];
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
for (i = 0; i < rsc->asz; i++) {
|
2023-09-28 08:40:30 +00:00
|
|
|
if (rsc->as[i].type == CERT_AS_ID) {
|
|
|
|
min = rsc->as[i].id;
|
|
|
|
max = rsc->as[i].id;
|
|
|
|
} else {
|
|
|
|
min = rsc->as[i].range.min;
|
|
|
|
max = rsc->as[i].range.max;
|
|
|
|
}
|
2023-04-30 01:15:27 +00:00
|
|
|
|
|
|
|
if (as_check_covered(min, max, cert->as, cert->asz) > 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (rsc->as[i].type) {
|
|
|
|
case CERT_AS_ID:
|
2023-09-28 08:40:30 +00:00
|
|
|
warnx("%s: RSC resourceBlock: uncovered AS: %u", fn,
|
|
|
|
min);
|
2023-04-30 01:15:27 +00:00
|
|
|
break;
|
|
|
|
case CERT_AS_RANGE:
|
2023-09-28 08:40:30 +00:00
|
|
|
warnx("%s: RSC resourceBlock: uncovered AS: %u--%u",
|
|
|
|
fn, min, max);
|
2023-04-30 01:15:27 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < rsc->ipsz; i++) {
|
|
|
|
if (ip_addr_check_covered(rsc->ips[i].afi, rsc->ips[i].min,
|
|
|
|
rsc->ips[i].max, cert->ips, cert->ipsz) > 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (rsc->ips[i].type) {
|
|
|
|
case CERT_IP_ADDR:
|
2023-09-28 08:40:30 +00:00
|
|
|
ip_addr_print(&rsc->ips[i].ip, rsc->ips[i].afi, buf,
|
|
|
|
sizeof(buf));
|
|
|
|
warnx("%s: RSC ResourceBlock: uncovered IP: %s", fn,
|
|
|
|
buf);
|
|
|
|
break;
|
|
|
|
case CERT_IP_RANGE:
|
|
|
|
ip_addr_range_print(&rsc->ips[i].range, rsc->ips[i].afi,
|
|
|
|
buf, sizeof(buf));
|
|
|
|
warnx("%s: RSC ResourceBlock: uncovered IP: %s", fn,
|
|
|
|
buf);
|
2023-04-30 01:15:27 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2023-06-07 21:20:56 +00:00
|
|
|
valid_econtent_version(const char *fn, const ASN1_INTEGER *aint,
|
|
|
|
uint64_t expected)
|
2023-04-30 01:15:27 +00:00
|
|
|
{
|
2023-06-07 21:20:56 +00:00
|
|
|
uint64_t version;
|
2023-04-30 01:15:27 +00:00
|
|
|
|
2023-06-07 21:20:56 +00:00
|
|
|
if (aint == NULL) {
|
|
|
|
if (expected == 0)
|
|
|
|
return 1;
|
|
|
|
warnx("%s: unexpected version 0", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-04-30 01:15:27 +00:00
|
|
|
|
2023-06-07 21:20:56 +00:00
|
|
|
if (!ASN1_INTEGER_get_uint64(&version, aint)) {
|
|
|
|
warnx("%s: ASN1_INTEGER_get_uint64 failed", fn);
|
2023-04-30 01:15:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-06-07 21:20:56 +00:00
|
|
|
if (version == 0) {
|
2023-04-30 01:15:27 +00:00
|
|
|
warnx("%s: incorrect encoding for version 0", fn);
|
|
|
|
return 0;
|
2023-06-07 21:20:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (version != expected) {
|
|
|
|
warnx("%s: unexpected version (expected %llu, got %llu)", fn,
|
|
|
|
(unsigned long long)expected, (unsigned long long)version);
|
2023-04-30 01:15:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2023-06-07 21:20:56 +00:00
|
|
|
|
|
|
|
return 1;
|
2023-04-30 01:15:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the ASPA: check that the customerASID is contained.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_aspa(const char *fn, struct cert *cert, struct aspa *aspa)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (as_check_covered(aspa->custasid, aspa->custasid,
|
|
|
|
cert->as, cert->asz) > 0)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
warnx("%s: ASPA: uncovered Customer ASID: %u", fn, aspa->custasid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate Geofeed prefixes: check that the prefixes are contained.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_geofeed(const char *fn, struct cert *cert, struct geofeed *g)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
char buf[64];
|
|
|
|
|
|
|
|
for (i = 0; i < g->geoipsz; i++) {
|
|
|
|
if (ip_addr_check_covered(g->geoips[i].ip->afi,
|
|
|
|
g->geoips[i].ip->min, g->geoips[i].ip->max, cert->ips,
|
|
|
|
cert->ipsz) > 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ip_addr_print(&g->geoips[i].ip->ip, g->geoips[i].ip->afi, buf,
|
|
|
|
sizeof(buf));
|
|
|
|
warnx("%s: Geofeed: uncovered IP: %s", fn, buf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate whether a given string is a valid UUID.
|
|
|
|
* Returns 1 if valid, 0 otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_uuid(const char *s)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
switch (n) {
|
|
|
|
case 8:
|
|
|
|
case 13:
|
|
|
|
case 18:
|
|
|
|
case 23:
|
|
|
|
if (s[n] != '-')
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
/* Check UUID is version 4 */
|
|
|
|
case 14:
|
|
|
|
if (s[n] != '4')
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
/* Check UUID variant is 1 */
|
|
|
|
case 19:
|
|
|
|
if (s[n] != '8' && s[n] != '9' && s[n] != 'a' &&
|
|
|
|
s[n] != 'A' && s[n] != 'b' && s[n] != 'B')
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case 36:
|
|
|
|
return s[n] == '\0';
|
|
|
|
default:
|
|
|
|
if (!isxdigit((unsigned char)s[n]))
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 01:29:48 +00:00
|
|
|
static int
|
|
|
|
valid_ca_pkey_rsa(const char *fn, EVP_PKEY *pkey)
|
2023-04-30 01:15:27 +00:00
|
|
|
{
|
|
|
|
RSA *rsa;
|
|
|
|
const BIGNUM *rsa_e;
|
|
|
|
int key_bits;
|
|
|
|
|
|
|
|
if ((key_bits = EVP_PKEY_bits(pkey)) != 2048) {
|
|
|
|
warnx("%s: RFC 7935: expected 2048-bit modulus, got %d bits",
|
|
|
|
fn, key_bits);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) {
|
|
|
|
warnx("%s: failed to extract RSA public key", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((rsa_e = RSA_get0_e(rsa)) == NULL) {
|
|
|
|
warnx("%s: failed to get RSA exponent", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!BN_is_word(rsa_e, 65537)) {
|
|
|
|
warnx("%s: incorrect exponent (e) in RSA public key", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
2023-11-11 01:29:48 +00:00
|
|
|
|
|
|
|
static int
|
|
|
|
valid_ca_pkey_ec(const char *fn, EVP_PKEY *pkey)
|
|
|
|
{
|
|
|
|
EC_KEY *ec;
|
|
|
|
const EC_GROUP *group;
|
|
|
|
int nid;
|
|
|
|
const char *cname;
|
|
|
|
|
|
|
|
if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) {
|
|
|
|
warnx("%s: failed to extract ECDSA public key", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((group = EC_KEY_get0_group(ec)) == NULL) {
|
|
|
|
warnx("%s: EC_KEY_get0_group failed", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nid = EC_GROUP_get_curve_name(group);
|
|
|
|
if (nid != NID_X9_62_prime256v1) {
|
|
|
|
if ((cname = EC_curve_nid2nist(nid)) == NULL)
|
|
|
|
cname = OBJ_nid2sn(nid);
|
|
|
|
warnx("%s: Expected P-256, got %s", fn, cname);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!EC_KEY_check_key(ec)) {
|
|
|
|
warnx("%s: EC_KEY_check_key failed", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
valid_ca_pkey(const char *fn, EVP_PKEY *pkey)
|
|
|
|
{
|
|
|
|
if (pkey == NULL) {
|
|
|
|
warnx("%s: failure, pkey is NULL", fn);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA)
|
|
|
|
return valid_ca_pkey_rsa(fn, pkey);
|
|
|
|
|
|
|
|
if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC)
|
|
|
|
return valid_ca_pkey_ec(fn, pkey);
|
|
|
|
|
|
|
|
warnx("%s: unsupported public key algorithm", fn);
|
|
|
|
return 0;
|
|
|
|
}
|