diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 7b4a116d35d1..2f7f729302e5 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -176,7 +176,7 @@ const std = @import("std.zig"); pub const errors = @import("crypto/errors.zig"); -pub const Tls = @import("crypto/Tls.zig"); +pub const tls = @import("crypto/tls.zig"); test { _ = aead.aegis.Aegis128L; diff --git a/lib/std/crypto/tls.zig b/lib/std/crypto/tls.zig new file mode 100644 index 000000000000..c93e918bf168 --- /dev/null +++ b/lib/std/crypto/tls.zig @@ -0,0 +1,325 @@ +//! Plaintext: +//! * type: ContentType +//! * legacy_record_version: u16 = 0x0303, +//! * length: u16, +//! - The length (in bytes) of the following TLSPlaintext.fragment. The +//! length MUST NOT exceed 2^14 bytes. +//! * fragment: opaque +//! - the data being transmitted +//! +//! Ciphertext +//! * ContentType opaque_type = application_data; /* 23 */ +//! * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ +//! * uint16 length; +//! * opaque encrypted_record[TLSCiphertext.length]; +//! +//! Handshake: +//! * type: HandshakeType +//! * length: u24 +//! * data: opaque +//! +//! ServerHello: +//! * ProtocolVersion legacy_version = 0x0303; +//! * Random random; +//! * opaque legacy_session_id_echo<0..32>; +//! * CipherSuite cipher_suite; +//! * uint8 legacy_compression_method = 0; +//! * Extension extensions<6..2^16-1>; +//! +//! Extension: +//! * ExtensionType extension_type; +//! * opaque extension_data<0..2^16-1>; + +const std = @import("../std.zig"); +const Tls = @This(); +const net = std.net; +const mem = std.mem; +const crypto = std.crypto; +const assert = std.debug.assert; + +pub const Client = @import("tls/Client.zig"); + +pub const ciphertext_record_header_len = 5; +pub const max_ciphertext_len = (1 << 14) + 256; +pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len; +pub const hello_retry_request_sequence = [32]u8{ + 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, + 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C, +}; + +pub const ProtocolVersion = enum(u16) { + tls_1_2 = 0x0303, + tls_1_3 = 0x0304, + _, +}; + +pub const ContentType = enum(u8) { + invalid = 0, + change_cipher_spec = 20, + alert = 21, + handshake = 22, + application_data = 23, + _, +}; + +pub const HandshakeType = enum(u8) { + client_hello = 1, + server_hello = 2, + new_session_ticket = 4, + end_of_early_data = 5, + encrypted_extensions = 8, + certificate = 11, + certificate_request = 13, + certificate_verify = 15, + finished = 20, + key_update = 24, + message_hash = 254, +}; + +pub const ExtensionType = enum(u16) { + /// RFC 6066 + server_name = 0, + /// RFC 6066 + max_fragment_length = 1, + /// RFC 6066 + status_request = 5, + /// RFC 8422, 7919 + supported_groups = 10, + /// RFC 8446 + signature_algorithms = 13, + /// RFC 5764 + use_srtp = 14, + /// RFC 6520 + heartbeat = 15, + /// RFC 7301 + application_layer_protocol_negotiation = 16, + /// RFC 6962 + signed_certificate_timestamp = 18, + /// RFC 7250 + client_certificate_type = 19, + /// RFC 7250 + server_certificate_type = 20, + /// RFC 7685 + padding = 21, + /// RFC 8446 + pre_shared_key = 41, + /// RFC 8446 + early_data = 42, + /// RFC 8446 + supported_versions = 43, + /// RFC 8446 + cookie = 44, + /// RFC 8446 + psk_key_exchange_modes = 45, + /// RFC 8446 + certificate_authorities = 47, + /// RFC 8446 + oid_filters = 48, + /// RFC 8446 + post_handshake_auth = 49, + /// RFC 8446 + signature_algorithms_cert = 50, + /// RFC 8446 + key_share = 51, +}; + +pub const AlertLevel = enum(u8) { + warning = 1, + fatal = 2, + _, +}; + +pub const AlertDescription = enum(u8) { + close_notify = 0, + unexpected_message = 10, + bad_record_mac = 20, + record_overflow = 22, + handshake_failure = 40, + bad_certificate = 42, + unsupported_certificate = 43, + certificate_revoked = 44, + certificate_expired = 45, + certificate_unknown = 46, + illegal_parameter = 47, + unknown_ca = 48, + access_denied = 49, + decode_error = 50, + decrypt_error = 51, + protocol_version = 70, + insufficient_security = 71, + internal_error = 80, + inappropriate_fallback = 86, + user_canceled = 90, + missing_extension = 109, + unsupported_extension = 110, + unrecognized_name = 112, + bad_certificate_status_response = 113, + unknown_psk_identity = 115, + certificate_required = 116, + no_application_protocol = 120, + _, +}; + +pub const SignatureScheme = enum(u16) { + // RSASSA-PKCS1-v1_5 algorithms + rsa_pkcs1_sha256 = 0x0401, + rsa_pkcs1_sha384 = 0x0501, + rsa_pkcs1_sha512 = 0x0601, + + // ECDSA algorithms + ecdsa_secp256r1_sha256 = 0x0403, + ecdsa_secp384r1_sha384 = 0x0503, + ecdsa_secp521r1_sha512 = 0x0603, + + // RSASSA-PSS algorithms with public key OID rsaEncryption + rsa_pss_rsae_sha256 = 0x0804, + rsa_pss_rsae_sha384 = 0x0805, + rsa_pss_rsae_sha512 = 0x0806, + + // EdDSA algorithms + ed25519 = 0x0807, + ed448 = 0x0808, + + // RSASSA-PSS algorithms with public key OID RSASSA-PSS + rsa_pss_pss_sha256 = 0x0809, + rsa_pss_pss_sha384 = 0x080a, + rsa_pss_pss_sha512 = 0x080b, + + // Legacy algorithms + rsa_pkcs1_sha1 = 0x0201, + ecdsa_sha1 = 0x0203, + + _, +}; + +pub const NamedGroup = enum(u16) { + // Elliptic Curve Groups (ECDHE) + secp256r1 = 0x0017, + secp384r1 = 0x0018, + secp521r1 = 0x0019, + x25519 = 0x001D, + x448 = 0x001E, + + // Finite Field Groups (DHE) + ffdhe2048 = 0x0100, + ffdhe3072 = 0x0101, + ffdhe4096 = 0x0102, + ffdhe6144 = 0x0103, + ffdhe8192 = 0x0104, + + _, +}; + +pub const CipherSuite = enum(u16) { + TLS_AES_128_GCM_SHA256 = 0x1301, + TLS_AES_256_GCM_SHA384 = 0x1302, + TLS_CHACHA20_POLY1305_SHA256 = 0x1303, + TLS_AES_128_CCM_SHA256 = 0x1304, + TLS_AES_128_CCM_8_SHA256 = 0x1305, +}; + +pub const CipherParams = union(CipherSuite) { + TLS_AES_128_GCM_SHA256: struct { + pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm; + pub const Hash = crypto.hash.sha2.Sha256; + pub const Hmac = crypto.auth.hmac.Hmac(Hash); + pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); + + handshake_secret: [Hkdf.key_len]u8, + master_secret: [Hkdf.key_len]u8, + client_handshake_key: [AEAD.key_length]u8, + server_handshake_key: [AEAD.key_length]u8, + client_finished_key: [Hmac.key_length]u8, + server_finished_key: [Hmac.key_length]u8, + client_handshake_iv: [AEAD.nonce_length]u8, + server_handshake_iv: [AEAD.nonce_length]u8, + transcript_hash: Hash, + }, + TLS_AES_256_GCM_SHA384: struct { + pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm; + pub const Hash = crypto.hash.sha2.Sha384; + pub const Hmac = crypto.auth.hmac.Hmac(Hash); + pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); + + handshake_secret: [Hkdf.key_len]u8, + master_secret: [Hkdf.key_len]u8, + client_handshake_key: [AEAD.key_length]u8, + server_handshake_key: [AEAD.key_length]u8, + client_finished_key: [Hmac.key_length]u8, + server_finished_key: [Hmac.key_length]u8, + client_handshake_iv: [AEAD.nonce_length]u8, + server_handshake_iv: [AEAD.nonce_length]u8, + transcript_hash: Hash, + }, + TLS_CHACHA20_POLY1305_SHA256: void, + TLS_AES_128_CCM_SHA256: void, + TLS_AES_128_CCM_8_SHA256: void, +}; + +/// Encryption parameters for application traffic. +pub const ApplicationCipher = union(CipherSuite) { + TLS_AES_128_GCM_SHA256: struct { + pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm; + pub const Hash = crypto.hash.sha2.Sha256; + pub const Hmac = crypto.auth.hmac.Hmac(Hash); + pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); + + client_key: [AEAD.key_length]u8, + server_key: [AEAD.key_length]u8, + client_iv: [AEAD.nonce_length]u8, + server_iv: [AEAD.nonce_length]u8, + }, + TLS_AES_256_GCM_SHA384: struct { + pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm; + pub const Hash = crypto.hash.sha2.Sha384; + pub const Hmac = crypto.auth.hmac.Hmac(Hash); + pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); + + client_key: [AEAD.key_length]u8, + server_key: [AEAD.key_length]u8, + client_iv: [AEAD.nonce_length]u8, + server_iv: [AEAD.nonce_length]u8, + }, + TLS_CHACHA20_POLY1305_SHA256: void, + TLS_AES_128_CCM_SHA256: void, + TLS_AES_128_CCM_8_SHA256: void, +}; + +pub fn hkdfExpandLabel( + comptime Hkdf: type, + key: [Hkdf.key_len]u8, + label: []const u8, + context: []const u8, + comptime len: usize, +) [len]u8 { + const max_label_len = 255; + const max_context_len = 255; + const tls13 = "tls13 "; + var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined; + mem.writeIntBig(u16, buf[0..2], len); + buf[2] = @intCast(u8, tls13.len + label.len); + buf[3..][0..tls13.len].* = tls13.*; + var i: usize = 3 + tls13.len; + mem.copy(u8, buf[i..], label); + i += label.len; + buf[i] = @intCast(u8, context.len); + i += 1; + mem.copy(u8, buf[i..], context); + i += context.len; + + var result: [len]u8 = undefined; + Hkdf.expand(&result, buf[0..i], key); + return result; +} + +pub fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 { + var result: [Hash.digest_length]u8 = undefined; + Hash.hash(&.{}, &result, .{}); + return result; +} + +pub fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 { + var result: [Hmac.mac_length]u8 = undefined; + Hmac.create(&result, message, &key); + return result; +} diff --git a/lib/std/crypto/Tls.zig b/lib/std/crypto/tls/Client.zig similarity index 75% rename from lib/std/crypto/Tls.zig rename to lib/std/crypto/tls/Client.zig index 0d79a5c32744..93693e40e922 100644 --- a/lib/std/crypto/Tls.zig +++ b/lib/std/crypto/tls/Client.zig @@ -1,297 +1,28 @@ -const std = @import("../std.zig"); -const Tls = @This(); +const std = @import("../../std.zig"); +const tls = std.crypto.tls; +const Client = @This(); const net = std.net; const mem = std.mem; const crypto = std.crypto; const assert = std.debug.assert; +const ApplicationCipher = tls.ApplicationCipher; +const CipherSuite = tls.CipherSuite; +const ContentType = tls.ContentType; +const HandshakeType = tls.HandshakeType; +const CipherParams = tls.CipherParams; +const max_ciphertext_len = tls.max_ciphertext_len; +const hkdfExpandLabel = tls.hkdfExpandLabel; + application_cipher: ApplicationCipher, read_seq: u64, write_seq: u64, /// The size is enough to contain exactly one TLSCiphertext record. -partially_read_buffer: [max_ciphertext_record_len]u8, +partially_read_buffer: [tls.max_ciphertext_record_len]u8, /// The number of partially read bytes inside `partiall_read_buffer`. partially_read_len: u15, eof: bool, -pub const ciphertext_record_header_len = 5; -pub const max_ciphertext_len = (1 << 14) + 256; -pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len; -pub const hello_retry_request_sequence = [32]u8{ - 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, - 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C, -}; - -pub const ProtocolVersion = enum(u16) { - tls_1_2 = 0x0303, - tls_1_3 = 0x0304, - _, -}; - -pub const ContentType = enum(u8) { - invalid = 0, - change_cipher_spec = 20, - alert = 21, - handshake = 22, - application_data = 23, - _, -}; - -pub const HandshakeType = enum(u8) { - client_hello = 1, - server_hello = 2, - new_session_ticket = 4, - end_of_early_data = 5, - encrypted_extensions = 8, - certificate = 11, - certificate_request = 13, - certificate_verify = 15, - finished = 20, - key_update = 24, - message_hash = 254, -}; - -pub const ExtensionType = enum(u16) { - /// RFC 6066 - server_name = 0, - /// RFC 6066 - max_fragment_length = 1, - /// RFC 6066 - status_request = 5, - /// RFC 8422, 7919 - supported_groups = 10, - /// RFC 8446 - signature_algorithms = 13, - /// RFC 5764 - use_srtp = 14, - /// RFC 6520 - heartbeat = 15, - /// RFC 7301 - application_layer_protocol_negotiation = 16, - /// RFC 6962 - signed_certificate_timestamp = 18, - /// RFC 7250 - client_certificate_type = 19, - /// RFC 7250 - server_certificate_type = 20, - /// RFC 7685 - padding = 21, - /// RFC 8446 - pre_shared_key = 41, - /// RFC 8446 - early_data = 42, - /// RFC 8446 - supported_versions = 43, - /// RFC 8446 - cookie = 44, - /// RFC 8446 - psk_key_exchange_modes = 45, - /// RFC 8446 - certificate_authorities = 47, - /// RFC 8446 - oid_filters = 48, - /// RFC 8446 - post_handshake_auth = 49, - /// RFC 8446 - signature_algorithms_cert = 50, - /// RFC 8446 - key_share = 51, -}; - -pub const AlertLevel = enum(u8) { - warning = 1, - fatal = 2, - _, -}; - -pub const AlertDescription = enum(u8) { - close_notify = 0, - unexpected_message = 10, - bad_record_mac = 20, - record_overflow = 22, - handshake_failure = 40, - bad_certificate = 42, - unsupported_certificate = 43, - certificate_revoked = 44, - certificate_expired = 45, - certificate_unknown = 46, - illegal_parameter = 47, - unknown_ca = 48, - access_denied = 49, - decode_error = 50, - decrypt_error = 51, - protocol_version = 70, - insufficient_security = 71, - internal_error = 80, - inappropriate_fallback = 86, - user_canceled = 90, - missing_extension = 109, - unsupported_extension = 110, - unrecognized_name = 112, - bad_certificate_status_response = 113, - unknown_psk_identity = 115, - certificate_required = 116, - no_application_protocol = 120, - _, -}; - -pub const SignatureScheme = enum(u16) { - // RSASSA-PKCS1-v1_5 algorithms - rsa_pkcs1_sha256 = 0x0401, - rsa_pkcs1_sha384 = 0x0501, - rsa_pkcs1_sha512 = 0x0601, - - // ECDSA algorithms - ecdsa_secp256r1_sha256 = 0x0403, - ecdsa_secp384r1_sha384 = 0x0503, - ecdsa_secp521r1_sha512 = 0x0603, - - // RSASSA-PSS algorithms with public key OID rsaEncryption - rsa_pss_rsae_sha256 = 0x0804, - rsa_pss_rsae_sha384 = 0x0805, - rsa_pss_rsae_sha512 = 0x0806, - - // EdDSA algorithms - ed25519 = 0x0807, - ed448 = 0x0808, - - // RSASSA-PSS algorithms with public key OID RSASSA-PSS - rsa_pss_pss_sha256 = 0x0809, - rsa_pss_pss_sha384 = 0x080a, - rsa_pss_pss_sha512 = 0x080b, - - // Legacy algorithms - rsa_pkcs1_sha1 = 0x0201, - ecdsa_sha1 = 0x0203, - - _, -}; - -pub const NamedGroup = enum(u16) { - // Elliptic Curve Groups (ECDHE) - secp256r1 = 0x0017, - secp384r1 = 0x0018, - secp521r1 = 0x0019, - x25519 = 0x001D, - x448 = 0x001E, - - // Finite Field Groups (DHE) - ffdhe2048 = 0x0100, - ffdhe3072 = 0x0101, - ffdhe4096 = 0x0102, - ffdhe6144 = 0x0103, - ffdhe8192 = 0x0104, - - _, -}; - -// Plaintext: -// * type: ContentType -// * legacy_record_version: u16 = 0x0303, -// * length: u16, -// - The length (in bytes) of the following TLSPlaintext.fragment. The -// length MUST NOT exceed 2^14 bytes. -// * fragment: opaque -// - the data being transmitted - -// Ciphertext -// * ContentType opaque_type = application_data; /* 23 */ -// * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ -// * uint16 length; -// * opaque encrypted_record[TLSCiphertext.length]; - -// Handshake: -// * type: HandshakeType -// * length: u24 -// * data: opaque - -// ServerHello: -// * ProtocolVersion legacy_version = 0x0303; -// * Random random; -// * opaque legacy_session_id_echo<0..32>; -// * CipherSuite cipher_suite; -// * uint8 legacy_compression_method = 0; -// * Extension extensions<6..2^16-1>; - -// Extension: -// * ExtensionType extension_type; -// * opaque extension_data<0..2^16-1>; - -pub const CipherSuite = enum(u16) { - TLS_AES_128_GCM_SHA256 = 0x1301, - TLS_AES_256_GCM_SHA384 = 0x1302, - TLS_CHACHA20_POLY1305_SHA256 = 0x1303, - TLS_AES_128_CCM_SHA256 = 0x1304, - TLS_AES_128_CCM_8_SHA256 = 0x1305, -}; - -pub const CipherParams = union(CipherSuite) { - TLS_AES_128_GCM_SHA256: struct { - const AEAD = crypto.aead.aes_gcm.Aes128Gcm; - const Hash = crypto.hash.sha2.Sha256; - const Hmac = crypto.auth.hmac.Hmac(Hash); - const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); - - handshake_secret: [Hkdf.key_len]u8, - master_secret: [Hkdf.key_len]u8, - client_handshake_key: [AEAD.key_length]u8, - server_handshake_key: [AEAD.key_length]u8, - client_finished_key: [Hmac.key_length]u8, - server_finished_key: [Hmac.key_length]u8, - client_handshake_iv: [AEAD.nonce_length]u8, - server_handshake_iv: [AEAD.nonce_length]u8, - transcript_hash: Hash, - }, - TLS_AES_256_GCM_SHA384: struct { - const AEAD = crypto.aead.aes_gcm.Aes256Gcm; - const Hash = crypto.hash.sha2.Sha384; - const Hmac = crypto.auth.hmac.Hmac(Hash); - const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); - - handshake_secret: [Hkdf.key_len]u8, - master_secret: [Hkdf.key_len]u8, - client_handshake_key: [AEAD.key_length]u8, - server_handshake_key: [AEAD.key_length]u8, - client_finished_key: [Hmac.key_length]u8, - server_finished_key: [Hmac.key_length]u8, - client_handshake_iv: [AEAD.nonce_length]u8, - server_handshake_iv: [AEAD.nonce_length]u8, - transcript_hash: Hash, - }, - TLS_CHACHA20_POLY1305_SHA256: void, - TLS_AES_128_CCM_SHA256: void, - TLS_AES_128_CCM_8_SHA256: void, -}; - -/// Encryption parameters for application traffic. -pub const ApplicationCipher = union(CipherSuite) { - TLS_AES_128_GCM_SHA256: struct { - const AEAD = crypto.aead.aes_gcm.Aes128Gcm; - const Hash = crypto.hash.sha2.Sha256; - const Hmac = crypto.auth.hmac.Hmac(Hash); - const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); - - client_key: [AEAD.key_length]u8, - server_key: [AEAD.key_length]u8, - client_iv: [AEAD.nonce_length]u8, - server_iv: [AEAD.nonce_length]u8, - }, - TLS_AES_256_GCM_SHA384: struct { - const AEAD = crypto.aead.aes_gcm.Aes256Gcm; - const Hash = crypto.hash.sha2.Sha384; - const Hmac = crypto.auth.hmac.Hmac(Hash); - const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); - - client_key: [AEAD.key_length]u8, - server_key: [AEAD.key_length]u8, - client_iv: [AEAD.nonce_length]u8, - server_iv: [AEAD.nonce_length]u8, - }, - TLS_CHACHA20_POLY1305_SHA256: void, - TLS_AES_128_CCM_SHA256: void, - TLS_AES_128_CCM_8_SHA256: void, -}; - const cipher_suites = blk: { const fields = @typeInfo(CipherSuite).Enum.fields; var result: [(fields.len + 1) * 2]u8 = undefined; @@ -305,7 +36,7 @@ const cipher_suites = blk: { }; /// `host` is only borrowed during this function call. -pub fn init(stream: net.Stream, host: []const u8) !Tls { +pub fn init(stream: net.Stream, host: []const u8) !Client { var x25519_priv_key: [32]u8 = undefined; crypto.random.bytes(&x25519_priv_key); const x25519_pub_key = crypto.dh.X25519.recoverPublicKey(x25519_priv_key) catch |err| { @@ -440,8 +171,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { switch (ct) { .alert => { - const level = @intToEnum(AlertLevel, frag[0]); - const desc = @intToEnum(AlertDescription, frag[1]); + const level = @intToEnum(tls.AlertLevel, frag[0]); + const desc = @intToEnum(tls.AlertDescription, frag[1]); std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); return error.TlsAlert; }, @@ -454,7 +185,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { const hello = frag[4..]; const legacy_version = mem.readIntBig(u16, hello[0..2]); const random = hello[2..34].*; - if (mem.eql(u8, &random, &hello_retry_request_sequence)) { + if (mem.eql(u8, &random, &tls.hello_retry_request_sequence)) { @panic("TODO handle HelloRetryRequest"); } const legacy_session_id_echo_len = hello[34]; @@ -478,16 +209,16 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { const next_i = i + ext_size; if (next_i > hello.len) return error.TlsBadLength; switch (et) { - @enumToInt(ExtensionType.supported_versions) => { + @enumToInt(tls.ExtensionType.supported_versions) => { if (supported_version != 0) return error.TlsIllegalParameter; supported_version = mem.readIntBig(u16, hello[i..][0..2]); }, - @enumToInt(ExtensionType.key_share) => { + @enumToInt(tls.ExtensionType.key_share) => { if (opt_x25519_server_pub_key != null) return error.TlsIllegalParameter; const named_group = mem.readIntBig(u16, hello[i..][0..2]); i += 2; switch (named_group) { - @enumToInt(NamedGroup.x25519) => { + @enumToInt(tls.NamedGroup.x25519) => { const key_size = mem.readIntBig(u16, hello[i..][0..2]); i += 2; if (key_size != 32) return error.TlsBadLength; @@ -509,10 +240,10 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { return error.TlsIllegalParameter; const tls_version = if (supported_version == 0) legacy_version else supported_version; switch (tls_version) { - @enumToInt(ProtocolVersion.tls_1_2) => { + @enumToInt(tls.ProtocolVersion.tls_1_2) => { std.debug.print("server wants TLS v1.2\n", .{}); }, - @enumToInt(ProtocolVersion.tls_1_3) => { + @enumToInt(tls.ProtocolVersion.tls_1_3) => { std.debug.print("server wants TLS v1.3\n", .{}); }, else => return error.TlsIllegalParameter, @@ -544,7 +275,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { const hello_hash = p.transcript_hash.peek(); const zeroes = [1]u8{0} ** P.Hash.digest_length; const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes); - const empty_hash = emptyHash(P.Hash); + const empty_hash = tls.emptyHash(P.Hash); const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length); p.handshake_secret = P.Hkdf.extract(&hs_derived_secret, &shared_key); const ap_derived_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "derived", &empty_hash, P.Hash.digest_length); @@ -670,7 +401,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { ct_i += 2; const next_ext_i = ct_i + ext_size; switch (et) { - @enumToInt(ExtensionType.server_name) => {}, + @enumToInt(tls.ExtensionType.server_name) => {}, else => { std.debug.print("encrypted extension: {any}\n", .{ et, @@ -699,7 +430,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { const P = @TypeOf(p.*); // TODO verify the server's data const handshake_hash = p.transcript_hash.finalResult(); - const verify_data = hmac(P.Hmac, &handshake_hash, p.client_finished_key); + const verify_data = tls.hmac(P.Hmac, &handshake_hash, p.client_finished_key); const out_cleartext = [_]u8{ @enumToInt(HandshakeType.finished), 0, 0, verify_data.len, // length @@ -782,8 +513,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { return error.TlsHandshakeFailure; } -pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { - var ciphertext_buf: [max_ciphertext_record_len * 4]u8 = undefined; +pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { + var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; // Due to the trailing inner content type byte in the ciphertext, we need // an additional buffer for storing the cleartext into before encrypting. var cleartext_buf: [max_ciphertext_len]u8 = undefined; @@ -792,16 +523,16 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { var iovec_end: usize = 0; var bytes_i: usize = 0; // How many bytes are taken up by overhead per record. - const overhead_len: usize = switch (tls.application_cipher) { + const overhead_len: usize = switch (c.application_cipher) { inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| l: { const P = @TypeOf(p.*); const V = @Vector(P.AEAD.nonce_length, u8); - const overhead_len = ciphertext_record_header_len + P.AEAD.tag_length + 1; + const overhead_len = tls.ciphertext_record_header_len + P.AEAD.tag_length + 1; while (true) { const encrypted_content_len = @intCast(u16, @min( @min(bytes.len - bytes_i, max_ciphertext_len - 1), ciphertext_buf.len - - ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, + tls.ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, )); if (encrypted_content_len == 0) break :l overhead_len; @@ -815,7 +546,7 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { const ad = ciphertext_buf[ciphertext_end..][0..5]; ad.* = [_]u8{@enumToInt(ContentType.application_data)} ++ - int2(@enumToInt(ProtocolVersion.tls_1_2)) ++ + int2(@enumToInt(tls.ProtocolVersion.tls_1_2)) ++ int2(ciphertext_len + P.AEAD.tag_length); ciphertext_end += ad.len; const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len]; @@ -823,12 +554,12 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length]; ciphertext_end += auth_tag.len; const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); - const operand: V = pad ++ @bitCast([8]u8, big(tls.write_seq)); - tls.write_seq += 1; + const operand: V = pad ++ @bitCast([8]u8, big(c.write_seq)); + c.write_seq += 1; const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.client_iv) ^ operand; P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); //std.debug.print("seq: {d} nonce: {} client_key: {} client_iv: {} ad: {} auth_tag: {}\nserver_key: {} server_iv: {}\n", .{ - // tls.write_seq - 1, + // c.write_seq - 1, // std.fmt.fmtSliceHexLower(&nonce), // std.fmt.fmtSliceHexLower(&p.client_key), // std.fmt.fmtSliceHexLower(&p.client_iv), @@ -878,37 +609,37 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { } } -pub fn writeAll(tls: *Tls, stream: net.Stream, bytes: []const u8) !void { +pub fn writeAll(c: *Client, stream: net.Stream, bytes: []const u8) !void { var index: usize = 0; while (index < bytes.len) { - index += try tls.write(stream, bytes[index..]); + index += try c.write(stream, bytes[index..]); } } /// Returns number of bytes that have been read, which are now populated inside /// `buffer`. A return value of zero bytes does not necessarily mean end of /// stream. -pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { - const prev_len = tls.partially_read_len; +pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize { + const prev_len = c.partially_read_len; var in_buf: [max_ciphertext_len * 4]u8 = undefined; - mem.copy(u8, &in_buf, tls.partially_read_buffer[0..prev_len]); + mem.copy(u8, &in_buf, c.partially_read_buffer[0..prev_len]); // Capacity of output buffer, in records, rounded up. const buf_cap = (buffer.len +| (max_ciphertext_len - 1)) / max_ciphertext_len; - const wanted_read_len = buf_cap * (max_ciphertext_len + ciphertext_record_header_len); + const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); const ask_slice = in_buf[prev_len..@min(wanted_read_len, in_buf.len)]; const actual_read_len = try stream.read(ask_slice); const frag = in_buf[0 .. prev_len + actual_read_len]; if (frag.len == 0) { - tls.eof = true; + c.eof = true; return 0; } var in: usize = 0; var out: usize = 0; while (true) { - if (in + ciphertext_record_header_len > frag.len) { - return finishRead(tls, frag, in, out); + if (in + tls.ciphertext_record_header_len > frag.len) { + return finishRead(c, frag, in, out); } const ct = @intToEnum(ContentType, frag[in]); in += 1; @@ -920,14 +651,14 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { const end = in + record_size; if (end > frag.len) { if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; - return finishRead(tls, frag, in, out); + return finishRead(c, frag, in, out); } switch (ct) { .alert => { @panic("TODO handle an alert here"); }, .application_data => { - const cleartext_len = switch (tls.application_cipher) { + const cleartext_len = switch (c.application_cipher) { inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { const P = @TypeOf(p.*); const V = @Vector(P.AEAD.nonce_length, u8); @@ -938,11 +669,11 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { const auth_tag = frag[in..][0..P.AEAD.tag_length].*; const cleartext = buffer[out..][0..ciphertext_len]; const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); - const operand: V = pad ++ @bitCast([8]u8, big(tls.read_seq)); - tls.read_seq += 1; + const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq)); + c.read_seq += 1; const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; //std.debug.print("seq: {d} nonce: {} server_key: {} server_iv: {}\n", .{ - // tls.read_seq - 1, + // c.read_seq - 1, // std.fmt.fmtSliceHexLower(&nonce), // std.fmt.fmtSliceHexLower(&p.server_key), // std.fmt.fmtSliceHexLower(&p.server_iv), @@ -965,10 +696,10 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]); switch (inner_ct) { .alert => { - const level = @intToEnum(AlertLevel, buffer[out]); - const desc = @intToEnum(AlertDescription, buffer[out + 1]); + const level = @intToEnum(tls.AlertLevel, buffer[out]); + const desc = @intToEnum(tls.AlertDescription, buffer[out + 1]); if (desc == .close_notify) { - tls.eof = true; + c.eof = true; return out; } std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); @@ -995,52 +726,13 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { } } -fn finishRead(tls: *Tls, frag: []const u8, in: usize, out: usize) usize { +fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { const saved_buf = frag[in..]; - mem.copy(u8, &tls.partially_read_buffer, saved_buf); - tls.partially_read_len = @intCast(u15, saved_buf.len); + mem.copy(u8, &c.partially_read_buffer, saved_buf); + c.partially_read_len = @intCast(u15, saved_buf.len); return out; } -fn hkdfExpandLabel( - comptime Hkdf: type, - key: [Hkdf.key_len]u8, - label: []const u8, - context: []const u8, - comptime len: usize, -) [len]u8 { - const max_label_len = 255; - const max_context_len = 255; - const tls13 = "tls13 "; - var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined; - mem.writeIntBig(u16, buf[0..2], len); - buf[2] = @intCast(u8, tls13.len + label.len); - buf[3..][0..tls13.len].* = tls13.*; - var i: usize = 3 + tls13.len; - mem.copy(u8, buf[i..], label); - i += label.len; - buf[i] = @intCast(u8, context.len); - i += 1; - mem.copy(u8, buf[i..], context); - i += context.len; - - var result: [len]u8 = undefined; - Hkdf.expand(&result, buf[0..i], key); - return result; -} - -fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 { - var result: [Hash.digest_length]u8 = undefined; - Hash.hash(&.{}, &result, .{}); - return result; -} - -fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 { - var result: [Hmac.mac_length]u8 = undefined; - Hmac.create(&result, message, &key); - return result; -} - const builtin = @import("builtin"); const native_endian = builtin.cpu.arch.endian(); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 2c9216343596..fcadf3669b11 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -12,7 +12,7 @@ pub const Request = struct { client: *Client, stream: net.Stream, headers: std.ArrayListUnmanaged(u8) = .{}, - tls: std.crypto.Tls, + tls_client: std.crypto.tls.Client, protocol: Protocol, pub const Protocol = enum { http, https }; @@ -51,7 +51,7 @@ pub const Request = struct { try req.stream.writeAll(req.headers.items); }, .https => { - try req.tls.writeAll(req.stream, req.headers.items); + try req.tls_client.writeAll(req.stream, req.headers.items); }, } } @@ -59,7 +59,7 @@ pub const Request = struct { pub fn read(req: *Request, buffer: []u8) !usize { switch (req.protocol) { .http => return req.stream.read(buffer), - .https => return req.tls.read(req.stream, buffer), + .https => return req.tls_client.read(req.stream, buffer), } } @@ -74,7 +74,7 @@ pub const Request = struct { if (amt == 0) { switch (req.protocol) { .http => break, - .https => if (req.tls.eof) break, + .https => if (req.tls_client.eof) break, } } index += amt; @@ -94,7 +94,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { .client = client, .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port), .protocol = options.protocol, - .tls = undefined, + .tls_client = undefined, }; client.active_requests += 1; errdefer req.deinit(); @@ -102,7 +102,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { switch (options.protocol) { .http => {}, .https => { - req.tls = try std.crypto.Tls.init(req.stream, options.host); + req.tls_client = try std.crypto.tls.Client.init(req.stream, options.host); }, }