| ... | ... | @@ -37,8 +37,54 @@ application_cipher: tls.ApplicationCipher, |
| 37 | 37 | /// `partial_ciphertext_end` describe the span of the segments. |
| 38 | 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 | 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 | 88 | const host_len = @intCast(u16, host.len); |
| 43 | 89 | |
| 44 | 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 | 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 | 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 | 711 | // Due to the trailing inner content type byte in the ciphertext, we need |
| 585 | 712 | // an additional buffer for storing the cleartext into before encrypting. |
| 586 | 713 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; |
| 587 | | var iovecs_buf: [5]std.os.iovec_const = undefined; |
| 588 | 714 | var ciphertext_end: usize = 0; |
| 589 | 715 | var iovec_end: usize = 0; |
| 590 | 716 | var bytes_i: usize = 0; |
| 591 | | // How many bytes are taken up by overhead per record. |
| 592 | | const overhead_len: usize = switch (c.application_cipher) { |
| 593 | | inline else => |*p| l: { |
| 717 | switch (c.application_cipher) { |
| 718 | inline else => |*p| { |
| 594 | 719 | const P = @TypeOf(p.*); |
| 595 | 720 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 596 | 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 | 723 | while (true) { |
| 598 | 724 | const encrypted_content_len = @intCast(u16, @min( |
| 599 | 725 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), |
| 600 | | ciphertext_buf.len - |
| 601 | | tls.record_header_len - P.AEAD.tag_length - ciphertext_end - 1, |
| 726 | ciphertext_buf.len - close_notify_alert_reserved - |
| 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 | 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 | 737 | bytes_i += encrypted_content_len; |
| 608 | 738 | const ciphertext_len = encrypted_content_len + 1; |
| 609 | 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 | 756 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); |
| 627 | 757 | |
| 628 | 758 | const record = ciphertext_buf[record_start..ciphertext_end]; |
| 629 | | iovecs_buf[iovec_end] = .{ |
| 759 | iovecs[iovec_end] = .{ |
| 630 | 760 | .iov_base = record.ptr, |
| 631 | 761 | .iov_len = record.len, |
| 632 | 762 | }; |
| 633 | 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 | 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 | 776 | /// Returns the number of bytes read, calling the underlying read function the |
| 673 | 777 | /// minimal number of times until the buffer has at least `len` bytes filled. |
| 674 | 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 | 782 | return readvAtLeast(c, stream, &iovecs, len); |
| 679 | 783 | } |
| 680 | 784 | |
| 785 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 681 | 786 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 682 | 787 | return readAtLeast(c, stream, buffer, 1); |
| 683 | 788 | } |
| 684 | 789 | |
| 790 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 685 | 791 | /// Returns the number of bytes read. If the number read is smaller than |
| 686 | 792 | /// `buffer.len`, it means the stream reached the end. Reaching the end of the |
| 687 | 793 | /// stream is not an error condition. |
| ... | ... | @@ -689,6 +795,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 689 | 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 | 799 | /// Returns the number of bytes read. If the number read is less than the space |
| 693 | 800 | /// provided it means the stream reached the end. Reaching the end of the |
| 694 | 801 | /// stream is not an error condition. |
| ... | ... | @@ -698,6 +805,7 @@ pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { |
| 698 | 805 | return readvAtLeast(c, stream, iovecs); |
| 699 | 806 | } |
| 700 | 807 | |
| 808 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 701 | 809 | /// Returns the number of bytes read, calling the underlying read function the |
| 702 | 810 | /// minimal number of times until the iovecs have at least `len` bytes filled. |
| 703 | 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 | 830 | } |
| 723 | 831 | } |
| 724 | 832 | |
| 833 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. |
| 725 | 834 | /// Returns number of bytes that have been read, populated inside `iovecs`. A |
| 726 | 835 | /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` |
| 727 | 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 | 838 | /// function asserts that `eof()` is `false`. |
| 730 | 839 | /// See `readv` for a higher level function that has the same, familiar API as |
| 731 | 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 | 842 | var vp: VecPut = .{ .iovecs = iovecs }; |
| 734 | 843 | |
| 735 | 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 | 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 | 1020 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); |
| 911 | 1021 | switch (inner_ct) { |
| ... | ... | @@ -1196,3 +1306,7 @@ const cipher_suites = enum_array(tls.CipherSuite, &.{ |
| 1196 | 1306 | .AES_256_GCM_SHA384, |
| 1197 | 1307 | .CHACHA20_POLY1305_SHA256, |
| 1198 | 1308 | }); |
| 1309 | |
| 1310 | test { |
| 1311 | _ = StreamInterface; |
| 1312 | } |