authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-05-28 09:37:56+02:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-06-01 13:43:55-05:00
log0e5e6cb10c12a5ae9fd83f85a82eafbe5eac1106
treeb9c72f096729ef7a51a76a8ec9e7fc490a809016
parent8136123aa7cdd6d53c682572405eb6c1d5e0f0a0
signaturelock-open Commit is signed but in an unrecognized format.

std.http: add TlsAlert descriptions so that they can at least be viewed in err return traces


5 files changed, 141 insertions(+), 68 deletions(-)

lib/std/crypto/tls.zig+62
......@@ -138,6 +138,35 @@ pub const AlertLevel = enum(u8) {
138138};
139139
140140pub const AlertDescription = enum(u8) {
141 pub const Error = error{
142 TlsAlertUnexpectedMessage,
143 TlsAlertBadRecordMac,
144 TlsAlertRecordOverflow,
145 TlsAlertHandshakeFailure,
146 TlsAlertBadCertificate,
147 TlsAlertUnsupportedCertificate,
148 TlsAlertCertificateRevoked,
149 TlsAlertCertificateExpired,
150 TlsAlertCertificateUnknown,
151 TlsAlertIllegalParameter,
152 TlsAlertUnknownCa,
153 TlsAlertAccessDenied,
154 TlsAlertDecodeError,
155 TlsAlertDecryptError,
156 TlsAlertProtocolVersion,
157 TlsAlertInsufficientSecurity,
158 TlsAlertInternalError,
159 TlsAlertInappropriateFallback,
160 TlsAlertMissingExtension,
161 TlsAlertUnsupportedExtension,
162 TlsAlertUnrecognizedName,
163 TlsAlertBadCertificateStatusResponse,
164 TlsAlertUnknownPskIdentity,
165 TlsAlertCertificateRequired,
166 TlsAlertNoApplicationProtocol,
167 TlsAlertUnknown,
168 };
169
141170 close_notify = 0,
142171 unexpected_message = 10,
143172 bad_record_mac = 20,
......@@ -166,6 +195,39 @@ pub const AlertDescription = enum(u8) {
166195 certificate_required = 116,
167196 no_application_protocol = 120,
168197 _,
198
199 pub fn toError(alert: AlertDescription) Error!void {
200 return switch (alert) {
201 .close_notify => {}, // not an error
202 .unexpected_message => error.TlsAlertUnexpectedMessage,
203 .bad_record_mac => error.TlsAlertBadRecordMac,
204 .record_overflow => error.TlsAlertRecordOverflow,
205 .handshake_failure => error.TlsAlertHandshakeFailure,
206 .bad_certificate => error.TlsAlertBadCertificate,
207 .unsupported_certificate => error.TlsAlertUnsupportedCertificate,
208 .certificate_revoked => error.TlsAlertCertificateRevoked,
209 .certificate_expired => error.TlsAlertCertificateExpired,
210 .certificate_unknown => error.TlsAlertCertificateUnknown,
211 .illegal_parameter => error.TlsAlertIllegalParameter,
212 .unknown_ca => error.TlsAlertUnknownCa,
213 .access_denied => error.TlsAlertAccessDenied,
214 .decode_error => error.TlsAlertDecodeError,
215 .decrypt_error => error.TlsAlertDecryptError,
216 .protocol_version => error.TlsAlertProtocolVersion,
217 .insufficient_security => error.TlsAlertInsufficientSecurity,
218 .internal_error => error.TlsAlertInternalError,
219 .inappropriate_fallback => error.TlsAlertInappropriateFallback,
220 .user_canceled => {}, // not an error
221 .missing_extension => error.TlsAlertMissingExtension,
222 .unsupported_extension => error.TlsAlertUnsupportedExtension,
223 .unrecognized_name => error.TlsAlertUnrecognizedName,
224 .bad_certificate_status_response => error.TlsAlertBadCertificateStatusResponse,
225 .unknown_psk_identity => error.TlsAlertUnknownPskIdentity,
226 .certificate_required => error.TlsAlertCertificateRequired,
227 .no_application_protocol => error.TlsAlertNoApplicationProtocol,
228 _ => error.TlsAlertUnknown,
229 };
230 }
169231};
170232
171233pub const SignatureScheme = enum(u16) {
lib/std/crypto/tls/Client.zig+14-7
......@@ -89,12 +89,11 @@ pub const StreamInterface = struct {
8989};
9090
9191pub fn InitError(comptime Stream: type) type {
92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error{
92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || tls.AlertDescription.Error || error{
9393 InsufficientEntropy,
9494 DiskQuota,
9595 LockViolation,
9696 NotOpenForWriting,
97 TlsAlert,
9897 TlsUnexpectedMessage,
9998 TlsIllegalParameter,
10099 TlsDecryptFailure,
......@@ -251,8 +250,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
251250 const level = ptd.decode(tls.AlertLevel);
252251 const desc = ptd.decode(tls.AlertDescription);
253252 _ = level;
254 _ = desc;
255 return error.TlsAlert;
253
254 // if this isn't a error alert, then it's a closure alert, which makes no sense in a handshake
255 try desc.toError();
256 // TODO: handle server-side closures
257 return error.TlsUnexpectedMessage;
256258 },
257259 .handshake => {
258260 try ptd.ensure(4);
......@@ -1071,8 +1073,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
10711073 const level = @intToEnum(tls.AlertLevel, frag[in]);
10721074 const desc = @intToEnum(tls.AlertDescription, frag[in + 1]);
10731075 _ = level;
1074 _ = desc;
1075 return error.TlsAlert;
1076
1077 try desc.toError();
1078 // TODO: handle server-side closures
1079 return error.TlsUnexpectedMessage;
10761080 },
10771081 .application_data => {
10781082 const cleartext = switch (c.application_cipher) {
......@@ -1112,7 +1116,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
11121116 return vp.total;
11131117 }
11141118 _ = level;
1115 return error.TlsAlert;
1119
1120 try desc.toError();
1121 // TODO: handle server-side closures
1122 return error.TlsUnexpectedMessage;
11161123 },
11171124 .handshake => {
11181125 var ct_i: usize = 0;
lib/std/http/Client.zig+14-10
......@@ -168,19 +168,23 @@ pub const Connection = struct {
168168 return switch (conn.protocol) {
169169 .plain => conn.stream.readAtLeast(buffer, len),
170170 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
171 } catch |err| switch (err) {
172 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
173 error.TlsAlert => return error.TlsAlert,
174 error.ConnectionTimedOut => return error.ConnectionTimedOut,
175 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
176 else => return error.UnexpectedReadFailure,
171 } catch |err| {
172 // TODO: https://github.com/ziglang/zig/issues/2473
173 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
174
175 switch (err) {
176 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
177 error.ConnectionTimedOut => return error.ConnectionTimedOut,
178 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
179 else => return error.UnexpectedReadFailure,
180 }
177181 };
178182 }
179183
180184 pub fn fill(conn: *Connection) ReadError!void {
181185 if (conn.read_end != conn.read_start) return;
182186
183 const nread = try conn.conn.read(conn.read_buf[0..]);
187 const nread = try conn.read(conn.read_buf[0..]);
184188 if (nread == 0) return error.EndOfStream;
185189 conn.read_start = 0;
186190 conn.read_end = @intCast(u16, nread);
......@@ -204,8 +208,8 @@ pub const Connection = struct {
204208
205209 if (available_read > available_buffer) { // partially read buffered data
206210 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..][0..available_buffer]);
207 out_index += available_buffer;
208 conn.read_start += available_buffer;
211 out_index += @intCast(u16, available_buffer);
212 conn.read_start += @intCast(u16, available_buffer);
209213
210214 break;
211215 } else if (available_read > 0) { // fully read buffered data
......@@ -759,7 +763,7 @@ pub const Request = struct {
759763 try req.connection.data.fill();
760764
761765 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.peek());
762 req.connection.data.clear(@intCast(u16, nchecked));
766 req.connection.data.drop(@intCast(u16, nchecked));
763767 }
764768
765769 if (has_trail) {
lib/std/http/Server.zig+3-3
......@@ -118,7 +118,7 @@ pub const BufferedConnection = struct {
118118 return bconn.read_buf[bconn.read_start..bconn.read_end];
119119 }
120120
121 pub fn clear(bconn: *BufferedConnection, num: u16) void {
121 pub fn drop(bconn: *BufferedConnection, num: u16) void {
122122 bconn.read_start += num;
123123 }
124124
......@@ -545,7 +545,7 @@ pub const Response = struct {
545545 try res.connection.fill();
546546
547547 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());
548 res.connection.clear(@intCast(u16, nchecked));
548 res.connection.drop(@intCast(u16, nchecked));
549549
550550 if (res.request.parser.state.isContent()) break;
551551 }
......@@ -612,7 +612,7 @@ pub const Response = struct {
612612 try res.connection.fill();
613613
614614 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());
615 res.connection.clear(@intCast(u16, nchecked));
615 res.connection.drop(@intCast(u16, nchecked));
616616 }
617617
618618 if (has_trail) {
lib/std/http/protocol.zig+48-48
......@@ -513,8 +513,8 @@ pub const HeadersParser = struct {
513513 ///
514514 /// If `skip` is true, the buffer will be unused and the body will be skipped.
515515 ///
516 /// See `std.http.Client.BufferedConnection for an example of `bconn`.
517 pub fn read(r: *HeadersParser, bconn: anytype, buffer: []u8, skip: bool) !usize {
516 /// See `std.http.Client.BufferedConnection for an example of `conn`.
517 pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
518518 assert(r.state.isContent());
519519 if (r.done) return 0;
520520
......@@ -526,10 +526,10 @@ pub const HeadersParser = struct {
526526 const data_avail = r.next_chunk_length;
527527
528528 if (skip) {
529 try bconn.fill();
529 try conn.fill();
530530
531 const nread = @min(bconn.peek().len, data_avail);
532 bconn.clear(@intCast(u16, nread));
531 const nread = @min(conn.peek().len, data_avail);
532 conn.drop(@intCast(u16, nread));
533533 r.next_chunk_length -= nread;
534534
535535 if (r.next_chunk_length == 0) r.done = true;
......@@ -539,7 +539,7 @@ pub const HeadersParser = struct {
539539 const out_avail = buffer.len;
540540
541541 const can_read = @intCast(usize, @min(data_avail, out_avail));
542 const nread = try bconn.read(buffer[0..can_read]);
542 const nread = try conn.read(buffer[0..can_read]);
543543 r.next_chunk_length -= nread;
544544
545545 if (r.next_chunk_length == 0) r.done = true;
......@@ -548,15 +548,15 @@ pub const HeadersParser = struct {
548548 }
549549 },
550550 .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
551 try bconn.fill();
551 try conn.fill();
552552
553 const i = r.findChunkedLen(bconn.peek());
554 bconn.clear(@intCast(u16, i));
553 const i = r.findChunkedLen(conn.peek());
554 conn.drop(@intCast(u16, i));
555555
556556 switch (r.state) {
557557 .invalid => return error.HttpChunkInvalid,
558558 .chunk_data => if (r.next_chunk_length == 0) {
559 if (std.mem.eql(u8, bconn.peek(), "\r\n")) {
559 if (std.mem.eql(u8, conn.peek(), "\r\n")) {
560560 r.state = .finished;
561561 } else {
562562 // The trailer section is formatted identically to the header section.
......@@ -576,14 +576,14 @@ pub const HeadersParser = struct {
576576 const out_avail = buffer.len - out_index;
577577
578578 if (skip) {
579 try bconn.fill();
579 try conn.fill();
580580
581 const nread = @min(bconn.peek().len, data_avail);
582 bconn.clear(@intCast(u16, nread));
581 const nread = @min(conn.peek().len, data_avail);
582 conn.drop(@intCast(u16, nread));
583583 r.next_chunk_length -= nread;
584584 } else {
585585 const can_read = @intCast(usize, @min(data_avail, out_avail));
586 const nread = try bconn.read(buffer[out_index..][0..can_read]);
586 const nread = try conn.read(buffer[out_index..][0..can_read]);
587587 r.next_chunk_length -= nread;
588588 out_index += nread;
589589 }
......@@ -628,74 +628,74 @@ const MockBufferedConnection = struct {
628628 start: u16 = 0,
629629 end: u16 = 0,
630630
631 pub fn fill(bconn: *MockBufferedConnection) ReadError!void {
632 if (bconn.end != bconn.start) return;
631 pub fn fill(conn: *MockBufferedConnection) ReadError!void {
632 if (conn.end != conn.start) return;
633633
634 const nread = try bconn.conn.read(bconn.buf[0..]);
634 const nread = try conn.conn.read(conn.buf[0..]);
635635 if (nread == 0) return error.EndOfStream;
636 bconn.start = 0;
637 bconn.end = @truncate(u16, nread);
636 conn.start = 0;
637 conn.end = @truncate(u16, nread);
638638 }
639639
640 pub fn peek(bconn: *MockBufferedConnection) []const u8 {
641 return bconn.buf[bconn.start..bconn.end];
640 pub fn peek(conn: *MockBufferedConnection) []const u8 {
641 return conn.buf[conn.start..conn.end];
642642 }
643643
644644 pub fn drop(conn: *MockBufferedConnection, num: u16) void {
645645 conn.start += num;
646646 }
647647
648 pub fn readAtLeast(bconn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize {
648 pub fn readAtLeast(conn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize {
649649 var out_index: u16 = 0;
650650 while (out_index < len) {
651 const available = bconn.end - bconn.start;
651 const available = conn.end - conn.start;
652652 const left = buffer.len - out_index;
653653
654654 if (available > 0) {
655655 const can_read = @truncate(u16, @min(available, left));
656656
657 @memcpy(buffer[out_index..][0..can_read], bconn.buf[bconn.start..][0..can_read]);
657 @memcpy(buffer[out_index..][0..can_read], conn.buf[conn.start..][0..can_read]);
658658 out_index += can_read;
659 bconn.start += can_read;
659 conn.start += can_read;
660660
661661 continue;
662662 }
663663
664 if (left > bconn.buf.len) {
664 if (left > conn.buf.len) {
665665 // skip the buffer if the output is large enough
666 return bconn.conn.read(buffer[out_index..]);
666 return conn.conn.read(buffer[out_index..]);
667667 }
668668
669 try bconn.fill();
669 try conn.fill();
670670 }
671671
672672 return out_index;
673673 }
674674
675 pub fn read(bconn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
676 return bconn.readAtLeast(buffer, 1);
675 pub fn read(conn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
676 return conn.readAtLeast(buffer, 1);
677677 }
678678
679679 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};
680680 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);
681681
682 pub fn reader(bconn: *MockBufferedConnection) Reader {
683 return Reader{ .context = bconn };
682 pub fn reader(conn: *MockBufferedConnection) Reader {
683 return Reader{ .context = conn };
684684 }
685685
686 pub fn writeAll(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
687 return bconn.conn.writeAll(buffer);
686 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
687 return conn.conn.writeAll(buffer);
688688 }
689689
690 pub fn write(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
691 return bconn.conn.write(buffer);
690 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
691 return conn.conn.write(buffer);
692692 }
693693
694694 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
695695 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);
696696
697 pub fn writer(bconn: *MockBufferedConnection) Writer {
698 return Writer{ .context = bconn };
697 pub fn writer(conn: *MockBufferedConnection) Writer {
698 return Writer{ .context = conn };
699699 }
700700};
701701
......@@ -753,12 +753,12 @@ test "HeadersParser.read length" {
753753 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
754754 var fbs = std.io.fixedBufferStream(data);
755755
756 var bconn = MockBufferedConnection{
756 var conn = MockBufferedConnection{
757757 .conn = fbs,
758758 };
759759
760760 while (true) { // read headers
761 try bconn.fill();
761 try conn.fill();
762762
763763 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
764764 conn.drop(@intCast(u16, nchecked));
......@@ -769,7 +769,7 @@ test "HeadersParser.read length" {
769769 var buf: [8]u8 = undefined;
770770
771771 r.next_chunk_length = 5;
772 const len = try r.read(&bconn, &buf, false);
772 const len = try r.read(&conn, &buf, false);
773773 try std.testing.expectEqual(@as(usize, 5), len);
774774 try std.testing.expectEqualStrings("Hello", buf[0..len]);
775775
......@@ -784,12 +784,12 @@ test "HeadersParser.read chunked" {
784784 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";
785785 var fbs = std.io.fixedBufferStream(data);
786786
787 var bconn = MockBufferedConnection{
787 var conn = MockBufferedConnection{
788788 .conn = fbs,
789789 };
790790
791791 while (true) { // read headers
792 try bconn.fill();
792 try conn.fill();
793793
794794 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
795795 conn.drop(@intCast(u16, nchecked));
......@@ -799,7 +799,7 @@ test "HeadersParser.read chunked" {
799799 var buf: [8]u8 = undefined;
800800
801801 r.state = .chunk_head_size;
802 const len = try r.read(&bconn, &buf, false);
802 const len = try r.read(&conn, &buf, false);
803803 try std.testing.expectEqual(@as(usize, 5), len);
804804 try std.testing.expectEqualStrings("Hello", buf[0..len]);
805805
......@@ -814,12 +814,12 @@ test "HeadersParser.read chunked trailer" {
814814 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";
815815 var fbs = std.io.fixedBufferStream(data);
816816
817 var bconn = MockBufferedConnection{
817 var conn = MockBufferedConnection{
818818 .conn = fbs,
819819 };
820820
821821 while (true) { // read headers
822 try bconn.fill();
822 try conn.fill();
823823
824824 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
825825 conn.drop(@intCast(u16, nchecked));
......@@ -829,12 +829,12 @@ test "HeadersParser.read chunked trailer" {
829829 var buf: [8]u8 = undefined;
830830
831831 r.state = .chunk_head_size;
832 const len = try r.read(&bconn, &buf, false);
832 const len = try r.read(&conn, &buf, false);
833833 try std.testing.expectEqual(@as(usize, 5), len);
834834 try std.testing.expectEqualStrings("Hello", buf[0..len]);
835835
836836 while (true) { // read headers
837 try bconn.fill();
837 try conn.fill();
838838
839839 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
840840 conn.drop(@intCast(u16, nchecked));