Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support certificate policies in the pki backend #4125

Merged
merged 10 commits into from
Mar 20, 2018
67 changes: 46 additions & 21 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,19 @@ type dataBundle struct {
}

type creationParameters struct {
Subject pkix.Name
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
OtherSANs map[string][]string
IsCA bool
KeyType string
KeyBits int
NotAfter time.Time
KeyUsage x509.KeyUsage
ExtKeyUsage certExtKeyUsage
Subject pkix.Name
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
OtherSANs map[string][]string
IsCA bool
KeyType string
KeyBits int
NotAfter time.Time
KeyUsage x509.KeyUsage
ExtKeyUsage certExtKeyUsage
PolicyIdentifiers []string
BasicConstraintsValidForNonCA bool

// Only used when signing a CA cert
UseCSRValues bool
Expand Down Expand Up @@ -918,16 +920,18 @@ func generateCreationBundle(b *backend, data *dataBundle) error {
}

data.params = &creationParameters{
Subject: subject,
DNSNames: dnsNames,
EmailAddresses: emailAddresses,
IPAddresses: ipAddresses,
OtherSANs: otherSANs,
KeyType: data.role.KeyType,
KeyBits: data.role.KeyBits,
NotAfter: notAfter,
KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)),
ExtKeyUsage: extUsage,
Subject: subject,
DNSNames: dnsNames,
EmailAddresses: emailAddresses,
IPAddresses: ipAddresses,
OtherSANs: otherSANs,
KeyType: data.role.KeyType,
KeyBits: data.role.KeyBits,
NotAfter: notAfter,
KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)),
ExtKeyUsage: extUsage,
PolicyIdentifiers: data.role.PolicyIdentifiers,
BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA,
}

// Don't deal with URLs or max path length if it's self-signed, as these
Expand Down Expand Up @@ -986,6 +990,17 @@ func addKeyUsages(data *dataBundle, certTemplate *x509.Certificate) {
}
}

// addPolicyIdentifiers adds certificate policies extension
//
func addPolicyIdentifiers(data *dataBundle, certTemplate *x509.Certificate) {
for _, oidstr := range data.params.PolicyIdentifiers {
oid, err := stringToOid(oidstr)
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return an error if we can't parse an OID instead of silently swallowing the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should do that at submission time, sanity-checking the role data that was provided? It's safer to do in both places but not sure it's really necessary.

certTemplate.PolicyIdentifiers = append(certTemplate.PolicyIdentifiers, oid)
}
}
}

// Performs the heavy lifting of creating a certificate. Returns
// a fully-filled-in ParsedCertBundle.
func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
Expand Down Expand Up @@ -1027,6 +1042,9 @@ func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
// Add this before calling addKeyUsages
if data.signingBundle == nil {
certTemplate.IsCA = true
} else if data.params.BasicConstraintsValidForNonCA {
certTemplate.BasicConstraintsValid = true
certTemplate.IsCA = false
}

// This will only be filled in from the generation paths
Expand All @@ -1035,6 +1053,8 @@ func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
certTemplate.PermittedDNSDomainsCritical = true
}

addPolicyIdentifiers(data, certTemplate)

addKeyUsages(data, certTemplate)

certTemplate.IssuingCertificateURL = data.params.URLs.IssuingCertificates
Expand Down Expand Up @@ -1220,6 +1240,8 @@ func signCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()}
}

addPolicyIdentifiers(data, certTemplate)

addKeyUsages(data, certTemplate)

var certBytes []byte
Expand All @@ -1241,6 +1263,9 @@ func signCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
if certTemplate.MaxPathLen == 0 {
certTemplate.MaxPathLenZero = true
}
} else if data.params.BasicConstraintsValidForNonCA {
certTemplate.BasicConstraintsValid = true
certTemplate.IsCA = false
}

