Add PSS support to PKI Secrets Engine (#16519)

* Add PSS signature support to Vault PKI engine

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Use issuer's RevocationSigAlg for CRL signing

We introduce a new parameter on issuers, revocation_signature_algorithm
to control the signature algorithm used during CRL signing. This is
because the SignatureAlgorithm value from the certificate itself is
incorrect for this purpose: a RSA root could sign an ECDSA intermediate
with say, SHA256WithRSA, but when the intermediate goes to sign a CRL,
it must use ECDSAWithSHA256 or equivalent instead of SHA256WithRSA. When
coupled with support for PSS-only keys, allowing the user to set the
signature algorithm value as desired seems like the best approach.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add use_pss, revocation_signature_algorithm docs

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add PSS to signature role issuance test matrix

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add changelog

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Allow roots to self-identify revocation alg

When using PSS support with a managed key, sometimes the underlying
device will not support PKCS#1v1.5 signatures. This results in CRL
building failing, unless we update the entry's signature algorithm
prior to building the CRL for the new root.

With a RSA-type key and use_pss=true, we use the signature bits value to
decide which hash function to use for PSS support.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add clearer error message on failed import

When CRL building fails during cert/key import, due to PSS failures,
give a better indication to the user that import succeeded its just CRL
building that failed. This tells them the parameter to adjust on the
issuer and warns that CRL building will fail until this is fixed.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add case insensitive SigAlgo matching

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Convert UsePSS back to regular bool

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Refactor PSS->certTemplate into helper function

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Proper string output on rev_sig_alg display

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Copy root's SignatureAlgorithm for CRL building

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
Alexander Scheel 2022-08-03 12:42:24 -04:00 committed by GitHub
parent d136ba385a
commit 8acbf7f480
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 351 additions and 46 deletions

View File

@ -3552,6 +3552,7 @@ func TestReadWriteDeleteRoles(t *testing.T) {
"allowed_serial_numbers": []interface{}{}, "allowed_serial_numbers": []interface{}{},
"generate_lease": false, "generate_lease": false,
"signature_bits": json.Number("256"), "signature_bits": json.Number("256"),
"use_pss": false,
"allowed_domains": []interface{}{}, "allowed_domains": []interface{}{},
"allowed_uri_sans_template": false, "allowed_uri_sans_template": false,
"enforce_hostnames": true, "enforce_hostnames": true,
@ -4459,6 +4460,7 @@ type KeySizeRegression struct {
// Signature Bits presently is only specified on the role. // Signature Bits presently is only specified on the role.
RoleSignatureBits []int RoleSignatureBits []int
RoleUsePSS bool
// These are tuples; must be of the same length. // These are tuples; must be of the same length.
TestKeyTypes []string TestKeyTypes []string
@ -4502,6 +4504,7 @@ func RoleKeySizeRegressionHelper(t *testing.T, b *backend, s logical.Storage, in
"key_type": test.RoleKeyType, "key_type": test.RoleKeyType,
"key_bits": roleKeyBits, "key_bits": roleKeyBits,
"signature_bits": roleSignatureBits, "signature_bits": roleSignatureBits,
"use_pss": test.RoleUsePSS,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -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) 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 tested += 1
} }
} }
@ -4548,36 +4560,41 @@ func TestBackend_Roles_KeySizeRegression(t *testing.T) {
testCases := []KeySizeRegression{ testCases := []KeySizeRegression{
// RSA with default parameters should fail to issue smaller RSA keys // RSA with default parameters should fail to issue smaller RSA keys
// and any size ECDSA/Ed25519 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. // 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 // EC with default parameters should fail to issue smaller EC keys
// and any size RSA/Ed25519 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 // 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 // independent of signature bits as that's computed from the issuer
// type (for EC based issuers). // 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}, /* 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}, []string{"ec", "ec", "ec"}, []int{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}, []string{"ec", "ec"}, []int{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}, []string{"ec"}, []int{521}, false}, /* 6 */ {"ec", []int{521}, []int{0, 256, 384, 512}, false, []string{"ec"}, []int{521}, false},
// Ed25519 should reject RSA and EC keys. // 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. // 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. // 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. // 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. // 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)) t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases))
} }

View File

@ -49,6 +49,7 @@ func getGenerationParams(sc *storageContext, data *framework.FieldData) (exporte
KeyType: keyType, KeyType: keyType,
KeyBits: keyBits, KeyBits: keyBits,
SignatureBits: data.Get("signature_bits").(int), SignatureBits: data.Get("signature_bits").(int),
UsePSS: data.Get("use_pss").(bool),
AllowLocalhost: true, AllowLocalhost: true,
AllowAnyName: true, AllowAnyName: true,
AllowIPSANs: true, AllowIPSANs: true,

View File

@ -138,6 +138,7 @@ func (sc *storageContext) fetchCAInfoByIssuerId(issuerId issuerID, usage issuerU
ParsedCertBundle: *parsedBundle, ParsedCertBundle: *parsedBundle,
URLs: nil, URLs: nil,
LeafNotAfterBehavior: entry.LeafNotAfterBehavior, LeafNotAfterBehavior: entry.LeafNotAfterBehavior,
RevocationSigAlg: entry.RevocationSigAlg,
} }
entries, err := getURLs(sc.Context, sc.Storage) entries, err := getURLs(sc.Context, sc.Storage)
@ -1373,6 +1374,7 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
KeyType: data.role.KeyType, KeyType: data.role.KeyType,
KeyBits: data.role.KeyBits, KeyBits: data.role.KeyBits,
SignatureBits: data.role.SignatureBits, SignatureBits: data.role.SignatureBits,
UsePSS: data.role.UsePSS,
NotAfter: notAfter, NotAfter: notAfter,
KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)), KeyUsage: x509.KeyUsage(parseKeyUsages(data.role.KeyUsage)),
ExtKeyUsage: parseExtKeyUsages(data.role), ExtKeyUsage: parseExtKeyUsages(data.role),

