summaryrefslogtreecommitdiff
path: root/regress
diff options
context:
space:
mode:
authorJoel Sing <jsing@cvs.openbsd.org>2020-07-14 18:27:29 +0000
committerJoel Sing <jsing@cvs.openbsd.org>2020-07-14 18:27:29 +0000
commitd6d1b9677130e7050d98626266a707ec00843340 (patch)
treed4051cc528e0f4f3c459aa4185bf049a553598ec /regress
parenta847b703f2e49b63b3d32cf614b5f4bfa9ed76de (diff)
Provide tools to build certificate changes for verify regress.
This provides a script that generates a variety of certificate chains and assembles them into bundles containing various permutations, which can be used to test our X.509 verification. A Go program is included to verify each of these bundles. ok beck@ tb@
Diffstat (limited to 'regress')
-rw-r--r--regress/lib/libcrypto/certs/Makefile21
-rw-r--r--regress/lib/libcrypto/certs/README123
-rw-r--r--regress/lib/libcrypto/certs/make-certs.sh381
-rw-r--r--regress/lib/libcrypto/certs/verify_test.go111
4 files changed, 636 insertions, 0 deletions
diff --git a/regress/lib/libcrypto/certs/Makefile b/regress/lib/libcrypto/certs/Makefile
new file mode 100644
index 00000000000..621c60907fb
--- /dev/null
+++ b/regress/lib/libcrypto/certs/Makefile
@@ -0,0 +1,21 @@
+# $OpenBSD: Makefile,v 1.1 2020/07/14 18:27:28 jsing Exp $
+
+.if ! (make(clean) || make(cleandir) || make(obj))
+GO_VERSION != sh -c "(go version) 2>/dev/null || true"
+.endif
+
+.if empty(GO_VERSION)
+regress:
+ @echo package go is required for this regress
+ @echo SKIPPED
+.endif
+
+REGRESS_TARGETS=regress-go-verify
+
+certs:
+ cd ${.CURDIR} && sh ./make-certs.sh
+
+regress-go-verify:
+ cd ${.CURDIR} && go test -test.v .
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libcrypto/certs/README b/regress/lib/libcrypto/certs/README
new file mode 100644
index 00000000000..0d6fa7d48a2
--- /dev/null
+++ b/regress/lib/libcrypto/certs/README
@@ -0,0 +1,123 @@
+This directory contains tools for building certificate chains to
+test verification. Each subdirectory contains a set of certificates
+that test a particular scenario. The root certificate(s) are contained
+in a roots.pem file, while the leaf certificate and any untrusted
+intermediate certificates are contained in a bundle.pem file.
+
+ 1a. A leaf certificate signed by the root certificate with no
+ intermediates (should verify).
+
+ 2a. A leaf certificate signed by an intermediate, which is signed
+ by a root certificate (should verify).
+
+ 2b. Same as (2a), however the intermediate is missing which should
+ prevent verification.
+
+ 3a. A leaf certificate signed by three intermediates, the last of
+ which is signed by a root certificate (should verify).
+
+ 3b. Same as (3a) however the first intermediate is missing which should
+ prevent verification.
+
+ 3c. Same as (3a) however the second intermediate is missing which should
+ prevent verification.
+
+ 3d. Same as (3a) however the third intermediate is missing which should
+ prevent verification.
+
+ 3e. Same as (3a) however the order of the intermediates is reversed
+ (should verify).
+
+ 4a. A leaf certificate signed by an intermediate, that is cross
+ signed by two root certificates (should verify with two chains).
+
+ 4b. Same as (4a) but with one root missing (should verify with one chain).
+
+ 4c. Same as (4b) but with the other root missing (should verify with one
+ chain).
+
+ 4d. Same as (4a) but with one intermediate missing (should verify with one
+ chain).
+
+ 4e. Same as (4d) but with the other intermediate missing (should verify
+ with one chain).
+
+ 4f. Same as (4a) but with the intermediates reversed (should verify with
+ two chains).
+
+ 4g. Same as (4b) but with the intermediates reversed (should verify with
+ one chain).
+
+ 4h. Same as (4c) but with the intermediates reversed (should verify with
+ one chain).
+
+ 5a. A leaf certificate signed by an intermediate, that is cross
+ signed by one root certificate and an intermediate, which in turn
+ is signed by a second root (should verify with two chains).
+
+ 5b. Same as (5a) but missing the first root certificate (should verify
+ with one chain).
+
+ 5c. Same as (5a) but missing the second root certificate (should verify
+ with one chain).
+
+ 5d. Same as (5a) but missing the first intermediate (should verify with
+ one chain).
+
+ 5e. Same as (5a) but missing the second intermediate (should verify
+ with one chain).
+
+ 5f. Same as (5a) but missing the cross-signed intermediate (should verify
+ with one chain).
+
+ 5g. Same as (5a) but order of intermediates is reversed (should verify
+ with two chains).
+
+ 5h. Same as (5g) but missing the first root certificate (should verify
+ with two chains).
+
+ 5i. Same as (5g) but missing the second root certificate (should verify
+ with two chains).
+
+ 6a. A leaf certificate signed by an intermediate, that is cross
+ signed by an expired root certificate and an intermediate, which
+ in turn is signed by a second root (should verify with one chain).
+
+ 6b. Same as (6a) but the order of the intermediates is reversed (should
+ verify with one chain).
+
+ 7a. A leaf certificate signed by an intermediate, that is cross
+ signed by a root certificate and an intermediate, which in turn
+ is signed by a second root that has expired (should verify with one
+ chain).
+
+ 7b. Same as (7a) but the order of the intermediates is reversed (should
+ verify with one chain).
+
+ 8a. An expired leaf certificate signed by an intermediate that is then
+ signed by a root certificate (should fail to verify).
+
+ 9a. A leaf certificate signed by an expired intermediate, which is
+ signed by a root certificate (should fail to verify).
+
+10a. A leaf certificate signed by an intermediate, that is cross
+ signed by two root certificates, with one of the cross signings
+ having expired (should verify with one chain).
+
+10b. Same as (10a) but order of intermediates is reversed (should verify
+ with one chain.
+
+11a. A leaf certificate signed by an intermediate, that is cross
+ signed by one root certificate and an expired intermediate, which
+ in turn is signed by a second root (should verify with one chain).
+
+11b. Same as (11a) but order of intermediates is reversed (should verify
+ with one chain.
+
+12a. A leaf certificate signed by an intermediate, that is signed by a
+ root certificate and cross signed as an expired intermediate, by a
+ second root (should verify with one chain).
+
+13a. A leaf certificate signed by an intermediate, that is signed by an
+ expired root certificate and cross signed as an intermediate, by a
+ second root (should verify with one chain).
diff --git a/regress/lib/libcrypto/certs/make-certs.sh b/regress/lib/libcrypto/certs/make-certs.sh
new file mode 100644
index 00000000000..b34a547ba25
--- /dev/null
+++ b/regress/lib/libcrypto/certs/make-certs.sh
@@ -0,0 +1,381 @@
+#!/bin/ksh
+
+#
+# Copyright (c) 2020 Joel Sing <jsing@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.
+#
+
+set -e
+set -u
+set -x
+
+readonly SUBJECT="/CN=LibreSSL Test"
+
+readonly TMPDIR=$(mktemp -d)
+
+cleanup() {
+ rm -rf "${TMPDIR}"
+}
+
+trap cleanup EXIT INT
+
+reset() {
+ echo '100001' > ${TMPDIR}/certserial
+ cat /dev/null > ${TMPDIR}/certindex
+}
+
+setup() {
+ reset
+
+ cat > ${TMPDIR}/openssl.cnf <<EOF
+[ca]
+default_ca = test_ca
+
+[test_ca]
+new_certs_dir = ${TMPDIR}/
+database = ${TMPDIR}/certindex
+default_days = 365
+default_md = sha256
+policy = test_policy
+serial = ${TMPDIR}/certserial
+
+[test_policy]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[v3_ca_root]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, cRLSign, keyCertSign
+
+[v3_ca_int]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, cRLSign, keyCertSign
+
+[v3_other]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:false
+keyUsage = critical, digitalSignature
+
+[req]
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+EOF
+}
+
+create_root() {
+ local name=$1 file=$2
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 -x509 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_ca_root \
+ -out "${TMPDIR}/${file}.crt"
+}
+
+create_expired_root() {
+ local name=$1 file=$2
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_ca_root \
+ -out "${TMPDIR}/${file}.csr"
+ openssl ca -batch -notext -selfsign \
+ -keyfile "${TMPDIR}/${file}.key" \
+ -startdate 20100101000000Z -enddate 20200101000000Z \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_ca_root \
+ -in "${TMPDIR}/${file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_intermediate() {
+ local name=$1 file=$2 issuer_file=$3
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -out "${TMPDIR}/${file}.csr"
+ openssl x509 -req -days 3650 -CA "${TMPDIR}/${issuer_file}.crt" \
+ -CAkey "${TMPDIR}/${issuer_file}.key" -CAcreateserial \
+ -extfile ${TMPDIR}/openssl.cnf -extensions v3_ca_int \
+ -in "${TMPDIR}/${file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_expired_intermediate() {
+ local name=$1 file=$2 issuer_file=$3
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -out "${TMPDIR}/${file}.csr"
+ openssl ca -batch -notext -cert "${TMPDIR}/${issuer_file}.crt" \
+ -keyfile "${TMPDIR}/${issuer_file}.key" \
+ -startdate 20100101000000Z -enddate 20200101000000Z \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_ca_int \
+ -in "${TMPDIR}/${file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_leaf() {
+ local name=$1 file=$2 issuer_file=$3
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -out "${TMPDIR}/${file}.csr"
+ openssl x509 -req -days 3650 -CA "${TMPDIR}/${issuer_file}.crt" \
+ -CAkey "${TMPDIR}/${issuer_file}.key" -CAcreateserial \
+ -extfile ${TMPDIR}/openssl.cnf -extensions v3_other \
+ -in "${TMPDIR}/${file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_expired_leaf() {
+ local name=$1 file=$2 issuer_file=$3
+
+ openssl req -new -days 3650 -nodes -newkey rsa:2048 -sha256 \
+ -subj "${SUBJECT} ${name}" -keyout "${TMPDIR}/${file}.key" \
+ -out "${TMPDIR}/${file}.csr"
+ openssl ca -batch -notext -cert "${TMPDIR}/${issuer_file}.crt" \
+ -keyfile "${TMPDIR}/${issuer_file}.key" \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_other \
+ -startdate 20100101000000Z -enddate 20200101000000Z \
+ -in "${TMPDIR}/${file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_cross_signed() {
+ local file=$1 csr_file=$2 issuer_file=$3
+
+ openssl x509 -req -days 3650 -CA "${TMPDIR}/${issuer_file}.crt" \
+ -CAkey "${TMPDIR}/${issuer_file}.key" -CAcreateserial \
+ -extfile ${TMPDIR}/openssl.cnf -extensions v3_ca_int \
+ -in "${TMPDIR}/${csr_file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_expired_cross_signed() {
+ local file=$1 csr_file=$2 issuer_file=$3
+
+ openssl ca -batch -notext -cert "${TMPDIR}/${issuer_file}.crt" \
+ -keyfile "${TMPDIR}/${issuer_file}.key" \
+ -startdate 20100101000000Z -enddate 20200101000000Z \
+ -config ${TMPDIR}/openssl.cnf -extensions v3_ca_int \
+ -in "${TMPDIR}/${csr_file}.csr" -out "${TMPDIR}/${file}.crt"
+}
+
+create_bundle() {
+ local bundle_file=$1
+ shift
+
+ mkdir -p $(dirname ${bundle_file})
+ cat /dev/null > ${bundle_file}
+
+ for _cert_file in $@; do
+ openssl x509 -nameopt oneline -subject -issuer -noout \
+ -in "${TMPDIR}/${_cert_file}.crt" >> ${bundle_file}
+ done
+ for _cert_file in $@; do
+ cat "${TMPDIR}/${_cert_file}.crt" >> ${bundle_file}
+ done
+}
+
+create_root_bundle() {
+ local bundle_file=$1
+ shift
+
+ mkdir -p $(dirname ${bundle_file})
+ cat /dev/null > ${bundle_file}
+
+ for _cert_file in $@; do
+ openssl x509 -nameopt oneline -subject -issuer \
+ -in "${TMPDIR}/${_cert_file}.crt" >> ${bundle_file}
+ done
+}
+
+setup
+
+# Scenario 1a.
+reset
+create_root "Root CA 1" "ca-root"
+create_leaf "Server 1" "server-1" "ca-root"
+create_root_bundle "./1a/roots.pem" "ca-root"
+create_bundle "./1a/bundle.pem" "server-1"
+
+# Scenarios 2a and 2b.
+reset
+create_root "Root CA 1" "ca-root"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root"
+create_leaf "Server 1" "server-1" "ca-int-1"
+create_root_bundle "./2a/roots.pem" "ca-root"
+create_bundle "./2a/bundle.pem" "server-1" "ca-int-1"
+create_root_bundle "./2b/roots.pem" "ca-root"
+create_bundle "./2b/bundle.pem" "server-1"
+
+# Scenarios 3a, 3b, 3c, 3d and 3e.
+reset
+create_root "Root CA 1" "ca-root"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_intermediate "Intermediate CA 3" "ca-int-3" "ca-int-2"
+create_leaf "Server 1" "server-1" "ca-int-3"
+create_root_bundle "./3a/roots.pem" "ca-root"
+create_bundle "./3a/bundle.pem" "server-1" "ca-int-3" "ca-int-2" "ca-int-1"
+create_root_bundle "./3b/roots.pem" "ca-root"
+create_bundle "./3b/bundle.pem" "server-1" "ca-int-3" "ca-int-2"
+create_root_bundle "./3c/roots.pem" "ca-root"
+create_bundle "./3c/bundle.pem" "server-1" "ca-int-3" "ca-int-1"
+create_root_bundle "./3d/roots.pem" "ca-root"
+create_bundle "./3d/bundle.pem" "server-1" "ca-int-2" "ca-int-1"
+create_root_bundle "./3e/roots.pem" "ca-root"
+create_bundle "./3e/bundle.pem" "server-1" "ca-int-1" "ca-int-2" "ca-int-3"
+
+# Scenarios 4a, 4b, 4c, 4d, 4e, 4f, 4g and 4h.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_root "Root CA 2" "ca-root-2"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_cross_signed "ca-int-1-xs" "ca-int-1" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-1"
+create_root_bundle "./4a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./4a/bundle.pem" "server-1" "ca-int-1" "ca-int-1-xs"
+create_root_bundle "./4b/roots.pem" "ca-root-1"
+create_bundle "./4b/bundle.pem" "server-1" "ca-int-1" "ca-int-1-xs"
+create_root_bundle "./4c/roots.pem" "ca-root-2"
+create_bundle "./4c/bundle.pem" "server-1" "ca-int-1" "ca-int-1-xs"
+create_root_bundle "./4d/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./4d/bundle.pem" "server-1" "ca-int-1"
+create_root_bundle "./4e/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./4e/bundle.pem" "server-1" "ca-int-1-xs"
+create_root_bundle "./4f/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./4f/bundle.pem" "server-1" "ca-int-1-xs" "ca-int-1"
+create_root_bundle "./4g/roots.pem" "ca-root-1"
+create_bundle "./4g/bundle.pem" "server-1" "ca-int-1-xs" "ca-int-1"
+create_root_bundle "./4h/roots.pem" "ca-root-2"
+create_bundle "./4h/bundle.pem" "server-1" "ca-int-1-xs" "ca-int-1"
+
+# Scenario 5a, 5b, 5c, 5d, 5e, 5f, 5g, 5h and 5i.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_root "Root CA 2" "ca-root-2"
+create_cross_signed "ca-int-2-xs" "ca-int-2" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./5a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./5a/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./5b/roots.pem" "ca-root-2"
+create_bundle "./5b/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./5c/roots.pem" "ca-root-1"
+create_bundle "./5c/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./5d/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./5d/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./5e/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./5e/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs"
+create_root_bundle "./5f/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./5f/bundle.pem" "server-1" "ca-int-2" "ca-int-1"
+create_root_bundle "./5g/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./5g/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+create_root_bundle "./5h/roots.pem" "ca-root-2"
+create_bundle "./5h/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+create_root_bundle "./5i/roots.pem" "ca-root-1"
+create_bundle "./5i/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+
+# Scenarios 6a and 6b.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_expired_root "Root CA 2" "ca-root-2"
+create_cross_signed "ca-int-2-xs" "ca-int-2" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./6a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./6a/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./6b/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./6b/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+
+# Scenarios 7a and 7b.
+reset
+create_expired_root "Root CA 1" "ca-root-1"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_root "Root CA 2" "ca-root-2"
+create_cross_signed "ca-int-2-xs" "ca-int-2" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./7a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./7a/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./7b/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./7b/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+
+# Scenario 8a.
+reset
+create_root "Root CA 1" "ca-root"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root"
+create_expired_leaf "Server 1" "server-1" "ca-int-1"
+create_root_bundle "./8a/roots.pem" "ca-root"
+create_bundle "./8a/bundle.pem" "server-1" "ca-int-1"
+
+# Scenario 9a.
+reset
+create_root "Root CA 1" "ca-root"
+create_expired_intermediate "Intermediate CA 1" "ca-int-1" "ca-root"
+create_leaf "Server 1" "server-1" "ca-int-1"
+create_root_bundle "./9a/roots.pem" "ca-root"
+create_bundle "./9a/bundle.pem" "server-1" "ca-int-1"
+
+# Scenarios 10a and 10b.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_root "Root CA 2" "ca-root-2"
+create_expired_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_cross_signed "ca-int-1-xs" "ca-int-1" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-1"
+create_root_bundle "./10a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./10a/bundle.pem" "server-1" "ca-int-1" "ca-int-1-xs"
+create_root_bundle "./10b/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./10b/bundle.pem" "server-1" "ca-int-1-xs" "ca-int-1"
+
+# Scenarios 11a and 11b.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_expired_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_root "Root CA 2" "ca-root-2"
+create_cross_signed "ca-int-2-xs" "ca-int-2" "ca-root-2"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./11a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./11a/bundle.pem" "server-1" "ca-int-2" "ca-int-2-xs" "ca-int-1"
+create_root_bundle "./11b/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./11b/bundle.pem" "server-1" "ca-int-2-xs" "ca-int-2" "ca-int-1"
+
+# Scenario 12a.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_expired_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_cross_signed "ca-root-2" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./12a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./12a/bundle.pem" "server-1" "ca-int-2" "ca-int-1"
+
+# Scenario 13a.
+reset
+create_root "Root CA 1" "ca-root-1"
+create_intermediate "Intermediate CA 1" "ca-int-1" "ca-root-1"
+create_expired_cross_signed "ca-root-2" "ca-int-1" "ca-root-1"
+create_intermediate "Intermediate CA 2" "ca-int-2" "ca-int-1"
+create_leaf "Server 1" "server-1" "ca-int-2"
+create_root_bundle "./13a/roots.pem" "ca-root-1" "ca-root-2"
+create_bundle "./13a/bundle.pem" "server-1" "ca-int-2" "ca-int-1"
diff --git a/regress/lib/libcrypto/certs/verify_test.go b/regress/lib/libcrypto/certs/verify_test.go
new file mode 100644
index 00000000000..25301109e4b
--- /dev/null
+++ b/regress/lib/libcrypto/certs/verify_test.go
@@ -0,0 +1,111 @@
+package verify
+
+import (
+ "crypto/x509"
+ "encoding/pem"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+)
+
+func TestVerify(t *testing.T) {
+ tests := []struct {
+ id string
+ wantChains int
+ }{
+ {"1a", 1},
+ {"2a", 1},
+ {"2b", 0},
+ {"3a", 1},
+ {"3b", 0},
+ {"3c", 0},
+ {"3d", 0},
+ {"3e", 1},
+ {"4a", 2},
+ {"4b", 1},
+ {"4c", 1},
+ {"4d", 1},
+ {"4e", 1},
+ {"4f", 2},
+ {"4g", 1},
+ {"4h", 1},
+ {"5a", 2},
+ {"5b", 1},
+ {"5c", 1},
+ {"5d", 1},
+ {"5e", 1},
+ {"5f", 1},
+ {"5g", 2},
+ {"5h", 1},
+ {"5i", 1},
+ {"6a", 1}, // Expired root.
+ {"6b", 1}, // Expired root.
+ {"7a", 1}, // Expired root.
+ {"7b", 1}, // Expired root.
+ {"8a", 0}, // Expired leaf.
+ {"9a", 0}, // Expired intermediate.
+ {"10a", 1}, // Cross signed with expired intermediate.
+ {"10b", 1}, // Cross signed with expired intermediate.
+ {"11a", 1}, // Cross signed with expired intermediate.
+ {"11b", 1}, // Cross signed with expired intermediate.
+ {"12a", 1}, // Cross signed with expired intermediate.
+ {"13a", 1}, // Cross signed with expired root.
+ }
+
+ for _, test := range tests {
+ t.Run(test.id, func(t *testing.T) {
+ rootsPEM, err := ioutil.ReadFile(filepath.Join(test.id, "roots.pem"))
+ if err != nil {
+ t.Fatalf("Failed to read roots PEM: %v", err)
+ }
+ bundlePEM, err := ioutil.ReadFile(filepath.Join(test.id, "bundle.pem"))
+ if err != nil {
+ t.Fatalf("Failed to read bundle PEM: %v", err)
+ }
+
+ // Pull the leaf certificate off the top of the bundle.
+ block, intermediatesPEM := pem.Decode(bundlePEM)
+ if block == nil {
+ t.Fatal("Failed to parse leaf from bundle")
+ }
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ t.Fatalf("Failed to parse certificate: %v", err)
+ }
+
+ roots := x509.NewCertPool()
+ if !roots.AppendCertsFromPEM(rootsPEM) {
+ t.Fatal("Failed to parse root certificates")
+ }
+ intermediates := x509.NewCertPool()
+ if len(intermediatesPEM) > 0 {
+ if !intermediates.AppendCertsFromPEM(intermediatesPEM) {
+ t.Fatal("Failed to parse intermediate certificates")
+ }
+ }
+
+ opts := x509.VerifyOptions{
+ Roots: roots,
+ Intermediates: intermediates,
+ }
+
+ chains, err := cert.Verify(opts)
+ if err != nil {
+ if test.wantChains > 0 {
+ t.Errorf("Failed to verify certificate: %v", err)
+ }
+ return
+ }
+ t.Logf("Found %d chains", len(chains))
+ if got, want := len(chains), test.wantChains; got != want {
+ t.Errorf("Got %d chains, want %d", got, want)
+ }
+ for i, chain := range chains {
+ t.Logf("Chain %d\n", i)
+ for _, cert := range chain {
+ t.Logf(" %v\n", cert.Subject)
+ }
+ }
+ })
+ }
+}