| author | |
| committer | |
| log | 93ab8be8d8464452af6d2e686e91be2c1da98979 |
| tree | 2c0e0c5264df2000325c22733bb154be5607eee1 |
| parent | 02c33d02e05f3dd067bc5492d2617b7805ef897d |
5 files changed, 1083 insertions(+), 1066 deletions(-)
lib/std/crypto.zig+1-1| ... | @@ -176,7 +176,7 @@ const std = @import("std.zig"); | ... | @@ -176,7 +176,7 @@ const std = @import("std.zig"); |
| 176 | 176 | ||
| 177 | pub const errors = @import("crypto/errors.zig"); | 177 | pub const errors = @import("crypto/errors.zig"); |
| 178 | 178 | ||
| 179 | pub const Tls = @import("crypto/Tls.zig"); | 179 | pub const tls = @import("crypto/tls.zig"); |
| 180 | 180 | ||
| 181 | test { | 181 | test { |
| 182 | _ = aead.aegis.Aegis128L; | 182 | _ = aead.aegis.Aegis128L; |
lib/std/crypto/Tls.zig deleted-1059| ... | @@ -1,1059 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const Tls = @This(); | ||
| 3 | const net = std.net; | ||
| 4 | const mem = std.mem; | ||
| 5 | const crypto = std.crypto; | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | |||
| 8 | application_cipher: ApplicationCipher, | ||
| 9 | read_seq: u64, | ||
| 10 | write_seq: u64, | ||
| 11 | /// The size is enough to contain exactly one TLSCiphertext record. | ||
| 12 | partially_read_buffer: [max_ciphertext_record_len]u8, | ||
| 13 | /// The number of partially read bytes inside `partiall_read_buffer`. | ||
| 14 | partially_read_len: u15, | ||
| 15 | eof: bool, | ||
| 16 | |||
| 17 | pub const ciphertext_record_header_len = 5; | ||
| 18 | pub const max_ciphertext_len = (1 << 14) + 256; | ||
| 19 | pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len; | ||
| 20 | pub const hello_retry_request_sequence = [32]u8{ | ||
| 21 | 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, | ||
| 22 | 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C, | ||
| 23 | }; | ||
| 24 | |||
| 25 | pub const ProtocolVersion = enum(u16) { | ||
| 26 | tls_1_2 = 0x0303, | ||
| 27 | tls_1_3 = 0x0304, | ||
| 28 | _, | ||
| 29 | }; | ||
| 30 | |||
| 31 | pub const ContentType = enum(u8) { | ||
| 32 | invalid = 0, | ||
| 33 | change_cipher_spec = 20, | ||
| 34 | alert = 21, | ||
| 35 | handshake = 22, | ||
| 36 | application_data = 23, | ||
| 37 | _, | ||
| 38 | }; | ||
| 39 | |||
| 40 | pub const HandshakeType = enum(u8) { | ||
| 41 | client_hello = 1, | ||
| 42 | server_hello = 2, | ||
| 43 | new_session_ticket = 4, | ||
| 44 | end_of_early_data = 5, | ||
| 45 | encrypted_extensions = 8, | ||
| 46 | certificate = 11, | ||
| 47 | certificate_request = 13, | ||
| 48 | certificate_verify = 15, | ||
| 49 | finished = 20, | ||
| 50 | key_update = 24, | ||
| 51 | message_hash = 254, | ||
| 52 | }; | ||
| 53 | |||
| 54 | pub const ExtensionType = enum(u16) { | ||
| 55 | /// RFC 6066 | ||
| 56 | server_name = 0, | ||
| 57 | /// RFC 6066 | ||
| 58 | max_fragment_length = 1, | ||
| 59 | /// RFC 6066 | ||
| 60 | status_request = 5, | ||
| 61 | /// RFC 8422, 7919 | ||
| 62 | supported_groups = 10, | ||
| 63 | /// RFC 8446 | ||
| 64 | signature_algorithms = 13, | ||
| 65 | /// RFC 5764 | ||
| 66 | use_srtp = 14, | ||
| 67 | /// RFC 6520 | ||
| 68 | heartbeat = 15, | ||
| 69 | /// RFC 7301 | ||
| 70 | application_layer_protocol_negotiation = 16, | ||
| 71 | /// RFC 6962 | ||
| 72 | signed_certificate_timestamp = 18, | ||
| 73 | /// RFC 7250 | ||
| 74 | client_certificate_type = 19, | ||
| 75 | /// RFC 7250 | ||
| 76 | server_certificate_type = 20, | ||
| 77 | /// RFC 7685 | ||
| 78 | padding = 21, | ||
| 79 | /// RFC 8446 | ||
| 80 | pre_shared_key = 41, | ||
| 81 | /// RFC 8446 | ||
| 82 | early_data = 42, | ||
| 83 | /// RFC 8446 | ||
| 84 | supported_versions = 43, | ||
| 85 | /// RFC 8446 | ||
| 86 | cookie = 44, | ||
| 87 | /// RFC 8446 | ||
| 88 | psk_key_exchange_modes = 45, | ||
| 89 | /// RFC 8446 | ||
| 90 | certificate_authorities = 47, | ||
| 91 | /// RFC 8446 | ||
| 92 | oid_filters = 48, | ||
| 93 | /// RFC 8446 | ||
| 94 | post_handshake_auth = 49, | ||
| 95 | /// RFC 8446 | ||
| 96 | signature_algorithms_cert = 50, | ||
| 97 | /// RFC 8446 | ||
| 98 | key_share = 51, | ||
| 99 | }; | ||
| 100 | |||
| 101 | pub const AlertLevel = enum(u8) { | ||
| 102 | warning = 1, | ||
| 103 | fatal = 2, | ||
| 104 | _, | ||
| 105 | }; | ||
| 106 | |||
| 107 | pub const AlertDescription = enum(u8) { | ||
| 108 | close_notify = 0, | ||
| 109 | unexpected_message = 10, | ||
| 110 | bad_record_mac = 20, | ||
| 111 | record_overflow = 22, | ||
| 112 | handshake_failure = 40, | ||
| 113 | bad_certificate = 42, | ||
| 114 | unsupported_certificate = 43, | ||
| 115 | certificate_revoked = 44, | ||
| 116 | certificate_expired = 45, | ||
| 117 | certificate_unknown = 46, | ||
| 118 | illegal_parameter = 47, | ||
| 119 | unknown_ca = 48, | ||
| 120 | access_denied = 49, | ||
| 121 | decode_error = 50, | ||
| 122 | decrypt_error = 51, | ||
| 123 | protocol_version = 70, | ||
| 124 | insufficient_security = 71, | ||
| 125 | internal_error = 80, | ||
| 126 | inappropriate_fallback = 86, | ||
| 127 | user_canceled = 90, | ||
| 128 | missing_extension = 109, | ||
| 129 | unsupported_extension = 110, | ||
| 130 | unrecognized_name = 112, | ||
| 131 | bad_certificate_status_response = 113, | ||
| 132 | unknown_psk_identity = 115, | ||
| 133 | certificate_required = 116, | ||
| 134 | no_application_protocol = 120, | ||
| 135 | _, | ||
| 136 | }; | ||
| 137 | |||
| 138 | pub const SignatureScheme = enum(u16) { | ||
| 139 | // RSASSA-PKCS1-v1_5 algorithms | ||
| 140 | rsa_pkcs1_sha256 = 0x0401, | ||
| 141 | rsa_pkcs1_sha384 = 0x0501, | ||
| 142 | rsa_pkcs1_sha512 = 0x0601, | ||
| 143 | |||
| 144 | // ECDSA algorithms | ||
| 145 | ecdsa_secp256r1_sha256 = 0x0403, | ||
| 146 | ecdsa_secp384r1_sha384 = 0x0503, | ||
| 147 | ecdsa_secp521r1_sha512 = 0x0603, | ||
| 148 | |||
| 149 | // RSASSA-PSS algorithms with public key OID rsaEncryption | ||
| 150 | rsa_pss_rsae_sha256 = 0x0804, | ||
| 151 | rsa_pss_rsae_sha384 = 0x0805, | ||
| 152 | rsa_pss_rsae_sha512 = 0x0806, | ||
| 153 | |||
| 154 | // EdDSA algorithms | ||
| 155 | ed25519 = 0x0807, | ||
| 156 | ed448 = 0x0808, | ||
| 157 | |||
| 158 | // RSASSA-PSS algorithms with public key OID RSASSA-PSS | ||
| 159 | rsa_pss_pss_sha256 = 0x0809, | ||
| 160 | rsa_pss_pss_sha384 = 0x080a, | ||
| 161 | rsa_pss_pss_sha512 = 0x080b, | ||
| 162 | |||
| 163 | // Legacy algorithms | ||
| 164 | rsa_pkcs1_sha1 = 0x0201, | ||
| 165 | ecdsa_sha1 = 0x0203, | ||
| 166 | |||
| 167 | _, | ||
| 168 | }; | ||
| 169 | |||
| 170 | pub const NamedGroup = enum(u16) { | ||
| 171 | // Elliptic Curve Groups (ECDHE) | ||
| 172 | secp256r1 = 0x0017, | ||
| 173 | secp384r1 = 0x0018, | ||
| 174 | secp521r1 = 0x0019, | ||
| 175 | x25519 = 0x001D, | ||
| 176 | x448 = 0x001E, | ||
| 177 | |||
| 178 | // Finite Field Groups (DHE) | ||
| 179 | ffdhe2048 = 0x0100, | ||
| 180 | ffdhe3072 = 0x0101, | ||
| 181 | ffdhe4096 = 0x0102, | ||
| 182 | ffdhe6144 = 0x0103, | ||
| 183 | ffdhe8192 = 0x0104, | ||
| 184 | |||
| 185 | _, | ||
| 186 | }; | ||
| 187 | |||
| 188 | // Plaintext: | ||
| 189 | // * type: ContentType | ||
| 190 | // * legacy_record_version: u16 = 0x0303, | ||
| 191 | // * length: u16, | ||
| 192 | // - The length (in bytes) of the following TLSPlaintext.fragment. The | ||
| 193 | // length MUST NOT exceed 2^14 bytes. | ||
| 194 | // * fragment: opaque | ||
| 195 | // - the data being transmitted | ||
| 196 | |||
| 197 | // Ciphertext | ||
| 198 | // * ContentType opaque_type = application_data; /* 23 */ | ||
| 199 | // * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ | ||
| 200 | // * uint16 length; | ||
| 201 | // * opaque encrypted_record[TLSCiphertext.length]; | ||
| 202 | |||
| 203 | // Handshake: | ||
| 204 | // * type: HandshakeType | ||
| 205 | // * length: u24 | ||
| 206 | // * data: opaque | ||
| 207 | |||
| 208 | // ServerHello: | ||
| 209 | // * ProtocolVersion legacy_version = 0x0303; | ||
| 210 | // * Random random; | ||
| 211 | // * opaque legacy_session_id_echo<0..32>; | ||
| 212 | // * CipherSuite cipher_suite; | ||
| 213 | // * uint8 legacy_compression_method = 0; | ||
| 214 | // * Extension extensions<6..2^16-1>; | ||
| 215 | |||
| 216 | // Extension: | ||
| 217 | // * ExtensionType extension_type; | ||
| 218 | // * opaque extension_data<0..2^16-1>; | ||
| 219 | |||
| 220 | pub const CipherSuite = enum(u16) { | ||
| 221 | TLS_AES_128_GCM_SHA256 = 0x1301, | ||
| 222 | TLS_AES_256_GCM_SHA384 = 0x1302, | ||
| 223 | TLS_CHACHA20_POLY1305_SHA256 = 0x1303, | ||
| 224 | TLS_AES_128_CCM_SHA256 = 0x1304, | ||
| 225 | TLS_AES_128_CCM_8_SHA256 = 0x1305, | ||
| 226 | }; | ||
| 227 | |||
| 228 | pub const CipherParams = union(CipherSuite) { | ||
| 229 | TLS_AES_128_GCM_SHA256: struct { | ||
| 230 | const AEAD = crypto.aead.aes_gcm.Aes128Gcm; | ||
| 231 | const Hash = crypto.hash.sha2.Sha256; | ||
| 232 | const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 233 | const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 234 | |||
| 235 | handshake_secret: [Hkdf.key_len]u8, | ||
| 236 | master_secret: [Hkdf.key_len]u8, | ||
| 237 | client_handshake_key: [AEAD.key_length]u8, | ||
| 238 | server_handshake_key: [AEAD.key_length]u8, | ||
| 239 | client_finished_key: [Hmac.key_length]u8, | ||
| 240 | server_finished_key: [Hmac.key_length]u8, | ||
| 241 | client_handshake_iv: [AEAD.nonce_length]u8, | ||
| 242 | server_handshake_iv: [AEAD.nonce_length]u8, | ||
| 243 | transcript_hash: Hash, | ||
| 244 | }, | ||
| 245 | TLS_AES_256_GCM_SHA384: struct { | ||
| 246 | const AEAD = crypto.aead.aes_gcm.Aes256Gcm; | ||
| 247 | const Hash = crypto.hash.sha2.Sha384; | ||
| 248 | const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 249 | const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 250 | |||
| 251 | handshake_secret: [Hkdf.key_len]u8, | ||
| 252 | master_secret: [Hkdf.key_len]u8, | ||
| 253 | client_handshake_key: [AEAD.key_length]u8, | ||
| 254 | server_handshake_key: [AEAD.key_length]u8, | ||
| 255 | client_finished_key: [Hmac.key_length]u8, | ||
| 256 | server_finished_key: [Hmac.key_length]u8, | ||
| 257 | client_handshake_iv: [AEAD.nonce_length]u8, | ||
| 258 | server_handshake_iv: [AEAD.nonce_length]u8, | ||
| 259 | transcript_hash: Hash, | ||
| 260 | }, | ||
| 261 | TLS_CHACHA20_POLY1305_SHA256: void, | ||
| 262 | TLS_AES_128_CCM_SHA256: void, | ||
| 263 | TLS_AES_128_CCM_8_SHA256: void, | ||
| 264 | }; | ||
| 265 | |||
| 266 | /// Encryption parameters for application traffic. | ||
| 267 | pub const ApplicationCipher = union(CipherSuite) { | ||
| 268 | TLS_AES_128_GCM_SHA256: struct { | ||
| 269 | const AEAD = crypto.aead.aes_gcm.Aes128Gcm; | ||
| 270 | const Hash = crypto.hash.sha2.Sha256; | ||
| 271 | const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 272 | const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 273 | |||
| 274 | client_key: [AEAD.key_length]u8, | ||
| 275 | server_key: [AEAD.key_length]u8, | ||
| 276 | client_iv: [AEAD.nonce_length]u8, | ||
| 277 | server_iv: [AEAD.nonce_length]u8, | ||
| 278 | }, | ||
| 279 | TLS_AES_256_GCM_SHA384: struct { | ||
| 280 | const AEAD = crypto.aead.aes_gcm.Aes256Gcm; | ||
| 281 | const Hash = crypto.hash.sha2.Sha384; | ||
| 282 | const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 283 | const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 284 | |||
| 285 | client_key: [AEAD.key_length]u8, | ||
| 286 | server_key: [AEAD.key_length]u8, | ||
| 287 | client_iv: [AEAD.nonce_length]u8, | ||
| 288 | server_iv: [AEAD.nonce_length]u8, | ||
| 289 | }, | ||
| 290 | TLS_CHACHA20_POLY1305_SHA256: void, | ||
| 291 | TLS_AES_128_CCM_SHA256: void, | ||
| 292 | TLS_AES_128_CCM_8_SHA256: void, | ||
| 293 | }; | ||
| 294 | |||
| 295 | const cipher_suites = blk: { | ||
| 296 | const fields = @typeInfo(CipherSuite).Enum.fields; | ||
| 297 | var result: [(fields.len + 1) * 2]u8 = undefined; | ||
| 298 | mem.writeIntBig(u16, result[0..2], result.len - 2); | ||
| 299 | for (fields) |field, i| { | ||
| 300 | const int = @enumToInt(@field(CipherSuite, field.name)); | ||
| 301 | result[(i + 1) * 2] = @truncate(u8, int >> 8); | ||
| 302 | result[(i + 1) * 2 + 1] = @truncate(u8, int); | ||
| 303 | } | ||
| 304 | break :blk result; | ||
| 305 | }; | ||
| 306 | |||
| 307 | /// `host` is only borrowed during this function call. | ||
| 308 | pub fn init(stream: net.Stream, host: []const u8) !Tls { | ||
| 309 | var x25519_priv_key: [32]u8 = undefined; | ||
| 310 | crypto.random.bytes(&x25519_priv_key); | ||
| 311 | const x25519_pub_key = crypto.dh.X25519.recoverPublicKey(x25519_priv_key) catch |err| { | ||
| 312 | switch (err) { | ||
| 313 | // Only possible to happen if the private key is all zeroes. | ||
| 314 | error.IdentityElement => return error.InsufficientEntropy, | ||
| 315 | } | ||
| 316 | }; | ||
| 317 | |||
| 318 | // random (u32) | ||
| 319 | var rand_buf: [32]u8 = undefined; | ||
| 320 | crypto.random.bytes(&rand_buf); | ||
| 321 | |||
| 322 | const extensions_header = [_]u8{ | ||
| 323 | // Extensions byte length | ||
| 324 | undefined, undefined, | ||
| 325 | |||
| 326 | // Extension: supported_versions (only TLS 1.3) | ||
| 327 | 0, 43, // ExtensionType.supported_versions | ||
| 328 | 0x00, 0x05, // byte length of this extension payload | ||
| 329 | 0x04, // byte length of supported versions | ||
| 330 | 0x03, 0x04, // TLS 1.3 | ||
| 331 | 0x03, 0x03, // TLS 1.2 | ||
| 332 | |||
| 333 | // Extension: signature_algorithms | ||
| 334 | 0, 13, // ExtensionType.signature_algorithms | ||
| 335 | 0x00, 0x22, // byte length of this extension payload | ||
| 336 | 0x00, 0x20, // byte length of signature algorithms list | ||
| 337 | 0x04, 0x01, // rsa_pkcs1_sha256 | ||
| 338 | 0x05, 0x01, // rsa_pkcs1_sha384 | ||
| 339 | 0x06, 0x01, // rsa_pkcs1_sha512 | ||
| 340 | 0x04, 0x03, // ecdsa_secp256r1_sha256 | ||
| 341 | 0x05, 0x03, // ecdsa_secp384r1_sha384 | ||
| 342 | 0x06, 0x03, // ecdsa_secp521r1_sha512 | ||
| 343 | 0x08, 0x04, // rsa_pss_rsae_sha256 | ||
| 344 | 0x08, 0x05, // rsa_pss_rsae_sha384 | ||
| 345 | 0x08, 0x06, // rsa_pss_rsae_sha512 | ||
| 346 | 0x08, 0x07, // ed25519 | ||
| 347 | 0x08, 0x08, // ed448 | ||
| 348 | 0x08, 0x09, // rsa_pss_pss_sha256 | ||
| 349 | 0x08, 0x0a, // rsa_pss_pss_sha384 | ||
| 350 | 0x08, 0x0b, // rsa_pss_pss_sha512 | ||
| 351 | 0x02, 0x01, // rsa_pkcs1_sha1 | ||
| 352 | 0x02, 0x03, // ecdsa_sha1 | ||
| 353 | |||
| 354 | // Extension: supported_groups | ||
| 355 | 0, 10, // ExtensionType.supported_groups | ||
| 356 | 0x00, 0x0c, // byte length of this extension payload | ||
| 357 | 0x00, 0x0a, // byte length of supported groups list | ||
| 358 | 0x00, 0x17, // secp256r1 | ||
| 359 | 0x00, 0x18, // secp384r1 | ||
| 360 | 0x00, 0x19, // secp521r1 | ||
| 361 | 0x00, 0x1D, // x25519 | ||
| 362 | 0x00, 0x1E, // x448 | ||
| 363 | |||
| 364 | // Extension: key_share | ||
| 365 | 0, 51, // ExtensionType.key_share | ||
| 366 | 0, 38, // byte length of this extension payload | ||
| 367 | 0, 36, // byte length of client_shares | ||
| 368 | 0x00, 0x1D, // NamedGroup.x25519 | ||
| 369 | 0, 32, // byte length of key_exchange | ||
| 370 | } ++ x25519_pub_key ++ [_]u8{ | ||
| 371 | |||
| 372 | // Extension: server_name | ||
| 373 | 0, 0, // ExtensionType.server_name | ||
| 374 | undefined, undefined, // byte length of this extension payload | ||
| 375 | undefined, undefined, // server_name_list byte count | ||
| 376 | 0x00, // name_type | ||
| 377 | undefined, undefined, // host name len | ||
| 378 | }; | ||
| 379 | |||
| 380 | var hello_header = [_]u8{ | ||
| 381 | // Plaintext header | ||
| 382 | @enumToInt(ContentType.handshake), | ||
| 383 | 0x03, 0x01, // legacy_record_version | ||
| 384 | undefined, undefined, // Plaintext fragment length (u16) | ||
| 385 | |||
| 386 | // Handshake header | ||
| 387 | @enumToInt(HandshakeType.client_hello), | ||
| 388 | undefined, undefined, undefined, // handshake length (u24) | ||
| 389 | |||
| 390 | // ClientHello | ||
| 391 | 0x03, 0x03, // legacy_version | ||
| 392 | } ++ rand_buf ++ [1]u8{0} ++ cipher_suites ++ [_]u8{ | ||
| 393 | 0x01, 0x00, // legacy_compression_methods | ||
| 394 | } ++ extensions_header; | ||
| 395 | |||
| 396 | mem.writeIntBig(u16, hello_header[3..][0..2], @intCast(u16, hello_header.len - 5 + host.len)); | ||
| 397 | mem.writeIntBig(u24, hello_header[6..][0..3], @intCast(u24, hello_header.len - 9 + host.len)); | ||
| 398 | mem.writeIntBig( | ||
| 399 | u16, | ||
| 400 | hello_header[hello_header.len - extensions_header.len ..][0..2], | ||
| 401 | @intCast(u16, extensions_header.len - 2 + host.len), | ||
| 402 | ); | ||
| 403 | mem.writeIntBig(u16, hello_header[hello_header.len - 7 ..][0..2], @intCast(u16, 5 + host.len)); | ||
| 404 | mem.writeIntBig(u16, hello_header[hello_header.len - 5 ..][0..2], @intCast(u16, 3 + host.len)); | ||
| 405 | mem.writeIntBig(u16, hello_header[hello_header.len - 2 ..][0..2], @intCast(u16, 0 + host.len)); | ||
| 406 | |||
| 407 | { | ||
| 408 | var iovecs = [_]std.os.iovec_const{ | ||
| 409 | .{ | ||
| 410 | .iov_base = &hello_header, | ||
| 411 | .iov_len = hello_header.len, | ||
| 412 | }, | ||
| 413 | .{ | ||
| 414 | .iov_base = host.ptr, | ||
| 415 | .iov_len = host.len, | ||
| 416 | }, | ||
| 417 | }; | ||
| 418 | try stream.writevAll(&iovecs); | ||
| 419 | } | ||
| 420 | |||
| 421 | const client_hello_bytes1 = hello_header[5..]; | ||
| 422 | |||
| 423 | var cipher_params: CipherParams = undefined; | ||
| 424 | |||
| 425 | var handshake_buf: [8000]u8 = undefined; | ||
| 426 | var len: usize = 0; | ||
| 427 | var i: usize = i: { | ||
| 428 | const plaintext = handshake_buf[0..5]; | ||
| 429 | len = try stream.readAtLeast(&handshake_buf, plaintext.len); | ||
| 430 | if (len < plaintext.len) return error.EndOfStream; | ||
| 431 | const ct = @intToEnum(ContentType, plaintext[0]); | ||
| 432 | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); | ||
| 433 | const end = plaintext.len + frag_len; | ||
| 434 | if (end > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 435 | if (end > len) { | ||
| 436 | len += try stream.readAtLeast(handshake_buf[len..], end - len); | ||
| 437 | if (end > len) return error.EndOfStream; | ||
| 438 | } | ||
| 439 | const frag = handshake_buf[plaintext.len..end]; | ||
| 440 | |||
| 441 | switch (ct) { | ||
| 442 | .alert => { | ||
| 443 | const level = @intToEnum(AlertLevel, frag[0]); | ||
| 444 | const desc = @intToEnum(AlertDescription, frag[1]); | ||
| 445 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | ||
| 446 | return error.TlsAlert; | ||
| 447 | }, | ||
| 448 | .handshake => { | ||
| 449 | if (frag[0] != @enumToInt(HandshakeType.server_hello)) { | ||
| 450 | return error.TlsUnexpectedMessage; | ||
| 451 | } | ||
| 452 | const length = mem.readIntBig(u24, frag[1..4]); | ||
| 453 | if (4 + length != frag.len) return error.TlsBadLength; | ||
| 454 | const hello = frag[4..]; | ||
| 455 | const legacy_version = mem.readIntBig(u16, hello[0..2]); | ||
| 456 | const random = hello[2..34].*; | ||
| 457 | if (mem.eql(u8, &random, &hello_retry_request_sequence)) { | ||
| 458 | @panic("TODO handle HelloRetryRequest"); | ||
| 459 | } | ||
| 460 | const legacy_session_id_echo_len = hello[34]; | ||
| 461 | if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter; | ||
| 462 | const cipher_suite_int = mem.readIntBig(u16, hello[35..37]); | ||
| 463 | const cipher_suite_tag = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch | ||
| 464 | return error.TlsIllegalParameter; | ||
| 465 | std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite_tag)}); | ||
| 466 | const legacy_compression_method = hello[37]; | ||
| 467 | _ = legacy_compression_method; | ||
| 468 | const extensions_size = mem.readIntBig(u16, hello[38..40]); | ||
| 469 | if (40 + extensions_size != hello.len) return error.TlsBadLength; | ||
| 470 | var i: usize = 40; | ||
| 471 | var supported_version: u16 = 0; | ||
| 472 | var opt_x25519_server_pub_key: ?*[32]u8 = null; | ||
| 473 | while (i < hello.len) { | ||
| 474 | const et = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 475 | i += 2; | ||
| 476 | const ext_size = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 477 | i += 2; | ||
| 478 | const next_i = i + ext_size; | ||
| 479 | if (next_i > hello.len) return error.TlsBadLength; | ||
| 480 | switch (et) { | ||
| 481 | @enumToInt(ExtensionType.supported_versions) => { | ||
| 482 | if (supported_version != 0) return error.TlsIllegalParameter; | ||
| 483 | supported_version = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 484 | }, | ||
| 485 | @enumToInt(ExtensionType.key_share) => { | ||
| 486 | if (opt_x25519_server_pub_key != null) return error.TlsIllegalParameter; | ||
| 487 | const named_group = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 488 | i += 2; | ||
| 489 | switch (named_group) { | ||
| 490 | @enumToInt(NamedGroup.x25519) => { | ||
| 491 | const key_size = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 492 | i += 2; | ||
| 493 | if (key_size != 32) return error.TlsBadLength; | ||
| 494 | opt_x25519_server_pub_key = hello[i..][0..32]; | ||
| 495 | }, | ||
| 496 | else => { | ||
| 497 | std.debug.print("named group: {x}\n", .{named_group}); | ||
| 498 | return error.TlsIllegalParameter; | ||
| 499 | }, | ||
| 500 | } | ||
| 501 | }, | ||
| 502 | else => { | ||
| 503 | std.debug.print("unexpected extension: {x}\n", .{et}); | ||
| 504 | }, | ||
| 505 | } | ||
| 506 | i = next_i; | ||
| 507 | } | ||
| 508 | const x25519_server_pub_key = opt_x25519_server_pub_key orelse | ||
| 509 | return error.TlsIllegalParameter; | ||
| 510 | const tls_version = if (supported_version == 0) legacy_version else supported_version; | ||
| 511 | switch (tls_version) { | ||
| 512 | @enumToInt(ProtocolVersion.tls_1_2) => { | ||
| 513 | std.debug.print("server wants TLS v1.2\n", .{}); | ||
| 514 | }, | ||
| 515 | @enumToInt(ProtocolVersion.tls_1_3) => { | ||
| 516 | std.debug.print("server wants TLS v1.3\n", .{}); | ||
| 517 | }, | ||
| 518 | else => return error.TlsIllegalParameter, | ||
| 519 | } | ||
| 520 | |||
| 521 | const shared_key = crypto.dh.X25519.scalarmult( | ||
| 522 | x25519_priv_key, | ||
| 523 | x25519_server_pub_key.*, | ||
| 524 | ) catch return error.TlsDecryptFailure; | ||
| 525 | |||
| 526 | switch (cipher_suite_tag) { | ||
| 527 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |tag| { | ||
| 528 | const P = std.meta.TagPayload(CipherParams, tag); | ||
| 529 | cipher_params = @unionInit(CipherParams, @tagName(tag), .{ | ||
| 530 | .handshake_secret = undefined, | ||
| 531 | .master_secret = undefined, | ||
| 532 | .client_handshake_key = undefined, | ||
| 533 | .server_handshake_key = undefined, | ||
| 534 | .client_finished_key = undefined, | ||
| 535 | .server_finished_key = undefined, | ||
| 536 | .client_handshake_iv = undefined, | ||
| 537 | .server_handshake_iv = undefined, | ||
| 538 | .transcript_hash = P.Hash.init(.{}), | ||
| 539 | }); | ||
| 540 | const p = &@field(cipher_params, @tagName(tag)); | ||
| 541 | p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1 | ||
| 542 | p.transcript_hash.update(host); // Client Hello part 2 | ||
| 543 | p.transcript_hash.update(frag); // Server Hello | ||
| 544 | const hello_hash = p.transcript_hash.peek(); | ||
| 545 | const zeroes = [1]u8{0} ** P.Hash.digest_length; | ||
| 546 | const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes); | ||
| 547 | const empty_hash = emptyHash(P.Hash); | ||
| 548 | const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length); | ||
| 549 | p.handshake_secret = P.Hkdf.extract(&hs_derived_secret, &shared_key); | ||
| 550 | const ap_derived_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "derived", &empty_hash, P.Hash.digest_length); | ||
| 551 | p.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); | ||
| 552 | const client_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); | ||
| 553 | const server_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length); | ||
| 554 | p.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length); | ||
| 555 | p.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length); | ||
| 556 | p.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length); | ||
| 557 | p.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length); | ||
| 558 | p.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length); | ||
| 559 | p.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length); | ||
| 560 | //std.debug.print("shared_key: {}\nhello_hash: {}\nearly_secret: {}\nempty_hash: {}\nderived_secret: {}\nhandshake_secret: {}\n client_secret: {}\n server_secret: {}\nclient_handshake_iv: {}\nserver_handshake_iv: {}\n", .{ | ||
| 561 | // std.fmt.fmtSliceHexLower(&shared_key), | ||
| 562 | // std.fmt.fmtSliceHexLower(&hello_hash), | ||
| 563 | // std.fmt.fmtSliceHexLower(&early_secret), | ||
| 564 | // std.fmt.fmtSliceHexLower(&empty_hash), | ||
| 565 | // std.fmt.fmtSliceHexLower(&hs_derived_secret), | ||
| 566 | // std.fmt.fmtSliceHexLower(&p.handshake_secret), | ||
| 567 | // std.fmt.fmtSliceHexLower(&client_secret), | ||
| 568 | // std.fmt.fmtSliceHexLower(&server_secret), | ||
| 569 | // std.fmt.fmtSliceHexLower(&p.client_handshake_iv), | ||
| 570 | // std.fmt.fmtSliceHexLower(&p.server_handshake_iv), | ||
| 571 | //}); | ||
| 572 | }, | ||
| 573 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 574 | @panic("TODO"); | ||
| 575 | }, | ||
| 576 | .TLS_AES_128_CCM_SHA256 => { | ||
| 577 | @panic("TODO"); | ||
| 578 | }, | ||
| 579 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 580 | @panic("TODO"); | ||
| 581 | }, | ||
| 582 | } | ||
| 583 | }, | ||
| 584 | else => return error.TlsUnexpectedMessage, | ||
| 585 | } | ||
| 586 | break :i end; | ||
| 587 | }; | ||
| 588 | |||
| 589 | var read_seq: u64 = 0; | ||
| 590 | |||
| 591 | while (true) { | ||
| 592 | const end_hdr = i + 5; | ||
| 593 | if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 594 | if (end_hdr > len) { | ||
| 595 | len += try stream.readAtLeast(handshake_buf[len..], end_hdr - len); | ||
| 596 | if (end_hdr > len) return error.EndOfStream; | ||
| 597 | } | ||
| 598 | const ct = @intToEnum(ContentType, handshake_buf[i]); | ||
| 599 | i += 1; | ||
| 600 | const legacy_version = mem.readIntBig(u16, handshake_buf[i..][0..2]); | ||
| 601 | i += 2; | ||
| 602 | _ = legacy_version; | ||
| 603 | const record_size = mem.readIntBig(u16, handshake_buf[i..][0..2]); | ||
| 604 | i += 2; | ||
| 605 | const end = i + record_size; | ||
| 606 | if (end > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 607 | if (end > len) { | ||
| 608 | len += try stream.readAtLeast(handshake_buf[len..], end - len); | ||
| 609 | if (end > len) return error.EndOfStream; | ||
| 610 | } | ||
| 611 | switch (ct) { | ||
| 612 | .change_cipher_spec => { | ||
| 613 | if (record_size != 1) return error.TlsUnexpectedMessage; | ||
| 614 | if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage; | ||
| 615 | }, | ||
| 616 | .application_data => { | ||
| 617 | var cleartext_buf: [8000]u8 = undefined; | ||
| 618 | const cleartext = switch (cipher_params) { | ||
| 619 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { | ||
| 620 | const P = @TypeOf(p.*); | ||
| 621 | const ciphertext_len = record_size - P.AEAD.tag_length; | ||
| 622 | const ciphertext = handshake_buf[i..][0..ciphertext_len]; | ||
| 623 | i += ciphertext.len; | ||
| 624 | if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow; | ||
| 625 | const cleartext = cleartext_buf[0..ciphertext.len]; | ||
| 626 | const auth_tag = handshake_buf[i..][0..P.AEAD.tag_length].*; | ||
| 627 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 628 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 629 | const operand: V = pad ++ @bitCast([8]u8, big(read_seq)); | ||
| 630 | read_seq += 1; | ||
| 631 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_handshake_iv) ^ operand; | ||
| 632 | const ad = handshake_buf[end_hdr - 5 ..][0..5]; | ||
| 633 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_handshake_key) catch | ||
| 634 | return error.TlsBadRecordMac; | ||
| 635 | p.transcript_hash.update(cleartext[0 .. cleartext.len - 1]); | ||
| 636 | break :c cleartext; | ||
| 637 | }, | ||
| 638 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 639 | @panic("TODO"); | ||
| 640 | }, | ||
| 641 | .TLS_AES_128_CCM_SHA256 => { | ||
| 642 | @panic("TODO"); | ||
| 643 | }, | ||
| 644 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 645 | @panic("TODO"); | ||
| 646 | }, | ||
| 647 | }; | ||
| 648 | |||
| 649 | const inner_ct = @intToEnum(ContentType, cleartext[cleartext.len - 1]); | ||
| 650 | switch (inner_ct) { | ||
| 651 | .handshake => { | ||
| 652 | var ct_i: usize = 0; | ||
| 653 | while (true) { | ||
| 654 | const handshake_type = cleartext[ct_i]; | ||
| 655 | ct_i += 1; | ||
| 656 | const handshake_len = mem.readIntBig(u24, cleartext[ct_i..][0..3]); | ||
| 657 | ct_i += 3; | ||
| 658 | const next_handshake_i = ct_i + handshake_len; | ||
| 659 | if (next_handshake_i > cleartext.len - 1) | ||
| 660 | return error.TlsBadLength; | ||
| 661 | switch (handshake_type) { | ||
| 662 | @enumToInt(HandshakeType.encrypted_extensions) => { | ||
| 663 | const total_ext_size = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 664 | ct_i += 2; | ||
| 665 | const end_ext_i = ct_i + total_ext_size; | ||
| 666 | while (ct_i < end_ext_i) { | ||
| 667 | const et = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 668 | ct_i += 2; | ||
| 669 | const ext_size = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 670 | ct_i += 2; | ||
| 671 | const next_ext_i = ct_i + ext_size; | ||
| 672 | switch (et) { | ||
| 673 | @enumToInt(ExtensionType.server_name) => {}, | ||
| 674 | else => { | ||
| 675 | std.debug.print("encrypted extension: {any}\n", .{ | ||
| 676 | et, | ||
| 677 | }); | ||
| 678 | }, | ||
| 679 | } | ||
| 680 | ct_i = next_ext_i; | ||
| 681 | } | ||
| 682 | }, | ||
| 683 | @enumToInt(HandshakeType.certificate) => { | ||
| 684 | std.debug.print("cool certificate bro\n", .{}); | ||
| 685 | }, | ||
| 686 | @enumToInt(HandshakeType.certificate_verify) => { | ||
| 687 | std.debug.print("the certificate came with a fancy signature\n", .{}); | ||
| 688 | }, | ||
| 689 | @enumToInt(HandshakeType.finished) => { | ||
| 690 | // This message is to trick buggy proxies into behaving correctly. | ||
| 691 | const client_change_cipher_spec_msg = [_]u8{ | ||
| 692 | @enumToInt(ContentType.change_cipher_spec), | ||
| 693 | 0x03, 0x03, // legacy protocol version | ||
| 694 | 0x00, 0x01, // length | ||
| 695 | 0x01, | ||
| 696 | }; | ||
| 697 | const app_cipher = switch (cipher_params) { | ||
| 698 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p, tag| c: { | ||
| 699 | const P = @TypeOf(p.*); | ||
| 700 | // TODO verify the server's data | ||
| 701 | const handshake_hash = p.transcript_hash.finalResult(); | ||
| 702 | const verify_data = hmac(P.Hmac, &handshake_hash, p.client_finished_key); | ||
| 703 | const out_cleartext = [_]u8{ | ||
| 704 | @enumToInt(HandshakeType.finished), | ||
| 705 | 0, 0, verify_data.len, // length | ||
| 706 | } ++ verify_data ++ [1]u8{@enumToInt(ContentType.handshake)}; | ||
| 707 | |||
| 708 | const wrapped_len = out_cleartext.len + P.AEAD.tag_length; | ||
| 709 | |||
| 710 | var finished_msg = [_]u8{ | ||
| 711 | @enumToInt(ContentType.application_data), | ||
| 712 | 0x03, 0x03, // legacy protocol version | ||
| 713 | 0, wrapped_len, // byte length of encrypted record | ||
| 714 | } ++ ([1]u8{undefined} ** wrapped_len); | ||
| 715 | |||
| 716 | const ad = finished_msg[0..5]; | ||
| 717 | const ciphertext = finished_msg[5..][0..out_cleartext.len]; | ||
| 718 | const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..]; | ||
| 719 | const nonce = p.client_handshake_iv; | ||
| 720 | P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); | ||
| 721 | |||
| 722 | const both_msgs = client_change_cipher_spec_msg ++ finished_msg; | ||
| 723 | try stream.writeAll(&both_msgs); | ||
| 724 | |||
| 725 | const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); | ||
| 726 | const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); | ||
| 727 | //std.debug.print("master_secret={}\nclient_secret={}\nserver_secret={}\n", .{ | ||
| 728 | // std.fmt.fmtSliceHexLower(&p.master_secret), | ||
| 729 | // std.fmt.fmtSliceHexLower(&client_secret), | ||
| 730 | // std.fmt.fmtSliceHexLower(&server_secret), | ||
| 731 | //}); | ||
| 732 | break :c @unionInit(ApplicationCipher, @tagName(tag), .{ | ||
| 733 | .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), | ||
| 734 | .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), | ||
| 735 | .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length), | ||
| 736 | .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length), | ||
| 737 | }); | ||
| 738 | }, | ||
| 739 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 740 | @panic("TODO"); | ||
| 741 | }, | ||
| 742 | .TLS_AES_128_CCM_SHA256 => { | ||
| 743 | @panic("TODO"); | ||
| 744 | }, | ||
| 745 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 746 | @panic("TODO"); | ||
| 747 | }, | ||
| 748 | }; | ||
| 749 | std.debug.print("remaining bytes: {d}\n", .{len - end}); | ||
| 750 | return .{ | ||
| 751 | .application_cipher = app_cipher, | ||
| 752 | .read_seq = 0, | ||
| 753 | .write_seq = 0, | ||
| 754 | .partially_read_buffer = undefined, | ||
| 755 | .partially_read_len = 0, | ||
| 756 | .eof = false, | ||
| 757 | }; | ||
| 758 | }, | ||
| 759 | else => { | ||
| 760 | std.debug.print("handshake type: {d}\n", .{cleartext[0]}); | ||
| 761 | return error.TlsUnexpectedMessage; | ||
| 762 | }, | ||
| 763 | } | ||
| 764 | ct_i = next_handshake_i; | ||
| 765 | if (ct_i >= cleartext.len - 1) break; | ||
| 766 | } | ||
| 767 | }, | ||
| 768 | else => { | ||
| 769 | std.debug.print("inner content type: {any}\n", .{inner_ct}); | ||
| 770 | return error.TlsUnexpectedMessage; | ||
| 771 | }, | ||
| 772 | } | ||
| 773 | }, | ||
| 774 | else => { | ||
| 775 | std.debug.print("content type: {s}\n", .{@tagName(ct)}); | ||
| 776 | return error.TlsUnexpectedMessage; | ||
| 777 | }, | ||
| 778 | } | ||
| 779 | i = end; | ||
| 780 | } | ||
| 781 | |||
| 782 | return error.TlsHandshakeFailure; | ||
| 783 | } | ||
| 784 | |||
| 785 | pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { | ||
| 786 | var ciphertext_buf: [max_ciphertext_record_len * 4]u8 = undefined; | ||
| 787 | // Due to the trailing inner content type byte in the ciphertext, we need | ||
| 788 | // an additional buffer for storing the cleartext into before encrypting. | ||
| 789 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; | ||
| 790 | var iovecs_buf: [5]std.os.iovec_const = undefined; | ||
| 791 | var ciphertext_end: usize = 0; | ||
| 792 | var iovec_end: usize = 0; | ||
| 793 | var bytes_i: usize = 0; | ||
| 794 | // How many bytes are taken up by overhead per record. | ||
| 795 | const overhead_len: usize = switch (tls.application_cipher) { | ||
| 796 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| l: { | ||
| 797 | const P = @TypeOf(p.*); | ||
| 798 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 799 | const overhead_len = ciphertext_record_header_len + P.AEAD.tag_length + 1; | ||
| 800 | while (true) { | ||
| 801 | const encrypted_content_len = @intCast(u16, @min( | ||
| 802 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), | ||
| 803 | ciphertext_buf.len - | ||
| 804 | ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, | ||
| 805 | )); | ||
| 806 | if (encrypted_content_len == 0) break :l overhead_len; | ||
| 807 | |||
| 808 | mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]); | ||
| 809 | cleartext_buf[encrypted_content_len] = @enumToInt(ContentType.application_data); | ||
| 810 | bytes_i += encrypted_content_len; | ||
| 811 | const ciphertext_len = encrypted_content_len + 1; | ||
| 812 | const cleartext = cleartext_buf[0..ciphertext_len]; | ||
| 813 | |||
| 814 | const record_start = ciphertext_end; | ||
| 815 | const ad = ciphertext_buf[ciphertext_end..][0..5]; | ||
| 816 | ad.* = | ||
| 817 | [_]u8{@enumToInt(ContentType.application_data)} ++ | ||
| 818 | int2(@enumToInt(ProtocolVersion.tls_1_2)) ++ | ||
| 819 | int2(ciphertext_len + P.AEAD.tag_length); | ||
| 820 | ciphertext_end += ad.len; | ||
| 821 | const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len]; | ||
| 822 | ciphertext_end += ciphertext_len; | ||
| 823 | const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length]; | ||
| 824 | ciphertext_end += auth_tag.len; | ||
| 825 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 826 | const operand: V = pad ++ @bitCast([8]u8, big(tls.write_seq)); | ||
| 827 | tls.write_seq += 1; | ||
| 828 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.client_iv) ^ operand; | ||
| 829 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); | ||
| 830 | //std.debug.print("seq: {d} nonce: {} client_key: {} client_iv: {} ad: {} auth_tag: {}\nserver_key: {} server_iv: {}\n", .{ | ||
| 831 | // tls.write_seq - 1, | ||
| 832 | // std.fmt.fmtSliceHexLower(&nonce), | ||
| 833 | // std.fmt.fmtSliceHexLower(&p.client_key), | ||
| 834 | // std.fmt.fmtSliceHexLower(&p.client_iv), | ||
| 835 | // std.fmt.fmtSliceHexLower(ad), | ||
| 836 | // std.fmt.fmtSliceHexLower(auth_tag), | ||
| 837 | // std.fmt.fmtSliceHexLower(&p.server_key), | ||
| 838 | // std.fmt.fmtSliceHexLower(&p.server_iv), | ||
| 839 | //}); | ||
| 840 | |||
| 841 | const record = ciphertext_buf[record_start..ciphertext_end]; | ||
| 842 | iovecs_buf[iovec_end] = .{ | ||
| 843 | .iov_base = record.ptr, | ||
| 844 | .iov_len = record.len, | ||
| 845 | }; | ||
| 846 | iovec_end += 1; | ||
| 847 | } | ||
| 848 | }, | ||
| 849 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 850 | @panic("TODO"); | ||
| 851 | }, | ||
| 852 | .TLS_AES_128_CCM_SHA256 => { | ||
| 853 | @panic("TODO"); | ||
| 854 | }, | ||
| 855 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 856 | @panic("TODO"); | ||
| 857 | }, | ||
| 858 | }; | ||
| 859 | |||
| 860 | // Ideally we would call writev exactly once here, however, we must ensure | ||
| 861 | // that we don't return with a record partially written. | ||
| 862 | var i: usize = 0; | ||
| 863 | var total_amt: usize = 0; | ||
| 864 | while (true) { | ||
| 865 | var amt = try stream.writev(iovecs_buf[i..iovec_end]); | ||
| 866 | while (amt >= iovecs_buf[i].iov_len) { | ||
| 867 | const encrypted_amt = iovecs_buf[i].iov_len; | ||
| 868 | total_amt += encrypted_amt - overhead_len; | ||
| 869 | amt -= encrypted_amt; | ||
| 870 | i += 1; | ||
| 871 | // Rely on the property that iovecs delineate records, meaning that | ||
| 872 | // if amt equals zero here, we have fortunately found ourselves | ||
| 873 | // with a short read that aligns at the record boundary. | ||
| 874 | if (i >= iovec_end or amt == 0) return total_amt; | ||
| 875 | } | ||
| 876 | iovecs_buf[i].iov_base += amt; | ||
| 877 | iovecs_buf[i].iov_len -= amt; | ||
| 878 | } | ||
| 879 | } | ||
| 880 | |||
| 881 | pub fn writeAll(tls: *Tls, stream: net.Stream, bytes: []const u8) !void { | ||
| 882 | var index: usize = 0; | ||
| 883 | while (index < bytes.len) { | ||
| 884 | index += try tls.write(stream, bytes[index..]); | ||
| 885 | } | ||
| 886 | } | ||
| 887 | |||
| 888 | /// Returns number of bytes that have been read, which are now populated inside | ||
| 889 | /// `buffer`. A return value of zero bytes does not necessarily mean end of | ||
| 890 | /// stream. | ||
| 891 | pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { | ||
| 892 | const prev_len = tls.partially_read_len; | ||
| 893 | var in_buf: [max_ciphertext_len * 4]u8 = undefined; | ||
| 894 | mem.copy(u8, &in_buf, tls.partially_read_buffer[0..prev_len]); | ||
| 895 | |||
| 896 | // Capacity of output buffer, in records, rounded up. | ||
| 897 | const buf_cap = (buffer.len +| (max_ciphertext_len - 1)) / max_ciphertext_len; | ||
| 898 | const wanted_read_len = buf_cap * (max_ciphertext_len + ciphertext_record_header_len); | ||
| 899 | const ask_slice = in_buf[prev_len..@min(wanted_read_len, in_buf.len)]; | ||
| 900 | const actual_read_len = try stream.read(ask_slice); | ||
| 901 | const frag = in_buf[0 .. prev_len + actual_read_len]; | ||
| 902 | if (frag.len == 0) { | ||
| 903 | tls.eof = true; | ||
| 904 | return 0; | ||
| 905 | } | ||
| 906 | var in: usize = 0; | ||
| 907 | var out: usize = 0; | ||
| 908 | |||
| 909 | while (true) { | ||
| 910 | if (in + ciphertext_record_header_len > frag.len) { | ||
| 911 | return finishRead(tls, frag, in, out); | ||
| 912 | } | ||
| 913 | const ct = @intToEnum(ContentType, frag[in]); | ||
| 914 | in += 1; | ||
| 915 | const legacy_version = mem.readIntBig(u16, frag[in..][0..2]); | ||
| 916 | in += 2; | ||
| 917 | _ = legacy_version; | ||
| 918 | const record_size = mem.readIntBig(u16, frag[in..][0..2]); | ||
| 919 | in += 2; | ||
| 920 | const end = in + record_size; | ||
| 921 | if (end > frag.len) { | ||
| 922 | if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; | ||
| 923 | return finishRead(tls, frag, in, out); | ||
| 924 | } | ||
| 925 | switch (ct) { | ||
| 926 | .alert => { | ||
| 927 | @panic("TODO handle an alert here"); | ||
| 928 | }, | ||
| 929 | .application_data => { | ||
| 930 | const cleartext_len = switch (tls.application_cipher) { | ||
| 931 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { | ||
| 932 | const P = @TypeOf(p.*); | ||
| 933 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 934 | const ad = frag[in - 5 ..][0..5]; | ||
| 935 | const ciphertext_len = record_size - P.AEAD.tag_length; | ||
| 936 | const ciphertext = frag[in..][0..ciphertext_len]; | ||
| 937 | in += ciphertext_len; | ||
| 938 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; | ||
| 939 | const cleartext = buffer[out..][0..ciphertext_len]; | ||
| 940 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 941 | const operand: V = pad ++ @bitCast([8]u8, big(tls.read_seq)); | ||
| 942 | tls.read_seq += 1; | ||
| 943 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; | ||
| 944 | //std.debug.print("seq: {d} nonce: {} server_key: {} server_iv: {}\n", .{ | ||
| 945 | // tls.read_seq - 1, | ||
| 946 | // std.fmt.fmtSliceHexLower(&nonce), | ||
| 947 | // std.fmt.fmtSliceHexLower(&p.server_key), | ||
| 948 | // std.fmt.fmtSliceHexLower(&p.server_iv), | ||
| 949 | //}); | ||
| 950 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch | ||
| 951 | return error.TlsBadRecordMac; | ||
| 952 | break :c cleartext.len; | ||
| 953 | }, | ||
| 954 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 955 | @panic("TODO"); | ||
| 956 | }, | ||
| 957 | .TLS_AES_128_CCM_SHA256 => { | ||
| 958 | @panic("TODO"); | ||
| 959 | }, | ||
| 960 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 961 | @panic("TODO"); | ||
| 962 | }, | ||
| 963 | }; | ||
| 964 | |||
| 965 | const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]); | ||
| 966 | switch (inner_ct) { | ||
| 967 | .alert => { | ||
| 968 | const level = @intToEnum(AlertLevel, buffer[out]); | ||
| 969 | const desc = @intToEnum(AlertDescription, buffer[out + 1]); | ||
| 970 | if (desc == .close_notify) { | ||
| 971 | tls.eof = true; | ||
| 972 | return out; | ||
| 973 | } | ||
| 974 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | ||
| 975 | return error.TlsAlert; | ||
| 976 | }, | ||
| 977 | .handshake => { | ||
| 978 | std.debug.print("the server wants to keep shaking hands\n", .{}); | ||
| 979 | }, | ||
| 980 | .application_data => { | ||
| 981 | out += cleartext_len - 1; | ||
| 982 | }, | ||
| 983 | else => { | ||
| 984 | std.debug.print("inner content type: {d}\n", .{inner_ct}); | ||
| 985 | return error.TlsUnexpectedMessage; | ||
| 986 | }, | ||
| 987 | } | ||
| 988 | }, | ||
| 989 | else => { | ||
| 990 | std.debug.print("unexpected ct: {any}\n", .{ct}); | ||
| 991 | return error.TlsUnexpectedMessage; | ||
| 992 | }, | ||
| 993 | } | ||
| 994 | in = end; | ||
| 995 | } | ||
| 996 | } | ||
| 997 | |||
| 998 | fn finishRead(tls: *Tls, frag: []const u8, in: usize, out: usize) usize { | ||
| 999 | const saved_buf = frag[in..]; | ||
| 1000 | mem.copy(u8, &tls.partially_read_buffer, saved_buf); | ||
| 1001 | tls.partially_read_len = @intCast(u15, saved_buf.len); | ||
| 1002 | return out; | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | fn hkdfExpandLabel( | ||
| 1006 | comptime Hkdf: type, | ||
| 1007 | key: [Hkdf.prk_length]u8, | ||
| 1008 | label: []const u8, | ||
| 1009 | context: []const u8, | ||
| 1010 | comptime len: usize, | ||
| 1011 | ) [len]u8 { | ||
| 1012 | const max_label_len = 255; | ||
| 1013 | const max_context_len = 255; | ||
| 1014 | const tls13 = "tls13 "; | ||
| 1015 | var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined; | ||
| 1016 | mem.writeIntBig(u16, buf[0..2], len); | ||
| 1017 | buf[2] = @intCast(u8, tls13.len + label.len); | ||
| 1018 | buf[3..][0..tls13.len].* = tls13.*; | ||
| 1019 | var i: usize = 3 + tls13.len; | ||
| 1020 | mem.copy(u8, buf[i..], label); | ||
| 1021 | i += label.len; | ||
| 1022 | buf[i] = @intCast(u8, context.len); | ||
| 1023 | i += 1; | ||
| 1024 | mem.copy(u8, buf[i..], context); | ||
| 1025 | i += context.len; | ||
| 1026 | |||
| 1027 | var result: [len]u8 = undefined; | ||
| 1028 | Hkdf.expand(&result, buf[0..i], key); | ||
| 1029 | return result; | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 { | ||
| 1033 | var result: [Hash.digest_length]u8 = undefined; | ||
| 1034 | Hash.hash(&.{}, &result, .{}); | ||
| 1035 | return result; | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 { | ||
| 1039 | var result: [Hmac.mac_length]u8 = undefined; | ||
| 1040 | Hmac.create(&result, message, &key); | ||
| 1041 | return result; | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | const builtin = @import("builtin"); | ||
| 1045 | const native_endian = builtin.cpu.arch.endian(); | ||
| 1046 | |||
| 1047 | inline fn big(x: anytype) @TypeOf(x) { | ||
| 1048 | return switch (native_endian) { | ||
| 1049 | .Big => x, | ||
| 1050 | .Little => @byteSwap(x), | ||
| 1051 | }; | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | inline fn int2(x: u16) [2]u8 { | ||
| 1055 | return .{ | ||
| 1056 | @truncate(u8, x >> 8), | ||
| 1057 | @truncate(u8, x), | ||
| 1058 | }; | ||
| 1059 | } | ||
lib/std/crypto/tls.zig created+325| ... | @@ -0,0 +1,325 @@ | ||
| 1 | //! Plaintext: | ||
| 2 | //! * type: ContentType | ||
| 3 | //! * legacy_record_version: u16 = 0x0303, | ||
| 4 | //! * length: u16, | ||
| 5 | //! - The length (in bytes) of the following TLSPlaintext.fragment. The | ||
| 6 | //! length MUST NOT exceed 2^14 bytes. | ||
| 7 | //! * fragment: opaque | ||
| 8 | //! - the data being transmitted | ||
| 9 | //! | ||
| 10 | //! Ciphertext | ||
| 11 | //! * ContentType opaque_type = application_data; /* 23 */ | ||
| 12 | //! * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ | ||
| 13 | //! * uint16 length; | ||
| 14 | //! * opaque encrypted_record[TLSCiphertext.length]; | ||
| 15 | //! | ||
| 16 | //! Handshake: | ||
| 17 | //! * type: HandshakeType | ||
| 18 | //! * length: u24 | ||
| 19 | //! * data: opaque | ||
| 20 | //! | ||
| 21 | //! ServerHello: | ||
| 22 | //! * ProtocolVersion legacy_version = 0x0303; | ||
| 23 | //! * Random random; | ||
| 24 | //! * opaque legacy_session_id_echo<0..32>; | ||
| 25 | //! * CipherSuite cipher_suite; | ||
| 26 | //! * uint8 legacy_compression_method = 0; | ||
| 27 | //! * Extension extensions<6..2^16-1>; | ||
| 28 | //! | ||
| 29 | //! Extension: | ||
| 30 | //! * ExtensionType extension_type; | ||
| 31 | //! * opaque extension_data<0..2^16-1>; | ||
| 32 | |||
| 33 | const std = @import("../std.zig"); | ||
| 34 | const Tls = @This(); | ||
| 35 | const net = std.net; | ||
| 36 | const mem = std.mem; | ||
| 37 | const crypto = std.crypto; | ||
| 38 | const assert = std.debug.assert; | ||
| 39 | |||
| 40 | pub const Client = @import("tls/Client.zig"); | ||
| 41 | |||
| 42 | pub const ciphertext_record_header_len = 5; | ||
| 43 | pub const max_ciphertext_len = (1 << 14) + 256; | ||
| 44 | pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len; | ||
| 45 | pub const hello_retry_request_sequence = [32]u8{ | ||
| 46 | 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, | ||
| 47 | 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C, | ||
| 48 | }; | ||
| 49 | |||
| 50 | pub const ProtocolVersion = enum(u16) { | ||
| 51 | tls_1_2 = 0x0303, | ||
| 52 | tls_1_3 = 0x0304, | ||
| 53 | _, | ||
| 54 | }; | ||
| 55 | |||
| 56 | pub const ContentType = enum(u8) { | ||
| 57 | invalid = 0, | ||
| 58 | change_cipher_spec = 20, | ||
| 59 | alert = 21, | ||
| 60 | handshake = 22, | ||
| 61 | application_data = 23, | ||
| 62 | _, | ||
| 63 | }; | ||
| 64 | |||
| 65 | pub const HandshakeType = enum(u8) { | ||
| 66 | client_hello = 1, | ||
| 67 | server_hello = 2, | ||
| 68 | new_session_ticket = 4, | ||
| 69 | end_of_early_data = 5, | ||
| 70 | encrypted_extensions = 8, | ||
| 71 | certificate = 11, | ||
| 72 | certificate_request = 13, | ||
| 73 | certificate_verify = 15, | ||
| 74 | finished = 20, | ||
| 75 | key_update = 24, | ||
| 76 | message_hash = 254, | ||
| 77 | }; | ||
| 78 | |||
| 79 | pub const ExtensionType = enum(u16) { | ||
| 80 | /// RFC 6066 | ||
| 81 | server_name = 0, | ||
| 82 | /// RFC 6066 | ||
| 83 | max_fragment_length = 1, | ||
| 84 | /// RFC 6066 | ||
| 85 | status_request = 5, | ||
| 86 | /// RFC 8422, 7919 | ||
| 87 | supported_groups = 10, | ||
| 88 | /// RFC 8446 | ||
| 89 | signature_algorithms = 13, | ||
| 90 | /// RFC 5764 | ||
| 91 | use_srtp = 14, | ||
| 92 | /// RFC 6520 | ||
| 93 | heartbeat = 15, | ||
| 94 | /// RFC 7301 | ||
| 95 | application_layer_protocol_negotiation = 16, | ||
| 96 | /// RFC 6962 | ||
| 97 | signed_certificate_timestamp = 18, | ||
| 98 | /// RFC 7250 | ||
| 99 | client_certificate_type = 19, | ||
| 100 | /// RFC 7250 | ||
| 101 | server_certificate_type = 20, | ||
| 102 | /// RFC 7685 | ||
| 103 | padding = 21, | ||
| 104 | /// RFC 8446 | ||
| 105 | pre_shared_key = 41, | ||
| 106 | /// RFC 8446 | ||
| 107 | early_data = 42, | ||
| 108 | /// RFC 8446 | ||
| 109 | supported_versions = 43, | ||
| 110 | /// RFC 8446 | ||
| 111 | cookie = 44, | ||
| 112 | /// RFC 8446 | ||
| 113 | psk_key_exchange_modes = 45, | ||
| 114 | /// RFC 8446 | ||
| 115 | certificate_authorities = 47, | ||
| 116 | /// RFC 8446 | ||
| 117 | oid_filters = 48, | ||
| 118 | /// RFC 8446 | ||
| 119 | post_handshake_auth = 49, | ||
| 120 | /// RFC 8446 | ||
| 121 | signature_algorithms_cert = 50, | ||
| 122 | /// RFC 8446 | ||
| 123 | key_share = 51, | ||
| 124 | }; | ||
| 125 | |||
| 126 | pub const AlertLevel = enum(u8) { | ||
| 127 | warning = 1, | ||
| 128 | fatal = 2, | ||
| 129 | _, | ||
| 130 | }; | ||
| 131 | |||
| 132 | pub const AlertDescription = enum(u8) { | ||
| 133 | close_notify = 0, | ||
| 134 | unexpected_message = 10, | ||
| 135 | bad_record_mac = 20, | ||
| 136 | record_overflow = 22, | ||
| 137 | handshake_failure = 40, | ||
| 138 | bad_certificate = 42, | ||
| 139 | unsupported_certificate = 43, | ||
| 140 | certificate_revoked = 44, | ||
| 141 | certificate_expired = 45, | ||
| 142 | certificate_unknown = 46, | ||
| 143 | illegal_parameter = 47, | ||
| 144 | unknown_ca = 48, | ||
| 145 | access_denied = 49, | ||
| 146 | decode_error = 50, | ||
| 147 | decrypt_error = 51, | ||
| 148 | protocol_version = 70, | ||
| 149 | insufficient_security = 71, | ||
| 150 | internal_error = 80, | ||
| 151 | inappropriate_fallback = 86, | ||
| 152 | user_canceled = 90, | ||
| 153 | missing_extension = 109, | ||
| 154 | unsupported_extension = 110, | ||
| 155 | unrecognized_name = 112, | ||
| 156 | bad_certificate_status_response = 113, | ||
| 157 | unknown_psk_identity = 115, | ||
| 158 | certificate_required = 116, | ||
| 159 | no_application_protocol = 120, | ||
| 160 | _, | ||
| 161 | }; | ||
| 162 | |||
| 163 | pub const SignatureScheme = enum(u16) { | ||
| 164 | // RSASSA-PKCS1-v1_5 algorithms | ||
| 165 | rsa_pkcs1_sha256 = 0x0401, | ||
| 166 | rsa_pkcs1_sha384 = 0x0501, | ||
| 167 | rsa_pkcs1_sha512 = 0x0601, | ||
| 168 | |||
| 169 | // ECDSA algorithms | ||
| 170 | ecdsa_secp256r1_sha256 = 0x0403, | ||
| 171 | ecdsa_secp384r1_sha384 = 0x0503, | ||
| 172 | ecdsa_secp521r1_sha512 = 0x0603, | ||
| 173 | |||
| 174 | // RSASSA-PSS algorithms with public key OID rsaEncryption | ||
| 175 | rsa_pss_rsae_sha256 = 0x0804, | ||
| 176 | rsa_pss_rsae_sha384 = 0x0805, | ||
| 177 | rsa_pss_rsae_sha512 = 0x0806, | ||
| 178 | |||
| 179 | // EdDSA algorithms | ||
| 180 | ed25519 = 0x0807, | ||
| 181 | ed448 = 0x0808, | ||
| 182 | |||
| 183 | // RSASSA-PSS algorithms with public key OID RSASSA-PSS | ||
| 184 | rsa_pss_pss_sha256 = 0x0809, | ||
| 185 | rsa_pss_pss_sha384 = 0x080a, | ||
| 186 | rsa_pss_pss_sha512 = 0x080b, | ||
| 187 | |||
| 188 | // Legacy algorithms | ||
| 189 | rsa_pkcs1_sha1 = 0x0201, | ||
| 190 | ecdsa_sha1 = 0x0203, | ||
| 191 | |||
| 192 | _, | ||
| 193 | }; | ||
| 194 | |||
| 195 | pub const NamedGroup = enum(u16) { | ||
| 196 | // Elliptic Curve Groups (ECDHE) | ||
| 197 | secp256r1 = 0x0017, | ||
| 198 | secp384r1 = 0x0018, | ||
| 199 | secp521r1 = 0x0019, | ||
| 200 | x25519 = 0x001D, | ||
| 201 | x448 = 0x001E, | ||
| 202 | |||
| 203 | // Finite Field Groups (DHE) | ||
| 204 | ffdhe2048 = 0x0100, | ||
| 205 | ffdhe3072 = 0x0101, | ||
| 206 | ffdhe4096 = 0x0102, | ||
| 207 | ffdhe6144 = 0x0103, | ||
| 208 | ffdhe8192 = 0x0104, | ||
| 209 | |||
| 210 | _, | ||
| 211 | }; | ||
| 212 | |||
| 213 | pub const CipherSuite = enum(u16) { | ||
| 214 | TLS_AES_128_GCM_SHA256 = 0x1301, | ||
| 215 | TLS_AES_256_GCM_SHA384 = 0x1302, | ||
| 216 | TLS_CHACHA20_POLY1305_SHA256 = 0x1303, | ||
| 217 | TLS_AES_128_CCM_SHA256 = 0x1304, | ||
| 218 | TLS_AES_128_CCM_8_SHA256 = 0x1305, | ||
| 219 | }; | ||
| 220 | |||
| 221 | pub const CipherParams = union(CipherSuite) { | ||
| 222 | TLS_AES_128_GCM_SHA256: struct { | ||
| 223 | pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm; | ||
| 224 | pub const Hash = crypto.hash.sha2.Sha256; | ||
| 225 | pub const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 226 | pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 227 | |||
| 228 | handshake_secret: [Hkdf.prk_length]u8, | ||
| 229 | master_secret: [Hkdf.prk_length]u8, | ||
| 230 | client_handshake_key: [AEAD.key_length]u8, | ||
| 231 | server_handshake_key: [AEAD.key_length]u8, | ||
| 232 | client_finished_key: [Hmac.key_length]u8, | ||
| 233 | server_finished_key: [Hmac.key_length]u8, | ||
| 234 | client_handshake_iv: [AEAD.nonce_length]u8, | ||
| 235 | server_handshake_iv: [AEAD.nonce_length]u8, | ||
| 236 | transcript_hash: Hash, | ||
| 237 | }, | ||
| 238 | TLS_AES_256_GCM_SHA384: struct { | ||
| 239 | pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm; | ||
| 240 | pub const Hash = crypto.hash.sha2.Sha384; | ||
| 241 | pub const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 242 | pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 243 | |||
| 244 | handshake_secret: [Hkdf.prk_length]u8, | ||
| 245 | master_secret: [Hkdf.prk_length]u8, | ||
| 246 | client_handshake_key: [AEAD.key_length]u8, | ||
| 247 | server_handshake_key: [AEAD.key_length]u8, | ||
| 248 | client_finished_key: [Hmac.key_length]u8, | ||
| 249 | server_finished_key: [Hmac.key_length]u8, | ||
| 250 | client_handshake_iv: [AEAD.nonce_length]u8, | ||
| 251 | server_handshake_iv: [AEAD.nonce_length]u8, | ||
| 252 | transcript_hash: Hash, | ||
| 253 | }, | ||
| 254 | TLS_CHACHA20_POLY1305_SHA256: void, | ||
| 255 | TLS_AES_128_CCM_SHA256: void, | ||
| 256 | TLS_AES_128_CCM_8_SHA256: void, | ||
| 257 | }; | ||
| 258 | |||
| 259 | /// Encryption parameters for application traffic. | ||
| 260 | pub const ApplicationCipher = union(CipherSuite) { | ||
| 261 | TLS_AES_128_GCM_SHA256: struct { | ||
| 262 | pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm; | ||
| 263 | pub const Hash = crypto.hash.sha2.Sha256; | ||
| 264 | pub const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 265 | pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 266 | |||
| 267 | client_key: [AEAD.key_length]u8, | ||
| 268 | server_key: [AEAD.key_length]u8, | ||
| 269 | client_iv: [AEAD.nonce_length]u8, | ||
| 270 | server_iv: [AEAD.nonce_length]u8, | ||
| 271 | }, | ||
| 272 | TLS_AES_256_GCM_SHA384: struct { | ||
| 273 | pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm; | ||
| 274 | pub const Hash = crypto.hash.sha2.Sha384; | ||
| 275 | pub const Hmac = crypto.auth.hmac.Hmac(Hash); | ||
| 276 | pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac); | ||
| 277 | |||
| 278 | client_key: [AEAD.key_length]u8, | ||
| 279 | server_key: [AEAD.key_length]u8, | ||
| 280 | client_iv: [AEAD.nonce_length]u8, | ||
| 281 | server_iv: [AEAD.nonce_length]u8, | ||
| 282 | }, | ||
| 283 | TLS_CHACHA20_POLY1305_SHA256: void, | ||
| 284 | TLS_AES_128_CCM_SHA256: void, | ||
| 285 | TLS_AES_128_CCM_8_SHA256: void, | ||
| 286 | }; | ||
| 287 | |||
| 288 | pub fn hkdfExpandLabel( | ||
| 289 | comptime Hkdf: type, | ||
| 290 | key: [Hkdf.prk_length]u8, | ||
| 291 | label: []const u8, | ||
| 292 | context: []const u8, | ||
| 293 | comptime len: usize, | ||
| 294 | ) [len]u8 { | ||
| 295 | const max_label_len = 255; | ||
| 296 | const max_context_len = 255; | ||
| 297 | const tls13 = "tls13 "; | ||
| 298 | var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined; | ||
| 299 | mem.writeIntBig(u16, buf[0..2], len); | ||
| 300 | buf[2] = @intCast(u8, tls13.len + label.len); | ||
| 301 | buf[3..][0..tls13.len].* = tls13.*; | ||
| 302 | var i: usize = 3 + tls13.len; | ||
| 303 | mem.copy(u8, buf[i..], label); | ||
| 304 | i += label.len; | ||
| 305 | buf[i] = @intCast(u8, context.len); | ||
| 306 | i += 1; | ||
| 307 | mem.copy(u8, buf[i..], context); | ||
| 308 | i += context.len; | ||
| 309 | |||
| 310 | var result: [len]u8 = undefined; | ||
| 311 | Hkdf.expand(&result, buf[0..i], key); | ||
| 312 | return result; | ||
| 313 | } | ||
| 314 | |||
| 315 | pub fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 { | ||
| 316 | var result: [Hash.digest_length]u8 = undefined; | ||
| 317 | Hash.hash(&.{}, &result, .{}); | ||
| 318 | return result; | ||
| 319 | } | ||
| 320 | |||
| 321 | pub fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 { | ||
| 322 | var result: [Hmac.mac_length]u8 = undefined; | ||
| 323 | Hmac.create(&result, message, &key); | ||
| 324 | return result; | ||
| 325 | } | ||
lib/std/crypto/tls/Client.zig created+751| ... | @@ -0,0 +1,751 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const tls = std.crypto.tls; | ||
| 3 | const Client = @This(); | ||
| 4 | const net = std.net; | ||
| 5 | const mem = std.mem; | ||
| 6 | const crypto = std.crypto; | ||
| 7 | const assert = std.debug.assert; | ||
| 8 | |||
| 9 | const ApplicationCipher = tls.ApplicationCipher; | ||
| 10 | const CipherSuite = tls.CipherSuite; | ||
| 11 | const ContentType = tls.ContentType; | ||
| 12 | const HandshakeType = tls.HandshakeType; | ||
| 13 | const CipherParams = tls.CipherParams; | ||
| 14 | const max_ciphertext_len = tls.max_ciphertext_len; | ||
| 15 | const hkdfExpandLabel = tls.hkdfExpandLabel; | ||
| 16 | |||
| 17 | application_cipher: ApplicationCipher, | ||
| 18 | read_seq: u64, | ||
| 19 | write_seq: u64, | ||
| 20 | /// The size is enough to contain exactly one TLSCiphertext record. | ||
| 21 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, | ||
| 22 | /// The number of partially read bytes inside `partiall_read_buffer`. | ||
| 23 | partially_read_len: u15, | ||
| 24 | eof: bool, | ||
| 25 | |||
| 26 | const cipher_suites = blk: { | ||
| 27 | const fields = @typeInfo(CipherSuite).Enum.fields; | ||
| 28 | var result: [(fields.len + 1) * 2]u8 = undefined; | ||
| 29 | mem.writeIntBig(u16, result[0..2], result.len - 2); | ||
| 30 | for (fields) |field, i| { | ||
| 31 | const int = @enumToInt(@field(CipherSuite, field.name)); | ||
| 32 | result[(i + 1) * 2] = @truncate(u8, int >> 8); | ||
| 33 | result[(i + 1) * 2 + 1] = @truncate(u8, int); | ||
| 34 | } | ||
| 35 | break :blk result; | ||
| 36 | }; | ||
| 37 | |||
| 38 | /// `host` is only borrowed during this function call. | ||
| 39 | pub fn init(stream: net.Stream, host: []const u8) !Client { | ||
| 40 | var x25519_priv_key: [32]u8 = undefined; | ||
| 41 | crypto.random.bytes(&x25519_priv_key); | ||
| 42 | const x25519_pub_key = crypto.dh.X25519.recoverPublicKey(x25519_priv_key) catch |err| { | ||
| 43 | switch (err) { | ||
| 44 | // Only possible to happen if the private key is all zeroes. | ||
| 45 | error.IdentityElement => return error.InsufficientEntropy, | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | |||
| 49 | // random (u32) | ||
| 50 | var rand_buf: [32]u8 = undefined; | ||
| 51 | crypto.random.bytes(&rand_buf); | ||
| 52 | |||
| 53 | const extensions_header = [_]u8{ | ||
| 54 | // Extensions byte length | ||
| 55 | undefined, undefined, | ||
| 56 | |||
| 57 | // Extension: supported_versions (only TLS 1.3) | ||
| 58 | 0, 43, // ExtensionType.supported_versions | ||
| 59 | 0x00, 0x05, // byte length of this extension payload | ||
| 60 | 0x04, // byte length of supported versions | ||
| 61 | 0x03, 0x04, // TLS 1.3 | ||
| 62 | 0x03, 0x03, // TLS 1.2 | ||
| 63 | |||
| 64 | // Extension: signature_algorithms | ||
| 65 | 0, 13, // ExtensionType.signature_algorithms | ||
| 66 | 0x00, 0x22, // byte length of this extension payload | ||
| 67 | 0x00, 0x20, // byte length of signature algorithms list | ||
| 68 | 0x04, 0x01, // rsa_pkcs1_sha256 | ||
| 69 | 0x05, 0x01, // rsa_pkcs1_sha384 | ||
| 70 | 0x06, 0x01, // rsa_pkcs1_sha512 | ||
| 71 | 0x04, 0x03, // ecdsa_secp256r1_sha256 | ||
| 72 | 0x05, 0x03, // ecdsa_secp384r1_sha384 | ||
| 73 | 0x06, 0x03, // ecdsa_secp521r1_sha512 | ||
| 74 | 0x08, 0x04, // rsa_pss_rsae_sha256 | ||
| 75 | 0x08, 0x05, // rsa_pss_rsae_sha384 | ||
| 76 | 0x08, 0x06, // rsa_pss_rsae_sha512 | ||
| 77 | 0x08, 0x07, // ed25519 | ||
| 78 | 0x08, 0x08, // ed448 | ||
| 79 | 0x08, 0x09, // rsa_pss_pss_sha256 | ||
| 80 | 0x08, 0x0a, // rsa_pss_pss_sha384 | ||
| 81 | 0x08, 0x0b, // rsa_pss_pss_sha512 | ||
| 82 | 0x02, 0x01, // rsa_pkcs1_sha1 | ||
| 83 | 0x02, 0x03, // ecdsa_sha1 | ||
| 84 | |||
| 85 | // Extension: supported_groups | ||
| 86 | 0, 10, // ExtensionType.supported_groups | ||
| 87 | 0x00, 0x0c, // byte length of this extension payload | ||
| 88 | 0x00, 0x0a, // byte length of supported groups list | ||
| 89 | 0x00, 0x17, // secp256r1 | ||
| 90 | 0x00, 0x18, // secp384r1 | ||
| 91 | 0x00, 0x19, // secp521r1 | ||
| 92 | 0x00, 0x1D, // x25519 | ||
| 93 | 0x00, 0x1E, // x448 | ||
| 94 | |||
| 95 | // Extension: key_share | ||
| 96 | 0, 51, // ExtensionType.key_share | ||
| 97 | 0, 38, // byte length of this extension payload | ||
| 98 | 0, 36, // byte length of client_shares | ||
| 99 | 0x00, 0x1D, // NamedGroup.x25519 | ||
| 100 | 0, 32, // byte length of key_exchange | ||
| 101 | } ++ x25519_pub_key ++ [_]u8{ | ||
| 102 | |||
| 103 | // Extension: server_name | ||
| 104 | 0, 0, // ExtensionType.server_name | ||
| 105 | undefined, undefined, // byte length of this extension payload | ||
| 106 | undefined, undefined, // server_name_list byte count | ||
| 107 | 0x00, // name_type | ||
| 108 | undefined, undefined, // host name len | ||
| 109 | }; | ||
| 110 | |||
| 111 | var hello_header = [_]u8{ | ||
| 112 | // Plaintext header | ||
| 113 | @enumToInt(ContentType.handshake), | ||
| 114 | 0x03, 0x01, // legacy_record_version | ||
| 115 | undefined, undefined, // Plaintext fragment length (u16) | ||
| 116 | |||
| 117 | // Handshake header | ||
| 118 | @enumToInt(HandshakeType.client_hello), | ||
| 119 | undefined, undefined, undefined, // handshake length (u24) | ||
| 120 | |||
| 121 | // ClientHello | ||
| 122 | 0x03, 0x03, // legacy_version | ||
| 123 | } ++ rand_buf ++ [1]u8{0} ++ cipher_suites ++ [_]u8{ | ||
| 124 | 0x01, 0x00, // legacy_compression_methods | ||
| 125 | } ++ extensions_header; | ||
| 126 | |||
| 127 | mem.writeIntBig(u16, hello_header[3..][0..2], @intCast(u16, hello_header.len - 5 + host.len)); | ||
| 128 | mem.writeIntBig(u24, hello_header[6..][0..3], @intCast(u24, hello_header.len - 9 + host.len)); | ||
| 129 | mem.writeIntBig( | ||
| 130 | u16, | ||
| 131 | hello_header[hello_header.len - extensions_header.len ..][0..2], | ||
| 132 | @intCast(u16, extensions_header.len - 2 + host.len), | ||
| 133 | ); | ||
| 134 | mem.writeIntBig(u16, hello_header[hello_header.len - 7 ..][0..2], @intCast(u16, 5 + host.len)); | ||
| 135 | mem.writeIntBig(u16, hello_header[hello_header.len - 5 ..][0..2], @intCast(u16, 3 + host.len)); | ||
| 136 | mem.writeIntBig(u16, hello_header[hello_header.len - 2 ..][0..2], @intCast(u16, 0 + host.len)); | ||
| 137 | |||
| 138 | { | ||
| 139 | var iovecs = [_]std.os.iovec_const{ | ||
| 140 | .{ | ||
| 141 | .iov_base = &hello_header, | ||
| 142 | .iov_len = hello_header.len, | ||
| 143 | }, | ||
| 144 | .{ | ||
| 145 | .iov_base = host.ptr, | ||
| 146 | .iov_len = host.len, | ||
| 147 | }, | ||
| 148 | }; | ||
| 149 | try stream.writevAll(&iovecs); | ||
| 150 | } | ||
| 151 | |||
| 152 | const client_hello_bytes1 = hello_header[5..]; | ||
| 153 | |||
| 154 | var cipher_params: CipherParams = undefined; | ||
| 155 | |||
| 156 | var handshake_buf: [8000]u8 = undefined; | ||
| 157 | var len: usize = 0; | ||
| 158 | var i: usize = i: { | ||
| 159 | const plaintext = handshake_buf[0..5]; | ||
| 160 | len = try stream.readAtLeast(&handshake_buf, plaintext.len); | ||
| 161 | if (len < plaintext.len) return error.EndOfStream; | ||
| 162 | const ct = @intToEnum(ContentType, plaintext[0]); | ||
| 163 | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); | ||
| 164 | const end = plaintext.len + frag_len; | ||
| 165 | if (end > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 166 | if (end > len) { | ||
| 167 | len += try stream.readAtLeast(handshake_buf[len..], end - len); | ||
| 168 | if (end > len) return error.EndOfStream; | ||
| 169 | } | ||
| 170 | const frag = handshake_buf[plaintext.len..end]; | ||
| 171 | |||
| 172 | switch (ct) { | ||
| 173 | .alert => { | ||
| 174 | const level = @intToEnum(tls.AlertLevel, frag[0]); | ||
| 175 | const desc = @intToEnum(tls.AlertDescription, frag[1]); | ||
| 176 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | ||
| 177 | return error.TlsAlert; | ||
| 178 | }, | ||
| 179 | .handshake => { | ||
| 180 | if (frag[0] != @enumToInt(HandshakeType.server_hello)) { | ||
| 181 | return error.TlsUnexpectedMessage; | ||
| 182 | } | ||
| 183 | const length = mem.readIntBig(u24, frag[1..4]); | ||
| 184 | if (4 + length != frag.len) return error.TlsBadLength; | ||
| 185 | const hello = frag[4..]; | ||
| 186 | const legacy_version = mem.readIntBig(u16, hello[0..2]); | ||
| 187 | const random = hello[2..34].*; | ||
| 188 | if (mem.eql(u8, &random, &tls.hello_retry_request_sequence)) { | ||
| 189 | @panic("TODO handle HelloRetryRequest"); | ||
| 190 | } | ||
| 191 | const legacy_session_id_echo_len = hello[34]; | ||
| 192 | if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter; | ||
| 193 | const cipher_suite_int = mem.readIntBig(u16, hello[35..37]); | ||
| 194 | const cipher_suite_tag = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch | ||
| 195 | return error.TlsIllegalParameter; | ||
| 196 | std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite_tag)}); | ||
| 197 | const legacy_compression_method = hello[37]; | ||
| 198 | _ = legacy_compression_method; | ||
| 199 | const extensions_size = mem.readIntBig(u16, hello[38..40]); | ||
| 200 | if (40 + extensions_size != hello.len) return error.TlsBadLength; | ||
| 201 | var i: usize = 40; | ||
| 202 | var supported_version: u16 = 0; | ||
| 203 | var opt_x25519_server_pub_key: ?*[32]u8 = null; | ||
| 204 | while (i < hello.len) { | ||
| 205 | const et = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 206 | i += 2; | ||
| 207 | const ext_size = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 208 | i += 2; | ||
| 209 | const next_i = i + ext_size; | ||
| 210 | if (next_i > hello.len) return error.TlsBadLength; | ||
| 211 | switch (et) { | ||
| 212 | @enumToInt(tls.ExtensionType.supported_versions) => { | ||
| 213 | if (supported_version != 0) return error.TlsIllegalParameter; | ||
| 214 | supported_version = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 215 | }, | ||
| 216 | @enumToInt(tls.ExtensionType.key_share) => { | ||
| 217 | if (opt_x25519_server_pub_key != null) return error.TlsIllegalParameter; | ||
| 218 | const named_group = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 219 | i += 2; | ||
| 220 | switch (named_group) { | ||
| 221 | @enumToInt(tls.NamedGroup.x25519) => { | ||
| 222 | const key_size = mem.readIntBig(u16, hello[i..][0..2]); | ||
| 223 | i += 2; | ||
| 224 | if (key_size != 32) return error.TlsBadLength; | ||
| 225 | opt_x25519_server_pub_key = hello[i..][0..32]; | ||
| 226 | }, | ||
| 227 | else => { | ||
| 228 | std.debug.print("named group: {x}\n", .{named_group}); | ||
| 229 | return error.TlsIllegalParameter; | ||
| 230 | }, | ||
| 231 | } | ||
| 232 | }, | ||
| 233 | else => { | ||
| 234 | std.debug.print("unexpected extension: {x}\n", .{et}); | ||
| 235 | }, | ||
| 236 | } | ||
| 237 | i = next_i; | ||
| 238 | } | ||
| 239 | const x25519_server_pub_key = opt_x25519_server_pub_key orelse | ||
| 240 | return error.TlsIllegalParameter; | ||
| 241 | const tls_version = if (supported_version == 0) legacy_version else supported_version; | ||
| 242 | switch (tls_version) { | ||
| 243 | @enumToInt(tls.ProtocolVersion.tls_1_2) => { | ||
| 244 | std.debug.print("server wants TLS v1.2\n", .{}); | ||
| 245 | }, | ||
| 246 | @enumToInt(tls.ProtocolVersion.tls_1_3) => { | ||
| 247 | std.debug.print("server wants TLS v1.3\n", .{}); | ||
| 248 | }, | ||
| 249 | else => return error.TlsIllegalParameter, | ||
| 250 | } | ||
| 251 | |||
| 252 | const shared_key = crypto.dh.X25519.scalarmult( | ||
| 253 | x25519_priv_key, | ||
| 254 | x25519_server_pub_key.*, | ||
| 255 | ) catch return error.TlsDecryptFailure; | ||
| 256 | |||
| 257 | switch (cipher_suite_tag) { | ||
| 258 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |tag| { | ||
| 259 | const P = std.meta.TagPayload(CipherParams, tag); | ||
| 260 | cipher_params = @unionInit(CipherParams, @tagName(tag), .{ | ||
| 261 | .handshake_secret = undefined, | ||
| 262 | .master_secret = undefined, | ||
| 263 | .client_handshake_key = undefined, | ||
| 264 | .server_handshake_key = undefined, | ||
| 265 | .client_finished_key = undefined, | ||
| 266 | .server_finished_key = undefined, | ||
| 267 | .client_handshake_iv = undefined, | ||
| 268 | .server_handshake_iv = undefined, | ||
| 269 | .transcript_hash = P.Hash.init(.{}), | ||
| 270 | }); | ||
| 271 | const p = &@field(cipher_params, @tagName(tag)); | ||
| 272 | p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1 | ||
| 273 | p.transcript_hash.update(host); // Client Hello part 2 | ||
| 274 | p.transcript_hash.update(frag); // Server Hello | ||
| 275 | const hello_hash = p.transcript_hash.peek(); | ||
| 276 | const zeroes = [1]u8{0} ** P.Hash.digest_length; | ||
| 277 | const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes); | ||
| 278 | const empty_hash = tls.emptyHash(P.Hash); | ||
| 279 | const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length); | ||
| 280 | p.handshake_secret = P.Hkdf.extract(&hs_derived_secret, &shared_key); | ||
| 281 | const ap_derived_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "derived", &empty_hash, P.Hash.digest_length); | ||
| 282 | p.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); | ||
| 283 | const client_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); | ||
| 284 | const server_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length); | ||
| 285 | p.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length); | ||
| 286 | p.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length); | ||
| 287 | p.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length); | ||
| 288 | p.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length); | ||
| 289 | p.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length); | ||
| 290 | p.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length); | ||
| 291 | //std.debug.print("shared_key: {}\nhello_hash: {}\nearly_secret: {}\nempty_hash: {}\nderived_secret: {}\nhandshake_secret: {}\n client_secret: {}\n server_secret: {}\nclient_handshake_iv: {}\nserver_handshake_iv: {}\n", .{ | ||
| 292 | // std.fmt.fmtSliceHexLower(&shared_key), | ||
| 293 | // std.fmt.fmtSliceHexLower(&hello_hash), | ||
| 294 | // std.fmt.fmtSliceHexLower(&early_secret), | ||
| 295 | // std.fmt.fmtSliceHexLower(&empty_hash), | ||
| 296 | // std.fmt.fmtSliceHexLower(&hs_derived_secret), | ||
| 297 | // std.fmt.fmtSliceHexLower(&p.handshake_secret), | ||
| 298 | // std.fmt.fmtSliceHexLower(&client_secret), | ||
| 299 | // std.fmt.fmtSliceHexLower(&server_secret), | ||
| 300 | // std.fmt.fmtSliceHexLower(&p.client_handshake_iv), | ||
| 301 | // std.fmt.fmtSliceHexLower(&p.server_handshake_iv), | ||
| 302 | //}); | ||
| 303 | }, | ||
| 304 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 305 | @panic("TODO"); | ||
| 306 | }, | ||
| 307 | .TLS_AES_128_CCM_SHA256 => { | ||
| 308 | @panic("TODO"); | ||
| 309 | }, | ||
| 310 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 311 | @panic("TODO"); | ||
| 312 | }, | ||
| 313 | } | ||
| 314 | }, | ||
| 315 | else => return error.TlsUnexpectedMessage, | ||
| 316 | } | ||
| 317 | break :i end; | ||
| 318 | }; | ||
| 319 | |||
| 320 | var read_seq: u64 = 0; | ||
| 321 | |||
| 322 | while (true) { | ||
| 323 | const end_hdr = i + 5; | ||
| 324 | if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 325 | if (end_hdr > len) { | ||
| 326 | len += try stream.readAtLeast(handshake_buf[len..], end_hdr - len); | ||
| 327 | if (end_hdr > len) return error.EndOfStream; | ||
| 328 | } | ||
| 329 | const ct = @intToEnum(ContentType, handshake_buf[i]); | ||
| 330 | i += 1; | ||
| 331 | const legacy_version = mem.readIntBig(u16, handshake_buf[i..][0..2]); | ||
| 332 | i += 2; | ||
| 333 | _ = legacy_version; | ||
| 334 | const record_size = mem.readIntBig(u16, handshake_buf[i..][0..2]); | ||
| 335 | i += 2; | ||
| 336 | const end = i + record_size; | ||
| 337 | if (end > handshake_buf.len) return error.TlsRecordOverflow; | ||
| 338 | if (end > len) { | ||
| 339 | len += try stream.readAtLeast(handshake_buf[len..], end - len); | ||
| 340 | if (end > len) return error.EndOfStream; | ||
| 341 | } | ||
| 342 | switch (ct) { | ||
| 343 | .change_cipher_spec => { | ||
| 344 | if (record_size != 1) return error.TlsUnexpectedMessage; | ||
| 345 | if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage; | ||
| 346 | }, | ||
| 347 | .application_data => { | ||
| 348 | var cleartext_buf: [8000]u8 = undefined; | ||
| 349 | const cleartext = switch (cipher_params) { | ||
| 350 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { | ||
| 351 | const P = @TypeOf(p.*); | ||
| 352 | const ciphertext_len = record_size - P.AEAD.tag_length; | ||
| 353 | const ciphertext = handshake_buf[i..][0..ciphertext_len]; | ||
| 354 | i += ciphertext.len; | ||
| 355 | if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow; | ||
| 356 | const cleartext = cleartext_buf[0..ciphertext.len]; | ||
| 357 | const auth_tag = handshake_buf[i..][0..P.AEAD.tag_length].*; | ||
| 358 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 359 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 360 | const operand: V = pad ++ @bitCast([8]u8, big(read_seq)); | ||
| 361 | read_seq += 1; | ||
| 362 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_handshake_iv) ^ operand; | ||
| 363 | const ad = handshake_buf[end_hdr - 5 ..][0..5]; | ||
| 364 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_handshake_key) catch | ||
| 365 | return error.TlsBadRecordMac; | ||
| 366 | p.transcript_hash.update(cleartext[0 .. cleartext.len - 1]); | ||
| 367 | break :c cleartext; | ||
| 368 | }, | ||
| 369 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 370 | @panic("TODO"); | ||
| 371 | }, | ||
| 372 | .TLS_AES_128_CCM_SHA256 => { | ||
| 373 | @panic("TODO"); | ||
| 374 | }, | ||
| 375 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 376 | @panic("TODO"); | ||
| 377 | }, | ||
| 378 | }; | ||
| 379 | |||
| 380 | const inner_ct = @intToEnum(ContentType, cleartext[cleartext.len - 1]); | ||
| 381 | switch (inner_ct) { | ||
| 382 | .handshake => { | ||
| 383 | var ct_i: usize = 0; | ||
| 384 | while (true) { | ||
| 385 | const handshake_type = cleartext[ct_i]; | ||
| 386 | ct_i += 1; | ||
| 387 | const handshake_len = mem.readIntBig(u24, cleartext[ct_i..][0..3]); | ||
| 388 | ct_i += 3; | ||
| 389 | const next_handshake_i = ct_i + handshake_len; | ||
| 390 | if (next_handshake_i > cleartext.len - 1) | ||
| 391 | return error.TlsBadLength; | ||
| 392 | switch (handshake_type) { | ||
| 393 | @enumToInt(HandshakeType.encrypted_extensions) => { | ||
| 394 | const total_ext_size = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 395 | ct_i += 2; | ||
| 396 | const end_ext_i = ct_i + total_ext_size; | ||
| 397 | while (ct_i < end_ext_i) { | ||
| 398 | const et = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 399 | ct_i += 2; | ||
| 400 | const ext_size = mem.readIntBig(u16, cleartext[ct_i..][0..2]); | ||
| 401 | ct_i += 2; | ||
| 402 | const next_ext_i = ct_i + ext_size; | ||
| 403 | switch (et) { | ||
| 404 | @enumToInt(tls.ExtensionType.server_name) => {}, | ||
| 405 | else => { | ||
| 406 | std.debug.print("encrypted extension: {any}\n", .{ | ||
| 407 | et, | ||
| 408 | }); | ||
| 409 | }, | ||
| 410 | } | ||
| 411 | ct_i = next_ext_i; | ||
| 412 | } | ||
| 413 | }, | ||
| 414 | @enumToInt(HandshakeType.certificate) => { | ||
| 415 | std.debug.print("cool certificate bro\n", .{}); | ||
| 416 | }, | ||
| 417 | @enumToInt(HandshakeType.certificate_verify) => { | ||
| 418 | std.debug.print("the certificate came with a fancy signature\n", .{}); | ||
| 419 | }, | ||
| 420 | @enumToInt(HandshakeType.finished) => { | ||
| 421 | // This message is to trick buggy proxies into behaving correctly. | ||
| 422 | const client_change_cipher_spec_msg = [_]u8{ | ||
| 423 | @enumToInt(ContentType.change_cipher_spec), | ||
| 424 | 0x03, 0x03, // legacy protocol version | ||
| 425 | 0x00, 0x01, // length | ||
| 426 | 0x01, | ||
| 427 | }; | ||
| 428 | const app_cipher = switch (cipher_params) { | ||
| 429 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p, tag| c: { | ||
| 430 | const P = @TypeOf(p.*); | ||
| 431 | // TODO verify the server's data | ||
| 432 | const handshake_hash = p.transcript_hash.finalResult(); | ||
| 433 | const verify_data = tls.hmac(P.Hmac, &handshake_hash, p.client_finished_key); | ||
| 434 | const out_cleartext = [_]u8{ | ||
| 435 | @enumToInt(HandshakeType.finished), | ||
| 436 | 0, 0, verify_data.len, // length | ||
| 437 | } ++ verify_data ++ [1]u8{@enumToInt(ContentType.handshake)}; | ||
| 438 | |||
| 439 | const wrapped_len = out_cleartext.len + P.AEAD.tag_length; | ||
| 440 | |||
| 441 | var finished_msg = [_]u8{ | ||
| 442 | @enumToInt(ContentType.application_data), | ||
| 443 | 0x03, 0x03, // legacy protocol version | ||
| 444 | 0, wrapped_len, // byte length of encrypted record | ||
| 445 | } ++ ([1]u8{undefined} ** wrapped_len); | ||
| 446 | |||
| 447 | const ad = finished_msg[0..5]; | ||
| 448 | const ciphertext = finished_msg[5..][0..out_cleartext.len]; | ||
| 449 | const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..]; | ||
| 450 | const nonce = p.client_handshake_iv; | ||
| 451 | P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); | ||
| 452 | |||
| 453 | const both_msgs = client_change_cipher_spec_msg ++ finished_msg; | ||
| 454 | try stream.writeAll(&both_msgs); | ||
| 455 | |||
| 456 | const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); | ||
| 457 | const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); | ||
| 458 | //std.debug.print("master_secret={}\nclient_secret={}\nserver_secret={}\n", .{ | ||
| 459 | // std.fmt.fmtSliceHexLower(&p.master_secret), | ||
| 460 | // std.fmt.fmtSliceHexLower(&client_secret), | ||
| 461 | // std.fmt.fmtSliceHexLower(&server_secret), | ||
| 462 | //}); | ||
| 463 | break :c @unionInit(ApplicationCipher, @tagName(tag), .{ | ||
| 464 | .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), | ||
| 465 | .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), | ||
| 466 | .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length), | ||
| 467 | .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length), | ||
| 468 | }); | ||
| 469 | }, | ||
| 470 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 471 | @panic("TODO"); | ||
| 472 | }, | ||
| 473 | .TLS_AES_128_CCM_SHA256 => { | ||
| 474 | @panic("TODO"); | ||
| 475 | }, | ||
| 476 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 477 | @panic("TODO"); | ||
| 478 | }, | ||
| 479 | }; | ||
| 480 | std.debug.print("remaining bytes: {d}\n", .{len - end}); | ||
| 481 | return .{ | ||
| 482 | .application_cipher = app_cipher, | ||
| 483 | .read_seq = 0, | ||
| 484 | .write_seq = 0, | ||
| 485 | .partially_read_buffer = undefined, | ||
| 486 | .partially_read_len = 0, | ||
| 487 | .eof = false, | ||
| 488 | }; | ||
| 489 | }, | ||
| 490 | else => { | ||
| 491 | std.debug.print("handshake type: {d}\n", .{cleartext[0]}); | ||
| 492 | return error.TlsUnexpectedMessage; | ||
| 493 | }, | ||
| 494 | } | ||
| 495 | ct_i = next_handshake_i; | ||
| 496 | if (ct_i >= cleartext.len - 1) break; | ||
| 497 | } | ||
| 498 | }, | ||
| 499 | else => { | ||
| 500 | std.debug.print("inner content type: {any}\n", .{inner_ct}); | ||
| 501 | return error.TlsUnexpectedMessage; | ||
| 502 | }, | ||
| 503 | } | ||
| 504 | }, | ||
| 505 | else => { | ||
| 506 | std.debug.print("content type: {s}\n", .{@tagName(ct)}); | ||
| 507 | return error.TlsUnexpectedMessage; | ||
| 508 | }, | ||
| 509 | } | ||
| 510 | i = end; | ||
| 511 | } | ||
| 512 | |||
| 513 | return error.TlsHandshakeFailure; | ||
| 514 | } | ||
| 515 | |||
| 516 | pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { | ||
| 517 | var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; | ||
| 518 | // Due to the trailing inner content type byte in the ciphertext, we need | ||
| 519 | // an additional buffer for storing the cleartext into before encrypting. | ||
| 520 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; | ||
| 521 | var iovecs_buf: [5]std.os.iovec_const = undefined; | ||
| 522 | var ciphertext_end: usize = 0; | ||
| 523 | var iovec_end: usize = 0; | ||
| 524 | var bytes_i: usize = 0; | ||
| 525 | // How many bytes are taken up by overhead per record. | ||
| 526 | const overhead_len: usize = switch (c.application_cipher) { | ||
| 527 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| l: { | ||
| 528 | const P = @TypeOf(p.*); | ||
| 529 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 530 | const overhead_len = tls.ciphertext_record_header_len + P.AEAD.tag_length + 1; | ||
| 531 | while (true) { | ||
| 532 | const encrypted_content_len = @intCast(u16, @min( | ||
| 533 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), | ||
| 534 | ciphertext_buf.len - | ||
| 535 | tls.ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, | ||
| 536 | )); | ||
| 537 | if (encrypted_content_len == 0) break :l overhead_len; | ||
| 538 | |||
| 539 | mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]); | ||
| 540 | cleartext_buf[encrypted_content_len] = @enumToInt(ContentType.application_data); | ||
| 541 | bytes_i += encrypted_content_len; | ||
| 542 | const ciphertext_len = encrypted_content_len + 1; | ||
| 543 | const cleartext = cleartext_buf[0..ciphertext_len]; | ||
| 544 | |||
| 545 | const record_start = ciphertext_end; | ||
| 546 | const ad = ciphertext_buf[ciphertext_end..][0..5]; | ||
| 547 | ad.* = | ||
| 548 | [_]u8{@enumToInt(ContentType.application_data)} ++ | ||
| 549 | int2(@enumToInt(tls.ProtocolVersion.tls_1_2)) ++ | ||
| 550 | int2(ciphertext_len + P.AEAD.tag_length); | ||
| 551 | ciphertext_end += ad.len; | ||
| 552 | const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len]; | ||
| 553 | ciphertext_end += ciphertext_len; | ||
| 554 | const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length]; | ||
| 555 | ciphertext_end += auth_tag.len; | ||
| 556 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 557 | const operand: V = pad ++ @bitCast([8]u8, big(c.write_seq)); | ||
| 558 | c.write_seq += 1; | ||
| 559 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.client_iv) ^ operand; | ||
| 560 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); | ||
| 561 | //std.debug.print("seq: {d} nonce: {} client_key: {} client_iv: {} ad: {} auth_tag: {}\nserver_key: {} server_iv: {}\n", .{ | ||
| 562 | // c.write_seq - 1, | ||
| 563 | // std.fmt.fmtSliceHexLower(&nonce), | ||
| 564 | // std.fmt.fmtSliceHexLower(&p.client_key), | ||
| 565 | // std.fmt.fmtSliceHexLower(&p.client_iv), | ||
| 566 | // std.fmt.fmtSliceHexLower(ad), | ||
| 567 | // std.fmt.fmtSliceHexLower(auth_tag), | ||
| 568 | // std.fmt.fmtSliceHexLower(&p.server_key), | ||
| 569 | // std.fmt.fmtSliceHexLower(&p.server_iv), | ||
| 570 | //}); | ||
| 571 | |||
| 572 | const record = ciphertext_buf[record_start..ciphertext_end]; | ||
| 573 | iovecs_buf[iovec_end] = .{ | ||
| 574 | .iov_base = record.ptr, | ||
| 575 | .iov_len = record.len, | ||
| 576 | }; | ||
| 577 | iovec_end += 1; | ||
| 578 | } | ||
| 579 | }, | ||
| 580 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 581 | @panic("TODO"); | ||
| 582 | }, | ||
| 583 | .TLS_AES_128_CCM_SHA256 => { | ||
| 584 | @panic("TODO"); | ||
| 585 | }, | ||
| 586 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 587 | @panic("TODO"); | ||
| 588 | }, | ||
| 589 | }; | ||
| 590 | |||
| 591 | // Ideally we would call writev exactly once here, however, we must ensure | ||
| 592 | // that we don't return with a record partially written. | ||
| 593 | var i: usize = 0; | ||
| 594 | var total_amt: usize = 0; | ||
| 595 | while (true) { | ||
| 596 | var amt = try stream.writev(iovecs_buf[i..iovec_end]); | ||
| 597 | while (amt >= iovecs_buf[i].iov_len) { | ||
| 598 | const encrypted_amt = iovecs_buf[i].iov_len; | ||
| 599 | total_amt += encrypted_amt - overhead_len; | ||
| 600 | amt -= encrypted_amt; | ||
| 601 | i += 1; | ||
| 602 | // Rely on the property that iovecs delineate records, meaning that | ||
| 603 | // if amt equals zero here, we have fortunately found ourselves | ||
| 604 | // with a short read that aligns at the record boundary. | ||
| 605 | if (i >= iovec_end or amt == 0) return total_amt; | ||
| 606 | } | ||
| 607 | iovecs_buf[i].iov_base += amt; | ||
| 608 | iovecs_buf[i].iov_len -= amt; | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | pub fn writeAll(c: *Client, stream: net.Stream, bytes: []const u8) !void { | ||
| 613 | var index: usize = 0; | ||
| 614 | while (index < bytes.len) { | ||
| 615 | index += try c.write(stream, bytes[index..]); | ||
| 616 | } | ||
| 617 | } | ||
| 618 | |||
| 619 | /// Returns number of bytes that have been read, which are now populated inside | ||
| 620 | /// `buffer`. A return value of zero bytes does not necessarily mean end of | ||
| 621 | /// stream. | ||
| 622 | pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize { | ||
| 623 | const prev_len = c.partially_read_len; | ||
| 624 | var in_buf: [max_ciphertext_len * 4]u8 = undefined; | ||
| 625 | mem.copy(u8, &in_buf, c.partially_read_buffer[0..prev_len]); | ||
| 626 | |||
| 627 | // Capacity of output buffer, in records, rounded up. | ||
| 628 | const buf_cap = (buffer.len +| (max_ciphertext_len - 1)) / max_ciphertext_len; | ||
| 629 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); | ||
| 630 | const ask_slice = in_buf[prev_len..@min(wanted_read_len, in_buf.len)]; | ||
| 631 | const actual_read_len = try stream.read(ask_slice); | ||
| 632 | const frag = in_buf[0 .. prev_len + actual_read_len]; | ||
| 633 | if (frag.len == 0) { | ||
| 634 | c.eof = true; | ||
| 635 | return 0; | ||
| 636 | } | ||
| 637 | var in: usize = 0; | ||
| 638 | var out: usize = 0; | ||
| 639 | |||
| 640 | while (true) { | ||
| 641 | if (in + tls.ciphertext_record_header_len > frag.len) { | ||
| 642 | return finishRead(c, frag, in, out); | ||
| 643 | } | ||
| 644 | const ct = @intToEnum(ContentType, frag[in]); | ||
| 645 | in += 1; | ||
| 646 | const legacy_version = mem.readIntBig(u16, frag[in..][0..2]); | ||
| 647 | in += 2; | ||
| 648 | _ = legacy_version; | ||
| 649 | const record_size = mem.readIntBig(u16, frag[in..][0..2]); | ||
| 650 | in += 2; | ||
| 651 | const end = in + record_size; | ||
| 652 | if (end > frag.len) { | ||
| 653 | if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; | ||
| 654 | return finishRead(c, frag, in, out); | ||
| 655 | } | ||
| 656 | switch (ct) { | ||
| 657 | .alert => { | ||
| 658 | @panic("TODO handle an alert here"); | ||
| 659 | }, | ||
| 660 | .application_data => { | ||
| 661 | const cleartext_len = switch (c.application_cipher) { | ||
| 662 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { | ||
| 663 | const P = @TypeOf(p.*); | ||
| 664 | const V = @Vector(P.AEAD.nonce_length, u8); | ||
| 665 | const ad = frag[in - 5 ..][0..5]; | ||
| 666 | const ciphertext_len = record_size - P.AEAD.tag_length; | ||
| 667 | const ciphertext = frag[in..][0..ciphertext_len]; | ||
| 668 | in += ciphertext_len; | ||
| 669 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; | ||
| 670 | const cleartext = buffer[out..][0..ciphertext_len]; | ||
| 671 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | ||
| 672 | const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq)); | ||
| 673 | c.read_seq += 1; | ||
| 674 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; | ||
| 675 | //std.debug.print("seq: {d} nonce: {} server_key: {} server_iv: {}\n", .{ | ||
| 676 | // c.read_seq - 1, | ||
| 677 | // std.fmt.fmtSliceHexLower(&nonce), | ||
| 678 | // std.fmt.fmtSliceHexLower(&p.server_key), | ||
| 679 | // std.fmt.fmtSliceHexLower(&p.server_iv), | ||
| 680 | //}); | ||
| 681 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch | ||
| 682 | return error.TlsBadRecordMac; | ||
| 683 | break :c cleartext.len; | ||
| 684 | }, | ||
| 685 | .TLS_CHACHA20_POLY1305_SHA256 => { | ||
| 686 | @panic("TODO"); | ||
| 687 | }, | ||
| 688 | .TLS_AES_128_CCM_SHA256 => { | ||
| 689 | @panic("TODO"); | ||
| 690 | }, | ||
| 691 | .TLS_AES_128_CCM_8_SHA256 => { | ||
| 692 | @panic("TODO"); | ||
| 693 | }, | ||
| 694 | }; | ||
| 695 | |||
| 696 | const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]); | ||
| 697 | switch (inner_ct) { | ||
| 698 | .alert => { | ||
| 699 | const level = @intToEnum(tls.AlertLevel, buffer[out]); | ||
| 700 | const desc = @intToEnum(tls.AlertDescription, buffer[out + 1]); | ||
| 701 | if (desc == .close_notify) { | ||
| 702 | c.eof = true; | ||
| 703 | return out; | ||
| 704 | } | ||
| 705 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | ||
| 706 | return error.TlsAlert; | ||
| 707 | }, | ||
| 708 | .handshake => { | ||
| 709 | std.debug.print("the server wants to keep shaking hands\n", .{}); | ||
| 710 | }, | ||
| 711 | .application_data => { | ||
| 712 | out += cleartext_len - 1; | ||
| 713 | }, | ||
| 714 | else => { | ||
| 715 | std.debug.print("inner content type: {d}\n", .{inner_ct}); | ||
| 716 | return error.TlsUnexpectedMessage; | ||
| 717 | }, | ||
| 718 | } | ||
| 719 | }, | ||
| 720 | else => { | ||
| 721 | std.debug.print("unexpected ct: {any}\n", .{ct}); | ||
| 722 | return error.TlsUnexpectedMessage; | ||
| 723 | }, | ||
| 724 | } | ||
| 725 | in = end; | ||
| 726 | } | ||
| 727 | } | ||
| 728 | |||
| 729 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { | ||
| 730 | const saved_buf = frag[in..]; | ||
| 731 | mem.copy(u8, &c.partially_read_buffer, saved_buf); | ||
| 732 | c.partially_read_len = @intCast(u15, saved_buf.len); | ||
| 733 | return out; | ||
| 734 | } | ||
| 735 | |||
| 736 | const builtin = @import("builtin"); | ||
| 737 | const native_endian = builtin.cpu.arch.endian(); | ||
| 738 | |||
| 739 | inline fn big(x: anytype) @TypeOf(x) { | ||
| 740 | return switch (native_endian) { | ||
| 741 | .Big => x, | ||
| 742 | .Little => @byteSwap(x), | ||
| 743 | }; | ||
| 744 | } | ||
| 745 | |||
| 746 | inline fn int2(x: u16) [2]u8 { | ||
| 747 | return .{ | ||
| 748 | @truncate(u8, x >> 8), | ||
| 749 | @truncate(u8, x), | ||
| 750 | }; | ||
| 751 | } | ||
lib/std/http/Client.zig+6-6| ... | @@ -12,7 +12,7 @@ pub const Request = struct { | ... | @@ -12,7 +12,7 @@ pub const Request = struct { |
| 12 | client: *Client, | 12 | client: *Client, |
| 13 | stream: net.Stream, | 13 | stream: net.Stream, |
| 14 | headers: std.ArrayListUnmanaged(u8) = .{}, | 14 | headers: std.ArrayListUnmanaged(u8) = .{}, |
| 15 | tls: std.crypto.Tls, | 15 | tls_client: std.crypto.tls.Client, |
| 16 | protocol: Protocol, | 16 | protocol: Protocol, |
| 17 | 17 | ||
| 18 | pub const Protocol = enum { http, https }; | 18 | pub const Protocol = enum { http, https }; |
| ... | @@ -51,7 +51,7 @@ pub const Request = struct { | ... | @@ -51,7 +51,7 @@ pub const Request = struct { |
| 51 | try req.stream.writeAll(req.headers.items); | 51 | try req.stream.writeAll(req.headers.items); |
| 52 | }, | 52 | }, |
| 53 | .https => { | 53 | .https => { |
| 54 | try req.tls.writeAll(req.stream, req.headers.items); | 54 | try req.tls_client.writeAll(req.stream, req.headers.items); |
| 55 | }, | 55 | }, |
| 56 | } | 56 | } |
| 57 | } | 57 | } |
| ... | @@ -59,7 +59,7 @@ pub const Request = struct { | ... | @@ -59,7 +59,7 @@ pub const Request = struct { |
| 59 | pub fn read(req: *Request, buffer: []u8) !usize { | 59 | pub fn read(req: *Request, buffer: []u8) !usize { |
| 60 | switch (req.protocol) { | 60 | switch (req.protocol) { |
| 61 | .http => return req.stream.read(buffer), | 61 | .http => return req.stream.read(buffer), |
| 62 | .https => return req.tls.read(req.stream, buffer), | 62 | .https => return req.tls_client.read(req.stream, buffer), |
| 63 | } | 63 | } |
| 64 | } | 64 | } |
| 65 | 65 | ||
| ... | @@ -74,7 +74,7 @@ pub const Request = struct { | ... | @@ -74,7 +74,7 @@ pub const Request = struct { |
| 74 | if (amt == 0) { | 74 | if (amt == 0) { |
| 75 | switch (req.protocol) { | 75 | switch (req.protocol) { |
| 76 | .http => break, | 76 | .http => break, |
| 77 | .https => if (req.tls.eof) break, | 77 | .https => if (req.tls_client.eof) break, |
| 78 | } | 78 | } |
| 79 | } | 79 | } |
| 80 | index += amt; | 80 | index += amt; |
| ... | @@ -94,7 +94,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { | ... | @@ -94,7 +94,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { |
| 94 | .client = client, | 94 | .client = client, |
| 95 | .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port), | 95 | .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port), |
| 96 | .protocol = options.protocol, | 96 | .protocol = options.protocol, |
| 97 | .tls = undefined, | 97 | .tls_client = undefined, |
| 98 | }; | 98 | }; |
| 99 | client.active_requests += 1; | 99 | client.active_requests += 1; |
| 100 | errdefer req.deinit(); | 100 | errdefer req.deinit(); |
| ... | @@ -102,7 +102,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { | ... | @@ -102,7 +102,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { |
| 102 | switch (options.protocol) { | 102 | switch (options.protocol) { |
| 103 | .http => {}, | 103 | .http => {}, |
| 104 | .https => { | 104 | .https => { |
| 105 | req.tls = try std.crypto.Tls.init(req.stream, options.host); | 105 | req.tls_client = try std.crypto.tls.Client.init(req.stream, options.host); |
| 106 | }, | 106 | }, |
| 107 | } | 107 | } |
| 108 | 108 |