View File

@ -2,6 +2,7 @@ package pki
import ( import (
"context" "context"
"encoding/asn1"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -25,6 +26,61 @@ func TestBackend_CRL_EnableDisableRoot(t *testing.T) {
crlEnableDisableTestForBackend(t, b, s, []string{caSerial}) 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) { func TestBackend_CRL_EnableDisableIntermediateWithRoot(t *testing.T) {
crlEnableDisableIntermediateTestForBackend(t, true) crlEnableDisableIntermediateTestForBackend(t, true)
} }

View File

@ -622,6 +622,7 @@ WRITE:
Number: big.NewInt(crlNumber), Number: big.NewInt(crlNumber),
ThisUpdate: time.Now(), ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(crlLifetime), NextUpdate: time.Now().Add(crlLifetime),
SignatureAlgorithm: signingBundle.RevocationSigAlg,
} }
crlBytes, err := x509.CreateRevocationList(rand.Reader, revocationListTemplate, signingBundle.Certificate, signingBundle.PrivateKey) crlBytes, err := x509.CreateRevocationList(rand.Reader, revocationListTemplate, signingBundle.Certificate, signingBundle.PrivateKey)

View File

@ -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{ fields["key_type"] = &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Default: "rsa", Default: "rsa",

View File

@ -2,6 +2,7 @@ package pki
import ( import (
"context" "context"
"crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"strings" "strings"
@ -106,6 +107,17 @@ this issuer; valid values are "read-only", "issuing-certificates", and
and always set.`, and always set.`,
Default: []string{"read-only", "issuing-certificates", "crl-signing"}, 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{ return &framework.Path{
// Returns a JSON entry. // Returns a JSON entry.
@ -179,6 +191,11 @@ func respondReadIssuer(issuer *issuerEntry) (*logical.Response, error) {
respManualChain = append(respManualChain, string(entity)) respManualChain = append(respManualChain, string(entity))
} }
revSigAlgStr := issuer.RevocationSigAlg.String()
if issuer.RevocationSigAlg == x509.UnknownSignatureAlgorithm {
revSigAlgStr = ""
}
return &logical.Response{ return &logical.Response{
Data: map[string]interface{}{ Data: map[string]interface{}{
"issuer_id": issuer.ID, "issuer_id": issuer.ID,
@ -189,6 +206,7 @@ func respondReadIssuer(issuer *issuerEntry) (*logical.Response, error) {
"ca_chain": issuer.CAChain, "ca_chain": issuer.CAChain,
"leaf_not_after_behavior": issuer.LeafNotAfterBehavior.String(), "leaf_not_after_behavior": issuer.LeafNotAfterBehavior.String(),
"usage": issuer.Usage.Names(), "usage": issuer.Usage.Names(),
"revocation_signature_algorithm": revSigAlgStr,
}, },
}, nil }, nil
} }
@ -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 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 modified := false
var oldName string var oldName string
@ -277,6 +312,11 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
modified = true modified = true
} }
if revSigAlg != issuer.RevocationSigAlg {
issuer.RevocationSigAlg = revSigAlg
modified = true
}
var updateChain bool var updateChain bool
var constructedChain []issuerID var constructedChain []issuerID
for index, newPathRef := range newPath { for index, newPathRef := range newPath {
@ -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 // Manual Chain Changes
newPathData, ok := data.GetOk("manual_chain") newPathData, ok := data.GetOk("manual_chain")
if ok { if ok {

View File

@ -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 // Nasty hack part two. :-) For generation of CSRs, certutil presently doesn't
// support configuration of this. However, because we need generation parameters, // support configuration of this. However, because we need generation parameters,
// which create a role and attempt to read this parameter, we need to provide // 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.. // including its schema, just enough for it to work..
data.Schema["signature_bits"] = &framework.FieldSchema{ data.Schema["signature_bits"] = &framework.FieldSchema{
Type: framework.TypeInt, Type: framework.TypeInt,
Default: 0, Default: 0,
} }
data.Raw["signature_bits"] = 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) sc := b.makeStorageContext(ctx, req.Storage)
exported, format, role, errorResp := getGenerationParams(sc, data) exported, format, role, errorResp := getGenerationParams(sc, data)

View File

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

View File

@ -82,6 +82,7 @@ with Active Directory Certificate Services.`,
// signed certificate's bits (that's on the /sign-intermediate // signed certificate's bits (that's on the /sign-intermediate
// endpoints). Remove it from the list of fields to avoid confusion. // endpoints). Remove it from the list of fields to avoid confusion.
delete(ret.Fields, "signature_bits") delete(ret.Fields, "signature_bits")
delete(ret.Fields, "use_pss")
return ret return ret
} }
@ -238,6 +239,13 @@ func (b *backend) pathImportIssuers(ctx context.Context, req *logical.Request, d
if len(createdIssuers) > 0 { if len(createdIssuers) > 0 {
err := b.crlBuilder.rebuild(ctx, b, req, true) err := b.crlBuilder.rebuild(ctx, b, req, true)
if err != nil { if err != nil {
// Before returning, check if the error message includes the
// string "PSS". If so, it indicates we might've wanted to modify
// this issuer, so convert the error to a warning.
if strings.Contains(err.Error(), "PSS") || strings.Contains(err.Error(), "pss") {
err = fmt.Errorf("Rebuilding the CRL failed with a message relating to the PSS signature algorithm. This likely means the revocation_signature_algorithm needs to be set on the newly imported issuer(s) because a managed key supports only the PSS algorithm; by default PKCS#1v1.5 was used to build the CRLs. CRLs will not be generated until this has been addressed, however the import was successful. The original error is reproduced below:\n\n\t%v", err)
}
return nil, err return nil, err
} }
} }

View File

@ -238,6 +238,13 @@ SHA-2-512. Defaults to 0 to automatically detect based on key length
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`, (SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
}, },
"use_pss": {
Type: framework.TypeBool,
Default: false,
Description: `Whether or not to use PSS signatures when using a
RSA key-type issuer. Defaults to false.`,
},
"key_usage": { "key_usage": {
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"}, Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"},
@ -673,6 +680,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
KeyType: data.Get("key_type").(string), KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int), KeyBits: data.Get("key_bits").(int),
SignatureBits: data.Get("signature_bits").(int), SignatureBits: data.Get("signature_bits").(int),
UsePSS: data.Get("use_pss").(bool),
UseCSRCommonName: data.Get("use_csr_common_name").(bool), UseCSRCommonName: data.Get("use_csr_common_name").(bool),
UseCSRSANs: data.Get("use_csr_sans").(bool), UseCSRSANs: data.Get("use_csr_sans").(bool),
KeyUsage: data.Get("key_usage").([]string), KeyUsage: data.Get("key_usage").([]string),
@ -869,6 +877,7 @@ func (b *backend) pathRolePatch(ctx context.Context, req *logical.Request, data
KeyType: getWithExplicitDefault(data, "key_type", oldEntry.KeyType).(string), KeyType: getWithExplicitDefault(data, "key_type", oldEntry.KeyType).(string),
KeyBits: getWithExplicitDefault(data, "key_bits", oldEntry.KeyBits).(int), KeyBits: getWithExplicitDefault(data, "key_bits", oldEntry.KeyBits).(int),
SignatureBits: getWithExplicitDefault(data, "signature_bits", oldEntry.SignatureBits).(int), SignatureBits: getWithExplicitDefault(data, "signature_bits", oldEntry.SignatureBits).(int),
UsePSS: getWithExplicitDefault(data, "use_pss", oldEntry.UsePSS).(bool),
UseCSRCommonName: getWithExplicitDefault(data, "use_csr_common_name", oldEntry.UseCSRCommonName).(bool), UseCSRCommonName: getWithExplicitDefault(data, "use_csr_common_name", oldEntry.UseCSRCommonName).(bool),
UseCSRSANs: getWithExplicitDefault(data, "use_csr_sans", oldEntry.UseCSRSANs).(bool), UseCSRSANs: getWithExplicitDefault(data, "use_csr_sans", oldEntry.UseCSRSANs).(bool),
KeyUsage: getWithExplicitDefault(data, "key_usage", oldEntry.KeyUsage).([]string), KeyUsage: getWithExplicitDefault(data, "key_usage", oldEntry.KeyUsage).([]string),
@ -1063,6 +1072,7 @@ type roleEntry struct {
UseCSRSANs bool `json:"use_csr_sans"` UseCSRSANs bool `json:"use_csr_sans"`
KeyType string `json:"key_type"` KeyType string `json:"key_type"`
KeyBits int `json:"key_bits"` KeyBits int `json:"key_bits"`
UsePSS bool `json:"use_pss"`
SignatureBits int `json:"signature_bits"` SignatureBits int `json:"signature_bits"`
MaxPathLength *int `json:",omitempty"` MaxPathLength *int `json:",omitempty"`
KeyUsageOld string `json:"key_usage,omitempty"` KeyUsageOld string `json:"key_usage,omitempty"`
@ -1118,6 +1128,7 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
"key_type": r.KeyType, "key_type": r.KeyType,
"key_bits": r.KeyBits, "key_bits": r.KeyBits,
"signature_bits": r.SignatureBits, "signature_bits": r.SignatureBits,
"use_pss": r.UsePSS,
"key_usage": r.KeyUsage, "key_usage": r.KeyUsage,
"ext_key_usage": r.ExtKeyUsage, "ext_key_usage": r.ExtKeyUsage,
"ext_key_usage_oids": r.ExtKeyUsageOIDs, "ext_key_usage_oids": r.ExtKeyUsageOIDs,

View File

@ -236,6 +236,25 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request,
resp.Data["key_id"] = myKey.ID resp.Data["key_id"] = myKey.ID
resp.Data["key_name"] = myKey.Name resp.Data["key_name"] = myKey.Name
// Update the issuer to reflect the PSS status here for revocation; this
// allows CRL building to succeed if the root is using a managed key with
// only PSS support.
if input.role.KeyType == "rsa" && input.role.UsePSS {
// The one time that it is safe (and good) to copy the
// SignatureAlgorithm field off the certificate (for the purposes of
// detecting PSS support) is when we've freshly generated it AND it
// is a root (exactly this endpoint).
//
// For intermediates, this doesn't hold (not this endpoint) as that
// reflects the parent key's preferences. For imports, this doesn't
// hold as the old system might've allowed other signature types that
// the new system (whether Vault or a managed key) doesn't.
myIssuer.RevocationSigAlg = parsedBundle.Certificate.SignatureAlgorithm
if err := sc.writeIssuer(myIssuer); err != nil {
return nil, fmt.Errorf("unable to store PSS-updated issuer: %v", err)
}
}
// Also store it as just the certificate identified by serial number, so it // Also store it as just the certificate identified by serial number, so it
// can be revoked // can be revoked
err = req.Storage.Put(ctx, &logical.StorageEntry{ err = req.Storage.Put(ctx, &logical.StorageEntry{
@ -290,6 +309,7 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R
EnforceHostnames: false, EnforceHostnames: false,
KeyType: "any", KeyType: "any",
SignatureBits: data.Get("signature_bits").(int), SignatureBits: data.Get("signature_bits").(int),
UsePSS: data.Get("use_pss").(bool),
AllowedOtherSANs: []string{"*"}, AllowedOtherSANs: []string{"*"},
AllowedSerialNumbers: []string{"*"}, AllowedSerialNumbers: []string{"*"},
AllowedURISANs: []string{"*"}, AllowedURISANs: []string{"*"},

View File

@ -85,6 +85,13 @@ in the above RFC section.`,
}, },
} }
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 path return path
} }

View File

@ -145,6 +145,7 @@ type issuerEntry struct {
SerialNumber string `json:"serial_number"` SerialNumber string `json:"serial_number"`
LeafNotAfterBehavior certutil.NotAfterBehavior `json:"not_after_behavior"` LeafNotAfterBehavior certutil.NotAfterBehavior `json:"not_after_behavior"`
Usage issuerUsage `json:"usage"` Usage issuerUsage `json:"usage"`
RevocationSigAlg x509.SignatureAlgorithm `json:"revocation_signature_algorithm"`
} }
type localCRLConfigEntry struct { type localCRLConfigEntry struct {
@ -427,6 +428,51 @@ func (i issuerEntry) EnsureUsage(usage issuerUsage) error {
return fmt.Errorf("unknown delta between usages: %v -> %v / for issuer [%v]", usage.Names(), i.Usage.Names(), issuerRef) return fmt.Errorf("unknown delta between usages: %v -> %v / for issuer [%v]", usage.Names(), i.Usage.Names(), issuerRef)
} }
func (i issuerEntry) CanMaybeSignWithAlgo(algo x509.SignatureAlgorithm) error {
// Hack: Go isn't kind enough expose its lovely signatureAlgorithmDetails
// informational struct for our usage. However, we don't want to actually
// fetch the private key and attempt a signature with this algo (as we'll
// mint new, previously unsigned material in the process that could maybe
// be potentially abused if it leaks).
//
// So...
//
// ...we maintain our own mapping of cert.PKI<->sigAlgos. Notably, we
// exclude DSA support as the PKI engine has never supported DSA keys.
if algo == x509.UnknownSignatureAlgorithm {
// Special cased to indicate upgrade and letting Go automatically
// chose the correct value.
return nil
}
cert, err := i.GetCertificate()
if err != nil {
return fmt.Errorf("unable to parse issuer's potential signature algorithm types: %v", err)
}
switch cert.PublicKeyAlgorithm {
case x509.RSA:
switch algo {
case x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA,
x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS,
x509.SHA512WithRSAPSS:
return nil
}
case x509.ECDSA:
switch algo {
case x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512:
return nil
}
case x509.Ed25519:
switch algo {
case x509.PureEd25519:
return nil
}
}
return fmt.Errorf("unable to use issuer of type %v to sign with %v key type", cert.PublicKeyAlgorithm.String(), algo.String())
}
func (sc *storageContext) listIssuers() ([]issuerID, error) { func (sc *storageContext) listIssuers() ([]issuerID, error) {
strList, err := sc.Storage.List(sc.Context, issuerPrefix) strList, err := sc.Storage.List(sc.Context, issuerPrefix)
if err != nil { if err != nil {

View File

@ -284,6 +284,10 @@ func CBWrite(b *backend, s logical.Storage, path string, data map[string]interfa
return CBReq(b, s, logical.UpdateOperation, path, data) return CBReq(b, s, logical.UpdateOperation, path, data)
} }
func CBPatch(b *backend, s logical.Storage, path string, data map[string]interface{}) (*logical.Response, error) {
return CBReq(b, s, logical.PatchOperation, path, data)
}
func CBList(b *backend, s logical.Storage, path string) (*logical.Response, error) { func CBList(b *backend, s logical.Storage, path string) (*logical.Response, error) {
return CBReq(b, s, logical.ListOperation, path, make(map[string]interface{})) return CBReq(b, s, logical.ListOperation, path, make(map[string]interface{}))
} }

3
changelog/16519.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secret/pki: Add RSA PSS signature support for issuing certificates, signing CRLs
```

View File

@ -49,6 +49,21 @@ var expectedNISTPCurveHashBits = map[int]int{
521: 512, 521: 512,
} }
// Mapping of constant names<->constant values for SignatureAlgorithm
var SignatureAlgorithmNames = map[string]x509.SignatureAlgorithm{
"sha256withrsa": x509.SHA256WithRSA,
"sha384withrsa": x509.SHA384WithRSA,
"sha512withrsa": x509.SHA512WithRSA,
"ecdsawithsha256": x509.ECDSAWithSHA256,
"ecdsawithsha384": x509.ECDSAWithSHA384,
"ecdsawithsha512": x509.ECDSAWithSHA512,
"sha256withrsapss": x509.SHA256WithRSAPSS,
"sha384withrsapss": x509.SHA384WithRSAPSS,
"sha512withrsapss": x509.SHA512WithRSAPSS,
"pureed25519": x509.PureEd25519,
"ed25519": x509.PureEd25519, // Duplicated for clarity; most won't expect the "Pure" prefix.
}
// GetHexFormatted returns the byte buffer formatted in hex with // GetHexFormatted returns the byte buffer formatted in hex with
// the specified separator between bytes. // the specified separator between bytes.
func GetHexFormatted(buf []byte, sep string) string { func GetHexFormatted(buf []byte, sep string) string {
@ -766,6 +781,29 @@ func CreateCertificateWithKeyGenerator(data *CreationBundle, randReader io.Reade
return createCertificate(data, randReader, keyGenerator) return createCertificate(data, randReader, keyGenerator)
} }
// Set correct correct RSA sig algo
func certTemplateSetSigAlgo(certTemplate *x509.Certificate, data *CreationBundle) {
if data.Params.UsePSS {
switch data.Params.SignatureBits {
case 256:
certTemplate.SignatureAlgorithm = x509.SHA256WithRSAPSS
case 384:
certTemplate.SignatureAlgorithm = x509.SHA384WithRSAPSS
case 512:
certTemplate.SignatureAlgorithm = x509.SHA512WithRSAPSS
}
} else {
switch data.Params.SignatureBits {
case 256:
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
certTemplate.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
certTemplate.SignatureAlgorithm = x509.SHA512WithRSA
}
}
}
func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGenerator KeyGenerator) (*ParsedCertBundle, error) { func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGenerator KeyGenerator) (*ParsedCertBundle, error) {
var err error var err error
result := &ParsedCertBundle{} result := &ParsedCertBundle{}
@ -834,14 +872,7 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen
if data.SigningBundle != nil { if data.SigningBundle != nil {
switch data.SigningBundle.PrivateKeyType { switch data.SigningBundle.PrivateKeyType {
case RSAPrivateKey: case RSAPrivateKey:
switch data.Params.SignatureBits { certTemplateSetSigAlgo(certTemplate, data)
case 256:
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
certTemplate.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
certTemplate.SignatureAlgorithm = x509.SHA512WithRSA
}
case Ed25519PrivateKey: case Ed25519PrivateKey:
certTemplate.SignatureAlgorithm = x509.PureEd25519 certTemplate.SignatureAlgorithm = x509.PureEd25519
case ECPrivateKey: case ECPrivateKey:
@ -863,14 +894,7 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen
switch data.Params.KeyType { switch data.Params.KeyType {
case "rsa": case "rsa":
switch data.Params.SignatureBits { certTemplateSetSigAlgo(certTemplate, data)
case 256:
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
certTemplate.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
certTemplate.SignatureAlgorithm = x509.SHA512WithRSA
}
case "ed25519": case "ed25519":
certTemplate.SignatureAlgorithm = x509.PureEd25519 certTemplate.SignatureAlgorithm = x509.PureEd25519
case "ec": case "ec":
@ -1097,14 +1121,7 @@ func signCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertBun
switch data.SigningBundle.PrivateKeyType { switch data.SigningBundle.PrivateKeyType {
case RSAPrivateKey: case RSAPrivateKey:
switch data.Params.SignatureBits { certTemplateSetSigAlgo(certTemplate, data)
case 256:
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
case 384:
certTemplate.SignatureAlgorithm = x509.SHA384WithRSA
case 512:
certTemplate.SignatureAlgorithm = x509.SHA512WithRSA
}
case ECPrivateKey: case ECPrivateKey:
switch data.Params.SignatureBits { switch data.Params.SignatureBits {
case 256: case 256:

View File

@ -710,6 +710,7 @@ type CAInfoBundle struct {
ParsedCertBundle ParsedCertBundle
URLs *URLEntries URLs *URLEntries
LeafNotAfterBehavior NotAfterBehavior LeafNotAfterBehavior NotAfterBehavior
RevocationSigAlg x509.SignatureAlgorithm
} }
func (b *CAInfoBundle) GetCAChain() []*CertBlock { func (b *CAInfoBundle) GetCAChain() []*CertBlock {
@ -782,6 +783,7 @@ type CreationParameters struct {
PolicyIdentifiers []string PolicyIdentifiers []string
BasicConstraintsValidForNonCA bool BasicConstraintsValidForNonCA bool
SignatureBits int SignatureBits int
UsePSS bool
ForceAppendCaChain bool ForceAppendCaChain bool
// Only used when signing a CA cert // Only used when signing a CA cert

View File

@ -581,6 +581,10 @@ when signing an externally-owned intermediate.
certain TLS implementations (such as OpenSSL) which use SKID/AKID certain TLS implementations (such as OpenSSL) which use SKID/AKID
matches in chain building to restrict possible valid chains. matches in chain building to restrict possible valid chains.
- `use_pss` `(bool: false)` - Specifies whether or not to use PSS signatures
over PKCS#1v1.5 signatures when a RSA-type issuer is used. Ignored for
ECDSA/Ed25519 issuers.
#### Sample Payload #### Sample Payload
```json ```json
@ -771,6 +775,10 @@ have access.**
`signature_bits` value; only RSA issuers will change signature types `signature_bits` value; only RSA issuers will change signature types
based on this parameter. based on this parameter.
- `use_pss` `(bool: false)` - Specifies whether or not to use PSS signatures
over PKCS#1v1.5 signatures when a RSA-type issuer is used. Ignored for
ECDSA/Ed25519 issuers.
#### Sample Payload #### Sample Payload
```json ```json
@ -1898,6 +1906,18 @@ do so, import a new issuer and a new `issuer_id` will be assigned.
could be marked `usage=read-only`, freezing the CRL. Finally, after a grace could be marked `usage=read-only`, freezing the CRL. Finally, after a grace
period, the issuer could be deleted. period, the issuer could be deleted.
- `revocation_signature_algorithm` `(string: "")` - Which signature algorithm
to use when building CRLs. See Go's [`x509.SignatureAlgorithm`](https://pkg.go.dev/crypto/x509#SignatureAlgorithm)
constant for possible values. This flag allows control over hash function
and signature scheme (PKCS#1v1.5 vs PSS). The default (empty string) value
is for Go to select the signature algorithm automatically, which may not
always work.
~> Note: This can fail if the underlying key does not support the requested
signature algorithm; this may not always be known at modification time.
This most commonly needs to be modified when using PKCS#11 managed keys
with the `CKM_RSA_PKCS_PSS` mechanism type.
#### Sample Payload #### Sample Payload
```json ```json
@ -2370,6 +2390,10 @@ request is denied.
`signature_bits` value; only RSA issuers will change signature types `signature_bits` value; only RSA issuers will change signature types
based on this parameter. based on this parameter.
- `use_pss` `(bool: false)` - Specifies whether or not to use PSS signatures
over PKCS#1v1.5 signatures when a RSA-type issuer is used. Ignored for
ECDSA/Ed25519 issuers.
- `key_usage` `(list: ["DigitalSignature", "KeyAgreement", "KeyEncipherment"])` - - `key_usage` `(list: ["DigitalSignature", "KeyAgreement", "KeyEncipherment"])` -
Specifies the allowed key usage constraint on issued certificates. Valid Specifies the allowed key usage constraint on issued certificates. Valid
values can be found at https://golang.org/pkg/crypto/x509/#KeyUsage - simply values can be found at https://golang.org/pkg/crypto/x509/#KeyUsage - simply