Skip to content

Commit

Permalink
feat: support of key's metadata for localkms (#21)
Browse files Browse the repository at this point in the history
Signed-off-by: Volodymyr Kit <[email protected]>
  • Loading branch information
justakit authored Sep 19, 2024
1 parent 38b6c31 commit 5467496
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 54 deletions.
38 changes: 34 additions & 4 deletions kms/localkms/localkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (l *LocalKMS) Create(kt kmsapi.KeyType, opts ...kmsapi.KeyOpts) (string, in
return "", nil, fmt.Errorf("create: failed to create new keyset handle: %w", err)
}

keyID, err := l.storeKeySet(kh, kt)
keyID, err := l.storeKeySet(kh, kt, opts...)
if err != nil {
return "", nil, fmt.Errorf("create: failed to store keyset: %w", err)
}
Expand All @@ -122,6 +122,15 @@ func (l *LocalKMS) Get(keyID string) (interface{}, error) {
return l.getKeySet(keyID)
}

// GetWithOpts key handle for the given keyID
// Returns:
// - handle instance (to private key)
// - metadata if any saved
// - error if failure
func (l *LocalKMS) GetWithOpts(keyID string, opts ...kmsapi.ExportKeyOpts) (any, map[string]any, error) {
return l.getKeySetWithOpts(keyID, opts...)
}

// Rotate a key referenced by keyID and return a new handle of a keyset including old key and
// new key with type kt. It also returns the updated keyID as the first return value
// Returns:
Expand Down Expand Up @@ -164,7 +173,7 @@ func (l *LocalKMS) Rotate(kt kmsapi.KeyType, keyID string, opts ...kmsapi.KeyOpt
return newID, updatedKH, nil
}

