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

Add PSS support to PKI Secrets Engine #16519

Merged
merged 12 commits into from
Aug 3, 2022
43 changes: 30 additions & 13 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3552,6 +3552,7 @@ func TestReadWriteDeleteRoles(t *testing.T) {
"allowed_serial_numbers": []interface{}{},
"generate_lease": false,
"signature_bits": json.Number("256"),
"use_pss": false,
"allowed_domains": []interface{}{},
"allowed_uri_sans_template": false,
"enforce_hostnames": true,
Expand Down Expand Up @@ -4459,6 +4460,7 @@ type KeySizeRegression struct {

// Signature Bits presently is only specified on the role.
RoleSignatureBits []int
RoleUsePSS bool

// These are tuples; must be of the same length.
TestKeyTypes []string
Expand Down Expand Up @@ -4502,6 +4504,7 @@ func RoleKeySizeRegressionHelper(t *testing.T, b *backend, s logical.Storage, in
"key_type": test.RoleKeyType,
"key_bits": roleKeyBits,
"signature_bits": roleSignatureBits,
"use_pss": test.RoleUsePSS,
})
if err != nil {
t.Fatal(err)
Expand All @@ -4527,6 +4530,15 @@ func RoleKeySizeRegressionHelper(t *testing.T, b *backend, s logical.Storage, in
t.Fatalf("key size regression test [%d] failed: haveErr: %v, expectErr: %v, err: %v, resp: %v, test case: %v, caKeyType: %v, caKeyBits: %v, role: %v, keyType: %v, keyBits: %v", index, haveErr, test.ExpectError, err, resp, test, caKeyType, caKeyBits, role, keyType, keyBits)
}

if resp != nil && test.RoleUsePSS && caKeyType == "rsa" {
leafCert := parseCert(t, resp.Data["certificate"].(string))
switch leafCert.SignatureAlgorithm {
case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS:
default:
t.Fatalf("key size regression test [%d] failed on role %v: unexpected signature algorithm; expected RSA-type CA to sign a leaf cert with PSS algorithm; got %v", index, role, leafCert.SignatureAlgorithm.String())
}
}

tested += 1
}
}
Expand All @@ -4548,36 +4560,41 @@ func TestBackend_Roles_KeySizeRegression(t *testing.T) {
testCases := []KeySizeRegression{
// RSA with default parameters should fail to issue smaller RSA keys
// and any size ECDSA/Ed25519 keys.
/* 0 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, []string{"rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{1024, 224, 256, 384, 521, 0}, true},
/* 0 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, false, []string{"rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{1024, 224, 256, 384, 521, 0}, true},
// But it should work to issue larger RSA keys.
/* 1 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, []string{"rsa", "rsa"}, []int{2048, 3072}, false},
/* 1 */ {"rsa", []int{0, 2048}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa"}, []int{2048, 3072}, false},

// EC with default parameters should fail to issue smaller EC keys
// and any size RSA/Ed25519 keys.
/* 2 */ {"ec", []int{0}, []int{0}, []string{"rsa", "ec", "ed25519"}, []int{2048, 224, 0}, true},
/* 2 */ {"ec", []int{0}, []int{0}, false, []string{"rsa", "ec", "ed25519"}, []int{2048, 224, 0}, true},
// But it should work to issue larger EC keys. Note that we should be
// independent of signature bits as that's computed from the issuer
// type (for EC based issuers).
/* 3 */ {"ec", []int{224}, []int{0, 256, 384, 521}, []string{"ec", "ec", "ec", "ec"}, []int{224, 256, 384, 521}, false},
/* 4 */ {"ec", []int{0, 256}, []int{0, 256, 384, 521}, []string{"ec", "ec", "ec"}, []int{256, 384, 521}, false},
/* 5 */ {"ec", []int{384}, []int{0, 256, 384, 521}, []string{"ec", "ec"}, []int{384, 521}, false},
/* 6 */ {"ec", []int{521}, []int{0, 256, 384, 512}, []string{"ec"}, []int{521}, false},
/* 3 */ {"ec", []int{224}, []int{0, 256, 384, 521}, false, []string{"ec", "ec", "ec", "ec"}, []int{224, 256, 384, 521}, false},
/* 4 */ {"ec", []int{0, 256}, []int{0, 256, 384, 521}, false, []string{"ec", "ec", "ec"}, []int{256, 384, 521}, false},
/* 5 */ {"ec", []int{384}, []int{0, 256, 384, 521}, false, []string{"ec", "ec"}, []int{384, 521}, false},
/* 6 */ {"ec", []int{521}, []int{0, 256, 384, 512}, false, []string{"ec"}, []int{521}, false},

// Ed25519 should reject RSA and EC keys.
/* 7 */ {"ed25519", []int{0}, []int{0}, []string{"rsa", "ec", "ec"}, []int{2048, 256, 521}, true},
/* 7 */ {"ed25519", []int{0}, []int{0}, false, []string{"rsa", "ec", "ec"}, []int{2048, 256, 521}, true},
// But it should work to issue Ed25519 keys.
/* 8 */ {"ed25519", []int{0}, []int{0}, []string{"ed25519"}, []int{0}, false},
/* 8 */ {"ed25519", []int{0}, []int{0}, false, []string{"ed25519"}, []int{0}, false},

// Any key type should reject insecure RSA key sizes.
/* 9 */ {"any", []int{0}, []int{0, 256, 384, 512}, []string{"rsa", "rsa"}, []int{512, 1024}, true},
/* 9 */ {"any", []int{0}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa"}, []int{512, 1024}, true},
// But work for everything else.
/* 10 */ {"any", []int{0}, []int{0, 256, 384, 512}, []string{"rsa", "rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{2048, 3072, 224, 256, 384, 521, 0}, false},
/* 10 */ {"any", []int{0}, []int{0, 256, 384, 512}, false, []string{"rsa", "rsa", "ec", "ec", "ec", "ec", "ed25519"}, []int{2048, 3072, 224, 256, 384, 521, 0}, false},

// RSA with larger than default key size should reject smaller ones.
/* 11 */ {"rsa", []int{3072}, []int{0, 256, 384, 512}, []string{"rsa"}, []int{2048}, true},
/* 11 */ {"rsa", []int{3072}, []int{0, 256, 384, 512}, false, []string{"rsa"}, []int{2048}, true},

// We should be able to sign with PSS with any CA key type.
/* 12 */ {"rsa", []int{0}, []int{0, 256, 384, 512}, true, []string{"rsa"}, []int{2048}, false},
/* 13 */ {"ec", []int{0}, []int{0}, true, []string{"ec"}, []int{256}, false},
/* 14 */ {"ed25519", []int{0}, []int{0}, true, []string{"ed25519"}, []int{0}, false},
}

if len(testCases) != 12 {
if len(testCases) != 15 {
t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases))
}

Expand Down
1 change: 1 addition & 0 deletions builtin/logical/pki/ca_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func getGenerationParams(sc *storageContext, data *framework.FieldData) (exporte
KeyType: keyType,
KeyBits: keyBits,
SignatureBits: data.Get("signature_bits").(int),
UsePSS: data.Get("use_pss").(bool),
AllowLocalhost: true,
AllowAnyName: true,
AllowIPSANs: true,
Expand Down
2 changes: 2 additions & 0 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func (sc *storageContext) fetchCAInfoByIssuerId(issuerId issuerID, usage issuerU
ParsedCertBundle: *parsedBundle,
URLs: nil,
LeafNotAfterBehavior: entry.LeafNotAfterBehavior,
RevocationSigAlg: entry.RevocationSigAlg,
}

entries, err := getURLs(sc.Context, sc.Storage)
Expand Down Expand Up @@ -1373,6 +1374,7 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
KeyType: data.role.KeyType,
KeyBits: data.role.KeyBits,
SignatureBits: data.role.SignatureBits,
UsePSS: data.role.UsePSS,
NotAfter: notAfter,
KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)),
ExtKeyUsage: parseExtKeyUsages(data.role),
Expand Down
56 changes: 56 additions & 0 deletions builtin/logical/pki/crl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pki

import (
"context"
"encoding/asn1"
"strings"
"testing"
"time"
Expand All @@ -25,6 +26,61 @@ func TestBackend_CRL_EnableDisableRoot(t *testing.T) {
crlEnableDisableTestForBackend(t, b, s, []string{caSerial})
}

func TestBackend_CRL_AllKeyTypeSigAlgos(t *testing.T) {
type testCase struct {
KeyType string
KeyBits int
SigAlgo string
}

testCases := []testCase{
{"rsa", 2048, "SHA256WithRSA"},
{"rsa", 2048, "SHA384WithRSA"},
{"rsa", 2048, "SHA512WithRSA"},
{"rsa", 2048, "SHA256WithRSAPSS"},
{"rsa", 2048, "SHA384WithRSAPSS"},
{"rsa", 2048, "SHA512WithRSAPSS"},
{"ec", 256, "ECDSAWithSHA256"},
{"ec", 384, "ECDSAWithSHA384"},
{"ec", 521, "ECDSAWithSHA512"},
{"ed25519", 0, "PureEd25519"},
}

for index, tc := range testCases {
t.Logf("tv %v", index)
b, s := createBackendWithStorage(t)

resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "myvault.com",
"key_type": tc.KeyType,
"key_bits": tc.KeyBits,
})
if err != nil {
t.Fatalf("tc %v: %v", index, err)
}
caSerial := resp.Data["serial_number"].(string)

_, err = CBPatch(b, s, "issuer/default", map[string]interface{}{
"revocation_signature_algorithm": tc.SigAlgo,
})
if err != nil {
t.Fatalf("tc %v: %v", index, err)
}

crlEnableDisableTestForBackend(t, b, s, []string{caSerial})

crl := getParsedCrlFromBackend(t, b, s, "crl")
if strings.HasSuffix(tc.SigAlgo, "PSS") {
algo := crl.SignatureAlgorithm
pssOid := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
if !algo.Algorithm.Equal(pssOid) {
t.Fatalf("tc %v failed: expected sig-alg to be %v / got %v", index, pssOid, algo)
}
}
}
}

func TestBackend_CRL_EnableDisableIntermediateWithRoot(t *testing.T) {
crlEnableDisableIntermediateTestForBackend(t, true)
}
Expand Down
1 change: 1 addition & 0 deletions builtin/logical/pki/crl_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ WRITE:
Number: big.NewInt(crlNumber),
ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(crlLifetime),
SignatureAlgorithm: signingBundle.RevocationSigAlg,
}

crlBytes, err := x509.CreateRevocationList(rand.Reader, revocationListTemplate, signingBundle.Certificate, signingBundle.PrivateKey)
Expand Down
7 changes: 7 additions & 0 deletions builtin/logical/pki/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ SHA-2-512. Defaults to 0 to automatically detect based on key length
},
}

