Skip to content

Commit

Permalink
Added notAfter and support Y10K expiry for IEEE 802.1AR-2018
Browse files Browse the repository at this point in the history
  • Loading branch information
skhilar committed Nov 5, 2021
1 parent 818502b commit 935e09b
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 16 deletions.
137 changes: 133 additions & 4 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,135 @@ func TestPKI_RequireCN(t *testing.T) {
}
}

func TestPKI_DeviceCert(t *testing.T) {
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

client := cluster.Cores[0].Client
var err error
err = client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
if err != nil {
t.Fatal(err)
}

resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"common_name": "myvault.com",
"not_after": "9999-12-31T23:59:59Z",
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected ca info")
}
var certBundle certutil.CertBundle
err = mapstructure.Decode(resp.Data, &certBundle)
if err != nil {
t.Fatal(err)
}

parsedCertBundle, err := certBundle.ToParsedCertBundle()
if err != nil {
t.Fatal(err)
}
cert := parsedCertBundle.Certificate
notAfter := cert.NotAfter.Format(time.RFC3339)
if notAfter != "9999-12-31T23:59:59Z" {
t.Fatal(fmt.Errorf("not after from certificate is not matching with input parameter"))
}

// Create a role which does require CN (default)
_, err = client.Logical().Write("pki/roles/example", map[string]interface{}{
"allowed_domains": "foobar.com,zipzap.com,abc.com,xyz.com",
"allow_bare_domains": true,
"allow_subdomains": true,
"not_after": "9999-12-31T23:59:59Z",
})
if err != nil {
t.Fatal(err)
}

// Issue a cert with require_cn set to true and with common name supplied.
// It should succeed.
resp, err = client.Logical().Write("pki/issue/example", map[string]interface{}{
"common_name": "foobar.com",
})
if err != nil {
t.Fatal(err)
}
err = mapstructure.Decode(resp.Data, &certBundle)
if err != nil {
t.Fatal(err)
}

parsedCertBundle, err = certBundle.ToParsedCertBundle()
if err != nil {
t.Fatal(err)
}
cert = parsedCertBundle.Certificate
notAfter = cert.NotAfter.Format(time.RFC3339)
if notAfter != "9999-12-31T23:59:59Z" {
t.Fatal(fmt.Errorf("not after from certificate is not matching with input parameter"))
}

}

func TestBackend_InvalidParameter(t *testing.T) {
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

client := cluster.Cores[0].Client
var err error
err = client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
if err != nil {
t.Fatal(err)
}

_, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"common_name": "myvault.com",
"not_after": "9999-12-31T23:59:59Z",
"ttl": "25h",
})
if err == nil {
t.Fatal(err)
}

_, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"common_name": "myvault.com",
"not_after": "9999-12-31T23:59:59",
})
if err == nil {
t.Fatal(err)
}
}
func TestBackend_CSRValues(t *testing.T) {
initTest.Do(setCerts)
defaultLeaseTTLVal := time.Hour * 24
Expand Down Expand Up @@ -691,10 +820,10 @@ func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s
// Generates steps to test out various role permutations
func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
roleVals := roleEntry{
MaxTTL: 12 * time.Hour,
KeyType: "rsa",
KeyBits: 2048,
RequireCN: true,
MaxTTL: 12 * time.Hour,
KeyType: "rsa",
KeyBits: 2048,
RequireCN: true,
SignatureBits: 256,
}
issueVals := certutil.IssueData{}
Expand Down
29 changes: 25 additions & 4 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ func validateNames(b *backend, data *inputBundle, names []string) string {
if data.role.AllowBareDomains &&
(strings.EqualFold(sanitizedName, currDomain) ||
(isEmail && strings.EqualFold(emailDomain, currDomain)) ||
// Handle the use case of AllowedDomain being an email address
(isEmail && strings.EqualFold(name, currDomain))) {
// Handle the use case of AllowedDomain being an email address
(isEmail && strings.EqualFold(name, currDomain))) {
valid = true
break
}
Expand Down Expand Up @@ -1034,8 +1034,23 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
var ttl time.Duration
var maxTTL time.Duration
var notAfter time.Time
var err error
{
ttl = time.Duration(data.apiData.Get("ttl").(int)) * time.Second
notAfterAlt := data.role.NotAfter
if notAfterAlt == "" {
notAfterAltRaw, ok := data.apiData.GetOk("not_after")
if ok {
notAfterAlt = notAfterAltRaw.(string)
}

}
if ttl > 0 && notAfterAlt != "" {
return nil, errutil.UserError{
Err: fmt.Sprintf(
"Either ttl or not_after should be provided. Both should not be provided in the same request."),
}
}

if ttl == 0 && data.role.TTL > 0 {
ttl = data.role.TTL
Expand All @@ -1055,8 +1070,14 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
ttl = maxTTL
}

notAfter = time.Now().Add(ttl)

if notAfterAlt != "" {
notAfter, err = time.Parse(time.RFC3339, notAfterAlt)
if err != nil {
return nil, errutil.UserError{Err: err.Error()}
}
} else {
notAfter = time.Now().Add(ttl)
}
// If it's not self-signed, verify that the issued certificate won't be
// valid past the lifetime of the CA certificate
if caSign != nil &&
Expand Down
12 changes: 6 additions & 6 deletions builtin/logical/pki/cert_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ func TestPki_PermitFQDNs(t *testing.T) {
fields := addCACommonFields(map[string]*framework.FieldSchema{})

cases := map[string]struct {
input *inputBundle
input *inputBundle
expectedDnsNames []string
expectedEmails []string
expectedEmails []string
}{
"base valid case": {
input: &inputBundle{
Expand All @@ -183,7 +183,7 @@ func TestPki_PermitFQDNs(t *testing.T) {
},
},
expectedDnsNames: []string{"example.com."},
expectedEmails: []string{},
expectedEmails: []string{},
},
"case insensitivity validation": {
input: &inputBundle{
Expand All @@ -202,7 +202,7 @@ func TestPki_PermitFQDNs(t *testing.T) {
},
},
expectedDnsNames: []string{"Example.Net", "eXaMPLe.COM"},
expectedEmails: []string{},
expectedEmails: []string{},
},
"case email as AllowedDomain with bare domains": {
input: &inputBundle{
Expand All @@ -220,7 +220,7 @@ func TestPki_PermitFQDNs(t *testing.T) {
},
},
expectedDnsNames: []string{},
expectedEmails: []string{"test@testemail.com"},
expectedEmails: []string{"test@testemail.com"},
},
"case email common name with bare domains": {
input: &inputBundle{
Expand All @@ -238,7 +238,7 @@ func TestPki_PermitFQDNs(t *testing.T) {
},
},
expectedDnsNames: []string{},
expectedEmails: []string{"test@testemail.com"},
expectedEmails: []string{"test@testemail.com"},
},
}

Expand Down
7 changes: 6 additions & 1 deletion builtin/logical/pki/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ this value.`,
more than one, specify alternative names in
the alt_names map using OID 2.5.4.5.`,
}
fields["not_after"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Set the not after field of the certificate with specified date value.
The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ`,
}

return fields
}
Expand Down Expand Up @@ -255,7 +260,7 @@ the key_type.`,
}

fields["signature_bits"] = &framework.FieldSchema{
Type: framework.TypeInt,
Type: framework.TypeInt,
Default: 256,
Description: `The number of bits to use in the signature
algorithm. Defaults to 256 for SHA256.
Expand Down
9 changes: 8 additions & 1 deletion builtin/logical/pki/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ for "generate_lease".`,
Value: 30,
},
},
"not_after": {
Type: framework.TypeString,
Description: `Set the not after field of the certificate with specified date value.
The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ`,
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
Expand Down Expand Up @@ -587,6 +592,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
PolicyIdentifiers: data.Get("policy_identifiers").([]string),
BasicConstraintsValidForNonCA: data.Get("basic_constraints_valid_for_non_ca").(bool),
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
NotAfter: data.Get("not_after").(string),
}

allowedOtherSANs := data.Get("allowed_other_sans").([]string)
Expand Down Expand Up @@ -787,7 +793,7 @@ type roleEntry struct {
ExtKeyUsageOIDs []string `json:"ext_key_usage_oids" mapstructure:"ext_key_usage_oids"`
BasicConstraintsValidForNonCA bool `json:"basic_constraints_valid_for_non_ca" mapstructure:"basic_constraints_valid_for_non_ca"`
NotBeforeDuration time.Duration `json:"not_before_duration" mapstructure:"not_before_duration"`

NotAfter string `json:"not_after" mapstructure:"not_after"`
// Used internally for signing intermediates
AllowExpirationPastCA bool
}
Expand Down Expand Up @@ -833,6 +839,7 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
"policy_identifiers": r.PolicyIdentifiers,
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
"not_before_duration": int64(r.NotBeforeDuration.Seconds()),
"not_after": r.NotAfter,
}
if r.MaxPathLength != nil {
responseData["max_path_length"] = r.MaxPathLength
Expand Down
1 change: 1 addition & 0 deletions builtin/logical/pki/path_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque
AllowedURISANs: []string{"*"},
AllowedSerialNumbers: []string{"*"},
AllowExpirationPastCA: true,
NotAfter: data.Get("not_after").(string),
}

if cn := data.Get("common_name").(string); len(cn) == 0 {
Expand Down
3 changes: 3 additions & 0 deletions changelog/12795.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
core/pki: Support Y10K value in notAfter field to be compliant with IEEE 802.1AR-2018 standard
```

0 comments on commit 935e09b

Please sign in to comment.