Add ability to configure the NotBefore property of certificates in role api (#5325)

* Add ability to configure the NotBefore property of certificates in role api

* Update index.html.md

* converting field to time.Duration

* setting default back to 30s

* renaming the parameter not_before_duration to differentiate between the NotBefore datetime on the cert

* Update description
This commit is contained in:
sk4ry 2018-10-02 17:10:43 +02:00 committed by Chris Hoffman
parent a105664141
commit 0fab335eec
6 changed files with 65 additions and 18 deletions

View file

@ -424,14 +424,6 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUs
}
}
// 40 seconds since we add 30 second slack for clock skew
if math.Abs(float64(time.Now().Unix()-cert.NotBefore.Unix())) > 40 {
return nil, fmt.Errorf("validity period starts out of range")
}
if !cert.NotBefore.Before(time.Now().Add(-10 * time.Second)) {
return nil, fmt.Errorf("validity period not far enough in the past")
}
if math.Abs(float64(time.Now().Add(validity).Unix()-cert.NotAfter.Unix())) > 20 {
return nil, fmt.Errorf("certificate validity end: %s; expected within 20 seconds of %s", cert.NotAfter.Format(time.RFC3339), time.Now().Add(validity).Format(time.RFC3339))
}
@ -976,6 +968,29 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
}
}
getNotBeforeCheck := func(role roleEntry) logicaltest.TestCheckFunc {
var certBundle certutil.CertBundle
return func(resp *logical.Response) error {
err := mapstructure.Decode(resp.Data, &certBundle)
if err != nil {
return err
}
parsedCertBundle, err := certBundle.ToParsedCertBundle()
if err != nil {
return fmt.Errorf("error checking generated certificate: %s", err)
}
cert := parsedCertBundle.Certificate
actualDiff := time.Now().Sub(cert.NotBefore)
certRoleDiff := role.NotBeforeDuration - actualDiff
// These times get truncated, so give a 1 second buffer on each side
if certRoleDiff > -1*time.Second && certRoleDiff < 1*time.Second {
return nil
}
return fmt.Errorf("validity period out of range diff: %v", certRoleDiff)
}
}
// Returns a TestCheckFunc that performs various validity checks on the
// returned certificate information, mostly within checkCertsAndPrivateKey
getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration) logicaltest.TestCheckFunc {
@ -1324,6 +1339,16 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
roleVals.PostalCode = []string{"f00", "b4r"}
addTests(getPostalCodeCheck(roleVals))
}
// NotBefore tests
{
roleVals.NotBeforeDuration = 10 * time.Second
addTests(getNotBeforeCheck(roleVals))
roleVals.NotBeforeDuration = 30 * time.Second
addTests(getNotBeforeCheck(roleVals))
roleVals.NotBeforeDuration = 0
}
// IP SAN tests
{
roleVals.UseCSRSANs = true
@ -1961,8 +1986,8 @@ func TestBackend_SignSelfIssued(t *testing.T) {
Subject: pkix.Name{
CommonName: "foo.bar.com",
},
SerialNumber: big.NewInt(1234),
IsCA: false,
SerialNumber: big.NewInt(1234),
IsCA: false,
BasicConstraintsValid: true,
}
@ -1992,8 +2017,8 @@ func TestBackend_SignSelfIssued(t *testing.T) {
Subject: pkix.Name{
CommonName: "bar.foo.com",
},
SerialNumber: big.NewInt(2345),
IsCA: true,
SerialNumber: big.NewInt(2345),
IsCA: true,
BasicConstraintsValid: true,
}
ss, ssCert := getSelfSigned(template, issuer)
@ -2573,10 +2598,9 @@ func setCerts() {
DNSNames: []string{"root.localhost"},
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
IsCA: true,
}
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, cak.Public(), cak)
if err != nil {

View file

@ -79,7 +79,6 @@ func TestBackend_CA_Steps(t *testing.T) {
DNSNames: []string{"root.localhost"},
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,

View file

@ -87,6 +87,9 @@ type creationParameters struct {
// The maximum path length to encode
MaxPathLength int
// The duration the certificate will use NotBefore
NotBeforeDuration time.Duration
}
type caInfoBundle struct {
@ -1019,6 +1022,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error {
ExtKeyUsageOIDs: data.role.ExtKeyUsageOIDs,
PolicyIdentifiers: data.role.PolicyIdentifiers,
BasicConstraintsValidForNonCA: data.role.BasicConstraintsValidForNonCA,
NotBeforeDuration: data.role.NotBeforeDuration,
}
// Don't deal with URLs or max path length if it's self-signed, as these
@ -1165,7 +1169,6 @@ func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
certTemplate := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: data.params.NotAfter,
IsCA: false,
SubjectKeyId: subjKeyID,
@ -1175,6 +1178,9 @@ func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
IPAddresses: data.params.IPAddresses,
URIs: data.params.URIs,
}
if data.params.NotBeforeDuration > 0 {
certTemplate.NotBefore = time.Now().Add(-1 * data.params.NotBeforeDuration)
}
if err := handleOtherSANs(certTemplate, data.params.OtherSANs); err != nil {
return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()}
@ -1365,11 +1371,13 @@ func signCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
certTemplate := &x509.Certificate{
SerialNumber: serialNumber,
Subject: data.params.Subject,
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: data.params.NotAfter,
SubjectKeyId: subjKeyID[:],
AuthorityKeyId: caCert.SubjectKeyId,
}
if data.params.NotBeforeDuration > 0 {
certTemplate.NotBefore = time.Now().Add(-1 * data.params.NotBeforeDuration)
}
switch data.signingBundle.PrivateKeyType {
case certutil.RSAPrivateKey:

View file

@ -286,6 +286,11 @@ for "generate_lease".`,
Type: framework.TypeBool,
Description: `Mark Basic Constraints valid when issuing non-CA certificates.`,
},
"not_before_duration": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Default: 30,
Description: `The duration before now the cert needs to be created / signed.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@ -493,6 +498,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
AllowedSerialNumbers: data.Get("allowed_serial_numbers").([]string),
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,
}
otherSANs := data.Get("allowed_other_sans").([]string)
@ -683,6 +689,7 @@ type roleEntry struct {
PolicyIdentifiers []string `json:"policy_identifiers" mapstructure:"policy_identifiers"`
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"`
// Used internally for signing intermediates
AllowExpirationPastCA bool
@ -726,6 +733,7 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
"require_cn": r.RequireCN,
"policy_identifiers": r.PolicyIdentifiers,
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
"not_before_duration": int64(r.NotBeforeDuration.Seconds()),
}
if r.MaxPathLength != nil {
responseData["max_path_length"] = r.MaxPathLength

View file

@ -104,6 +104,11 @@ export default DS.Model.extend({
basicConstraintsValidForNonCA: attr('boolean', {
label: 'Mark Basic Constraints valid when issuing non-CA certificates.',
}),
notBeforeDuration: attr({
label: 'Not Before Duration',
editType: 'ttl',
defaultValue: '30s',
}),
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
canDelete: alias('updatePath.canDelete'),
@ -137,6 +142,7 @@ export default DS.Model.extend({
'organization',
'keyUsage',
'allowedOtherSans',
'notBeforeDuration',
],
},
{

View file

@ -883,11 +883,13 @@ request is denied.
optional while generating a certificate.
- `policy_identifiers` `(list: [])` A comma-separated string or list of policy
oids.
OIDs.
- `basic_constraints_valid_for_non_ca` `(bool: false)` - Mark Basic Constraints
valid when issuing non-CA certificates.
- `not_before_duration` `(duration: "30s")`  Specifies the duration by which to backdate the NotBefore property.
### Sample Payload