fields["use_pss"] = &framework.FieldSchema{
Type: framework.TypeBool,
Default: false,
Description: `Whether or not to use PSS signatures when using a
RSA key-type issuer. Defaults to false.`,
}

fields["key_type"] = &framework.FieldSchema{
Type: framework.TypeString,
Default: "rsa",
Expand Down
82 changes: 74 additions & 8 deletions builtin/logical/pki/path_fetch_issuers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pki

import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
Expand Down Expand Up @@ -106,6 +107,17 @@ this issuer; valid values are "read-only", "issuing-certificates", and
and always set.`,
Default: []string{"read-only", "issuing-certificates", "crl-signing"},
}
fields["revocation_signature_algorithm"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Which x509.SignatureAlgorithm name to use for
signing CRLs. This parameter allows differentiation between PKCS#1v1.5
and PSS keys and choice of signature hash algorithm. The default (empty
string) value is for Go to select the signature algorithm. This can fail
if the underlying key does not support the requested signature algorithm,
which may not be known at modification time (such as with PKCS#11 managed
RSA keys).`,
Default: "",
}

return &framework.Path{
// Returns a JSON entry.
Expand Down Expand Up @@ -179,16 +191,22 @@ func respondReadIssuer(issuer *issuerEntry) (*logical.Response, error) {
respManualChain = append(respManualChain, string(entity))
}

revSigAlgStr := issuer.RevocationSigAlg.String()
if issuer.RevocationSigAlg == x509.UnknownSignatureAlgorithm {
revSigAlgStr = ""
}

return &logical.Response{
Data: map[string]interface{}{
"issuer_id": issuer.ID,
"issuer_name": issuer.Name,
"key_id": issuer.KeyID,
"certificate": issuer.Certificate,
"manual_chain": respManualChain,
"ca_chain": issuer.CAChain,
"leaf_not_after_behavior": issuer.LeafNotAfterBehavior.String(),
"usage": issuer.Usage.Names(),
"issuer_id": issuer.ID,
"issuer_name": issuer.Name,
"key_id": issuer.KeyID,
"certificate": issuer.Certificate,
"manual_chain": respManualChain,
"ca_chain": issuer.CAChain,
"leaf_not_after_behavior": issuer.LeafNotAfterBehavior.String(),
"usage": issuer.Usage.Names(),
"revocation_signature_algorithm": revSigAlgStr,
},
}, nil
}
Expand Down Expand Up @@ -258,6 +276,23 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
return logical.ErrorResponse(fmt.Sprintf("Unable to parse specified usages: %v - valid values are %v", rawUsage, AllIssuerUsages.Names())), nil
}

