Skip to content

Commit

Permalink
Annotate extension types in server hello
Browse files Browse the repository at this point in the history
  • Loading branch information
jawj committed Sep 14, 2023
1 parent 0241470 commit 81181e6
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 48 deletions.
53 changes: 29 additions & 24 deletions docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,14 +513,14 @@ function hexFromU8(u8, spacer = "") {
}

// src/tls/parseServerHello.ts
function parseServerHello(hello, sessionId) {
function parseServerHello(h, sessionId) {
let serverPublicKey;
let tlsVersionSpecified;
const [endServerHelloMessage] = hello.expectLength(hello.remaining());
hello.expectUint8(2, "handshake type: server hello");
const [endServerHello] = hello.expectLengthUint24("server hello");
hello.expectUint16(771, "TLS version 1.2 (middlebox compatibility)");
const serverRandom = hello.readBytes(32);
const [endServerHelloMessage] = h.expectLength(h.remaining());
h.expectUint8(2, "handshake type: server hello");
const [endServerHello] = h.expectLengthUint24("server hello");
h.expectUint16(771, "TLS version 1.2 (middlebox compatibility)");
const serverRandom = h.readBytes(32);
if (equal(serverRandom, [
// SHA-256 of "HelloRetryRequest", https://datatracker.ietf.org/doc/html/rfc8446#page-32
// see also: echo -n "HelloRetryRequest" | openssl dgst -sha256 -hex
Expand Down Expand Up @@ -558,33 +558,36 @@ function parseServerHello(hello, sessionId) {
156
]))
throw new Error("Unexpected HelloRetryRequest");
hello.comment('server random \u2014 [not SHA256("HelloRetryRequest")](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3)');
hello.expectUint8(sessionId.length, "session ID length (matches client session ID)");
hello.expectBytes(sessionId, "session ID (matches client session ID)");
hello.expectUint16(4865, "cipher (matches client hello)");
hello.expectUint8(0, "no compression");
const [endExtensions, extensionsRemaining] = hello.expectLengthUint16("extensions");
h.comment('server random \u2014 [not SHA256("HelloRetryRequest")](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3)');
h.expectUint8(sessionId.length, "session ID length (matches client session ID)");
h.expectBytes(sessionId, "session ID (matches client session ID)");
h.expectUint16(4865, "cipher (matches client hello)");
h.expectUint8(0, "no compression");
const [endExtensions, extensionsRemaining] = h.expectLengthUint16("extensions");
while (extensionsRemaining() > 0) {
const extensionType = hello.readUint16("extension type");
const [endExtension] = hello.expectLengthUint16("extension");
const extensionType = h.readUint16("extension type:");
h.comment(
extensionType === 43 ? "TLS version" : extensionType === 51 ? "key share" : "unknown"
);
const [endExtension] = h.expectLengthUint16("extension");
if (extensionType === 43) {
hello.expectUint16(772, "TLS version 1.3");
h.expectUint16(772, "TLS version 1.3");
tlsVersionSpecified = true;
} else if (extensionType === 51) {
hello.expectUint16(23, "secp256r1 (NIST P-256) key share");
const [endKeyShare, keyShareRemaining] = hello.expectLengthUint16("key share");
h.expectUint16(23, "key share type: secp256r1 (NIST P-256)");
const [endKeyShare, keyShareRemaining] = h.expectLengthUint16("key share");
const keyShareLength = keyShareRemaining();
if (keyShareLength !== 65)
throw new Error(`Expected 65 bytes of key share, but got ${keyShareLength}`);
if (1) {
hello.expectUint8(4, "legacy point format: always 4, which means uncompressed ([RFC 8446 \xA74.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC 8422 \xA75.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);
hello.comment("y coordinate");
h.expectUint8(4, "legacy point format: always 4, which means uncompressed ([RFC 8446 \xA74.2.8.2](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2) and [RFC 8422 \xA75.4.1](https://datatracker.ietf.org/doc/html/rfc8422#section-5.4.1))");
const x = h.readBytes(32);
h.comment("x coordinate");
const y = h.readBytes(32);
h.comment("y coordinate");
serverPublicKey = concat([4], x, y);
} else {
serverPublicKey = hello.readBytes(keyShareLength);
serverPublicKey = h.readBytes(keyShareLength);
}
endKeyShare();
} else {
Expand Down Expand Up @@ -1730,7 +1733,9 @@ async function verifyCerts(host, certs, rootCerts, requireServerTlsExtKeyUsage =
throw new Error("Signing certificate basicConstraints do not indicate a CA certificate");
log(`%c\u2713 certificate basicConstraints indicate a CA certificate`, "color: #8c8;");
const { pathLength } = signingCert.basicConstraints;
if (pathLength !== void 0) {
if (pathLength === void 0) {
log(`%c\u2713 certificate pathLength is not constrained`, "color: #8c8;");
} else {
if (pathLength < i)
throw new Error("Exceeded certificate pathLength");
log(`%c\u2713 certificate pathLength is not exceeded`, "color: #8c8;");
Expand Down
51 changes: 28 additions & 23 deletions src/tls/parseServerHello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { concat, equal } from '../util/array';
import { Bytes } from '../util/bytes';
import { hexFromU8 } from '../util/hex';

export default function parseServerHello(hello: Bytes, sessionId: Uint8Array) {
export default function parseServerHello(h: Bytes, sessionId: Uint8Array) {
let serverPublicKey;
let tlsVersionSpecified;

const [endServerHelloMessage] = hello.expectLength(hello.remaining());
const [endServerHelloMessage] = h.expectLength(h.remaining());

hello.expectUint8(0x02, chatty && 'handshake type: server hello');
const [endServerHello] = hello.expectLengthUint24(chatty && 'server hello');
h.expectUint8(0x02, chatty && 'handshake type: server hello');
const [endServerHello] = h.expectLengthUint24(chatty && 'server hello');

hello.expectUint16(0x0303, chatty && 'TLS version 1.2 (middlebox compatibility)');
const serverRandom = hello.readBytes(32);
h.expectUint16(0x0303, chatty && 'TLS version 1.2 (middlebox compatibility)');
const serverRandom = h.readBytes(32);
if (equal(serverRandom, [
// SHA-256 of "HelloRetryRequest", https://datatracker.ietf.org/doc/html/rfc8446#page-32
// see also: echo -n "HelloRetryRequest" | openssl dgst -sha256 -hex
Expand All @@ -21,37 +21,42 @@ export default function parseServerHello(hello: Bytes, sessionId: Uint8Array) {
0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e,
0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c
])) throw new Error('Unexpected HelloRetryRequest');
chatty && hello.comment('server random — [not SHA256("HelloRetryRequest")](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3)');
chatty && h.comment('server random — [not SHA256("HelloRetryRequest")](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3)');

hello.expectUint8(sessionId.length, chatty && 'session ID length (matches client session ID)');
hello.expectBytes(sessionId, chatty && 'session ID (matches client session ID)');
h.expectUint8(sessionId.length, chatty && 'session ID length (matches client session ID)');
h.expectBytes(sessionId, chatty && 'session ID (matches client session ID)');

hello.expectUint16(0x1301, chatty && 'cipher (matches client hello)');
hello.expectUint8(0x00, chatty && 'no compression');
h.expectUint16(0x1301, chatty && 'cipher (matches client hello)');
h.expectUint8(0x00, chatty && 'no compression');

const [endExtensions, extensionsRemaining] = hello.expectLengthUint16(chatty && 'extensions');
const [endExtensions, extensionsRemaining] = h.expectLengthUint16(chatty && 'extensions');
while (extensionsRemaining() > 0) {
const extensionType = hello.readUint16(chatty && 'extension type');
const [endExtension] = hello.expectLengthUint16(chatty && 'extension');
const extensionType = h.readUint16(chatty && 'extension type:');
chatty && h.comment(
extensionType === 0x002b ? 'TLS version' :
extensionType === 0x0033 ? 'key share' :
'unknown');

const [endExtension] = h.expectLengthUint16(chatty && 'extension');

if (extensionType === 0x002b) {
hello.expectUint16(0x0304, chatty && 'TLS version 1.3');
h.expectUint16(0x0304, chatty && 'TLS version 1.3');
tlsVersionSpecified = true;

} else if (extensionType === 0x0033) {
hello.expectUint16(0x0017, chatty && 'secp256r1 (NIST P-256) key share');
const [endKeyShare, keyShareRemaining] = hello.expectLengthUint16('key share');
h.expectUint16(0x0017, chatty && 'key share type: secp256r1 (NIST P-256)');
const [endKeyShare, keyShareRemaining] = h.expectLengthUint16('key share');
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 ([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);
hello.comment('y coordinate');
h.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 = h.readBytes(32);
h.comment('x coordinate');
const y = h.readBytes(32);
h.comment('y coordinate');
serverPublicKey = concat([4], x, y);
} else {
serverPublicKey = hello.readBytes(keyShareLength);
serverPublicKey = h.readBytes(keyShareLength);
}
// TODO: will SubtleCrypto validate this for us when deriving the shared secret, or must we do it?
// https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2
Expand Down
5 changes: 4 additions & 1 deletion src/tls/verifyCerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ export async function verifyCerts(
chatty && log(`%c✓ certificate basicConstraints indicate a CA certificate`, 'color: #8c8;');

const { pathLength } = signingCert.basicConstraints;
if (pathLength !== undefined) {
if (pathLength === undefined) {
chatty && log(`%c✓ certificate pathLength is not constrained`, 'color: #8c8;');

} else {
if (pathLength < i) throw new Error('Exceeded certificate pathLength');
chatty && log(`%c✓ certificate pathLength is not exceeded`, 'color: #8c8;');
}
Expand Down

0 comments on commit 81181e6

Please sign in to comment.