Browse Source

feat(e2ee) update to SFrame draft -02

Drop end to end signature. The impersonation attack vector is deemed out of
scope since insiders are already part of the meeting.
dev1
Saúl Ibarra Corretgé 4 years ago
parent
commit
cacf6c7b56

+ 6
- 77
modules/e2ee/Context.js View File

@@ -67,9 +67,6 @@ export class Context {
67 67
         this._sendCount = BigInt(0); // eslint-disable-line new-cap
68 68
 
69 69
         this._id = id;
70
-
71
-        this._signatureKey = null;
72
-        this._signatureOptions = null;
73 70
     }
74 71
 
75 72
     /**
@@ -108,34 +105,6 @@ export class Context {
108 105
         this._sendCount = BigInt(0); // eslint-disable-line new-cap
109 106
     }
110 107
 
111
-    /**
112
-     * Sets the public or private key used to sign or verify frames.
113
-     * @param {CryptoKey} public or private CryptoKey object.
114
-     * @param {Object} signature options. Will be passed to sign/verify and need to specify byteLength of the signature.
115
-     *  Defaults to ECDSA with SHA-256 and a byteLength of 132.
116
-     */
117
-    setSignatureKey(key, options = {
118
-        name: 'ECDSA',
119
-        hash: { name: 'SHA-256' },
120
-        byteLength: 132 // Length of the signature.
121
-    }) {
122
-        this._signatureKey = key;
123
-        this._signatureOptions = options;
124
-    }
125
-
126
-    /**
127
-     * Decide whether we should sign a frame.
128
-     * @returns {boolean}
129
-     * @private
130
-     */
131
-    _shouldSignFrame() {
132
-        if (!this._signatureKey) {
133
-            return false;
134
-        }
135
-
136
-        return true;
137
-    }
138
-
139 108
     /**
140 109
      * Function that will be injected in a stream and will encrypt the given encoded frames.
141 110
      *
@@ -186,12 +155,7 @@ export class Context {
186 155
                 }
187 156
             }
188 157
 
189
-
190
-            // If a signature is included, the S bit is set and a fixed number
191
-            // of bytes (depending on the signature algorithm) is inserted between
192
-            // CTR and the trailing byte.
193
-            const signatureLength = this._shouldSignFrame(encodedFrame) ? this._signatureOptions.byteLength : 0;
194
-            const frameTrailer = new Uint8Array(counterLength + signatureLength + 1);
158
+            const frameTrailer = new Uint8Array(counterLength + 1);
195 159
 
196 160
             frameTrailer.set(new Uint8Array(counter.buffer, counter.byteLength - counterLength));
197 161
 
@@ -199,9 +163,6 @@ export class Context {
199 163
             // This is different from the sframe draft, increases the key space and lets us
200 164
             // ignore the case of a zero-length counter at the receiver.
201 165
             frameTrailer[frameTrailer.byteLength - 1] = keyIndex | ((counterLength - 1) << 4);
202
-            if (signatureLength) {
203
-                frameTrailer[frameTrailer.byteLength - 1] |= 0x80; // set the signature bit.
204
-            }
205 166
 
206 167
             // XOR the counter with the saltKey to construct the AES CTR.
207 168
             const saltKey = new DataView(this._cryptoKeyRing[keyIndex].saltKey);
@@ -236,13 +197,6 @@ export class Context {
236 197
                     // Set the truncated authentication tag.
237 198
                     newUint8.set(truncatedAuthTag, UNENCRYPTED_BYTES[encodedFrame.type] + cipherText.byteLength);
238 199
 
239
-                    // Sign with the long-term signature key.
240
-                    if (signatureLength) {
241
-                        const signature = await crypto.subtle.sign(this._signatureOptions,
242
-                            this._signatureKey, truncatedAuthTag);
243
-
244
-                        newUint8.set(new Uint8Array(signature), newUint8.byteLength - signature.byteLength - 1);
245
-                    }
246 200
                     encodedFrame.data = newData;
247 201
 
248 202
                     return controller.enqueue(encodedFrame);
@@ -274,42 +228,17 @@ export class Context {
274 228
 
275 229
         if (this._cryptoKeyRing[this._currentKeyIndex] && this._cryptoKeyRing[keyIndex]) {
276 230
             const counterLength = 1 + ((data[encodedFrame.data.byteLength - 1] >> 4) & 0x7);
277
-            const signatureLength = data[encodedFrame.data.byteLength - 1] & 0x80
278
-                ? this._signatureOptions.byteLength : 0;
279 231
             const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
280 232
 
281 233
             // Extract the truncated authentication tag.
282 234
             const authTagOffset = encodedFrame.data.byteLength - (DIGEST_LENGTH[encodedFrame.type]
283
-                + counterLength + signatureLength + 1);
235
+                + counterLength + 1);
284 236
             const authTag = encodedFrame.data.slice(authTagOffset, authTagOffset
285 237
                 + DIGEST_LENGTH[encodedFrame.type]);
286 238
 
287
-            // Verify the long-term signature of the authentication tag.
288
-            if (signatureLength) {
289
-                if (this._signatureKey) {
290
-                    const signature = data.subarray(data.byteLength - signatureLength - 1, data.byteLength - 1);
291
-                    const validSignature = await crypto.subtle.verify(this._signatureOptions,
292
-                            this._signatureKey, signature, authTag);
293
-
294
-                    if (!validSignature) {
295
-                        // TODO: surface this to the app. We are encrypted but validation failed.
296
-                        console.error('Long-term signature mismatch (or no signature key)');
297
-
298
-                        return;
299
-                    }
300
-
301
-                    // TODO: surface this to the app. We are now encrypted and verified.
302
-                } else {
303
-                    // TODO: surface this to the app. We are now encrypted but can not verify.
304
-                }
305
-
306
-                // Then set signature bytes to 0.
307
-                data.set(new Uint8Array(signatureLength), encodedFrame.data.byteLength - (signatureLength + 1));
308
-            }
309
-
310 239
             // Set authentication tag bytes to 0.
311 240
             data.set(new Uint8Array(DIGEST_LENGTH[encodedFrame.type]), encodedFrame.data.byteLength
312
-                - (DIGEST_LENGTH[encodedFrame.type] + counterLength + signatureLength + 1));
241
+                - (DIGEST_LENGTH[encodedFrame.type] + counterLength + 1));
313 242
 
314 243
             // Do truncated hash comparison of the authentication tag.
315 244
             // If the hash does not match we might have to advance the ratchet a limited number
@@ -350,8 +279,8 @@ export class Context {
350 279
             // Extract the counter.
351 280
             const counter = new Uint8Array(16);
352 281
 
353
-            counter.set(data.slice(encodedFrame.data.byteLength - (counterLength + signatureLength + 1),
354
-                encodedFrame.data.byteLength - (signatureLength + 1)), 16 - counterLength);
282
+            counter.set(data.slice(encodedFrame.data.byteLength - (counterLength + 1),
283
+                encodedFrame.data.byteLength - 1), 16 - counterLength);
355 284
             const counterView = new DataView(counter.buffer);
356 285
 
357 286
             // XOR the counter with the saltKey to construct the AES CTR.
@@ -369,7 +298,7 @@ export class Context {
369 298
             }, this._cryptoKeyRing[keyIndex].encryptionKey, new Uint8Array(encodedFrame.data,
370 299
                     UNENCRYPTED_BYTES[encodedFrame.type],
371 300
                     encodedFrame.data.byteLength - (UNENCRYPTED_BYTES[encodedFrame.type]
372
-                    + DIGEST_LENGTH[encodedFrame.type] + counterLength + signatureLength + 1))
301
+                    + DIGEST_LENGTH[encodedFrame.type] + counterLength + 1))
373 302
             ).then(plainText => {
374 303
                 const newData = new ArrayBuffer(UNENCRYPTED_BYTES[encodedFrame.type] + plainText.byteLength);
375 304
                 const newUint8 = new Uint8Array(newData);

+ 0
- 168
modules/e2ee/Context.spec.js View File

@@ -170,172 +170,4 @@ describe('E2EE Context', () => {
170 170
             await sender.encodeFunction(makeAudioFrame(), sendController);
171 171
         });
172 172
     });
173
-
174
-    describe('E2EE Signature', () => {
175
-        let privateKey;
176
-        let publicKey;
177
-
178
-        // Generated one-time using
179
-        // await crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-521'}, false, ['sign', 'verify']);
180
-        // then exported as JWK. Only use as test vectors.
181
-        const rawPublicKey = {
182
-            crv: 'P-521',
183
-            ext: true,
184
-            key_ops: [ 'verify' ], // eslint-disable-line camelcase
185
-            kty: 'EC',
186
-            x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
187
-            y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
188
-        };
189
-        const rawPrivateKey = {
190
-            crv: 'P-521',
191
-            ext: true,
192
-            key_ops: [ 'sign' ], // eslint-disable-line camelcase
193
-            kty: 'EC',
194
-            d: 'AV3aTIFuO9Zm0SXVlnujUvlvGvyPrY0pEOtX2pxD2JwPvWWoLXfTA052MHhqiii2RORe_7Ivm_PNeBwhYcO04i-K',
195
-            x: 'AEs3y1FyefvjTC6JaJ1s00k5CFnESu5xIofPAmu286Y4UWyx8kB3jTHPKDO8bK81XT2_HbbN9ONm2D5TYCCxoR5r',
196
-            y: 'AZHlLHuSEWM401dy2lo-nu100Hp1ixcYePf9sNboaZruXctvoAt_sAX6MM0NccHx4587yhWfn9NG7fCX60P5KAvA'
197
-        };
198
-
199
-        beforeEach(async () => {
200
-            privateKey = await crypto.subtle.importKey('jwk', rawPrivateKey, { name: 'ECDSA',
201
-                namedCurve: 'P-521' }, false, [ 'sign' ]);
202
-            publicKey = await crypto.subtle.importKey('jwk', rawPublicKey, { name: 'ECDSA',
203
-                namedCurve: 'P-521' }, false, [ 'verify' ]);
204
-
205
-            await sender.setKey(key, 0);
206
-            await receiver.setKey(key, 0);
207
-            sender.setSignatureKey(privateKey);
208
-            receiver.setSignatureKey(publicKey);
209
-        });
210
-
211
-        it('signs the first frame', async done => {
212
-            sendController = {
213
-                enqueue: encodedFrame => {
214
-                    const data = new Uint8Array(encodedFrame.data);
215
-
216
-                    // Check that the signature bit is set.
217
-                    expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
218
-
219
-                    // An audio frame will have an overhead of 6 bytes with this counter and key size:
220
-                    //   4 bytes truncated signature, counter (1 byte) and 1 byte trailer.
221
-                    // In addition to that we have the 132 bytes signature.
222
-                    expect(data.byteLength).toEqual(audioBytes.length + 6 + 132);
223
-
224
-                    // TODO: provide test vector for the signature.
225
-                    done();
226
-                }
227
-            };
228
-            await sender.encodeFunction(makeAudioFrame(), sendController);
229
-        });
230
-
231
-        it('signs subsequent frames from different sources', async done => {
232
-            let frameCount = 0;
233
-
234
-            sendController = {
235
-                enqueue: encodedFrame => {
236
-                    frameCount++;
237
-                    const data = new Uint8Array(encodedFrame.data);
238
-
239
-                    expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
240
-
241
-                    if (frameCount === 2) {
242
-                        done();
243
-                    }
244
-                }
245
-            };
246
-
247
-            await sender.encodeFunction(makeAudioFrame(), sendController);
248
-
249
-            const secondFrame = makeAudioFrame();
250
-
251
-            secondFrame.getMetadata = () => {
252
-                return { synchronizationSource: 456 };
253
-            };
254
-            await sender.encodeFunction(secondFrame, sendController);
255
-        });
256
-
257
-        it('signs subsequent key frames from the same source', async done => {
258
-            let frameCount = 0;
259
-
260
-            sendController = {
261
-                enqueue: encodedFrame => {
262
-                    frameCount++;
263
-                    const data = new Uint8Array(encodedFrame.data);
264
-
265
-                    expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
266
-
267
-                    if (frameCount === 2) {
268
-                        done();
269
-                    }
270
-                }
271
-            };
272
-
273
-            await sender.encodeFunction(makeVideoFrame(), sendController);
274
-            await sender.encodeFunction(makeVideoFrame(), sendController);
275
-        });
276
-
277
-
278
-        it('signs subsequent frames from the same source', async done => {
279
-            let frameCount = 0;
280
-
281
-            sendController = {
282
-                enqueue: encodedFrame => {
283
-                    frameCount++;
284
-                    const data = new Uint8Array(encodedFrame.data);
285
-
286
-                    expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
287
-
288
-                    if (frameCount === 2) {
289
-                        done();
290
-                    }
291
-                }
292
-            };
293
-
294
-            await sender.encodeFunction(makeAudioFrame(), sendController);
295
-            await sender.encodeFunction(makeAudioFrame(), sendController);
296
-        });
297
-
298
-        it('signs after ratcheting the sender key', async done => {
299
-            let frameCount = 0;
300
-
301
-            sendController = {
302
-                enqueue: encodedFrame => {
303
-                    frameCount++;
304
-                    const data = new Uint8Array(encodedFrame.data);
305
-
306
-                    expect(data[data.byteLength - 1] & 0x80).toEqual(0x80);
307
-
308
-                    if (frameCount === 2) {
309
-                        done();
310
-                    }
311
-                }
312
-            };
313
-
314
-            await sender.encodeFunction(makeAudioFrame(), sendController);
315
-
316
-            // Ratchet the key. We reimport from the raw bytes.
317
-            const material = await importKey(key);
318
-
319
-            await sender.setKey(await ratchet(material), 0);
320
-            await sender.encodeFunction(makeAudioFrame(), sendController);
321
-        });
322
-
323
-        it('verifies the frame', async done => {
324
-            sendController = {
325
-                enqueue: async encodedFrame => {
326
-                    await receiver.decodeFunction(encodedFrame, receiveController);
327
-                }
328
-            };
329
-            receiveController = {
330
-                enqueue: encodedFrame => {
331
-                    const data = new Uint8Array(encodedFrame.data);
332
-
333
-                    expect(data.byteLength).toEqual(audioBytes.length);
334
-                    expect(Array.from(data)).toEqual(audioBytes);
335
-                    done();
336
-                }
337
-            };
338
-            await sender.encodeFunction(makeAudioFrame(), sendController);
339
-        });
340
-    });
341 173
 });

+ 0
- 13
modules/e2ee/E2EEContext.js View File

@@ -139,17 +139,4 @@ export default class E2EEcontext {
139 139
             keyIndex
140 140
         });
141 141
     }
142
-
143
-    /**
144
-     * Set the E2EE signature key for the specified participant.
145
-     * @param {string} participantId - the ID of the participant who's key we are setting.
146
-     * @param {CryptoKey} key - the webcrypto key to set.
147
-     */
148
-    setSignatureKey(participantId, key) {
149
-        this._worker.postMessage({
150
-            operation: 'setSignatureKey',
151
-            participantId,
152
-            key
153
-        });
154
-    }
155 142
 }