// Revocation signature algorithm changes
revSigAlgStr := data.Get("revocation_signature_algorithm").(string)
revSigAlg, present := certutil.SignatureAlgorithmNames[strings.ToLower(revSigAlgStr)]
if !present && revSigAlgStr != "" {
var knownAlgos []string
for algoName := range certutil.SignatureAlgorithmNames {
knownAlgos = append(knownAlgos, algoName)
}

return logical.ErrorResponse(fmt.Sprintf("Unknown signature algorithm value: %v - valid values are %v", revSigAlg, strings.Join(knownAlgos, ", "))), nil
} else if revSigAlgStr == "" {
revSigAlg = x509.UnknownSignatureAlgorithm
}
if err := issuer.CanMaybeSignWithAlgo(revSigAlg); err != nil {
return nil, err
}

modified := false

var oldName string
Expand All @@ -277,6 +312,11 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
modified = true
}

if revSigAlg != issuer.RevocationSigAlg {
issuer.RevocationSigAlg = revSigAlg
modified = true
}

var updateChain bool
var constructedChain []issuerID
for index, newPathRef := range newPath {
Expand Down Expand Up @@ -426,6 +466,32 @@ func (b *backend) pathPatchIssuer(ctx context.Context, req *logical.Request, dat
}
}

// Revocation signature algorithm changes
rawRevSigAlg, ok := data.GetOk("revocation_signature_algorithm")
if ok {
revSigAlgStr := rawRevSigAlg.(string)
revSigAlg, present := certutil.SignatureAlgorithmNames[strings.ToLower(revSigAlgStr)]
if !present && revSigAlgStr != "" {
var knownAlgos []string
for algoName := range certutil.SignatureAlgorithmNames {
knownAlgos = append(knownAlgos, algoName)
}

return logical.ErrorResponse(fmt.Sprintf("Unknown signature algorithm value: %v - valid values are %v", revSigAlg, strings.Join(knownAlgos, ", "))), nil
} else if revSigAlgStr == "" {
revSigAlg = x509.UnknownSignatureAlgorithm
}

if err := issuer.CanMaybeSignWithAlgo(revSigAlg); err != nil {
return nil, err
}

if revSigAlg != issuer.RevocationSigAlg {
issuer.RevocationSigAlg = revSigAlg
modified = true
}
}

