Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support of key's metadata for localkms #21

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
Loading