Skip to content

Commit

Permalink
Add a large number of ClientHello-parsing tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
rfk committed Feb 8, 2019
1 parent a0ada0e commit 43d42c4
Show file tree
Hide file tree
Showing 18 changed files with 1,513 additions and 883 deletions.
893 changes: 497 additions & 396 deletions dist/FxAccountsPairingChannel.babel.umd.js

Large diffs are not rendered by default.

114 changes: 54 additions & 60 deletions dist/FxAccountsPairingChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* This uses the event-target-shim node library published under the MIT license:
* https://github.com/mysticatea/event-target-shim/blob/master/LICENSE
*
* Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:85aa0894331b0889a051, Chunkhash:33548d0281b2c935c528.
* Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:a0d70678bae2e02e9da5, Chunkhash:2d27059daa7566de3760.
*
*/

Expand Down Expand Up @@ -419,27 +419,15 @@ class utils_BufferReader extends utils_BufferWithPointer {
}

readVectorBytes8() {
let bytes;
this.readVector8(buf => {
bytes = buf.readBytes(buf.length());
});
return bytes;
return this.readBytes(this.readUint8());
}

readVectorBytes16() {
let bytes;
this.readVector16(buf => {
bytes = buf.readBytes(buf.length());
});
return bytes;
return this.readBytes(this.readUint16());
}

readVectorBytes24() {
let bytes;
this.readVector24(buf => {
bytes = buf.readBytes(buf.length());
});
return bytes;
return this.readBytes(this.readUint24());
}
}

Expand Down Expand Up @@ -607,8 +595,8 @@ const KEY_LENGTH = 16;
const IV_LENGTH = 12;
const HASH_LENGTH = 32;

async function prepareKey(key, mode) {
return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]);
async function prepareKey(key, modes) {
return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, modes);
}

