@@ -124,3 +124,62 @@ describe('AesGcmEncryption (AES-GCM) – real Web Crypto', () => {
124124 ) ;
125125 } ) ;
126126} ) ;
127+
128+ // ---------------------------------------------------------------------------
129+ // AES key exchange via RSA-encrypted channel
130+ // ---------------------------------------------------------------------------
131+ describe ( 'AES key exchange via RSA-encrypted channel' , ( ) => {
132+ it ( 'AES key exported by sender can be RSA-encrypted and decrypted by receiver, enabling symmetric decryption' , async ( ) => {
133+ // Simulate Bob (receiver): generate an RSA key pair
134+ const { publicKey : bobPublicKey , privateKey : bobPrivateKey } = await cryptoUtils . generateKeypairs ( ) ;
135+
136+ // Simulate Alice (sender): generate an AES key
137+ const aliceAes = new AesGcmEncryption ( ) ;
138+ await aliceAes . int ( ) ;
139+
140+ // Alice exports her AES key as a JWK string (this is what getRawAesKeyToExport returns)
141+ const aesKeyJwk = await aliceAes . getRawAesKeyToExport ( ) ;
142+
143+ // Alice encrypts the AES key with Bob's RSA public key before sending it to the server
144+ const encryptedAesKey = await cryptoUtils . encryptMessage ( aesKeyJwk , bobPublicKey ) ;
145+ expect ( typeof encryptedAesKey ) . toBe ( 'string' ) ;
146+ expect ( encryptedAesKey ) . not . toBe ( aesKeyJwk ) ; // must be ciphertext, not plaintext
147+
148+ // Bob decrypts the AES key using his RSA private key
149+ const decryptedAesKeyJwk = await cryptoUtils . decryptMessage ( encryptedAesKey , bobPrivateKey ) ;
150+ expect ( decryptedAesKeyJwk ) . toBe ( aesKeyJwk ) ; // recovered plaintext must match original
151+
152+ // Bob sets the decrypted AES key as his remote key
153+ const bobAes = new AesGcmEncryption ( ) ;
154+ await bobAes . setRemoteAesKey ( decryptedAesKeyJwk ) ;
155+
156+ // Alice encrypts some data with her local AES key
157+ const originalText = 'Secret message over AES-GCM 🔐' ;
158+ const { encryptedData, iv } = await aliceAes . encryptData (
159+ new TextEncoder ( ) . encode ( originalText ) . buffer
160+ ) ;
161+
162+ // Bob decrypts the data using the AES key he received through the RSA-encrypted channel
163+ const decryptedBuffer = await bobAes . decryptData ( encryptedData , iv ) ;
164+ const decryptedText = new TextDecoder ( ) . decode ( decryptedBuffer ) ;
165+
166+ expect ( decryptedText ) . toBe ( originalText ) ;
167+ } ) ;
168+
169+ it ( 'a third party cannot decrypt the AES key without the receiver private key' , async ( ) => {
170+ const { publicKey : bobPublicKey } = await cryptoUtils . generateKeypairs ( ) ;
171+ const { privateKey : evePrivateKey } = await cryptoUtils . generateKeypairs ( ) ; // attacker's key
172+
173+ const aliceAes = new AesGcmEncryption ( ) ;
174+ await aliceAes . int ( ) ;
175+ const aesKeyJwk = await aliceAes . getRawAesKeyToExport ( ) ;
176+
177+ // Alice encrypts AES key with Bob's public key
178+ const encryptedAesKey = await cryptoUtils . encryptMessage ( aesKeyJwk , bobPublicKey ) ;
179+
180+ // Eve (third party) tries to decrypt with her own private key and fails
181+ await expect (
182+ cryptoUtils . decryptMessage ( encryptedAesKey , evePrivateKey )
183+ ) . rejects . toThrow ( ) ;
184+ } ) ;
185+ } ) ;
0 commit comments