|
@@ -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);
|