Skip to content

Commit

Permalink
Add issuer usage restrictions
Browse files Browse the repository at this point in the history
This allows issuers to have usage restrictions, limiting whether they
can be used to issue certificates or if they can generate CRLs. This
allows certain issuers to not generate a CRL (if the global config is
with the CRL enabled) or allows the issuer to not issue new certificates
(but potentially letting the CRL generation continue).

Setting both fields to false effectively forms a soft delete capability.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
  • Loading branch information
cipherboy committed May 2, 2022
1 parent 75632ef commit 19faacd
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 7 deletions.
2 changes: 1 addition & 1 deletion builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2660,7 +2660,7 @@ func TestBackend_SignSelfIssued(t *testing.T) {
t.Fatal(err)
}

signingBundle, err := fetchCAInfo(context.Background(), b, &logical.Request{Storage: storage}, defaultRef)
signingBundle, err := fetchCAInfo(context.Background(), b, &logical.Request{Storage: storage}, defaultRef, false /* issue */, false /* crl */)
if err != nil {
t.Fatal(err)
}
Expand Down
10 changes: 9 additions & 1 deletion builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func getFormat(data *framework.FieldData) string {
}

// fetchCAInfo will fetch the CA info, will return an error if no ca info exists.
func fetchCAInfo(ctx context.Context, b *backend, req *logical.Request, issuerRef string) (*certutil.CAInfoBundle, error) {
func fetchCAInfo(ctx context.Context, b *backend, req *logical.Request, issuerRef string, issuance bool, crl bool) (*certutil.CAInfoBundle, error) {
entry, bundle, err := fetchCertBundle(ctx, b, req.Storage, issuerRef)
if err != nil {
switch err.(type) {
Expand All @@ -93,6 +93,14 @@ func fetchCAInfo(ctx context.Context, b *backend, req *logical.Request, issuerRe
}
}

if issuance && !entry.UseForIssuance {
return nil, errutil.InternalError{Err: fmt.Sprintf("issuer %v is not configured for issuing certificates", issuerRef)}
}

if crl && !entry.UseForCRLSigning {
return nil, errutil.InternalError{Err: fmt.Sprintf("issuer %v is not configured for signing CRLs", issuerRef)}
}

if bundle == nil {
return nil, errutil.UserError{Err: "no CA information is present"}
}
Expand Down
13 changes: 12 additions & 1 deletion builtin/logical/pki/crl_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial st
return nil, nil
}

signingBundle, caErr := fetchCAInfo(ctx, b, req, defaultRef)
signingBundle, caErr := fetchCAInfo(ctx, b, req, defaultRef, false /* issue */, false /* crl */)
if caErr != nil {
switch caErr.(type) {
case errutil.UserError:
Expand Down Expand Up @@ -270,6 +270,11 @@ func buildCRLs(ctx context.Context, b *backend, req *logical.Request, forceNew b
continue
}

// Skip entries which aren't enabled for CRL signing.
if !thisEntry.UseForCRLSigning {
continue
}

issuerIDEntryMap[issuer] = thisEntry

thisCert, err := thisEntry.GetCertificate()
Expand Down Expand Up @@ -365,6 +370,12 @@ func buildCRLs(ctx context.Context, b *backend, req *logical.Request, forceNew b
// remove them, remembering their CRLs IDs. If we've completely removed
// all issuers pointing to that CRL number, we can remove it from the
// number map and from storage.
//
// Note that we persist the last generated CRL for a specified issuer
// if it is later disabled for CRL generation. This mirrors the old
// root deletion behavior, but using soft issuer deletes. If there is an
// alternate, equivalent issuer however, we'll keep updating the shared
// CRL; all equivalent issuers must have their CRLs disabled.
for mapIssuerId := range crlConfig.IssuerIDCRLMap {
stillHaveIssuer := false
for _, listedIssuerId := range issuers {
Expand Down
2 changes: 1 addition & 1 deletion builtin/logical/pki/path_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (b *backend) pathFetchRead(ctx context.Context, req *logical.Request, data

// Prefer fetchCAInfo to fetchCertBySerial for CA certificates.
if serial == "ca_chain" || serial == "ca" {
caInfo, err := fetchCAInfo(ctx, b, req, defaultRef)
caInfo, err := fetchCAInfo(ctx, b, req, defaultRef, false /* issue */, false /* crl */)
if err != nil {
switch err.(type) {
case errutil.UserError:
Expand Down
38 changes: 38 additions & 0 deletions builtin/logical/pki/path_fetch_issuers.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ the entire validity period. It is suggested to use "truncate" for
intermediate CAs and "permit" only for root CAs.`,
Default: "err",
}
fields["use_for_issuance"] = &framework.FieldSchema{
Type: framework.TypeBool,
Description: `Whether or not to use this issuer for signing
CSRs and issuing new certificates. Disabling allows the key to persist
for e.g., CRL generation without allowing new certificates to be signed.
Setting both use_for_issuance and use_for_crl_signing to false is effectively
a soft delete.`,
Default: true,
}
fields["use_for_crl_signing"] = &framework.FieldSchema{
Type: framework.TypeBool,
Description: `Whether or not to use this issuer for signing
CRLs. Disabling allows for per-issuer overriding of CRL generation
properties. Setting both use_for_issuance and use_for_crl_signing to false
is effectively a soft delete.`,
Default: true,
}

return &framework.Path{
// Returns a JSON entry.
Expand Down Expand Up @@ -162,6 +179,8 @@ func (b *backend) pathGetIssuer(ctx context.Context, req *logical.Request, data
"manual_chain": respManualChain,
"ca_chain": issuer.CAChain,
"leaf_not_after_behavior": issuer.LeafNotAfterBehavior,
"use_for_issuance": issuer.UseForIssuance,
"use_for_crl_signing": issuer.UseForCRLSigning,
},
}, nil
}
Expand Down Expand Up @@ -202,6 +221,10 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
// errs should still be surfaced, however.
return logical.ErrorResponse(err.Error()), nil
}
if err == errIssuerNameInUse && issuer.Name != newName {
// When the new name is in use but isn't this name, throw an error.
return logical.ErrorResponse(err.Error()), nil
}

newPath := data.Get("manual_chain").([]string)
rawLeafBehavior := data.Get("leaf_not_after_behavior").(string)
Expand All @@ -217,6 +240,9 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
return logical.ErrorResponse("Unknown value for field `leaf_not_after_behavior`. Possible values are `err`, `truncate`, and `permit`."), nil
}

newForIssuance := data.Get("use_for_issuance").(bool)
newForCRLs := data.Get("use_for_crl_signing").(bool)

modified := false

if newName != issuer.Name {
Expand All @@ -229,6 +255,16 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
modified = true
}

if newForIssuance != issuer.UseForIssuance {
issuer.UseForIssuance = newForIssuance
modified = true
}

if newForCRLs != issuer.UseForCRLSigning {
issuer.UseForCRLSigning = newForCRLs
modified = true
}

var updateChain bool
var constructedChain []issuerID
for index, newPathRef := range newPath {
Expand Down Expand Up @@ -289,6 +325,8 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
"manual_chain": respManualChain,
"ca_chain": issuer.CAChain,
"leaf_not_after_behavior": issuer.LeafNotAfterBehavior,
"use_for_issuance": issuer.UseForIssuance,
"use_for_crl_signing": issuer.UseForCRLSigning,
},
}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion builtin/logical/pki/path_issue_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d
}

var caErr error
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName)
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName, true /* issue */, false /* crl */)
if caErr != nil {
switch caErr.(type) {
case errutil.UserError:
Expand Down
4 changes: 2 additions & 2 deletions builtin/logical/pki/path_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R
}

var caErr error
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName)
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName, true /* issue */, false /* crl */)
if caErr != nil {
switch caErr.(type) {
case errutil.UserError:
Expand Down Expand Up @@ -417,7 +417,7 @@ func (b *backend) pathIssuerSignSelfIssued(ctx context.Context, req *logical.Req
}

var caErr error
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName)
signingBundle, caErr := fetchCAInfo(ctx, b, req, issuerName, true /* issue */, false /* crl */)
if caErr != nil {
switch caErr.(type) {
case errutil.UserError:
Expand Down
4 changes: 4 additions & 0 deletions builtin/logical/pki/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type issuerEntry struct {
ManualChain []issuerID `json:"manual_chain" structs:"manual_chain" mapstructure:"manual_chain"`
SerialNumber string `json:"serial_number" structs:"serial_number" mapstructure:"serial_number"`
LeafNotAfterBehavior certutil.NotAfterBehavior `json:"not_after_behavior" structs:"not_after_behavior" mapstructure:"not_after_behavior"`
UseForIssuance bool `json:"use_for_issuance" structs:"use_for_issuance" mapstructure:"use_for_issuance"`
UseForCRLSigning bool `json:"use_for_crl_signing" structs:"use_for_crl_signing" mapstructure:"use_for_crl_signing"`
}

type localCRLConfigEntry struct {
Expand Down Expand Up @@ -445,6 +447,8 @@ func importIssuer(ctx context.Context, s logical.Storage, certValue string, issu
result.Name = issuerName
result.Certificate = certValue
result.LeafNotAfterBehavior = certutil.ErrNotAfterBehavior
result.UseForIssuance = true
result.UseForCRLSigning = true

// We shouldn't add CSRs or multiple certificates in this
countCertificates := strings.Count(result.Certificate, "-BEGIN ")
Expand Down
4 changes: 4 additions & 0 deletions builtin/logical/pki/storage_migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@ func getLegacyCertBundle(ctx context.Context, s logical.Storage) (*issuerEntry,
// Fake a storage entry with backwards compatibility in mind. We only need
// the fields in the CAInfoBundle; everything else doesn't matter.
issuer := &issuerEntry{
ID: "legacy-entry-shim",
Name: "legacy-entry-shim",
LeafNotAfterBehavior: certutil.ErrNotAfterBehavior,
UseForIssuance: true,
UseForCRLSigning: true,
}

return issuer, cb, nil
Expand Down

0 comments on commit 19faacd

Please sign in to comment.