| ... | ... | @@ -8,6 +8,13 @@ const assert = std.debug.assert; |
| 8 | 8 | state: State = .start, |
| 9 | 9 | x25519_priv_key: [32]u8 = undefined, |
| 10 | 10 | x25519_pub_key: [32]u8 = undefined, |
| 11 | x25519_server_pub_key: [32]u8 = undefined, |
| 12 | |
| 13 | const ProtocolVersion = enum(u16) { |
| 14 | tls_1_2 = 0x0303, |
| 15 | tls_1_3 = 0x0304, |
| 16 | _, |
| 17 | }; |
| 11 | 18 | |
| 12 | 19 | const State = enum { |
| 13 | 20 | /// In this state, all fields are undefined except state. |
| ... | ... | @@ -186,6 +193,18 @@ const NamedGroup = enum(u16) { |
| 186 | 193 | // * length: u24 |
| 187 | 194 | // * data: opaque |
| 188 | 195 | |
| 196 | // ServerHello: |
| 197 | // * ProtocolVersion legacy_version = 0x0303; |
| 198 | // * Random random; |
| 199 | // * opaque legacy_session_id_echo<0..32>; |
| 200 | // * CipherSuite cipher_suite; |
| 201 | // * uint8 legacy_compression_method = 0; |
| 202 | // * Extension extensions<6..2^16-1>; |
| 203 | |
| 204 | // Extension: |
| 205 | // * ExtensionType extension_type; |
| 206 | // * opaque extension_data<0..2^16-1>; |
| 207 | |
| 189 | 208 | const CipherSuite = enum(u16) { |
| 190 | 209 | TLS_AES_128_GCM_SHA256 = 0x1301, |
| 191 | 210 | TLS_AES_256_GCM_SHA384 = 0x1302, |
| ... | ... | @@ -259,10 +278,10 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void { |
| 259 | 278 | |
| 260 | 279 | // Extension: key_share |
| 261 | 280 | 0, 51, // ExtensionType.key_share |
| 262 | | 0x00, 38, // byte length of this extension payload |
| 263 | | 0x00, 36, // byte length of client_shares |
| 281 | 0, 38, // byte length of this extension payload |
| 282 | 0, 36, // byte length of client_shares |
| 264 | 283 | 0x00, 0x1D, // NamedGroup.x25519 |
| 265 | | 0x00, 32, // byte length of key_exchange |
| 284 | 0, 32, // byte length of key_exchange |
| 266 | 285 | } ++ tls.x25519_pub_key ++ [_]u8{ |
| 267 | 286 | |
| 268 | 287 | // Extension: server_name |
| ... | ... | @@ -313,21 +332,103 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void { |
| 313 | 332 | try stream.writevAll(&iovecs); |
| 314 | 333 | |
| 315 | 334 | { |
| 316 | | var buf: [1000]u8 = undefined; |
| 317 | | const amt = try stream.read(&buf); |
| 318 | | const resp = buf[0..amt]; |
| 319 | | const ct = @intToEnum(ContentType, resp[0]); |
| 335 | var handshake_buf: [4000]u8 = undefined; |
| 336 | const plaintext = handshake_buf[0..5]; |
| 337 | const amt = try stream.readAtLeast(&handshake_buf, plaintext.len); |
| 338 | if (amt < plaintext.len) return error.EndOfStream; |
| 339 | const ct = @intToEnum(ContentType, plaintext[0]); |
| 340 | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); |
| 341 | const end = plaintext.len + frag_len; |
| 342 | if (end > handshake_buf.len) return error.TlsServerHelloTooBig; |
| 343 | if (amt < end) { |
| 344 | const amt2 = try stream.readAll(handshake_buf[amt..end]); |
| 345 | if (amt2 < plaintext.len) return error.EndOfStream; |
| 346 | } |
| 347 | const frag = handshake_buf[plaintext.len..end]; |
| 348 | |
| 320 | 349 | if (ct == .alert) { |
| 321 | | //const prot_ver = @bitCast(u16, resp[1..][0..2].*); |
| 322 | | const len = std.mem.readIntBig(u16, resp[3..][0..2]); |
| 323 | | const alert = resp[5..][0..len]; |
| 324 | | const level = @intToEnum(AlertLevel, alert[0]); |
| 325 | | const desc = @intToEnum(AlertDescription, alert[1]); |
| 350 | const level = @intToEnum(AlertLevel, frag[0]); |
| 351 | const desc = @intToEnum(AlertDescription, frag[1]); |
| 326 | 352 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); |
| 327 | 353 | std.process.exit(1); |
| 354 | } else if (ct == .handshake) { |
| 355 | if (frag[0] != @enumToInt(HandshakeType.server_hello)) { |
| 356 | return error.TlsUnexpectedMessage; |
| 357 | } |
| 358 | const length = mem.readIntBig(u24, frag[1..4]); |
| 359 | if (4 + length != frag.len) return error.TlsBadLength; |
| 360 | const hello = frag[4..]; |
| 361 | const legacy_version = mem.readIntBig(u16, hello[0..2]); |
| 362 | const random = hello[2..34].*; |
| 363 | _ = random; |
| 364 | const legacy_session_id_echo_len = hello[34]; |
| 365 | if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter; |
| 366 | const cipher_suite_int = mem.readIntBig(u16, hello[35..37]); |
| 367 | const cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch |
| 368 | return error.TlsIllegalParameter; |
| 369 | std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)}); |
| 370 | const legacy_compression_method = hello[37]; |
| 371 | _ = legacy_compression_method; |
| 372 | const extensions_size = mem.readIntBig(u16, hello[38..40]); |
| 373 | if (40 + extensions_size != hello.len) return error.TlsBadLength; |
| 374 | var i: usize = 40; |
| 375 | var supported_version: u16 = 0; |
| 376 | var have_server_pub_key = false; |
| 377 | while (i < hello.len) { |
| 378 | const et = mem.readIntBig(u16, hello[i..][0..2]); |
| 379 | i += 2; |
| 380 | const ext_size = mem.readIntBig(u16, hello[i..][0..2]); |
| 381 | i += 2; |
| 382 | const next_i = i + ext_size; |
| 383 | if (next_i > hello.len) return error.TlsBadLength; |
| 384 | switch (et) { |
| 385 | @enumToInt(ExtensionType.supported_versions) => { |
| 386 | if (supported_version != 0) return error.TlsIllegalParameter; |
| 387 | supported_version = mem.readIntBig(u16, hello[i..][0..2]); |
| 388 | }, |
| 389 | @enumToInt(ExtensionType.key_share) => { |
| 390 | if (have_server_pub_key) return error.TlsIllegalParameter; |
| 391 | const named_group = mem.readIntBig(u16, hello[i..][0..2]); |
| 392 | i += 2; |
| 393 | switch (named_group) { |
| 394 | @enumToInt(NamedGroup.x25519) => { |
| 395 | const key_size = mem.readIntBig(u16, hello[i..][0..2]); |
| 396 | i += 2; |
| 397 | if (key_size != 32) return error.TlsBadLength; |
| 398 | const encrypted_key = hello[i..][0..32].*; |
| 399 | const server_pub_key = try crypto.dh.X25519.scalarmult( |
| 400 | tls.x25519_priv_key, |
| 401 | encrypted_key, |
| 402 | ); |
| 403 | tls.x25519_server_pub_key = server_pub_key; |
| 404 | have_server_pub_key = true; |
| 405 | }, |
| 406 | else => { |
| 407 | std.debug.print("named group: {x}\n", .{named_group}); |
| 408 | return error.TlsIllegalParameter; |
| 409 | }, |
| 410 | } |
| 411 | }, |
| 412 | else => { |
| 413 | std.debug.print("unexpected extension: {x}\n", .{et}); |
| 414 | }, |
| 415 | } |
| 416 | i = next_i; |
| 417 | } |
| 418 | if (!have_server_pub_key) return error.TlsIllegalParameter; |
| 419 | const tls_version = if (supported_version == 0) legacy_version else supported_version; |
| 420 | switch (tls_version) { |
| 421 | @enumToInt(ProtocolVersion.tls_1_2) => { |
| 422 | std.debug.print("server wants TLS v1.2\n", .{}); |
| 423 | }, |
| 424 | @enumToInt(ProtocolVersion.tls_1_3) => { |
| 425 | std.debug.print("server wants TLS v1.3\n", .{}); |
| 426 | }, |
| 427 | else => return error.TlsIllegalParameter, |
| 428 | } |
| 328 | 429 | } else { |
| 329 | 430 | std.debug.print("content_type: {s}\n", .{@tagName(ct)}); |
| 330 | | std.debug.print("got {d} bytes: {s}\n", .{ amt, std.fmt.fmtSliceHexLower(resp) }); |
| 431 | std.debug.print("got {d} bytes: {s}\n", .{ amt, std.fmt.fmtSliceHexLower(frag) }); |
| 331 | 432 | } |
| 332 | 433 | } |
| 333 | 434 | |