| ... | @@ -37,8 +37,54 @@ application_cipher: tls.ApplicationCipher, | ... | @@ -37,8 +37,54 @@ application_cipher: tls.ApplicationCipher, |
| 37 | /// `partial_ciphertext_end` describe the span of the segments. | 37 | /// `partial_ciphertext_end` describe the span of the segments. |
| 38 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, | 38 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, |
| 39 | | 39 | |
| | 40 | /// This is an example of the type that is needed by the read and write |
| | 41 | /// functions. It can have any fields but it must at least have these |
| | 42 | /// functions. |
| | 43 | /// |
| | 44 | /// Note that `std.net.Stream` conforms to this interface. |
| | 45 | /// |
| | 46 | /// This declaration serves as documentation only. |
| | 47 | pub const StreamInterface = struct { |
| | 48 | /// Can be any error set. |
| | 49 | pub const ReadError = error{}; |
| | 50 | |
| | 51 | /// Returns the number of bytes read. The number read may be less than the |
| | 52 | /// buffer space provided. End-of-stream is indicated by a return value of 0. |
| | 53 | /// |
| | 54 | /// The `iovecs` parameter is mutable because so that function may to |
| | 55 | /// mutate the fields in order to handle partial reads from the underlying |
| | 56 | /// stream layer. |
| | 57 | pub fn readv(this: @This(), iovecs: []std.os.iovec) ReadError!usize { |
| | 58 | _ = .{ this, iovecs }; |
| | 59 | @panic("unimplemented"); |
| | 60 | } |
| | 61 | |
| | 62 | /// Can be any error set. |
| | 63 | pub const WriteError = error{}; |
| | 64 | |
| | 65 | /// Returns the number of bytes read, which may be less than the buffer |
| | 66 | /// space provided. A short read does not indicate end-of-stream. |
| | 67 | pub fn writev(this: @This(), iovecs: []const std.os.iovec_const) WriteError!usize { |
| | 68 | _ = .{ this, iovecs }; |
| | 69 | @panic("unimplemented"); |
| | 70 | } |
| | 71 | |
| | 72 | /// Returns the number of bytes read, which may be less than the buffer |
| | 73 | /// space provided, indicating end-of-stream. |
| | 74 | /// The `iovecs` parameter is mutable in case this function needs to mutate |
| | 75 | /// the fields in order to handle partial writes from the underlying layer. |
| | 76 | pub fn writevAll(this: @This(), iovecs: []std.os.iovec_const) WriteError!usize { |
| | 77 | // This can be implemented in terms of writev, or specialized if desired. |
| | 78 | _ = .{ this, iovecs }; |
| | 79 | @panic("unimplemented"); |
| | 80 | } |
| | 81 | }; |
| | 82 | |
| | 83 | /// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which |
| | 84 | /// must conform to `StreamInterface`. |
| | 85 | /// |
| 40 | /// `host` is only borrowed during this function call. | 86 | /// `host` is only borrowed during this function call. |
| 41 | pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) !Client { | 87 | pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) !Client { |
| 42 | const host_len = @intCast(u16, host.len); | 88 | const host_len = @intCast(u16, host.len); |
| 43 | | 89 | |
| 44 | var random_buffer: [128]u8 = undefined; | 90 | var random_buffer: [128]u8 = undefined; |
| ... | @@ -579,31 +625,115 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) | ... | @@ -579,31 +625,115 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 579 | } | 625 | } |
| 580 | } | 626 | } |
| 581 | | 627 | |
| 582 | pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { | 628 | /// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. |
| | 629 | /// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`. |
| | 630 | pub fn write(c: *Client, stream: anytype, bytes: []const u8) !usize { |
| | 631 | return writeEnd(c, stream, bytes, false); |
| | 632 | } |
| | 633 | |
| | 634 | /// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. |
| | 635 | pub fn writeAll(c: *Client, stream: anytype, bytes: []const u8) !void { |
| | 636 | var index: usize = 0; |
| | 637 | while (index < bytes.len) { |
| | 638 | index += try c.write(stream, bytes[index..]); |
| | 639 | } |
| | 640 | } |
| | 641 | |
| | 642 | /// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. |
| | 643 | /// If `end` is true, then this function additionally sends a `close_notify` alert, |
| | 644 | /// which is necessary for the server to distinguish between a properly finished |
| | 645 | /// TLS session, or a truncation attack. |
| | 646 | pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !void { |
| | 647 | var index: usize = 0; |
| | 648 | while (index < bytes.len) { |
| | 649 | index += try c.writeEnd(stream, bytes[index..], end); |
| | 650 | } |
| | 651 | } |
| | 652 | |
| | 653 | /// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`. |
| | 654 | /// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`. |
| | 655 | /// If `end` is true, then this function additionally sends a `close_notify` alert, |
| | 656 | /// which is necessary for the server to distinguish between a properly finished |
| | 657 | /// TLS session, or a truncation attack. |
| | 658 | pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize { |
| 583 | var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; | 659 | var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; |
| | 660 | var iovecs_buf: [6]std.os.iovec_const = undefined; |
| | 661 | var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data); |
| | 662 | if (end) { |
| | 663 | prepared.iovec_end += prepareCiphertextRecord( |
| | 664 | c, |
| | 665 | iovecs_buf[prepared.iovec_end..], |
| | 666 | ciphertext_buf[prepared.ciphertext_end..], |
| | 667 | &tls.close_notify_alert, |
| | 668 | .alert, |
| | 669 | ).iovec_end; |
| | 670 | } |
| | 671 | |
| | 672 | const iovec_end = prepared.iovec_end; |
| | 673 | const overhead_len = prepared.overhead_len; |
| | 674 | |
| | 675 | // Ideally we would call writev exactly once here, however, we must ensure |
| | 676 | // that we don't return with a record partially written. |
| | 677 | var i: usize = 0; |
| | 678 | var total_amt: usize = 0; |
| | 679 | while (true) { |
| | 680 | var amt = try stream.writev(iovecs_buf[i..iovec_end]); |
| | 681 | while (amt >= iovecs_buf[i].iov_len) { |
| | 682 | const encrypted_amt = iovecs_buf[i].iov_len; |
| | 683 | total_amt += encrypted_amt - overhead_len; |
| | 684 | amt -= encrypted_amt; |
| | 685 | i += 1; |
| | 686 | // Rely on the property that iovecs delineate records, meaning that |
| | 687 | // if amt equals zero here, we have fortunately found ourselves |
| | 688 | // with a short read that aligns at the record boundary. |
| | 689 | if (i >= iovec_end) return total_amt; |
| | 690 | // We also cannot return on a vector boundary if the final close_notify is |
| | 691 | // not sent; otherwise the caller would not know to retry the call. |
| | 692 | if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt; |
| | 693 | } |
| | 694 | iovecs_buf[i].iov_base += amt; |
| | 695 | iovecs_buf[i].iov_len -= amt; |
| | 696 | } |
| | 697 | } |
| | 698 | |
| | 699 | fn prepareCiphertextRecord( |
| | 700 | c: *Client, |
| | 701 | iovecs: []std.os.iovec_const, |
| | 702 | ciphertext_buf: []u8, |
| | 703 | bytes: []const u8, |
| | 704 | inner_content_type: tls.ContentType, |
| | 705 | ) struct { |
| | 706 | iovec_end: usize, |
| | 707 | ciphertext_end: usize, |
| | 708 | /// How many bytes are taken up by overhead per record. |
| | 709 | overhead_len: usize, |
| | 710 | } { |
| 584 | // Due to the trailing inner content type byte in the ciphertext, we need | 711 | // Due to the trailing inner content type byte in the ciphertext, we need |
| 585 | // an additional buffer for storing the cleartext into before encrypting. | 712 | // an additional buffer for storing the cleartext into before encrypting. |
| 586 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; | 713 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; |
| 587 | var iovecs_buf: [5]std.os.iovec_const = undefined; | | |
| 588 | var ciphertext_end: usize = 0; | 714 | var ciphertext_end: usize = 0; |
| 589 | var iovec_end: usize = 0; | 715 | var iovec_end: usize = 0; |
| 590 | var bytes_i: usize = 0; | 716 | var bytes_i: usize = 0; |
| 591 | // How many bytes are taken up by overhead per record. | 717 | switch (c.application_cipher) { |
| 592 | const overhead_len: usize = switch (c.application_cipher) { | 718 | inline else => |*p| { |
| 593 | inline else => |*p| l: { | | |
| 594 | const P = @TypeOf(p.*); | 719 | const P = @TypeOf(p.*); |
| 595 | const V = @Vector(P.AEAD.nonce_length, u8); | 720 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 596 | const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1; | 721 | const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1; |
| | 722 | const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len; |
| 597 | while (true) { | 723 | while (true) { |
| 598 | const encrypted_content_len = @intCast(u16, @min( | 724 | const encrypted_content_len = @intCast(u16, @min( |
| 599 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), | 725 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), |
| 600 | ciphertext_buf.len - | 726 | ciphertext_buf.len - close_notify_alert_reserved - |
| 601 | tls.record_header_len - P.AEAD.tag_length - ciphertext_end - 1, | 727 | overhead_len - ciphertext_end, |
| 602 | )); | 728 | )); |
| 603 | if (encrypted_content_len == 0) break :l overhead_len; | 729 | if (encrypted_content_len == 0) return .{ |
| | 730 | .iovec_end = iovec_end, |
| | 731 | .ciphertext_end = ciphertext_end, |
| | 732 | .overhead_len = overhead_len, |
| | 733 | }; |
| 604 | | 734 | |
| 605 | mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]); | 735 | mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]); |
| 606 | cleartext_buf[encrypted_content_len] = @enumToInt(tls.ContentType.application_data); | 736 | cleartext_buf[encrypted_content_len] = @enumToInt(inner_content_type); |
| 607 | bytes_i += encrypted_content_len; | 737 | bytes_i += encrypted_content_len; |
| 608 | const ciphertext_len = encrypted_content_len + 1; | 738 | const ciphertext_len = encrypted_content_len + 1; |
| 609 | const cleartext = cleartext_buf[0..ciphertext_len]; | 739 | const cleartext = cleartext_buf[0..ciphertext_len]; |
| ... | @@ -626,40 +756,13 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { | ... | @@ -626,40 +756,13 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { |
| 626 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); | 756 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); |
| 627 | | 757 | |
| 628 | const record = ciphertext_buf[record_start..ciphertext_end]; | 758 | const record = ciphertext_buf[record_start..ciphertext_end]; |
| 629 | iovecs_buf[iovec_end] = .{ | 759 | iovecs[iovec_end] = .{ |
| 630 | .iov_base = record.ptr, | 760 | .iov_base = record.ptr, |
| 631 | .iov_len = record.len, | 761 | .iov_len = record.len, |
| 632 | }; | 762 | }; |
| 633 | iovec_end += 1; | 763 | iovec_end += 1; |
| 634 | } | 764 | } |
| 635 | }, | 765 | }, |
| 636 | }; | | |
| 637 | | | |
| 638 | // Ideally we would call writev exactly once here, however, we must ensure | | |
| 639 | // that we don't return with a record partially written. | | |
| 640 | var i: usize = 0; | | |
| 641 | var total_amt: usize = 0; | | |
| 642 | while (true) { | | |
| 643 | var amt = try stream.writev(iovecs_buf[i..iovec_end]); | | |
| 644 | while (amt >= iovecs_buf[i].iov_len) { | | |
| 645 | const encrypted_amt = iovecs_buf[i].iov_len; | | |
| 646 | total_amt += encrypted_amt - overhead_len; | | |
| 647 | amt -= encrypted_amt; | | |
| 648 | i += 1; | | |
| 649 | // Rely on the property that iovecs delineate records, meaning that | | |
| 650 | // if amt equals zero here, we have fortunately found ourselves | | |
| 651 | // with a short read that aligns at the record boundary. | | |
| 652 | if (i >= iovec_end or amt == 0) return total_amt; | | |
| 653 | } | | |
| 654 | iovecs_buf[i].iov_base += amt; | | |
| 655 | iovecs_buf[i].iov_len -= amt; | | |
| 656 | } | | |
| 657 | } | | |
| 658 | | | |
| 659 | pub fn writeAll(c: *Client, stream: net.Stream, bytes: []const u8) !void { | | |
| 660 | var index: usize = 0; | | |
| 661 | while (index < bytes.len) { | | |
| 662 | index += try c.write(stream, bytes[index..]); | | |
| 663 | } | 766 | } |
| 664 | } | 767 | } |
| 665 | | 768 | |
| ... | @@ -669,6 +772,7 @@ pub fn eof(c: Client) bool { | ... | @@ -669,6 +772,7 @@ pub fn eof(c: Client) bool { |
| 669 | c.partial_ciphertext_idx >= c.partial_ciphertext_end; | 772 | c.partial_ciphertext_idx >= c.partial_ciphertext_end; |
| 670 | } | 773 | } |
| 671 | | 774 | |
| | 775 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 672 | /// Returns the number of bytes read, calling the underlying read function the | 776 | /// Returns the number of bytes read, calling the underlying read function the |
| 673 | /// minimal number of times until the buffer has at least `len` bytes filled. | 777 | /// minimal number of times until the buffer has at least `len` bytes filled. |
| 674 | /// If the number read is less than `len` it means the stream reached the end. | 778 | /// If the number read is less than `len` it means the stream reached the end. |
| ... | @@ -678,10 +782,12 @@ pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize | ... | @@ -678,10 +782,12 @@ pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize |
| 678 | return readvAtLeast(c, stream, &iovecs, len); | 782 | return readvAtLeast(c, stream, &iovecs, len); |
| 679 | } | 783 | } |
| 680 | | 784 | |
| | 785 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 681 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { | 786 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 682 | return readAtLeast(c, stream, buffer, 1); | 787 | return readAtLeast(c, stream, buffer, 1); |
| 683 | } | 788 | } |
| 684 | | 789 | |
| | 790 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 685 | /// Returns the number of bytes read. If the number read is smaller than | 791 | /// Returns the number of bytes read. If the number read is smaller than |
| 686 | /// `buffer.len`, it means the stream reached the end. Reaching the end of the | 792 | /// `buffer.len`, it means the stream reached the end. Reaching the end of the |
| 687 | /// stream is not an error condition. | 793 | /// stream is not an error condition. |
| ... | @@ -689,6 +795,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { | ... | @@ -689,6 +795,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 689 | return readAtLeast(c, stream, buffer, buffer.len); | 795 | return readAtLeast(c, stream, buffer, buffer.len); |
| 690 | } | 796 | } |
| 691 | | 797 | |
| | 798 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 692 | /// Returns the number of bytes read. If the number read is less than the space | 799 | /// Returns the number of bytes read. If the number read is less than the space |
| 693 | /// provided it means the stream reached the end. Reaching the end of the | 800 | /// provided it means the stream reached the end. Reaching the end of the |
| 694 | /// stream is not an error condition. | 801 | /// stream is not an error condition. |
| ... | @@ -698,6 +805,7 @@ pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { | ... | @@ -698,6 +805,7 @@ pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { |
| 698 | return readvAtLeast(c, stream, iovecs); | 805 | return readvAtLeast(c, stream, iovecs); |
| 699 | } | 806 | } |
| 700 | | 807 | |
| | 808 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 701 | /// Returns the number of bytes read, calling the underlying read function the | 809 | /// Returns the number of bytes read, calling the underlying read function the |
| 702 | /// minimal number of times until the iovecs have at least `len` bytes filled. | 810 | /// minimal number of times until the iovecs have at least `len` bytes filled. |
| 703 | /// If the number read is less than `len` it means the stream reached the end. | 811 | /// If the number read is less than `len` it means the stream reached the end. |
| ... | @@ -722,6 +830,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: us | ... | @@ -722,6 +830,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: us |
| 722 | } | 830 | } |
| 723 | } | 831 | } |
| 724 | | 832 | |
| | 833 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 725 | /// Returns number of bytes that have been read, populated inside `iovecs`. A | 834 | /// Returns number of bytes that have been read, populated inside `iovecs`. A |
| 726 | /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` | 835 | /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` |
| 727 | /// for the end of stream. The `eof()` may be true after any call to | 836 | /// for the end of stream. The `eof()` may be true after any call to |
| ... | @@ -729,7 +838,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: us | ... | @@ -729,7 +838,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: us |
| 729 | /// function asserts that `eof()` is `false`. | 838 | /// function asserts that `eof()` is `false`. |
| 730 | /// See `readv` for a higher level function that has the same, familiar API as | 839 | /// See `readv` for a higher level function that has the same, familiar API as |
| 731 | /// other read functions, such as `std.fs.File.read`. | 840 | /// other read functions, such as `std.fs.File.read`. |
| 732 | pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iovec) !usize { | 841 | pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec) !usize { |
| 733 | var vp: VecPut = .{ .iovecs = iovecs }; | 842 | var vp: VecPut = .{ .iovecs = iovecs }; |
| 734 | | 843 | |
| 735 | // Give away the buffered cleartext we have, if any. | 844 | // Give away the buffered cleartext we have, if any. |
| ... | @@ -905,7 +1014,8 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove | ... | @@ -905,7 +1014,8 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 905 | break :c cleartext; | 1014 | break :c cleartext; |
| 906 | }, | 1015 | }, |
| 907 | }; | 1016 | }; |
| 908 | c.read_seq += 1; | 1017 | |
| | 1018 | c.read_seq = try std.math.add(u64, c.read_seq, 1); |
| 909 | | 1019 | |
| 910 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); | 1020 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); |
| 911 | switch (inner_ct) { | 1021 | switch (inner_ct) { |
| ... | @@ -1196,3 +1306,7 @@ const cipher_suites = enum_array(tls.CipherSuite, &.{ | ... | @@ -1196,3 +1306,7 @@ const cipher_suites = enum_array(tls.CipherSuite, &.{ |
| 1196 | .AES_256_GCM_SHA384, | 1306 | .AES_256_GCM_SHA384, |
| 1197 | .CHACHA20_POLY1305_SHA256, | 1307 | .CHACHA20_POLY1305_SHA256, |
| 1198 | }); | 1308 | }); |
| | 1309 | |
| | 1310 | test { |
| | 1311 | _ = StreamInterface; |
| | 1312 | } |