Skip to content

Commit 023dd1d

Browse files
authored
[ARCH-333] Update import to override key name if it is set. (#1682)
1 parent 39714ba commit 023dd1d

File tree

2 files changed

+61
-27
lines changed

2 files changed

+61
-27
lines changed

keystore/admin.go

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ type ImportKeysRequest struct {
5656
}
5757

5858
type ImportKeyRequest struct {
59-
KeyName string
60-
Data []byte
61-
Password string
59+
NewKeyName string
60+
Data []byte
61+
Password string
6262
}
6363

6464
type ImportKeysResponse struct{}
@@ -93,10 +93,30 @@ type SetMetadataUpdate struct {
9393
type SetMetadataResponse struct{}
9494

9595
type Admin interface {
96+
// CreateKeys creates multiple keys in a single atomic operation.
97+
// The response preserves the order of the keys in the request.
98+
// Returns ErrKeyAlreadyExists if any key name already exists,
99+
// ErrInvalidKeyName if any key name is invalid,
100+
// ErrUnsupportedKeyType if any key type is not supported.
96101
CreateKeys(ctx context.Context, req CreateKeysRequest) (CreateKeysResponse, error)
102+
103+
// DeleteKeys deletes multiple keys in a single atomic operation.
104+
// Returns ErrKeyNotFound if any key does not exist.
97105
DeleteKeys(ctx context.Context, req DeleteKeysRequest) (DeleteKeysResponse, error)
106+
107+
// ImportKeys imports multiple encrypted keys in a single atomic operation.
108+
// Keys can be renamed during import using NewKeyName.
109+
// Returns ErrKeyAlreadyExists if a key with the target name exists,
110+
// ErrInvalidKeyName if the target name is invalid.
98111
ImportKeys(ctx context.Context, req ImportKeysRequest) (ImportKeysResponse, error)
112+
113+
// ExportKeys exports multiple keys in encrypted format.
114+
// Each key is encrypted using the parameters specified in the request.
115+
// Returns ErrKeyNotFound if any key does not exist.
99116
ExportKeys(ctx context.Context, req ExportKeysRequest) (ExportKeysResponse, error)
117+
118+
// SetMetadata updates metadata for multiple keys in a single atomic operation.
119+
// Returns ErrKeyNotFound if any key does not exist.
100120
SetMetadata(ctx context.Context, req SetMetadataRequest) (SetMetadataResponse, error)
101121
}
102122

@@ -237,40 +257,45 @@ func (ks *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (Impo
237257
defer ks.mu.Unlock()
238258

239259
ksCopy := maps.Clone(ks.keystore)
240-
for _, keyReq := range req.Keys {
241-
if err := ValidKeyName(keyReq.KeyName); err != nil {
242-
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrInvalidKeyName, err)
243-
}
244-
if _, ok := ksCopy[keyReq.KeyName]; ok {
245-
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrKeyAlreadyExists, keyReq.KeyName)
246-
}
260+
for i, keyReq := range req.Keys {
247261
encData := gethkeystore.CryptoJSON{}
248262
err := json.Unmarshal(keyReq.Data, &encData)
249263
if err != nil {
250-
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal encrypted import data: %w", keyReq.KeyName, err)
264+
return ImportKeysResponse{}, fmt.Errorf("key num = %d, failed to unmarshal encrypted import data: %w", i, err)
251265
}
252266
decData, err := gethkeystore.DecryptDataV3(encData, keyReq.Password)
253267
if err != nil {
254-
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to decrypt key: %w", keyReq.KeyName, err)
268+
return ImportKeysResponse{}, fmt.Errorf("key num = %d, failed to decrypt key: %w", i, err)
255269
}
256270
keypb := &serialization.Key{}
257271
err = proto.Unmarshal(decData, keypb)
258272
if err != nil {
259-
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal key: %w", keyReq.KeyName, err)
273+
return ImportKeysResponse{}, fmt.Errorf("key num = %d, failed to unmarshal key: %w", i, err)
260274
}
261275
pkRaw := internal.NewRaw(keypb.PrivateKey)
262276
keyType := KeyType(keypb.KeyType)
263277
publicKey, err := publicKeyFromPrivateKey(pkRaw, keyType)
264278
if err != nil {
265-
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to get public key from private key: %w", keyReq.KeyName, err)
279+
return ImportKeysResponse{}, fmt.Errorf("key num = %d, failed to get public key from private key: %w", i, err)
266280
}
267281
metadata := keypb.Metadata
268282
// The proto compiler sets empty slices to nil during the serialization (https://github.com/golang/protobuf/issues/1348).
269283
// We set metadata back to empty slice to be consistent with the Create method which initializes it as such.
270284
if metadata == nil {
271285
metadata = []byte{}
272286
}
273-
ksCopy[keyReq.KeyName] = newKey(keyType, pkRaw, publicKey, time.Unix(keypb.CreatedAt, 0), metadata)
287+
288+
keyName := keyReq.NewKeyName
289+
if keyName == "" {
290+
keyName = keypb.Name
291+
}
292+
if err := ValidKeyName(keyName); err != nil {
293+
return ImportKeysResponse{}, fmt.Errorf("key name = %s, %w: %s", keyName, ErrInvalidKeyName, err)
294+
}
295+
if _, ok := ksCopy[keyName]; ok {
296+
return ImportKeysResponse{}, fmt.Errorf("key name = %s, %w: %s", keyName, ErrKeyAlreadyExists, keyName)
297+
}
298+
ksCopy[keyName] = newKey(keyType, pkRaw, publicKey, time.Unix(keypb.CreatedAt, 0), metadata)
274299
}
275300
// Persist it to storage.
276301
if err := ks.save(ctx, ksCopy); err != nil {

keystore/admin_test.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,35 @@ func TestKeystore_ExportImport(t *testing.T) {
231231
require.Len(t, exportResponse.Keys, 1)
232232
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
233233
Keys: []keystore.ImportKeyRequest{
234-
{KeyName: "key1", Password: exportParams.Password, Data: exportResponse.Keys[0].Data},
234+
{Password: exportParams.Password, Data: exportResponse.Keys[0].Data},
235235
},
236236
})
237237
require.NoError(t, err)
238+
239+
// Importing a key with the same name again fails.
240+
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
241+
Keys: []keystore.ImportKeyRequest{
242+
{Password: exportParams.Password, Data: exportResponse.Keys[0].Data},
243+
},
244+
})
245+
require.ErrorIs(t, err, keystore.ErrKeyAlreadyExists)
246+
247+
// Importing a key with a new name is allowed.
248+
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
249+
Keys: []keystore.ImportKeyRequest{
250+
{NewKeyName: "new-name", Password: exportParams.Password, Data: exportResponse.Keys[0].Data},
251+
},
252+
})
253+
require.NoError(t, err)
254+
255+
// Verify that imported key matches exported key.
256+
// We cannot compare private keys directly, so we test that signing with key1 from ks1 and verifying
257+
// with key1 from ks2 works as if the two keys are the same.
238258
key1ks1, err := ks1.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
239259
require.NoError(t, err)
240260
key1ks2, err := ks2.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
241261
require.Equal(t, key1ks1, key1ks2)
242262

243-
// We cannot compare private keys directly, so we test that signing with key1 from ks1 and verifying
244-
// with key1 from ks2 works as if the two keys are the same.
245263
testData := []byte("hello world")
246264
signature, err := ks2.Sign(t.Context(), keystore.SignRequest{
247265
KeyName: "key1",
@@ -266,15 +284,6 @@ func TestKeystore_ExportImport(t *testing.T) {
266284
})
267285
require.ErrorIs(t, err, keystore.ErrKeyNotFound)
268286
})
269-
270-
t.Run("import existing key", func(t *testing.T) {
271-
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
272-
Keys: []keystore.ImportKeyRequest{
273-
{KeyName: "key1", Password: "", Data: []byte{}},
274-
},
275-
})
276-
require.ErrorIs(t, err, keystore.ErrKeyAlreadyExists)
277-
})
278287
}
279288

280289
func TestKeystore_SetMetadata(t *testing.T) {

0 commit comments

Comments
 (0)