authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-15 20:35:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log40a85506b2e6a97af9c06bdcd001b6fd84cc549a
tree8a7bffbaf33a5638cd42a8d8e3a2ace188697240
parent595fff7cb664b5dc517a682b3daec5ee2767fe0d

std.crypto.Tls: add read/write methods


3 files changed, 459 insertions(+), 137 deletions(-)

lib/std/crypto/Tls.zig+427-135
......@@ -5,24 +5,24 @@ const mem = std.mem;
55const crypto = std.crypto;
66const assert = std.debug.assert;
77
8state: State = .start,
9x25519_priv_key: [32]u8 = undefined,
10x25519_pub_key: [32]u8 = undefined,
11x25519_server_pub_key: [32]u8 = undefined,
12
13const ProtocolVersion = enum(u16) {
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_len + ciphertext_record_header_len]u8,
13/// The number of partially read bytes inside `partiall_read_buffer`.
14partially_read_len: u15,
15
16pub const ciphertext_record_header_len = 5;
17pub const max_ciphertext_len = (1 << 14) + 256;
18
19pub const ProtocolVersion = enum(u16) {
1420 tls_1_2 = 0x0303,
1521 tls_1_3 = 0x0304,
1622 _,
1723};
1824
19const State = enum {
20 /// In this state, all fields are undefined except state.
21 start,
22 sent_hello,
23};
24
25const ContentType = enum(u8) {
25pub const ContentType = enum(u8) {
2626 invalid = 0,
2727 change_cipher_spec = 20,
2828 alert = 21,
......@@ -31,7 +31,7 @@ const ContentType = enum(u8) {
3131 _,
3232};
3333
34const HandshakeType = enum(u8) {
34pub const HandshakeType = enum(u8) {
3535 client_hello = 1,
3636 server_hello = 2,
3737 new_session_ticket = 4,
......@@ -45,7 +45,7 @@ const HandshakeType = enum(u8) {
4545 message_hash = 254,
4646};
4747
48const ExtensionType = enum(u16) {
48pub const ExtensionType = enum(u16) {
4949 /// RFC 6066
5050 server_name = 0,
5151 /// RFC 6066
......@@ -92,13 +92,13 @@ const ExtensionType = enum(u16) {
9292 key_share = 51,
9393};
9494
95const AlertLevel = enum(u8) {
95pub const AlertLevel = enum(u8) {
9696 warning = 1,
9797 fatal = 2,
9898 _,
9999};
100100
101const AlertDescription = enum(u8) {
101pub const AlertDescription = enum(u8) {
102102 close_notify = 0,
103103 unexpected_message = 10,
104104 bad_record_mac = 20,
......@@ -129,7 +129,7 @@ const AlertDescription = enum(u8) {
129129 _,
130130};
131131
132const SignatureScheme = enum(u16) {
132pub const SignatureScheme = enum(u16) {
133133 // RSASSA-PKCS1-v1_5 algorithms
134134 rsa_pkcs1_sha256 = 0x0401,
135135 rsa_pkcs1_sha384 = 0x0501,
......@@ -161,7 +161,7 @@ const SignatureScheme = enum(u16) {
161161 _,
162162};
163163
164const NamedGroup = enum(u16) {
164pub const NamedGroup = enum(u16) {
165165 // Elliptic Curve Groups (ECDHE)
166166 secp256r1 = 0x0017,
167167 secp384r1 = 0x0018,
......@@ -211,7 +211,7 @@ const NamedGroup = enum(u16) {
211211// * ExtensionType extension_type;
212212// * opaque extension_data<0..2^16-1>;
213213
214const CipherSuite = enum(u16) {
214pub const CipherSuite = enum(u16) {
215215 TLS_AES_128_GCM_SHA256 = 0x1301,
216216 TLS_AES_256_GCM_SHA384 = 0x1302,
217217 TLS_CHACHA20_POLY1305_SHA256 = 0x1303,
......@@ -219,6 +219,73 @@ const CipherSuite = enum(u16) {
219219 TLS_AES_128_CCM_8_SHA256 = 0x1305,
220220};
221221
222pub const CipherParams = union(CipherSuite) {
223 TLS_AES_128_GCM_SHA256: struct {
224 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
225 const Hash = crypto.hash.sha2.Sha256;
226 const Hmac = crypto.auth.hmac.Hmac(Hash);
227 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
228
229 handshake_secret: [Hkdf.key_len]u8,
230 master_secret: [Hkdf.key_len]u8,
231 client_handshake_key: [AEAD.key_length]u8,
232 server_handshake_key: [AEAD.key_length]u8,
233 client_finished_key: [Hmac.key_length]u8,
234 server_finished_key: [Hmac.key_length]u8,
235 client_handshake_iv: [AEAD.nonce_length]u8,
236 server_handshake_iv: [AEAD.nonce_length]u8,
237 transcript_hash: Hash,
238 },
239 TLS_AES_256_GCM_SHA384: struct {
240 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
241 const Hash = crypto.hash.sha2.Sha384;
242 const Hmac = crypto.auth.hmac.Hmac(Hash);
243 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
244
245 handshake_secret: [Hkdf.key_len]u8,
246 master_secret: [Hkdf.key_len]u8,
247 client_handshake_key: [AEAD.key_length]u8,
248 server_handshake_key: [AEAD.key_length]u8,
249 client_finished_key: [Hmac.key_length]u8,
250 server_finished_key: [Hmac.key_length]u8,
251 client_handshake_iv: [AEAD.nonce_length]u8,
252 server_handshake_iv: [AEAD.nonce_length]u8,
253 transcript_hash: Hash,
254 },
255 TLS_CHACHA20_POLY1305_SHA256: void,
256 TLS_AES_128_CCM_SHA256: void,
257 TLS_AES_128_CCM_8_SHA256: void,
258};
259
260/// Encryption parameters for application traffic.
261pub const ApplicationCipher = union(CipherSuite) {
262 TLS_AES_128_GCM_SHA256: struct {
263 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
264 const Hash = crypto.hash.sha2.Sha256;
265 const Hmac = crypto.auth.hmac.Hmac(Hash);
266 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
267
268 client_key: [AEAD.key_length]u8,
269 server_key: [AEAD.key_length]u8,
270 client_iv: [AEAD.nonce_length]u8,
271 server_iv: [AEAD.nonce_length]u8,
272 },
273 TLS_AES_256_GCM_SHA384: struct {
274 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
275 const Hash = crypto.hash.sha2.Sha384;
276 const Hmac = crypto.auth.hmac.Hmac(Hash);
277 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
278
279 client_key: [AEAD.key_length]u8,
280 server_key: [AEAD.key_length]u8,
281 client_iv: [AEAD.nonce_length]u8,
282 server_iv: [AEAD.nonce_length]u8,
283 },
284 TLS_CHACHA20_POLY1305_SHA256: void,
285 TLS_AES_128_CCM_SHA256: void,
286 TLS_AES_128_CCM_8_SHA256: void,
287};
288
222289const cipher_suites = blk: {
223290 const fields = @typeInfo(CipherSuite).Enum.fields;
224291 var result: [(fields.len + 1) * 2]u8 = undefined;
......@@ -231,10 +298,11 @@ const cipher_suites = blk: {
231298 break :blk result;
232299};
233300
234pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
235 assert(tls.state == .start);
236 crypto.random.bytes(&tls.x25519_priv_key);
237 tls.x25519_pub_key = crypto.dh.X25519.recoverPublicKey(tls.x25519_priv_key) catch |err| {
301/// `host` is only borrowed during this function call.
302pub fn init(stream: net.Stream, host: []const u8) !Tls {
303 var x25519_priv_key: [32]u8 = undefined;
304 crypto.random.bytes(&x25519_priv_key);
305 const x25519_pub_key = crypto.dh.X25519.recoverPublicKey(x25519_priv_key) catch |err| {
238306 switch (err) {
239307 // Only possible to happen if the private key is all zeroes.
240308 error.IdentityElement => return error.InsufficientEntropy,
......@@ -293,7 +361,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
293361 0, 36, // byte length of client_shares
294362 0x00, 0x1D, // NamedGroup.x25519
295363 0, 32, // byte length of key_exchange
296 } ++ tls.x25519_pub_key ++ [_]u8{
364 } ++ x25519_pub_key ++ [_]u8{
297365
298366 // Extension: server_name
299367 0, 0, // ExtensionType.server_name
......@@ -330,25 +398,23 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
330398 mem.writeIntBig(u16, hello_header[hello_header.len - 5 ..][0..2], @intCast(u16, 3 + host.len));
331399 mem.writeIntBig(u16, hello_header[hello_header.len - 2 ..][0..2], @intCast(u16, 0 + host.len));
332400
333 var iovecs = [_]std.os.iovec_const{
334 .{
335 .iov_base = &hello_header,
336 .iov_len = hello_header.len,
337 },
338 .{
339 .iov_base = host.ptr,
340 .iov_len = host.len,
341 },
342 };
343 try stream.writevAll(&iovecs);
401 {
402 var iovecs = [_]std.os.iovec_const{
403 .{
404 .iov_base = &hello_header,
405 .iov_len = hello_header.len,
406 },
407 .{
408 .iov_base = host.ptr,
409 .iov_len = host.len,
410 },
411 };
412 try stream.writevAll(&iovecs);
413 }
344414
345415 const client_hello_bytes1 = hello_header[5..];
346416
347 var client_handshake_key: [32]u8 = undefined;
348 var server_handshake_key: [32]u8 = undefined;
349 var client_handshake_iv: [12]u8 = undefined;
350 var server_handshake_iv: [12]u8 = undefined;
351 var cipher_suite: CipherSuite = undefined;
417 var cipher_params: CipherParams = undefined;
352418
353419 var handshake_buf: [4000]u8 = undefined;
354420 var len: usize = 0;
......@@ -386,16 +452,16 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
386452 const legacy_session_id_echo_len = hello[34];
387453 if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter;
388454 const cipher_suite_int = mem.readIntBig(u16, hello[35..37]);
389 cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch
455 const cipher_suite_tag = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch
390456 return error.TlsIllegalParameter;
391 std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)});
457 std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite_tag)});
392458 const legacy_compression_method = hello[37];
393459 _ = legacy_compression_method;
394460 const extensions_size = mem.readIntBig(u16, hello[38..40]);
395461 if (40 + extensions_size != hello.len) return error.TlsBadLength;
396462 var i: usize = 40;
397463 var supported_version: u16 = 0;
398 var have_server_pub_key = false;
464 var opt_x25519_server_pub_key: ?*[32]u8 = null;
399465 while (i < hello.len) {
400466 const et = mem.readIntBig(u16, hello[i..][0..2]);
401467 i += 2;
......@@ -409,7 +475,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
409475 supported_version = mem.readIntBig(u16, hello[i..][0..2]);
410476 },
411477 @enumToInt(ExtensionType.key_share) => {
412 if (have_server_pub_key) return error.TlsIllegalParameter;
478 if (opt_x25519_server_pub_key != null) return error.TlsIllegalParameter;
413479 const named_group = mem.readIntBig(u16, hello[i..][0..2]);
414480 i += 2;
415481 switch (named_group) {
......@@ -417,8 +483,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
417483 const key_size = mem.readIntBig(u16, hello[i..][0..2]);
418484 i += 2;
419485 if (key_size != 32) return error.TlsBadLength;
420 tls.x25519_server_pub_key = hello[i..][0..32].*;
421 have_server_pub_key = true;
486 opt_x25519_server_pub_key = hello[i..][0..32];
422487 },
423488 else => {
424489 std.debug.print("named group: {x}\n", .{named_group});
......@@ -432,7 +497,8 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
432497 }
433498 i = next_i;
434499 }
435 if (!have_server_pub_key) return error.TlsIllegalParameter;
500 const x25519_server_pub_key = opt_x25519_server_pub_key orelse
501 return error.TlsIllegalParameter;
436502 const tls_version = if (supported_version == 0) legacy_version else supported_version;
437503 switch (tls_version) {
438504 @enumToInt(ProtocolVersion.tls_1_2) => {
......@@ -445,28 +511,44 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
445511 }
446512
447513 const shared_key = crypto.dh.X25519.scalarmult(
448 tls.x25519_priv_key,
449 tls.x25519_server_pub_key,
514 x25519_priv_key,
515 x25519_server_pub_key.*,
450516 ) catch return error.TlsDecryptFailure;
451517
452 switch (cipher_suite) {
453 .TLS_AES_128_GCM_SHA256 => {
454 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
455 const Hash = crypto.hash.sha2.Sha256;
456 const Hmac = crypto.auth.hmac.Hmac(Hash);
457 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
458
459 const hello_hash = helloHash(client_hello_bytes1, host, frag, Hash);
460 const early_secret = Hkdf.extract(&[1]u8{0}, &([1]u8{0} ** Hash.digest_length));
461 const empty_hash = emptyHash(Hash);
462 const derived_secret = hkdfExpandLabel(Hkdf, early_secret, "derived", &empty_hash, Hash.digest_length);
463 const handshake_secret = Hkdf.extract(&derived_secret, &shared_key);
464 const client_secret = hkdfExpandLabel(Hkdf, handshake_secret, "c hs traffic", &hello_hash, Hash.digest_length);
465 const server_secret = hkdfExpandLabel(Hkdf, handshake_secret, "s hs traffic", &hello_hash, Hash.digest_length);
466 client_handshake_key[0..AEAD.key_length].* = hkdfExpandLabel(Hkdf, client_secret, "key", "", AEAD.key_length);
467 server_handshake_key[0..AEAD.key_length].* = hkdfExpandLabel(Hkdf, server_secret, "key", "", AEAD.key_length);
468 client_handshake_iv = hkdfExpandLabel(Hkdf, client_secret, "iv", "", AEAD.nonce_length);
469 server_handshake_iv = hkdfExpandLabel(Hkdf, server_secret, "iv", "", AEAD.nonce_length);
518 switch (cipher_suite_tag) {
519 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |tag| {
520 const P = std.meta.TagPayload(CipherParams, tag);
521 cipher_params = @unionInit(CipherParams, @tagName(tag), .{
522 .handshake_secret = undefined,
523 .master_secret = undefined,
524 .client_handshake_key = undefined,
525 .server_handshake_key = undefined,
526 .client_finished_key = undefined,
527 .server_finished_key = undefined,
528 .client_handshake_iv = undefined,
529 .server_handshake_iv = undefined,
530 .transcript_hash = P.Hash.init(.{}),
531 });
532 const p = &@field(cipher_params, @tagName(tag));
533 p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1
534 p.transcript_hash.update(host); // Client Hello part 2
535 p.transcript_hash.update(frag); // Server Hello
536 const hello_hash = p.transcript_hash.peek();
537 const zeroes = [1]u8{0} ** P.Hash.digest_length;
538 const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes);
539 const empty_hash = emptyHash(P.Hash);
540 const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length);
541 p.handshake_secret = P.Hkdf.extract(&hs_derived_secret, &shared_key);
542 const ap_derived_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "derived", &empty_hash, P.Hash.digest_length);
543 p.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
544 const client_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
545 const server_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
546 p.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length);
547 p.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length);
548 p.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
549 p.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
550 p.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
551 p.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
470552 //std.debug.print("shared_key: {}\nhello_hash: {}\nearly_secret: {}\nempty_hash: {}\nderived_secret: {}\nhandshake_secret: {}\n client_secret: {}\n server_secret: {}\n", .{
471553 // std.fmt.fmtSliceHexLower(&shared_key),
472554 // std.fmt.fmtSliceHexLower(&hello_hash),
......@@ -478,24 +560,6 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
478560 // std.fmt.fmtSliceHexLower(&server_secret),
479561 //});
480562 },
481 .TLS_AES_256_GCM_SHA384 => {
482 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
483 const Hash = crypto.hash.sha2.Sha384;
484 const Hmac = crypto.auth.hmac.Hmac(Hash);
485 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
486
487 const hello_hash = helloHash(client_hello_bytes1, host, frag, Hash);
488 const early_secret = Hkdf.extract(&[1]u8{0}, &([1]u8{0} ** Hash.digest_length));
489 const empty_hash = emptyHash(Hash);
490 const derived_secret = hkdfExpandLabel(Hkdf, early_secret, "derived", &empty_hash, Hash.digest_length);
491 const handshake_secret = Hkdf.extract(&derived_secret, &shared_key);
492 const client_secret = hkdfExpandLabel(Hkdf, handshake_secret, "c hs traffic", &hello_hash, Hash.digest_length);
493 const server_secret = hkdfExpandLabel(Hkdf, handshake_secret, "s hs traffic", &hello_hash, Hash.digest_length);
494 client_handshake_key = hkdfExpandLabel(Hkdf, client_secret, "key", "", AEAD.key_length);
495 server_handshake_key = hkdfExpandLabel(Hkdf, server_secret, "key", "", AEAD.key_length);
496 client_handshake_iv = hkdfExpandLabel(Hkdf, client_secret, "iv", "", AEAD.nonce_length);
497 server_handshake_iv = hkdfExpandLabel(Hkdf, server_secret, "iv", "", AEAD.nonce_length);
498 },
499563 .TLS_CHACHA20_POLY1305_SHA256 => {
500564 @panic("TODO");
501565 },
......@@ -541,50 +605,24 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
541605 },
542606 .application_data => {
543607 var cleartext_buf: [1000]u8 = undefined;
544 const cleartext = switch (cipher_suite) {
545 .TLS_AES_128_GCM_SHA256 => c: {
546 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
547 const ciphertext_len = record_size - AEAD.tag_length;
608 const cleartext = switch (cipher_params) {
609 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: {
610 const P = @TypeOf(p.*);
611 const ciphertext_len = record_size - P.AEAD.tag_length;
548612 const ciphertext = handshake_buf[i..][0..ciphertext_len];
549613 i += ciphertext.len;
550614 if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow;
551615 const cleartext = cleartext_buf[0..ciphertext.len];
552 const auth_tag = handshake_buf[i..][0..AEAD.tag_length].*;
553 const V = @Vector(AEAD.nonce_length, u8);
554 const pad = [1]u8{0} ** (AEAD.nonce_length - 8);
616 const auth_tag = handshake_buf[i..][0..P.AEAD.tag_length].*;
617 const V = @Vector(P.AEAD.nonce_length, u8);
618 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
555619 const operand: V = pad ++ @bitCast([8]u8, big(read_seq));
556620 read_seq += 1;
557 const nonce: [AEAD.nonce_length]u8 = @as(V, server_handshake_iv) ^ operand;
558 //std.debug.print("seq: {d} nonce: {} operand: {}\n", .{
559 // read_seq - 1,
560 // std.fmt.fmtSliceHexLower(&nonce),
561 // std.fmt.fmtSliceHexLower(&@as([12]u8, operand)),
562 //});
621 const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_handshake_iv) ^ operand;
563622 const ad = handshake_buf[end_hdr - 5 ..][0..5];
564 const key = server_handshake_key[0..AEAD.key_length].*;
565 AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, key) catch
623 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_handshake_key) catch
566624 return error.TlsBadRecordMac;
567
568 break :c cleartext;
569 },
570 .TLS_AES_256_GCM_SHA384 => c: {
571 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
572 const ciphertext_len = record_size - AEAD.tag_length;
573 const ciphertext = handshake_buf[i..][0..ciphertext_len];
574 i += ciphertext.len;
575 if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow;
576 const cleartext = cleartext_buf[0..ciphertext.len];
577 const auth_tag = handshake_buf[i..][0..AEAD.tag_length].*;
578 const V = @Vector(AEAD.nonce_length, u8);
579 const pad = [1]u8{0} ** (AEAD.nonce_length - 8);
580 const operand: V = pad ++ @bitCast([8]u8, big(read_seq));
581 read_seq += 1;
582 const nonce: [AEAD.nonce_length]u8 = @as(V, server_handshake_iv) ^ operand;
583 const ad = handshake_buf[end_hdr - 5 ..][0..5];
584 const key = server_handshake_key[0..AEAD.key_length].*;
585 AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, key) catch
586 return error.TlsBadRecordMac;
587
625 p.transcript_hash.update(cleartext[0 .. cleartext.len - 1]);
588626 break :c cleartext;
589627 },
590628 .TLS_CHACHA20_POLY1305_SHA256 => {
......@@ -611,6 +649,86 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
611649 }
612650 std.debug.print("empty encrypted extensions\n", .{});
613651 },
652 @enumToInt(HandshakeType.certificate) => {
653 std.debug.print("cool certificate bro\n", .{});
654 },
655 @enumToInt(HandshakeType.certificate_verify) => {
656 std.debug.print("the certificate came with a fancy signature\n", .{});
657 },
658 @enumToInt(HandshakeType.finished) => {
659 // This message is to trick buggy proxies into behaving correctly.
660 const client_change_cipher_spec_msg = [_]u8{
661 @enumToInt(ContentType.change_cipher_spec),
662 0x03, 0x03, // legacy protocol version
663 0x00, 0x01, // length
664 0x01,
665 };
666 const app_cipher = switch (cipher_params) {
667 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p, tag| c: {
668 const P = @TypeOf(p.*);
669 // TODO verify the server's data
670 const handshake_hash = p.transcript_hash.finalResult();
671 const verify_data = hmac(P.Hmac, &handshake_hash, p.client_finished_key);
672 const out_cleartext = [_]u8{
673 @enumToInt(HandshakeType.finished),
674 0, 0, verify_data.len + 1 + P.AEAD.tag_length, // length
675 } ++ verify_data ++ [1]u8{@enumToInt(ContentType.handshake)};
676
677 const wrapped_len = out_cleartext.len + P.AEAD.tag_length;
678
679 var finished_msg = [_]u8{
680 @enumToInt(ContentType.application_data),
681 0x03, 0x03, // legacy protocol version
682 0, wrapped_len, // byte length of encrypted record
683 } ++ ([1]u8{undefined} ** wrapped_len);
684
685 const ad = finished_msg[0..5];
686 const ciphertext = finished_msg[5..][0..out_cleartext.len];
687 const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..];
688 const nonce = p.client_handshake_iv;
689 P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key);
690
691 {
692 var iovecs = [_]std.os.iovec_const{
693 .{
694 .iov_base = &client_change_cipher_spec_msg,
695 .iov_len = client_change_cipher_spec_msg.len,
696 },
697 .{
698 .iov_base = &finished_msg,
699 .iov_len = finished_msg.len,
700 },
701 };
702 try stream.writevAll(&iovecs);
703 }
704
705 const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
706 const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
707 break :c @unionInit(ApplicationCipher, @tagName(tag), .{
708 .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length),
709 .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length),
710 .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length),
711 .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length),
712 });
713 },
714 .TLS_CHACHA20_POLY1305_SHA256 => {
715 @panic("TODO");
716 },
717 .TLS_AES_128_CCM_SHA256 => {
718 @panic("TODO");
719 },
720 .TLS_AES_128_CCM_8_SHA256 => {
721 @panic("TODO");
722 },
723 };
724 return .{
725 .application_cipher = app_cipher,
726 .read_seq = read_seq,
727 .write_seq = 1,
728 .partially_read_buffer = undefined,
729 .partially_read_len = 0,
730 };
731 },
614732 else => {
615733 std.debug.print("handshake type: {d}\n", .{cleartext[0]});
616734 return error.TlsUnexpectedMessage;
......@@ -631,14 +749,185 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
631749 i = end;
632750 }
633751
634 tls.state = .sent_hello;
752 return error.TlsHandshakeFailure;
753}
754
755pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize {
756 var ciphertext_buf: [max_ciphertext_len * 4]u8 = undefined;
757 var iovecs_buf: [5]std.os.iovec_const = undefined;
758 var ciphertext_end: usize = 0;
759 var iovec_end: usize = 0;
760 var bytes_i: usize = 0;
761 switch (tls.application_cipher) {
762 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| {
763 const P = @TypeOf(p.*);
764 const V = @Vector(P.AEAD.nonce_length, u8);
765 while (true) {
766 const ciphertext_len = @intCast(u16, @min(
767 @min(bytes.len - bytes_i, max_ciphertext_len),
768 ciphertext_buf.len - 5 - P.AEAD.tag_length - ciphertext_end,
769 ));
770 if (ciphertext_len == 0) return bytes_i;
771
772 const wrapped_len = ciphertext_len + P.AEAD.tag_length;
773 const record = ciphertext_buf[ciphertext_end..][0 .. 5 + wrapped_len];
774
775 const ad = record[0..5];
776 ciphertext_end += 5;
777 const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len];
778 ciphertext_end += ciphertext_len;
779 const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length];
780 ciphertext_end += P.AEAD.tag_length;
781 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
782 const operand: V = pad ++ @bitCast([8]u8, big(tls.write_seq));
783 tls.write_seq += 1;
784 const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.client_iv) ^ operand;
785 ad.* =
786 [_]u8{@enumToInt(ContentType.application_data)} ++
787 int2(@enumToInt(ProtocolVersion.tls_1_2)) ++
788 int2(wrapped_len);
789 const cleartext = bytes[bytes_i..ciphertext.len];
790 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key);
791
792 iovecs_buf[iovec_end] = .{
793 .iov_base = record.ptr,
794 .iov_len = record.len,
795 };
796 iovec_end += 1;
797
798 bytes_i += ciphertext_len;
799 }
800 },
801 .TLS_CHACHA20_POLY1305_SHA256 => {
802 @panic("TODO");
803 },
804 .TLS_AES_128_CCM_SHA256 => {
805 @panic("TODO");
806 },
807 .TLS_AES_128_CCM_8_SHA256 => {
808 @panic("TODO");
809 },
810 }
811
812 // Ideally we would call writev exactly once here, however, we must ensure
813 // that we don't return with a record partially written.
814 var i: usize = 0;
815 var total_amt: usize = 0;
816 while (true) {
817 var amt = try stream.writev(iovecs_buf[i..iovec_end]);
818 total_amt += amt;
819 while (amt >= iovecs_buf[i].iov_len) {
820 amt -= iovecs_buf[i].iov_len;
821 i += 1;
822 // Rely on the property that iovecs delineate records, meaning that
823 // if amt equals zero here, we have fortunately found ourselves
824 // with a short read that aligns at the record boundary.
825 if (i >= iovec_end or amt == 0) return total_amt;
826 }
827 iovecs_buf[i].iov_base += amt;
828 iovecs_buf[i].iov_len -= amt;
829 }
635830}
636831
637pub fn writeAll(tls: *Tls, stream: net.Stream, buffer: []const u8) !void {
638 _ = tls;
639 _ = stream;
640 _ = buffer;
641 @panic("hold on a minute, we didn't finish implementing the handshake yet");
832pub fn writeAll(tls: *Tls, stream: net.Stream, bytes: []const u8) !void {
833 var index: usize = 0;
834 while (index < bytes.len) {
835 index += try tls.write(stream, bytes[index..]);
836 }
837}
838
839/// Returns number of bytes that have been read, which are now populated inside
840/// `buffer`. A return value of zero bytes does not necessarily mean end of
841/// stream.
842pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize {
843 const prev_len = tls.partially_read_len;
844 var in_buf: [max_ciphertext_len * 4]u8 = undefined;
845 mem.copy(u8, &in_buf, tls.partially_read_buffer[0..prev_len]);
846
847 // Capacity of output buffer, in records, rounded up.
848 const buf_cap = (buffer.len +| (max_ciphertext_len - 1)) / max_ciphertext_len;
849 const wanted_read_len = buf_cap * (max_ciphertext_len + ciphertext_record_header_len);
850 const actual_read_len = try stream.read(in_buf[prev_len..@min(wanted_read_len, in_buf.len)]);
851 const frag = in_buf[0 .. prev_len + actual_read_len];
852 var in: usize = 0;
853 var out: usize = 0;
854
855 while (true) {
856 if (in + ciphertext_record_header_len > frag.len) {
857 return finishRead(tls, frag, in, out);
858 }
859 const ct = @intToEnum(ContentType, frag[in]);
860 in += 1;
861 const legacy_version = mem.readIntBig(u16, frag[in..][0..2]);
862 in += 2;
863 _ = legacy_version;
864 const record_size = mem.readIntBig(u16, frag[in..][0..2]);
865 in += 2;
866 const end = in + record_size;
867 if (end > frag.len) {
868 if (record_size > max_ciphertext_len) return error.TlsRecordOverflow;
869 return finishRead(tls, frag, in, out);
870 }
871 switch (ct) {
872 .alert => {
873 @panic("TODO handle an alert here");
874 },
875 .application_data => {
876 const cleartext_len = switch (tls.application_cipher) {
877 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: {
878 const P = @TypeOf(p.*);
879 const V = @Vector(P.AEAD.nonce_length, u8);
880 const ciphertext_len = record_size - P.AEAD.tag_length;
881 const ciphertext = frag[in..][0..ciphertext_len];
882 in += ciphertext_len;
883 const auth_tag = frag[in..][0..P.AEAD.tag_length].*;
884 const cleartext = buffer[out..][0..ciphertext_len];
885 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
886 const operand: V = pad ++ @bitCast([8]u8, big(tls.read_seq));
887 tls.read_seq += 1;
888 const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand;
889 const ad = frag[0..ciphertext_record_header_len];
890 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch
891 return error.TlsBadRecordMac;
892 break :c cleartext.len;
893 },
894 .TLS_CHACHA20_POLY1305_SHA256 => {
895 @panic("TODO");
896 },
897 .TLS_AES_128_CCM_SHA256 => {
898 @panic("TODO");
899 },
900 .TLS_AES_128_CCM_8_SHA256 => {
901 @panic("TODO");
902 },
903 };
904
905 const inner_ct = buffer[out + cleartext_len - 1];
906 switch (inner_ct) {
907 @enumToInt(ContentType.handshake) => {
908 std.debug.print("the server wants to keep shaking hands\n", .{});
909 },
910 @enumToInt(ContentType.application_data) => {
911 out += cleartext_len - 1;
912 },
913 else => {
914 return error.TlsUnexpectedMessage;
915 },
916 }
917 },
918 else => {
919 return error.TlsUnexpectedMessage;
920 },
921 }
922 in = end;
923 }
924}
925
926fn finishRead(tls: *Tls, frag: []const u8, in: usize, out: usize) usize {
927 const saved_buf = frag[in..];
928 mem.copy(u8, &tls.partially_read_buffer, saved_buf);
929 tls.partially_read_len = @intCast(u15, saved_buf.len);
930 return out;
642931}
643932
644933fn hkdfExpandLabel(
......@@ -674,13 +963,9 @@ fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 {
674963 return result;
675964}
676965
677fn helloHash(s0: []const u8, s1: []const u8, s2: []const u8, comptime Hash: type) [Hash.digest_length]u8 {
678 var h = Hash.init(.{});
679 h.update(s0);
680 h.update(s1);
681 h.update(s2);
682 var result: [Hash.digest_length]u8 = undefined;
683 h.final(&result);
966fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 {
967 var result: [Hmac.mac_length]u8 = undefined;
968 Hmac.create(&result, message, &key);
684969 return result;
685970}
686971
......@@ -693,3 +978,10 @@ inline fn big(x: anytype) @TypeOf(x) {
693978 .Little => @byteSwap(x),
694979 };
695980}
981
982inline fn int2(x: u16) [2]u8 {
983 return .{
984 @truncate(u8, x >> 8),
985 @truncate(u8, x),
986 };
987}
lib/std/crypto/sha2.zig+22
......@@ -142,6 +142,11 @@ fn Sha2x32(comptime params: Sha2Params32) type {
142142 d.total_len += b.len;
143143 }
144144
145 pub fn peek(d: Self) [digest_length]u8 {
146 var copy = d;
147 return copy.finalResult();
148 }
149
145150 pub fn final(d: *Self, out: *[digest_length]u8) void {
146151 // The buffer here will never be completely full.
147152 mem.set(u8, d.buf[d.buf_len..], 0);
......@@ -175,6 +180,12 @@ fn Sha2x32(comptime params: Sha2Params32) type {
175180 }
176181 }
177182
183 pub fn finalResult(d: *Self) [digest_length]u8 {
184 var result: [digest_length]u8 = undefined;
185 d.final(&result);
186 return result;
187 }
188
178189 const W = [64]u32{
179190 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
180191 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
......@@ -621,6 +632,11 @@ fn Sha2x64(comptime params: Sha2Params64) type {
621632 d.total_len += b.len;
622633 }
623634
635 pub fn peek(d: Self) [digest_length]u8 {
636 var copy = d;
637 return copy.finalResult();
638 }
639
624640 pub fn final(d: *Self, out: *[digest_length]u8) void {
625641 // The buffer here will never be completely full.
626642 mem.set(u8, d.buf[d.buf_len..], 0);
......@@ -654,6 +670,12 @@ fn Sha2x64(comptime params: Sha2Params64) type {
654670 }
655671 }
656672
673 pub fn finalResult(d: *Self) [digest_length]u8 {
674 var result: [digest_length]u8 = undefined;
675 d.final(&result);
676 return result;
677 }
678
657679 fn round(d: *Self, b: *const [128]u8) void {
658680 var s: [80]u64 = undefined;
659681
lib/std/http/Client.zig+10-2
......@@ -12,7 +12,7 @@ pub const Request = struct {
1212 client: *Client,
1313 stream: net.Stream,
1414 headers: std.ArrayListUnmanaged(u8) = .{},
15 tls: std.crypto.Tls = .{},
15 tls: std.crypto.Tls,
1616 protocol: Protocol,
1717
1818 pub const Protocol = enum { http, https };
......@@ -55,6 +55,13 @@ pub const Request = struct {
5555 },
5656 }
5757 }
58
59 pub fn read(req: *Request, buffer: []u8) !usize {
60 switch (req.protocol) {
61 .http => return req.stream.read(buffer),
62 .https => return req.tls.read(req.stream, buffer),
63 }
64 }
5865};
5966
6067pub fn deinit(client: *Client) void {
......@@ -68,6 +75,7 @@ pub fn request(client: *Client, options: Request.Options) !Request {
6875 .client = client,
6976 .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port),
7077 .protocol = options.protocol,
78 .tls = undefined,
7179 };
7280 client.active_requests += 1;
7381 errdefer req.deinit();
......@@ -75,7 +83,7 @@ pub fn request(client: *Client, options: Request.Options) !Request {
7583 switch (options.protocol) {
7684 .http => {},
7785 .https => {
78 try req.tls.init(req.stream, options.host);
86 req.tls = try std.crypto.Tls.init(req.stream, options.host);
7987 },
8088 }
8189