-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add path for retrieving the public key (#5)
* Add path for retrieving the public key * Use --get for curl
- Loading branch information
1 parent
a7d4151
commit 6cd9918
Showing
5 changed files
with
319 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package gcpkms | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/errwrap" | ||
"github.com/hashicorp/vault/logical" | ||
"github.com/hashicorp/vault/logical/framework" | ||
|
||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" | ||
) | ||
|
||
func (b *backend) pathPubkey() *framework.Path { | ||
return &framework.Path{ | ||
Pattern: "pubkey/" + framework.GenericNameRegex("key"), | ||
|
||
HelpSynopsis: "Retrieve the public key associated with the named key", | ||
HelpDescription: ` | ||
Retrieve the PEM-encoded Google Cloud KMS public key associated with the Vault | ||
named key. The key will only be available if the key is asymmetric. | ||
`, | ||
|
||
Fields: map[string]*framework.FieldSchema{ | ||
"key": &framework.FieldSchema{ | ||
Type: framework.TypeString, | ||
Description: ` | ||
Name of the key for which to get the public key. This key must already exist in | ||
Vault and Google Cloud KMS. | ||
`, | ||
}, | ||
|
||
"key_version": &framework.FieldSchema{ | ||
Type: framework.TypeInt, | ||
Description: ` | ||
Integer version of the crypto key version from which to exact the public key. | ||
This field is required. | ||
`, | ||
}, | ||
}, | ||
|
||
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
logical.ReadOperation: withFieldValidator(b.pathPubkeyRead), | ||
}, | ||
} | ||
} | ||
|
||
// pathPubkeyRead corresponds to GET gcpkms/pubkey/:key and is used to read the | ||
// public key contents of the crypto key version. | ||
func (b *backend) pathPubkeyRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||
key := d.Get("key").(string) | ||
keyVersion := d.Get("key_version").(int) | ||
|
||
if keyVersion == 0 { | ||
return nil, errMissingFields("key_version") | ||
} | ||
|
||
k, err := b.Key(ctx, req.Storage, key) | ||
if err != nil { | ||
if err == ErrKeyNotFound { | ||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||
} | ||
return nil, err | ||
} | ||
|
||
if k.MinVersion > 0 && keyVersion < k.MinVersion { | ||
resp := fmt.Sprintf("requested version %d is less than minimum allowed version of %d", | ||
keyVersion, k.MinVersion) | ||
return logical.ErrorResponse(resp), logical.ErrPermissionDenied | ||
} | ||
|
||
if k.MaxVersion > 0 && keyVersion > k.MaxVersion { | ||
resp := fmt.Sprintf("requested version %d is greater than maximum allowed version of %d", | ||
keyVersion, k.MaxVersion) | ||
return logical.ErrorResponse(resp), logical.ErrPermissionDenied | ||
} | ||
|
||
kmsClient, closer, err := b.KMSClient(req.Storage) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer closer() | ||
|
||
pk, err := kmsClient.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ | ||
Name: fmt.Sprintf("%s/cryptoKeyVersions/%d", k.CryptoKeyID, keyVersion), | ||
}) | ||
if err != nil { | ||
return nil, errwrap.Wrapf("failed to get public key: {{err}}", err) | ||
} | ||
|
||
return &logical.Response{ | ||
Data: map[string]interface{}{ | ||
"pem": pk.Pem, | ||
"algorithm": algorithmToString(pk.Algorithm), | ||
}, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package gcpkms | ||
|
||
import ( | ||
"context" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/hashicorp/vault/logical" | ||
|
||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" | ||
) | ||
|
||
func TestPathPubkey_Read(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("field_validation", func(t *testing.T) { | ||
t.Parallel() | ||
testFieldValidation(t, logical.ReadOperation, "pubkey/my-key") | ||
}) | ||
|
||
t.Run("asymmetric_decrypt", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
algorithms := []kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ | ||
kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_2048_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_3072_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_4096_SHA256, | ||
} | ||
|
||
for _, algo := range algorithms { | ||
algo := algo | ||
name := strings.ToLower(algo.String()) | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
cryptoKey, cleanup := testCreateKMSCryptoKeyAsymmetricDecrypt(t, algo) | ||
defer cleanup() | ||
|
||
b, storage := testBackend(t) | ||
|
||
ctx := context.Background() | ||
if err := storage.Put(ctx, &logical.StorageEntry{ | ||
Key: "keys/my-key", | ||
Value: []byte(`{"name":"my-key", "crypto_key_id":"` + cryptoKey + `"}`), | ||
}); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Get the public key | ||
resp, err := b.HandleRequest(ctx, &logical.Request{ | ||
Storage: storage, | ||
Operation: logical.ReadOperation, | ||
Path: "pubkey/my-key", | ||
Data: map[string]interface{}{ | ||
"key_version": 1, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Verify it's a pem public key (this is kinda testing KMS, but it's | ||
// a good test to ensure the API doesn't change). | ||
pemStr, ok := resp.Data["pem"].(string) | ||
if !ok { | ||
t.Fatal("missing pem") | ||
} | ||
|
||
// Extract the PEM-encoded data block | ||
block, _ := pem.Decode([]byte(pemStr)) | ||
if block == nil { | ||
t.Fatalf("not pem: %s", pemStr) | ||
} | ||
|
||
// Decode the public key | ||
if _, err := x509.ParsePKIXPublicKey(block.Bytes); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
}) | ||
|
||
t.Run("asymmetric_sign", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
algorithms := []kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, | ||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, | ||
kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, | ||
kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384, | ||
} | ||
|
||
for _, algo := range algorithms { | ||
algo := algo | ||
name := strings.ToLower(algo.String()) | ||
|
||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
cryptoKey, cleanup := testCreateKMSCryptoKeyAsymmetricSign(t, algo) | ||
defer cleanup() | ||
|
||
b, storage := testBackend(t) | ||
|
||
ctx := context.Background() | ||
if err := storage.Put(ctx, &logical.StorageEntry{ | ||
Key: "keys/my-key", | ||
Value: []byte(`{"name":"my-key", "crypto_key_id":"` + cryptoKey + `"}`), | ||
}); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Get the public key | ||
resp, err := b.HandleRequest(ctx, &logical.Request{ | ||
Storage: storage, | ||
Operation: logical.ReadOperation, | ||
Path: "pubkey/my-key", | ||
Data: map[string]interface{}{ | ||
"key_version": 1, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Verify it's a pem public key (this is kinda testing KMS, but it's | ||
// a good test to ensure the API doesn't change). | ||
pemStr, ok := resp.Data["pem"].(string) | ||
if !ok { | ||
t.Fatal("missing pem") | ||
} | ||
|
||
// Extract the PEM-encoded data block | ||
block, _ := pem.Decode([]byte(pemStr)) | ||
if block == nil { | ||
t.Fatalf("not pem: %s", pemStr) | ||
} | ||
|
||
// Decode the public key | ||
if _, err := x509.ParsePKIXPublicKey(block.Bytes); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters