// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"math/big"
	"net"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"
)

const (
	// testNameConstraintsAgainstOpenSSL can be set to true to run tests
	// against the system OpenSSL. This is disabled by default because Go
	// cannot depend on having OpenSSL installed at testing time.
	testNameConstraintsAgainstOpenSSL = false

	// debugOpenSSLFailure can be set to true, when
	// testNameConstraintsAgainstOpenSSL is also true, to cause
	// intermediate files to be preserved for debugging.
	debugOpenSSLFailure = false
)

type nameConstraintsTest struct {
	roots         []constraintsSpec
	intermediates [][]constraintsSpec
	leaf          leafSpec
	requestedEKUs []ExtKeyUsage
	expectedError string
	noOpenSSL     bool
	ignoreCN      bool
}

type constraintsSpec struct {
	ok   []string
	bad  []string
	ekus []string
}

type leafSpec struct {
	sans []string
	ekus []string
	cn   string
}

var nameConstraintsTests = []nameConstraintsTest{
	// #0: dummy test for the certificate generation process itself.
	{
		roots: make([]constraintsSpec, 1),
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #1: dummy test for the certificate generation process itself: single
	// level of intermediate.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #2: dummy test for the certificate generation process itself: two
	// levels of intermediates.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #3: matching DNS constraint in root
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #4: matching DNS constraint in intermediate.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #5: .example.com only matches subdomains.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
		expectedError: "\"example.com\" is not permitted",
	},

	// #6: .example.com matches subdomains.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.example.com"},
		},
	},

	// #7: .example.com matches multiple levels of subdomains
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.bar.example.com"},
		},
	},

	// #8: specifying a permitted list of names does not exclude other name
	// types
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:10.1.1.1"},
		},
	},

	// #9: specifying a permitted list of names does not exclude other name
	// types
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:10.0.0.0/8"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #10: intermediates can try to permit other names, which isn't
	// forbidden if the leaf doesn't mention them. I.e. name constraints
	// apply to names, not constraints themselves.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:example.com", "dns:foo.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #11: intermediates cannot add permitted names that the root doesn't
	// grant them.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:example.com", "dns:foo.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.com"},
		},
		expectedError: "\"foo.com\" is not permitted",
	},

	// #12: intermediates can further limit their scope if they wish.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:.bar.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.bar.example.com"},
		},
	},

	// #13: intermediates can further limit their scope and that limitation
	// is effective
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:.bar.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.notbar.example.com"},
		},
		expectedError: "\"foo.notbar.example.com\" is not permitted",
	},

	// #14: roots can exclude subtrees and that doesn't affect other names.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.com"},
		},
	},

	// #15: roots exclusions are effective.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.example.com"},
		},
		expectedError: "\"foo.example.com\" is excluded",
	},

	// #16: intermediates can also exclude names and that doesn't affect
	// other names.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					bad: []string{"dns:.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.com"},
		},
	},

	// #17: intermediate exclusions are effective.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					bad: []string{"dns:.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.example.com"},
		},
		expectedError: "\"foo.example.com\" is excluded",
	},

	// #18: having an exclusion doesn't prohibit other types of names.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"dns:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.com", "ip:10.1.1.1"},
		},
	},

	// #19: IP-based exclusions are permitted and don't affect unrelated IP
	// addresses.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:10.0.0.0/8"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:192.168.1.1"},
		},
	},

	// #20: IP-based exclusions are effective
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:10.0.0.0/8"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:10.0.0.1"},
		},
		expectedError: "\"10.0.0.1\" is excluded",
	},

	// #21: intermediates can further constrain IP ranges.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:0.0.0.0/1"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					bad: []string{"ip:11.0.0.0/8"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:11.0.0.1"},
		},
		expectedError: "\"11.0.0.1\" is excluded",
	},

	// #22: when multiple intermediates are present, chain building can
	// avoid intermediates with incompatible constraints.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:.foo.com"},
				},
				{
					ok: []string{"dns:.example.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.example.com"},
		},
		noOpenSSL: true, // OpenSSL's chain building is not informed by constraints.
	},

	// #23: (same as the previous test, but in the other order in ensure
	// that we don't pass it by luck.)
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ok: []string{"dns:.example.com"},
				},
				{
					ok: []string{"dns:.foo.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.example.com"},
		},
		noOpenSSL: true, // OpenSSL's chain building is not informed by constraints.
	},

	// #24: when multiple roots are valid, chain building can avoid roots
	// with incompatible constraints.
	{
		roots: []constraintsSpec{
			{},
			{
				ok: []string{"dns:foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
		noOpenSSL: true, // OpenSSL's chain building is not informed by constraints.
	},

	// #25: (same as the previous test, but in the other order in ensure
	// that we don't pass it by luck.)
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com"},
			},
			{},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
		noOpenSSL: true, // OpenSSL's chain building is not informed by constraints.
	},

	// #26: chain building can find a valid path even with multiple levels
	// of alternative intermediates and alternative roots.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com"},
			},
			{
				ok: []string{"dns:example.com"},
			},
			{},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
				{
					ok: []string{"dns:foo.com"},
				},
			},
			{
				{},
				{
					ok: []string{"dns:foo.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:bar.com"},
		},
		noOpenSSL: true, // OpenSSL's chain building is not informed by constraints.
	},

	// #27: chain building doesn't get stuck when there is no valid path.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com"},
			},
			{
				ok: []string{"dns:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
				{
					ok: []string{"dns:foo.com"},
				},
			},
			{
				{
					ok: []string{"dns:bar.com"},
				},
				{
					ok: []string{"dns:foo.com"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:bar.com"},
		},
		expectedError: "\"bar.com\" is not permitted",
	},

	// #28: unknown name types don't cause a problem without constraints.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"unknown:"},
		},
	},

	// #29: unknown name types are allowed even in constrained chains.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com", "dns:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"unknown:"},
		},
	},

	// #30: without SANs, a certificate with a CN is still accepted in a
	// constrained chain, since we ignore the CN in VerifyHostname.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com", "dns:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{},
			cn:   "foo.com",
		},
	},

	// #31: IPv6 addresses work in constraints: roots can permit them as
	// expected.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:abcd:1234::"},
		},
	},

	// #32: IPv6 addresses work in constraints: root restrictions are
	// effective.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:1234:abcd::"},
		},
		expectedError: "\"2000:1234:abcd::\" is not permitted",
	},

	// #33: An IPv6 permitted subtree doesn't affect DNS names.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:abcd::", "dns:foo.com"},
		},
	},

	// #34: IPv6 exclusions don't affect unrelated addresses.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:1234::"},
		},
	},

	// #35: IPv6 exclusions are effective.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:abcd::"},
		},
		expectedError: "\"2000:abcd::\" is excluded",
	},

	// #36: IPv6 constraints do not permit IPv4 addresses.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:2000:abcd::/32"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:10.0.0.1"},
		},
		expectedError: "\"10.0.0.1\" is not permitted",
	},

	// #37: IPv4 constraints do not permit IPv6 addresses.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"ip:10.0.0.0/8"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:2000:abcd::"},
		},
		expectedError: "\"2000:abcd::\" is not permitted",
	},

	// #38: an exclusion of an unknown type doesn't affect other names.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"unknown:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #39: a permitted subtree of an unknown type doesn't affect other
	// name types.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"unknown:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #40: exact email constraints work
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:foo@example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
	},

	// #41: exact email constraints are effective
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:foo@example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:bar@example.com"},
		},
		expectedError: "\"bar@example.com\" is not permitted",
	},

	// #42: email canonicalisation works.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:foo@example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:\"\\f\\o\\o\"@example.com"},
		},
		noOpenSSL: true, // OpenSSL doesn't canonicalise email addresses before matching
	},

	// #43: limiting email addresses to a host works.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
	},

	// #44: a leading dot matches hosts one level deep
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@sub.example.com"},
		},
	},

	// #45: a leading dot does not match the host itself
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
		expectedError: "\"foo@example.com\" is not permitted",
	},

	// #46: a leading dot also matches two (or more) levels deep.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@sub.sub.example.com"},
		},
	},

	// #47: the local part of an email is case-sensitive
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:foo@example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:Foo@example.com"},
		},
		expectedError: "\"Foo@example.com\" is not permitted",
	},

	// #48: the domain part of an email is not case-sensitive
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:foo@EXAMPLE.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
	},

	// #49: the domain part of a DNS constraint is also not case-sensitive.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:EXAMPLE.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #50: URI constraints only cover the host part of the URI
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{
				"uri:http://example.com/bar",
				"uri:http://example.com:8080/",
				"uri:https://example.com/wibble#bar",
			},
		},
	},

	// #51: URIs with IPs are rejected
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://1.2.3.4/"},
		},
		expectedError: "URI with IP",
	},

	// #52: URIs with IPs and ports are rejected
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://1.2.3.4:43/"},
		},
		expectedError: "URI with IP",
	},

	// #53: URIs with IPv6 addresses are also rejected
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://[2006:abcd::1]/"},
		},
		expectedError: "URI with IP",
	},

	// #54: URIs with IPv6 addresses with ports are also rejected
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://[2006:abcd::1]:16/"},
		},
		expectedError: "URI with IP",
	},

	// #55: URI constraints are effective
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://bar.com/"},
		},
		expectedError: "\"http://bar.com/\" is not permitted",
	},

	// #56: URI constraints are effective
	{
		roots: []constraintsSpec{
			{
				bad: []string{"uri:foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://foo.com/"},
		},
		expectedError: "\"http://foo.com/\" is excluded",
	},

	// #57: URI constraints can allow subdomains
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://www.foo.com/"},
		},
	},

	// #58: excluding an IPv4-mapped-IPv6 address doesn't affect the IPv4
	// version of that address.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"ip:::ffff:1.2.3.4/128"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:1.2.3.4"},
		},
	},

	// #59: a URI constraint isn't matched by a URN.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:urn:example"},
		},
		expectedError: "URI with empty host",
	},

	// #60: excluding all IPv6 addresses doesn't exclude all IPv4 addresses
	// too, even though IPv4 is mapped into the IPv6 range.
	{
		roots: []constraintsSpec{
			{
				ok:  []string{"ip:1.2.3.0/24"},
				bad: []string{"ip:::0/0"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"ip:1.2.3.4"},
		},
	},

	// #61: omitting extended key usage in a CA certificate implies that
	// any usage is ok.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth", "other"},
		},
	},

	// #62: The “any” EKU also means that any usage is ok.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"any"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth", "other"},
		},
	},

	// #63: An intermediate with enumerated EKUs causes a failure if we
	// test for an EKU not in that set. (ServerAuth is required by
	// default.)
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"email"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth"},
		},
		expectedError: "incompatible key usage",
	},

	// #64: an unknown EKU in the leaf doesn't break anything, even if it's not
	// correctly nested.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"email"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"other"},
		},
		requestedEKUs: []ExtKeyUsage{ExtKeyUsageAny},
	},

	// #65: trying to add extra permitted key usages in an intermediate
	// (after a limitation in the root) is acceptable so long as the leaf
	// certificate doesn't use them.
	{
		roots: []constraintsSpec{
			{
				ekus: []string{"serverAuth"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"serverAuth", "email"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth"},
		},
	},

	// #66: EKUs in roots are not ignored.
	{
		roots: []constraintsSpec{
			{
				ekus: []string{"email"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"serverAuth"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth"},
		},
		expectedError: "incompatible key usage",
	},

	// #67: SGC key usages used to permit serverAuth and clientAuth,
	// but don't anymore.
	{
		roots: []constraintsSpec{
			{},
		},
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"netscapeSGC"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth", "clientAuth"},
		},
		expectedError: "incompatible key usage",
	},

	// #68: SGC key usages used to permit serverAuth and clientAuth,
	// but don't anymore.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"msSGC"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth", "clientAuth"},
		},
		expectedError: "incompatible key usage",
	},

	// #69: an empty DNS constraint should allow anything.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
	},

	// #70: an empty DNS constraint should also reject everything.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"dns:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
		},
		expectedError: "\"example.com\" is excluded",
	},

	// #71: an empty email constraint should allow anything
	{
		roots: []constraintsSpec{
			{
				ok: []string{"email:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
	},

	// #72: an empty email constraint should also reject everything.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"email:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:foo@example.com"},
		},
		expectedError: "\"foo@example.com\" is excluded",
	},

	// #73: an empty URI constraint should allow anything
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:https://example.com/test"},
		},
	},

	// #74: an empty URI constraint should also reject everything.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"uri:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:https://example.com/test"},
		},
		expectedError: "\"https://example.com/test\" is excluded",
	},

	// #75: serverAuth in a leaf shouldn't permit clientAuth when requested in
	// VerifyOptions.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"serverAuth"},
		},
		requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth},
		expectedError: "incompatible key usage",
	},

	// #76: MSSGC in a leaf used to match a request for serverAuth, but doesn't
	// anymore.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"msSGC"},
		},
		requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth},
		expectedError: "incompatible key usage",
	},

	// An invalid DNS SAN should be detected only at validation time so
	// that we can process CA certificates in the wild that have invalid SANs.
	// See https://github.com/golang/go/issues/23995

	// #77: an invalid DNS or mail SAN will not be detected if name constraint
	// checking is not triggered.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:this is invalid", "email:this @ is invalid"},
		},
	},

	// #78: an invalid DNS SAN will be detected if any name constraint checking
	// is triggered.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"uri:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:this is invalid"},
		},
		expectedError: "cannot parse dnsName",
	},

	// #79: an invalid email SAN will be detected if any name constraint
	// checking is triggered.
	{
		roots: []constraintsSpec{
			{
				bad: []string{"uri:"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"email:this @ is invalid"},
		},
		expectedError: "cannot parse rfc822Name",
	},

	// #80: if several EKUs are requested, satisfying any of them is sufficient.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			ekus: []string{"email"},
		},
		requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection},
	},

	// #81: EKUs that are not asserted in VerifyOpts are not required to be
	// nested.
	{
		roots: make([]constraintsSpec, 1),
		intermediates: [][]constraintsSpec{
			{
				{
					ekus: []string{"serverAuth"},
				},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:example.com"},
			// There's no email EKU in the intermediate. This would be rejected if
			// full nesting was required.
			ekus: []string{"email", "serverAuth"},
		},
	},

	// #82: a certificate without SANs and CN is accepted in a constrained chain.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com", "dns:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{},
		},
	},

	// #83: a certificate without SANs and with a CN that does not parse as a
	// hostname is accepted in a constrained chain.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com", "dns:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{},
			cn:   "foo,bar",
		},
	},

	// #84: a certificate with SANs and CN is accepted in a constrained chain.
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.com", "dns:.foo.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:foo.com"},
			cn:   "foo.bar",
		},
	},

	// #85: .example.com is an invalid DNS name, it should not match the
	// constraint example.com.
	{
		roots:         []constraintsSpec{{ok: []string{"dns:example.com"}}},
		leaf:          leafSpec{sans: []string{"dns:.example.com"}},
		expectedError: "cannot parse dnsName \".example.com\"",
	},
	// #86: URIs with IPv6 addresses with zones and ports are rejected
	{
		roots: []constraintsSpec{
			{
				ok: []string{"uri:example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"uri:http://[2006:abcd::1%25.example.com]:16/"},
		},
		expectedError: "URI with IP",
	},
	// #87: subdomain excluded constraints preclude wildcard names
	{
		roots: []constraintsSpec{
			{
				bad: []string{"dns:foo.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:*.example.com"},
		},
		expectedError: "\"*.example.com\" is excluded by constraint \"foo.example.com\"",
	},
	// #88: wildcard names are not matched by subdomain permitted constraints
	{
		roots: []constraintsSpec{
			{
				ok: []string{"dns:foo.example.com"},
			},
		},
		intermediates: [][]constraintsSpec{
			{
				{},
			},
		},
		leaf: leafSpec{
			sans: []string{"dns:*.example.com"},
		},
		expectedError: "\"*.example.com\" is not permitted",
	},
}

func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) {
	var serialBytes [16]byte
	rand.Read(serialBytes[:])

	template := &Certificate{
		SerialNumber: new(big.Int).SetBytes(serialBytes[:]),
		Subject: pkix.Name{
			CommonName: name,
		},
		NotBefore:             time.Unix(1000, 0),
		NotAfter:              time.Unix(2000, 0),
		KeyUsage:              KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	if err := addConstraintsToTemplate(constraints, template); err != nil {
		return nil, err
	}

	if parent == nil {
		parent = template
	}
	derBytes, err := CreateCertificate(rand.Reader, template, parent, &key.PublicKey, parentKey)
	if err != nil {
		return nil, err
	}

	caCert, err := ParseCertificate(derBytes)
	if err != nil {
		return nil, err
	}

	return caCert, nil
}

func makeConstraintsLeafCert(leaf leafSpec, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) {
	var serialBytes [16]byte
	rand.Read(serialBytes[:])

	template := &Certificate{
		SerialNumber: new(big.Int).SetBytes(serialBytes[:]),
		Subject: pkix.Name{
			OrganizationalUnit: []string{"Leaf"},
			CommonName:         leaf.cn,
		},
		NotBefore:             time.Unix(1000, 0),
		NotAfter:              time.Unix(2000, 0),
		KeyUsage:              KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
		IsCA:                  false,
	}

	for _, name := range leaf.sans {
		switch {
		case strings.HasPrefix(name, "dns:"):
			template.DNSNames = append(template.DNSNames, name[4:])

		case strings.HasPrefix(name, "ip:"):
			ip := net.ParseIP(name[3:])
			if ip == nil {
				return nil, fmt.Errorf("cannot parse IP %q", name[3:])
			}
			template.IPAddresses = append(template.IPAddresses, ip)

		case strings.HasPrefix(name, "invalidip:"):
			ipBytes, err := hex.DecodeString(name[10:])
			if err != nil {
				return nil, fmt.Errorf("cannot parse invalid IP: %s", err)
			}
			template.IPAddresses = append(template.IPAddresses, net.IP(ipBytes))

		case strings.HasPrefix(name, "email:"):
			template.EmailAddresses = append(template.EmailAddresses, name[6:])

		case strings.HasPrefix(name, "uri:"):
			uri, err := url.Parse(name[4:])
			if err != nil {
				return nil, fmt.Errorf("cannot parse URI %q: %s", name[4:], err)
			}
			template.URIs = append(template.URIs, uri)

		case strings.HasPrefix(name, "unknown:"):
			// This is a special case for testing unknown
			// name types. A custom SAN extension is
			// injected into the certificate.
			if len(leaf.sans) != 1 {
				panic("when using unknown name types, it must be the sole name")
			}

			template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
				Id: []int{2, 5, 29, 17},
				Value: []byte{
					0x30, // SEQUENCE
					3,    // three bytes
					9,    // undefined GeneralName type 9
					1,
					1,
				},
			})

		default:
			return nil, fmt.Errorf("unknown name type %q", name)
		}
	}

	var err error
	if template.ExtKeyUsage, template.UnknownExtKeyUsage, err = parseEKUs(leaf.ekus); err != nil {
		return nil, err
	}

	if parent == nil {
		parent = template
	}

	derBytes, err := CreateCertificate(rand.Reader, template, parent, &key.PublicKey, parentKey)
	if err != nil {
		return nil, err
	}

	return ParseCertificate(derBytes)
}

