Skip to content

Commit

Permalink
Now explaining session tickets, and using plurals better
Browse files Browse the repository at this point in the history
  • Loading branch information
jawj committed Sep 14, 2023
1 parent 74c46f7 commit 11259d9
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 79 deletions.
108 changes: 70 additions & 38 deletions docs/index.js

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions src/tls/makeClientHello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export default function makeClientHello(host: string, publicKey: Uint8Array, ses
const h = new Bytes(1024);

h.writeUint8(0x16, chatty && 'record type: handshake');
h.writeUint16(0x0301, chatty && 'TLS legacy record version 1.0 ([RFC8446 §5.1](https://datatracker.ietf.org/doc/html/rfc8446#section-5.1))');
h.writeUint16(0x0301, chatty && 'TLS legacy record version 1.0 ([RFC 8446 §5.1](https://datatracker.ietf.org/doc/html/rfc8446#section-5.1))');

const endRecordHeader = h.writeLengthUint16();
const endRecordHeader = h.writeLengthUint16('TLS record');
h.writeUint8(0x01, chatty && 'handshake type: client hello');

const endHandshakeHeader = h.writeLengthUint24();
Expand All @@ -17,21 +17,21 @@ export default function makeClientHello(host: string, publicKey: Uint8Array, ses

const endSessionId = h.writeLengthUint8(chatty && 'session ID');
h.writeBytes(sessionId);
chatty && h.comment('session ID (middlebox compatibility again: [RFC8446 appendix D4](https://datatracker.ietf.org/doc/html/rfc8446#appendix-D.4))');
chatty && h.comment('session ID (middlebox compatibility again: [RFC 8446 appendix D4](https://datatracker.ietf.org/doc/html/rfc8446#appendix-D.4))');
endSessionId();

const endCiphers = h.writeLengthUint16(chatty && 'ciphers ([RFC8446 appendix B4](https://datatracker.ietf.org/doc/html/rfc8446#appendix-B.4))');
const endCiphers = h.writeLengthUint16(chatty && 'ciphers ([RFC 8446 appendix B4](https://datatracker.ietf.org/doc/html/rfc8446#appendix-B.4))');
h.writeUint16(0x1301, chatty && 'cipher: TLS_AES_128_GCM_SHA256');
endCiphers();

const endCompressionMethods = h.writeLengthUint8(chatty && 'compression methods');
h.writeUint8(0x00, chatty && 'compression method: none');
endCompressionMethods();

const endExtensions = h.writeLengthUint16(chatty && 'extensions ([RFC8446 §4.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2))');
const endExtensions = h.writeLengthUint16(chatty && 'extensions ([RFC 8446 §4.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2))');

if (useSNI) {
h.writeUint16(0x0000, chatty && 'extension type: SNI ([RFC6066 §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3))');
h.writeUint16(0x0000, chatty && 'extension type: SNI ([RFC 6066 §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3))');
const endSNIExt = h.writeLengthUint16(chatty && 'SNI data');
const endSNI = h.writeLengthUint16(chatty && 'SNI records');
h.writeUint8(0x00, chatty && 'list entry type: DNS hostname');
Expand All @@ -42,42 +42,42 @@ export default function makeClientHello(host: string, publicKey: Uint8Array, ses
endSNIExt();
}

h.writeUint16(0x000b, chatty && 'extension type: EC point formats (for middlebox compatibility, from TLS 1.2: [RFC8422 §5.1.2](https://datatracker.ietf.org/doc/html/rfc8422#section-5.1.2))');
h.writeUint16(0x000b, chatty && 'extension type: EC point formats (for middlebox compatibility, from TLS 1.2: [RFC 8422 §5.1.2](https://datatracker.ietf.org/doc/html/rfc8422#section-5.1.2))');
const endFormatTypesExt = h.writeLengthUint16(chatty && 'formats data');
const endFormatTypes = h.writeLengthUint8(chatty && 'formats');
h.writeUint8(0x00, chatty && 'format: uncompressed');
endFormatTypes();
endFormatTypesExt()

h.writeUint16(0x000a, chatty && 'extension type: supported groups ([RFC8446 §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7))');
h.writeUint16(0x000a, chatty && 'extension type: supported groups ([RFC 8446 §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7))');
const endGroupsExt = h.writeLengthUint16(chatty && 'groups data');
const endGroups = h.writeLengthUint16(chatty && 'groups');
h.writeUint16(0x0017, chatty && 'curve secp256r1');
endGroups();
endGroupsExt();

h.writeUint16(0x000d, chatty && 'extension type: signature algorithms ([RFC8446 §4.2.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3))');
h.writeUint16(0x000d, chatty && 'extension type: signature algorithms ([RFC 8446 §4.2.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3))');
const endSigsExt = h.writeLengthUint16(chatty && 'signature algorithms data');
const endSigs = h.writeLengthUint16(chatty && 'signature algorithms');
h.writeUint16(0x0403, chatty && 'ecdsa_secp256r1_sha256');
h.writeUint16(0x0804, chatty && 'rsa_pss_rsae_sha256');
endSigs();
endSigsExt();

h.writeUint16(0x002b, chatty && 'extension type: supported TLS versions ([RFC8446 §4.2.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.1))');
h.writeUint16(0x002b, chatty && 'extension type: supported TLS versions ([RFC 8446 §4.2.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.1))');
const endVersionsExt = h.writeLengthUint16(chatty && 'TLS versions data');
const endVersions = h.writeLengthUint8(chatty && 'TLS versions');
h.writeUint16(0x0304, chatty && 'TLS version 1.3');
endVersions();
endVersionsExt();

h.writeUint16(0x0033, chatty && 'extension type: key share ([RFC8446 §4.2.8](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8))');
h.writeUint16(0x0033, chatty && 'extension type: key share ([RFC 8446 §4.2.8](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8))');
const endKeyShareExt = h.writeLengthUint16(chatty && 'key share data');
const endKeyShares = h.writeLengthUint16(chatty && 'key shares');
h.writeUint16(0x0017, chatty && 'secp256r1 (NIST P-256) key share ([RFC8446 §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7))');
h.writeUint16(0x0017, chatty && 'secp256r1 (NIST P-256) key share ([RFC 8446 §4.2.7](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7))');
const endKeyShare = h.writeLengthUint16(chatty && 'key share');
if (chatty) {
h.writeUint8(publicKey[0], 'legacy point format: always 4, which means uncompressed ([RFC8446 §4.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC8422 §5.4.1](https://datatracker.ietf.org/doc/html/rfc8422#section-5.4.1))');
h.writeUint8(publicKey[0], 'legacy point format: always 4, which means uncompressed ([RFC 8446 §4.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC 8422 §5.4.1](https://datatracker.ietf.org/doc/html/rfc8422#section-5.4.1))');
h.writeBytes(publicKey.subarray(1, 33));
h.comment('x coordinate');
h.writeBytes(publicKey.subarray(33, 65));
Expand Down
2 changes: 1 addition & 1 deletion src/tls/parseServerHello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function parseServerHello(hello: Bytes, sessionId: Uint8Array) {
const keyShareLength = keyShareRemaining();
if (keyShareLength !== 65) throw new Error(`Expected 65 bytes of key share, but got ${keyShareLength}`);
if (chatty) {
hello.expectUint8(4, 'legacy point format: always 4, which means uncompressed ([RFC8446 §4.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC8422 §5.4.1](https://datatracker.ietf.org/doc/html/rfc8422#section-5.4.1))')
hello.expectUint8(4, 'legacy point format: always 4, which means uncompressed ([RFC 8446 §4.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC 8422 §5.4.1](https://datatracker.ietf.org/doc/html/rfc8422#section-5.4.1))')
const x = hello.readBytes(32);
hello.comment('x coordinate');
const y = hello.readBytes(32);
Expand Down
14 changes: 7 additions & 7 deletions src/tls/readEncryptedHandshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function readEncryptedHandshake(
) {
const hs = new ASN1Bytes(await readHandshakeRecord());

hs.expectUint8(0x08, chatty && 'handshake record type: encrypted extensions ([RFC8446 §4.3.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.1))');
hs.expectUint8(0x08, chatty && 'handshake record type: encrypted extensions ([RFC 8446 §4.3.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.1))');
const [eeMessageEnd] = hs.expectLengthUint24();
const [extEnd, extRemaining] = hs.expectLengthUint16(chatty && 'extensions');

Expand All @@ -41,7 +41,7 @@ export async function readEncryptedHandshake(
- https://datatracker.ietf.org/doc/html/rfc6066#section-3
*/
chatty && hs.comment('SNI');
hs.expectUint16(0x0000, chatty && 'no extension data ([RFC6066 §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3))');
hs.expectUint16(0x0000, chatty && 'no extension data ([RFC 6066 §3](https://datatracker.ietf.org/doc/html/rfc6066#section-3))');

} else if (extType === 0x000a) {
/*
Expand All @@ -58,7 +58,7 @@ export async function readEncryptedHandshake(
regardless of whether they are currently supported by the client.
- https://www.rfc-editor.org/rfc/rfc8446#section-4.2
*/
chatty && hs.comment('supported groups ([RFC8446 §4.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2))');
chatty && hs.comment('supported groups ([RFC 8446 §4.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2))');
const [endGroups, groupsRemaining] = hs.expectLengthUint16('groups data');
hs.skip(groupsRemaining(), chatty && 'ignored');
endGroups()
Expand All @@ -77,7 +77,7 @@ export async function readEncryptedHandshake(
// certificate request (unusual)
let certMsgType = hs.readUint8();
if (certMsgType === 0x0d) {
chatty && hs.comment('handshake record type: certificate request ([RFC8446 §4.3.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.2))');
chatty && hs.comment('handshake record type: certificate request ([RFC 8446 §4.3.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.2))');
clientCertRequested = true;

const [endCertReq] = hs.expectLengthUint24('certificate request data');
Expand All @@ -97,7 +97,7 @@ export async function readEncryptedHandshake(

// certificates
if (certMsgType !== 0x0b) throw new Error(`Unexpected handshake message type 0x${hexFromU8([certMsgType])}`);
chatty && hs.comment('handshake record type: certificate ([RFC8446 §4.4.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2))');
chatty && hs.comment('handshake record type: certificate ([RFC 8446 §4.4.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2))');
const [endCertPayload] = hs.expectLengthUint24(chatty && 'certificate payload');

hs.expectUint8(0x00, chatty && '0 bytes of request context follow');
Expand Down Expand Up @@ -129,7 +129,7 @@ export async function readEncryptedHandshake(

if (hs.remaining() === 0) hs.extend(await readHandshakeRecord());

hs.expectUint8(0x0f, chatty && 'handshake message type: certificate verify ([RFC8446 §4.4.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.3))');
hs.expectUint8(0x0f, chatty && 'handshake message type: certificate verify ([RFC 8446 §4.4.3](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.3))');
const [endCertVerifyPayload] = hs.expectLengthUint24(chatty && 'handshake message data');
const sigType = hs.readUint16();

Expand Down Expand Up @@ -179,7 +179,7 @@ export async function readEncryptedHandshake(

if (hs.remaining() === 0) hs.extend(await readHandshakeRecord());

hs.expectUint8(0x14, chatty && 'handshake message type: finished ([RFC8446 §4.4.4](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.4))');
hs.expectUint8(0x14, chatty && 'handshake message type: finished ([RFC 8446 §4.4.4](https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.4))');
const [endHsFinishedPayload, hsFinishedPayloadRemaining] = hs.expectLengthUint24(chatty && 'verify hash');
const verifyHash = hs.readBytes(hsFinishedPayloadRemaining());
chatty && hs.comment('verify hash');
Expand Down
38 changes: 38 additions & 0 deletions src/tls/sessionTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Bytes } from '../util/bytes';
import { log } from '../presentation/log';
import { highlightBytes } from '../presentation/highlights';
import { LogColours } from '../presentation/appearance';

export function parseSessionTicket(record: Uint8Array) {
if (chatty) {
const ticket = new Bytes(record);
ticket.expectUint8(0x04, 'session ticket message ([RFC 8846 §4.6.1](https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.1))');

const [endTicketRecord] = ticket.expectLengthUint24('session ticket message');

const ticketSeconds = ticket.readUint32();
ticket.comment(`ticket lifetime in seconds: ${ticketSeconds} = ${ticketSeconds / 3600} hours`)

ticket.readUint32('ticket age add');

const [endTicketNonce, ticketNonceRemaining] = ticket.expectLengthUint8('ticket nonce');
ticket.readBytes(ticketNonceRemaining());
ticket.comment('ticket nonce');
endTicketNonce();

const [endTicket, ticketRemaining] = ticket.expectLengthUint16('ticket');
ticket.readBytes(ticketRemaining());
ticket.comment('ticket');
endTicket();

const [endTicketExts, ticketExtsRemaining] = ticket.expectLengthUint16('ticket extensions');
if (ticketExtsRemaining() > 0) {
ticket.readBytes(ticketExtsRemaining());
ticket.comment('ticket extensions (ignored)');
}
endTicketExts();

endTicketRecord();
log(...highlightBytes(ticket.commentedString(), LogColours.server));
}
}
25 changes: 14 additions & 11 deletions src/tls/tlsRecord.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Crypter } from './aesgcm';
import { LogColours } from '../presentation/appearance';
import { Bytes } from '../util/bytes';
import { concat } from '../util/array';
import { parseSessionTicket } from './sessionTicket';
import { LogColours } from '../presentation/appearance';
import { highlightBytes } from '../presentation/highlights';
import { log } from '../presentation/log';
import { hexFromU8 } from '../util/hex';
import { concat } from '../util/array';

export enum RecordType {
ChangeCipherSpec = 0x14,
Expand All @@ -15,12 +16,12 @@ export enum RecordType {
}

export const RecordTypeName = {
0x14: 'ChangeCipherSpec',
0x15: 'Alert',
0x16: 'Handshake',
0x17: 'Application',
0x18: 'Heartbeat',
};
[RecordType.ChangeCipherSpec]: 'ChangeCipherSpec',
[RecordType.Alert]: 'Alert',
[RecordType.Handshake]: 'Handshake',
[RecordType.Application]: 'Application',
[RecordType.Heartbeat]: 'Heartbeat',
} as const;

const maxPlaintextRecordLength = 1 << 14;
const maxCiphertextRecordLength = maxPlaintextRecordLength + 1 /* record type */ + 255 /* max aead */;
Expand All @@ -40,7 +41,8 @@ export async function readTlsRecord(read: (length: number) => Promise<Uint8Array

header.expectUint16(0x0303, 'TLS record version 1.2 (middlebox compatibility)');

const length = header.readUint16(chatty && '% bytes of TLS record follow');
const length = header.readUint16();
chatty && header.comment(`${length === 0 ? 'no' : length} byte${length === 1 ? '' : 's'} of TLS record follow${length === 1 ? 's' : ''}`)
if (length > maxLength) throw new Error(`Record too long: ${length} bytes`)

const content = await read(length);
Expand Down Expand Up @@ -76,13 +78,14 @@ export async function readEncryptedTlsRecord(read: (length: number) => Promise<U
if (closeNotify) return undefined; // 0x00 is close_notify
}

chatty && log(`... decrypted payload (see below) ... %s%c %s`, type.toString(16).padStart(2, '0'), `color: ${LogColours.server}`, `actual decrypted record type: ${(RecordTypeName as any)[type]}`);

if (type === RecordType.Handshake && record[0] === 0x04) { // new session ticket message: always ignore these
chatty && log(...highlightBytes(hexFromU8(record, ' ') + ' session ticket message: ignored', LogColours.server));
parseSessionTicket(record);
return readEncryptedTlsRecord(read, decrypter, expectedType);
}

if (expectedType !== undefined && type !== expectedType) throw new Error(`Unexpected TLS record type 0x${type.toString(16).padStart(2, '0')} (expected 0x${expectedType.toString(16).padStart(2, '0')})`);
chatty && log(`... decrypted payload (see below) ... %s%c %s`, type.toString(16).padStart(2, '0'), `color: ${LogColours.server}`, `actual decrypted record type: ${(RecordTypeName as any)[type]}`);

return record;
}
Expand Down
24 changes: 15 additions & 9 deletions src/util/bytes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export class Bytes {
return this;
}

lengthComment(length: number, comment?: string, inclusive = false) {
return length === 1 ?
`${length} byte${comment ? ` of ${comment}` : ''} ${inclusive ? 'starts here' : 'follows'}` :
`${length === 0 ? 'no' : length} bytes${comment ? ` of ${comment}` : ''} ${inclusive ? 'start here' : 'follow'}`;
}

// reading

readBytes(length: number) {
Expand Down Expand Up @@ -148,49 +154,49 @@ export class Bytes {

expectLengthUint8(comment?: string) {
const length = this.readUint8();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} follow`);
chatty && this.comment(this.lengthComment(length, comment));
return this.expectLength(length);
}

expectLengthUint16(comment?: string) {
const length = this.readUint16();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} follow`);
chatty && this.comment(this.lengthComment(length, comment));
return this.expectLength(length);
}

expectLengthUint24(comment?: string) {
const length = this.readUint24();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} follow`);
chatty && this.comment(this.lengthComment(length, comment));
return this.expectLength(length);
}

expectLengthUint32(comment?: string) {
const length = this.readUint32();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} follow`);
chatty && this.comment(this.lengthComment(length, comment));
return this.expectLength(length);
}

expectLengthUint8Incl(comment?: string) {
const length = this.readUint8();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} start here`);
chatty && this.comment(this.lengthComment(length, comment, true));
return this.expectLength(length - 1);
}

expectLengthUint16Incl(comment?: string) {
const length = this.readUint16();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} start here`);
chatty && this.comment(this.lengthComment(length, comment, true));
return this.expectLength(length - 2);
}

expectLengthUint24Incl(comment?: string) {
const length = this.readUint24();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} start here`);
chatty && this.comment(this.lengthComment(length, comment, true));
return this.expectLength(length - 3);
}

expectLengthUint32Incl(comment?: string) {
const length = this.readUint32();
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} start here`);
chatty && this.comment(this.lengthComment(length, comment, true));
return this.expectLength(length - 4);
}

Expand Down Expand Up @@ -263,7 +269,7 @@ export class Bytes {
}
else if (lengthBytes === 4) this.dataView.setUint32(startOffset, length);
else throw new Error(`Invalid length for length field: ${lengthBytes}`);
chatty && this.comment(`${length} bytes${comment ? ` of ${comment}` : ''} ${inclusive ? 'start here' : 'follow'}`, endOffset);
chatty && this.comment(this.lengthComment(length, comment, inclusive), endOffset);
this.indent -= 1;
this.indents[this.offset] = this.indent;
};
Expand Down

0 comments on commit 11259d9

Please sign in to comment.