--- /dev/null
+/* $OpenBSD: policy.c,v 1.1 2023/04/27 12:23:31 beck Exp $ */
+/*
+ * Copyright (c) 2020 Joel Sing <jsing@openbsd.org>
+ * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org>
+ *
+ * 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 <err.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "x509_verify.h"
+
+#define MODE_MODERN_VFY 0
+#define MODE_MODERN_VFY_DIR 1
+#define MODE_LEGACY_VFY 2
+#define MODE_VERIFY 3
+
+static int verbose = 1;
+
+#define OID1 "1.2.840.113554.4.1.72585.2.1"
+#define OID2 "1.2.840.113554.4.1.72585.2.2"
+#define OID3 "1.2.840.113554.4.1.72585.2.3"
+#define OID4 "1.2.840.113554.4.1.72585.2.4"
+#define OID5 "1.2.840.113554.4.1.72585.2.5"
+
+#ifndef CERTSDIR
+#define CERTSDIR "."
+#endif
+
+static int
+passwd_cb(char *buf, int size, int rwflag, void *u)
+{
+ memset(buf, 0, size);
+ return (0);
+}
+
+static int
+certs_from_file(const char *filename, STACK_OF(X509) **certs)
+{
+ STACK_OF(X509_INFO) *xis = NULL;
+ STACK_OF(X509) *xs = NULL;
+ BIO *bio = NULL;
+ X509 *x;
+ int i;
+
+ if (*certs == NULL) {
+ if ((xs = sk_X509_new_null()) == NULL)
+ errx(1, "failed to create X509 stack");
+ } else {
+ xs = *certs;
+ }
+ if ((bio = BIO_new_file(filename, "r")) == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "failed to create bio");
+ }
+ if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL)
+ errx(1, "failed to read PEM");
+
+ for (i = 0; i < sk_X509_INFO_num(xis); i++) {
+ if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL)
+ continue;
+ if (!sk_X509_push(xs, x))
+ errx(1, "failed to push X509");
+ X509_up_ref(x);
+ }
+
+ *certs = xs;
+ xs = NULL;
+
+ sk_X509_INFO_pop_free(xis, X509_INFO_free);
+ sk_X509_pop_free(xs, X509_free);
+ BIO_free(bio);
+
+ return 1;
+}
+
+static int
+verify_cert_cb(int ok, X509_STORE_CTX *xsc)
+{
+ X509 *current_cert;
+ int verify_err;
+
+ current_cert = X509_STORE_CTX_get_current_cert(xsc);
+ if (current_cert != NULL) {
+ X509_NAME_print_ex_fp(stderr,
+ X509_get_subject_name(current_cert), 0,
+ XN_FLAG_ONELINE);
+ fprintf(stderr, "\n");
+ }
+
+ verify_err = X509_STORE_CTX_get_error(xsc);
+ if (verify_err != X509_V_OK) {
+ fprintf(stderr, "verify error at depth %d: %s\n",
+ X509_STORE_CTX_get_error_depth(xsc),
+ X509_verify_cert_error_string(verify_err));
+ }
+
+ return ok;
+}
+
+static void
+verify_cert(const char *roots_file, const char *intermediate_file,
+ const char *leaf_file, int *chains, int *error, int *error_depth,
+ int mode, ASN1_OBJECT *policy_oid, ASN1_OBJECT *policy_oid2)
+{
+ STACK_OF(X509) *roots = NULL, *bundle = NULL;
+ X509_STORE_CTX *xsc = NULL;
+ X509_STORE *store = NULL;
+ X509 *leaf = NULL;
+ int ret;
+
+ *chains = 0;
+ *error = 0;
+ *error_depth = 0;
+
+
+ if (!certs_from_file(roots_file, &roots))
+ errx(1, "failed to load roots from '%s'", roots_file);
+ if (!certs_from_file(leaf_file, &bundle))
+ errx(1, "failed to load leaf from '%s'", leaf_file);
+ if (intermediate_file != NULL && !certs_from_file(intermediate_file,
+ &bundle))
+ errx(1, "failed to load intermediate from '%s'",
+ intermediate_file);
+ printf ("%d certs %d roots\n", sk_X509_num(bundle), sk_X509_num(roots));
+ if (sk_X509_num(bundle) < 1)
+ errx(1, "not enough certs in bundle");
+ leaf = sk_X509_shift(bundle);
+
+ if ((xsc = X509_STORE_CTX_new()) == NULL)
+ errx(1, "X509_STORE_CTX");
+ if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "failed to init store context");
+ }
+
+ int flags = X509_V_FLAG_POLICY_CHECK;
+ flags |= X509_V_FLAG_EXPLICIT_POLICY;
+ // flags |= X509_V_FLAG_INHIBIT_MAP;
+ if (mode == MODE_LEGACY_VFY)
+ flags |= X509_V_FLAG_LEGACY_VERIFY;
+ X509_STORE_CTX_set_flags(xsc, flags);
+
+ if (verbose)
+ X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb);
+ X509_STORE_CTX_set0_trusted_stack(xsc, roots);
+
+ if (policy_oid != NULL) {
+ X509_VERIFY_PARAM * param = X509_STORE_CTX_get0_param(xsc);
+ ASN1_OBJECT * copy = OBJ_dup(policy_oid);
+ X509_VERIFY_PARAM_add0_policy(param, copy);
+ }
+ if (policy_oid2 != NULL) {
+ X509_VERIFY_PARAM * param = X509_STORE_CTX_get0_param(xsc);
+ ASN1_OBJECT * copy = OBJ_dup(policy_oid2);
+ X509_VERIFY_PARAM_add0_policy(param, copy);
+ }
+
+ ret = X509_verify_cert(xsc);
+
+ *error = X509_STORE_CTX_get_error(xsc);
+ *error_depth = X509_STORE_CTX_get_error_depth(xsc);
+
+ if (ret == 1) {
+ *chains = 1; /* XXX */
+ goto done;
+ }
+
+ if (*error == 0)
+ errx(1, "Error unset on failure!\n");
+
+ fprintf(stderr, "failed to verify at %d: %s\n",
+ *error_depth, X509_verify_cert_error_string(*error));
+
+ done:
+ sk_X509_pop_free(roots, X509_free);
+ sk_X509_pop_free(bundle, X509_free);
+ X509_STORE_free(store);
+ X509_STORE_CTX_free(xsc);
+ X509_free(leaf);
+}
+
+static void
+verify_cert_new(const char *roots_file, const char *intermediate_file,
+ const char*leaf_file, int *chains)
+{
+ STACK_OF(X509) *roots = NULL, *bundle = NULL;
+ X509_STORE_CTX *xsc = NULL;
+ X509 *leaf = NULL;
+ struct x509_verify_ctx *ctx;
+
+ *chains = 0;
+
+ if (!certs_from_file(roots_file, &roots))
+ errx(1, "failed to load roots from '%s'", roots_file);
+ if (!certs_from_file(leaf_file, &bundle))
+ errx(1, "failed to load leaf from '%s'", leaf_file);
+ if (intermediate_file != NULL && !certs_from_file(intermediate_file,
+ &bundle))
+ errx(1, "failed to load intermediate from '%s'",
+ intermediate_file);
+ if (sk_X509_num(bundle) < 1)
+ errx(1, "not enough certs in bundle");
+ leaf = sk_X509_shift(bundle);
+
+ if ((xsc = X509_STORE_CTX_new()) == NULL)
+ errx(1, "X509_STORE_CTX");
+ if (!X509_STORE_CTX_init(xsc, NULL, leaf, bundle)) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "failed to init store context");
+ }
+ if (verbose)
+ X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb);
+
+ if ((ctx = x509_verify_ctx_new(roots)) == NULL)
+ errx(1, "failed to create ctx");
+ if (!x509_verify_ctx_set_intermediates(ctx, bundle))
+ errx(1, "failed to set intermediates");
+
+ if ((*chains = x509_verify(ctx, leaf, NULL)) == 0) {
+ fprintf(stderr, "failed to verify at %lu: %s\n",
+ x509_verify_ctx_error_depth(ctx),
+ x509_verify_ctx_error_string(ctx));
+ } else {
+ int c;
+
+ for (c = 0; verbose && c < *chains; c++) {
+ STACK_OF(X509) *chain;
+ int i;
+
+ fprintf(stderr, "Chain %d\n--------\n", c);
+ chain = x509_verify_ctx_chain(ctx, c);
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ X509 *cert = sk_X509_value(chain, i);
+ X509_NAME_print_ex_fp(stderr,
+ X509_get_subject_name(cert), 0,
+ XN_FLAG_ONELINE);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+ sk_X509_pop_free(roots, X509_free);
+ sk_X509_pop_free(bundle, X509_free);
+ X509_free(leaf);
+ X509_STORE_CTX_free(xsc);
+ x509_verify_ctx_free(ctx);
+}
+
+struct verify_cert_test {
+ const char *id;
+ const char *root_file;
+ const char *intermediate_file;
+ const char *leaf_file;
+ const char *policy_oid_to_check;
+ const char *policy_oid_to_check2;
+ int want_chains;
+ int want_error;
+ int want_error_depth;
+ int want_legacy_error;
+ int want_legacy_error_depth;
+ int failing;
+};
+
+struct verify_cert_test verify_cert_tests[] = {
+ // The chain is good for |oid1| and |oid2|, but not |oid3|.
+ {
+ .id = "nothing in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .want_chains = 1,
+ },
+ {
+ .id = "1, in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID1,
+ .want_chains = 1,
+ },
+ {
+ .id = "2, in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID2,
+ .want_chains = 1,
+ },
+ {
+ .id = "3, in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID2,
+ .want_chains = 0,
+ },
+ {
+ .id = "1 and 2, in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID1,
+ .policy_oid_to_check2 = OID2,
+ .want_chains = 1,
+ },
+ {
+ .id = "1 and 3, in 1 and 2",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID1,
+ .policy_oid_to_check2 = OID3,
+ .want_chains = 1,
+ },
+ // The policy extension cannot be parsed.
+ {
+ .id = "1 in invalid intermediate poicy",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate_invalid.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID1,
+ .want_chains = 0,
+ },
+ {
+ .id = "invalid intermediate",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate_invalid.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .want_chains = 0,
+ },
+ {
+ .id = "1 in invalid policy in leaf",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf_invalid.pem",
+ .policy_oid_to_check = OID1,
+ .want_chains = 0,
+ },
+ {
+ .id = "invalid leaf",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf_invalid.pem",
+ .want_chains = 0,
+ },
+ // There is a duplicate policy in the leaf policy extension.
+ {
+ .id = "1 in duplicate policy extension in leaf",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf_duplicate.pem",
+ .policy_oid_to_check = OID1,
+ .want_chains = 0,
+ },
+ // There is a duplicate policy in the intermediate policy extension.
+ {
+ .id = "1 in duplicate policy extension in intermediate",
+ .root_file = CERTSDIR "/" "policy_root.pem",
+ .intermediate_file = CERTSDIR "/" "policy_intermediate_duplicate.pem",
+ .leaf_file = CERTSDIR "/" "policy_leaf.pem",
+ .policy_oid_to_check = OID1,
+ .want_chains = 0,
+ },
+};
+
+#define N_VERIFY_CERT_TESTS \
+ (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests))
+
+static int
+verify_cert_test(int mode)
+{
+ struct verify_cert_test *vct;
+ int chains, error, error_depth;
+ int failed = 0;
+ size_t i;
+
+ for (i = 0; i < N_VERIFY_CERT_TESTS; i++) {
+ vct = &verify_cert_tests[i];
+ ASN1_OBJECT *policy_oid = vct->policy_oid_to_check ?
+ OBJ_txt2obj(vct->policy_oid_to_check, 1) : NULL;
+ ASN1_OBJECT *policy_oid2 = vct->policy_oid_to_check2 ?
+ OBJ_txt2obj(vct->policy_oid_to_check2, 1) : NULL;
+
+ error = 0;
+ error_depth = 0;
+
+ fprintf(stderr, "== Test %zu (%s)\n", i, vct->id);
+ if (mode == MODE_VERIFY)
+ verify_cert_new(vct->root_file, vct->intermediate_file,
+ vct->leaf_file, &chains);
+ else
+ verify_cert(vct->root_file, vct->intermediate_file,
+ vct->leaf_file, &chains, &error, &error_depth,
+ mode, policy_oid, policy_oid2);
+
+ if ((mode == MODE_VERIFY && chains == vct->want_chains) ||
+ (chains == 0 && vct->want_chains == 0) ||
+ (chains == 1 && vct->want_chains > 0)) {
+ fprintf(stderr, "INFO: Succeeded with %d chains%s\n",
+ chains, vct->failing ? " (legacy failure)" : "");
+ if (mode == MODE_LEGACY_VFY && vct->failing)
+ failed |= 1;
+ } else {
+ fprintf(stderr, "FAIL: Failed with %d chains%s\n",
+ chains, vct->failing ? " (legacy failure)" : "");
+ if (!vct->failing)
+ failed |= 1;
+ }
+
+ if (mode == MODE_LEGACY_VFY) {
+ if (error != vct->want_legacy_error) {
+ fprintf(stderr, "FAIL: Got legacy error %d, "
+ "want %d\n", error, vct->want_legacy_error);
+ failed |= 1;
+ }
+ if (error_depth != vct->want_legacy_error_depth) {
+ fprintf(stderr, "FAIL: Got legacy error depth "
+ "%d, want %d\n", error_depth,
+ vct->want_legacy_error_depth);
+ failed |= 1;
+ }
+ }
+ fprintf(stderr, "\n");
+ ASN1_OBJECT_free(policy_oid);
+ ASN1_OBJECT_free(policy_oid2);
+
+ }
+ return failed;
+}
+
+int
+main(int argc, char **argv)
+{
+ int failed = 0;
+
+ fprintf(stderr, "\n\nTesting legacy x509_vfy\n");
+ failed |= verify_cert_test(MODE_LEGACY_VFY);
+ fprintf(stderr, "\n\nTesting modern x509_vfy\n");
+ failed |= verify_cert_test(MODE_MODERN_VFY);
+ // New does not support policy goo at the moment.
+ // fprintf(stderr, "\n\nTestin x509_verify\n");
+ // failed |= verify_cert_test(MODE_VERIFY);
+
+ return (failed);
+}