Skip to content

Commit

Permalink
Custom EKUs handling in profiles and CSRWhitelist
Browse files Browse the repository at this point in the history
This mod allowes one to define custom EKUs as OIDs in
Usages. It also allows one to copy EKUs from CSR using
CSRWhitelist (only EKUs present in profiles Usages may
be copied).

Related: cloudflare#385
Author-Change-Id: IB#1094896
  • Loading branch information
pboguslawski committed Oct 15, 2019
1 parent 1a911ca commit d98aa26
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 17 deletions.
106 changes: 100 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,18 @@ import (
// Since API clients are expected to be trusted, but CSRs are not, fields
// provided through the API are not subject to whitelisting through this
// mechanism.
// Note: when EKUs = true, only EKUs defined in Usage are copied from CSR;
// when EKUs = false or CSRWhitelist is not present, EKUs are copied from Usage.
type CSRWhitelist struct {
Subject, PublicKeyAlgorithm, PublicKey, SignatureAlgorithm bool
DNSNames, IPAddresses, EmailAddresses, URIs bool
Subject bool `json:"subject"`
PublicKeyAlgorithm bool `json:"public_key_algorithm"`
PublicKey bool `json:"public_key"`
SignatureAlgorithm bool `json:"signature_algorithm"`
DNSNames bool `json:"dns_names"`
IPAddresses bool `json:"ip_addresses"`
EmailAddresses bool `json:"email_addressess"`
URIs bool `json:"uris"`
EKUs bool `json:"ekus"`
}

// OID is our own version of asn1's ObjectIdentifier, so we can define a custom
Expand Down Expand Up @@ -114,7 +123,7 @@ type SigningProfile struct {
RemoteServer string
RemoteCAs *x509.CertPool
ClientCert *tls.Certificate
CSRWhitelist *CSRWhitelist
CSRWhitelist *CSRWhitelist `json:"csr_whitelist"`
NameWhitelist *regexp.Regexp
ExtensionWhitelist map[string]bool
ClientProvidesSerialNumbers bool
Expand Down Expand Up @@ -428,15 +437,34 @@ func (p *Signing) NeedsLocalSigner() bool {
return false
}

// str2OID converts string to OID if possible (or throws error if not)
func str2OID(s string) (oid asn1.ObjectIdentifier, err error) {
strArray := strings.Split(s, ".")
oid = make(asn1.ObjectIdentifier, len(strArray), len(strArray))
for i, s := range strArray {
oid[i], err = strconv.Atoi(s)
if err != nil {
return
}
}
return
}

// Usages parses the list of key uses in the profile, translating them
// to a list of X.509 key usages and extended key usages. The unknown
// to a list of X.509 key usages and extended key usages (as OIDs). The unknown
// uses are collected into a slice that is also returned.
func (p *SigningProfile) Usages() (ku x509.KeyUsage, eku []x509.ExtKeyUsage, unk []string) {
func (p *SigningProfile) Usages() (ku x509.KeyUsage, eku []asn1.ObjectIdentifier, unk []string) {
for _, keyUse := range p.Usage {
if kuse, ok := KeyUsage[keyUse]; ok {
ku |= kuse
} else if ekuse, ok := ExtKeyUsage[keyUse]; ok {
eku = append(eku, ekuse)
if oid, ok := OIDFromKnownExtKeyUsage(ekuse); ok {
eku = append(eku, oid)
} else {
unk = append(unk, keyUse)
}
} else if oid, err := str2OID(keyUse); err == nil {
eku = append(eku, oid)
} else {
unk = append(unk, keyUse)
}
Expand Down Expand Up @@ -632,6 +660,72 @@ var ExtKeyUsage = map[string]x509.ExtKeyUsage{
"netscape sgc": x509.ExtKeyUsageNetscapeServerGatedCrypto,
}

// RFC 5280, 4.2.1.12 Extended Key Usage
//
// anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
//
// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
//
// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
// id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
// id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
// id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 }
// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
var (
oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0}
oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1}
oidExtKeyUsageClientAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2}
oidExtKeyUsageCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3}
oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4}
oidExtKeyUsageIPSECEndSystem = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5}
oidExtKeyUsageIPSECTunnel = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6}
oidExtKeyUsageIPSECUser = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7}
oidExtKeyUsageTimeStamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8}
oidExtKeyUsageOCSPSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9}
oidExtKeyUsageMicrosoftServerGatedCrypto = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3}
oidExtKeyUsageNetscapeServerGatedCrypto = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1}
)

// extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID.
var knownExtKeyUsageOIDs = []struct {
extKeyUsage x509.ExtKeyUsage
oid asn1.ObjectIdentifier
}{
{x509.ExtKeyUsageAny, oidExtKeyUsageAny},
{x509.ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth},
{x509.ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth},
{x509.ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning},
{x509.ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection},
{x509.ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem},
{x509.ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel},
{x509.ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser},
{x509.ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping},
{x509.ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning},
{x509.ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto},
{x509.ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto},
}

// KnownExtKeyUsageFromOID converts OID to known EKU.
func KnownExtKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku x509.ExtKeyUsage, ok bool) {
for _, pair := range knownExtKeyUsageOIDs {
if oid.Equal(pair.oid) {
return pair.extKeyUsage, true
}
}
return
}

// OIDFromKnownExtKeyUsage converts known EKU to OID.
func OIDFromKnownExtKeyUsage(eku x509.ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) {
for _, pair := range knownExtKeyUsageOIDs {
if eku == pair.extKeyUsage {
return pair.oid, true
}
}
return
}

// An AuthKey contains an entry for a key used for authentication.
type AuthKey struct {
// Type contains information needed to select the appropriate
Expand Down
5 changes: 3 additions & 2 deletions doc/cmd/cfssl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ blank.
+ crl sign
+ encipher only
+ decipher only

+ Ext Key Usages
+ any
+ server auth
Expand All @@ -112,6 +112,7 @@ blank.
+ ocsp signing
+ microsoft sgc
+ netscape sgc
+ any EKU as OID string i.e. 1.3.6.1.5.5.7.3.2

+ issuer_urls: a list of Authority Information Access (RFC 5280
4.2.2.1) URLs pointing to the issuer certificate.
Expand Down Expand Up @@ -193,7 +194,7 @@ A minimal configuration file might look like:
}
},
"default": {
"usages": ["digital signature", "email protection"],
"usages": ["digital signature", "1.3.6.1.5.5.7.3.4"],
"expiry": "8000h"
}
},
Expand Down
12 changes: 10 additions & 2 deletions selfsign/selfsign.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func Sign(priv crypto.Signer, csrPEM []byte, profile *config.SigningProfile) ([]
pubhash.Write(subPKI.SubjectPublicKey.Bytes)

var (
eku []x509.ExtKeyUsage
eku []asn1.ObjectIdentifier
ku x509.KeyUsage
expiry time.Duration
crlURL, ocspURL string
Expand Down Expand Up @@ -120,7 +120,15 @@ func Sign(priv crypto.Signer, csrPEM []byte, profile *config.SigningProfile) ([]
template.NotBefore = now.Add(-5 * time.Minute).UTC()
template.NotAfter = now.Add(expiry).UTC()
template.KeyUsage = ku
template.ExtKeyUsage = eku

for _, oidProfile := range eku {
if e, ok := config.KnownExtKeyUsageFromOID(oidProfile); ok {
template.ExtKeyUsage = append(template.ExtKeyUsage, e)
} else {
template.UnknownExtKeyUsage = append(template.UnknownExtKeyUsage, oidProfile)
}
}

template.BasicConstraintsValid = true
template.IsCA = profile.CAConstraint.IsCA
template.SubjectKeyId = pubhash.Sum(nil)
Expand Down
9 changes: 7 additions & 2 deletions signer/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/cloudflare/cfssl/info"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
"github.com/google/certificate-transparency-go"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/client"
"github.com/google/certificate-transparency-go/jsonclient"

Expand Down Expand Up @@ -311,9 +311,10 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
// Copy out only the fields from the CSR authorized by policy.
safeTemplate := x509.Certificate{}
// If the profile contains no explicit whitelist, assume that all fields
// should be copied from the CSR.
// (except EKUs) should be copied from the CSR.
if profile.CSRWhitelist == nil {
safeTemplate = *csrTemplate
safeTemplate.UnknownExtKeyUsage = []asn1.ObjectIdentifier{}
} else {
if profile.CSRWhitelist.Subject {
safeTemplate.Subject = csrTemplate.Subject
Expand All @@ -339,6 +340,10 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
if profile.CSRWhitelist.URIs {
safeTemplate.URIs = csrTemplate.URIs
}
if profile.CSRWhitelist.EKUs {
// Will be further filtered in signer.FillTemplate call below.
safeTemplate.UnknownExtKeyUsage = csrTemplate.UnknownExtKeyUsage
}
}

if req.CRLOverride != "" {
Expand Down
58 changes: 53 additions & 5 deletions signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,20 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
template.MaxPathLen = constraints.MaxPathLen
template.MaxPathLenZero = template.MaxPathLen == 0
}
// Check the CSR for the X.509 Extended Key Usage (RFC 5280, 4.2.1.12)
// extension and temporarily append to template as UnknownExtKeyUsage if found
if val.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 37}) {
var eku []asn1.ObjectIdentifier
var rest []byte

if rest, err = asn1.Unmarshal(val.Value, &eku); err != nil {
return nil, cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
} else if len(rest) != 0 {
return nil, cferr.Wrap(cferr.CSRError, cferr.ParseFailed, errors.New("x509: trailing data after X.509 Extended Key Usage"))
}

template.UnknownExtKeyUsage = eku
}
}

return
Expand Down Expand Up @@ -254,8 +268,8 @@ func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.Si
}

