authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-11-04 20:45:18-05:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-11-07 20:25:26-05:00
log485f20a10ab489274b71b0d3106dcf81dbe3ac15
treef32b757c41e3895ccb6297fd83b9bab47c719cf5
parent90a761c18659919f466715714395ce27425c45f7

std.crypto.tls: remove hardcoded initial loop

This was preventing TLSv1.2 from working in some cases, because servers are allowed to send multiple handshake messages in the first handshake record, whereas this inital loop was assuming that it only contained a server hello.

1 files changed, 142 insertions(+), 171 deletions(-)

lib/std/crypto/tls/Client.zig+142-171
......@@ -214,158 +214,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
214214 try stream.writevAll(&iovecs);
215215 }
216216
217 const client_hello_bytes1 = cleartext_header[tls.record_header_len..];
218
219217 var tls_version: tls.ProtocolVersion = undefined;
220 var cipher_suite_tag: tls.CipherSuite = undefined;
221 var handshake_cipher: tls.HandshakeCipher = undefined;
222 var handshake_buffer: [8000]u8 = undefined;
223 var d: tls.Decoder = .{ .buf = &handshake_buffer };
224 {
225 try d.readAtLeastOurAmt(stream, tls.record_header_len);
226 const ct = d.decode(tls.ContentType);
227 d.skip(2); // legacy_record_version
228 const record_len = d.decode(u16);
229 try d.readAtLeast(stream, record_len);
230 const server_hello_fragment = d.buf[d.idx..][0..record_len];
231 var ptd = try d.sub(record_len);
232 switch (ct) {
233 .alert => {
234 try ptd.ensure(2);
235 const level = ptd.decode(tls.AlertLevel);
236 const desc = ptd.decode(tls.AlertDescription);
237 _ = level;
238
239 // if this isn't a error alert, then it's a closure alert, which makes no sense in a handshake
240 try desc.toError();
241 // TODO: handle server-side closures
242 return error.TlsUnexpectedMessage;
243 },
244 .handshake => {
245 try ptd.ensure(4);
246 const handshake_type = ptd.decode(tls.HandshakeType);
247 if (handshake_type != .server_hello) return error.TlsUnexpectedMessage;
248 const length = ptd.decode(u24);
249 var hsd = try ptd.sub(length);
250 try hsd.ensure(2 + 32 + 1);
251 const legacy_version = hsd.decode(u16);
252 @memcpy(&server_hello_rand, hsd.array(32));
253 if (mem.eql(u8, &server_hello_rand, &tls.hello_retry_request_sequence)) {
254 // This is a HelloRetryRequest message. This client implementation
255 // does not expect to get one.
256 return error.TlsUnexpectedMessage;
257 }
258 const legacy_session_id_echo_len = hsd.decode(u8);
259 try hsd.ensure(legacy_session_id_echo_len + 2 + 1);
260 const legacy_session_id_echo = hsd.slice(legacy_session_id_echo_len);
261 cipher_suite_tag = hsd.decode(tls.CipherSuite);
262 hsd.skip(1); // legacy_compression_method
263 var supported_version: ?u16 = null;
264 if (!hsd.eof()) {
265 try hsd.ensure(2);
266 const extensions_size = hsd.decode(u16);
267 var all_extd = try hsd.sub(extensions_size);
268 while (!all_extd.eof()) {
269 try all_extd.ensure(2 + 2);
270 const et = all_extd.decode(tls.ExtensionType);
271 const ext_size = all_extd.decode(u16);
272 var extd = try all_extd.sub(ext_size);
273 switch (et) {
274 .supported_versions => {
275 if (supported_version) |_| return error.TlsIllegalParameter;
276 try extd.ensure(2);
277 supported_version = extd.decode(u16);
278 },
279 .key_share => {
280 if (key_share.getSharedSecret()) |_| return error.TlsIllegalParameter;
281 try extd.ensure(4);
282 const named_group = extd.decode(tls.NamedGroup);
283 const key_size = extd.decode(u16);
284 try extd.ensure(key_size);
285 try key_share.exchange(named_group, extd.slice(key_size));
286 },
287 else => {},
288 }
289 }
290 }
291
292 tls_version = @enumFromInt(supported_version orelse legacy_version);
293 switch (tls_version) {
294 .tls_1_3 => if (!mem.eql(u8, legacy_session_id_echo, &legacy_session_id)) return error.TlsIllegalParameter,
295 .tls_1_2 => if (mem.eql(u8, server_hello_rand[24..31], "DOWNGRD") and
296 server_hello_rand[31] >> 1 == 0x00) return error.TlsIllegalParameter,
297 else => return error.TlsIllegalParameter,
298 }
299
300 switch (cipher_suite_tag) {
301 inline .AES_128_GCM_SHA256,
302 .AES_256_GCM_SHA384,
303 .CHACHA20_POLY1305_SHA256,
304 .AEGIS_256_SHA512,
305 .AEGIS_128L_SHA256,
306
307 .ECDHE_RSA_WITH_AES_128_GCM_SHA256,
308 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
309 .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
310 => |tag| {
311 handshake_cipher = @unionInit(tls.HandshakeCipher, @tagName(tag.with()), .{
312 .transcript_hash = .init(.{}),
313 .version = undefined,
314 });
315 const p = &@field(handshake_cipher, @tagName(tag.with()));
316 p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1
317 p.transcript_hash.update(host); // Client Hello part 2
318 p.transcript_hash.update(server_hello_fragment);
319 },
320
321 else => return error.TlsIllegalParameter,
322 }
323 switch (tls_version) {
324 .tls_1_3 => switch (cipher_suite_tag) {
325 inline .AES_128_GCM_SHA256,
326 .AES_256_GCM_SHA384,
327 .CHACHA20_POLY1305_SHA256,
328 .AEGIS_256_SHA512,
329 .AEGIS_128L_SHA256,
330 => |tag| {
331 const sk = key_share.getSharedSecret() orelse return error.TlsIllegalParameter;
332 const p = &@field(handshake_cipher, @tagName(tag.with()));
333 const P = @TypeOf(p.*).A;
334 const hello_hash = p.transcript_hash.peek();
335 const zeroes = [1]u8{0} ** P.Hash.digest_length;
336 const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes);
337 const empty_hash = tls.emptyHash(P.Hash);
338 p.version = .{ .tls_1_3 = undefined };
339 const pv = &p.version.tls_1_3;
340 const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length);
341 pv.handshake_secret = P.Hkdf.extract(&hs_derived_secret, sk);
342 const ap_derived_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "derived", &empty_hash, P.Hash.digest_length);
343 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
344 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
345 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
346 pv.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length);
347 pv.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length);
348 pv.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
349 pv.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
350 pv.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
351 pv.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
352 },
353 else => return error.TlsIllegalParameter,
354 },
355 .tls_1_2 => switch (cipher_suite_tag) {
356 .ECDHE_RSA_WITH_AES_128_GCM_SHA256,
357 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
358 .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
359 => {},
360 else => return error.TlsIllegalParameter,
361 },
362 else => return error.TlsIllegalParameter,
363 }
364 },
365 else => return error.TlsUnexpectedMessage,
366 }
367 }
368
369218 // This is used for two purposes:
370219 // * Detect whether a certificate is the first one presented, in which case
371220 // we need to verify the host name.
......@@ -384,13 +233,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
384233 /// Application cipher is in use
385234 application,
386235 };
387 var pending_cipher_state: CipherState = switch (tls_version) {
388 .tls_1_3 => .handshake,
389 .tls_1_2 => .cleartext,
390 else => unreachable,
391 };
392 var cipher_state: CipherState = .cleartext;
236 var pending_cipher_state: CipherState = .cleartext;
237 var cipher_state = pending_cipher_state;
393238 const HandshakeState = enum {
239 /// In this state we expect only a server hello message.
240 hello,
394241 /// In this state we expect only an encrypted_extensions message.
395242 encrypted_extensions,
396243 /// In this state we expect certificate handshake messages.
......@@ -404,15 +251,14 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
404251 /// In this state, we expect only the finished handshake message.
405252 finished,
406253 };
407 var handshake_state: HandshakeState = switch (tls_version) {
408 .tls_1_3 => .encrypted_extensions,
409 .tls_1_2 => .certificate,
410 else => unreachable,
411 };
412 var cleartext_bufs: [2][8000]u8 = undefined;
254 var handshake_state: HandshakeState = .hello;
255 var handshake_cipher: tls.HandshakeCipher = undefined;
413256 var main_cert_pub_key: CertificatePublicKey = undefined;
414257 const now_sec = std.time.timestamp();
415258
259 var cleartext_bufs: [2][8000]u8 = undefined;
260 var handshake_buffer: [8000]u8 = undefined;
261 var d: tls.Decoder = .{ .buf = &handshake_buffer };
416262 while (true) {
417263 try d.readAtLeastOurAmt(stream, tls.record_header_len);
418264 const record_header = d.buf[d.idx..][0..tls.record_header_len];
......@@ -526,11 +372,132 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
526372 var hsd = try ctd.sub(handshake_len);
527373 const wrapped_handshake = ctd.buf[ctd.idx - handshake_len - 4 .. ctd.idx];
528374 switch (handshake_type) {
375 .server_hello => {
376 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
377 if (handshake_state != .hello) return error.TlsUnexpectedMessage;
378 try hsd.ensure(2 + 32 + 1);
379 const legacy_version = hsd.decode(u16);
380 @memcpy(&server_hello_rand, hsd.array(32));
381 if (mem.eql(u8, &server_hello_rand, &tls.hello_retry_request_sequence)) {
382 // This is a HelloRetryRequest message. This client implementation
383 // does not expect to get one.
384 return error.TlsUnexpectedMessage;
385 }
386 const legacy_session_id_echo_len = hsd.decode(u8);
387 try hsd.ensure(legacy_session_id_echo_len + 2 + 1);
388 const legacy_session_id_echo = hsd.slice(legacy_session_id_echo_len);
389 const cipher_suite_tag = hsd.decode(tls.CipherSuite);
390 hsd.skip(1); // legacy_compression_method
391 var supported_version: ?u16 = null;
392 if (!hsd.eof()) {
393 try hsd.ensure(2);
394 const extensions_size = hsd.decode(u16);
395 var all_extd = try hsd.sub(extensions_size);
396 while (!all_extd.eof()) {
397 try all_extd.ensure(2 + 2);
398 const et = all_extd.decode(tls.ExtensionType);
399 const ext_size = all_extd.decode(u16);
400 var extd = try all_extd.sub(ext_size);
401 switch (et) {
402 .supported_versions => {
403 if (supported_version) |_| return error.TlsIllegalParameter;
404 try extd.ensure(2);
405 supported_version = extd.decode(u16);
406 },
407 .key_share => {
408 if (key_share.getSharedSecret()) |_| return error.TlsIllegalParameter;
409 try extd.ensure(4);
410 const named_group = extd.decode(tls.NamedGroup);
411 const key_size = extd.decode(u16);
412 try extd.ensure(key_size);
413 try key_share.exchange(named_group, extd.slice(key_size));
414 },
415 else => {},
416 }
417 }
418 }
419
420 tls_version = @enumFromInt(supported_version orelse legacy_version);
421 switch (tls_version) {
422 .tls_1_3 => if (!mem.eql(u8, legacy_session_id_echo, &legacy_session_id)) return error.TlsIllegalParameter,
423 .tls_1_2 => if (mem.eql(u8, server_hello_rand[24..31], "DOWNGRD") and
424 server_hello_rand[31] >> 1 == 0x00) return error.TlsIllegalParameter,
425 else => return error.TlsIllegalParameter,
426 }
427
428 switch (cipher_suite_tag) {
429 inline .AES_128_GCM_SHA256,
430 .AES_256_GCM_SHA384,
431 .CHACHA20_POLY1305_SHA256,
432 .AEGIS_256_SHA512,
433 .AEGIS_128L_SHA256,
434
435 .ECDHE_RSA_WITH_AES_128_GCM_SHA256,
436 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
437 .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
438 => |tag| {
439 handshake_cipher = @unionInit(tls.HandshakeCipher, @tagName(tag.with()), .{
440 .transcript_hash = .init(.{}),
441 .version = undefined,
442 });
443 const p = &@field(handshake_cipher, @tagName(tag.with()));
444 p.transcript_hash.update(cleartext_header[tls.record_header_len..]); // Client Hello part 1
445 p.transcript_hash.update(host); // Client Hello part 2
446 p.transcript_hash.update(wrapped_handshake);
447 },
448
449 else => return error.TlsIllegalParameter,
450 }
451 switch (tls_version) {
452 .tls_1_3 => {
453 switch (cipher_suite_tag) {
454 inline .AES_128_GCM_SHA256,
455 .AES_256_GCM_SHA384,
456 .CHACHA20_POLY1305_SHA256,
457 .AEGIS_256_SHA512,
458 .AEGIS_128L_SHA256,
459 => |tag| {
460 const sk = key_share.getSharedSecret() orelse return error.TlsIllegalParameter;
461 const p = &@field(handshake_cipher, @tagName(tag.with()));
462 const P = @TypeOf(p.*).A;
463 const hello_hash = p.transcript_hash.peek();
464 const zeroes = [1]u8{0} ** P.Hash.digest_length;
465 const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes);
466 const empty_hash = tls.emptyHash(P.Hash);
467 p.version = .{ .tls_1_3 = undefined };
468 const pv = &p.version.tls_1_3;
469 const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length);
470 pv.handshake_secret = P.Hkdf.extract(&hs_derived_secret, sk);
471 const ap_derived_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "derived", &empty_hash, P.Hash.digest_length);
472 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
473 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
474 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
475 pv.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length);
476 pv.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length);
477 pv.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
478 pv.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
479 pv.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
480 pv.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
481 },
482 else => return error.TlsIllegalParameter,
483 }
484 pending_cipher_state = .handshake;
485 handshake_state = .encrypted_extensions;
486 },
487 .tls_1_2 => switch (cipher_suite_tag) {
488 .ECDHE_RSA_WITH_AES_128_GCM_SHA256,
489 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
490 .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
491 => handshake_state = .certificate,
492 else => return error.TlsIllegalParameter,
493 },
494 else => return error.TlsIllegalParameter,
495 }
496 },
529497 .encrypted_extensions => {
530498 if (tls_version != .tls_1_3) return error.TlsUnexpectedMessage;
531499 if (cipher_state != .handshake) return error.TlsUnexpectedMessage;
532500 if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage;
533 handshake_state = .certificate;
534501 switch (handshake_cipher) {
535502 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
536503 }
......@@ -548,16 +515,18 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
548515 else => {},
549516 }
550517 }
518 handshake_state = .certificate;
551519 },
552520 .certificate => cert: {
553 switch (handshake_cipher) {
554 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
555 }
521 if (cipher_state == .application) return error.TlsUnexpectedMessage;
556522 switch (handshake_state) {
557523 .certificate => {},
558524 .trust_chain_established => break :cert,
559525 else => return error.TlsUnexpectedMessage,
560526 }
527 switch (handshake_cipher) {
528 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
529 }
561530
562531 switch (tls_version) {
563532 .tls_1_3 => {
......@@ -614,7 +583,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
614583 if (tls_version != .tls_1_2) return error.TlsUnexpectedMessage;
615584 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
616585 switch (handshake_state) {
617 .trust_chain_established => handshake_state = .server_hello_done,
586 .trust_chain_established => {},
618587 .certificate => return error.TlsCertificateNotVerified,
619588 else => return error.TlsUnexpectedMessage,
620589 }
......@@ -631,12 +600,12 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
631600 const server_pub_key = hsd.slice(key_size);
632601 try main_cert_pub_key.verifySignature(&hsd, &.{ &client_hello_rand, &server_hello_rand, hsd.buf[0..hsd.idx] });
633602 try key_share.exchange(named_group, server_pub_key);
603 handshake_state = .server_hello_done;
634604 },
635605 .server_hello_done => {
636606 if (tls_version != .tls_1_2) return error.TlsUnexpectedMessage;
637607 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
638608 if (handshake_state != .server_hello_done) return error.TlsUnexpectedMessage;
639 handshake_state = .finished;
640609
641610 const client_key_exchange_msg = .{@intFromEnum(tls.ContentType.handshake)} ++
642611 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
......@@ -680,7 +649,6 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
680649 .app_cipher = std.mem.bytesToValue(P.Tls_1_2, &key_block),
681650 } };
682651 const pv = &p.version.tls_1_2;
683 pending_cipher_state = .application;
684652 const nonce: [P.AEAD.nonce_length]u8 = if (builtin.zig_backend == .stage2_x86_64 and
685653 P.AEAD.nonce_length > comptime std.simd.suggestVectorLength(u8) orelse 1)
686654 nonce: {
......@@ -715,12 +683,14 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
715683 },
716684 }
717685 write_seq += 1;
686 pending_cipher_state = .application;
687 handshake_state = .finished;
718688 },
719689 .certificate_verify => {
720690 if (tls_version != .tls_1_3) return error.TlsUnexpectedMessage;
721691 if (cipher_state != .handshake) return error.TlsUnexpectedMessage;
722692 switch (handshake_state) {
723 .trust_chain_established => handshake_state = .finished,
693 .trust_chain_established => {},
724694 .certificate => return error.TlsCertificateNotVerified,
725695 else => return error.TlsUnexpectedMessage,
726696 }
......@@ -733,6 +703,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
733703 p.transcript_hash.update(wrapped_handshake);
734704 },
735705 }
706 handshake_state = .finished;
736707 },
737708 .finished => {
738709 if (cipher_state == .cleartext) return error.TlsUnexpectedMessage;