authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-16 14:08:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log93ab8be8d8464452af6d2e686e91be2c1da98979
tree2c0e0c5264df2000325c22733bb154be5607eee1
parent02c33d02e05f3dd067bc5492d2617b7805ef897d

extract std.crypto.tls.Client into separate namespace


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");
176176
177pub const errors = @import("crypto/errors.zig");177pub const errors = @import("crypto/errors.zig");
178178
179pub const Tls = @import("crypto/Tls.zig");179pub const tls = @import("crypto/tls.zig");
180180
181test {181test {
182 _ = aead.aegis.Aegis128L;182 _ = aead.aegis.Aegis128L;
lib/std/crypto/Tls.zig deleted-1059
...@@ -1,1059 +0,0 @@
1const std = @import("../std.zig");
2const Tls = @This();
3const net = std.net;
4const mem = std.mem;
5const crypto = std.crypto;
6const assert = std.debug.assert;
7
8application_cipher: ApplicationCipher,
9read_seq: u64,
10write_seq: u64,
11/// The size is enough to contain exactly one TLSCiphertext record.
12partially_read_buffer: [max_ciphertext_record_len]u8,
13/// The number of partially read bytes inside `partiall_read_buffer`.
14partially_read_len: u15,
15eof: bool,
16
17pub const ciphertext_record_header_len = 5;
18pub const max_ciphertext_len = (1 << 14) + 256;
19pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len;
20pub 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
25pub const ProtocolVersion = enum(u16) {
26 tls_1_2 = 0x0303,
27 tls_1_3 = 0x0304,
28 _,
29};
30
31pub 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
40pub 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
54pub 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
101pub const AlertLevel = enum(u8) {
102 warning = 1,
103 fatal = 2,
104 _,
105};
106
107pub 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
138pub 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
170pub 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
220pub 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
228pub 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.
267pub 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
295const 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.
308pub 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
785pub 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
881pub 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.
891pub 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
998fn 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
1005fn 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
1032fn 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
1038fn 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
1044const builtin = @import("builtin");
1045const native_endian = builtin.cpu.arch.endian();
1046
1047inline fn big(x: anytype) @TypeOf(x) {
1048 return switch (native_endian) {
1049 .Big => x,
1050 .Little => @byteSwap(x),
1051 };
1052}
1053
1054inline 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
33const std = @import("../std.zig");
34const Tls = @This();
35const net = std.net;
36const mem = std.mem;
37const crypto = std.crypto;
38const assert = std.debug.assert;
39
40pub const Client = @import("tls/Client.zig");
41
42pub const ciphertext_record_header_len = 5;
43pub const max_ciphertext_len = (1 << 14) + 256;
44pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len;
45pub 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
50pub const ProtocolVersion = enum(u16) {
51 tls_1_2 = 0x0303,
52 tls_1_3 = 0x0304,
53 _,
54};
55
56pub 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
65pub 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
79pub 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
126pub const AlertLevel = enum(u8) {
127 warning = 1,
128 fatal = 2,
129 _,
130};
131
132pub 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
163pub 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
195pub 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
213pub 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
221pub 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.
260pub 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
288pub 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
315pub 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
321pub 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 @@
1const std = @import("../../std.zig");
2const tls = std.crypto.tls;
3const Client = @This();
4const net = std.net;
5const mem = std.mem;
6const crypto = std.crypto;
7const assert = std.debug.assert;
8
9const ApplicationCipher = tls.ApplicationCipher;
10const CipherSuite = tls.CipherSuite;
11const ContentType = tls.ContentType;
12const HandshakeType = tls.HandshakeType;
13const CipherParams = tls.CipherParams;
14const max_ciphertext_len = tls.max_ciphertext_len;
15const hkdfExpandLabel = tls.hkdfExpandLabel;
16
17application_cipher: ApplicationCipher,
18read_seq: u64,
19write_seq: u64,
20/// The size is enough to contain exactly one TLSCiphertext record.
21partially_read_buffer: [tls.max_ciphertext_record_len]u8,
22/// The number of partially read bytes inside `partiall_read_buffer`.
23partially_read_len: u15,
24eof: bool,
25
26const 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.
39pub 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
516pub 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
612pub 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.
622pub 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
729fn 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
736const builtin = @import("builtin");
737const native_endian = builtin.cpu.arch.endian();
738
739inline fn big(x: anytype) @TypeOf(x) {
740 return switch (native_endian) {
741 .Big => x,
742 .Little => @byteSwap(x),
743 };
744}
745
746inline 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,
1717
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 }
6565
...@@ -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 }
108108