From 5467496690a1fd04b6abf5881a2e6d160fe3289b Mon Sep 17 00:00:00 2001 From: Volodymyr Kit Date: Thu, 19 Sep 2024 16:47:48 +0300 Subject: [PATCH] feat: support of key's metadata for localkms (#21) Signed-off-by: Volodymyr Kit --- kms/localkms/localkms.go | 38 +++++++++++++++++-- kms/localkms/localkms_reader.go | 57 ++++++++++++++++++++++------- kms/localkms/localkms_writer.go | 14 ++++++- kms/webkms/testdata/ec-pubCert1.pem | 24 ++++++------ kms/webkms/testdata/ec-pubCert2.pem | 24 ++++++------ kms/webkms/testdata/ec-pubCert3.pem | 24 ++++++------ spi/kms/key_opts.go | 15 +++++++- spi/kms/kms.go | 10 +++++ spi/kms/privKey_opts.go | 40 +++++++++++++++++++- 9 files changed, 192 insertions(+), 54 deletions(-) diff --git a/kms/localkms/localkms.go b/kms/localkms/localkms.go index e87b32f..a280a25 100644 --- a/kms/localkms/localkms.go +++ b/kms/localkms/localkms.go @@ -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) } @@ -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: @@ -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 @@ -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) { @@ -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: diff --git a/kms/localkms/localkms_reader.go b/kms/localkms/localkms_reader.go index 767ee7a..17d2a46 100644 --- a/kms/localkms/localkms_reader.go +++ b/kms/localkms/localkms_reader.go @@ -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) } diff --git a/kms/localkms/localkms_writer.go b/kms/localkms/localkms_writer.go index 7e96fea..0bd9d44 100644 --- a/kms/localkms/localkms_writer.go +++ b/kms/localkms/localkms_writer.go @@ -31,6 +31,7 @@ func newWriter(kmsStore kmsapi.Store, opts ...kmsapi.PrivateKeyOpts) *storeWrite return &storeWriter{ storage: kmsStore, requestedKeysetID: pOpts.KsID(), + metadata: pOpts.Metadata(), } } @@ -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 } @@ -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 } diff --git a/kms/webkms/testdata/ec-pubCert1.pem b/kms/webkms/testdata/ec-pubCert1.pem index 5283f6b..e572d87 100644 --- a/kms/webkms/testdata/ec-pubCert1.pem +++ b/kms/webkms/testdata/ec-pubCert1.pem @@ -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----- diff --git a/kms/webkms/testdata/ec-pubCert2.pem b/kms/webkms/testdata/ec-pubCert2.pem index 5f8c72c..5282424 100644 --- a/kms/webkms/testdata/ec-pubCert2.pem +++ b/kms/webkms/testdata/ec-pubCert2.pem @@ -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----- diff --git a/kms/webkms/testdata/ec-pubCert3.pem b/kms/webkms/testdata/ec-pubCert3.pem index 4350f27..1dee12a 100644 --- a/kms/webkms/testdata/ec-pubCert3.pem +++ b/kms/webkms/testdata/ec-pubCert3.pem @@ -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----- diff --git a/spi/kms/key_opts.go b/spi/kms/key_opts.go index 68e883a..e7427fc 100644 --- a/spi/kms/key_opts.go +++ b/spi/kms/key_opts.go @@ -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. @@ -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) @@ -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 + } +} diff --git a/spi/kms/kms.go b/spi/kms/kms.go index e71f177..f40b0ab 100644 --- a/spi/kms/kms.go +++ b/spi/kms/kms.go @@ -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 diff --git a/spi/kms/privKey_opts.go b/spi/kms/privKey_opts.go index cd265a7..97c916a 100644 --- a/spi/kms/privKey_opts.go +++ b/spi/kms/privKey_opts.go @@ -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. @@ -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) @@ -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 + } +}