// Manual Chain Changes
newPathData, ok := data.GetOk("manual_chain")
if ok {
Expand Down
7 changes: 6 additions & 1 deletion builtin/logical/pki/path_intermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,18 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req
// Nasty hack part two. :-) For generation of CSRs, certutil presently doesn't
// support configuration of this. However, because we need generation parameters,
// which create a role and attempt to read this parameter, we need to provide
// a value (which will be ignored). Hence, we stub in the missing parameter here,
// a value (which will be ignored). Hence, we stub in the missing parameters here,
// including its schema, just enough for it to work..
data.Schema["signature_bits"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: 0,
}
data.Raw["signature_bits"] = 0
data.Schema["use_pss"] = &framework.FieldSchema{
Type: framework.TypeBool,
Default: false,
}
data.Raw["use_pss"] = false

sc := b.makeStorageContext(ctx, req.Storage)
exported, format, role, errorResp := getGenerationParams(sc, data)
Expand Down
8 changes: 8 additions & 0 deletions builtin/logical/pki/path_issue_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ SHA-2-512. Defaults to 0 to automatically detect based on key length
},
}

ret.Fields["use_pss"] = &framework.FieldSchema{
Type: framework.TypeBool,
Default: false,
Description: `Whether or not to use PSS signatures when using a
RSA key-type issuer. Defaults to false.`,
}

return ret
}

Expand Down Expand Up @@ -211,6 +218,7 @@ func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, da
ExtKeyUsage: data.Get("ext_key_usage").([]string),
ExtKeyUsageOIDs: data.Get("ext_key_usage_oids").([]string),
SignatureBits: data.Get("signature_bits").(int),
UsePSS: data.Get("use_pss").(bool),
}
*entry.AllowWildcardCertificates = true

Expand Down
Loading