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:
Jacob Hoffman-Andrews 2015-06-24 14:44:23 -07:00
parent c41c0a6b41
commit dd19f0a529
10 changed files with 268 additions and 83 deletions

24
Godeps/Godeps.json generated
View File

@ -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",

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)))
}
}

View File

@ -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",

View File

@ -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": {

View File

@ -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",