if len(data.params.PermittedDNSDomains) > 0 {
Expand Down
210 changes: 112 additions & 98 deletions builtin/logical/pki/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ for "generate_lease".`,
Default: true,
Description: `If set to false, makes the 'common_name' field optional while generating a certificate.`,
},
"policy_identifiers": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated string or list of policy oids.`,
},
"basic_constraints_valid_for_non_ca": &framework.FieldSchema{
Type: framework.TypeBool,
Description: `Mark Basic Constraints valid when issuing non-CA certificates.`,
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
Expand Down Expand Up @@ -419,35 +427,37 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
name := data.Get("name").(string)

entry := &roleEntry{
MaxTTL: (time.Duration(data.Get("max_ttl").(int)) * time.Second).String(),
TTL: (time.Duration(data.Get("ttl").(int)) * time.Second).String(),
AllowLocalhost: data.Get("allow_localhost").(bool),
AllowedDomains: data.Get("allowed_domains").([]string),
AllowBareDomains: data.Get("allow_bare_domains").(bool),
AllowSubdomains: data.Get("allow_subdomains").(bool),
AllowGlobDomains: data.Get("allow_glob_domains").(bool),
AllowAnyName: data.Get("allow_any_name").(bool),
EnforceHostnames: data.Get("enforce_hostnames").(bool),
AllowIPSANs: data.Get("allow_ip_sans").(bool),
ServerFlag: data.Get("server_flag").(bool),
ClientFlag: data.Get("client_flag").(bool),
CodeSigningFlag: data.Get("code_signing_flag").(bool),
EmailProtectionFlag: data.Get("email_protection_flag").(bool),
KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int),
UseCSRCommonName: data.Get("use_csr_common_name").(bool),
UseCSRSANs: data.Get("use_csr_sans").(bool),
KeyUsage: data.Get("key_usage").([]string),
OU: data.Get("ou").([]string),
Organization: data.Get("organization").([]string),
Country: data.Get("country").([]string),
Locality: data.Get("locality").([]string),
Province: data.Get("province").([]string),
StreetAddress: data.Get("street_address").([]string),
PostalCode: data.Get("postal_code").([]string),
GenerateLease: new(bool),
NoStore: data.Get("no_store").(bool),
RequireCN: data.Get("require_cn").(bool),
MaxTTL: (time.Duration(data.Get("max_ttl").(int)) * time.Second).String(),
TTL: (time.Duration(data.Get("ttl").(int)) * time.Second).String(),
AllowLocalhost: data.Get("allow_localhost").(bool),
AllowedDomains: data.Get("allowed_domains").([]string),
AllowBareDomains: data.Get("allow_bare_domains").(bool),
AllowSubdomains: data.Get("allow_subdomains").(bool),
AllowGlobDomains: data.Get("allow_glob_domains").(bool),
AllowAnyName: data.Get("allow_any_name").(bool),
EnforceHostnames: data.Get("enforce_hostnames").(bool),
AllowIPSANs: data.Get("allow_ip_sans").(bool),
ServerFlag: data.Get("server_flag").(bool),
ClientFlag: data.Get("client_flag").(bool),
CodeSigningFlag: data.Get("code_signing_flag").(bool),
EmailProtectionFlag: data.Get("email_protection_flag").(bool),
KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int),
UseCSRCommonName: data.Get("use_csr_common_name").(bool),
UseCSRSANs: data.Get("use_csr_sans").(bool),
KeyUsage: data.Get("key_usage").([]string),
OU: data.Get("ou").([]string),
Organization: data.Get("organization").([]string),
Country: data.Get("country").([]string),
Locality: data.Get("locality").([]string),
Province: data.Get("province").([]string),
StreetAddress: data.Get("street_address").([]string),
PostalCode: data.Get("postal_code").([]string),
GenerateLease: new(bool),
NoStore: data.Get("no_store").(bool),
RequireCN: data.Get("require_cn").(bool),
PolicyIdentifiers: data.Get("policy_identifiers").([]string),
BasicConstraintsValidForNonCA: data.Get("basic_constraints_valid_for_non_ca").(bool),
}

otherSANs := data.Get("allowed_other_sans").([]string)
Expand Down Expand Up @@ -531,82 +541,86 @@ func parseKeyUsages(input []string) int {
}