+ 0
- 32
modules/e2ee/E2EEncryption.js View File

@@ -17,13 +17,6 @@ const logger = getLogger(__filename);
17 17
 // joins or leaves.
18 18
 const DEBOUNCE_PERIOD = 5000;
19 19
 
20
-// We use ECDSA with Curve P-521 for the long-term signing keys. See
21
-//   https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams
22
-const SIGNATURE_OPTIONS = {
23
-    name: 'ECDSA',
24
-    namedCurve: 'P-521'
25
-};
26
-
27 20
 /**
28 21
  * This module integrates {@link E2EEContext} with {@link JitsiConference} in order to enable E2E encryption.
29 22
  */
@@ -39,7 +32,6 @@ export class E2EEncryption {
39 32
         this._enabled = false;
40 33
         this._initialized = false;
41 34
         this._key = undefined;
42
-        this._signatureKeyPair = undefined;
43 35
 
44 36
         this._e2eeCtx = new E2EEContext();
45 37
         this._olmAdapter = new OlmAdapter(conference);
@@ -131,17 +123,6 @@ export class E2EEncryption {
131 123
         this._enabled = enabled;
132 124
 
133 125
         if (!this._initialized && enabled) {
134
-            // Generate a frame signing key pair. Per session currently.
135
-            this._signatureKeyPair = await crypto.subtle.generateKey(SIGNATURE_OPTIONS,
136
-                true, [ 'sign', 'verify' ]);
137
-            this._e2eeCtx.setSignatureKey(this.conference.myUserId(), this._signatureKeyPair.privateKey);
138
-
139
-            // Serialize the JWK of the signing key. Using JSON, might be easy to xml-ify.
140
-            const serializedSigningKey = await crypto.subtle.exportKey('jwk', this._signatureKeyPair.publicKey);
141
-
142
-            // TODO: sign this with the OLM account key.
143
-            this.conference.setLocalParticipantProperty('e2ee.signatureKey', JSON.stringify(serializedSigningKey));
144
-
145 126
             // Need to re-create the peerconnections in order to apply the insertable streams constraint.
146 127
             // TODO: this was necessary due to some audio issues when indertable streams are used
147 128
             // even though encryption is not performed. This should be fixed in the browser eventually.
@@ -268,19 +249,6 @@ export class E2EEncryption {
268 249
         case 'e2ee.idKey':
269 250
             logger.debug(`Participant ${participant.getId()} updated their id key: ${newValue}`);
270 251
             break;
271
-        case 'e2ee.signatureKey':
272
-            logger.debug(`Participant ${participant.getId()} updated their signature key: ${newValue}`);
273
-            if (newValue) {
274
-                const parsed = JSON.parse(newValue);
275
-
276
-                const importedKey = await crypto.subtle.importKey('jwk', parsed, { name: 'ECDSA',
277
-                    namedCurve: parsed.crv }, true, parsed.key_ops);
278
-
279
-                this._e2eeCtx.setSignatureKey(participant.getId(), importedKey);
280
-            } else {
281
-                logger.warn(`e2ee signatureKey for ${participant.getId()} could not be updated with empty value.`);
282
-            }
283
-            break;
284 252
         }
285 253
     }
286 254
 

+ 0
- 10
modules/e2ee/Worker.js View File

@@ -58,16 +58,6 @@ onmessage = async event => {
58 58
         } else {
59 59
             context.setKey(false, keyIndex);
60 60
         }
61
-    } else if (operation === 'setSignatureKey') {
62
-        const { participantId, key, signatureOptions } = event.data;
63
-
64
-        if (!contexts.has(participantId)) {
65
-            contexts.set(participantId, new Context(participantId));
66
-        }
67
-        const context = contexts.get(participantId);
68
-
69
-        context.setSignatureKey(key, signatureOptions);
70
-
71 61
     } else if (operation === 'cleanup') {
72 62
         const { participantId } = event.data;
73 63
 

Loading…
Cancel
Save