local-server/vendor/github.com/cloudflare/cfssl/signer/local/local_test.go
Mikaël Cluseau 4d889632f6 vendor
2018-06-17 18:32:44 +11:00

1424 lines
37 KiB
Go

package local
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"io/ioutil"
"math/big"
"net"
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"sort"
"strings"
"testing"
"time"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
cferr "github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
"github.com/google/certificate-transparency-go"
)
const (
fullSubjectCSR = "testdata/test.csr"
testCSR = "testdata/ecdsa256.csr"
testSANCSR = "testdata/san_domain.csr"
testCaFile = "testdata/ca.pem"
testCaKeyFile = "testdata/ca_key.pem"
testECDSACaFile = "testdata/ecdsa256_ca.pem"
testECDSACaKeyFile = "testdata/ecdsa256_ca_key.pem"
)
var expiry = 1 * time.Minute
// Start a signer with the testing RSA CA cert and key.
func newTestSigner(t *testing.T) (s *Signer) {
s, err := NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewSignerFromFilePolicy(t *testing.T) {
var CAConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"signature": {
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "43800h",
Expiry: expiry,
CAConstraint: config.CAConstraint{IsCA: true},
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, CAConfig.Signing)
if err != nil {
t.Fatal(err)
}
}
func TestNewSignerFromFileInvalidPolicy(t *testing.T) {
var invalidConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"invalid": {
Usage: []string{"wiretapping"},
Expiry: expiry,
},
"empty": {},
},
Default: &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, invalidConfig.Signing)
if err == nil {
t.Fatal(err)
}
if !strings.Contains(err.Error(), `"code":5200`) {
t.Fatal(err)
}
}
func TestNewSignerFromFileNoUsageInPolicy(t *testing.T) {
var invalidConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"invalid": {
Usage: []string{},
Expiry: expiry,
},
"empty": {},
},
Default: &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, invalidConfig.Signing)
if err == nil {
t.Fatal("expect InvalidPolicy error")
}
if !strings.Contains(err.Error(), `"code":5200`) {
t.Fatal(err)
}
}
func TestNewSignerFromFileEdgeCases(t *testing.T) {
res, err := NewSignerFromFile("nil", "nil", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile(testCaFile, "nil", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile("../../helpers/testdata/messedupcert.pem", "local.go", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile("../../helpers/testdata/cert.pem", "../../helpers/testdata/messed_up_priv_key.pem", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
}
func TestSign(t *testing.T) {
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal("Failed to produce signer")
}
// test the empty request
_, err = s.Sign(signer.SignRequest{})
if err == nil {
t.Fatalf("Empty request failed to produce an error")
}
// not a csr
certPem, err := ioutil.ReadFile("../../helpers/testdata/cert.pem")
if err != nil {
t.Fatal(err)
}
// csr with ip as hostname
pem, err := ioutil.ReadFile("testdata/ip.csr")
if err != nil {
t.Fatal(err)
}
// improper request
validReq := signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(certPem)}
_, err = s.Sign(validReq)
if err == nil {
t.Fatal("A bad case failed to raise an error")
}
validReq = signer.SignRequest{Hosts: signer.SplitHosts("128.84.126.213"), Request: string(pem)}
_, err = s.Sign(validReq)
if err != nil {
t.Fatal("A bad case failed to raise an error")
}
pem, err = ioutil.ReadFile("testdata/ex.csr")
validReq = signer.SignRequest{
Request: string(pem),
Hosts: []string{"example.com"},
}
s.Sign(validReq)
if err != nil {
t.Fatal("Failed to sign")
}
}
func TestCertificate(t *testing.T) {
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal(err)
}
c, err := s.Certificate("", "")
if !reflect.DeepEqual(*c, *s.ca) || err != nil {
t.Fatal("Certificate() producing incorrect results")
}
}
func TestPolicy(t *testing.T) {
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal(err)
}
sgn := config.Signing{}
s.SetPolicy(&sgn)
if s.Policy() != &sgn {
t.Fatal("Policy is malfunctioning")
}
}
func newCustomSigner(t *testing.T, testCaFile, testCaKeyFile string) (s *Signer) {
s, err := NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewSignerFromFile(t *testing.T) {
newTestSigner(t)
}
const (
testHostName = "localhost"
)
func testSignFile(t *testing.T, certFile string) ([]byte, error) {
s := newTestSigner(t)
pem, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
return s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(pem)})
}
type csrTest struct {
file string
keyAlgo string
keyLen int
// Error checking function
errorCallback func(*testing.T, error)
}
// A helper function that returns a errorCallback function which expects an error.
func ExpectError() func(*testing.T, error) {
return func(t *testing.T, err error) {
if err == nil {
t.Fatal("Expected error. Got nothing.")
}
}
}
var csrTests = []csrTest{
{
file: "testdata/rsa2048.csr",
keyAlgo: "rsa",
keyLen: 2048,
errorCallback: nil,
},
{
file: "testdata/rsa3072.csr",
keyAlgo: "rsa",
keyLen: 3072,
errorCallback: nil,
},
{
file: "testdata/rsa4096.csr",
keyAlgo: "rsa",
keyLen: 4096,
errorCallback: nil,
},
{
file: "testdata/ecdsa256.csr",
keyAlgo: "ecdsa",
keyLen: 256,
errorCallback: nil,
},
{
file: "testdata/ecdsa384.csr",
keyAlgo: "ecdsa",
keyLen: 384,
errorCallback: nil,
},
{
file: "testdata/ecdsa521.csr",
keyAlgo: "ecdsa",
keyLen: 521,
errorCallback: nil,
},
{
file: "testdata/rsa-old.csr",
keyAlgo: "rsa",
keyLen: 2048,
errorCallback: nil,
},
}
func TestSignCSRs(t *testing.T) {
s := newTestSigner(t)
hostname := "cloudflare.com"
for _, test := range csrTests {
csr, err := ioutil.ReadFile(test.file)
if err != nil {
t.Fatal("CSR loading error:", err)
}
// It is possible to use different SHA2 algorithm with RSA CA key.
rsaSigAlgos := []x509.SignatureAlgorithm{x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA}
for _, sigAlgo := range rsaSigAlgos {
s.sigAlgo = sigAlgo
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)})
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen)
}
cert, _ := helpers.ParseCertificatePEM(certBytes)
if cert.SignatureAlgorithm != s.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
}
}
}
}
func TestECDSASigner(t *testing.T) {
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
hostname := "cloudflare.com"
for _, test := range csrTests {
csr, err := ioutil.ReadFile(test.file)
if err != nil {
t.Fatal("CSR loading error:", err)
}
// Try all ECDSA SignatureAlgorithm
SigAlgos := []x509.SignatureAlgorithm{x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512}
for _, sigAlgo := range SigAlgos {
s.sigAlgo = sigAlgo
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)})
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen)
}
cert, _ := helpers.ParseCertificatePEM(certBytes)
if cert.SignatureAlgorithm != s.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
}
}
}
}
const (
ecdsaInterCSR = "testdata/ecdsa256-inter.csr"
ecdsaInterKey = "testdata/ecdsa256-inter.key"
rsaInterCSR = "testdata/rsa2048-inter.csr"
rsaInterKey = "testdata/rsa2048-inter.key"
)
func TestCAIssuing(t *testing.T) {
var caCerts = []string{testCaFile, testECDSACaFile}
var caKeys = []string{testCaKeyFile, testECDSACaKeyFile}
var interCSRs = []string{ecdsaInterCSR, rsaInterCSR}
var interKeys = []string{ecdsaInterKey, rsaInterKey}
var CAPolicy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: true, MaxPathLenZero: true},
},
}
var hostname = "cloudflare-inter.com"
// Each RSA or ECDSA root CA issues two intermediate CAs (one ECDSA and one RSA).
// For each intermediate CA, use it to issue additional RSA and ECDSA intermediate CSRs.
for i, caFile := range caCerts {
caKeyFile := caKeys[i]
s := newCustomSigner(t, caFile, caKeyFile)
s.policy = CAPolicy
for j, csr := range interCSRs {
csrBytes, _ := ioutil.ReadFile(csr)
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csrBytes)})
if err != nil {
t.Fatal(err)
}
interCert, err := helpers.ParseCertificatePEM(certBytes)
if err != nil {
t.Fatal(err)
}
keyBytes, _ := ioutil.ReadFile(interKeys[j])
interKey, _ := helpers.ParsePrivateKeyPEM(keyBytes)
interSigner := &Signer{
ca: interCert,
priv: interKey,
policy: CAPolicy,
sigAlgo: signer.DefaultSigAlgo(interKey),
}
for _, anotherCSR := range interCSRs {
anotherCSRBytes, _ := ioutil.ReadFile(anotherCSR)
bytes, err := interSigner.Sign(
signer.SignRequest{
Hosts: signer.SplitHosts(hostname),
Request: string(anotherCSRBytes),
})
if err != nil {
t.Fatal(err)
}
cert, err := helpers.ParseCertificatePEM(bytes)
if err != nil {
t.Fatal(err)
}
if cert.SignatureAlgorithm != interSigner.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
if cert.MaxPathLen != 0 {
t.Fatal("CA Cert Max Path is not zero.")
}
if cert.MaxPathLenZero != true {
t.Fatal("CA Cert Max Path is not zero.")
}
}
}
}
}
func TestPopulateSubjectFromCSR(t *testing.T) {
// a subject with all its fields full.
fullSubject := &signer.Subject{
CN: "CN",
Names: []csr.Name{
{
C: "C",
ST: "ST",
L: "L",
O: "O",
OU: "OU",
},
},
SerialNumber: "deadbeef",
}
fullName := pkix.Name{
CommonName: "CommonName",
Country: []string{"Country"},
Province: []string{"Province"},
Organization: []string{"Organization"},
OrganizationalUnit: []string{"OrganizationalUnit"},
SerialNumber: "SerialNumber",
}
noCN := *fullSubject
noCN.CN = ""
name := PopulateSubjectFromCSR(&noCN, fullName)
if name.CommonName != "CommonName" {
t.Fatal("Failed to replace empty common name")
}
noC := *fullSubject
noC.Names[0].C = ""
name = PopulateSubjectFromCSR(&noC, fullName)
if !reflect.DeepEqual(name.Country, fullName.Country) {
t.Fatal("Failed to replace empty country")
}
noL := *fullSubject
noL.Names[0].L = ""
name = PopulateSubjectFromCSR(&noL, fullName)
if !reflect.DeepEqual(name.Locality, fullName.Locality) {
t.Fatal("Failed to replace empty locality")
}
noO := *fullSubject
noO.Names[0].O = ""
name = PopulateSubjectFromCSR(&noO, fullName)
if !reflect.DeepEqual(name.Organization, fullName.Organization) {
t.Fatal("Failed to replace empty organization")
}
noOU := *fullSubject
noOU.Names[0].OU = ""
name = PopulateSubjectFromCSR(&noOU, fullName)
if !reflect.DeepEqual(name.OrganizationalUnit, fullName.OrganizationalUnit) {
t.Fatal("Failed to replace empty organizational unit")
}
noSerial := *fullSubject
noSerial.SerialNumber = ""
name = PopulateSubjectFromCSR(&noSerial, fullName)
if name.SerialNumber != fullName.SerialNumber {
t.Fatalf("Failed to replace empty serial number: want %#v, got %#v", fullName.SerialNumber, name.SerialNumber)
}
}
func TestOverrideSubject(t *testing.T) {
csrPEM, err := ioutil.ReadFile(fullSubjectCSR)
if err != nil {
t.Fatalf("%v", err)
}
req := &signer.Subject{
Names: []csr.Name{
{O: "example.net"},
},
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost", "xyz@example.com"},
Request: string(csrPEM),
Subject: req,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
block, _ := pem.Decode(csrPEM)
template, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
t.Fatal(err.Error())
}
if cert.Subject.Organization[0] != "example.net" {
t.Fatalf("Failed to override subject: want example.net but have %s", cert.Subject.Organization[0])
}
if cert.Subject.Country[0] != template.Subject.Country[0] {
t.Fatal("Failed to override Country")
}
if cert.Subject.Locality[0] != template.Subject.Locality[0] {
t.Fatal("Failed to override Locality")
}
if cert.Subject.Organization[0] == template.Subject.Organization[0] {
t.Fatal("Shouldn't have overrode Organization")
}
if cert.Subject.OrganizationalUnit[0] != template.Subject.OrganizationalUnit[0] {
t.Fatal("Failed to override OrganizationalUnit")
}
log.Info("Overrode subject info")
}
func TestOverwriteHosts(t *testing.T) {
for _, csrFile := range []string{testCSR, testSANCSR} {
csrPEM, err := ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
csrDER, _ := pem.Decode([]byte(csrPEM))
if err != nil {
t.Fatal(err)
}
csr, err := x509.ParseCertificateRequest(csrDER.Bytes)
if err != nil {
t.Fatal(err)
}
csrHosts := csr.DNSNames
for _, ip := range csr.IPAddresses {
csrHosts = append(csrHosts, ip.String())
}
sort.Strings(csrHosts)
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
for _, hosts := range [][]string{
nil,
{},
{"127.0.0.1", "localhost", "xyz@example.com"},
} {
request := signer.SignRequest{
Hosts: hosts,
Request: string(csrPEM),
Subject: nil,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
// get the hosts, and add the ips and email addresses
certHosts := cert.DNSNames
for _, ip := range cert.IPAddresses {
certHosts = append(certHosts, ip.String())
}
for _, email := range cert.EmailAddresses {
certHosts = append(certHosts, email)
}
// compare the sorted host lists
sort.Strings(certHosts)
sort.Strings(request.Hosts)
if len(request.Hosts) > 0 && !reflect.DeepEqual(certHosts, request.Hosts) {
t.Fatalf("Hosts not the same. cert hosts: %v, expected: %v", certHosts, request.Hosts)
}
if request.Hosts == nil && !reflect.DeepEqual(certHosts, csrHosts) {
t.Fatalf("Hosts not the same. cert hosts: %v, expected csr hosts: %v", certHosts, csrHosts)
}
if request.Hosts != nil && len(request.Hosts) == 0 && len(certHosts) != 0 {
t.Fatalf("Hosts not the same. cert hosts: %v, expected: %v", certHosts, request.Hosts)
}
}
}
}
func TestOverrideValidity(t *testing.T) {
csrPEM, err := ioutil.ReadFile(fullSubjectCSR)
if err != nil {
t.Fatalf("%v", err)
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
req := signer.SignRequest{
Request: string(csrPEM),
}
// The default expiry value.
expiry := 8760 * time.Hour
// default case
now := time.Now().UTC()
certPEM, err := s.Sign(req)
if err != nil {
t.Fatalf("Error signing default request: %s", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
if !cert.NotBefore.After(now.Add(-10*time.Minute)) || !cert.NotBefore.Before(now.Add(10*time.Minute)) {
t.Fatalf("Unexpected NotBefore: wanted %s +/-10 minutes, got %s", now, cert.NotBefore)
}
expectedNotAfter := now.Round(time.Minute).Add(expiry)
if !cert.NotAfter.After(expectedNotAfter.Add(-10*time.Minute)) || !cert.NotAfter.Before(expectedNotAfter.Add(10*time.Minute)) {
t.Fatalf("Unexpected NotAfter: wanted %s +/-10 minutes, got %s", now, cert.NotAfter)
}
// custom case, NotBefore only
now = time.Now().UTC()
req.NotBefore = now.Add(-time.Hour * 5).Truncate(time.Hour)
req.NotAfter = time.Time{}
certPEM, err = s.Sign(req)
if err != nil {
t.Fatalf("Error signing default request: %s", err)
}
cert, err = helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
if !cert.NotBefore.Equal(req.NotBefore) {
t.Fatalf("Unexpected NotBefore: wanted %s, got %s", req.NotBefore, cert.NotBefore)
}
expectedNotAfter = req.NotBefore.Add(expiry)
if !cert.NotAfter.After(expectedNotAfter.Add(-10*time.Minute)) || !cert.NotAfter.Before(expectedNotAfter.Add(10*time.Minute)) {
t.Fatalf("Unexpected NotAfter: wanted %s +/-10 minutes, got %s", expectedNotAfter, cert.NotAfter)
}
// custom case, NotAfter only
now = time.Now().UTC()
req.NotBefore = time.Time{}
req.NotAfter = now.Add(-time.Hour * 5).Truncate(time.Hour)
certPEM, err = s.Sign(req)
if err != nil {
t.Fatalf("Error signing default request: %s", err)
}
cert, err = helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
if !cert.NotBefore.After(now.Add(-10*time.Minute)) || !cert.NotBefore.Before(now.Add(10*time.Minute)) {
t.Fatalf("Unexpected NotBefore: wanted %s +/-10 minutes, got %s", now, cert.NotBefore)
}
if !cert.NotAfter.Equal(req.NotAfter) {
t.Fatalf("Unexpected NotAfter: wanted %s, got %s", req.NotAfter, cert.NotAfter)
}
// custom case, NotBefore and NotAfter
now = time.Now().UTC()
req.NotBefore = now.Add(-time.Hour * 5).Truncate(time.Hour)
req.NotAfter = now.Add(time.Hour * 5).Truncate(time.Hour)
certPEM, err = s.Sign(req)
if err != nil {
t.Fatalf("Error signing default request: %s", err)
}
cert, err = helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
if !cert.NotBefore.Equal(req.NotBefore) {
t.Fatalf("Unexpected NotBefore: wanted %s, got %s", req.NotBefore, cert.NotBefore)
}
if !cert.NotAfter.Equal(req.NotAfter) {
t.Fatalf("Unexpected NotAfter: wanted %s, got %s", req.NotAfter, cert.NotAfter)
}
}
func expectOneValueOf(t *testing.T, s []string, e, n string) {
if len(s) != 1 {
t.Fatalf("Expected %s to have a single value, but it has %d values", n, len(s))
}
if s[0] != e {
t.Fatalf("Expected %s to be '%s', but it is '%s'", n, e, s[0])
}
}
func expectEmpty(t *testing.T, s []string, n string) {
if len(s) != 0 {
t.Fatalf("Expected no values in %s, but have %d values: %v", n, len(s), s)
}
}
func TestCASignPathlen(t *testing.T) {
var csrPathlenTests = []struct {
name string
caCertFile string
caKeyFile string
caProfile bool
csrFile string
err error
pathlen int
isZero bool
isCA bool
}{
{
name: "pathlen 1 signing pathlen 0",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_0.csr",
err: nil,
pathlen: 0,
isZero: true,
isCA: true,
},
{
name: "pathlen 1 signing pathlen 1",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_1.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "pathlen 0 signing pathlen 0",
caCertFile: testCaFile,
caKeyFile: testCaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_0.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "pathlen 0 signing pathlen 1",
caCertFile: testCaFile,
caKeyFile: testCaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_1.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "pathlen 0 signing pathlen unspecified",
caCertFile: testCaFile,
caKeyFile: testCaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_unspecified.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "pathlen 1 signing unspecified pathlen",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: true,
csrFile: "testdata/inter_pathlen_unspecified.csr",
err: nil,
// golang x509 parses unspecified pathlen as MaxPathLen == -1 and
// MaxPathLenZero == false
pathlen: -1,
isZero: false,
isCA: true,
},
{
name: "non-ca singing profile signing pathlen 0",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: false,
csrFile: "testdata/inter_pathlen_0.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "non-ca singing profile signing pathlen 1",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: false,
csrFile: "testdata/inter_pathlen_1.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
{
name: "non-ca singing profile signing pathlen 0",
caCertFile: testECDSACaFile,
caKeyFile: testECDSACaKeyFile,
caProfile: false,
csrFile: "testdata/inter_pathlen_unspecified.csr",
err: cferr.New(cferr.PolicyError, cferr.InvalidRequest),
},
}
for _, testCase := range csrPathlenTests {
csrPEM, err := ioutil.ReadFile(testCase.csrFile)
if err != nil {
t.Fatalf("%v", err)
}
req := &signer.Subject{
Names: []csr.Name{
{O: "sam certificate authority"},
},
CN: "localhost",
}
s := newCustomSigner(t, testCase.caCertFile, testCase.caKeyFile)
// No policy CSR whitelist: the normal set of CSR fields get passed through to
// certificate.
s.policy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: testCase.caProfile,
MaxPathLen: testCase.pathlen,
MaxPathLenZero: testCase.isZero,
},
},
}
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost"},
Request: string(csrPEM),
Subject: req,
}
certPEM, err := s.Sign(request)
if !reflect.DeepEqual(err, testCase.err) {
t.Fatalf("%s: expected: %v, actual: %v", testCase.name, testCase.err, err)
}
if err == nil {
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%s: %v", testCase.name, err)
}
if cert.IsCA != testCase.isCA {
t.Fatalf("%s: unexpected IsCA value: %v", testCase.name, cert.IsCA)
}
if cert.MaxPathLen != testCase.pathlen {
t.Fatalf("%s: unexpected pathlen value: %v", testCase.name, cert.MaxPathLen)
}
if cert.MaxPathLenZero != testCase.isZero {
t.Fatalf("%s: unexpected pathlen value: %v", testCase.name, cert.MaxPathLenZero)
}
}
}
}
func TestNoWhitelistSign(t *testing.T) {
csrPEM, err := ioutil.ReadFile(fullSubjectCSR)
if err != nil {
t.Fatalf("%v", err)
}
req := &signer.Subject{
Names: []csr.Name{
{O: "sam certificate authority"},
},
CN: "localhost",
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
// No policy CSR whitelist: the normal set of CSR fields get passed through to
// certificate.
s.policy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: true},
},
}
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost"},
Request: string(csrPEM),
Subject: req,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
name := cert.Subject
if name.CommonName != "localhost" {
t.Fatalf("Expected certificate common name to be 'localhost' but have '%v'", name.CommonName)
}
// CSR has: Subject: C=US, O=CloudFlare, OU=WWW, L=Ithaca, ST=New York
// Expect all to be passed through.
expectOneValueOf(t, name.Organization, "sam certificate authority", "O")
expectOneValueOf(t, name.OrganizationalUnit, "WWW", "OU")
expectOneValueOf(t, name.Province, "New York", "ST")
expectOneValueOf(t, name.Locality, "Ithaca", "L")
expectOneValueOf(t, name.Country, "US", "C")
}
func TestWhitelistSign(t *testing.T) {
csrPEM, err := ioutil.ReadFile(fullSubjectCSR)
if err != nil {
t.Fatalf("%v", err)
}
req := &signer.Subject{
Names: []csr.Name{
{O: "sam certificate authority"},
},
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
// Whitelist only key-related fields. Subject, DNSNames, etc shouldn't get
// passed through from CSR.
s.policy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: true},
CSRWhitelist: &config.CSRWhitelist{
PublicKey: true,
PublicKeyAlgorithm: true,
SignatureAlgorithm: true,
},
},
}
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost"},
Request: string(csrPEM),
Subject: req,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
name := cert.Subject
if name.CommonName != "" {
t.Fatalf("Expected empty certificate common name under policy without "+
"Subject whitelist, got %v", name.CommonName)
}
// O is provided by the signing API request, not the CSR, so it's allowed to
// be copied into the certificate.
expectOneValueOf(t, name.Organization, "sam certificate authority", "O")
expectEmpty(t, name.OrganizationalUnit, "OU")
expectEmpty(t, name.Province, "ST")
expectEmpty(t, name.Locality, "L")
expectEmpty(t, name.Country, "C")
if cert.PublicKeyAlgorithm != x509.RSA {
t.Fatalf("Expected public key algorithm to be RSA")
}
// Signature algorithm is allowed to be copied from CSR, but is overridden by
// DefaultSigAlgo.
if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 {
t.Fatalf("Expected public key algorithm to be ECDSAWithSHA256, got %v",
cert.SignatureAlgorithm)
}
}
func TestNameWhitelistSign(t *testing.T) {
csrPEM, err := ioutil.ReadFile(fullSubjectCSR)
if err != nil {
t.Fatalf("%v", err)
}
subInvalid := &signer.Subject{
CN: "localhost.com",
}
subValid := &signer.Subject{
CN: "1lab41.cf",
}
wl := regexp.MustCompile("^1[a-z]*[0-9]*\\.cf$")
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
// Whitelist only key-related fields. Subject, DNSNames, etc shouldn't get
// passed through from CSR.
s.policy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: true},
NameWhitelist: wl,
},
}
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "1machine23.cf"},
Request: string(csrPEM),
}
_, err = s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
request = signer.SignRequest{
Hosts: []string{"invalid.cf", "1machine23.cf"},
Request: string(csrPEM),
}
_, err = s.Sign(request)
if err == nil {
t.Fatalf("expected a policy error")
}
request = signer.SignRequest{
Hosts: []string{"1machine23.cf"},
Request: string(csrPEM),
Subject: subInvalid,
}
_, err = s.Sign(request)
if err == nil {
t.Fatalf("expected a policy error")
}
request = signer.SignRequest{
Hosts: []string{"1machine23.cf"},
Request: string(csrPEM),
Subject: subValid,
}
_, err = s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
}
func TestExtensionSign(t *testing.T) {
csrPEM, err := ioutil.ReadFile(testCSR)
if err != nil {
t.Fatalf("%v", err)
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
// By default, no extensions should be allowed
request := signer.SignRequest{
Request: string(csrPEM),
Extensions: []signer.Extension{
{ID: config.OID(asn1.ObjectIdentifier{1, 2, 3, 4})},
},
}
_, err = s.Sign(request)
if err == nil {
t.Fatalf("expected a policy error")
}
// Whitelist a specific extension. The extension with OID 1.2.3.4 should be
// allowed through, but the one with OID 1.2.3.5 should not.
s.policy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CAConstraint: config.CAConstraint{IsCA: true},
ExtensionWhitelist: map[string]bool{"1.2.3.4": true},
},
}
// Test that a forbidden extension triggers a sign error
request = signer.SignRequest{
Request: string(csrPEM),
Extensions: []signer.Extension{
{ID: config.OID(asn1.ObjectIdentifier{1, 2, 3, 5})},
},
}
_, err = s.Sign(request)
if err == nil {
t.Fatalf("expected a policy error")
}
extValue := []byte{0x05, 0x00}
extValueHex := hex.EncodeToString(extValue)
// Test that an allowed extension makes it through
request = signer.SignRequest{
Request: string(csrPEM),
Extensions: []signer.Extension{
{
ID: config.OID(asn1.ObjectIdentifier{1, 2, 3, 4}),
Critical: false,
Value: extValueHex,
},
},
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
foundAllowed := false
for _, ext := range cert.Extensions {
if ext.Id.String() == "1.2.3.4" {
foundAllowed = true
if ext.Critical {
t.Fatalf("Extensions should not be marked critical")
}
if !bytes.Equal(extValue, ext.Value) {
t.Fatalf("Extension has wrong value: %s != %s", hex.EncodeToString(ext.Value), extValueHex)
}
}
}
if !foundAllowed {
t.Fatalf("Custom extension not included in the certificate")
}
}
func TestCTFailure(t *testing.T) {
// start a fake CT server that returns bad request
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(400)
}))
defer ts.Close()
var config = &config.Signing{
Default: &config.SigningProfile{
Expiry: helpers.OneYear,
CAConstraint: config.CAConstraint{IsCA: true},
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
ExpiryString: "8760h",
CTLogServers: []string{ts.URL},
},
}
testSigner, err := NewSignerFromFile(testCaFile, testCaKeyFile, config)
if err != nil {
t.Fatalf("%v", err)
}
var pem []byte
pem, err = ioutil.ReadFile("testdata/ex.csr")
if err != nil {
t.Fatalf("%v", err)
}
validReq := signer.SignRequest{
Request: string(pem),
Hosts: []string{"example.com"},
}
_, err = testSigner.Sign(validReq)
if err == nil {
t.Fatal("Expected CT log submission failure")
}
}
func TestCTSuccess(t *testing.T) {
// start a fake CT server that will accept the submission
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"sct_version":0,"id":"KHYaGJAn++880NYaAY12sFBXKcenQRvMvfYE9F1CYVM=","timestamp":1337,"extensions":"","signature":"BAMARjBEAiAIc21J5ZbdKZHw5wLxCP+MhBEsV5+nfvGyakOIv6FOvAIgWYMZb6Pw///uiNM7QTg2Of1OqmK1GbeGuEl9VJN8v8c="}`))
w.WriteHeader(200)
}))
defer ts.Close()
var config = &config.Signing{
Default: &config.SigningProfile{
Expiry: helpers.OneYear,
CAConstraint: config.CAConstraint{IsCA: true},
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
ExpiryString: "8760h",
CTLogServers: []string{ts.URL},
},
}
testSigner, err := NewSignerFromFile(testCaFile, testCaKeyFile, config)
if err != nil {
t.Fatalf("%v", err)
}
var pem []byte
pem, err = ioutil.ReadFile("testdata/ex.csr")
if err != nil {
t.Fatalf("%v", err)
}
validReq := signer.SignRequest{
Request: string(pem),
Hosts: []string{"example.com"},
}
_, err = testSigner.Sign(validReq)
if err != nil {
t.Fatal("Expected CT log submission success")
}
}
func TestReturnPrecert(t *testing.T) {
var config = &config.Signing{
Default: &config.SigningProfile{
Expiry: helpers.OneYear,
CAConstraint: config.CAConstraint{IsCA: true},
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
ExpiryString: "8760h",
},
}
testSigner, err := NewSignerFromFile(testCaFile, testCaKeyFile, config)
if err != nil {
t.Fatalf("%v", err)
}
csr, err := ioutil.ReadFile("testdata/ex.csr")
if err != nil {
t.Fatalf("%v", err)
}
validReq := signer.SignRequest{
Request: string(csr),
Hosts: []string{"example.com"},
ReturnPrecert: true,
}
certBytes, err := testSigner.Sign(validReq)
if err != nil {
t.Fatal("Failed to sign request")
}
block, _ := pem.Decode(certBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("Failed to parse signed cert: %s", err)
}
// check cert with poison extension was returned
poisoned := false
for _, ext := range cert.Extensions {
if ext.Id.Equal(signer.CTPoisonOID) {
poisoned = true
break
}
}
if !poisoned {
t.Fatal("Certificate without poison CT extension was returned")
}
}
func TestSignFromPrecert(t *testing.T) {
var config = &config.Signing{
Default: &config.SigningProfile{
Expiry: helpers.OneYear,
CAConstraint: config.CAConstraint{IsCA: true},
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
ExpiryString: "8760h",
},
}
testSigner, err := NewSignerFromFile(testCaFile, testCaKeyFile, config)
if err != nil {
t.Fatalf("%v", err)
}
// Generate a precert
k, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatalf("Failed to generate test key: %s", err)
}
precertBytes, err := testSigner.sign(&x509.Certificate{
SignatureAlgorithm: x509.SHA512WithRSA,
PublicKey: k.Public(),
SerialNumber: big.NewInt(10),
Subject: pkix.Name{CommonName: "CN"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
ExtraExtensions: []pkix.Extension{
{Id: signer.CTPoisonOID, Critical: true, Value: []byte{0x05, 0x00}},
},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
SubjectKeyId: []byte{0, 1},
AuthorityKeyId: []byte{1, 0},
OCSPServer: []string{"ocsp?"},
IssuingCertificateURL: []string{"url"},
DNSNames: []string{"example.com"},
EmailAddresses: []string{"email@example.com"},
IPAddresses: []net.IP{net.ParseIP("1.1.1.1")},
CRLDistributionPoints: []string{"crl"},
PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3}},
})
if err != nil {
t.Fatalf("Failed to sign request: %s", err)
}
block, _ := pem.Decode(precertBytes)
precert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("Failed to parse signed cert: %s", err)
}
// Create a cert from the precert
scts := []ct.SignedCertificateTimestamp{{}}
certBytes, err := testSigner.SignFromPrecert(precert, scts)
if err != nil {
t.Fatalf("Failed to sign cert from precert: %s", err)
}
block, _ = pem.Decode(certBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("Failed to parse signed cert: %s", err)
}
// check cert doesn't contains poison extension
poisoned := false
for _, ext := range cert.Extensions {
if ext.Id.Equal(signer.CTPoisonOID) {
poisoned = true
break
}
}
if poisoned {
t.Fatal("Certificate with poison CT extension was returned")
}
// check cert contains SCT list extension
list := false
for _, ext := range cert.Extensions {
if ext.Id.Equal(signer.SCTListOID) {
list = true
break
}
}
if !list {
t.Fatal("Certificate without SCT list extension was returned")
}
// Break poison extension
precert.Extensions[7].Value = []byte{1, 3, 3, 7}
_, err = testSigner.SignFromPrecert(precert, scts)
if err == nil {
t.Fatal("SignFromPrecert didn't fail with invalid poison extension")
}
precert.Extensions[7].Critical = false
_, err = testSigner.SignFromPrecert(precert, scts)
if err == nil {
t.Fatal("SignFromPrecert didn't fail with non-critical poison extension")
}
precert.Extensions = append(precert.Extensions[:7], precert.Extensions[8:]...)
_, err = testSigner.SignFromPrecert(precert, scts)
if err == nil {
t.Fatal("SignFromPrecert didn't fail with missing poison extension")
}
precert.Signature = []byte("nop")
_, err = testSigner.SignFromPrecert(precert, scts)
if err == nil {
t.Fatal("SignFromPrecert didn't fail with signature not from CA")
}
}