Update cfssl to latest master.
Picks up fix for specifying User Notice policy qualifier. Specify user notice in test configs.
This commit is contained in:
parent
c41c0a6b41
commit
dd19f0a529
|
|
@ -12,51 +12,51 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/config",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs12",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/info",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/log",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
||||
"Rev": "6f428f654df58d23d1321bcbe3598f6b8a02167a"
|
||||
"Rev": "fd649feb9eb317e3ed24afee0d92c0ea55bf4a33"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
|
|
|
|||
|
|
@ -38,38 +38,46 @@ type CSRWhitelist struct {
|
|||
// JSON marshal / unmarshal.
|
||||
type OID asn1.ObjectIdentifier
|
||||
|
||||
// CertificatePolicy is a flattening of the ASN.1 PolicyInformation structure from
|
||||
// CertificatePolicy represents the ASN.1 PolicyInformation structure from
|
||||
// https://tools.ietf.org/html/rfc3280.html#page-106.
|
||||
// Valid values of Type are "id-qt-unotice" and "id-qt-cps"
|
||||
type CertificatePolicy struct {
|
||||
ID OID
|
||||
Type string
|
||||
Qualifier string
|
||||
ID OID
|
||||
Qualifiers []CertificatePolicyQualifier
|
||||
}
|
||||
|
||||
// CertificatePolicyQualifier represents a single qualifier from an ASN.1
|
||||
// PolicyInformation structure.
|
||||
type CertificatePolicyQualifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
// A SigningProfile stores information that the CA needs to store
|
||||
// signature policy.
|
||||
type SigningProfile struct {
|
||||
Usage []string `json:"usages"`
|
||||
IssuerURL []string `json:"issuer_urls"`
|
||||
OCSP string `json:"ocsp_url"`
|
||||
CRL string `json:"crl_url"`
|
||||
CA bool `json:"is_ca"`
|
||||
OCSPNoCheck bool `json:"ocsp_no_check"`
|
||||
ExpiryString string `json:"expiry"`
|
||||
BackdateString string `json:"backdate"`
|
||||
AuthKeyName string `json:"auth_key"`
|
||||
RemoteName string `json:"remote"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
Usage []string `json:"usages"`
|
||||
IssuerURL []string `json:"issuer_urls"`
|
||||
OCSP string `json:"ocsp_url"`
|
||||
CRL string `json:"crl_url"`
|
||||
CA bool `json:"is_ca"`
|
||||
OCSPNoCheck bool `json:"ocsp_no_check"`
|
||||
ExpiryString string `json:"expiry"`
|
||||
BackdateString string `json:"backdate"`
|
||||
AuthKeyName string `json:"auth_key"`
|
||||
RemoteName string `json:"remote"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
NameWhitelistString string `json:"name_whitelist"`
|
||||
|
||||
Policies []CertificatePolicy
|
||||
Expiry time.Duration
|
||||
Backdate time.Duration
|
||||
Provider auth.Provider
|
||||
RemoteServer string
|
||||
UseSerialSeq bool
|
||||
CSRWhitelist *CSRWhitelist
|
||||
Policies []CertificatePolicy
|
||||
Expiry time.Duration
|
||||
Backdate time.Duration
|
||||
Provider auth.Provider
|
||||
RemoteServer string
|
||||
UseSerialSeq bool
|
||||
CSRWhitelist *CSRWhitelist
|
||||
NameWhitelist *regexp.Regexp
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a JSON string into an OID.
|
||||
|
|
@ -162,8 +170,11 @@ func (p *SigningProfile) populate(cfg *Config) error {
|
|||
|
||||
if len(p.Policies) > 0 {
|
||||
for _, policy := range p.Policies {
|
||||
if policy.Type != "" && policy.Type != "id-qt-unotice" && policy.Type != "id-qt-cps" {
|
||||
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
|
||||
for _, qualifier := range policy.Qualifiers {
|
||||
if qualifier.Type != "" && qualifier.Type != "id-qt-unotice" && qualifier.Type != "id-qt-cps" {
|
||||
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
||||
errors.New("invalid policy qualifier type"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -200,6 +211,16 @@ func (p *SigningProfile) populate(cfg *Config) error {
|
|||
}
|
||||
}
|
||||
|
||||
if p.NameWhitelistString != "" {
|
||||
log.Debug("compiling whitelist regular expression")
|
||||
rule, err := regexp.Compile(p.NameWhitelistString)
|
||||
if err != nil {
|
||||
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
||||
errors.New("failed to compile name whitelist section"))
|
||||
}
|
||||
p.NameWhitelist = rule
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -225,6 +225,20 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
OverrideHosts(&safeTemplate, req.Hosts)
|
||||
safeTemplate.Subject = PopulateSubjectFromCSR(req.Subject, safeTemplate.Subject)
|
||||
|
||||
// If there is a whitelist, ensure that both the Common Name and SAN DNSNames match
|
||||
if profile.NameWhitelist != nil {
|
||||
if safeTemplate.Subject.CommonName != "" {
|
||||
if profile.NameWhitelist.Find([]byte(safeTemplate.Subject.CommonName)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.DNSNames {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.sign(&safeTemplate, profile, serialSeq)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -807,3 +808,75 @@ func TestWhitelistSign(t *testing.T) {
|
|||
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,
|
||||
CA: 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ func (s *Signer) remoteOp(req interface{}, profile, target string) (resp interfa
|
|||
if target == "info" {
|
||||
resp, err = server.Info(jsonData)
|
||||
} else if p.Provider != nil {
|
||||
resp, err = server.AuthReq(jsonData, nil, p.Provider, target)
|
||||
resp, err = server.AuthSign(jsonData, nil, p.Provider)
|
||||
} else {
|
||||
resp, err = server.Req(jsonData, target)
|
||||
resp, err = server.Sign(jsonData)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -27,19 +27,11 @@ import (
|
|||
// MaxPathLen is the default path length for a new CA certificate.
|
||||
var MaxPathLen = 2
|
||||
|
||||
// A Whitelist marks which fields should be set. As a bool's default
|
||||
// value is false, a whitelist should only keep those fields marked
|
||||
// true.
|
||||
type Whitelist struct {
|
||||
CN, C, ST, L, O, OU bool
|
||||
}
|
||||
|
||||
// Subject contains the information that should be used to override the
|
||||
// subject information when signing a certificate.
|
||||
type Subject struct {
|
||||
CN string
|
||||
Names []csr.Name `json:"names"`
|
||||
Whitelist *Whitelist `json:"whitelist,omitempty"`
|
||||
CN string
|
||||
Names []csr.Name `json:"names"`
|
||||
}
|
||||
|
||||
// SignRequest stores a signature request, which contains the hostname,
|
||||
|
|
@ -351,13 +343,26 @@ func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.Si
|
|||
return nil
|
||||
}
|
||||
|
||||
type policyQualifier struct {
|
||||
type policyInformation struct {
|
||||
PolicyIdentifier asn1.ObjectIdentifier
|
||||
Qualifiers []interface{}
|
||||
CPSPolicyQualifiers []cpsPolicyQualifier `asn1:"omitempty"`
|
||||
// User Notice policy qualifiers have a slightly different ASN.1 structure
|
||||
// from that used for CPS policy qualifiers.
|
||||
UserNoticePolicyQualifiers []userNoticePolicyQualifier `asn1:"omitempty"`
|
||||
}
|
||||
|
||||
type cpsPolicyQualifier struct {
|
||||
PolicyQualifierID asn1.ObjectIdentifier
|
||||
Qualifier string `asn1:"tag:optional,ia5"`
|
||||
}
|
||||
type policyInformation struct {
|
||||
PolicyIdentifier asn1.ObjectIdentifier
|
||||
PolicyQualifiers []policyQualifier `asn1:"omitempty"`
|
||||
|
||||
type userNotice struct {
|
||||
ExplicitText string `asn1:"tag:optional,utf8"`
|
||||
}
|
||||
type userNoticePolicyQualifier struct {
|
||||
PolicyQualifierID asn1.ObjectIdentifier
|
||||
Qualifier userNotice
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -382,26 +387,25 @@ func addPolicies(template *x509.Certificate, policies []config.CertificatePolicy
|
|||
// The PolicyIdentifier is an OID assigned to a given issuer.
|
||||
PolicyIdentifier: asn1.ObjectIdentifier(policy.ID),
|
||||
}
|
||||
switch policy.Type {
|
||||
case "id-qt-unotice":
|
||||
pi.PolicyQualifiers = []policyQualifier{
|
||||
policyQualifier{
|
||||
PolicyQualifierID: iDQTUserNotice,
|
||||
Qualifier: policy.Qualifier,
|
||||
},
|
||||
for _, qualifier := range policy.Qualifiers {
|
||||
switch qualifier.Type {
|
||||
case "id-qt-unotice":
|
||||
pi.Qualifiers = append(pi.Qualifiers,
|
||||
userNoticePolicyQualifier{
|
||||
PolicyQualifierID: iDQTUserNotice,
|
||||
Qualifier: userNotice{
|
||||
ExplicitText: qualifier.Value,
|
||||
},
|
||||
})
|
||||
case "id-qt-cps":
|
||||
pi.Qualifiers = append(pi.Qualifiers,
|
||||
cpsPolicyQualifier{
|
||||
PolicyQualifierID: iDQTCertificationPracticeStatement,
|
||||
Qualifier: qualifier.Value,
|
||||
})
|
||||
default:
|
||||
return errors.New("Invalid qualifier type in Policies " + qualifier.Type)
|
||||
}
|
||||
case "id-qt-cps":
|
||||
pi.PolicyQualifiers = []policyQualifier{
|
||||
policyQualifier{
|
||||
PolicyQualifierID: iDQTCertificationPracticeStatement,
|
||||
Qualifier: policy.Qualifier,
|
||||
},
|
||||
}
|
||||
case "":
|
||||
// Empty qualifier type is fine: Include this Certificate Policy, but
|
||||
// don't include a Policy Qualifier.
|
||||
default:
|
||||
return errors.New("Invalid qualifier type in Policies " + policy.Type)
|
||||
}
|
||||
asn1PolicyList = append(asn1PolicyList, pi)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
package signer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
)
|
||||
|
||||
func TestSplitHosts(t *testing.T) {
|
||||
|
|
@ -23,3 +31,63 @@ func TestSplitHosts(t *testing.T) {
|
|||
t.Fatal("SplitHost fails to split multiple domains")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPolicies(t *testing.T) {
|
||||
var cert x509.Certificate
|
||||
addPolicies(&cert, []config.CertificatePolicy{
|
||||
config.CertificatePolicy{
|
||||
ID: config.OID{1, 2, 3, 4},
|
||||
},
|
||||
})
|
||||
|
||||
if len(cert.ExtraExtensions) != 1 {
|
||||
t.Fatal("No extension added")
|
||||
}
|
||||
ext := cert.ExtraExtensions[0]
|
||||
if !reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 32}) {
|
||||
t.Fatal(fmt.Sprintf("Wrong OID for policy qualifier %v", ext.Id))
|
||||
}
|
||||
if ext.Critical {
|
||||
t.Fatal("Policy qualifier marked critical")
|
||||
}
|
||||
expectedBytes, _ := hex.DecodeString("3009300706032a03043000")
|
||||
if !bytes.Equal(ext.Value, expectedBytes) {
|
||||
t.Fatal(fmt.Sprintf("Value didn't match expected bytes: %s vs %s",
|
||||
hex.EncodeToString(ext.Value), hex.EncodeToString(expectedBytes)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddPoliciesWithQualifiers(t *testing.T) {
|
||||
var cert x509.Certificate
|
||||
addPolicies(&cert, []config.CertificatePolicy{
|
||||
config.CertificatePolicy{
|
||||
ID: config.OID{1, 2, 3, 4},
|
||||
Qualifiers: []config.CertificatePolicyQualifier{
|
||||
config.CertificatePolicyQualifier{
|
||||
Type: "id-qt-cps",
|
||||
Value: "http://example.com/cps",
|
||||
},
|
||||
config.CertificatePolicyQualifier{
|
||||
Type: "id-qt-unotice",
|
||||
Value: "Do What Thou Wilt",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if len(cert.ExtraExtensions) != 1 {
|
||||
t.Fatal("No extension added")
|
||||
}
|
||||
ext := cert.ExtraExtensions[0]
|
||||
if !reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 32}) {
|
||||
t.Fatal(fmt.Sprintf("Wrong OID for policy qualifier %v", ext.Id))
|
||||
}
|
||||
if ext.Critical {
|
||||
t.Fatal("Policy qualifier marked critical")
|
||||
}
|
||||
expectedBytes, _ := hex.DecodeString("304e304c06032a03043045302206082b060105050702011616687474703a2f2f6578616d706c652e636f6d2f637073301f06082b0601050507020230130c11446f20576861742054686f752057696c74")
|
||||
if !bytes.Equal(ext.Value, expectedBytes) {
|
||||
t.Fatal(fmt.Sprintf("Value didn't match expected bytes: %s vs %s",
|
||||
hex.EncodeToString(ext.Value), hex.EncodeToString(expectedBytes)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,9 +74,14 @@
|
|||
"ID": "2.23.140.1.2.1"
|
||||
},
|
||||
{
|
||||
"ID": "1.3.6.1.4.1.44947.1.1.1",
|
||||
"type": "id-qt-cps",
|
||||
"qualifier": "http://cps.root-x1.letsencrypt.org"
|
||||
"ID": "1.2.3.4",
|
||||
"Qualifiers": [ {
|
||||
"type": "id-qt-cps",
|
||||
"value": "http://example.com/cps"
|
||||
}, {
|
||||
"type": "id-qt-unotice",
|
||||
"value": "Do What Thou Wilt"
|
||||
} ]
|
||||
}
|
||||
],
|
||||
"expiry": "8760h",
|
||||
|
|
|
|||
|
|
@ -74,11 +74,6 @@
|
|||
{
|
||||
"ID": "2.23.140.1.2.1"
|
||||
},
|
||||
{
|
||||
"ID": "1.3.6.1.4.1.44947.1.1.1",
|
||||
"type": "id-qt-cps",
|
||||
"qualifier": "http://cps.root-x1.letsencrypt.org"
|
||||
}
|
||||
],
|
||||
"expiry": "8760h",
|
||||
"CSRWhitelist": {
|
||||
|
|
|
|||
|
|
@ -69,9 +69,14 @@
|
|||
"ID": "2.23.140.1.2.1"
|
||||
},
|
||||
{
|
||||
"ID": "1.3.6.1.4.1.44947.1.1.1",
|
||||
"type": "id-qt-cps",
|
||||
"qualifier": "http://cps.root-x1.letsencrypt.org"
|
||||
"ID": "1.2.3.4",
|
||||
"Qualifiers": [ {
|
||||
"type": "id-qt-cps",
|
||||
"value": "http://example.com/cps"
|
||||
}, {
|
||||
"type": "id-qt-unotice",
|
||||
"value": "Do What Thou Wilt"
|
||||
} ]
|
||||
}
|
||||
],
|
||||
"expiry": "8760h",
|
||||
|
|
|
|||
Loading…
Reference in New Issue