func customConstraintsExtension(typeNum int, constraint []byte, isExcluded bool) pkix.Extension {
	appendConstraint := func(contents []byte, tag uint8) []byte {
		contents = append(contents, tag|32 /* constructed */ |0x80 /* context-specific */)
		contents = append(contents, byte(4+len(constraint)) /* length */)
		contents = append(contents, 0x30 /* SEQUENCE */)
		contents = append(contents, byte(2+len(constraint)) /* length */)
		contents = append(contents, byte(typeNum) /* GeneralName type */)
		contents = append(contents, byte(len(constraint)))
		return append(contents, constraint...)
	}

	var contents []byte
	if !isExcluded {
		contents = appendConstraint(contents, 0 /* tag 0 for permitted */)
	} else {
		contents = appendConstraint(contents, 1 /* tag 1 for excluded */)
	}

	var value []byte
	value = append(value, 0x30 /* SEQUENCE */)
	value = append(value, byte(len(contents)))
	value = append(value, contents...)

	return pkix.Extension{
		Id:    []int{2, 5, 29, 30},
		Value: value,
	}
}

func addConstraintsToTemplate(constraints constraintsSpec, template *Certificate) error {
	parse := func(constraints []string) (dnsNames []string, ips []*net.IPNet, emailAddrs []string, uriDomains []string, err error) {
		for _, constraint := range constraints {
			switch {
			case strings.HasPrefix(constraint, "dns:"):
				dnsNames = append(dnsNames, constraint[4:])

			case strings.HasPrefix(constraint, "ip:"):
				_, ipNet, err := net.ParseCIDR(constraint[3:])
				if err != nil {
					return nil, nil, nil, nil, err
				}
				ips = append(ips, ipNet)

			case strings.HasPrefix(constraint, "email:"):
				emailAddrs = append(emailAddrs, constraint[6:])

			case strings.HasPrefix(constraint, "uri:"):
				uriDomains = append(uriDomains, constraint[4:])

			default:
				return nil, nil, nil, nil, fmt.Errorf("unknown constraint %q", constraint)
			}
		}

		return dnsNames, ips, emailAddrs, uriDomains, err
	}

	handleSpecialConstraint := func(constraint string, isExcluded bool) bool {
		switch {
		case constraint == "unknown:":
			template.ExtraExtensions = append(template.ExtraExtensions, customConstraintsExtension(9 /* undefined GeneralName type */, []byte{1}, isExcluded))

		default:
			return false
		}

		return true
	}

	if len(constraints.ok) == 1 && len(constraints.bad) == 0 {
		if handleSpecialConstraint(constraints.ok[0], false) {
			return nil
		}
	}

	if len(constraints.bad) == 1 && len(constraints.ok) == 0 {
		if handleSpecialConstraint(constraints.bad[0], true) {
			return nil
		}
	}

	var err error
	template.PermittedDNSDomains, template.PermittedIPRanges, template.PermittedEmailAddresses, template.PermittedURIDomains, err = parse(constraints.ok)
	if err != nil {
		return err
	}

	template.ExcludedDNSDomains, template.ExcludedIPRanges, template.ExcludedEmailAddresses, template.ExcludedURIDomains, err = parse(constraints.bad)
	if err != nil {
		return err
	}

	if template.ExtKeyUsage, template.UnknownExtKeyUsage, err = parseEKUs(constraints.ekus); err != nil {
		return err
	}

	return nil
}

