1+ /**
2+ * Interface for pluggable symmetric encryption strategies.
3+ * Implement this to swap in any symmetric cipher (e.g. AES-GCM, ChaCha20-Poly1305).
4+ */
15export interface ISymmetricEncryption {
26 /** Generate / initialise the local encryption key. Idempotent. */
37 init ( ) : Promise < void > ;
@@ -12,99 +16,63 @@ export interface ISymmetricEncryption {
1216}
1317
1418/**
15- * AES-GCM encryption using ECDH X25519 for key exchange .
16- * Both peers derive the same AES-256-GCM key independently — the raw key is never transmitted .
19+ * AES-256- GCM implementation of ISymmetricEncryption .
20+ * Used for encrypting Audio/Video WebRTC streams .
1721 */
1822export class AesGcmEncryption implements ISymmetricEncryption {
19- private ecdhPrivateKey ?: CryptoKey ;
20- private ecdhPublicKey ?: CryptoKey ;
21- private sharedAesKey ?: CryptoKey ;
23+ private aesKeyLocal ?: CryptoKey ;
24+ private aesKeyRemote ?: CryptoKey ;
2225
2326 public async init ( ) : Promise < void > {
24- if ( this . ecdhPrivateKey ) {
27+ if ( this . aesKeyLocal ) {
2528 return ;
2629 }
27- // Generate ECDH key pair
28- const keyPair = await globalThis . crypto . subtle . generateKey (
29- { name : "X25519" } ,
30- true , // extractable
31- [ "deriveKey" , "deriveBits" ]
32- ) as CryptoKeyPair ;
33-
34- this . ecdhPrivateKey = keyPair . privateKey ;
35- this . ecdhPublicKey = keyPair . publicKey ;
36- }
37-
38- public getRemoteAesKey ( ) : CryptoKey {
39- if ( ! this . sharedAesKey ) {
40- throw new Error ( 'Shared AES key not derived' ) ;
41- }
42- return this . sharedAesKey ;
30+ this . aesKeyLocal = await window . crypto . subtle . generateKey (
31+ { name : "AES-GCM" , length : 256 } ,
32+ true ,
33+ [ "encrypt" , "decrypt" ]
34+ ) ;
4335 }
4436
45- public async getRawAesKeyToExport ( ) : Promise < string > {
46- if ( ! this . ecdhPublicKey ) {
47- throw new Error ( 'ECDH keys not generated' ) ;
37+ public async exportKey ( ) : Promise < string > {
38+ if ( ! this . aesKeyLocal ) {
39+ throw new Error ( 'AES key not generated' ) ;
4840 }
49- const jsonWebKey = await globalThis . crypto . subtle . exportKey ( "jwk" , this . ecdhPublicKey ) ;
41+ const jsonWebKey = await crypto . subtle . exportKey ( "jwk" , this . aesKeyLocal ) ;
5042 return JSON . stringify ( jsonWebKey ) ;
5143 }
5244
53- /** Satisfies ISymmetricEncryption interface — delegates to getRawAesKeyToExport */
54- public exportKey ( ) : Promise < string > {
55- return this . getRawAesKeyToExport ( ) ;
56- }
57-
58- public async setRemoteAesKey ( key : string ) : Promise < void > {
59- if ( ! this . ecdhPrivateKey ) {
60- throw new Error ( 'Local ECDH private key not generated' ) ;
61- }
45+ public async importRemoteKey ( key : string ) : Promise < void > {
6246 const jsonWebKey = JSON . parse ( key ) ;
63- const remotePublicKey = await globalThis . crypto . subtle . importKey (
47+ this . aesKeyRemote = await crypto . subtle . importKey (
6448 "jwk" ,
6549 jsonWebKey ,
66- { name : "X25519 " } ,
50+ { name : "AES-GCM " } ,
6751 true ,
68- [ ] // public keys don't require key usages for derivation
52+ [ "decrypt" ]
6953 ) ;
70-
71- // Derive shared AES-GCM key — never leaves the device
72- this . sharedAesKey = await globalThis . crypto . subtle . deriveKey (
73- { name : "X25519" , public : remotePublicKey } ,
74- this . ecdhPrivateKey ,
75- { name : "AES-GCM" , length : 256 } ,
76- false , // AES key is never extractable/transmitted
77- [ "encrypt" , "decrypt" ]
78- ) ;
79- }
80-
81- /** Satisfies ISymmetricEncryption interface — delegates to setRemoteAesKey */
82- public importRemoteKey ( key : string ) : Promise < void > {
83- return this . setRemoteAesKey ( key ) ;
8454 }
8555
8656 public async encryptData ( data : ArrayBuffer ) : Promise < { encryptedData : Uint8Array < ArrayBuffer > ; iv : Uint8Array < ArrayBuffer > } > {
87- if ( ! this . sharedAesKey ) {
88- throw new Error ( 'Shared AES key not derived .' ) ;
57+ if ( ! this . aesKeyLocal ) {
58+ throw new Error ( 'Local AES key not generated .' ) ;
8959 }
90- // Generate an Initialization Vector (IV) for AES-GCM (12 bytes)
91- const iv = globalThis . crypto . getRandomValues ( new Uint8Array ( 12 ) ) ;
92- // Encrypt the frame data using AES-GCM
93- const encryptedData = await globalThis . crypto . subtle . encrypt (
60+ const iv = crypto . getRandomValues ( new Uint8Array ( 12 ) ) ;
61+ const encryptedData = await crypto . subtle . encrypt (
9462 { name : "AES-GCM" , iv } ,
95- this . sharedAesKey ,
63+ this . aesKeyLocal ,
9664 data
9765 ) ;
9866 return { encryptedData : new Uint8Array ( encryptedData ) , iv } ;
9967 }
10068
10169 public async decryptData ( data : BufferSource , iv : BufferSource ) : Promise < ArrayBuffer > {
102- if ( ! this . sharedAesKey ) {
103- throw new Error ( 'Shared AES key not derived .' ) ;
70+ if ( ! this . aesKeyRemote ) {
71+ throw new Error ( 'Remote AES key not set .' ) ;
10472 }
105- return globalThis . crypto . subtle . decrypt (
73+ return crypto . subtle . decrypt (
10674 { name : "AES-GCM" , iv } ,
107- this . sharedAesKey ,
75+ this . aesKeyRemote ,
10876 data
10977 ) ;
11078 }
0 commit comments