2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-03-22 11:03:15 -04:00
|
|
|
package azure
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
2023-01-11 04:08:57 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/keyservice/internal/config"
|
|
|
|
"github.com/edgelesssys/constellation/v2/keyservice/internal/storage"
|
|
|
|
"github.com/edgelesssys/constellation/v2/keyservice/kms"
|
|
|
|
"github.com/edgelesssys/constellation/v2/keyservice/kms/util"
|
2022-03-22 11:03:15 -04:00
|
|
|
)
|
|
|
|
|
2022-03-24 13:00:17 -04:00
|
|
|
type hsmClientAPI interface {
|
2022-07-27 16:02:33 -04:00
|
|
|
CreateKey(ctx context.Context, name string, parameters azkeys.CreateKeyParameters, options *azkeys.CreateKeyOptions) (azkeys.CreateKeyResponse, error)
|
|
|
|
ImportKey(ctx context.Context, name string, parameters azkeys.ImportKeyParameters, options *azkeys.ImportKeyOptions) (azkeys.ImportKeyResponse, error)
|
|
|
|
GetKey(ctx context.Context, name string, version string, options *azkeys.GetKeyOptions) (azkeys.GetKeyResponse, error)
|
|
|
|
UnwrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.UnwrapKeyOptions) (azkeys.UnwrapKeyResponse, error)
|
|
|
|
WrapKey(ctx context.Context, name string, version string, parameters azkeys.KeyOperationsParameters, options *azkeys.WrapKeyOptions) (azkeys.WrapKeyResponse, error)
|
2022-03-24 13:00:17 -04:00
|
|
|
}
|
|
|
|
|
2022-07-08 04:59:59 -04:00
|
|
|
// HSMDefaultCloud is the suffix for HSM Vaults.
|
2022-03-22 11:03:15 -04:00
|
|
|
const HSMDefaultCloud VaultSuffix = ".managedhsm.azure.net/"
|
|
|
|
|
|
|
|
// HSMClient implements the CloudKMS interface for Azure managed HSM.
|
|
|
|
type HSMClient struct {
|
2022-07-27 16:02:33 -04:00
|
|
|
credentials azcore.TokenCredential
|
|
|
|
client hsmClientAPI
|
|
|
|
storage kms.Storage
|
|
|
|
vaultURL string
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewHSM initializes a KMS client for Azure manged HSM Key Vault.
|
|
|
|
func NewHSM(ctx context.Context, vaultName string, store kms.Storage, opts *Opts) (*HSMClient, error) {
|
|
|
|
if opts == nil {
|
|
|
|
opts = &Opts{}
|
|
|
|
}
|
2022-10-18 06:08:59 -04:00
|
|
|
cred, err := azidentity.NewDefaultAzureCredential(opts.Credentials)
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("loading credentials: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
vaultURL := vaultPrefix + vaultName + string(HSMDefaultCloud)
|
2022-11-09 06:09:22 -05:00
|
|
|
client, err := azkeys.NewClient(vaultURL, cred, opts.Keys)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating azure key vault client: %w", err)
|
|
|
|
}
|
2022-03-22 11:03:15 -04:00
|
|
|
|
2022-03-25 07:50:16 -04:00
|
|
|
// `azkeys.NewClient()` does not error if the vault is non existent
|
2022-03-22 11:03:15 -04:00
|
|
|
// Test here if we can reach the vault, and error otherwise
|
2022-07-27 16:02:33 -04:00
|
|
|
pager := client.NewListKeysPager(&azkeys.ListKeysOptions{MaxResults: to.Ptr[int32](2)})
|
|
|
|
if _, err := pager.NextPage(ctx); err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return nil, fmt.Errorf("HSM not reachable: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if store == nil {
|
|
|
|
store = storage.NewMemMapStorage()
|
|
|
|
}
|
|
|
|
|
2022-03-24 13:00:17 -04:00
|
|
|
return &HSMClient{
|
2022-07-27 16:02:33 -04:00
|
|
|
vaultURL: vaultURL,
|
|
|
|
client: client,
|
|
|
|
credentials: cred,
|
|
|
|
storage: store,
|
2022-03-24 13:00:17 -04:00
|
|
|
}, nil
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateKEK creates a new Key Encryption Key using Azure managed HSM.
|
|
|
|
//
|
|
|
|
// If no key material is provided, a new key is generated by the HSM, otherwise the key material is used to import the key.
|
|
|
|
func (c *HSMClient) CreateKEK(ctx context.Context, keyID string, key []byte) error {
|
|
|
|
if len(key) == 0 {
|
2022-07-27 16:02:33 -04:00
|
|
|
if _, err := c.client.CreateKey(ctx, keyID, azkeys.CreateKeyParameters{
|
|
|
|
Kty: to.Ptr(azkeys.JSONWebKeyTypeOctHSM),
|
|
|
|
KeySize: to.Ptr[int32](config.SymmetricKeyLength * 8),
|
|
|
|
Tags: toAzureTags(config.KmsTags),
|
|
|
|
}, &azkeys.CreateKeyOptions{}); err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return fmt.Errorf("creating new KEK: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
jwk := azkeys.JSONWebKey{
|
|
|
|
K: key,
|
|
|
|
KeyOps: []*string{
|
2022-07-27 16:02:33 -04:00
|
|
|
to.Ptr("wrapKey"),
|
|
|
|
to.Ptr("unwrapKey"),
|
2022-03-22 11:03:15 -04:00
|
|
|
},
|
2022-07-27 16:02:33 -04:00
|
|
|
Kty: to.Ptr(azkeys.JSONWebKeyTypeOctHSM),
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-27 16:02:33 -04:00
|
|
|
importParams := azkeys.ImportKeyParameters{
|
|
|
|
HSM: to.Ptr(true),
|
2022-03-22 11:03:15 -04:00
|
|
|
KeyAttributes: &azkeys.KeyAttributes{
|
2022-07-27 16:02:33 -04:00
|
|
|
Enabled: to.Ptr(true),
|
2022-03-22 11:03:15 -04:00
|
|
|
},
|
2022-07-27 16:02:33 -04:00
|
|
|
Tags: toAzureTags(config.KmsTags),
|
|
|
|
Key: &jwk,
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
|
2022-07-27 16:02:33 -04:00
|
|
|
if _, err := c.client.ImportKey(ctx, keyID, importParams, &azkeys.ImportKeyOptions{}); err != nil {
|
2022-03-22 11:03:15 -04:00
|
|
|
return fmt.Errorf("importing KEK to Azure HSM: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDEK loads an encrypted DEK from storage and unwraps it using an HSM-backed key.
|
|
|
|
func (c *HSMClient) GetDEK(ctx context.Context, kekID string, keyID string, dekSize int) ([]byte, error) {
|
|
|
|
encryptedDEK, err := c.storage.Get(ctx, keyID)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, storage.ErrDEKUnset) {
|
|
|
|
return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the DEK does not exist we generate a new random DEK and save it to storage
|
|
|
|
newDEK, err := util.GetRandomKey(dekSize)
|
|
|
|
if err != nil {
|
2022-06-09 10:04:30 -04:00
|
|
|
return nil, fmt.Errorf("key generation: %w", err)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
|
|
|
if err := c.putDEK(ctx, kekID, keyID, newDEK); err != nil {
|
|
|
|
return nil, fmt.Errorf("creating new DEK: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return newDEK, nil
|
|
|
|
}
|
|
|
|
|
2022-07-27 16:02:33 -04:00
|
|
|
params := azkeys.KeyOperationsParameters{
|
|
|
|
Algorithm: to.Ptr(azkeys.JSONWebKeyEncryptionAlgorithmA256KW),
|
|
|
|
Value: encryptedDEK,
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-27 16:02:33 -04:00
|
|
|
res, err := c.client.UnwrapKey(ctx, kekID, "", params, &azkeys.UnwrapKeyOptions{})
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unwrapping key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.Result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// putDEK wraps a key using an HSM-backed key and saves it to storage.
|
|
|
|
func (c *HSMClient) putDEK(ctx context.Context, kekID, keyID string, plainDEK []byte) error {
|
2022-07-27 16:02:33 -04:00
|
|
|
params := azkeys.KeyOperationsParameters{
|
|
|
|
Algorithm: to.Ptr(azkeys.JSONWebKeyEncryptionAlgorithmA256KW),
|
|
|
|
Value: plainDEK,
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-27 16:02:33 -04:00
|
|
|
res, err := c.client.WrapKey(ctx, kekID, "", params, &azkeys.WrapKeyOptions{})
|
2022-03-22 11:03:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("wrapping key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.storage.Put(ctx, keyID, res.Result)
|
|
|
|
}
|
|
|
|
|
2022-07-27 16:02:33 -04:00
|
|
|
// toAzureTags converts a map of tags to map of tag pointers.
|
|
|
|
func toAzureTags(tags map[string]string) map[string]*string {
|
|
|
|
tagsOut := make(map[string]*string)
|
|
|
|
for k, v := range tags {
|
|
|
|
tagsOut[k] = to.Ptr(v)
|
2022-03-22 11:03:15 -04:00
|
|
|
}
|
2022-07-27 16:02:33 -04:00
|
|
|
return tagsOut
|
2022-03-24 13:00:17 -04:00
|
|
|
}
|