async function encrypt(key, iv, plaintext, additionalData) {
Expand Down Expand Up @@ -853,13 +841,12 @@ class messages_ClientHello extends messages_HandshakeMessage {
static _read(buf) {
// The legacy_version field may indicate an earlier version of TLS
// for backwards compatibility, but must not predate TLS!

if (buf.readUint16() < VERSION_TLS_1_0) {
throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
}
// The random bytes provided by the peer.
const random = buf.readBytes(32);
// Read legacy_session_id so the server can echo it.
// Read legacy_session_id, so the server can echo it.
const sessionId = buf.readVectorBytes8();
// We only support a single ciphersuite, but the peer may offer several.
// Scan the list to confirm that the one we want is present.
Expand Down Expand Up @@ -960,7 +947,7 @@ class messages_ClientHello extends messages_HandshakeMessage {
break;
default:
// Ignore all other extensions
return true;
buf.incr(buf.length());
}
});
seenExtensions.add(extType);
Expand Down Expand Up @@ -1234,11 +1221,11 @@ class states_State {
}

async recvApplicationData(bytes) {
throw new Error('not ready to receive application data');
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}

async recvHandshakeMessage(msg) {
throw new Error('not expecting to receive a handhake message');
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}

async recvAlertMessage(err) {
Expand All @@ -1255,14 +1242,20 @@ class states_State {
}
}

async recvChangeCipherSpec(bytes) {
// We may receive CHANGE_CIPHER_SPEC records for b/w compat reasons;
// they must contain exactly a single 0x01 byte and must otherwise be ignored.
if (bytes.byteLength !== 1 || bytes[0] !== 1) {
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}
}

async handleError(err) {
let msg = err;
if (! (err instanceof TLSError)) {
msg = new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
}
await this.conn._sendAlertMessage(msg);
this.conn._closeForSend();
this.conn._closeForRecv();
return await this.conn._transition(ERROR, err);
}

Expand Down Expand Up @@ -1305,6 +1298,11 @@ class UNINITIALIZED extends states_State {
class ERROR extends states_State {
async initialize(err) {
this.error = err;
// Unceremoniously shut down the record layer on error.
const recordlayer = this.conn._recordlayer;
recordlayer.send = recordlayer.recv = recordlayer.flush = () => {
throw this.error;
};
}
async sendApplicationData(bytes) {
throw this.error;
Expand All @@ -1321,6 +1319,9 @@ class ERROR extends states_State {
async recvAlertMessage(err) {
throw this.error;
}
async recvChangeCipherSpec(bytes) {
throw this.error;
}
async handleError(err) {
// We've already errored out the connection, don't try to do it again.
}
Expand Down Expand Up @@ -1348,6 +1349,9 @@ class states_CONNECTED extends states_State {
async recvApplicationData(bytes) {
return bytes;
}
async recvChangeCipherSpec(bytes) {
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}
async close() {
await this.conn._sendAlertMessage(new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY));
this.conn._closeForSend();
Expand Down Expand Up @@ -1493,7 +1497,11 @@ class states_SERVER_RECVD_CH extends states_State {
// Validate the PSK binder.
const keyschedule = this.conn._keyschedule;
const transcript = keyschedule.getTranscript();
await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, clientHello.pskBinders[pskIndex], transcript.slice(0, -PSK_BINDERS_SIZE));
let pskBindersSize = 2; // Vector16 reprsentation overhead.
for (const binder of clientHello.pskBinders) {
pskBindersSize += binder.byteLength + 1; // Vector8 representation overhead.
}
await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, clientHello.pskBinders[pskIndex], transcript.slice(0, -pskBindersSize));
await this.conn._transition(states_SERVER_NEGOTIATED, clientHello, pskIndex);
}
}
Expand Down Expand Up @@ -1724,9 +1732,9 @@ class recordlayer_CipherState {
this.seqnum = 0;
}

async setKey(key, mode) {
async setKey(key, modes) {
// Derive key and iv per https://tools.ietf.org/html/rfc8446#section-7.3
this.key = await prepareKey(await hkdfExpandLabel(key, 'key', EMPTY, KEY_LENGTH), mode);
this.key = await prepareKey(await hkdfExpandLabel(key, 'key', EMPTY, KEY_LENGTH), modes);
this.iv = await hkdfExpandLabel(key, 'iv', EMPTY, IV_LENGTH);
this.seqnum = 0;
}
Expand Down Expand Up @@ -1770,26 +1778,14 @@ class recordlayer_RecordLayer {

async setSendKey(key) {
await this.flush();
await this._sendCipherState.setKey(key, 'encrypt');
await this._sendCipherState.setKey(key, ['encrypt']);
}

async setRecvKey(key) {
await this._recvCipherState.setKey(key, 'decrypt');
}

closeForSend() {
this._sendCipherState = null;
}

closeForRecv() {
this._recvCipherState = null;
await this._recvCipherState.setKey(key, ['decrypt']);
}

async send(type, data) {
// Refuse to send any more data after being closed.
if (! this._sendCipherState) {
throw new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY);
}
// Forbid sending data that doesn't fit into a single record.
// We do not support fragmentation over multiple records.
if (data.byteLength > MAX_RECORD_SIZE) {
Expand Down Expand Up @@ -1828,10 +1824,6 @@ class recordlayer_RecordLayer {
if (! type) {
return;
}
// Refuse to send any more data after being closed.
if (! this._sendCipherState) {
throw new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY);
}
let length = buf.tell() - RECORD_HEADER_SIZE;
if (this._sendCipherState.key === null) {
// Refuse to send plaintext application data.
Expand Down Expand Up @@ -1871,11 +1863,6 @@ class recordlayer_RecordLayer {
}

async recv(data) {
// Reject any data received after the peer has closed its connection,
// which might have happened as a result of a previous mesage from the input data.
if (! this._recvCipherState) {
throw new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY);
}
// For simplicity, we assume that the given data conatins exactly one record.
// Peers using this library will send one record at a time over the websocket
// connection, and we can assume that the server-side websocket bridge will split
Expand Down Expand Up @@ -1954,7 +1941,7 @@ class recordlayer_RecordLayer {
}
}
if (i < 0) {
throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}
type = paddedPlaintext[i];
// `change_cipher_spec` records must always be plaintext.
Expand Down Expand Up @@ -2060,11 +2047,7 @@ class tlsconnection_Connection {
// Dispatch based on the type of the record.
switch (type) {
case RECORD_TYPE.CHANGE_CIPHER_SPEC:
// We may receive CHANGE_CIPHER_SPEC records for b/w compat reasons;
// they must contain exactly a single 0x01 byte and must otherwise be ignored.
if (bytes.byteLength !== 1 || bytes[0] !== 1) {
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}
await this._state.recvChangeCipherSpec(bytes);
return null;
case RECORD_TYPE.ALERT:
await this._state.recvAlertMessage(TLSError.fromBytes(bytes));
Expand All @@ -2076,6 +2059,9 @@ class tlsconnection_Connection {
// Store the in-progress record buffer on `this` so that we can guard
// against handshake messages that span a change in keys.
this._handshakeRecvBuffer = new utils_BufferReader(bytes);
if (! this._handshakeRecvBuffer.hasMoreBytes()) {
throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
}
do {
// Each handshake messages has a type and length prefix, per
// https://tools.ietf.org/html/rfc8446#appendix-B.3
Expand Down Expand Up @@ -2172,11 +2158,15 @@ class tlsconnection_Connection {
}

_closeForSend() {
this._recordlayer.closeForSend();
this._recordlayer.send = this._recordlayer.flush = () => {
throw new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY);
};
}

_closeForRecv() {
this._recordlayer.closeForRecv();
this._recordlayer.recv = () => {
throw new TLSError(ALERT_DESCRIPTION.CLOSE_NOTIFY);
};
}
}

Expand Down Expand Up @@ -3061,7 +3051,6 @@ if (

/* harmony default export */ var event_target_shim = (EventTarget);

//# sourceMappingURL=event-target-shim.mjs.map

// CONCATENATED MODULE: ./src/index.js
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PairingChannel", function() { return src_PairingChannel; });
Expand Down Expand Up @@ -3233,11 +3222,16 @@ class src_PairingChannel extends EventTarget {





const _internals = {
BufferReader: utils_BufferReader,
BufferWriter: utils_BufferWriter,
CipherState: recordlayer_CipherState,
ClientConnection: tlsconnection_ClientConnection,
EncryptedExtensions: messages_EncryptedExtensions,
Finished: messages_Finished,
KeySchedule: keyschedule_KeySchedule,
RecordLayer: recordlayer_RecordLayer,
ServerConnection: tlsconnection_ServerConnection,
TLSError: TLSError,
Expand Down
4 changes: 2 additions & 2 deletions src/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const KEY_LENGTH = 16;
export const IV_LENGTH = 12;
export const HASH_LENGTH = 32;

export async function prepareKey(key, mode) {
return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]);
export async function prepareKey(key, modes) {
return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, modes);
}

export async function encrypt(key, iv, plaintext, additionalData) {
Expand Down
5 changes: 5 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,18 @@ export { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes };
// expose a bunch of implementation details.

import { TLSError } from './alerts.js';
import { KeySchedule } from './keyschedule.js';
import { CipherState, RecordLayer } from './recordlayer.js';
import { EncryptedExtensions, Finished } from './messages.js';
import { BufferReader, BufferWriter, arrayToBytes, bytesAreEqual, zeros } from './utils.js';
export const _internals = {
BufferReader,
BufferWriter,
CipherState,
ClientConnection,
EncryptedExtensions,
Finished,
KeySchedule,
RecordLayer,
ServerConnection,
TLSError,
Expand Down
5 changes: 2 additions & 3 deletions src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,12 @@ export class ClientHello extends HandshakeMessage {
static _read(buf) {
// The legacy_version field may indicate an earlier version of TLS
// for backwards compatibility, but must not predate TLS!

if (buf.readUint16() < VERSION_TLS_1_0) {
throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
}
// The random bytes provided by the peer.
const random = buf.readBytes(32);
// Read legacy_session_id so the server can echo it.
// Read legacy_session_id, so the server can echo it.
const sessionId = buf.readVectorBytes8();
// We only support a single ciphersuite, but the peer may offer several.
// Scan the list to confirm that the one we want is present.
Expand Down Expand Up @@ -246,7 +245,7 @@ export class ClientHello extends HandshakeMessage {
break;
default:
// Ignore all other extensions
return true;
buf.incr(buf.length());
}
});
seenExtensions.add(extType);
Expand Down
Loading

0 comments on commit 43d42c4

Please sign in to comment.