| ... | @@ -188,6 +188,12 @@ const NamedGroup = enum(u16) { | ... | @@ -188,6 +188,12 @@ const NamedGroup = enum(u16) { |
| 188 | // * fragment: opaque | 188 | // * fragment: opaque |
| 189 | // - the data being transmitted | 189 | // - the data being transmitted |
| 190 | | 190 | |
| | 191 | // Ciphertext |
| | 192 | // * ContentType opaque_type = application_data; /* 23 */ |
| | 193 | // * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ |
| | 194 | // * uint16 length; |
| | 195 | // * opaque encrypted_record[TLSCiphertext.length]; |
| | 196 | |
| 191 | // Handshake: | 197 | // Handshake: |
| 192 | // * type: HandshakeType | 198 | // * type: HandshakeType |
| 193 | // * length: u24 | 199 | // * length: u24 |
| ... | @@ -331,105 +337,144 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void { | ... | @@ -331,105 +337,144 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void { |
| 331 | }; | 337 | }; |
| 332 | try stream.writevAll(&iovecs); | 338 | try stream.writevAll(&iovecs); |
| 333 | | 339 | |
| 334 | { | 340 | var handshake_buf: [4000]u8 = undefined; |
| 335 | var handshake_buf: [4000]u8 = undefined; | 341 | var len: usize = 0; |
| | 342 | var i: usize = i: { |
| 336 | const plaintext = handshake_buf[0..5]; | 343 | const plaintext = handshake_buf[0..5]; |
| 337 | const amt = try stream.readAtLeast(&handshake_buf, plaintext.len); | 344 | len = try stream.readAtLeast(&handshake_buf, plaintext.len); |
| 338 | if (amt < plaintext.len) return error.EndOfStream; | 345 | if (len < plaintext.len) return error.EndOfStream; |
| 339 | const ct = @intToEnum(ContentType, plaintext[0]); | 346 | const ct = @intToEnum(ContentType, plaintext[0]); |
| 340 | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); | 347 | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); |
| 341 | const end = plaintext.len + frag_len; | 348 | const end = plaintext.len + frag_len; |
| 342 | if (end > handshake_buf.len) return error.TlsServerHelloTooBig; | 349 | if (end > handshake_buf.len) return error.TlsRecordOverflow; |
| 343 | if (amt < end) { | 350 | if (end > len) { |
| 344 | const amt2 = try stream.readAll(handshake_buf[amt..end]); | 351 | len += try stream.readAtLeast(handshake_buf[len..], end - len); |
| 345 | if (amt2 < plaintext.len) return error.EndOfStream; | 352 | if (end > len) return error.EndOfStream; |
| 346 | } | 353 | } |
| 347 | const frag = handshake_buf[plaintext.len..end]; | 354 | const frag = handshake_buf[plaintext.len..end]; |
| 348 | | 355 | |
| 349 | if (ct == .alert) { | 356 | switch (ct) { |
| 350 | const level = @intToEnum(AlertLevel, frag[0]); | 357 | .alert => { |
| 351 | const desc = @intToEnum(AlertDescription, frag[1]); | 358 | const level = @intToEnum(AlertLevel, frag[0]); |
| 352 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | 359 | const desc = @intToEnum(AlertDescription, frag[1]); |
| 353 | std.process.exit(1); | 360 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); |
| 354 | } else if (ct == .handshake) { | 361 | return error.TlsAlert; |
| 355 | if (frag[0] != @enumToInt(HandshakeType.server_hello)) { | 362 | }, |
| 356 | return error.TlsUnexpectedMessage; | 363 | .handshake => { |
| 357 | } | 364 | if (frag[0] != @enumToInt(HandshakeType.server_hello)) { |
| 358 | const length = mem.readIntBig(u24, frag[1..4]); | 365 | return error.TlsUnexpectedMessage; |
| 359 | if (4 + length != frag.len) return error.TlsBadLength; | 366 | } |
| 360 | const hello = frag[4..]; | 367 | const length = mem.readIntBig(u24, frag[1..4]); |
| 361 | const legacy_version = mem.readIntBig(u16, hello[0..2]); | 368 | if (4 + length != frag.len) return error.TlsBadLength; |
| 362 | const random = hello[2..34].*; | 369 | const hello = frag[4..]; |
| 363 | _ = random; | 370 | const legacy_version = mem.readIntBig(u16, hello[0..2]); |
| 364 | const legacy_session_id_echo_len = hello[34]; | 371 | const random = hello[2..34].*; |
| 365 | if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter; | 372 | _ = random; |
| 366 | const cipher_suite_int = mem.readIntBig(u16, hello[35..37]); | 373 | const legacy_session_id_echo_len = hello[34]; |
| 367 | const cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch | 374 | if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter; |
| 368 | return error.TlsIllegalParameter; | 375 | const cipher_suite_int = mem.readIntBig(u16, hello[35..37]); |
| 369 | std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)}); | 376 | const cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch |
| 370 | const legacy_compression_method = hello[37]; | 377 | return error.TlsIllegalParameter; |
| 371 | _ = legacy_compression_method; | 378 | std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)}); |
| 372 | const extensions_size = mem.readIntBig(u16, hello[38..40]); | 379 | const legacy_compression_method = hello[37]; |
| 373 | if (40 + extensions_size != hello.len) return error.TlsBadLength; | 380 | _ = legacy_compression_method; |
| 374 | var i: usize = 40; | 381 | const extensions_size = mem.readIntBig(u16, hello[38..40]); |
| 375 | var supported_version: u16 = 0; | 382 | if (40 + extensions_size != hello.len) return error.TlsBadLength; |
| 376 | var have_server_pub_key = false; | 383 | var i: usize = 40; |
| 377 | while (i < hello.len) { | 384 | var supported_version: u16 = 0; |
| 378 | const et = mem.readIntBig(u16, hello[i..][0..2]); | 385 | var have_server_pub_key = false; |
| 379 | i += 2; | 386 | while (i < hello.len) { |
| 380 | const ext_size = mem.readIntBig(u16, hello[i..][0..2]); | 387 | const et = mem.readIntBig(u16, hello[i..][0..2]); |
| 381 | i += 2; | 388 | i += 2; |
| 382 | const next_i = i + ext_size; | 389 | const ext_size = mem.readIntBig(u16, hello[i..][0..2]); |
| 383 | if (next_i > hello.len) return error.TlsBadLength; | 390 | i += 2; |
| 384 | switch (et) { | 391 | const next_i = i + ext_size; |
| 385 | @enumToInt(ExtensionType.supported_versions) => { | 392 | if (next_i > hello.len) return error.TlsBadLength; |
| 386 | if (supported_version != 0) return error.TlsIllegalParameter; | 393 | switch (et) { |
| 387 | supported_version = mem.readIntBig(u16, hello[i..][0..2]); | 394 | @enumToInt(ExtensionType.supported_versions) => { |
| 388 | }, | 395 | if (supported_version != 0) return error.TlsIllegalParameter; |
| 389 | @enumToInt(ExtensionType.key_share) => { | 396 | supported_version = mem.readIntBig(u16, hello[i..][0..2]); |
| 390 | if (have_server_pub_key) return error.TlsIllegalParameter; | 397 | }, |
| 391 | const named_group = mem.readIntBig(u16, hello[i..][0..2]); | 398 | @enumToInt(ExtensionType.key_share) => { |
| 392 | i += 2; | 399 | if (have_server_pub_key) return error.TlsIllegalParameter; |
| 393 | switch (named_group) { | 400 | const named_group = mem.readIntBig(u16, hello[i..][0..2]); |
| 394 | @enumToInt(NamedGroup.x25519) => { | 401 | i += 2; |
| 395 | const key_size = mem.readIntBig(u16, hello[i..][0..2]); | 402 | switch (named_group) { |
| 396 | i += 2; | 403 | @enumToInt(NamedGroup.x25519) => { |
| 397 | if (key_size != 32) return error.TlsBadLength; | 404 | const key_size = mem.readIntBig(u16, hello[i..][0..2]); |
| 398 | const encrypted_key = hello[i..][0..32].*; | 405 | i += 2; |
| 399 | const server_pub_key = try crypto.dh.X25519.scalarmult( | 406 | if (key_size != 32) return error.TlsBadLength; |
| 400 | tls.x25519_priv_key, | 407 | const encrypted_key = hello[i..][0..32].*; |
| 401 | encrypted_key, | 408 | const server_pub_key = try crypto.dh.X25519.scalarmult( |
| 402 | ); | 409 | tls.x25519_priv_key, |
| 403 | tls.x25519_server_pub_key = server_pub_key; | 410 | encrypted_key, |
| 404 | have_server_pub_key = true; | 411 | ); |
| 405 | }, | 412 | tls.x25519_server_pub_key = server_pub_key; |
| 406 | else => { | 413 | have_server_pub_key = true; |
| 407 | std.debug.print("named group: {x}\n", .{named_group}); | 414 | }, |
| 408 | return error.TlsIllegalParameter; | 415 | else => { |
| 409 | }, | 416 | std.debug.print("named group: {x}\n", .{named_group}); |
| 410 | } | 417 | return error.TlsIllegalParameter; |
| | 418 | }, |
| | 419 | } |
| | 420 | }, |
| | 421 | else => { |
| | 422 | std.debug.print("unexpected extension: {x}\n", .{et}); |
| | 423 | }, |
| | 424 | } |
| | 425 | i = next_i; |
| | 426 | } |
| | 427 | if (!have_server_pub_key) return error.TlsIllegalParameter; |
| | 428 | const tls_version = if (supported_version == 0) legacy_version else supported_version; |
| | 429 | switch (tls_version) { |
| | 430 | @enumToInt(ProtocolVersion.tls_1_2) => { |
| | 431 | std.debug.print("server wants TLS v1.2\n", .{}); |
| 411 | }, | 432 | }, |
| 412 | else => { | 433 | @enumToInt(ProtocolVersion.tls_1_3) => { |
| 413 | std.debug.print("unexpected extension: {x}\n", .{et}); | 434 | std.debug.print("server wants TLS v1.3\n", .{}); |
| 414 | }, | 435 | }, |
| | 436 | else => return error.TlsIllegalParameter, |
| 415 | } | 437 | } |
| 416 | i = next_i; | 438 | }, |
| 417 | } | 439 | else => return error.TlsUnexpectedMessage, |
| 418 | if (!have_server_pub_key) return error.TlsIllegalParameter; | 440 | } |
| 419 | const tls_version = if (supported_version == 0) legacy_version else supported_version; | 441 | break :i end; |
| 420 | switch (tls_version) { | 442 | }; |
| 421 | @enumToInt(ProtocolVersion.tls_1_2) => { | 443 | |
| 422 | std.debug.print("server wants TLS v1.2\n", .{}); | 444 | while (true) { |
| 423 | }, | 445 | const end_hdr = i + 5; |
| 424 | @enumToInt(ProtocolVersion.tls_1_3) => { | 446 | if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow; |
| 425 | std.debug.print("server wants TLS v1.3\n", .{}); | 447 | if (end_hdr > len) { |
| 426 | }, | 448 | len += try stream.readAtLeast(handshake_buf[len..], end_hdr - len); |
| 427 | else => return error.TlsIllegalParameter, | 449 | if (end_hdr > len) return error.EndOfStream; |
| 428 | } | 450 | } |
| 429 | } else { | 451 | const ct = @intToEnum(ContentType, handshake_buf[i]); |
| 430 | std.debug.print("content_type: {s}\n", .{@tagName(ct)}); | 452 | i += 1; |
| 431 | std.debug.print("got {d} bytes: {s}\n", .{ amt, std.fmt.fmtSliceHexLower(frag) }); | 453 | const legacy_version = mem.readIntBig(u16, handshake_buf[i..][0..2]); |
| | 454 | i += 2; |
| | 455 | _ = legacy_version; |
| | 456 | const record_size = mem.readIntBig(u16, handshake_buf[i..][0..2]); |
| | 457 | i += 2; |
| | 458 | const end = i + record_size; |
| | 459 | if (end > handshake_buf.len) return error.TlsRecordOverflow; |
| | 460 | if (end > len) { |
| | 461 | len += try stream.readAtLeast(handshake_buf[len..], end - len); |
| | 462 | if (end > len) return error.EndOfStream; |
| | 463 | } |
| | 464 | switch (ct) { |
| | 465 | .change_cipher_spec => { |
| | 466 | if (record_size != 1) return error.TlsUnexpectedMessage; |
| | 467 | if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage; |
| | 468 | }, |
| | 469 | .application_data => { |
| | 470 | std.debug.print("TODO: decrypt these {d} bytes\n", .{record_size}); |
| | 471 | }, |
| | 472 | else => { |
| | 473 | std.debug.print("content type: {s}\n", .{@tagName(ct)}); |
| | 474 | return error.TlsUnexpectedMessage; |
| | 475 | }, |
| 432 | } | 476 | } |
| | 477 | i = end; |
| 433 | } | 478 | } |
| 434 | | 479 | |
| 435 | tls.state = .sent_hello; | 480 | tls.state = .sent_hello; |