| ... | ... | @@ -4,11 +4,12 @@ const native_endian = builtin.cpu.arch.endian(); |
| 4 | 4 | const std = @import("../../std.zig"); |
| 5 | 5 | const tls = std.crypto.tls; |
| 6 | 6 | const Client = @This(); |
| 7 | | const net = std.net; |
| 8 | 7 | const mem = std.mem; |
| 9 | 8 | const crypto = std.crypto; |
| 10 | 9 | const assert = std.debug.assert; |
| 11 | 10 | const Certificate = std.crypto.Certificate; |
| 11 | const Reader = std.io.Reader; |
| 12 | const Writer = std.io.Writer; |
| 12 | 13 | |
| 13 | 14 | const max_ciphertext_len = tls.max_ciphertext_len; |
| 14 | 15 | const hmacExpandLabel = tls.hmacExpandLabel; |
| ... | ... | @@ -21,38 +22,22 @@ const array = tls.array; |
| 21 | 22 | /// |
| 22 | 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 | | /// This buffer is segmented into four parts: |
| 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. |
| 25 | /// `remaining_cleartext_len` tells how many bytes inside this buffer have |
| 26 | /// already been decrypted. |
| 32 | 27 | input: *std.io.BufferedReader, |
| 28 | /// Tells how many bytes inside `input` have already been decrypted. |
| 29 | remaining_cleartext_len: u15, |
| 30 | |
| 33 | 31 | /// The encrypted stream from the client to the server. Bytes are pushed here |
| 34 | 32 | /// via `writer`. |
| 35 | | /// |
| 36 | | /// The buffer is asserted to have capacity at least `min_buffer_len`. |
| 37 | 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 | 38 | tls_version: tls.ProtocolVersion, |
| 47 | 39 | read_seq: u64, |
| 48 | 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 | 41 | /// When this is true, the stream may still not be at the end because there |
| 57 | 42 | /// may be data in the input buffer. |
| 58 | 43 | received_close_notify: bool, |
| ... | ... | @@ -60,11 +45,13 @@ received_close_notify: bool, |
| 60 | 45 | /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify |
| 61 | 46 | /// message has been received. By setting this flag to `true`, instead, the |
| 62 | 47 | /// end-of-stream will be forwarded to the application layer above TLS. |
| 48 | /// |
| 63 | 49 | /// This makes the application vulnerable to truncation attacks unless the |
| 64 | 50 | /// application layer itself verifies that the amount of data received equals |
| 65 | 51 | /// the amount of data expected, such as HTTP with the Content-Length header. |
| 66 | 52 | allow_truncation_attacks: bool, |
| 67 | 53 | application_cipher: tls.ApplicationCipher, |
| 54 | |
| 68 | 55 | /// If non-null, ssl secrets are logged to a stream. Creating such a log file |
| 69 | 56 | /// allows other programs with access to that file to decrypt all traffic over |
| 70 | 57 | /// this connection. |
| ... | ... | @@ -80,6 +67,7 @@ pub const ReadError = error{ |
| 80 | 67 | TlsRecordOverflow, |
| 81 | 68 | TlsUnexpectedMessage, |
| 82 | 69 | TlsIllegalParameter, |
| 70 | TlsSequenceOverflow, |
| 83 | 71 | }; |
| 84 | 72 | |
| 85 | 73 | 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` |
| 103 | | /// each require a buffer capacity at least this amount. |
| 90 | /// The `std.io.BufferedReader` supplied to `init` requires a buffer capacity |
| 91 | /// at least this amount. |
| 104 | 92 | pub const min_buffer_len = tls.max_ciphertext_record_len; |
| 105 | 93 | |
| 106 | 94 | pub const Options = struct { |
| ... | ... | @@ -126,7 +114,10 @@ pub const Options = struct { |
| 126 | 114 | }, |
| 127 | 115 | /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows |
| 128 | 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 | 123 | const InitError = error{ |
| ... | ... | @@ -183,16 +174,14 @@ const InitError = error{ |
| 183 | 174 | /// |
| 184 | 175 | /// `host` is only borrowed during this function call. |
| 185 | 176 | /// |
| 186 | | /// Both `input` and `output` are asserted to have buffer capacity at least |
| 187 | | /// `min_buffer_len`. |
| 177 | /// `input` is asserted to have buffer capacity at least `min_buffer_len`. |
| 188 | 178 | pub fn init( |
| 189 | 179 | client: *Client, |
| 190 | 180 | input: *std.io.BufferedReader, |
| 191 | 181 | output: *std.io.BufferedWriter, |
| 192 | 182 | options: Options, |
| 193 | 183 | ) InitError!void { |
| 194 | | assert(input.storage.buffer.len >= min_buffer_len); |
| 195 | | assert(output.buffer.len >= min_buffer_len); |
| 184 | assert(input.buffer.len >= min_buffer_len); |
| 196 | 185 | client.alert = null; |
| 197 | 186 | const host = switch (options.host) { |
| 198 | 187 | .no_verification => "", |
| ... | ... | @@ -286,7 +275,7 @@ pub fn init( |
| 286 | 275 | |
| 287 | 276 | { |
| 288 | 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 | 281 | var tls_version: tls.ProtocolVersion = undefined; |
| ... | ... | @@ -335,20 +324,26 @@ pub fn init( |
| 335 | 324 | var cleartext_fragment_start: usize = 0; |
| 336 | 325 | var cleartext_fragment_end: usize = 0; |
| 337 | 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 | 327 | fragment: while (true) { |
| 341 | | try d.readAtLeastOurAmt(input, tls.record_header_len); |
| 342 | | const record_header = d.buf[d.idx..][0..tls.record_header_len]; |
| 343 | | const record_ct = d.decode(tls.ContentType); |
| 344 | | d.skip(2); // legacy_version |
| 345 | | const record_len = d.decode(u16); |
| 346 | | try d.readAtLeast(input, record_len); |
| 347 | | var record_decoder = try d.sub(record_len); |
| 328 | // Ensure the input buffer pointer is stable in this scope. |
| 329 | input.rebaseCapacity(tls.max_ciphertext_record_len); |
| 330 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 331 | error.EndOfStream => return error.TlsConnectionTruncated, |
| 332 | error.ReadFailed => return error.ReadFailed, |
| 333 | }; |
| 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 | 343 | var ctd, const ct = content: switch (cipher_state) { |
| 349 | 344 | .cleartext => .{ record_decoder, record_ct }, |
| 350 | 345 | .handshake => { |
| 351 | | std.debug.assert(tls_version == .tls_1_3); |
| 346 | assert(tls_version == .tls_1_3); |
| 352 | 347 | if (record_ct != .application_data) return error.TlsUnexpectedMessage; |
| 353 | 348 | try record_decoder.ensure(record_len); |
| 354 | 349 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; |
| ... | ... | @@ -380,7 +375,7 @@ pub fn init( |
| 380 | 375 | break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct }; |
| 381 | 376 | }, |
| 382 | 377 | .application => { |
| 383 | | std.debug.assert(tls_version == .tls_1_2); |
| 378 | assert(tls_version == .tls_1_2); |
| 384 | 379 | if (record_ct != .handshake) return error.TlsUnexpectedMessage; |
| 385 | 380 | try record_decoder.ensure(record_len); |
| 386 | 381 | const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; |
| ... | ... | @@ -536,7 +531,7 @@ pub fn init( |
| 536 | 531 | pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); |
| 537 | 532 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); |
| 538 | 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 | 535 | .client_random = &client_hello_rand, |
| 541 | 536 | }, .{ |
| 542 | 537 | .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret, |
| ... | ... | @@ -710,7 +705,7 @@ pub fn init( |
| 710 | 705 | &client_hello_rand, |
| 711 | 706 | &server_hello_rand, |
| 712 | 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 | 709 | .client_random = &client_hello_rand, |
| 715 | 710 | }, .{ |
| 716 | 711 | .CLIENT_RANDOM = &master_secret, |
| ... | ... | @@ -763,7 +758,7 @@ pub fn init( |
| 763 | 758 | &client_change_cipher_spec_msg, |
| 764 | 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 | 764 | write_seq += 1; |
| ... | ... | @@ -828,11 +823,11 @@ pub fn init( |
| 828 | 823 | &client_change_cipher_spec_msg, |
| 829 | 824 | &finished_msg, |
| 830 | 825 | }; |
| 831 | | try output.writevAll(&all_msgs_vec); |
| 826 | try output.writeVecAll(&all_msgs_vec); |
| 832 | 827 | |
| 833 | 828 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 834 | 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 | 831 | .counter = key_seq, |
| 837 | 832 | .client_random = &client_hello_rand, |
| 838 | 833 | }, .{ |
| ... | ... | @@ -859,11 +854,9 @@ pub fn init( |
| 859 | 854 | else => unreachable, |
| 860 | 855 | }, |
| 861 | 856 | }; |
| 862 | | const leftover = d.rest(); |
| 863 | 857 | client.* = .{ |
| 864 | 858 | .input = input, |
| 865 | 859 | .output = output, |
| 866 | | .reader = undefined, |
| 867 | 860 | .tls_version = tls_version, |
| 868 | 861 | .read_seq = switch (tls_version) { |
| 869 | 862 | .tls_1_3 => 0, |
| ... | ... | @@ -875,29 +868,18 @@ pub fn init( |
| 875 | 868 | .tls_1_2 => write_seq, |
| 876 | 869 | else => unreachable, |
| 877 | 870 | }, |
| 878 | | .partial_cleartext_idx = 0, |
| 879 | | .partial_ciphertext_idx = 0, |
| 880 | | .partial_ciphertext_end = @intCast(leftover.len), |
| 871 | .remaining_cleartext_len = 0, |
| 881 | 872 | .received_close_notify = false, |
| 882 | 873 | .allow_truncation_attacks = false, |
| 883 | 874 | .application_cipher = app_cipher, |
| 884 | | .partially_read_buffer = undefined, |
| 885 | | .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{ |
| 886 | | .client_key_seq = key_seq, |
| 887 | | .server_key_seq = key_seq, |
| 888 | | .client_random = client_hello_rand, |
| 889 | | .file = key_log_file, |
| 890 | | } else null, |
| 875 | .ssl_key_log = options.ssl_key_log, |
| 876 | }; |
| 877 | if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{ |
| 878 | .client_key_seq = key_seq, |
| 879 | .server_key_seq = key_seq, |
| 880 | .client_random = client_hello_rand, |
| 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 | 883 | return; |
| 902 | 884 | }, |
| 903 | 885 | else => return error.TlsUnexpectedMessage, |
| ... | ... | @@ -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 | 909 | return .{ |
| 917 | 910 | .context = c, |
| 918 | 911 | .vtable = &.{ |
| 919 | 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 | 919 | const c: *Client = @alignCast(@ptrCast(context)); |
| 927 | 920 | const sliced_data = if (splat == 0) data[0..data.len -| 1] else data; |
| 928 | 921 | const output = c.output; |
| ... | ... | @@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i |
| 942 | 935 | /// Sends a `close_notify` alert, which is necessary for the server to |
| 943 | 936 | /// distinguish between a properly finished TLS session, or a truncation |
| 944 | 937 | /// attack. |
| 945 | | pub fn end(c: *Client) std.io.Writer.Error!void { |
| 938 | pub fn end(c: *Client) Writer.Error!void { |
| 946 | 939 | const output = c.output; |
| 947 | 940 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 948 | 941 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); |
| ... | ... | @@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord( |
| 1054 | 1047 | } |
| 1055 | 1048 | |
| 1056 | 1049 | pub fn eof(c: Client) bool { |
| 1057 | | return c.received_close_notify and |
| 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; |
| 1050 | return c.received_close_notify and c.remaining_cleartext_len == 0; |
| 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 | 1054 | const c: *Client = @ptrCast(@alignCast(context)); |
| 1075 | 1055 | if (c.eof()) return error.EndOfStream; |
| 1076 | | |
| 1077 | | var vp: VecPut = .{ .iovecs = data }; |
| 1078 | | |
| 1079 | | // Give away the buffered cleartext we have, if any. |
| 1080 | | const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx]; |
| 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 | | } |
| 1056 | const input = c.input; |
| 1057 | if (c.remaining_cleartext_len > 0) { |
| 1058 | const n = try bw.write(input.bufferContents()[0..c.remaining_cleartext_len]); |
| 1059 | c.remaining_cleartext_len = @intCast(c.remaining_cleartext_len - n); |
| 1060 | return n; |
| 1103 | 1061 | } |
| 1104 | | |
| 1105 | | assert(!c.received_close_notify); |
| 1106 | | |
| 1107 | | // Ideally, this buffer would never be used. It is needed when `iovecs` are |
| 1108 | | // too small to fit the cleartext, which may be as large as `max_ciphertext_len`. |
| 1109 | | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; |
| 1110 | | // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`. |
| 1111 | | var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined; |
| 1112 | | // How many bytes left in the user's buffer. |
| 1113 | | const free_size = vp.freeSize(); |
| 1114 | | // The amount of the user's buffer that we need to repurpose for storing |
| 1115 | | // ciphertext. The end of the buffer will be used for such purposes. |
| 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, |
| 1062 | // If at least one full encrypted record is not buffered, read once. |
| 1063 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 1064 | error.EndOfStream => { |
| 1065 | // This is either a truncation attack, a bug in the server, or an |
| 1066 | // intentional omission of the close_notify message due to truncation |
| 1067 | // detection handled above the TLS layer. |
| 1068 | if (c.allow_truncation_attacks) { |
| 1069 | c.received_close_notify = true; |
| 1070 | return error.EndOfStream; |
| 1071 | } else { |
| 1072 | return failRead(c, error.TlsConnectionTruncated); |
| 1073 | } |
| 1137 | 1074 | }, |
| 1075 | error.ReadFailed => return error.ReadFailed, |
| 1138 | 1076 | }; |
| 1139 | | |
| 1140 | | // Cleartext capacity of output buffer, in records. Minimum one full record. |
| 1141 | | const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1); |
| 1142 | | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len); |
| 1143 | | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end; |
| 1144 | | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); |
| 1145 | | const actual_read_len = try c.input.readv(ask_iovecs); |
| 1146 | | if (actual_read_len == 0) { |
| 1147 | | // This is either a truncation attack, a bug in the server, or an |
| 1148 | | // intentional omission of the close_notify message due to truncation |
| 1149 | | // detection handled above the TLS layer. |
| 1150 | | if (c.allow_truncation_attacks) { |
| 1151 | | c.received_close_notify = true; |
| 1152 | | } else { |
| 1153 | | return failRead(c, error.TlsConnectionTruncated); |
| 1154 | | } |
| 1077 | const ct: tls.ContentType = @enumFromInt(record_header[0]); |
| 1078 | const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big); |
| 1079 | _ = legacy_version; |
| 1080 | const record_len = mem.readInt(u16, record_header[3..][0..2], .big); |
| 1081 | if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow); |
| 1082 | const record_end = 5 + record_len; |
| 1083 | if (record_end > input.bufferContents().len) { |
| 1084 | input.fillMore() catch |err| switch (err) { |
| 1085 | error.EndOfStream => return failRead(c, error.TlsConnectionTruncated), |
| 1086 | error.ReadFailed => return error.ReadFailed, |
| 1087 | }; |
| 1088 | if (record_end > input.bufferContents().len) return 0; |
| 1155 | 1089 | } |
| 1156 | 1090 | |
| 1157 | | // There might be more bytes inside `in_stack_buffer` that need to be processed, |
| 1158 | | // but at least frag0 will have one complete ciphertext record. |
| 1159 | | const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len); |
| 1160 | | const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end]; |
| 1161 | | var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len]; |
| 1162 | | // We need to decipher frag0 and frag1 but there may be a ciphertext record |
| 1163 | | // straddling the boundary. We can handle this with two memcpy() calls to |
| 1164 | | // assemble the straddling record in between handling the two sides. |
| 1165 | | var frag = frag0; |
| 1166 | | var in: usize = 0; |
| 1167 | | while (true) { |
| 1168 | | if (in == frag.len) { |
| 1169 | | // Perfect split. |
| 1170 | | if (frag.ptr == frag1.ptr) { |
| 1171 | | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| 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]), |
| 1091 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; |
| 1092 | const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) { |
| 1093 | inline else => |*p| switch (c.tls_version) { |
| 1094 | .tls_1_3 => { |
| 1095 | const pv = &p.tls_1_3; |
| 1096 | const P = @TypeOf(p.*); |
| 1097 | const ad = input.take(tls.record_header_len) catch unreachable; // already peeked |
| 1098 | const ciphertext_len = record_len - P.AEAD.tag_length; |
| 1099 | const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked |
| 1100 | const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked |
| 1101 | const nonce = nonce: { |
| 1102 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 1103 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 1104 | const operand: V = pad ++ std.mem.toBytes(big(c.read_seq)); |
| 1105 | break :nonce @as(V, pv.server_iv) ^ operand; |
| 1304 | 1106 | }; |
| 1305 | | switch (alert.description) { |
| 1306 | | .close_notify => { |
| 1307 | | c.received_close_notify = true; |
| 1308 | | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| 1309 | | return vp.total; |
| 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 | | } |
| 1107 | const cleartext = cleartext_stack_buffer[0..ciphertext.len]; |
| 1108 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch |
| 1109 | return failRead(c, error.TlsBadRecordMac); |
| 1110 | const msg = mem.trimRight(u8, cleartext, "\x00"); |
| 1111 | break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) }; |
| 1320 | 1112 | }, |
| 1321 | | .handshake => { |
| 1322 | | var ct_i: usize = 0; |
| 1323 | | while (true) { |
| 1324 | | const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]); |
| 1325 | | ct_i += 1; |
| 1326 | | const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big); |
| 1327 | | ct_i += 3; |
| 1328 | | const next_handshake_i = ct_i + handshake_len; |
| 1329 | | if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength); |
| 1330 | | const handshake = cleartext[ct_i..next_handshake_i]; |
| 1331 | | switch (handshake_type) { |
| 1332 | | .new_session_ticket => { |
| 1333 | | // This client implementation ignores new session tickets. |
| 1334 | | }, |
| 1335 | | .key_update => { |
| 1336 | | switch (c.application_cipher) { |
| 1337 | | inline else => |*p| { |
| 1338 | | const pv = &p.tls_1_3; |
| 1339 | | const P = @TypeOf(p.*); |
| 1340 | | const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length); |
| 1341 | | if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{ |
| 1342 | | .counter = key_log.serverCounter(), |
| 1343 | | .client_random = &key_log.client_random, |
| 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 | | } |
| 1113 | .tls_1_2 => { |
| 1114 | const pv = &p.tls_1_2; |
| 1115 | const P = @TypeOf(p.*); |
| 1116 | const message_len: u16 = record_len - P.record_iv_length - P.mac_length; |
| 1117 | const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked |
| 1118 | const ad = std.mem.toBytes(big(c.read_seq)) ++ |
| 1119 | ad_header[0 .. 1 + 2] ++ |
| 1120 | std.mem.toBytes(big(message_len)); |
| 1121 | const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked |
| 1122 | const masked_read_seq = c.read_seq & |
| 1123 | comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length); |
| 1124 | const nonce: [P.AEAD.nonce_length]u8 = nonce: { |
| 1125 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 1126 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 1127 | const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq))); |
| 1128 | break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand; |
| 1129 | }; |
| 1130 | const ciphertext = input.take(message_len) catch unreachable; // already peeked |
| 1131 | const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked |
| 1132 | const cleartext = cleartext_stack_buffer[0..ciphertext.len]; |
| 1133 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch |
| 1134 | return failRead(c, error.TlsBadRecordMac); |
| 1135 | break :cleartext .{ cleartext, ct }; |
| 1383 | 1136 | }, |
| 1384 | | .application_data => { |
| 1385 | | // Determine whether the output buffer or a stack |
| 1386 | | // buffer was used for storing the cleartext. |
| 1387 | | if (cleartext.ptr == &cleartext_stack_buffer) { |
| 1388 | | // Stack buffer was used, so we must copy to the output buffer. |
| 1389 | | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1390 | | // We have already run out of room in iovecs. Continue |
| 1391 | | // appending to `partially_read_buffer`. |
| 1392 | | @memcpy( |
| 1393 | | c.partially_read_buffer[c.partial_ciphertext_idx..][0..cleartext.len], |
| 1394 | | cleartext, |
| 1395 | | ); |
| 1396 | | c.partial_ciphertext_idx = @intCast(c.partial_ciphertext_idx + cleartext.len); |
| 1397 | | } else { |
| 1398 | | const amt = vp.put(cleartext); |
| 1399 | | if (amt < cleartext.len) { |
| 1400 | | const rest = cleartext[amt..]; |
| 1401 | | c.partial_cleartext_idx = 0; |
| 1402 | | c.partial_ciphertext_idx = @intCast(rest.len); |
| 1403 | | @memcpy(c.partially_read_buffer[0..rest.len], rest); |
| 1137 | else => unreachable, |
| 1138 | }, |
| 1139 | }; |
| 1140 | c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow); |
| 1141 | switch (inner_ct) { |
| 1142 | .alert => { |
| 1143 | if (cleartext.len != 2) return failRead(c, error.TlsDecodeError); |
| 1144 | const alert: tls.Alert = .{ |
| 1145 | .level = @enumFromInt(cleartext[0]), |
| 1146 | .description = @enumFromInt(cleartext[1]), |
| 1147 | }; |
| 1148 | switch (alert.description) { |
| 1149 | .close_notify => { |
| 1150 | c.received_close_notify = true; |
| 1151 | return 0; |
| 1152 | }, |
| 1153 | .user_canceled => { |
| 1154 | // TODO: handle server-side closures |
| 1155 | return failRead(c, error.TlsUnexpectedMessage); |
| 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 | | } |
| 1406 | | } else { |
| 1407 | | // Output buffer was used directly which means no |
| 1408 | | // memory copying needs to occur, and we can move |
| 1409 | | // on to the next ciphertext record. |
| 1410 | | vp.next(cleartext.len); |
| 1194 | c.read_seq = 0; |
| 1195 | |
| 1196 | switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) { |
| 1197 | .update_requested => { |
| 1198 | switch (c.application_cipher) { |
| 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 | | }, |
| 1413 | | else => return failRead(c, error.TlsUnexpectedMessage), |
| 1414 | | } |
| 1415 | | in = end; |
| 1222 | ct_i = next_handshake_i; |
| 1223 | if (ct_i >= cleartext.len) break; |
| 1224 | } |
| 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 { |
| 1420 | | _ = context; |
| 1421 | | _ = limit; |
| 1422 | | @panic("TODO"); |
| 1240 | fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize { |
| 1241 | var bw: std.io.BufferedWriter = undefined; |
| 1242 | bw.initFixed(data[0]); |
| 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 | 1258 | fn failRead(c: *Client, err: ReadError) error{ReadFailed} { |
| ... | ... | @@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} { |
| 1427 | 1260 | return error.ReadFailed; |
| 1428 | 1261 | } |
| 1429 | 1262 | |
| 1430 | | fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void { |
| 1431 | | const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false; |
| 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}" ++ |
| 1263 | fn logSecrets(bw: *std.io.BufferedWriter, context: anytype, secrets: anytype) void { |
| 1264 | inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++ |
| 1436 | 1265 | (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++ |
| 1437 | 1266 | (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{ |
| 1438 | 1267 | context.client_random, |
| ... | ... | @@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi |
| 1440 | 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 | 1272 | inline fn big(x: anytype) @TypeOf(x) { |
| 1497 | 1273 | return switch (native_endian) { |
| 1498 | 1274 | .big => x, |
| ... | ... | @@ -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 | 1532 | /// The priority order here is chosen based on what crypto algorithms Zig has |
| 1832 | 1533 | /// available in the standard library as well as what is faster. Following are |
| 1833 | 1534 | /// a few data points on the relative performance of these algorithms. |