Allow o specify an hsm using the uri.

This commit is contained in:
Mariano Cano 2021-10-07 16:18:36 -07:00
parent f1ef3fb351
commit abdb56065d
6 changed files with 69 additions and 17 deletions

View file

@ -113,11 +113,13 @@ type KeyVaultClient interface {
// //
// - azurekms:name=key-name;vault=vault-name // - azurekms:name=key-name;vault=vault-name
// - azurekms:name=key-name;vault=vault-name?version=key-version // - azurekms:name=key-name;vault=vault-name?version=key-version
// - azurekms:name=key-name;vault=vault-name?hsm=true
// //
// The scheme is "azurekms"; "name" is the key name; "vault" is the key vault // The scheme is "azurekms"; "name" is the key name; "vault" is the key vault
// name where the key is located; "version" is an optional parameter that // name where the key is located; "version" is an optional parameter that
// defines the version of they key, if version is not given, the latest one will // defines the version of they key, if version is not given, the latest one will
// be used. // be used; "hsm" defines if an HSM want to be used for this key, this is
// specially useful when this is used from `step`.
// //
// TODO(mariano): The implementation is using /services/keyvault/v7.1/keyvault // TODO(mariano): The implementation is using /services/keyvault/v7.1/keyvault
// package, at some point Azure might create a keyvault client with all the // package, at some point Azure might create a keyvault client with all the
@ -165,7 +167,7 @@ func (k *KeyVault) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
} }
vault, name, version, err := parseKeyName(req.Name) vault, name, version, _, err := parseKeyName(req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -187,11 +189,18 @@ func (k *KeyVault) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespo
return nil, errors.New("createKeyRequest 'name' cannot be empty") return nil, errors.New("createKeyRequest 'name' cannot be empty")
} }
vault, name, _, err := parseKeyName(req.Name) vault, name, _, hsm, err := parseKeyName(req.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Override protection level to HSM only if it's not specified, and is given
// in the uri.
protectionLevel := req.ProtectionLevel
if protectionLevel == apiv1.UnspecifiedProtectionLevel && hsm {
protectionLevel = apiv1.HSM
}
kt, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] kt, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
if !ok { if !ok {
return nil, errors.Errorf("keyVault does not support signature algorithm '%s'", req.SignatureAlgorithm) return nil, errors.Errorf("keyVault does not support signature algorithm '%s'", req.SignatureAlgorithm)
@ -216,7 +225,7 @@ func (k *KeyVault) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespo
defer cancel() defer cancel()
resp, err := k.baseClient.CreateKey(ctx, vaultBaseURL(vault), name, keyvault.KeyCreateParameters{ resp, err := k.baseClient.CreateKey(ctx, vaultBaseURL(vault), name, keyvault.KeyCreateParameters{
Kty: kt.KeyType(req.ProtectionLevel), Kty: kt.KeyType(protectionLevel),
KeySize: keySize, KeySize: keySize,
Curve: kt.Curve, Curve: kt.Curve,
KeyOps: &[]keyvault.JSONWebKeyOperation{ KeyOps: &[]keyvault.JSONWebKeyOperation{

View file

@ -202,11 +202,13 @@ func TestKeyVault_CreateKey(t *testing.T) {
}{ }{
{"P-256", keyvault.EC, nil, keyvault.P256, ecJWK}, {"P-256", keyvault.EC, nil, keyvault.P256, ecJWK},
{"P-256 HSM", keyvault.ECHSM, nil, keyvault.P256, ecJWK}, {"P-256 HSM", keyvault.ECHSM, nil, keyvault.P256, ecJWK},
{"P-256 HSM (uri)", keyvault.ECHSM, nil, keyvault.P256, ecJWK},
{"P-256 Default", keyvault.EC, nil, keyvault.P256, ecJWK}, {"P-256 Default", keyvault.EC, nil, keyvault.P256, ecJWK},
{"P-384", keyvault.EC, nil, keyvault.P384, ecJWK}, {"P-384", keyvault.EC, nil, keyvault.P384, ecJWK},
{"P-521", keyvault.EC, nil, keyvault.P521, ecJWK}, {"P-521", keyvault.EC, nil, keyvault.P521, ecJWK},
{"RSA 0", keyvault.RSA, &value3072, "", rsaJWK}, {"RSA 0", keyvault.RSA, &value3072, "", rsaJWK},
{"RSA 0 HSM", keyvault.RSAHSM, &value3072, "", rsaJWK}, {"RSA 0 HSM", keyvault.RSAHSM, &value3072, "", rsaJWK},
{"RSA 0 HSM (uri)", keyvault.RSAHSM, &value3072, "", rsaJWK},
{"RSA 2048", keyvault.RSA, &value2048, "", rsaJWK}, {"RSA 2048", keyvault.RSA, &value2048, "", rsaJWK},
{"RSA 3072", keyvault.RSA, &value3072, "", rsaJWK}, {"RSA 3072", keyvault.RSA, &value3072, "", rsaJWK},
{"RSA 4096", keyvault.RSA, &value4096, "", rsaJWK}, {"RSA 4096", keyvault.RSA, &value4096, "", rsaJWK},
@ -269,6 +271,16 @@ func TestKeyVault_CreateKey(t *testing.T) {
SigningKey: "azurekms:name=my-key;vault=my-vault", SigningKey: "azurekms:name=my-key;vault=my-vault",
}, },
}, false}, }, false},
{"ok P-256 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{
Name: "azurekms:vault=my-vault;name=my-key?hsm=true",
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
}}, &apiv1.CreateKeyResponse{
Name: "azurekms:name=my-key;vault=my-vault",
PublicKey: ecPub,
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: "azurekms:name=my-key;vault=my-vault",
},
}, false},
{"ok P-256 Default", fields{client}, args{&apiv1.CreateKeyRequest{ {"ok P-256 Default", fields{client}, args{&apiv1.CreateKeyRequest{
Name: "azurekms:vault=my-vault;name=my-key", Name: "azurekms:vault=my-vault;name=my-key",
}}, &apiv1.CreateKeyResponse{ }}, &apiv1.CreateKeyResponse{
@ -322,6 +334,17 @@ func TestKeyVault_CreateKey(t *testing.T) {
SigningKey: "azurekms:name=my-key;vault=my-vault", SigningKey: "azurekms:name=my-key;vault=my-vault",
}, },
}, false}, }, false},
{"ok RSA 0 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{
Name: "azurekms:vault=my-vault;name=my-key;hsm=true",
Bits: 0,
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
}}, &apiv1.CreateKeyResponse{
Name: "azurekms:name=my-key;vault=my-vault",
PublicKey: rsaPub,
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: "azurekms:name=my-key;vault=my-vault",
},
}, false},
{"ok RSA 2048", fields{client}, args{&apiv1.CreateKeyRequest{ {"ok RSA 2048", fields{client}, args{&apiv1.CreateKeyRequest{
Name: "azurekms:vault=my-vault;name=my-key", Name: "azurekms:vault=my-vault;name=my-key",
Bits: 2048, Bits: 2048,

View file

@ -25,7 +25,7 @@ type Signer struct {
// NewSigner creates a new signer using a key in the AWS KMS. // NewSigner creates a new signer using a key in the AWS KMS.
func NewSigner(client KeyVaultClient, signingKey string) (crypto.Signer, error) { func NewSigner(client KeyVaultClient, signingKey string) (crypto.Signer, error) {
vault, name, version, err := parseKeyName(signingKey) vault, name, version, _, err := parseKeyName(signingKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -221,6 +221,12 @@ func TestSigner_Sign(t *testing.T) {
{"fail sign length", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{ {"fail sign length", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{
Result: &rsaSHA256ResultSig, Result: &rsaSHA256ResultSig,
}, nil}, }, nil},
{"fail base64", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{
Result: func() *string {
v := "😎"
return &v
}(),
}, nil},
} }
for _, e := range expects { for _, e := range expects {
value := base64.RawURLEncoding.EncodeToString(e.digest) value := base64.RawURLEncoding.EncodeToString(e.digest)
@ -291,6 +297,9 @@ func TestSigner_Sign(t *testing.T) {
{"fail sign length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ {"fail sign length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
rand.Reader, p256Digest[:], crypto.SHA256, rand.Reader, p256Digest[:], crypto.SHA256,
}, nil, true}, }, nil, true},
{"fail base64", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
rand.Reader, p256Digest[:], crypto.SHA256,
}, nil, true},
{"fail RSA-PSS salt length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{ {"fail RSA-PSS salt length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{
rand.Reader, rsaPSSSHA256Digest[:], &rsa.PSSOptions{ rand.Reader, rsaPSSSHA256Digest[:], &rsa.PSSOptions{
SaltLength: 64, SaltLength: 64,

View file

@ -42,11 +42,15 @@ func getKeyName(vault, name string, bundle keyvault.KeyBundle) string {
// parseKeyName returns the key vault, name and version from URIs like: // parseKeyName returns the key vault, name and version from URIs like:
// //
// - azurekms:vault=key-vault;name=key-name // - azurekms:vault=key-vault;name=key-name
// - azurekms:vault=key-vault;name=key-name;id=key-id // - azurekms:vault=key-vault;name=key-name?version=key-id
// - azurekms:vault=key-vault;name=key-name?version=key-id&hsm=true
// //
// The key-id defines the version of the key, if it is not passed the latest // The key-id defines the version of the key, if it is not passed the latest
// version will be used. // version will be used.
func parseKeyName(rawURI string) (vault, name, version string, err error) { //
// HSM can also be passed to define the protection level if this is not given in
// CreateQuery.
func parseKeyName(rawURI string) (vault, name, version string, hsm bool, err error) {
var u *uri.URI var u *uri.URI
u, err = uri.ParseWithScheme("azurekms", rawURI) u, err = uri.ParseWithScheme("azurekms", rawURI)
@ -63,6 +67,7 @@ func parseKeyName(rawURI string) (vault, name, version string, err error) {
return return
} }
version = u.Get("version") version = u.Get("version")
hsm = u.GetBool("hsm")
return return
} }

View file

@ -51,21 +51,24 @@ func Test_parseKeyName(t *testing.T) {
wantVault string wantVault string
wantName string wantName string
wantVersion string wantVersion string
wantHsm bool
wantErr bool wantErr bool
}{ }{
{"ok", args{"azurekms:name=my-key;vault=my-vault?version=my-version"}, "my-vault", "my-key", "my-version", false}, {"ok", args{"azurekms:name=my-key;vault=my-vault?version=my-version"}, "my-vault", "my-key", "my-version", false, false},
{"ok opaque version", args{"azurekms:name=my-key;vault=my-vault;version=my-version"}, "my-vault", "my-key", "my-version", false}, {"ok opaque version", args{"azurekms:name=my-key;vault=my-vault;version=my-version"}, "my-vault", "my-key", "my-version", false, false},
{"ok no version", args{"azurekms:name=my-key;vault=my-vault"}, "my-vault", "my-key", "", false}, {"ok no version", args{"azurekms:name=my-key;vault=my-vault"}, "my-vault", "my-key", "", false, false},
{"fail scheme", args{"azure:name=my-key;vault=my-vault"}, "", "", "", true}, {"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true"}, "my-vault", "my-key", "", true, false},
{"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault"}, "", "", "", true}, {"ok hsm false", args{"azurekms:name=my-key;vault=my-vault?hsm=false"}, "my-vault", "my-key", "", false, false},
{"fail no name", args{"azurekms:vault=my-vault"}, "", "", "", true}, {"fail scheme", args{"azure:name=my-key;vault=my-vault"}, "", "", "", false, true},
{"fail empty name", args{"azurekms:name=;vault=my-vault"}, "", "", "", true}, {"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault"}, "", "", "", false, true},
{"fail no vault", args{"azurekms:name=my-key"}, "", "", "", true}, {"fail no name", args{"azurekms:vault=my-vault"}, "", "", "", false, true},
{"fail empty vault", args{"azurekms:name=my-key;vault="}, "", "", "", true}, {"fail empty name", args{"azurekms:name=;vault=my-vault"}, "", "", "", false, true},
{"fail no vault", args{"azurekms:name=my-key"}, "", "", "", false, true},
{"fail empty vault", args{"azurekms:name=my-key;vault="}, "", "", "", false, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotVault, gotName, gotVersion, err := parseKeyName(tt.args.rawURI) gotVault, gotName, gotVersion, gotHsm, err := parseKeyName(tt.args.rawURI)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("parseKeyName() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("parseKeyName() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -79,6 +82,9 @@ func Test_parseKeyName(t *testing.T) {
if gotVersion != tt.wantVersion { if gotVersion != tt.wantVersion {
t.Errorf("parseKeyName() gotVersion = %v, want %v", gotVersion, tt.wantVersion) t.Errorf("parseKeyName() gotVersion = %v, want %v", gotVersion, tt.wantVersion)
} }
if gotHsm != tt.wantHsm {
t.Errorf("parseKeyName() gotHsm = %v, want %v", gotHsm, tt.wantHsm)
}
}) })
} }
} }