var (
eku []x509.ExtKeyUsage
ku x509.KeyUsage
eku []asn1.ObjectIdentifier
backdate time.Duration
expiry time.Duration
crlURL, ocspURL string
Expand All @@ -266,14 +280,15 @@ func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.Si
// This should be used when validating the profile at load, and isn't used
// here.
ku, eku, _ = profile.Usages()
if profile.IssuerURL == nil {
issuerURL = defaultProfile.IssuerURL
}

if ku == 0 && len(eku) == 0 {
return cferr.New(cferr.PolicyError, cferr.NoKeyUsages)
}

if profile.IssuerURL == nil {
issuerURL = defaultProfile.IssuerURL
}

if expiry = profile.Expiry; expiry == 0 {
expiry = defaultProfile.Expiry
}
Expand Down Expand Up @@ -310,8 +325,41 @@ func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.Si

template.NotBefore = notBefore
template.NotAfter = notAfter

template.KeyUsage = ku
template.ExtKeyUsage = eku

if profile.CSRWhitelist != nil && profile.CSRWhitelist.EKUs {
// If EKUs are whitelisted - add only EKUs from CSR that are defined in profile.
ekusFromCSR := template.UnknownExtKeyUsage
template.UnknownExtKeyUsage = []asn1.ObjectIdentifier{}
for _, oidCSR := range ekusFromCSR {
for _, oidProfile := range eku {
if oidCSR.Equal(oidProfile) {
if e, ok := config.KnownExtKeyUsageFromOID(oidCSR); ok {
template.ExtKeyUsage = append(template.ExtKeyUsage, e)
} else {
template.UnknownExtKeyUsage = append(template.UnknownExtKeyUsage, oidCSR)
}
}

}
}
} else {
// If EKUs are not whitelisted - add EKUs defined in profile.
template.UnknownExtKeyUsage = []asn1.ObjectIdentifier{}
for _, oidProfile := range eku {
if e, ok := config.KnownExtKeyUsageFromOID(oidProfile); ok {
template.ExtKeyUsage = append(template.ExtKeyUsage, e)
} else {
template.UnknownExtKeyUsage = append(template.UnknownExtKeyUsage, oidProfile)
}
}
}

if template.KeyUsage == 0 && len(template.ExtKeyUsage) == 0 && len(template.UnknownExtKeyUsage) == 0 {
return cferr.New(cferr.PolicyError, cferr.NoKeyUsages)
}

template.BasicConstraintsValid = true
template.IsCA = profile.CAConstraint.IsCA
if template.IsCA {
Expand Down

0 comments on commit d98aa26

Please sign in to comment.