func parseEKUs(ekuStrs []string) (ekus []ExtKeyUsage, unknowns []asn1.ObjectIdentifier, err error) {
	for _, s := range ekuStrs {
		switch s {
		case "serverAuth":
			ekus = append(ekus, ExtKeyUsageServerAuth)
		case "clientAuth":
			ekus = append(ekus, ExtKeyUsageClientAuth)
		case "email":
			ekus = append(ekus, ExtKeyUsageEmailProtection)
		case "netscapeSGC":
			ekus = append(ekus, ExtKeyUsageNetscapeServerGatedCrypto)
		case "msSGC":
			ekus = append(ekus, ExtKeyUsageMicrosoftServerGatedCrypto)
		case "any":
			ekus = append(ekus, ExtKeyUsageAny)
		case "other":
			unknowns = append(unknowns, asn1.ObjectIdentifier{2, 4, 1, 2, 3})
		default:
			return nil, nil, fmt.Errorf("unknown EKU %q", s)
		}
	}

	return
}

func TestConstraintCases(t *testing.T) {
	privateKeys := sync.Pool{
		New: func() any {
			priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
			if err != nil {
				panic(err)
			}
			return priv
		},
	}

	for i, test := range nameConstraintsTests {
		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
			rootPool := NewCertPool()
			rootKey := privateKeys.Get().(*ecdsa.PrivateKey)
			rootName := "Root " + strconv.Itoa(i)

			// keys keeps track of all the private keys used in a given
			// test and puts them back in the privateKeys pool at the end.
			keys := []*ecdsa.PrivateKey{rootKey}

			// At each level (root, intermediate(s), leaf), parent points to
			// an example parent certificate and parentKey the key for the
			// parent level. Since all certificates at a given level have
			// the same name and public key, any parent certificate is
			// sufficient to get the correct issuer name and authority
			// key ID.
			var parent *Certificate
			parentKey := rootKey

			for _, root := range test.roots {
				rootCert, err := makeConstraintsCACert(root, rootName, rootKey, nil, rootKey)
				if err != nil {
					t.Fatalf("failed to create root: %s", err)
				}

				parent = rootCert
				rootPool.AddCert(rootCert)
			}

			intermediatePool := NewCertPool()

			for level, intermediates := range test.intermediates {
				levelKey := privateKeys.Get().(*ecdsa.PrivateKey)
				keys = append(keys, levelKey)
				levelName := "Intermediate level " + strconv.Itoa(level)
				var last *Certificate

				for _, intermediate := range intermediates {
					caCert, err := makeConstraintsCACert(intermediate, levelName, levelKey, parent, parentKey)
					if err != nil {
						t.Fatalf("failed to create %q: %s", levelName, err)
					}

					last = caCert
					intermediatePool.AddCert(caCert)
				}

				parent = last
				parentKey = levelKey
			}

			leafKey := privateKeys.Get().(*ecdsa.PrivateKey)
			keys = append(keys, leafKey)

			leafCert, err := makeConstraintsLeafCert(test.leaf, leafKey, parent, parentKey)
			if err != nil {
				t.Fatalf("cannot create leaf: %s", err)
			}

			// Skip tests with CommonName set because OpenSSL will try to match it
			// against name constraints, while we ignore it when it's not hostname-looking.
			if !test.noOpenSSL && testNameConstraintsAgainstOpenSSL && test.leaf.cn == "" {
				output, err := testChainAgainstOpenSSL(t, leafCert, intermediatePool, rootPool)
				if err == nil && len(test.expectedError) > 0 {
					t.Error("unexpectedly succeeded against OpenSSL")
					if debugOpenSSLFailure {
						return
					}
				}

				if err != nil {
					if _, ok := err.(*exec.ExitError); !ok {
						t.Errorf("OpenSSL failed to run: %s", err)
					} else if len(test.expectedError) == 0 {
						t.Errorf("OpenSSL unexpectedly failed: %v", output)
						if debugOpenSSLFailure {
							return
						}
					}
				}
			}

			verifyOpts := VerifyOptions{
				Roots:         rootPool,
				Intermediates: intermediatePool,
				CurrentTime:   time.Unix(1500, 0),
				KeyUsages:     test.requestedEKUs,
			}
			_, err = leafCert.Verify(verifyOpts)

			logInfo := true
			if len(test.expectedError) == 0 {
				if err != nil {
					t.Errorf("unexpected failure: %s", err)
				} else {
					logInfo = false
				}
			} else {
				if err == nil {
					t.Error("unexpected success")
				} else if !strings.Contains(err.Error(), test.expectedError) {
					t.Errorf("expected error containing %q, but got: %s", test.expectedError, err)
				} else {
					logInfo = false
				}
			}

			if logInfo {
				certAsPEM := func(cert *Certificate) string {
					var buf bytes.Buffer
					pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
					return buf.String()
				}
				t.Errorf("root:\n%s", certAsPEM(rootPool.mustCert(t, 0)))
				if intermediates := allCerts(t, intermediatePool); len(intermediates) > 0 {
					for ii, intermediate := range intermediates {
						t.Errorf("intermediate %d:\n%s", ii, certAsPEM(intermediate))
					}
				}
				t.Errorf("leaf:\n%s", certAsPEM(leafCert))
			}

			for _, key := range keys {
				privateKeys.Put(key)
			}
		})
	}
}