type roleEntry struct {
LeaseMax string `json:"lease_max"`
Lease string `json:"lease"`
MaxTTL string `json:"max_ttl" mapstructure:"max_ttl"`
TTL string `json:"ttl" mapstructure:"ttl"`
AllowLocalhost bool `json:"allow_localhost" mapstructure:"allow_localhost"`
AllowedBaseDomain string `json:"allowed_base_domain" mapstructure:"allowed_base_domain"`
AllowedDomainsOld string `json:"allowed_domains,omit_empty"`
AllowedDomains []string `json:"allowed_domains_list" mapstructure:"allowed_domains"`
AllowBaseDomain bool `json:"allow_base_domain"`
AllowBareDomains bool `json:"allow_bare_domains" mapstructure:"allow_bare_domains"`
AllowTokenDisplayName bool `json:"allow_token_displayname" mapstructure:"allow_token_displayname"`
AllowSubdomains bool `json:"allow_subdomains" mapstructure:"allow_subdomains"`
AllowGlobDomains bool `json:"allow_glob_domains" mapstructure:"allow_glob_domains"`
AllowAnyName bool `json:"allow_any_name" mapstructure:"allow_any_name"`
EnforceHostnames bool `json:"enforce_hostnames" mapstructure:"enforce_hostnames"`
AllowIPSANs bool `json:"allow_ip_sans" mapstructure:"allow_ip_sans"`
ServerFlag bool `json:"server_flag" mapstructure:"server_flag"`
ClientFlag bool `json:"client_flag" mapstructure:"client_flag"`
CodeSigningFlag bool `json:"code_signing_flag" mapstructure:"code_signing_flag"`
EmailProtectionFlag bool `json:"email_protection_flag" mapstructure:"email_protection_flag"`
UseCSRCommonName bool `json:"use_csr_common_name" mapstructure:"use_csr_common_name"`
UseCSRSANs bool `json:"use_csr_sans" mapstructure:"use_csr_sans"`
KeyType string `json:"key_type" mapstructure:"key_type"`
KeyBits int `json:"key_bits" mapstructure:"key_bits"`
MaxPathLength *int `json:",omitempty" mapstructure:"max_path_length"`
KeyUsageOld string `json:"key_usage,omitempty"`
KeyUsage []string `json:"key_usage_list" mapstructure:"key_usage"`
OUOld string `json:"ou,omitempty"`
OU []string `json:"ou_list" mapstructure:"ou"`
OrganizationOld string `json:"organization,omitempty"`
Organization []string `json:"organization_list" mapstructure:"organization"`
Country []string `json:"country" mapstructure:"country"`
Locality []string `json:"locality" mapstructure:"locality"`
Province []string `json:"province" mapstructure:"province"`
StreetAddress []string `json:"street_address" mapstructure:"street_address"`
PostalCode []string `json:"postal_code" mapstructure:"postal_code"`
GenerateLease *bool `json:"generate_lease,omitempty"`
NoStore bool `json:"no_store" mapstructure:"no_store"`
RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
LeaseMax string `json:"lease_max"`
Lease string `json:"lease"`
MaxTTL string `json:"max_ttl" mapstructure:"max_ttl"`
TTL string `json:"ttl" mapstructure:"ttl"`
AllowLocalhost bool `json:"allow_localhost" mapstructure:"allow_localhost"`
AllowedBaseDomain string `json:"allowed_base_domain" mapstructure:"allowed_base_domain"`
AllowedDomainsOld string `json:"allowed_domains,omit_empty"`
AllowedDomains []string `json:"allowed_domains_list" mapstructure:"allowed_domains"`
AllowBaseDomain bool `json:"allow_base_domain"`
AllowBareDomains bool `json:"allow_bare_domains" mapstructure:"allow_bare_domains"`
AllowTokenDisplayName bool `json:"allow_token_displayname" mapstructure:"allow_token_displayname"`
AllowSubdomains bool `json:"allow_subdomains" mapstructure:"allow_subdomains"`
AllowGlobDomains bool `json:"allow_glob_domains" mapstructure:"allow_glob_domains"`
AllowAnyName bool `json:"allow_any_name" mapstructure:"allow_any_name"`
EnforceHostnames bool `json:"enforce_hostnames" mapstructure:"enforce_hostnames"`
AllowIPSANs bool `json:"allow_ip_sans" mapstructure:"allow_ip_sans"`
ServerFlag bool `json:"server_flag" mapstructure:"server_flag"`
ClientFlag bool `json:"client_flag" mapstructure:"client_flag"`
CodeSigningFlag bool `json:"code_signing_flag" mapstructure:"code_signing_flag"`
EmailProtectionFlag bool `json:"email_protection_flag" mapstructure:"email_protection_flag"`
UseCSRCommonName bool `json:"use_csr_common_name" mapstructure:"use_csr_common_name"`
UseCSRSANs bool `json:"use_csr_sans" mapstructure:"use_csr_sans"`
KeyType string `json:"key_type" mapstructure:"key_type"`
KeyBits int `json:"key_bits" mapstructure:"key_bits"`
MaxPathLength *int `json:",omitempty" mapstructure:"max_path_length"`
KeyUsageOld string `json:"key_usage,omitempty"`
KeyUsage []string `json:"key_usage_list" mapstructure:"key_usage"`
OUOld string `json:"ou,omitempty"`
OU []string `json:"ou_list" mapstructure:"ou"`
OrganizationOld string `json:"organization,omitempty"`
Organization []string `json:"organization_list" mapstructure:"organization"`
Country []string `json:"country" mapstructure:"country"`
Locality []string `json:"locality" mapstructure:"locality"`
Province []string `json:"province" mapstructure:"province"`
StreetAddress []string `json:"street_address" mapstructure:"street_address"`
PostalCode []string `json:"postal_code" mapstructure:"postal_code"`
GenerateLease *bool `json:"generate_lease,omitempty"`
NoStore bool `json:"no_store" mapstructure:"no_store"`
RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
PolicyIdentifiers []string `json:"policy_identifiers" mapstructure:"policy_identifiers"`
BasicConstraintsValidForNonCA bool `json:"basic_constraints_valid_for_non_ca" mapstructure:"basic_constraints_valid_for_non_ca"`

// Used internally for signing intermediates
AllowExpirationPastCA bool
}

