Allow to kms signers to define the SignatureAlgorithm

CloudKMS keys signs data using an specific signature algorithm, in RSA keys,
this can be PKCS#1 RSA or RSA-PSS, if the later is used, x509.CreateCertificate
will fail unless the template SignatureCertificate is properly set.

On contrast, AWSKMS RSA keys, are just RSA keys, and can sign with PKCS#1 or
RSA-PSS schemes, so right now the way to enforce one or the other is to used
templates.
This commit is contained in:
Mariano Cano 2021-09-08 15:33:34 -07:00
parent 837db2e147
commit 6d644880bd
6 changed files with 174 additions and 5 deletions

View file

@ -1,6 +1,7 @@
package apiv1 package apiv1
import ( import (
"crypto/x509"
"net/http" "net/http"
"strings" "strings"
) )
@ -26,6 +27,12 @@ type CertificateAuthorityCreator interface {
CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error) CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error)
} }
// SignatureAlgorithmGetter is an optional implementation in a crypto.Signer
// that returns the SignatureAlgorithm to use.
type SignatureAlgorithmGetter interface {
SignatureAlgorithm() x509.SignatureAlgorithm
}
// Type represents the CAS type used. // Type represents the CAS type used.
type Type string type Type string

View file

@ -68,7 +68,7 @@ func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1
} }
req.Template.Issuer = c.CertificateChain[0].Subject req.Template.Issuer = c.CertificateChain[0].Subject
cert, err := x509util.CreateCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer) cert, err := createCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -93,7 +93,7 @@ func (c *SoftCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.R
req.Template.NotAfter = t.Add(req.Lifetime) req.Template.NotAfter = t.Add(req.Lifetime)
req.Template.Issuer = c.CertificateChain[0].Subject req.Template.Issuer = c.CertificateChain[0].Subject
cert, err := x509util.CreateCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer) cert, err := createCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -150,12 +150,12 @@ func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthori
var cert *x509.Certificate var cert *x509.Certificate
switch req.Type { switch req.Type {
case apiv1.RootCA: case apiv1.RootCA:
cert, err = x509util.CreateCertificate(req.Template, req.Template, signer.Public(), signer) cert, err = createCertificate(req.Template, req.Template, signer.Public(), signer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case apiv1.IntermediateCA: case apiv1.IntermediateCA:
cert, err = x509util.CreateCertificate(req.Template, req.Parent.Certificate, signer.Public(), req.Parent.Signer) cert, err = createCertificate(req.Template, req.Parent.Certificate, signer.Public(), req.Parent.Signer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -210,3 +210,16 @@ func (c *SoftCAS) createSigner(req *kmsapi.CreateSignerRequest) (crypto.Signer,
} }
return c.KeyManager.CreateSigner(req) return c.KeyManager.CreateSigner(req)
} }
// createCertificate sets the SignatureAlgorithm of the template if necessary
// and calls x509util.CreateCertificate.
func createCertificate(template, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
// Signers can specify the signature algorithm. This is specially important
// when x509.CreateCertificates attempts to validate a RSAPSS signature.
if template.SignatureAlgorithm == 0 {
if sa, ok := signer.(apiv1.SignatureAlgorithmGetter); ok {
template.SignatureAlgorithm = sa.SignatureAlgorithm()
}
}
return x509util.CreateCertificate(template, parent, pub, signer)
}

View file

@ -75,6 +75,15 @@ var (
testSignedIntermediateTemplate = mustSign(testIntermediateTemplate, testSignedRootTemplate, testNow, testNow.Add(24*time.Hour)) testSignedIntermediateTemplate = mustSign(testIntermediateTemplate, testSignedRootTemplate, testNow, testNow.Add(24*time.Hour))
) )
type signatureAlgorithmSigner struct {
crypto.Signer
algorithm x509.SignatureAlgorithm
}
func (s *signatureAlgorithmSigner) SignatureAlgorithm() x509.SignatureAlgorithm {
return s.algorithm
}
type mockKeyManager struct { type mockKeyManager struct {
signer crypto.Signer signer crypto.Signer
errGetPublicKey error errGetPublicKey error
@ -247,6 +256,13 @@ func TestSoftCAS_CreateCertificate(t *testing.T) {
tmplNoSerial := *testTemplate tmplNoSerial := *testTemplate
tmplNoSerial.SerialNumber = nil tmplNoSerial.SerialNumber = nil
saTemplate := *testSignedTemplate
saTemplate.SignatureAlgorithm = 0
saSigner := &signatureAlgorithmSigner{
Signer: testSigner,
algorithm: x509.PureEd25519,
}
type fields struct { type fields struct {
Issuer *x509.Certificate Issuer *x509.Certificate
Signer crypto.Signer Signer crypto.Signer
@ -267,6 +283,12 @@ func TestSoftCAS_CreateCertificate(t *testing.T) {
Certificate: testSignedTemplate, Certificate: testSignedTemplate,
CertificateChain: []*x509.Certificate{testIssuer}, CertificateChain: []*x509.Certificate{testIssuer},
}, false}, }, false},
{"ok signature algorithm", fields{testIssuer, saSigner}, args{&apiv1.CreateCertificateRequest{
Template: &saTemplate, Lifetime: 24 * time.Hour,
}}, &apiv1.CreateCertificateResponse{
Certificate: testSignedTemplate,
CertificateChain: []*x509.Certificate{testIssuer},
}, false},
{"ok with notBefore", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{ {"ok with notBefore", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{
Template: &tmplNotBefore, Lifetime: 24 * time.Hour, Template: &tmplNotBefore, Lifetime: 24 * time.Hour,
}}, &apiv1.CreateCertificateResponse{ }}, &apiv1.CreateCertificateResponse{
@ -316,6 +338,11 @@ func TestSoftCAS_RenewCertificate(t *testing.T) {
tmplNoSerial := *testTemplate tmplNoSerial := *testTemplate
tmplNoSerial.SerialNumber = nil tmplNoSerial.SerialNumber = nil
saSigner := &signatureAlgorithmSigner{
Signer: testSigner,
algorithm: x509.PureEd25519,
}
type fields struct { type fields struct {
Issuer *x509.Certificate Issuer *x509.Certificate
Signer crypto.Signer Signer crypto.Signer
@ -336,6 +363,12 @@ func TestSoftCAS_RenewCertificate(t *testing.T) {
Certificate: testSignedTemplate, Certificate: testSignedTemplate,
CertificateChain: []*x509.Certificate{testIssuer}, CertificateChain: []*x509.Certificate{testIssuer},
}, false}, }, false},
{"ok signature algorithm", fields{testIssuer, saSigner}, args{&apiv1.RenewCertificateRequest{
Template: testTemplate, Lifetime: 24 * time.Hour,
}}, &apiv1.RenewCertificateResponse{
Certificate: testSignedTemplate,
CertificateChain: []*x509.Certificate{testIssuer},
}, false},
{"fail template", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Lifetime: 24 * time.Hour}}, nil, true}, {"fail template", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Lifetime: 24 * time.Hour}}, nil, true},
{"fail lifetime", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Template: testTemplate}}, nil, true}, {"fail lifetime", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Template: testTemplate}}, nil, true},
{"fail CreateCertificate", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{ {"fail CreateCertificate", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{
@ -425,6 +458,11 @@ func Test_now(t *testing.T) {
func TestSoftCAS_CreateCertificateAuthority(t *testing.T) { func TestSoftCAS_CreateCertificateAuthority(t *testing.T) {
mockNow(t) mockNow(t)
saSigner := &signatureAlgorithmSigner{
Signer: testSigner,
algorithm: x509.PureEd25519,
}
type fields struct { type fields struct {
Issuer *x509.Certificate Issuer *x509.Certificate
Signer crypto.Signer Signer crypto.Signer
@ -467,6 +505,17 @@ func TestSoftCAS_CreateCertificateAuthority(t *testing.T) {
PrivateKey: testSigner, PrivateKey: testSigner,
Signer: testSigner, Signer: testSigner,
}, false}, }, false},
{"ok signature algorithm", fields{nil, nil, &mockKeyManager{signer: saSigner}}, args{&apiv1.CreateCertificateAuthorityRequest{
Type: apiv1.RootCA,
Template: testRootTemplate,
Lifetime: 24 * time.Hour,
}}, &apiv1.CreateCertificateAuthorityResponse{
Name: "Test Root CA",
Certificate: testSignedRootTemplate,
PublicKey: testSignedRootTemplate.PublicKey,
PrivateKey: saSigner,
Signer: saSigner,
}, false},
{"fail template", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ {"fail template", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{
Type: apiv1.RootCA, Type: apiv1.RootCA,
Lifetime: 24 * time.Hour, Lifetime: 24 * time.Hour,

View file

@ -3,6 +3,7 @@ package cloudkms
import ( import (
"context" "context"
"crypto" "crypto"
"crypto/x509"
"log" "log"
"strings" "strings"
"time" "time"
@ -63,6 +64,19 @@ var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
apiv1.ECDSAWithSHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384, apiv1.ECDSAWithSHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384,
} }
var cryptoKeyVersionMapping = map[kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm]x509.SignatureAlgorithm{
kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256: x509.ECDSAWithSHA256,
kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384: x509.ECDSAWithSHA384,
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256: x509.SHA256WithRSA,
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256: x509.SHA256WithRSA,
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256: x509.SHA256WithRSA,
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512: x509.SHA512WithRSA,
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256: x509.SHA256WithRSAPSS,
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256: x509.SHA256WithRSAPSS,
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256: x509.SHA256WithRSAPSS,
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512: x509.SHA512WithRSAPSS,
}
// KeyManagementClient defines the methods on KeyManagementClient that this // KeyManagementClient defines the methods on KeyManagementClient that this
// package will use. This interface will be used for unit testing. // package will use. This interface will be used for unit testing.
type KeyManagementClient interface { type KeyManagementClient interface {

View file

@ -2,6 +2,7 @@ package cloudkms
import ( import (
"crypto" "crypto"
"crypto/x509"
"io" "io"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -13,6 +14,7 @@ import (
type Signer struct { type Signer struct {
client KeyManagementClient client KeyManagementClient
signingKey string signingKey string
algorithm x509.SignatureAlgorithm
publicKey crypto.PublicKey publicKey crypto.PublicKey
} }
@ -40,7 +42,7 @@ func (s *Signer) preloadKey(signingKey string) error {
if err != nil { if err != nil {
return errors.Wrap(err, "cloudKMS GetPublicKey failed") return errors.Wrap(err, "cloudKMS GetPublicKey failed")
} }
s.algorithm = cryptoKeyVersionMapping[response.Algorithm]
s.publicKey, err = pemutil.ParseKey([]byte(response.Pem)) s.publicKey, err = pemutil.ParseKey([]byte(response.Pem))
return err return err
} }
@ -84,3 +86,10 @@ func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]
return response.Signature, nil return response.Signature, nil
} }
// SignatureAlgorithm returns the algorithm that must be specified in a
// certificate to sign. This is specially important to distinguish RSA and
// RSAPSS schemas.
func (s *Signer) SignatureAlgorithm() x509.SignatureAlgorithm {
return s.algorithm
}

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/x509"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -156,3 +157,79 @@ func Test_signer_Sign(t *testing.T) {
}) })
} }
} }
func TestSigner_SignatureAlgorithm(t *testing.T) {
pemBytes, err := ioutil.ReadFile("testdata/pub.pem")
if err != nil {
t.Fatal(err)
}
client := &MockClient{
getPublicKey: func(_ context.Context, req *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) {
var algorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm
switch req.Name {
case "ECDSA-SHA256":
algorithm = kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256
case "ECDSA-SHA384":
algorithm = kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384
case "SHA256-RSA-2048":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256
case "SHA256-RSA-3072":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256
case "SHA256-RSA-4096":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256
case "SHA512-RSA-4096":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512
case "SHA256-RSAPSS-2048":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256
case "SHA256-RSAPSS-3072":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256
case "SHA256-RSAPSS-4096":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256
case "SHA512-RSAPSS-4096":
algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512
}
return &kmspb.PublicKey{
Pem: string(pemBytes),
Algorithm: algorithm,
}, nil
},
}
if err != nil {
t.Fatal(err)
}
type fields struct {
client KeyManagementClient
signingKey string
}
tests := []struct {
name string
fields fields
want x509.SignatureAlgorithm
}{
{"ECDSA-SHA256", fields{client, "ECDSA-SHA256"}, x509.ECDSAWithSHA256},
{"ECDSA-SHA384", fields{client, "ECDSA-SHA384"}, x509.ECDSAWithSHA384},
{"SHA256-RSA-2048", fields{client, "SHA256-RSA-2048"}, x509.SHA256WithRSA},
{"SHA256-RSA-3072", fields{client, "SHA256-RSA-3072"}, x509.SHA256WithRSA},
{"SHA256-RSA-4096", fields{client, "SHA256-RSA-4096"}, x509.SHA256WithRSA},
{"SHA512-RSA-4096", fields{client, "SHA512-RSA-4096"}, x509.SHA512WithRSA},
{"SHA256-RSAPSS-2048", fields{client, "SHA256-RSAPSS-2048"}, x509.SHA256WithRSAPSS},
{"SHA256-RSAPSS-3072", fields{client, "SHA256-RSAPSS-3072"}, x509.SHA256WithRSAPSS},
{"SHA256-RSAPSS-4096", fields{client, "SHA256-RSAPSS-4096"}, x509.SHA256WithRSAPSS},
{"SHA512-RSAPSS-4096", fields{client, "SHA512-RSAPSS-4096"}, x509.SHA512WithRSAPSS},
{"unknown", fields{client, "UNKNOWN"}, x509.UnknownSignatureAlgorithm},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
signer, err := NewSigner(tt.fields.client, tt.fields.signingKey)
if err != nil {
t.Errorf("NewSigner() error = %v", err)
}
if got := signer.SignatureAlgorithm(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Signer.SignatureAlgorithm() = %v, want %v", got, tt.want)
}
})
}
}