func writePEMsToTempFile(certs []*Certificate) *os.File {
	file, err := os.CreateTemp("", "name_constraints_test")
	if err != nil {
		panic("cannot create tempfile")
	}

	pemBlock := &pem.Block{Type: "CERTIFICATE"}
	for _, cert := range certs {
		pemBlock.Bytes = cert.Raw
		pem.Encode(file, pemBlock)
	}

	return file
}

func testChainAgainstOpenSSL(t *testing.T, leaf *Certificate, intermediates, roots *CertPool) (string, error) {
	args := []string{"verify", "-no_check_time"}

	rootsFile := writePEMsToTempFile(allCerts(t, roots))
	if debugOpenSSLFailure {
		println("roots file:", rootsFile.Name())
	} else {
		defer os.Remove(rootsFile.Name())
	}
	args = append(args, "-CAfile", rootsFile.Name())

	if intermediates.len() > 0 {
		intermediatesFile := writePEMsToTempFile(allCerts(t, intermediates))
		if debugOpenSSLFailure {
			println("intermediates file:", intermediatesFile.Name())
		} else {
			defer os.Remove(intermediatesFile.Name())
		}
		args = append(args, "-untrusted", intermediatesFile.Name())
	}

	leafFile := writePEMsToTempFile([]*Certificate{leaf})
	if debugOpenSSLFailure {
		println("leaf file:", leafFile.Name())
	} else {
		defer os.Remove(leafFile.Name())
	}
	args = append(args, leafFile.Name())

	var output bytes.Buffer
	cmd := exec.Command("openssl", args...)
	cmd.Stdout = &output
	cmd.Stderr = &output

	err := cmd.Run()
	return output.String(), err
}

