@@ -2,12 +2,26 @@ package keystore
22
33import (
44 "context"
5+ "crypto/aes"
6+ "crypto/cipher"
7+ "crypto/ecdh"
8+ "crypto/rand"
9+ "crypto/sha256"
10+ "encoding/json"
511 "fmt"
12+ "io"
13+
14+ "golang.org/x/crypto/curve25519"
15+ "golang.org/x/crypto/hkdf"
16+ "golang.org/x/crypto/nacl/box"
17+
18+ "github.com/smartcontractkit/chainlink-common/keystore/internal"
619)
720
821type EncryptRequest struct {
9- KeyName string
10- Data []byte
22+ KeyName string
23+ RemotePubKey []byte
24+ Data []byte
1125}
1226
1327type EncryptResponse struct {
@@ -25,13 +39,50 @@ type DecryptResponse struct {
2539
2640type DeriveSharedSecretRequest struct {
2741 LocalKeyName string
28- RemotePubKey []byte // Maybe this naming is confusing?
42+ RemotePubKey []byte
2943}
3044
3145type DeriveSharedSecretResponse struct {
3246 SharedSecret []byte
3347}
3448
49+ const (
50+ aesGCMNonceSize = 12
51+ hkdfSaltSize = 16
52+
53+ // ciphertext framing for EcdhP256 + HKDF-SHA256 + AES-GCM
54+ // [1B version=1][2B ephLen][ephPub][1B saltLen][salt][1B nonceLen][nonce][ciphertext]
55+ encVersionV1 byte = 1
56+
57+ algP256HKDFAESGCM = "ecdh-p256+hkdf-sha256+aes-256-gcm"
58+ )
59+
60+ func hkdfAESGCMKey (sharedSecret , salt , info []byte , keyLen int ) ([]byte , error ) {
61+ r := hkdf .New (sha256 .New , sharedSecret , salt , info )
62+ key := make ([]byte , keyLen )
63+ if _ , err := io .ReadFull (r , key ); err != nil {
64+ return nil , fmt .Errorf ("hkdf: %w" , err )
65+ }
66+ return key , nil
67+ }
68+
69+ type encAAD struct {
70+ V byte `json:"v"`
71+ Alg string `json:"alg"`
72+ EPK []byte `json:"epk"`
73+ Salt []byte `json:"salt"`
74+ Nonce []byte `json:"nonce"`
75+ }
76+
77+ type encEnvelope struct {
78+ V byte `json:"v"`
79+ Alg string `json:"alg"`
80+ EPK []byte `json:"epk"`
81+ Salt []byte `json:"salt"`
82+ Nonce []byte `json:"nonce"`
83+ CT []byte `json:"ct"`
84+ }
85+
3586// Encryptor is an interfaces for hybrid encryption (key exchange + encryption) operations.
3687// WARNING: Using the shared secret should only be used directly in
3788// cases where very custom encryption schemes are needed and you know
@@ -57,15 +108,199 @@ func (UnimplementedEncryptor) DeriveSharedSecret(ctx context.Context, req Derive
57108 return DeriveSharedSecretResponse {}, fmt .Errorf ("Encryptor.DeriveSharedSecret: %w" , ErrUnimplemented )
58109}
59110
60- // TODO: Encryptor implementation.
61111func (k * keystore ) Encrypt (ctx context.Context , req EncryptRequest ) (EncryptResponse , error ) {
62- return EncryptResponse {}, nil
112+ k .mu .RLock ()
113+ defer k .mu .RUnlock ()
114+
115+ key , ok := k .keystore [req .KeyName ]
116+ if ! ok {
117+ return EncryptResponse {}, fmt .Errorf ("key not found: %s" , req .KeyName )
118+ }
119+ switch key .keyType {
120+ case X25519 :
121+ if len (req .RemotePubKey ) != 32 {
122+ return EncryptResponse {}, fmt .Errorf ("remote public key must be 32 bytes for X25519" )
123+ }
124+ encrypted , err := box .SealAnonymous (nil , req .Data , (* [32 ]byte )(req .RemotePubKey ), rand .Reader )
125+ if err != nil {
126+ return EncryptResponse {}, fmt .Errorf ("failed to encrypt data: %w" , err )
127+ }
128+ return EncryptResponse {
129+ EncryptedData : encrypted ,
130+ }, nil
131+ case EcdhP256 :
132+ curve := ecdh .P256 ()
133+ if len (req .RemotePubKey ) == 0 {
134+ return EncryptResponse {}, fmt .Errorf ("remote public key required for EcdhP256" )
135+ }
136+ recipientPub , err := curve .NewPublicKey (req .RemotePubKey )
137+ if err != nil {
138+ return EncryptResponse {}, fmt .Errorf ("invalid P-256 public key: %w" , err )
139+ }
140+ // Ephemeral key pair
141+ ephPriv , err := curve .GenerateKey (rand .Reader )
142+ if err != nil {
143+ return EncryptResponse {}, fmt .Errorf ("failed to generate ephemeral key: %w" , err )
144+ }
145+ shared , err := ephPriv .ECDH (recipientPub )
146+ if err != nil {
147+ return EncryptResponse {}, fmt .Errorf ("ecdh failed: %w" , err )
148+ }
149+ // Derive AES-256-GCM key
150+ salt := make ([]byte , hkdfSaltSize )
151+ if _ , err := rand .Read (salt ); err != nil {
152+ return EncryptResponse {}, fmt .Errorf ("salt generation failed: %w" , err )
153+ }
154+ info := []byte ("keystore:ecdh-p256:aes-gcm:hkdf-sha256:v1" )
155+ aeadKey , err := hkdfAESGCMKey (shared , salt , info , 32 )
156+ if err != nil {
157+ return EncryptResponse {}, err
158+ }
159+ block , err := aes .NewCipher (aeadKey )
160+ if err != nil {
161+ return EncryptResponse {}, fmt .Errorf ("aes: %w" , err )
162+ }
163+ gcm , err := cipher .NewGCM (block )
164+ if err != nil {
165+ return EncryptResponse {}, fmt .Errorf ("gcm: %w" , err )
166+ }
167+ nonce := make ([]byte , aesGCMNonceSize )
168+ if _ , err := rand .Read (nonce ); err != nil {
169+ return EncryptResponse {}, fmt .Errorf ("nonce generation failed: %w" , err )
170+ }
171+ ephPub := ephPriv .PublicKey ().Bytes ()
172+ head := encAAD {
173+ V : encVersionV1 ,
174+ Alg : algP256HKDFAESGCM ,
175+ EPK : ephPub ,
176+ Salt : salt ,
177+ Nonce : nonce ,
178+ }
179+ aadBytes , err := json .Marshal (head )
180+ if err != nil {
181+ return EncryptResponse {}, fmt .Errorf ("aad marshal: %w" , err )
182+ }
183+ ciphertext := gcm .Seal (nil , nonce , req .Data , aadBytes )
184+ env := encEnvelope {
185+ V : encVersionV1 ,
186+ Alg : algP256HKDFAESGCM ,
187+ EPK : ephPub ,
188+ Salt : salt ,
189+ Nonce : nonce ,
190+ CT : ciphertext ,
191+ }
192+ out , err := json .Marshal (env )
193+ if err != nil {
194+ return EncryptResponse {}, fmt .Errorf ("envelope marshal: %w" , err )
195+ }
196+ return EncryptResponse {EncryptedData : out }, nil
197+ default :
198+ return EncryptResponse {}, fmt .Errorf ("unsupported key type: %s" , key .keyType )
199+ }
63200}
64201
65202func (k * keystore ) Decrypt (ctx context.Context , req DecryptRequest ) (DecryptResponse , error ) {
66- return DecryptResponse {}, nil
203+ k .mu .RLock ()
204+ defer k .mu .RUnlock ()
205+
206+ key , ok := k .keystore [req .KeyName ]
207+ if ! ok {
208+ return DecryptResponse {}, fmt .Errorf ("key not found: %s" , req .KeyName )
209+ }
210+ switch key .keyType {
211+ case X25519 :
212+ decrypted , ok := box .OpenAnonymous (nil , req .EncryptedData , (* [32 ]byte )(key .publicKey ), (* [32 ]byte )(internal .Bytes (key .privateKey )))
213+ if ! ok {
214+ return DecryptResponse {}, fmt .Errorf ("failed to decrypt data" )
215+ }
216+ return DecryptResponse {
217+ Data : decrypted ,
218+ }, nil
219+ case EcdhP256 :
220+ var env encEnvelope
221+ if err := json .Unmarshal (req .EncryptedData , & env ); err != nil {
222+ return DecryptResponse {}, fmt .Errorf ("envelope unmarshal: %w" , err )
223+ }
224+ if env .V != encVersionV1 || env .Alg != algP256HKDFAESGCM {
225+ return DecryptResponse {}, fmt .Errorf ("unsupported envelope version/alg" )
226+ }
227+ curve := ecdh .P256 ()
228+ priv , err := curve .NewPrivateKey (internal .Bytes (key .privateKey ))
229+ if err != nil {
230+ return DecryptResponse {}, fmt .Errorf ("invalid P-256 private key: %w" , err )
231+ }
232+ ephPub , err := curve .NewPublicKey (env .EPK )
233+ if err != nil {
234+ return DecryptResponse {}, fmt .Errorf ("invalid P-256 ephemeral public key: %w" , err )
235+ }
236+ shared , err := priv .ECDH (ephPub )
237+ if err != nil {
238+ return DecryptResponse {}, fmt .Errorf ("ecdh failed: %w" , err )
239+ }
240+ info := []byte ("keystore:ecdh-p256:aes-gcm:hkdf-sha256:v1" )
241+ aeadKey , err := hkdfAESGCMKey (shared , env .Salt , info , 32 )
242+ if err != nil {
243+ return DecryptResponse {}, err
244+ }
245+ block , err := aes .NewCipher (aeadKey )
246+ if err != nil {
247+ return DecryptResponse {}, fmt .Errorf ("aes: %w" , err )
248+ }
249+ gcm , err := cipher .NewGCM (block )
250+ if err != nil {
251+ return DecryptResponse {}, fmt .Errorf ("gcm: %w" , err )
252+ }
253+ aad := encAAD {V : env .V , Alg : env .Alg , EPK : env .EPK , Salt : env .Salt , Nonce : env .Nonce }
254+ aadBytes , err := json .Marshal (aad )
255+ if err != nil {
256+ return DecryptResponse {}, fmt .Errorf ("aad marshal: %w" , err )
257+ }
258+ pt , err := gcm .Open (nil , env .Nonce , env .CT , aadBytes )
259+ if err != nil {
260+ return DecryptResponse {}, fmt .Errorf ("gcm open: %w" , err )
261+ }
262+ return DecryptResponse {Data : pt }, nil
263+ default :
264+ return DecryptResponse {}, fmt .Errorf ("unsupported key type: %s" , key .keyType )
265+ }
67266}
68267
69268func (k * keystore ) DeriveSharedSecret (ctx context.Context , req DeriveSharedSecretRequest ) (DeriveSharedSecretResponse , error ) {
70- return DeriveSharedSecretResponse {}, nil
269+ k .mu .RLock ()
270+ defer k .mu .RUnlock ()
271+
272+ key , ok := k .keystore [req .LocalKeyName ]
273+ if ! ok {
274+ return DeriveSharedSecretResponse {}, fmt .Errorf ("key not found: %s" , req .LocalKeyName )
275+ }
276+ switch key .keyType {
277+ case X25519 :
278+ if len (req .RemotePubKey ) != 32 {
279+ return DeriveSharedSecretResponse {}, fmt .Errorf ("remote public key must be 32 bytes" )
280+ }
281+ sharedSecret , err := curve25519 .X25519 (internal .Bytes (key .privateKey ), req .RemotePubKey )
282+ if err != nil {
283+ return DeriveSharedSecretResponse {}, fmt .Errorf ("failed to derive shared secret: %w" , err )
284+ }
285+ return DeriveSharedSecretResponse {
286+ SharedSecret : sharedSecret ,
287+ }, nil
288+ case EcdhP256 :
289+ curve := ecdh .P256 ()
290+ priv , err := curve .NewPrivateKey (internal .Bytes (key .privateKey ))
291+ if err != nil {
292+ return DeriveSharedSecretResponse {}, fmt .Errorf ("invalid P-256 private key: %w" , err )
293+ }
294+ remotePub , err := curve .NewPublicKey (req .RemotePubKey )
295+ if err != nil {
296+ return DeriveSharedSecretResponse {}, fmt .Errorf ("invalid P-256 public key: %w" , err )
297+ }
298+ shared , err := priv .ECDH (remotePub )
299+ if err != nil {
300+ return DeriveSharedSecretResponse {}, fmt .Errorf ("ecdh failed: %w" , err )
301+ }
302+ return DeriveSharedSecretResponse {SharedSecret : shared }, nil
303+ default :
304+ return DeriveSharedSecretResponse {}, fmt .Errorf ("unsupported key type: %s" , key .keyType )
305+ }
71306}
0 commit comments