mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-02 06:25:37 +00:00
2188 lines
45 KiB
Go
2188 lines
45 KiB
Go
|
// 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"
|
|||
|
"io/ioutil"
|
|||
|
"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 rejected in a constrained chain.
|
|||
|
{
|
|||
|
roots: []constraintsSpec{
|
|||
|
{
|
|||
|
ok: []string{"dns:foo.com", "dns:.foo.com"},
|
|||
|
},
|
|||
|
},
|
|||
|
intermediates: [][]constraintsSpec{
|
|||
|
{
|
|||
|
{},
|
|||
|
},
|
|||
|
},
|
|||
|
leaf: leafSpec{
|
|||
|
sans: []string{},
|
|||
|
cn: "foo.com",
|
|||
|
},
|
|||
|
expectedError: "leaf doesn't have a SAN extension",
|
|||
|
},
|
|||
|
|
|||
|
// #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: in order to support COMODO chains, SGC key usages permit
|
|||
|
// serverAuth and clientAuth.
|
|||
|
{
|
|||
|
roots: []constraintsSpec{
|
|||
|
{},
|
|||
|
},
|
|||
|
intermediates: [][]constraintsSpec{
|
|||
|
{
|
|||
|
{
|
|||
|
ekus: []string{"netscapeSGC"},
|
|||
|
},
|
|||
|
},
|
|||
|
},
|
|||
|
leaf: leafSpec{
|
|||
|
sans: []string{"dns:example.com"},
|
|||
|
ekus: []string{"serverAuth", "clientAuth"},
|
|||
|
},
|
|||
|
},
|
|||
|
|
|||
|
// #68: in order to support COMODO chains, SGC key usages permit
|
|||
|
// serverAuth and clientAuth.
|
|||
|
{
|
|||
|
roots: make([]constraintsSpec, 1),
|
|||
|
intermediates: [][]constraintsSpec{
|
|||
|
{
|
|||
|
{
|
|||
|
ekus: []string{"msSGC"},
|
|||
|
},
|
|||
|
},
|
|||
|
},
|
|||
|
leaf: leafSpec{
|
|||
|
sans: []string{"dns:example.com"},
|
|||
|
ekus: []string{"serverAuth", "clientAuth"},
|
|||
|
},
|
|||
|
},
|
|||
|
|
|||
|
// #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: However, MSSGC in a leaf should match a request for serverAuth.
|
|||
|
{
|
|||
|
roots: make([]constraintsSpec, 1),
|
|||
|
intermediates: [][]constraintsSpec{
|
|||
|
{
|
|||
|
{},
|
|||
|
},
|
|||
|
},
|
|||
|
leaf: leafSpec{
|
|||
|
sans: []string{"dns:example.com"},
|
|||
|
ekus: []string{"msSGC"},
|
|||
|
},
|
|||
|
requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth},
|
|||
|
},
|
|||
|
|
|||
|
// 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: without SANs, a certificate with a valid CN is accepted in a
|
|||
|
// constrained chain if x509ignoreCN is set.
|
|||
|
{
|
|||
|
roots: []constraintsSpec{
|
|||
|
{
|
|||
|
ok: []string{"dns:foo.com", "dns:.foo.com"},
|
|||
|
},
|
|||
|
},
|
|||
|
intermediates: [][]constraintsSpec{
|
|||
|
{
|
|||
|
{},
|
|||
|
},
|
|||
|
},
|
|||
|
leaf: leafSpec{
|
|||
|
sans: []string{},
|
|||
|
cn: "foo.com",
|
|||
|
},
|
|||
|
ignoreCN: true,
|
|||
|
},
|
|||
|
}
|
|||
|
|
|||
|
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) {
|
|||
|
defer func(savedIgnoreCN bool) {
|
|||
|
ignoreCN = savedIgnoreCN
|
|||
|
}(ignoreCN)
|
|||
|
|
|||
|
privateKeys := sync.Pool{
|
|||
|
New: func() interface{} {
|
|||
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|||
|
if err != nil {
|
|||
|
panic(err)
|
|||
|
}
|
|||
|
return priv
|
|||
|
},
|
|||
|
}
|
|||
|
|
|||
|
for i, test := range nameConstraintsTests {
|
|||
|
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("#%d: failed to create root: %s", i, 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("#%d: failed to create %q: %s", i, 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("#%d: cannot create leaf: %s", i, 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(leafCert, intermediatePool, rootPool)
|
|||
|
if err == nil && len(test.expectedError) > 0 {
|
|||
|
t.Errorf("#%d: unexpectedly succeeded against OpenSSL", i)
|
|||
|
if debugOpenSSLFailure {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if err != nil {
|
|||
|
if _, ok := err.(*exec.ExitError); !ok {
|
|||
|
t.Errorf("#%d: OpenSSL failed to run: %s", i, err)
|
|||
|
} else if len(test.expectedError) == 0 {
|
|||
|
t.Errorf("#%d: OpenSSL unexpectedly failed: %v", i, output)
|
|||
|
if debugOpenSSLFailure {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ignoreCN = test.ignoreCN
|
|||
|
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("#%d: unexpected failure: %s", i, err)
|
|||
|
} else {
|
|||
|
logInfo = false
|
|||
|
}
|
|||
|
} else {
|
|||
|
if err == nil {
|
|||
|
t.Errorf("#%d: unexpected success", i)
|
|||
|
} else if !strings.Contains(err.Error(), test.expectedError) {
|
|||
|
t.Errorf("#%d: expected error containing %q, but got: %s", i, 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("#%d: root:\n%s", i, certAsPEM(rootPool.certs[0]))
|
|||
|
t.Errorf("#%d: leaf:\n%s", i, certAsPEM(leafCert))
|
|||
|
}
|
|||
|
|
|||
|
for _, key := range keys {
|
|||
|
privateKeys.Put(key)
|
|||
|
}
|
|||
|
keys = keys[:0]
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func writePEMsToTempFile(certs []*Certificate) *os.File {
|
|||
|
file, err := ioutil.TempFile("", "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(leaf *Certificate, intermediates, roots *CertPool) (string, error) {
|
|||
|
args := []string{"verify", "-no_check_time"}
|
|||
|
|
|||
|
rootsFile := writePEMsToTempFile(roots.certs)
|
|||
|
if debugOpenSSLFailure {
|
|||
|
println("roots file:", rootsFile.Name())
|
|||
|
} else {
|
|||
|
defer os.Remove(rootsFile.Name())
|
|||
|
}
|
|||
|
args = append(args, "-CAfile", rootsFile.Name())
|
|||
|
|
|||
|
if len(intermediates.certs) > 0 {
|
|||
|
intermediatesFile := writePEMsToTempFile(intermediates.certs)
|
|||
|
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)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|