var rfc2821Tests = []struct {
	in                string
	localPart, domain string
}{
	{"foo@example.com", "foo", "example.com"},
	{"@example.com", "", ""},
	{"\"@example.com", "", ""},
	{"\"\"@example.com", "", "example.com"},
	{"\"a\"@example.com", "a", "example.com"},
	{"\"\\a\"@example.com", "a", "example.com"},
	{"a\"@example.com", "", ""},
	{"foo..bar@example.com", "", ""},
	{".foo.bar@example.com", "", ""},
	{"foo.bar.@example.com", "", ""},
	{"|{}?'@example.com", "|{}?'", "example.com"},

	// Examples from RFC 3696
	{"Abc\\@def@example.com", "Abc@def", "example.com"},
	{"Fred\\ Bloggs@example.com", "Fred Bloggs", "example.com"},
	{"Joe.\\\\Blow@example.com", "Joe.\\Blow", "example.com"},
	{"\"Abc@def\"@example.com", "Abc@def", "example.com"},
	{"\"Fred Bloggs\"@example.com", "Fred Bloggs", "example.com"},
	{"customer/department=shipping@example.com", "customer/department=shipping", "example.com"},
	{"$A12345@example.com", "$A12345", "example.com"},
	{"!def!xyz%abc@example.com", "!def!xyz%abc", "example.com"},
	{"_somename@example.com", "_somename", "example.com"},
}

