| ... | @@ -4,11 +4,12 @@ const native_endian = builtin.cpu.arch.endian(); | ... | @@ -4,11 +4,12 @@ const native_endian = builtin.cpu.arch.endian(); |
| 4 | const std = @import("../../std.zig"); | 4 | const std = @import("../../std.zig"); |
| 5 | const tls = std.crypto.tls; | 5 | const tls = std.crypto.tls; |
| 6 | const Client = @This(); | 6 | const Client = @This(); |
| 7 | const net = std.net; | | |
| 8 | const mem = std.mem; | 7 | const mem = std.mem; |
| 9 | const crypto = std.crypto; | 8 | const crypto = std.crypto; |
| 10 | const assert = std.debug.assert; | 9 | const assert = std.debug.assert; |
| 11 | const Certificate = std.crypto.Certificate; | 10 | const Certificate = std.crypto.Certificate; |
| | 11 | const Reader = std.io.Reader; |
| | 12 | const Writer = std.io.Writer; |
| 12 | | 13 | |
| 13 | const max_ciphertext_len = tls.max_ciphertext_len; | 14 | const max_ciphertext_len = tls.max_ciphertext_len; |
| 14 | const hmacExpandLabel = tls.hmacExpandLabel; | 15 | const hmacExpandLabel = tls.hmacExpandLabel; |
| ... | @@ -21,38 +22,22 @@ const array = tls.array; | ... | @@ -21,38 +22,22 @@ const array = tls.array; |
| 21 | /// | 22 | /// |
| 22 | /// The buffer is asserted to have capacity at least `min_buffer_len`. | 23 | /// The buffer is asserted to have capacity at least `min_buffer_len`. |
| 23 | /// | 24 | /// |
| 24 | /// The size is enough to contain exactly one TLSCiphertext record. | 25 | /// `remaining_cleartext_len` tells how many bytes inside this buffer have |
| 25 | /// This buffer is segmented into four parts: | 26 | /// already been decrypted. |
| 26 | /// 0. unused | | |
| 27 | /// 1. cleartext | | |
| 28 | /// 2. ciphertext | | |
| 29 | /// 3. unused | | |
| 30 | /// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and | | |
| 31 | /// `partial_ciphertext_end` describe the span of the segments. | | |
| 32 | input: *std.io.BufferedReader, | 27 | input: *std.io.BufferedReader, |
| | 28 | /// Tells how many bytes inside `input` have already been decrypted. |
| | 29 | remaining_cleartext_len: u15, |
| | 30 | |
| 33 | /// The encrypted stream from the client to the server. Bytes are pushed here | 31 | /// The encrypted stream from the client to the server. Bytes are pushed here |
| 34 | /// via `writer`. | 32 | /// via `writer`. |
| 35 | /// | | |
| 36 | /// The buffer is asserted to have capacity at least `min_buffer_len`. | | |
| 37 | output: *std.io.BufferedWriter, | 33 | output: *std.io.BufferedWriter, |
| 38 | /// Cleartext received from the server here. | | |
| 39 | /// | | |
| 40 | /// Its buffer aliases the buffer of `input`. | | |
| 41 | reader: std.io.BufferedReader, | | |
| 42 | /// Populated when `error.TlsAlert` is returned. | | |
| 43 | alert: ?tls.Alert, | | |
| 44 | read_err: ?ReadError, | | |
| 45 | | 34 | |
| | 35 | /// Populated when `error.TlsAlert` is returned. |
| | 36 | alert: ?tls.Alert = null, |
| | 37 | read_err: ?ReadError = null, |
| 46 | tls_version: tls.ProtocolVersion, | 38 | tls_version: tls.ProtocolVersion, |
| 47 | read_seq: u64, | 39 | read_seq: u64, |
| 48 | write_seq: u64, | 40 | write_seq: u64, |
| 49 | /// The starting index of cleartext bytes inside the input buffer. | | |
| 50 | partial_cleartext_idx: u15, | | |
| 51 | /// The ending index of cleartext bytes inside the input buffer as well | | |
| 52 | /// as the starting index of ciphertext bytes. | | |
| 53 | partial_ciphertext_idx: u15, | | |
| 54 | /// The ending index of ciphertext bytes inside the input buffer. | | |
| 55 | partial_ciphertext_end: u15, | | |
| 56 | /// When this is true, the stream may still not be at the end because there | 41 | /// When this is true, the stream may still not be at the end because there |
| 57 | /// may be data in the input buffer. | 42 | /// may be data in the input buffer. |
| 58 | received_close_notify: bool, | 43 | received_close_notify: bool, |
| ... | @@ -60,11 +45,13 @@ received_close_notify: bool, | ... | @@ -60,11 +45,13 @@ received_close_notify: bool, |
| 60 | /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify | 45 | /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify |
| 61 | /// message has been received. By setting this flag to `true`, instead, the | 46 | /// message has been received. By setting this flag to `true`, instead, the |
| 62 | /// end-of-stream will be forwarded to the application layer above TLS. | 47 | /// end-of-stream will be forwarded to the application layer above TLS. |
| | 48 | /// |
| 63 | /// This makes the application vulnerable to truncation attacks unless the | 49 | /// This makes the application vulnerable to truncation attacks unless the |
| 64 | /// application layer itself verifies that the amount of data received equals | 50 | /// application layer itself verifies that the amount of data received equals |
| 65 | /// the amount of data expected, such as HTTP with the Content-Length header. | 51 | /// the amount of data expected, such as HTTP with the Content-Length header. |
| 66 | allow_truncation_attacks: bool, | 52 | allow_truncation_attacks: bool, |
| 67 | application_cipher: tls.ApplicationCipher, | 53 | application_cipher: tls.ApplicationCipher, |
| | 54 | |
| 68 | /// If non-null, ssl secrets are logged to a stream. Creating such a log file | 55 | /// If non-null, ssl secrets are logged to a stream. Creating such a log file |
| 69 | /// allows other programs with access to that file to decrypt all traffic over | 56 | /// allows other programs with access to that file to decrypt all traffic over |
| 70 | /// this connection. | 57 | /// this connection. |
| ... | @@ -80,6 +67,7 @@ pub const ReadError = error{ | ... | @@ -80,6 +67,7 @@ pub const ReadError = error{ |
| 80 | TlsRecordOverflow, | 67 | TlsRecordOverflow, |
| 81 | TlsUnexpectedMessage, | 68 | TlsUnexpectedMessage, |
| 82 | TlsIllegalParameter, | 69 | TlsIllegalParameter, |
| | 70 | TlsSequenceOverflow, |
| 83 | }; | 71 | }; |
| 84 | | 72 | |
| 85 | pub const SslKeyLog = struct { | 73 | pub const SslKeyLog = struct { |
| ... | @@ -99,8 +87,8 @@ pub const SslKeyLog = struct { | ... | @@ -99,8 +87,8 @@ pub const SslKeyLog = struct { |
| 99 | } | 87 | } |
| 100 | }; | 88 | }; |
| 101 | | 89 | |
| 102 | /// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init` | 90 | /// The `std.io.BufferedReader` supplied to `init` requires a buffer capacity |
| 103 | /// each require a buffer capacity at least this amount. | 91 | /// at least this amount. |
| 104 | pub const min_buffer_len = tls.max_ciphertext_record_len; | 92 | pub const min_buffer_len = tls.max_ciphertext_record_len; |
| 105 | | 93 | |
| 106 | pub const Options = struct { | 94 | pub const Options = struct { |
| ... | @@ -126,7 +114,10 @@ pub const Options = struct { | ... | @@ -126,7 +114,10 @@ pub const Options = struct { |
| 126 | }, | 114 | }, |
| 127 | /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows | 115 | /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows |
| 128 | /// other programs with access to that file to decrypt all traffic over this connection. | 116 | /// other programs with access to that file to decrypt all traffic over this connection. |
| 129 | ssl_key_log: ?*std.io.BufferedWriter = null, | 117 | /// |
| | 118 | /// Only the `writer` field is observed during the handshake (`init`). |
| | 119 | /// After that, the other fields are populated. |
| | 120 | ssl_key_log: ?*SslKeyLog = null, |
| 130 | }; | 121 | }; |
| 131 | | 122 | |
| 132 | const InitError = error{ | 123 | const InitError = error{ |
| ... | @@ -183,16 +174,14 @@ const InitError = error{ | ... | @@ -183,16 +174,14 @@ const InitError = error{ |
| 183 | /// | 174 | /// |
| 184 | /// `host` is only borrowed during this function call. | 175 | /// `host` is only borrowed during this function call. |
| 185 | /// | 176 | /// |
| 186 | /// Both `input` and `output` are asserted to have buffer capacity at least | 177 | /// `input` is asserted to have buffer capacity at least `min_buffer_len`. |
| 187 | /// `min_buffer_len`. | | |
| 188 | pub fn init( | 178 | pub fn init( |
| 189 | client: *Client, | 179 | client: *Client, |
| 190 | input: *std.io.BufferedReader, | 180 | input: *std.io.BufferedReader, |
| 191 | output: *std.io.BufferedWriter, | 181 | output: *std.io.BufferedWriter, |
| 192 | options: Options, | 182 | options: Options, |
| 193 | ) InitError!void { | 183 | ) InitError!void { |
| 194 | assert(input.storage.buffer.len >= min_buffer_len); | 184 | assert(input.buffer.len >= min_buffer_len); |
| 195 | assert(output.buffer.len >= min_buffer_len); | | |
| 196 | client.alert = null; | 185 | client.alert = null; |
| 197 | const host = switch (options.host) { | 186 | const host = switch (options.host) { |
| 198 | .no_verification => "", | 187 | .no_verification => "", |
| ... | @@ -286,7 +275,7 @@ pub fn init( | ... | @@ -286,7 +275,7 @@ pub fn init( |
| 286 | | 275 | |
| 287 | { | 276 | { |
| 288 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; | 277 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; |
| 289 | try output.writevAll(iovecs[0..if (host.len == 0) 1 else 2]); | 278 | try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]); |
| 290 | } | 279 | } |
| 291 | | 280 | |
| 292 | var tls_version: tls.ProtocolVersion = undefined; | 281 | var tls_version: tls.ProtocolVersion = undefined; |
| ... | @@ -335,20 +324,26 @@ pub fn init( | ... | @@ -335,20 +324,26 @@ pub fn init( |
| 335 | var cleartext_fragment_start: usize = 0; | 324 | var cleartext_fragment_start: usize = 0; |
| 336 | var cleartext_fragment_end: usize = 0; | 325 | var cleartext_fragment_end: usize = 0; |
| 337 | var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined; | 326 | var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined; |
| 338 | var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined; | | |
| 339 | var d: tls.Decoder = .{ .buf = &handshake_buffer }; | | |
| 340 | fragment: while (true) { | 327 | fragment: while (true) { |
| 341 | try d.readAtLeastOurAmt(input, tls.record_header_len); | 328 | // Ensure the input buffer pointer is stable in this scope. |
| 342 | const record_header = d.buf[d.idx..][0..tls.record_header_len]; | 329 | input.rebaseCapacity(tls.max_ciphertext_record_len); |
| 343 | const record_ct = d.decode(tls.ContentType); | 330 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 344 | d.skip(2); // legacy_version | 331 | error.EndOfStream => return error.TlsConnectionTruncated, |
| 345 | const record_len = d.decode(u16); | 332 | error.ReadFailed => return error.ReadFailed, |
| 346 | try d.readAtLeast(input, record_len); | 333 | }; |
| 347 | var record_decoder = try d.sub(record_len); | 334 | const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked |
| | 335 | input.toss(2); // legacy_version |
| | 336 | const record_len = input.takeInt(u16, .big) catch unreachable; // already peeked |
| | 337 | if (record_len > tls.max_ciphertext_len) return error.TlsRecordOverflow; |
| | 338 | const record_buffer = input.take(record_len) catch |err| switch (err) { |
| | 339 | error.EndOfStream => return error.TlsConnectionTruncated, |
| | 340 | error.ReadFailed => return error.ReadFailed, |
| | 341 | }; |
| | 342 | var record_decoder: tls.Decoder = .fromTheirSlice(record_buffer); |
| 348 | var ctd, const ct = content: switch (cipher_state) { | 343 | var ctd, const ct = content: switch (cipher_state) { |
| 349 | .cleartext => .{ record_decoder, record_ct }, | 344 | .cleartext => .{ record_decoder, record_ct }, |
| 350 | .handshake => { | 345 | .handshake => { |
| 351 | std.debug.assert(tls_version == .tls_1_3); | 346 | assert(tls_version == .tls_1_3); |
| 352 | if (record_ct != .application_data) return error.TlsUnexpectedMessage; | 347 | if (record_ct != .application_data) return error.TlsUnexpectedMessage; |
| 353 | try record_decoder.ensure(record_len); | 348 | try record_decoder.ensure(record_len); |
| 354 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; | 349 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; |
| ... | @@ -380,7 +375,7 @@ pub fn init( | ... | @@ -380,7 +375,7 @@ pub fn init( |
| 380 | break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct }; | 375 | break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct }; |
| 381 | }, | 376 | }, |
| 382 | .application => { | 377 | .application => { |
| 383 | std.debug.assert(tls_version == .tls_1_2); | 378 | assert(tls_version == .tls_1_2); |
| 384 | if (record_ct != .handshake) return error.TlsUnexpectedMessage; | 379 | if (record_ct != .handshake) return error.TlsUnexpectedMessage; |
| 385 | try record_decoder.ensure(record_len); | 380 | try record_decoder.ensure(record_len); |
| 386 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; | 381 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; |
| ... | @@ -536,7 +531,7 @@ pub fn init( | ... | @@ -536,7 +531,7 @@ pub fn init( |
| 536 | pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); | 531 | pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); |
| 537 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); | 532 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); |
| 538 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length); | 533 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length); |
| 539 | if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{ | 534 | if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ |
| 540 | .client_random = &client_hello_rand, | 535 | .client_random = &client_hello_rand, |
| 541 | }, .{ | 536 | }, .{ |
| 542 | .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret, | 537 | .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret, |
| ... | @@ -710,7 +705,7 @@ pub fn init( | ... | @@ -710,7 +705,7 @@ pub fn init( |
| 710 | &client_hello_rand, | 705 | &client_hello_rand, |
| 711 | &server_hello_rand, | 706 | &server_hello_rand, |
| 712 | }, 48); | 707 | }, 48); |
| 713 | if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{ | 708 | if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ |
| 714 | .client_random = &client_hello_rand, | 709 | .client_random = &client_hello_rand, |
| 715 | }, .{ | 710 | }, .{ |
| 716 | .CLIENT_RANDOM = &master_secret, | 711 | .CLIENT_RANDOM = &master_secret, |
| ... | @@ -763,7 +758,7 @@ pub fn init( | ... | @@ -763,7 +758,7 @@ pub fn init( |
| 763 | &client_change_cipher_spec_msg, | 758 | &client_change_cipher_spec_msg, |
| 764 | &client_verify_msg, | 759 | &client_verify_msg, |
| 765 | }; | 760 | }; |
| 766 | try output.writevAll(&all_msgs_vec); | 761 | try output.writeVecAll(&all_msgs_vec); |
| 767 | }, | 762 | }, |
| 768 | } | 763 | } |
| 769 | write_seq += 1; | 764 | write_seq += 1; |
| ... | @@ -828,11 +823,11 @@ pub fn init( | ... | @@ -828,11 +823,11 @@ pub fn init( |
| 828 | &client_change_cipher_spec_msg, | 823 | &client_change_cipher_spec_msg, |
| 829 | &finished_msg, | 824 | &finished_msg, |
| 830 | }; | 825 | }; |
| 831 | try output.writevAll(&all_msgs_vec); | 826 | try output.writeVecAll(&all_msgs_vec); |
| 832 | | 827 | |
| 833 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); | 828 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 834 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); | 829 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| 835 | if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{ | 830 | if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ |
| 836 | .counter = key_seq, | 831 | .counter = key_seq, |
| 837 | .client_random = &client_hello_rand, | 832 | .client_random = &client_hello_rand, |
| 838 | }, .{ | 833 | }, .{ |
| ... | @@ -859,11 +854,9 @@ pub fn init( | ... | @@ -859,11 +854,9 @@ pub fn init( |
| 859 | else => unreachable, | 854 | else => unreachable, |
| 860 | }, | 855 | }, |
| 861 | }; | 856 | }; |
| 862 | const leftover = d.rest(); | | |
| 863 | client.* = .{ | 857 | client.* = .{ |
| 864 | .input = input, | 858 | .input = input, |
| 865 | .output = output, | 859 | .output = output, |
| 866 | .reader = undefined, | | |
| 867 | .tls_version = tls_version, | 860 | .tls_version = tls_version, |
| 868 | .read_seq = switch (tls_version) { | 861 | .read_seq = switch (tls_version) { |
| 869 | .tls_1_3 => 0, | 862 | .tls_1_3 => 0, |
| ... | @@ -875,29 +868,18 @@ pub fn init( | ... | @@ -875,29 +868,18 @@ pub fn init( |
| 875 | .tls_1_2 => write_seq, | 868 | .tls_1_2 => write_seq, |
| 876 | else => unreachable, | 869 | else => unreachable, |
| 877 | }, | 870 | }, |
| 878 | .partial_cleartext_idx = 0, | 871 | .remaining_cleartext_len = 0, |
| 879 | .partial_ciphertext_idx = 0, | | |
| 880 | .partial_ciphertext_end = @intCast(leftover.len), | | |
| 881 | .received_close_notify = false, | 872 | .received_close_notify = false, |
| 882 | .allow_truncation_attacks = false, | 873 | .allow_truncation_attacks = false, |
| 883 | .application_cipher = app_cipher, | 874 | .application_cipher = app_cipher, |
| 884 | .partially_read_buffer = undefined, | 875 | .ssl_key_log = options.ssl_key_log, |
| 885 | .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{ | 876 | }; |
| 886 | .client_key_seq = key_seq, | 877 | if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{ |
| 887 | .server_key_seq = key_seq, | 878 | .client_key_seq = key_seq, |
| 888 | .client_random = client_hello_rand, | 879 | .server_key_seq = key_seq, |
| 889 | .file = key_log_file, | 880 | .client_random = client_hello_rand, |
| 890 | } else null, | 881 | .writer = ssl_key_log.writer, |
| 891 | }; | 882 | }; |
| 892 | @memcpy(client.partially_read_buffer[0..leftover.len], leftover); | | |
| 893 | client.reader.init(.{ | | |
| 894 | .context = client, | | |
| 895 | .vtable = &.{ | | |
| 896 | .read = read, | | |
| 897 | .readVec = readVec, | | |
| 898 | .discard = discard, | | |
| 899 | }, | | |
| 900 | }, input.storage.buffer[0..0]); | | |
| 901 | return; | 883 | return; |
| 902 | }, | 884 | }, |
| 903 | else => return error.TlsUnexpectedMessage, | 885 | else => return error.TlsUnexpectedMessage, |
| ... | @@ -912,17 +894,28 @@ pub fn init( | ... | @@ -912,17 +894,28 @@ pub fn init( |
| 912 | } | 894 | } |
| 913 | } | 895 | } |
| 914 | | 896 | |
| 915 | pub fn writer(c: *Client) std.io.Writer { | 897 | pub fn reader(c: *Client) Reader { |
| | 898 | return .{ |
| | 899 | .context = c, |
| | 900 | .vtable = &.{ |
| | 901 | .read = read, |
| | 902 | .readVec = readVec, |
| | 903 | .discard = discard, |
| | 904 | }, |
| | 905 | }; |
| | 906 | } |
| | 907 | |
| | 908 | pub fn writer(c: *Client) Writer { |
| 916 | return .{ | 909 | return .{ |
| 917 | .context = c, | 910 | .context = c, |
| 918 | .vtable = &.{ | 911 | .vtable = &.{ |
| 919 | .writeSplat = writeSplat, | 912 | .writeSplat = writeSplat, |
| 920 | .writeFile = std.io.Writer.unimplementedWriteFile, | 913 | .writeFile = Writer.unimplementedWriteFile, |
| 921 | }, | 914 | }, |
| 922 | }; | 915 | }; |
| 923 | } | 916 | } |
| 924 | | 917 | |
| 925 | fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { | 918 | fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize { |
| 926 | const c: *Client = @alignCast(@ptrCast(context)); | 919 | const c: *Client = @alignCast(@ptrCast(context)); |
| 927 | const sliced_data = if (splat == 0) data[0..data.len -| 1] else data; | 920 | const sliced_data = if (splat == 0) data[0..data.len -| 1] else data; |
| 928 | const output = c.output; | 921 | const output = c.output; |
| ... | @@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i | ... | @@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i |
| 942 | /// Sends a `close_notify` alert, which is necessary for the server to | 935 | /// Sends a `close_notify` alert, which is necessary for the server to |
| 943 | /// distinguish between a properly finished TLS session, or a truncation | 936 | /// distinguish between a properly finished TLS session, or a truncation |
| 944 | /// attack. | 937 | /// attack. |
| 945 | pub fn end(c: *Client) std.io.Writer.Error!void { | 938 | pub fn end(c: *Client) Writer.Error!void { |
| 946 | const output = c.output; | 939 | const output = c.output; |
| 947 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); | 940 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 948 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); | 941 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); |
| ... | @@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord( | ... | @@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord( |
| 1054 | } | 1047 | } |
| 1055 | | 1048 | |
| 1056 | pub fn eof(c: Client) bool { | 1049 | pub fn eof(c: Client) bool { |
| 1057 | return c.received_close_notify and | 1050 | return c.received_close_notify and c.remaining_cleartext_len == 0; |
| 1058 | c.partial_cleartext_idx >= c.partial_ciphertext_idx and | | |
| 1059 | c.partial_ciphertext_idx >= c.partial_ciphertext_end; | | |
| 1060 | } | | |
| 1061 | | | |
| 1062 | fn read( | | |
| 1063 | context: ?*anyopaque, | | |
| 1064 | bw: *std.io.BufferedWriter, | | |
| 1065 | limit: std.io.Reader.Limit, | | |
| 1066 | ) std.io.Reader.RwError!usize { | | |
| 1067 | const buf = limit.slice(try bw.writableSliceGreedy(1)); | | |
| 1068 | const n = try readVec(context, &.{buf}); | | |
| 1069 | bw.advance(n); | | |
| 1070 | return n; | | |
| 1071 | } | 1051 | } |
| 1072 | | 1052 | |
| 1073 | fn readVec(context: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { | 1053 | fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) Reader.RwError!usize { |
| 1074 | const c: *Client = @ptrCast(@alignCast(context)); | 1054 | const c: *Client = @ptrCast(@alignCast(context)); |
| 1075 | if (c.eof()) return error.EndOfStream; | 1055 | if (c.eof()) return error.EndOfStream; |
| 1076 | | 1056 | const input = c.input; |
| 1077 | var vp: VecPut = .{ .iovecs = data }; | 1057 | if (c.remaining_cleartext_len > 0) { |
| 1078 | | 1058 | const n = try bw.write(input.bufferContents()[0..c.remaining_cleartext_len]); |
| 1079 | // Give away the buffered cleartext we have, if any. | 1059 | c.remaining_cleartext_len = @intCast(c.remaining_cleartext_len - n); |
| 1080 | const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx]; | 1060 | return n; |
| 1081 | if (partial_cleartext.len > 0) { | | |
| 1082 | const amt: u15 = @intCast(vp.put(partial_cleartext)); | | |
| 1083 | c.partial_cleartext_idx += amt; | | |
| 1084 | | | |
| 1085 | if (c.partial_cleartext_idx == c.partial_ciphertext_idx and | | |
| 1086 | c.partial_ciphertext_end == c.partial_ciphertext_idx) | | |
| 1087 | { | | |
| 1088 | // The buffer is now empty. | | |
| 1089 | c.partial_cleartext_idx = 0; | | |
| 1090 | c.partial_ciphertext_idx = 0; | | |
| 1091 | c.partial_ciphertext_end = 0; | | |
| 1092 | } | | |
| 1093 | | | |
| 1094 | if (c.received_close_notify) { | | |
| 1095 | c.partial_ciphertext_end = 0; | | |
| 1096 | assert(vp.total == amt); | | |
| 1097 | return amt; | | |
| 1098 | } else if (amt > 0) { | | |
| 1099 | // We don't need more data, so don't call read. | | |
| 1100 | assert(vp.total == amt); | | |
| 1101 | return amt; | | |
| 1102 | } | | |
| 1103 | } | 1061 | } |
| 1104 | | 1062 | // If at least one full encrypted record is not buffered, read once. |
| 1105 | assert(!c.received_close_notify); | 1063 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 1106 | | 1064 | error.EndOfStream => { |
| 1107 | // Ideally, this buffer would never be used. It is needed when `iovecs` are | 1065 | // This is either a truncation attack, a bug in the server, or an |
| 1108 | // too small to fit the cleartext, which may be as large as `max_ciphertext_len`. | 1066 | // intentional omission of the close_notify message due to truncation |
| 1109 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; | 1067 | // detection handled above the TLS layer. |
| 1110 | // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`. | 1068 | if (c.allow_truncation_attacks) { |
| 1111 | var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined; | 1069 | c.received_close_notify = true; |
| 1112 | // How many bytes left in the user's buffer. | 1070 | return error.EndOfStream; |
| 1113 | const free_size = vp.freeSize(); | 1071 | } else { |
| 1114 | // The amount of the user's buffer that we need to repurpose for storing | 1072 | return failRead(c, error.TlsConnectionTruncated); |
| 1115 | // ciphertext. The end of the buffer will be used for such purposes. | 1073 | } |
| 1116 | const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len; | | |
| 1117 | // The amount of the user's buffer that will be used to give cleartext. The | | |
| 1118 | // beginning of the buffer will be used for such purposes. | | |
| 1119 | const cleartext_buf_len = free_size - ciphertext_buf_len; | | |
| 1120 | | | |
| 1121 | // Recoup `partially_read_buffer` space. This is necessary because it is assumed | | |
| 1122 | // below that `frag0` is big enough to hold at least one record. | | |
| 1123 | limitedOverlapCopy(c.partially_read_buffer[0..c.partial_ciphertext_end], c.partial_ciphertext_idx); | | |
| 1124 | c.partial_ciphertext_end -= c.partial_ciphertext_idx; | | |
| 1125 | c.partial_ciphertext_idx = 0; | | |
| 1126 | c.partial_cleartext_idx = 0; | | |
| 1127 | const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..]; | | |
| 1128 | | | |
| 1129 | var ask_iovecs_buf: [2]std.posix.iovec = .{ | | |
| 1130 | .{ | | |
| 1131 | .base = first_iov.ptr, | | |
| 1132 | .len = first_iov.len, | | |
| 1133 | }, | | |
| 1134 | .{ | | |
| 1135 | .base = &in_stack_buffer, | | |
| 1136 | .len = in_stack_buffer.len, | | |
| 1137 | }, | 1074 | }, |
| | 1075 | error.ReadFailed => return error.ReadFailed, |
| 1138 | }; | 1076 | }; |
| 1139 | | 1077 | const ct: tls.ContentType = @enumFromInt(record_header[0]); |
| 1140 | // Cleartext capacity of output buffer, in records. Minimum one full record. | 1078 | const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big); |
| 1141 | const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1); | 1079 | _ = legacy_version; |
| 1142 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len); | 1080 | const record_len = mem.readInt(u16, record_header[3..][0..2], .big); |
| 1143 | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end; | 1081 | if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow); |
| 1144 | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); | 1082 | const record_end = 5 + record_len; |
| 1145 | const actual_read_len = try c.input.readv(ask_iovecs); | 1083 | if (record_end > input.bufferContents().len) { |
| 1146 | if (actual_read_len == 0) { | 1084 | input.fillMore() catch |err| switch (err) { |
| 1147 | // This is either a truncation attack, a bug in the server, or an | 1085 | error.EndOfStream => return failRead(c, error.TlsConnectionTruncated), |
| 1148 | // intentional omission of the close_notify message due to truncation | 1086 | error.ReadFailed => return error.ReadFailed, |
| 1149 | // detection handled above the TLS layer. | 1087 | }; |
| 1150 | if (c.allow_truncation_attacks) { | 1088 | if (record_end > input.bufferContents().len) return 0; |
| 1151 | c.received_close_notify = true; | | |
| 1152 | } else { | | |
| 1153 | return failRead(c, error.TlsConnectionTruncated); | | |
| 1154 | } | | |
| 1155 | } | 1089 | } |
| 1156 | | 1090 | |
| 1157 | // There might be more bytes inside `in_stack_buffer` that need to be processed, | 1091 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; |
| 1158 | // but at least frag0 will have one complete ciphertext record. | 1092 | const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) { |
| 1159 | const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len); | 1093 | inline else => |*p| switch (c.tls_version) { |
| 1160 | const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end]; | 1094 | .tls_1_3 => { |
| 1161 | var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len]; | 1095 | const pv = &p.tls_1_3; |
| 1162 | // We need to decipher frag0 and frag1 but there may be a ciphertext record | 1096 | const P = @TypeOf(p.*); |
| 1163 | // straddling the boundary. We can handle this with two memcpy() calls to | 1097 | const ad = input.take(tls.record_header_len) catch unreachable; // already peeked |
| 1164 | // assemble the straddling record in between handling the two sides. | 1098 | const ciphertext_len = record_len - P.AEAD.tag_length; |
| 1165 | var frag = frag0; | 1099 | const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked |
| 1166 | var in: usize = 0; | 1100 | const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked |
| 1167 | while (true) { | 1101 | const nonce = nonce: { |
| 1168 | if (in == frag.len) { | 1102 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 1169 | // Perfect split. | 1103 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 1170 | if (frag.ptr == frag1.ptr) { | 1104 | const operand: V = pad ++ std.mem.toBytes(big(c.read_seq)); |
| 1171 | c.partial_ciphertext_end = c.partial_ciphertext_idx; | 1105 | break :nonce @as(V, pv.server_iv) ^ operand; |
| 1172 | return vp.total; | | |
| 1173 | } | | |
| 1174 | frag = frag1; | | |
| 1175 | in = 0; | | |
| 1176 | continue; | | |
| 1177 | } | | |
| 1178 | | | |
| 1179 | if (in + tls.record_header_len > frag.len) { | | |
| 1180 | if (frag.ptr == frag1.ptr) | | |
| 1181 | return finishRead(c, frag, in, vp.total); | | |
| 1182 | | | |
| 1183 | const first = frag[in..]; | | |
| 1184 | | | |
| 1185 | if (frag1.len < tls.record_header_len) | | |
| 1186 | return finishRead2(c, first, frag1, vp.total); | | |
| 1187 | | | |
| 1188 | // A record straddles the two fragments. Copy into the now-empty first fragment. | | |
| 1189 | const record_len_byte_0: u16 = straddleByte(frag, frag1, in + 3); | | |
| 1190 | const record_len_byte_1: u16 = straddleByte(frag, frag1, in + 4); | | |
| 1191 | const record_len = (record_len_byte_0 << 8) | record_len_byte_1; | | |
| 1192 | if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow); | | |
| 1193 | | | |
| 1194 | const full_record_len = record_len + tls.record_header_len; | | |
| 1195 | const second_len = full_record_len - first.len; | | |
| 1196 | if (frag1.len < second_len) | | |
| 1197 | return finishRead2(c, first, frag1, vp.total); | | |
| 1198 | | | |
| 1199 | limitedOverlapCopy(frag, in); | | |
| 1200 | @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]); | | |
| 1201 | frag = frag[0..full_record_len]; | | |
| 1202 | frag1 = frag1[second_len..]; | | |
| 1203 | in = 0; | | |
| 1204 | continue; | | |
| 1205 | } | | |
| 1206 | const ct: tls.ContentType = @enumFromInt(frag[in]); | | |
| 1207 | in += 1; | | |
| 1208 | const legacy_version = mem.readInt(u16, frag[in..][0..2], .big); | | |
| 1209 | in += 2; | | |
| 1210 | _ = legacy_version; | | |
| 1211 | const record_len = mem.readInt(u16, frag[in..][0..2], .big); | | |
| 1212 | if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow); | | |
| 1213 | in += 2; | | |
| 1214 | const the_end = in + record_len; | | |
| 1215 | if (the_end > frag.len) { | | |
| 1216 | // We need the record header on the next iteration of the loop. | | |
| 1217 | in -= tls.record_header_len; | | |
| 1218 | | | |
| 1219 | if (frag.ptr == frag1.ptr) | | |
| 1220 | return finishRead(c, frag, in, vp.total); | | |
| 1221 | | | |
| 1222 | // A record straddles the two fragments. Copy into the now-empty first fragment. | | |
| 1223 | const first = frag[in..]; | | |
| 1224 | const full_record_len = record_len + tls.record_header_len; | | |
| 1225 | const second_len = full_record_len - first.len; | | |
| 1226 | if (frag1.len < second_len) | | |
| 1227 | return finishRead2(c, first, frag1, vp.total); | | |
| 1228 | | | |
| 1229 | limitedOverlapCopy(frag, in); | | |
| 1230 | @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]); | | |
| 1231 | frag = frag[0..full_record_len]; | | |
| 1232 | frag1 = frag1[second_len..]; | | |
| 1233 | in = 0; | | |
| 1234 | continue; | | |
| 1235 | } | | |
| 1236 | const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) { | | |
| 1237 | inline else => |*p| switch (c.tls_version) { | | |
| 1238 | .tls_1_3 => { | | |
| 1239 | const pv = &p.tls_1_3; | | |
| 1240 | const P = @TypeOf(p.*); | | |
| 1241 | const ad = frag[in - tls.record_header_len ..][0..tls.record_header_len]; | | |
| 1242 | const ciphertext_len = record_len - P.AEAD.tag_length; | | |
| 1243 | const ciphertext = frag[in..][0..ciphertext_len]; | | |
| 1244 | in += ciphertext_len; | | |
| 1245 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; | | |
| 1246 | const nonce = nonce: { | | |
| 1247 | const V = @Vector(P.AEAD.nonce_length, u8); | | |
| 1248 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | | |
| 1249 | const operand: V = pad ++ std.mem.toBytes(big(c.read_seq)); | | |
| 1250 | break :nonce @as(V, pv.server_iv) ^ operand; | | |
| 1251 | }; | | |
| 1252 | const out_buf = vp.peek(); | | |
| 1253 | const cleartext_buf = if (ciphertext.len <= out_buf.len) | | |
| 1254 | out_buf | | |
| 1255 | else | | |
| 1256 | &cleartext_stack_buffer; | | |
| 1257 | const cleartext = cleartext_buf[0..ciphertext.len]; | | |
| 1258 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch | | |
| 1259 | return failRead(c, error.TlsBadRecordMac); | | |
| 1260 | const msg = mem.trimEnd(u8, cleartext, "\x00"); | | |
| 1261 | break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) }; | | |
| 1262 | }, | | |
| 1263 | .tls_1_2 => { | | |
| 1264 | const pv = &p.tls_1_2; | | |
| 1265 | const P = @TypeOf(p.*); | | |
| 1266 | const message_len: u16 = record_len - P.record_iv_length - P.mac_length; | | |
| 1267 | const ad = std.mem.toBytes(big(c.read_seq)) ++ | | |
| 1268 | frag[in - tls.record_header_len ..][0 .. 1 + 2] ++ | | |
| 1269 | std.mem.toBytes(big(message_len)); | | |
| 1270 | const record_iv = frag[in..][0..P.record_iv_length].*; | | |
| 1271 | in += P.record_iv_length; | | |
| 1272 | const masked_read_seq = c.read_seq & | | |
| 1273 | comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length); | | |
| 1274 | const nonce: [P.AEAD.nonce_length]u8 = nonce: { | | |
| 1275 | const V = @Vector(P.AEAD.nonce_length, u8); | | |
| 1276 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | | |
| 1277 | const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq))); | | |
| 1278 | break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand; | | |
| 1279 | }; | | |
| 1280 | const ciphertext = frag[in..][0..message_len]; | | |
| 1281 | in += message_len; | | |
| 1282 | const auth_tag = frag[in..][0..P.mac_length].*; | | |
| 1283 | in += P.mac_length; | | |
| 1284 | const out_buf = vp.peek(); | | |
| 1285 | const cleartext_buf = if (message_len <= out_buf.len) | | |
| 1286 | out_buf | | |
| 1287 | else | | |
| 1288 | &cleartext_stack_buffer; | | |
| 1289 | const cleartext = cleartext_buf[0..ciphertext.len]; | | |
| 1290 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch | | |
| 1291 | return failRead(c, error.TlsBadRecordMac); | | |
| 1292 | break :cleartext .{ cleartext, ct }; | | |
| 1293 | }, | | |
| 1294 | else => unreachable, | | |
| 1295 | }, | | |
| 1296 | }; | | |
| 1297 | c.read_seq = try std.math.add(u64, c.read_seq, 1); | | |
| 1298 | switch (inner_ct) { | | |
| 1299 | .alert => { | | |
| 1300 | if (cleartext.len != 2) return failRead(c, error.TlsDecodeError); | | |
| 1301 | const alert: tls.Alert = .{ | | |
| 1302 | .level = @enumFromInt(cleartext[0]), | | |
| 1303 | .description = @enumFromInt(cleartext[1]), | | |
| 1304 | }; | 1106 | }; |
| 1305 | switch (alert.description) { | 1107 | const cleartext = cleartext_stack_buffer[0..ciphertext.len]; |
| 1306 | .close_notify => { | 1108 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch |
| 1307 | c.received_close_notify = true; | 1109 | return failRead(c, error.TlsBadRecordMac); |
| 1308 | c.partial_ciphertext_end = c.partial_ciphertext_idx; | 1110 | const msg = mem.trimRight(u8, cleartext, "\x00"); |
| 1309 | return vp.total; | 1111 | break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) }; |
| 1310 | }, | | |
| 1311 | .user_canceled => { | | |
| 1312 | // TODO: handle server-side closures | | |
| 1313 | return failRead(c, error.TlsUnexpectedMessage); | | |
| 1314 | }, | | |
| 1315 | else => { | | |
| 1316 | c.alert = alert; | | |
| 1317 | return failRead(c, error.TlsAlert); | | |
| 1318 | }, | | |
| 1319 | } | | |
| 1320 | }, | 1112 | }, |
| 1321 | .handshake => { | 1113 | .tls_1_2 => { |
| 1322 | var ct_i: usize = 0; | 1114 | const pv = &p.tls_1_2; |
| 1323 | while (true) { | 1115 | const P = @TypeOf(p.*); |
| 1324 | const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]); | 1116 | const message_len: u16 = record_len - P.record_iv_length - P.mac_length; |
| 1325 | ct_i += 1; | 1117 | const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked |
| 1326 | const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big); | 1118 | const ad = std.mem.toBytes(big(c.read_seq)) ++ |
| 1327 | ct_i += 3; | 1119 | ad_header[0 .. 1 + 2] ++ |
| 1328 | const next_handshake_i = ct_i + handshake_len; | 1120 | std.mem.toBytes(big(message_len)); |
| 1329 | if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength); | 1121 | const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked |
| 1330 | const handshake = cleartext[ct_i..next_handshake_i]; | 1122 | const masked_read_seq = c.read_seq & |
| 1331 | switch (handshake_type) { | 1123 | comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length); |
| 1332 | .new_session_ticket => { | 1124 | const nonce: [P.AEAD.nonce_length]u8 = nonce: { |
| 1333 | // This client implementation ignores new session tickets. | 1125 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 1334 | }, | 1126 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 1335 | .key_update => { | 1127 | const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq))); |
| 1336 | switch (c.application_cipher) { | 1128 | break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand; |
| 1337 | inline else => |*p| { | 1129 | }; |
| 1338 | const pv = &p.tls_1_3; | 1130 | const ciphertext = input.take(message_len) catch unreachable; // already peeked |
| 1339 | const P = @TypeOf(p.*); | 1131 | const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked |
| 1340 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length); | 1132 | const cleartext = cleartext_stack_buffer[0..ciphertext.len]; |
| 1341 | if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{ | 1133 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch |
| 1342 | .counter = key_log.serverCounter(), | 1134 | return failRead(c, error.TlsBadRecordMac); |
| 1343 | .client_random = &key_log.client_random, | 1135 | break :cleartext .{ cleartext, ct }; |
| 1344 | }, .{ | | |
| 1345 | .SERVER_TRAFFIC_SECRET = &server_secret, | | |
| 1346 | }); | | |
| 1347 | pv.server_secret = server_secret; | | |
| 1348 | pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length); | | |
| 1349 | pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length); | | |
| 1350 | }, | | |
| 1351 | } | | |
| 1352 | c.read_seq = 0; | | |
| 1353 | | | |
| 1354 | switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) { | | |
| 1355 | .update_requested => { | | |
| 1356 | switch (c.application_cipher) { | | |
| 1357 | inline else => |*p| { | | |
| 1358 | const pv = &p.tls_1_3; | | |
| 1359 | const P = @TypeOf(p.*); | | |
| 1360 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length); | | |
| 1361 | if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{ | | |
| 1362 | .counter = key_log.clientCounter(), | | |
| 1363 | .client_random = &key_log.client_random, | | |
| 1364 | }, .{ | | |
| 1365 | .CLIENT_TRAFFIC_SECRET = &client_secret, | | |
| 1366 | }); | | |
| 1367 | pv.client_secret = client_secret; | | |
| 1368 | pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length); | | |
| 1369 | pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length); | | |
| 1370 | }, | | |
| 1371 | } | | |
| 1372 | c.write_seq = 0; | | |
| 1373 | }, | | |
| 1374 | .update_not_requested => {}, | | |
| 1375 | _ => return failRead(c, error.TlsIllegalParameter), | | |
| 1376 | } | | |
| 1377 | }, | | |
| 1378 | else => return failRead(c, error.TlsUnexpectedMessage), | | |
| 1379 | } | | |
| 1380 | ct_i = next_handshake_i; | | |
| 1381 | if (ct_i >= cleartext.len) break; | | |
| 1382 | } | | |
| 1383 | }, | 1136 | }, |
| 1384 | .application_data => { | 1137 | else => unreachable, |
| 1385 | // Determine whether the output buffer or a stack | 1138 | }, |
| 1386 | // buffer was used for storing the cleartext. | 1139 | }; |
| 1387 | if (cleartext.ptr == &cleartext_stack_buffer) { | 1140 | c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow); |
| 1388 | // Stack buffer was used, so we must copy to the output buffer. | 1141 | switch (inner_ct) { |
| 1389 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { | 1142 | .alert => { |
| 1390 | // We have already run out of room in iovecs. Continue | 1143 | if (cleartext.len != 2) return failRead(c, error.TlsDecodeError); |
| 1391 | // appending to `partially_read_buffer`. | 1144 | const alert: tls.Alert = .{ |
| 1392 | @memcpy( | 1145 | .level = @enumFromInt(cleartext[0]), |
| 1393 | c.partially_read_buffer[c.partial_ciphertext_idx..][0..cleartext.len], | 1146 | .description = @enumFromInt(cleartext[1]), |
| 1394 | cleartext, | 1147 | }; |
| 1395 | ); | 1148 | switch (alert.description) { |
| 1396 | c.partial_ciphertext_idx = @intCast(c.partial_ciphertext_idx + cleartext.len); | 1149 | .close_notify => { |
| 1397 | } else { | 1150 | c.received_close_notify = true; |
| 1398 | const amt = vp.put(cleartext); | 1151 | return 0; |
| 1399 | if (amt < cleartext.len) { | 1152 | }, |
| 1400 | const rest = cleartext[amt..]; | 1153 | .user_canceled => { |
| 1401 | c.partial_cleartext_idx = 0; | 1154 | // TODO: handle server-side closures |
| 1402 | c.partial_ciphertext_idx = @intCast(rest.len); | 1155 | return failRead(c, error.TlsUnexpectedMessage); |
| 1403 | @memcpy(c.partially_read_buffer[0..rest.len], rest); | 1156 | }, |
| | 1157 | else => { |
| | 1158 | c.alert = alert; |
| | 1159 | return failRead(c, error.TlsAlert); |
| | 1160 | }, |
| | 1161 | } |
| | 1162 | }, |
| | 1163 | .handshake => { |
| | 1164 | var ct_i: usize = 0; |
| | 1165 | while (true) { |
| | 1166 | const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]); |
| | 1167 | ct_i += 1; |
| | 1168 | const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big); |
| | 1169 | ct_i += 3; |
| | 1170 | const next_handshake_i = ct_i + handshake_len; |
| | 1171 | if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength); |
| | 1172 | const handshake = cleartext[ct_i..next_handshake_i]; |
| | 1173 | switch (handshake_type) { |
| | 1174 | .new_session_ticket => { |
| | 1175 | // This client implementation ignores new session tickets. |
| | 1176 | }, |
| | 1177 | .key_update => { |
| | 1178 | switch (c.application_cipher) { |
| | 1179 | inline else => |*p| { |
| | 1180 | const pv = &p.tls_1_3; |
| | 1181 | const P = @TypeOf(p.*); |
| | 1182 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length); |
| | 1183 | if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ |
| | 1184 | .counter = key_log.serverCounter(), |
| | 1185 | .client_random = &key_log.client_random, |
| | 1186 | }, .{ |
| | 1187 | .SERVER_TRAFFIC_SECRET = &server_secret, |
| | 1188 | }); |
| | 1189 | pv.server_secret = server_secret; |
| | 1190 | pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length); |
| | 1191 | pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length); |
| | 1192 | }, |
| 1404 | } | 1193 | } |
| 1405 | } | 1194 | c.read_seq = 0; |
| 1406 | } else { | 1195 | |
| 1407 | // Output buffer was used directly which means no | 1196 | switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) { |
| 1408 | // memory copying needs to occur, and we can move | 1197 | .update_requested => { |
| 1409 | // on to the next ciphertext record. | 1198 | switch (c.application_cipher) { |
| 1410 | vp.next(cleartext.len); | 1199 | inline else => |*p| { |
| | 1200 | const pv = &p.tls_1_3; |
| | 1201 | const P = @TypeOf(p.*); |
| | 1202 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length); |
| | 1203 | if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ |
| | 1204 | .counter = key_log.clientCounter(), |
| | 1205 | .client_random = &key_log.client_random, |
| | 1206 | }, .{ |
| | 1207 | .CLIENT_TRAFFIC_SECRET = &client_secret, |
| | 1208 | }); |
| | 1209 | pv.client_secret = client_secret; |
| | 1210 | pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length); |
| | 1211 | pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length); |
| | 1212 | }, |
| | 1213 | } |
| | 1214 | c.write_seq = 0; |
| | 1215 | }, |
| | 1216 | .update_not_requested => {}, |
| | 1217 | _ => return failRead(c, error.TlsIllegalParameter), |
| | 1218 | } |
| | 1219 | }, |
| | 1220 | else => return failRead(c, error.TlsUnexpectedMessage), |
| 1411 | } | 1221 | } |
| 1412 | }, | 1222 | ct_i = next_handshake_i; |
| 1413 | else => return failRead(c, error.TlsUnexpectedMessage), | 1223 | if (ct_i >= cleartext.len) break; |
| 1414 | } | 1224 | } |
| 1415 | in = end; | 1225 | return 0; |
| | 1226 | }, |
| | 1227 | .application_data => { |
| | 1228 | const n = try bw.write(limit.sliceConst(cleartext)); |
| | 1229 | if (n < cleartext.len) { |
| | 1230 | const remainder = cleartext[n..]; |
| | 1231 | input.unread(remainder); |
| | 1232 | c.remaining_cleartext_len = @intCast(remainder.len); |
| | 1233 | } |
| | 1234 | return n; |
| | 1235 | }, |
| | 1236 | else => return failRead(c, error.TlsUnexpectedMessage), |
| 1416 | } | 1237 | } |
| 1417 | } | 1238 | } |
| 1418 | | 1239 | |
| 1419 | fn discard(context: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { | 1240 | fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize { |
| 1420 | _ = context; | 1241 | var bw: std.io.BufferedWriter = undefined; |
| 1421 | _ = limit; | 1242 | bw.initFixed(data[0]); |
| 1422 | @panic("TODO"); | 1243 | return read(context, &bw, .limited(data[0].len)) catch |err| switch (err) { |
| | 1244 | error.WriteFailed => unreachable, |
| | 1245 | else => |e| return e, |
| | 1246 | }; |
| | 1247 | } |
| | 1248 | |
| | 1249 | fn discard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize { |
| | 1250 | var null_writer: Writer.Null = undefined; |
| | 1251 | var bw = null_writer.writer().unbuffered(); |
| | 1252 | return read(context, &bw, limit) catch |err| switch (err) { |
| | 1253 | error.WriteFailed => unreachable, |
| | 1254 | else => |e| return e, |
| | 1255 | }; |
| 1423 | } | 1256 | } |
| 1424 | | 1257 | |
| 1425 | fn failRead(c: *Client, err: ReadError) error{ReadFailed} { | 1258 | fn failRead(c: *Client, err: ReadError) error{ReadFailed} { |
| ... | @@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} { | ... | @@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} { |
| 1427 | return error.ReadFailed; | 1260 | return error.ReadFailed; |
| 1428 | } | 1261 | } |
| 1429 | | 1262 | |
| 1430 | fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void { | 1263 | fn logSecrets(bw: *std.io.BufferedWriter, context: anytype, secrets: anytype) void { |
| 1431 | const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false; | 1264 | inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++ |
| 1432 | defer if (locked) key_log_file.unlock(); | | |
| 1433 | key_log_file.seekFromEnd(0) catch {}; | | |
| 1434 | var w = key_log_file.writer().unbuffered(); | | |
| 1435 | inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++ | | |
| 1436 | (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++ | 1265 | (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++ |
| 1437 | (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{ | 1266 | (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{ |
| 1438 | context.client_random, | 1267 | context.client_random, |
| ... | @@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi | ... | @@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi |
| 1440 | }) catch {}; | 1269 | }) catch {}; |
| 1441 | } | 1270 | } |
| 1442 | | 1271 | |
| 1443 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) std.io.Reader.Status { | | |
| 1444 | const saved_buf = frag[in..]; | | |
| 1445 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { | | |
| 1446 | // There is cleartext at the beginning already which we need to preserve. | | |
| 1447 | c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + saved_buf.len); | | |
| 1448 | @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx..][0..saved_buf.len], saved_buf); | | |
| 1449 | } else { | | |
| 1450 | c.partial_cleartext_idx = 0; | | |
| 1451 | c.partial_ciphertext_idx = 0; | | |
| 1452 | c.partial_ciphertext_end = @intCast(saved_buf.len); | | |
| 1453 | @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf); | | |
| 1454 | } | | |
| 1455 | return .{ .len = out, .end = c.eof() }; | | |
| 1456 | } | | |
| 1457 | | | |
| 1458 | /// Note that `first` usually overlaps with `c.partially_read_buffer`. | | |
| 1459 | fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) std.io.Reader.Status { | | |
| 1460 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { | | |
| 1461 | // There is cleartext at the beginning already which we need to preserve. | | |
| 1462 | c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len); | | |
| 1463 | // TODO: eliminate this call to copyForwards | | |
| 1464 | std.mem.copyForwards(u8, c.partially_read_buffer[c.partial_ciphertext_idx..][0..first.len], first); | | |
| 1465 | @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx + first.len ..][0..frag1.len], frag1); | | |
| 1466 | } else { | | |
| 1467 | c.partial_cleartext_idx = 0; | | |
| 1468 | c.partial_ciphertext_idx = 0; | | |
| 1469 | c.partial_ciphertext_end = @intCast(first.len + frag1.len); | | |
| 1470 | // TODO: eliminate this call to copyForwards | | |
| 1471 | std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first); | | |
| 1472 | @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1); | | |
| 1473 | } | | |
| 1474 | return .{ .len = out, .end = c.eof() }; | | |
| 1475 | } | | |
| 1476 | | | |
| 1477 | fn limitedOverlapCopy(frag: []u8, in: usize) void { | | |
| 1478 | const first = frag[in..]; | | |
| 1479 | if (first.len <= in) { | | |
| 1480 | // A single, non-overlapping memcpy suffices. | | |
| 1481 | @memcpy(frag[0..first.len], first); | | |
| 1482 | } else { | | |
| 1483 | // One memcpy call would overlap, so just do this instead. | | |
| 1484 | std.mem.copyForwards(u8, frag, first); | | |
| 1485 | } | | |
| 1486 | } | | |
| 1487 | | | |
| 1488 | fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 { | | |
| 1489 | if (index < s1.len) { | | |
| 1490 | return s1[index]; | | |
| 1491 | } else { | | |
| 1492 | return s2[index - s1.len]; | | |
| 1493 | } | | |
| 1494 | } | | |
| 1495 | | | |
| 1496 | inline fn big(x: anytype) @TypeOf(x) { | 1272 | inline fn big(x: anytype) @TypeOf(x) { |
| 1497 | return switch (native_endian) { | 1273 | return switch (native_endian) { |
| 1498 | .big => x, | 1274 | .big => x, |
| ... | @@ -1753,81 +1529,6 @@ const CertificatePublicKey = struct { | ... | @@ -1753,81 +1529,6 @@ const CertificatePublicKey = struct { |
| 1753 | } | 1529 | } |
| 1754 | }; | 1530 | }; |
| 1755 | | 1531 | |
| 1756 | /// Abstraction for sending multiple byte buffers to a slice of iovecs. | | |
| 1757 | const VecPut = struct { | | |
| 1758 | iovecs: []const std.posix.iovec, | | |
| 1759 | idx: usize = 0, | | |
| 1760 | off: usize = 0, | | |
| 1761 | total: usize = 0, | | |
| 1762 | | | |
| 1763 | /// Returns the amount actually put which is always equal to bytes.len | | |
| 1764 | /// unless the vectors ran out of space. | | |
| 1765 | fn put(vp: *VecPut, bytes: []const u8) usize { | | |
| 1766 | if (vp.idx >= vp.iovecs.len) return 0; | | |
| 1767 | var bytes_i: usize = 0; | | |
| 1768 | while (true) { | | |
| 1769 | const v = vp.iovecs[vp.idx]; | | |
| 1770 | const dest = v.base[vp.off..v.len]; | | |
| 1771 | const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)]; | | |
| 1772 | @memcpy(dest[0..src.len], src); | | |
| 1773 | bytes_i += src.len; | | |
| 1774 | vp.off += src.len; | | |
| 1775 | if (vp.off >= v.len) { | | |
| 1776 | vp.off = 0; | | |
| 1777 | vp.idx += 1; | | |
| 1778 | if (vp.idx >= vp.iovecs.len) { | | |
| 1779 | vp.total += bytes_i; | | |
| 1780 | return bytes_i; | | |
| 1781 | } | | |
| 1782 | } | | |
| 1783 | if (bytes_i >= bytes.len) { | | |
| 1784 | vp.total += bytes_i; | | |
| 1785 | return bytes_i; | | |
| 1786 | } | | |
| 1787 | } | | |
| 1788 | } | | |
| 1789 | | | |
| 1790 | /// Returns the next buffer that consecutive bytes can go into. | | |
| 1791 | fn peek(vp: VecPut) []u8 { | | |
| 1792 | if (vp.idx >= vp.iovecs.len) return &.{}; | | |
| 1793 | const v = vp.iovecs[vp.idx]; | | |
| 1794 | return v.base[vp.off..v.len]; | | |
| 1795 | } | | |
| 1796 | | | |
| 1797 | // After writing to the result of peek(), one can call next() to | | |
| 1798 | // advance the cursor. | | |
| 1799 | fn next(vp: *VecPut, len: usize) void { | | |
| 1800 | vp.total += len; | | |
| 1801 | vp.off += len; | | |
| 1802 | if (vp.off >= vp.iovecs[vp.idx].len) { | | |
| 1803 | vp.off = 0; | | |
| 1804 | vp.idx += 1; | | |
| 1805 | } | | |
| 1806 | } | | |
| 1807 | | | |
| 1808 | fn freeSize(vp: VecPut) usize { | | |
| 1809 | if (vp.idx >= vp.iovecs.len) return 0; | | |
| 1810 | var total: usize = 0; | | |
| 1811 | total += vp.iovecs[vp.idx].len - vp.off; | | |
| 1812 | if (vp.idx + 1 >= vp.iovecs.len) return total; | | |
| 1813 | for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.len; | | |
| 1814 | return total; | | |
| 1815 | } | | |
| 1816 | }; | | |
| 1817 | | | |
| 1818 | /// Limit iovecs to a specific byte size. | | |
| 1819 | fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec { | | |
| 1820 | var bytes_left: usize = len; | | |
| 1821 | for (iovecs, 0..) |*iovec, vec_i| { | | |
| 1822 | if (bytes_left <= iovec.len) { | | |
| 1823 | iovec.len = bytes_left; | | |
| 1824 | return iovecs[0 .. vec_i + 1]; | | |
| 1825 | } | | |
| 1826 | bytes_left -= iovec.len; | | |
| 1827 | } | | |
| 1828 | return iovecs; | | |
| 1829 | } | | |
| 1830 | | | |
| 1831 | /// The priority order here is chosen based on what crypto algorithms Zig has | 1532 | /// The priority order here is chosen based on what crypto algorithms Zig has |
| 1832 | /// available in the standard library as well as what is faster. Following are | 1533 | /// available in the standard library as well as what is faster. Following are |
| 1833 | /// a few data points on the relative performance of these algorithms. | 1534 | /// a few data points on the relative performance of these algorithms. |