diff --git a/cli/testdata/csr.json b/cli/testdata/csr.json index bc01b19f3..2929cd276 100644 --- a/cli/testdata/csr.json +++ b/cli/testdata/csr.json @@ -18,5 +18,6 @@ "1.2.3.4.5": "abc" } } - ] -} \ No newline at end of file + ], + "delegation_enabled": true +} diff --git a/csr/csr.go b/csr/csr.go index f26ca132a..0ca250994 100644 --- a/csr/csr.go +++ b/csr/csr.go @@ -37,7 +37,7 @@ type Name struct { L string `json:"L,omitempty" yaml:"L,omitempty"` // Locality O string `json:"O,omitempty" yaml:"O,omitempty"` // OrganisationName OU string `json:"OU,omitempty" yaml:"OU,omitempty"` // OrganisationalUnitName - E string `json:"E,omitempty" yaml:"E,omitempty"` + E string `json:"E,omitempty" yaml:"E,omitempty"` SerialNumber string `json:"SerialNumber,omitempty" yaml:"SerialNumber,omitempty"` OID map[string]string `json:"OID,omitempty", yaml:"OID,omitempty"` } @@ -136,14 +136,15 @@ type CAConfig struct { // A CertificateRequest encapsulates the API interface to the // certificate request functionality. type CertificateRequest struct { - CN string `json:"CN" yaml:"CN"` - Names []Name `json:"names" yaml:"names"` - Hosts []string `json:"hosts" yaml:"hosts"` - KeyRequest *KeyRequest `json:"key,omitempty" yaml:"key,omitempty"` - CA *CAConfig `json:"ca,omitempty" yaml:"ca,omitempty"` - SerialNumber string `json:"serialnumber,omitempty" yaml:"serialnumber,omitempty"` - Extensions []pkix.Extension `json:"extensions,omitempty" yaml:"extensions,omitempty"` - CRL string `json:"crl_url,omitempty" yaml:"crl_url,omitempty"` + CN string `json:"CN" yaml:"CN"` + Names []Name `json:"names" yaml:"names"` + Hosts []string `json:"hosts" yaml:"hosts"` + KeyRequest *KeyRequest `json:"key,omitempty" yaml:"key,omitempty"` + CA *CAConfig `json:"ca,omitempty" yaml:"ca,omitempty"` + SerialNumber string `json:"serialnumber,omitempty" yaml:"serialnumber,omitempty"` + DelegationEnabled bool `json:"delegation_enabled,omitempty" yaml:"delegation_enabled,omitempty"` + Extensions []pkix.Extension `json:"extensions,omitempty" yaml:"extensions,omitempty"` + CRL string `json:"crl_url,omitempty" yaml:"crl_url,omitempty"` } // New returns a new, empty CertificateRequest with a @@ -196,9 +197,9 @@ func (cr *CertificateRequest) Name() (pkix.Name, error) { } name.ExtraNames = append(name.ExtraNames, pkix.AttributeTypeAndValue{Type: oid, Value: v}) } - if n.E != "" { - name.ExtraNames = append(name.ExtraNames, pkix.AttributeTypeAndValue{Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: n.E}) - } + if n.E != "" { + name.ExtraNames = append(name.ExtraNames, pkix.AttributeTypeAndValue{Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: n.E}) + } } name.SerialNumber = cr.SerialNumber return name, nil @@ -430,6 +431,10 @@ func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err erro } } + if req.DelegationEnabled { + tpl.ExtraExtensions = append(tpl.Extensions, helpers.DelegationExtension) + } + if req.Extensions != nil { err = appendExtensionsToCSR(req.Extensions, &tpl) if err != nil { diff --git a/csr/csr_test.go b/csr/csr_test.go index 89fd9d313..1cf4577a5 100644 --- a/csr/csr_test.go +++ b/csr/csr_test.go @@ -786,3 +786,50 @@ func TestExtractCertificateRequest(t *testing.T) { t.Fatal("Bad Certificate Request!") } } + +// TestDelegationCSR tests that we create requests with the DC extension +func TestDelegationCSR(t *testing.T) { + var cr = &CertificateRequest{ + CN: "Test Common Name", + Names: []Name{ + { + C: "US", + ST: "California", + L: "San Francisco", + O: "CloudFlare, Inc.", + OU: "Systems Engineering", + }, + { + C: "GB", + ST: "London", + L: "London", + O: "CloudFlare, Inc", + OU: "Systems Engineering", + }, + }, + DelegationEnabled: true, + Hosts: []string{"cloudflare.com", "www.cloudflare.com"}, + KeyRequest: NewKeyRequest(), + } + csr, _, err := ParseRequest(cr) + if err != nil { + t.Fatal("could not generate csr") + } + unPem, _ := pem.Decode(csr) + if unPem == nil { + t.Fatal("Failed to decode pem") + } + res, err := x509.ParseCertificateRequest(unPem.Bytes) + if err != nil { + t.Fatalf("spat out nonsense as a csr: %v", err) + } + found := false + for _, ext := range res.Extensions { + if ext.Id.Equal(helpers.DelegationUsage) { + found = true + } + } + if !found { + t.Fatal("generated csr has no extension") + } +} diff --git a/helpers/helpers.go b/helpers/helpers.go index 8b957582d..2557ddb57 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -39,6 +39,16 @@ const OneYear = 8760 * time.Hour // OneDay is a time.Duration representing a day's worth of seconds. const OneDay = 24 * time.Hour +// DelegationUsage is the OID for the DelegationUseage extensions +var DelegationUsage = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 44} + +// DelegationExtension +var DelegationExtension = pkix.Extension{ + Id: DelegationUsage, + Critical: false, + Value: []byte{0x05, 0x00}, // ASN.1 NULL +} + // InclusiveDate returns the time.Time representation of a date - 1 // nanosecond. This allows time.After to be used inclusively. func InclusiveDate(year int, month time.Month, day int) time.Time { diff --git a/signer/signer.go b/signer/signer.go index 6224cca97..ea650bd6d 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -20,6 +20,7 @@ import ( "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/info" ) @@ -45,7 +46,7 @@ type Extension struct { // Extensions provided in the signRequest are copied into the certificate, as // long as they are in the ExtensionWhitelist for the signer's policy. // Extensions requested in the CSR are ignored, except for those processed by -// ParseCertificateRequest (mainly subjectAltName). +// ParseCertificateRequest (mainly subjectAltName) and DelegationUsage. type SignRequest struct { Hosts []string `json:"hosts"` Request string `json:"certificate_request"` @@ -240,6 +241,8 @@ func ParseCertificateRequest(s Signer, p *config.SigningProfile, csrBytes []byte template.IsCA = constraints.IsCA template.MaxPathLen = constraints.MaxPathLen template.MaxPathLenZero = template.MaxPathLen == 0 + } else if val.Id.Equal(helpers.DelegationUsage) { + template.ExtraExtensions = append(template.ExtraExtensions, val) } else { // If the profile has 'copy_extensions' to true then lets add it if p.CopyExtensions {