func TestRFC2821Parsing(t *testing.T) {
	for i, test := range rfc2821Tests {
		mailbox, ok := parseRFC2821Mailbox(test.in)
		expectedFailure := len(test.localPart) == 0 && len(test.domain) == 0

		if ok && expectedFailure {
			t.Errorf("#%d: %q unexpectedly parsed as (%q, %q)", i, test.in, mailbox.local, mailbox.domain)
			continue
		}

		if !ok && !expectedFailure {
			t.Errorf("#%d: unexpected failure for %q", i, test.in)
			continue
		}

		if !ok {
			continue
		}

		if mailbox.local != test.localPart || mailbox.domain != test.domain {
			t.Errorf("#%d: %q parsed as (%q, %q), but wanted (%q, %q)", i, test.in, mailbox.local, mailbox.domain, test.localPart, test.domain)
		}
	}
}

func TestBadNamesInConstraints(t *testing.T) {
	constraintParseError := func(err error) bool {
		str := err.Error()
		return strings.Contains(str, "failed to parse ") && strings.Contains(str, "constraint")
	}

	encodingError := func(err error) bool {
		return strings.Contains(err.Error(), "cannot be encoded as an IA5String")
	}

	// Bad names in constraints should not parse.
	badNames := []struct {
		name    string
		matcher func(error) bool
	}{
		{"dns:foo.com.", constraintParseError},
		{"email:abc@foo.com.", constraintParseError},
		{"email:foo.com.", constraintParseError},
		{"uri:example.com.", constraintParseError},
		{"uri:1.2.3.4", constraintParseError},
		{"uri:ffff::1", constraintParseError},
		{"dns:not–hyphen.com", encodingError},
		{"email:foo@not–hyphen.com", encodingError},
		{"uri:not–hyphen.com", encodingError},
	}

	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}

	for _, test := range badNames {
		_, err := makeConstraintsCACert(constraintsSpec{
			ok: []string{test.name},
		}, "TestAbsoluteNamesInConstraints", priv, nil, priv)

		if err == nil {
			t.Errorf("bad name %q unexpectedly accepted in name constraint", test.name)
			continue
		} else {
			if !test.matcher(err) {
				t.Errorf("bad name %q triggered unrecognised error: %s", test.name, err)
			}
		}
	}
}

func TestBadNamesInSANs(t *testing.T) {
	// Bad names in URI and IP SANs should not parse. Bad DNS and email SANs
	// will parse and are tested in name constraint tests at the top of this
	// file.
	badNames := []string{
		"uri:https://example.com./dsf",
		"invalidip:0102",
		"invalidip:0102030405",
	}

	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}

	for _, badName := range badNames {
		_, err := makeConstraintsLeafCert(leafSpec{sans: []string{badName}}, priv, nil, priv)

		if err == nil {
			t.Errorf("bad name %q unexpectedly accepted in SAN", badName)
			continue
		}

		if str := err.Error(); !strings.Contains(str, "cannot parse ") {
			t.Errorf("bad name %q triggered unrecognised error: %s", badName, str)
		}
	}
}