func (r *roleEntry) ToResponseData() map[string]interface{} {
responseData := map[string]interface{}{
"ttl": r.TTL,
"max_ttl": r.MaxTTL,
"allow_localhost": r.AllowLocalhost,
"allowed_domains": r.AllowedDomains,
"allow_bare_domains": r.AllowBareDomains,
"allow_token_displayname": r.AllowTokenDisplayName,
"allow_subdomains": r.AllowSubdomains,
"allow_glob_domains": r.AllowGlobDomains,
"allow_any_name": r.AllowAnyName,
"enforce_hostnames": r.EnforceHostnames,
"allow_ip_sans": r.AllowIPSANs,
"server_flag": r.ServerFlag,
"client_flag": r.ClientFlag,
"code_signing_flag": r.CodeSigningFlag,
"email_protection_flag": r.EmailProtectionFlag,
"use_csr_common_name": r.UseCSRCommonName,
"use_csr_sans": r.UseCSRSANs,
"key_type": r.KeyType,
"key_bits": r.KeyBits,
"key_usage": r.KeyUsage,
"ou": r.OU,
"organization": r.Organization,
"country": r.Country,
"locality": r.Locality,
"province": r.Province,
"street_address": r.StreetAddress,
"postal_code": r.PostalCode,
"no_store": r.NoStore,
"allowed_other_sans": r.AllowedOtherSANs,
"ttl": r.TTL,
"max_ttl": r.MaxTTL,
"allow_localhost": r.AllowLocalhost,
"allowed_domains": r.AllowedDomains,
"allow_bare_domains": r.AllowBareDomains,
"allow_token_displayname": r.AllowTokenDisplayName,
"allow_subdomains": r.AllowSubdomains,
"allow_glob_domains": r.AllowGlobDomains,
"allow_any_name": r.AllowAnyName,
"enforce_hostnames": r.EnforceHostnames,
"allow_ip_sans": r.AllowIPSANs,
"server_flag": r.ServerFlag,
"client_flag": r.ClientFlag,
"code_signing_flag": r.CodeSigningFlag,
"email_protection_flag": r.EmailProtectionFlag,
"use_csr_common_name": r.UseCSRCommonName,
"use_csr_sans": r.UseCSRSANs,
"key_type": r.KeyType,
"key_bits": r.KeyBits,
"key_usage": r.KeyUsage,
"ou": r.OU,
"organization": r.Organization,
"country": r.Country,
"locality": r.Locality,
"province": r.Province,
"street_address": r.StreetAddress,
"postal_code": r.PostalCode,
"no_store": r.NoStore,
"allowed_other_sans": r.AllowedOtherSANs,
"policy_identifiers": r.PolicyIdentifiers,
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
}
if r.MaxPathLength != nil {
responseData["max_path_length"] = r.MaxPathLength
Expand Down