func (l *LocalKMS) storeKeySet(kh *keyset.Handle, kt kmsapi.KeyType) (string, error) {
func (l *LocalKMS) storeKeySet(kh *keyset.Handle, kt kmsapi.KeyType, opts ...kmsapi.KeyOpts) (string, error) {
var (
kid string
err error
Expand Down Expand Up @@ -192,13 +201,19 @@ func (l *LocalKMS) storeKeySet(kh *keyset.Handle, kt kmsapi.KeyType) (string, er
return "", fmt.Errorf("storeKeySet: failed to write json key to buffer: %w", err)
}

keyOpts := kmsapi.NewKeyOpt()

for _, opt := range opts {
opt(keyOpts)
}

// asymmetric keys are JWK thumbprints of the public key, base64URL encoded stored in kid.
// symmetric keys will have a randomly generated key ID (where kid is empty)
if kid != "" {
return writeToStore(l.store, buf, kmsapi.WithKeyID(kid))
return writeToStore(l.store, buf, kmsapi.WithKeyID(kid), kmsapi.ImportWithMetadata(keyOpts.Metadata()))
}

return writeToStore(l.store, buf)
return writeToStore(l.store, buf, kmsapi.ImportWithMetadata(keyOpts.Metadata()))
}

func writeToStore(store kmsapi.Store, buf *bytes.Buffer, opts ...kmsapi.PrivateKeyOpts) (string, error) {
Expand Down Expand Up @@ -228,6 +243,21 @@ func (l *LocalKMS) getKeySet(id string) (*keyset.Handle, error) {
return kh, nil
}

func (l *LocalKMS) getKeySetWithOpts(id string, opts ...kmsapi.ExportKeyOpts) (*keyset.Handle, map[string]any, error) {
localDBReader := newReader(l.store, id, opts...)

jsonKeysetReader := keyset.NewJSONReader(localDBReader)

// Read reads the encrypted keyset handle back from the io.reader implementation
// and decrypts it using primaryKeyEnvAEAD.
kh, err := keyset.Read(jsonKeysetReader, l.primaryKeyEnvAEAD)
if err != nil {
return nil, nil, fmt.Errorf("getKeySet: failed to read json keyset from reader: %w", err)
}

return kh, localDBReader.metadata, nil
}

// ExportPubKeyBytes will fetch a key referenced by id then gets its public key in raw bytes and returns it.
// The key must be an asymmetric key.
// Returns:
Expand Down
57 changes: 43 additions & 14 deletions kms/localkms/localkms_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,67 @@ import (
"fmt"

"github.com/trustbloc/kms-go/spi/kms"
kmsapi "github.com/trustbloc/kms-go/spi/kms"
)

// newReader will create a new local storage storeReader of a keyset with ID value = keysetID
// it is used internally by local kms.
func newReader(store kms.Store, keysetID string) *storeReader {
func newReader(store kms.Store, keysetID string, opts ...kmsapi.ExportKeyOpts) *storeReader {
pOpts := kmsapi.NewExportOpt()

for _, opt := range opts {
opt(pOpts)
}

return &storeReader{
storage: store,
keysetID: keysetID,
storage: store,
keysetID: keysetID,
getMetadata: pOpts.GetMetadata(),
}
}

// storeReader struct to load a keyset from a local storage.
type storeReader struct {
buf *bytes.Buffer
storage kms.Store
keysetID string
buf *bytes.Buffer
storage kms.Store
keysetID string
getMetadata bool
metadata map[string]any
}

// Read the keyset from local storage into p.
func (l *storeReader) Read(p []byte) (int, error) {
if l.buf == nil {
if l.keysetID == "" {
return 0, fmt.Errorf("keysetID is not set")
}
if l.buf != nil {
return l.buf.Read(p)
}

if l.keysetID == "" {
return 0, fmt.Errorf("keysetID is not set")
}

data, err := l.storage.Get(l.keysetID)
if err != nil {
return 0, fmt.Errorf("cannot read data for keysetID %s: %w", l.keysetID, err)
var data []byte

var err error

var metadata map[string]any

if l.getMetadata {
metadataStorage, ok := l.storage.(kmsapi.StoreWithMetadata)
if !ok {
return 0, fmt.Errorf("requested to get 'metadata', but storage doesn't support it")
}

l.buf = bytes.NewBuffer(data)
data, metadata, err = metadataStorage.GetWithMetadata(l.keysetID)
} else {
data, err = l.storage.Get(l.keysetID)
}

if err != nil {
return 0, fmt.Errorf("cannot read data for keysetID %s: %w", l.keysetID, err)
}

l.metadata = metadata
l.buf = bytes.NewBuffer(data)

return l.buf.Read(p)
}
14 changes: 13 additions & 1 deletion kms/localkms/localkms_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func newWriter(kmsStore kmsapi.Store, opts ...kmsapi.PrivateKeyOpts) *storeWrite
return &storeWriter{
storage: kmsStore,
requestedKeysetID: pOpts.KsID(),
metadata: pOpts.Metadata(),
}
}

Expand All @@ -39,6 +40,7 @@ type storeWriter struct {
storage kmsapi.Store
//
requestedKeysetID string
metadata map[string]any
// KeysetID is set when Write() is called
KeysetID string
}
Expand All @@ -61,7 +63,17 @@ func (l *storeWriter) Write(p []byte) (int, error) {
}
}

err = l.storage.Put(ksID, p)
if len(l.metadata) != 0 {
metadataStorage, ok := l.storage.(kmsapi.StoreWithMetadata)
if !ok {
return 0, fmt.Errorf("requested to save 'metadata', but storage doesn't support it")
}

err = metadataStorage.PutWithMetadata(ksID, p, l.metadata)
} else {
err = l.storage.Put(ksID, p)
}

if err != nil {
return 0, err
}
Expand Down
24 changes: 13 additions & 11 deletions kms/webkms/testdata/ec-pubCert1.pem
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
-----BEGIN CERTIFICATE-----
MIIB9zCCAZ2gAwIBAgIUOpIXXroHVWLOlicAo9VtdElkfRwwCgYIKoZIzj0EAwIw
VTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQKDB9FeGFtcGxlIElu
dGVybmV0IENBIEluYy46Q0EgU2VjMQ8wDQYDVQQLDAZDQSBTZWMwHhcNMjMwNDE5
MTIzMTExWhcNMjQwNDE4MTIzMTExWjB5MQswCQYDVQQGEwJDQTELMAkGA1UECAwC
T04xKDAmBgNVBAoMH0V4YW1wbGUgSW5jLjphcmllcy1mcmFtZXdvcmstZ28xGzAZ
BgNVBAsMEmFyaWVzLWZyYW1ld29yay1nbzEWMBQGA1UEAwwNKi5leGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL4wNC9Jo5zKerVaBNMzzgun79vk
pEyVnGDld7ss10oR41IL8VCFSURb4VlXZZwGLZ2/Kj3Du3xXKsYcz0l2bzejJzAl
MCMGA1UdEQQcMBqCDSouZXhhbXBsZS5jb22CCWxvY2FsaG9zdDAKBggqhkjOPQQD
AgNIADBFAiEAgVXfMMZSe5OP9a13cicbCw2d0EKh5/UqLLp3xk4ycpoCIG5XUR6g
ekAHv7e+ZapiSTVVhSRWU5DV00vAil0NI3Ha
MIICUTCCAfagAwIBAgIJAO0O74K+mvU5MAoGCCqGSM49BAMCMFUxCzAJBgNVBAYT
AkNBMQswCQYDVQQIDAJPTjEoMCYGA1UECgwfRXhhbXBsZSBJbnRlcm5ldCBDQSBJ
bmMuOkNBIFNlYzEPMA0GA1UECwwGQ0EgU2VjMB4XDTI0MDkxNjE0MzIyMloXDTI1
MDkxNjE0MzIyMloweTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQK
DB9FeGFtcGxlIEluYy46YXJpZXMtZnJhbWV3b3JrLWdvMRswGQYDVQQLDBJhcmll
cy1mcmFtZXdvcmstZ28xFjAUBgNVBAMMDSouZXhhbXBsZS5jb20wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAS+MDQvSaOcynq1WgTTM84Lp+/b5KRMlZxg5Xe7LNdK
EeNSC/FQhUlEW+FZV2WcBi2dvyo9w7t8VyrGHM9Jdm83o4GKMIGHMB0GA1UdDgQW
BBRe9n5uUKScnRR6JfY+wEIb35BUiDAfBgNVHSMEGDAWgBRlUFs/5IUaBI72BA35
eVLYPJtCeDATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwIwYDVR0R
BBwwGoINKi5leGFtcGxlLmNvbYIJbG9jYWxob3N0MAoGCCqGSM49BAMCA0kAMEYC
IQC+19JlCnE+7Z9dlSQPyinWRJlGKjrspV9GMCXoZtgAUAIhAMFX6M5a39bmj34B
IoSMDjMR9YtqeN4PXD6JiqPS3pKO
-----END CERTIFICATE-----
24 changes: 13 additions & 11 deletions kms/webkms/testdata/ec-pubCert2.pem
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
-----BEGIN CERTIFICATE-----
MIIB9jCCAZ2gAwIBAgIUOpIXXroHVWLOlicAo9VtdElkfR0wCgYIKoZIzj0EAwIw
VTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQKDB9FeGFtcGxlIElu
dGVybmV0IENBIEluYy46Q0EgU2VjMQ8wDQYDVQQLDAZDQSBTZWMwHhcNMjMwNDE5
MTIzMTExWhcNMjQwNDE4MTIzMTExWjB5MQswCQYDVQQGEwJDQTELMAkGA1UECAwC
T04xKDAmBgNVBAoMH0V4YW1wbGUgSW5jLjphcmllcy1mcmFtZXdvcmstZ28xGzAZ
BgNVBAsMEmFyaWVzLWZyYW1ld29yay1nbzEWMBQGA1UEAwwNKi5leGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJvVTejsgFUN5gC1EBo1GgCr1xNx
KDT66Jf/ZQbXAKMjOkzqUKOkYY8MR/W11EoGCqN99GhDtGoo1KSZg299zlujJzAl
MCMGA1UdEQQcMBqCDSouZXhhbXBsZS5jb22CCWxvY2FsaG9zdDAKBggqhkjOPQQD
AgNHADBEAiAEZpyLGi/9bC9aZOhDJK3PserIUzZbcOR79jwsNJs8fwIgVnK+qt5+
P2TkafB+aTXC25N2TuEoQivrGS+6LH1sS1Y=
MIICUTCCAfagAwIBAgIJAO0O74K+mvU6MAoGCCqGSM49BAMCMFUxCzAJBgNVBAYT
AkNBMQswCQYDVQQIDAJPTjEoMCYGA1UECgwfRXhhbXBsZSBJbnRlcm5ldCBDQSBJ
bmMuOkNBIFNlYzEPMA0GA1UECwwGQ0EgU2VjMB4XDTI0MDkxNjE0MzMwN1oXDTI1
MDkxNjE0MzMwN1oweTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQK
DB9FeGFtcGxlIEluYy46YXJpZXMtZnJhbWV3b3JrLWdvMRswGQYDVQQLDBJhcmll
cy1mcmFtZXdvcmstZ28xFjAUBgNVBAMMDSouZXhhbXBsZS5jb20wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAASb1U3o7IBVDeYAtRAaNRoAq9cTcSg0+uiX/2UG1wCj
IzpM6lCjpGGPDEf1tdRKBgqjffRoQ7RqKNSkmYNvfc5bo4GKMIGHMB0GA1UdDgQW
BBSxgd4FuULYHXaOfx+rqPzTiAF7TDAfBgNVHSMEGDAWgBSPMn0w1WM35xjBVJ7O
PZOI9mA8bzATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwIwYDVR0R
BBwwGoINKi5leGFtcGxlLmNvbYIJbG9jYWxob3N0MAoGCCqGSM49BAMCA0kAMEYC
IQCs53mSO7t1BIoiGrvVSJAXVW/J+h22HuJcNpQpiekDRQIhAJ3tLl5GXSga3u86
ngVqJoGxBSNVp1svT4/aQK4Styct
-----END CERTIFICATE-----
24 changes: 13 additions & 11 deletions kms/webkms/testdata/ec-pubCert3.pem
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
-----BEGIN CERTIFICATE-----
MIIB+DCCAZ2gAwIBAgIUOpIXXroHVWLOlicAo9VtdElkfR4wCgYIKoZIzj0EAwIw
VTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQKDB9FeGFtcGxlIElu
dGVybmV0IENBIEluYy46Q0EgU2VjMQ8wDQYDVQQLDAZDQSBTZWMwHhcNMjMwNDE5
MTIzMTExWhcNMjQwNDE4MTIzMTExWjB5MQswCQYDVQQGEwJDQTELMAkGA1UECAwC
T04xKDAmBgNVBAoMH0V4YW1wbGUgSW5jLjphcmllcy1mcmFtZXdvcmstZ28xGzAZ
BgNVBAsMEmFyaWVzLWZyYW1ld29yay1nbzEWMBQGA1UEAwwNKi5leGFtcGxlLmNv
bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBpAuMCB/Qal9r4VJlSCBiDJ8PuF
arCg4GjNM6JjKE3pTFmeB/MdW/XTaR+2nVx3dVk60nMvVyoBS2+pDedYVYejJzAl
MCMGA1UdEQQcMBqCDSouZXhhbXBsZS5jb22CCWxvY2FsaG9zdDAKBggqhkjOPQQD
AgNJADBGAiEA10vOUAHf+iCwyPIJACGffBqa7r+NNt5fTVnf/iHq+UcCIQCFcTcz
/vBiwivgDXmh8JRvXceE9YvTZu5fXeeoskmq4w==
MIICUTCCAfagAwIBAgIJAO0O74K+mvU7MAoGCCqGSM49BAMCMFUxCzAJBgNVBAYT
AkNBMQswCQYDVQQIDAJPTjEoMCYGA1UECgwfRXhhbXBsZSBJbnRlcm5ldCBDQSBJ
bmMuOkNBIFNlYzEPMA0GA1UECwwGQ0EgU2VjMB4XDTI0MDkxNjE0MzMzOVoXDTI1
MDkxNjE0MzMzOVoweTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSgwJgYDVQQK
DB9FeGFtcGxlIEluYy46YXJpZXMtZnJhbWV3b3JrLWdvMRswGQYDVQQLDBJhcmll
cy1mcmFtZXdvcmstZ28xFjAUBgNVBAMMDSouZXhhbXBsZS5jb20wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAQaQLjAgf0Gpfa+FSZUggYgyfD7hWqwoOBozTOiYyhN
6UxZngfzHVv102kftp1cd3VZOtJzL1cqAUtvqQ3nWFWHo4GKMIGHMB0GA1UdDgQW
BBSrLt2iDu3dHrzp5B6/mDdXy3cLKzAfBgNVHSMEGDAWgBTUufKRsvs9upZuarOy
TEqnIE0UezATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwIwYDVR0R
BBwwGoINKi5leGFtcGxlLmNvbYIJbG9jYWxob3N0MAoGCCqGSM49BAMCA0kAMEYC
IQDX8rNlF0Zup7LMiXqhQGlh+w5Z7r4g0zbTUIzWfV4yZwIhAN3f+kwzdtXvQVTC
ZcDL/VUota85MH7TjrBdyHkxinkL
-----END CERTIFICATE-----
15 changes: 14 additions & 1 deletion spi/kms/key_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ package kms

// keyOpts holds options for Create, Rotate and CreateAndExportPubKeyBytes.
type keyOpts struct {
attrs []string
attrs []string
metadata map[string]any
}

// NewKeyOpt creates a new empty key option.
Expand All @@ -25,6 +26,11 @@ func (pk *keyOpts) Attrs() []string {
return pk.attrs
}

// Metadata gets the additional data to be stored along with the key.
func (pk *keyOpts) Metadata() map[string]any {
return pk.metadata
}

// KeyOpts are the create key option.
type KeyOpts func(opts *keyOpts)

Expand All @@ -34,3 +40,10 @@ func WithAttrs(attrs []string) KeyOpts {
opts.attrs = attrs
}
}

// WithMetadata option is for creating a key that can have additional metadata.
func WithMetadata(metadata map[string]any) KeyOpts {
return func(opts *keyOpts) {
opts.metadata = metadata
}
}
10 changes: 10 additions & 0 deletions spi/kms/kms.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ type Store interface {
Delete(keysetID string) error
}

// StoreWithMetadata defines extended storage capability to work with key's metadata.
type StoreWithMetadata interface {
// PutWithMetadata stores the given key and metadata under the given keysetID.
PutWithMetadata(keysetID string, key []byte, metadata map[string]any) error
// GetWithMetadata retrieves the key and its' metadata stored under the given keysetID.
// If no key is found, the returned error is expected to wrap ErrKeyNotFound.
// KMS implementations may check to see if the error wraps that error type for certain operations.
GetWithMetadata(keysetID string) (key []byte, metadata map[string]any, err error)
}

// Provider for KeyManager builder/constructor.
type Provider interface {
StorageProvider() Store
Expand Down
40 changes: 39 additions & 1 deletion spi/kms/privKey_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ package kms

// privateKeyOpts holds options for ImportPrivateKey.
type privateKeyOpts struct {
ksID string
ksID string
metadata map[string]any
}

// NewOpt creates a new empty private key option.
Expand All @@ -25,6 +26,11 @@ func (pk *privateKeyOpts) KsID() string {
return pk.ksID
}

// Metadata gets the additional data to be stored along with the key.
func (pk *privateKeyOpts) Metadata() map[string]any {
return pk.metadata
}

// PrivateKeyOpts are the import private key option.
type PrivateKeyOpts func(opts *privateKeyOpts)

Expand All @@ -34,3 +40,35 @@ func WithKeyID(keyID string) PrivateKeyOpts {
opts.ksID = keyID
}
}

// ImportWithMetadata option is for importing a private key that can have additional metadata.
func ImportWithMetadata(metadata map[string]any) PrivateKeyOpts {
return func(opts *privateKeyOpts) {
opts.metadata = metadata
}
}

// exportKeyOpts holds options for ExportPubKey.
type exportKeyOpts struct {
getMetadata bool
}

// NewExportOpt creates a new empty export pub key option.
func NewExportOpt() *exportKeyOpts { // nolint
return &exportKeyOpts{}
}

// GetMetadata indicates that metadata have to be exported along with the key.
func (pk *exportKeyOpts) GetMetadata() bool {
return pk.getMetadata
}

// ExportKeyOpts are the export public key option.
type ExportKeyOpts func(opts *exportKeyOpts)

// ExportWithMetadata option is for exporting public key with metadata.
func ExportWithMetadata(getMetadata bool) ExportKeyOpts {
return func(opts *exportKeyOpts) {
opts.getMetadata = getMetadata
}
}

0 comments on commit 5467496

Please sign in to comment.