diff --git a/keystore/corekeys/csa.go b/keystore/corekeys/csa.go index 2df114dde1..62088f9acc 100644 --- a/keystore/corekeys/csa.go +++ b/keystore/corekeys/csa.go @@ -4,131 +4,14 @@ package corekeys import ( "context" - "encoding/json" - "errors" - "fmt" - - gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore" - "google.golang.org/protobuf/proto" - - "github.com/smartcontractkit/chainlink-common/keystore" - "github.com/smartcontractkit/chainlink-common/keystore/serialization" -) - -var ( - ErrInvalidExportFormat = errors.New("invalid export format") ) -const ( - TypeCSA = "csa" - nameDefault = "default" - exportFormat = "github.com/smartcontractkit/chainlink-common/keystore/corekeys" -) - -type Store struct { - keystore.Keystore -} - -type Envelope struct { - Type string - Keys []keystore.ExportKeyResponse - ExportFormat string -} - -func NewStore(ks keystore.Keystore) *Store { - return &Store{ - Keystore: ks, - } -} - -// decryptKey decrypts an encrypted key using the provided password and returns the deserialized key. -func decryptKey(encryptedData []byte, password string) (*serialization.Key, error) { - encData := gethkeystore.CryptoJSON{} - err := json.Unmarshal(encryptedData, &encData) - if err != nil { - return nil, fmt.Errorf("could not unmarshal key material into CryptoJSON: %w", err) - } - - decData, err := gethkeystore.DecryptDataV3(encData, password) - if err != nil { - return nil, fmt.Errorf("could not decrypt data: %w", err) - } - - keypb := &serialization.Key{} - err = proto.Unmarshal(decData, keypb) - if err != nil { - return nil, fmt.Errorf("could not unmarshal key into serialization.Key: %w", err) - } - - return keypb, nil -} +const TypeCSA = "csa" func (ks *Store) GenerateEncryptedCSAKey(ctx context.Context, password string) ([]byte, error) { - path := keystore.NewKeyPath(TypeCSA, nameDefault) - _, err := ks.CreateKeys(ctx, keystore.CreateKeysRequest{ - Keys: []keystore.CreateKeyRequest{ - { - KeyName: path.String(), - KeyType: keystore.Ed25519, - }, - }, - }) - if err != nil { - return nil, fmt.Errorf("failed to generate exportable key: %w", err) - } - - er, err := ks.ExportKeys(ctx, keystore.ExportKeysRequest{ - Keys: []keystore.ExportKeyParam{ - { - KeyName: path.String(), - Enc: keystore.EncryptionParams{ - Password: password, - ScryptParams: keystore.DefaultScryptParams, - }, - }, - }, - }) - if err != nil { - return nil, fmt.Errorf("failed to export key: %w", err) - } - - envelope := Envelope{ - Type: TypeCSA, - Keys: er.Keys, - ExportFormat: exportFormat, - } - - data, err := json.Marshal(&envelope) - if err != nil { - return nil, fmt.Errorf("failed to marshal envelope: %w", err) - } - - return data, nil + return ks.generateEncryptedKey(ctx, TypeCSA, password) } func FromEncryptedCSAKey(data []byte, password string) ([]byte, error) { - envelope := Envelope{} - err := json.Unmarshal(data, &envelope) - if err != nil { - return nil, fmt.Errorf("could not unmarshal import data into envelope: %w", err) - } - - if envelope.ExportFormat != exportFormat { - return nil, fmt.Errorf("invalid export format: %w", ErrInvalidExportFormat) - } - - if envelope.Type != TypeCSA { - return nil, fmt.Errorf("invalid key type: expected %s, got %s", TypeCSA, envelope.Type) - } - - if len(envelope.Keys) != 1 { - return nil, fmt.Errorf("expected exactly one key in envelope, got %d", len(envelope.Keys)) - } - - keypb, err := decryptKey(envelope.Keys[0].Data, password) - if err != nil { - return nil, err - } - - return keypb.PrivateKey, nil + return fromEncryptedKey(data, TypeCSA, password) } diff --git a/keystore/corekeys/dkg.go b/keystore/corekeys/dkg.go new file mode 100644 index 0000000000..2c90f4886f --- /dev/null +++ b/keystore/corekeys/dkg.go @@ -0,0 +1,17 @@ +// `corekeys` provides utilities to generate keys that are compatible with the core node +// and can be imported by it. +package corekeys + +import ( + "context" +) + +const TypeDKG = "dkg" + +func (ks *Store) GenerateEncryptedDKGKey(ctx context.Context, password string) ([]byte, error) { + return ks.generateEncryptedKey(ctx, TypeDKG, password) +} + +func FromEncryptedDKGKey(data []byte, password string) ([]byte, error) { + return fromEncryptedKey(data, TypeDKG, password) +} diff --git a/keystore/corekeys/dkg_test.go b/keystore/corekeys/dkg_test.go new file mode 100644 index 0000000000..c7f82a97b8 --- /dev/null +++ b/keystore/corekeys/dkg_test.go @@ -0,0 +1,81 @@ +package corekeys + +import ( + "crypto/ecdh" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/keystore" +) + +func TestDKGKeyRoundTrip(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedDKGKey(ctx, password) + require.NoError(t, err) + require.NotEmpty(t, encryptedKey) + + dkgKeyPath := keystore.NewKeyPath(TypeDKG, nameDefault) + getKeysResp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{dkgKeyPath.String()}, + }) + require.NoError(t, err) + require.Len(t, getKeysResp.Keys, 1) + + storedPublicKey := getKeysResp.Keys[0].KeyInfo.PublicKey + require.NotEmpty(t, storedPublicKey) + + privateKey, err := FromEncryptedDKGKey(encryptedKey, password) + require.NoError(t, err) + require.NotEmpty(t, privateKey) + + require.Len(t, privateKey, 32) + + curve := ecdh.P256() + p256PrivateKey, err := curve.NewPrivateKey(privateKey) + require.NoError(t, err) + derivedPublicKey := p256PrivateKey.PublicKey().Bytes() + require.Equal(t, storedPublicKey, derivedPublicKey) +} + +func TestDKGKeyImportWithWrongPassword(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + wrongPassword := "wrong-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedDKGKey(ctx, password) + require.NoError(t, err) + require.NotNil(t, encryptedKey) + + _, err = FromEncryptedDKGKey(encryptedKey, wrongPassword) + require.Error(t, err) + require.Contains(t, err.Error(), "could not decrypt data") +} + +func TestDKGKeyImportInvalidFormat(t *testing.T) { + t.Parallel() + + _, err := FromEncryptedDKGKey([]byte("invalid json"), "password") + require.Error(t, err) + require.Contains(t, err.Error(), "could not unmarshal import data") +} diff --git a/keystore/corekeys/evm.go b/keystore/corekeys/evm.go new file mode 100644 index 0000000000..f0db29c1f5 --- /dev/null +++ b/keystore/corekeys/evm.go @@ -0,0 +1,17 @@ +// `corekeys` provides utilities to generate keys that are compatible with the core node +// and can be imported by it. +package corekeys + +import ( + "context" +) + +const TypeEVM = "evm" + +func (ks *Store) GenerateEncryptedEVMKey(ctx context.Context, password string) ([]byte, error) { + return ks.generateEncryptedKey(ctx, TypeEVM, password) +} + +func FromEncryptedEVMKey(data []byte, password string) ([]byte, error) { + return fromEncryptedKey(data, TypeEVM, password) +} diff --git a/keystore/corekeys/evm_test.go b/keystore/corekeys/evm_test.go new file mode 100644 index 0000000000..16cd553aaf --- /dev/null +++ b/keystore/corekeys/evm_test.go @@ -0,0 +1,81 @@ +package corekeys + +import ( + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/keystore" +) + +func TestEVMKeyRoundTrip(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedEVMKey(ctx, password) + require.NoError(t, err) + require.NotEmpty(t, encryptedKey) + + evmKeyPath := keystore.NewKeyPath(TypeEVM, nameDefault) + getKeysResp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{evmKeyPath.String()}, + }) + require.NoError(t, err) + require.Len(t, getKeysResp.Keys, 1) + + storedPublicKey := getKeysResp.Keys[0].KeyInfo.PublicKey + require.NotEmpty(t, storedPublicKey) + + privateKey, err := FromEncryptedEVMKey(encryptedKey, password) + require.NoError(t, err) + require.NotEmpty(t, privateKey) + + require.Len(t, privateKey, 32) + + ecdsaPK, err := crypto.ToECDSA(privateKey) + require.NoError(t, err) + + derivedPublicKey := crypto.FromECDSAPub(&ecdsaPK.PublicKey) + require.Equal(t, storedPublicKey, derivedPublicKey) +} + +func TestEVMKeyImportWithWrongPassword(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + wrongPassword := "wrong-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedEVMKey(ctx, password) + require.NoError(t, err) + require.NotNil(t, encryptedKey) + + _, err = FromEncryptedEVMKey(encryptedKey, wrongPassword) + require.Error(t, err) + require.Contains(t, err.Error(), "could not decrypt data") +} + +func TestEVMKeyImportInvalidFormat(t *testing.T) { + t.Parallel() + + _, err := FromEncryptedEVMKey([]byte("invalid json"), "password") + require.Error(t, err) + require.Contains(t, err.Error(), "could not unmarshal import data") +} diff --git a/keystore/corekeys/keys.go b/keystore/corekeys/keys.go new file mode 100644 index 0000000000..53a84c7bb3 --- /dev/null +++ b/keystore/corekeys/keys.go @@ -0,0 +1,161 @@ +package corekeys + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore" + "google.golang.org/protobuf/proto" + + "github.com/smartcontractkit/chainlink-common/keystore" + "github.com/smartcontractkit/chainlink-common/keystore/serialization" +) + +var keyTypeToAlgorithmMap = keyTypeToAlgorithm{ + TypeCSA: keystore.Ed25519, + TypeP2P: keystore.Ed25519, + TypeEVM: keystore.ECDSA_S256, + TypeSolana: keystore.Ed25519, + TypeDKG: keystore.ECDH_P256, + TypeWorkflowKey: keystore.X25519, +} + +type keyTypeToAlgorithm map[string]keystore.KeyType + +func (m keyTypeToAlgorithm) Keys() []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +var ( + ErrInvalidExportFormat = errors.New("invalid export format") +) + +const ( + nameDefault = "default" + exportFormat = "github.com/smartcontractkit/chainlink-common/keystore/corekeys" +) + +type Store struct { + keystore.Keystore +} + +type Envelope struct { + Type string + Keys []keystore.ExportKeyResponse + ExportFormat string +} + +func NewStore(ks keystore.Keystore) *Store { + return &Store{ + Keystore: ks, + } +} + +// decryptKey decrypts an encrypted key using the provided password and returns the deserialized key. +func decryptKey(encryptedData []byte, password string) (*serialization.Key, error) { + encData := gethkeystore.CryptoJSON{} + err := json.Unmarshal(encryptedData, &encData) + if err != nil { + return nil, fmt.Errorf("could not unmarshal key material into CryptoJSON: %w", err) + } + + decData, err := gethkeystore.DecryptDataV3(encData, password) + if err != nil { + return nil, fmt.Errorf("could not decrypt data: %w", err) + } + + keypb := &serialization.Key{} + err = proto.Unmarshal(decData, keypb) + if err != nil { + return nil, fmt.Errorf("could not unmarshal key into serialization.Key: %w", err) + } + + return keypb, nil +} + +func (ks *Store) generateEncryptedKey(ctx context.Context, keyType string, password string) ([]byte, error) { + algo, ok := keyTypeToAlgorithmMap[keyType] + if !ok { + return nil, fmt.Errorf("unsupported key type: %s. Supported types are: %s", keyType, strings.Join(keyTypeToAlgorithmMap.Keys(), ", ")) + } + + path := keystore.NewKeyPath(keyType, nameDefault) + _, err := ks.CreateKeys(ctx, keystore.CreateKeysRequest{ + Keys: []keystore.CreateKeyRequest{ + { + KeyName: path.String(), + KeyType: algo, + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to generate exportable key: %w", err) + } + + er, err := ks.ExportKeys(ctx, keystore.ExportKeysRequest{ + Keys: []keystore.ExportKeyParam{ + { + KeyName: path.String(), + Enc: keystore.EncryptionParams{ + Password: password, + ScryptParams: keystore.DefaultScryptParams, + }, + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to export key: %w", err) + } + + envelope := Envelope{ + Type: keyType, + Keys: er.Keys, + ExportFormat: exportFormat, + } + + data, err := json.Marshal(&envelope) + if err != nil { + return nil, fmt.Errorf("failed to marshal envelope: %w", err) + } + + return data, nil +} + +func fromEncryptedKey(data []byte, keyType string, password string) ([]byte, error) { + _, ok := keyTypeToAlgorithmMap[keyType] + if !ok { + return nil, fmt.Errorf("unsupported key type: %s. Supported types are: %s", keyType, strings.Join(keyTypeToAlgorithmMap.Keys(), ", ")) + } + + envelope := Envelope{} + err := json.Unmarshal(data, &envelope) + if err != nil { + return nil, fmt.Errorf("could not unmarshal import data into envelope: %w", err) + } + + if envelope.ExportFormat != exportFormat { + return nil, fmt.Errorf("invalid export format: %w", ErrInvalidExportFormat) + } + + if envelope.Type != keyType { + return nil, fmt.Errorf("invalid key type: expected %s, got %s", keyType, envelope.Type) + } + + if len(envelope.Keys) != 1 { + return nil, fmt.Errorf("expected exactly one key in envelope, got %d", len(envelope.Keys)) + } + + keypb, err := decryptKey(envelope.Keys[0].Data, password) + if err != nil { + return nil, err + } + + return keypb.PrivateKey, nil +} diff --git a/keystore/corekeys/p2p.go b/keystore/corekeys/p2p.go new file mode 100644 index 0000000000..8642c16970 --- /dev/null +++ b/keystore/corekeys/p2p.go @@ -0,0 +1,17 @@ +// `corekeys` provides utilities to generate keys that are compatible with the core node +// and can be imported by it. +package corekeys + +import ( + "context" +) + +const TypeP2P = "p2p" + +func (ks *Store) GenerateEncryptedP2PKey(ctx context.Context, password string) ([]byte, error) { + return ks.generateEncryptedKey(ctx, TypeP2P, password) +} + +func FromEncryptedP2PKey(data []byte, password string) ([]byte, error) { + return fromEncryptedKey(data, TypeP2P, password) +} diff --git a/keystore/corekeys/p2p_test.go b/keystore/corekeys/p2p_test.go new file mode 100644 index 0000000000..0df1247788 --- /dev/null +++ b/keystore/corekeys/p2p_test.go @@ -0,0 +1,78 @@ +package corekeys + +import ( + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/keystore" +) + +func TestP2PKeyRoundTrip(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedP2PKey(ctx, password) + require.NoError(t, err) + require.NotEmpty(t, encryptedKey) + + p2pKeyPath := keystore.NewKeyPath(TypeP2P, nameDefault) + getKeysResp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{p2pKeyPath.String()}, + }) + require.NoError(t, err) + require.Len(t, getKeysResp.Keys, 1) + + storedPublicKey := getKeysResp.Keys[0].KeyInfo.PublicKey + require.NotEmpty(t, storedPublicKey) + + privateKey, err := FromEncryptedP2PKey(encryptedKey, password) + require.NoError(t, err) + require.NotEmpty(t, privateKey) + + require.Len(t, privateKey, 64) + + derivedPublicKey := ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey) + require.Equal(t, storedPublicKey, []byte(derivedPublicKey)) +} + +func TestP2PKeyImportWithWrongPassword(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + wrongPassword := "wrong-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedP2PKey(ctx, password) + require.NoError(t, err) + require.NotNil(t, encryptedKey) + + _, err = FromEncryptedP2PKey(encryptedKey, wrongPassword) + require.Error(t, err) + require.Contains(t, err.Error(), "could not decrypt data") +} + +func TestP2PKeyImportInvalidFormat(t *testing.T) { + t.Parallel() + + _, err := FromEncryptedP2PKey([]byte("invalid json"), "password") + require.Error(t, err) + require.Contains(t, err.Error(), "could not unmarshal import data") +} diff --git a/keystore/corekeys/solana.go b/keystore/corekeys/solana.go new file mode 100644 index 0000000000..fada5a2aab --- /dev/null +++ b/keystore/corekeys/solana.go @@ -0,0 +1,17 @@ +// `corekeys` provides utilities to generate keys that are compatible with the core node +// and can be imported by it. +package corekeys + +import ( + "context" +) + +const TypeSolana = "solana" + +func (ks *Store) GenerateEncryptedSolanaKey(ctx context.Context, password string) ([]byte, error) { + return ks.generateEncryptedKey(ctx, TypeSolana, password) +} + +func FromEncryptedSolanaKey(data []byte, password string) ([]byte, error) { + return fromEncryptedKey(data, TypeSolana, password) +} diff --git a/keystore/corekeys/solana_test.go b/keystore/corekeys/solana_test.go new file mode 100644 index 0000000000..86ddeae1cc --- /dev/null +++ b/keystore/corekeys/solana_test.go @@ -0,0 +1,78 @@ +package corekeys + +import ( + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/keystore" +) + +func TestSolanaKeyRoundTrip(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedSolanaKey(ctx, password) + require.NoError(t, err) + require.NotEmpty(t, encryptedKey) + + solanaKeyPath := keystore.NewKeyPath(TypeSolana, nameDefault) + getKeysResp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{solanaKeyPath.String()}, + }) + require.NoError(t, err) + require.Len(t, getKeysResp.Keys, 1) + + storedPublicKey := getKeysResp.Keys[0].KeyInfo.PublicKey + require.NotEmpty(t, storedPublicKey) + + privateKey, err := FromEncryptedSolanaKey(encryptedKey, password) + require.NoError(t, err) + require.NotEmpty(t, privateKey) + + require.Len(t, privateKey, 64) + + derivedPublicKey := ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey) + require.Equal(t, storedPublicKey, []byte(derivedPublicKey)) +} + +func TestSolanaKeyImportWithWrongPassword(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + wrongPassword := "wrong-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedSolanaKey(ctx, password) + require.NoError(t, err) + require.NotNil(t, encryptedKey) + + _, err = FromEncryptedSolanaKey(encryptedKey, wrongPassword) + require.Error(t, err) + require.Contains(t, err.Error(), "could not decrypt data") +} + +func TestSolanaKeyImportInvalidFormat(t *testing.T) { + t.Parallel() + + _, err := FromEncryptedSolanaKey([]byte("invalid json"), "password") + require.Error(t, err) + require.Contains(t, err.Error(), "could not unmarshal import data") +} diff --git a/keystore/corekeys/workflowkey.go b/keystore/corekeys/workflowkey.go new file mode 100644 index 0000000000..4598ebec8b --- /dev/null +++ b/keystore/corekeys/workflowkey.go @@ -0,0 +1,17 @@ +// `corekeys` provides utilities to generate keys that are compatible with the core node +// and can be imported by it. +package corekeys + +import ( + "context" +) + +const TypeWorkflowKey = "workflow" + +func (ks *Store) GenerateEncryptedWorkflowKey(ctx context.Context, password string) ([]byte, error) { + return ks.generateEncryptedKey(ctx, TypeWorkflowKey, password) +} + +func FromEncryptedWorkflowKey(data []byte, password string) ([]byte, error) { + return fromEncryptedKey(data, TypeWorkflowKey, password) +} diff --git a/keystore/corekeys/workflowkey_test.go b/keystore/corekeys/workflowkey_test.go new file mode 100644 index 0000000000..f8e2074016 --- /dev/null +++ b/keystore/corekeys/workflowkey_test.go @@ -0,0 +1,79 @@ +package corekeys + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/curve25519" + + "github.com/smartcontractkit/chainlink-common/keystore" +) + +func TestWorkflowKeyRoundTrip(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedWorkflowKey(ctx, password) + require.NoError(t, err) + require.NotEmpty(t, encryptedKey) + + workflowKeyPath := keystore.NewKeyPath(TypeWorkflowKey, nameDefault) + getKeysResp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{ + KeyNames: []string{workflowKeyPath.String()}, + }) + require.NoError(t, err) + require.Len(t, getKeysResp.Keys, 1) + + storedPublicKey := getKeysResp.Keys[0].KeyInfo.PublicKey + require.NotEmpty(t, storedPublicKey) + + privateKey, err := FromEncryptedWorkflowKey(encryptedKey, password) + require.NoError(t, err) + require.NotEmpty(t, privateKey) + + require.Len(t, privateKey, 32) + + derivedPublicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint) + require.NoError(t, err) + require.Equal(t, storedPublicKey, derivedPublicKey) +} + +func TestWorkflowKeyImportWithWrongPassword(t *testing.T) { + t.Parallel() + ctx := t.Context() + password := "test-password" + wrongPassword := "wrong-password" + + st := keystore.NewMemoryStorage() + ks, err := keystore.LoadKeystore(ctx, st, "test", + keystore.WithScryptParams(keystore.FastScryptParams), + ) + require.NoError(t, err) + + coreshimKs := NewStore(ks) + + encryptedKey, err := coreshimKs.GenerateEncryptedWorkflowKey(ctx, password) + require.NoError(t, err) + require.NotNil(t, encryptedKey) + + _, err = FromEncryptedWorkflowKey(encryptedKey, wrongPassword) + require.Error(t, err) + require.Contains(t, err.Error(), "could not decrypt data") +} + +func TestWorkflowKeyImportInvalidFormat(t *testing.T) { + t.Parallel() + + _, err := FromEncryptedWorkflowKey([]byte("invalid json"), "password") + require.Error(t, err) + require.Contains(t, err.Error(), "could not unmarshal import data") +}