| ... | ... | @@ -126,88 +126,73 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 126 | 126 | const client_hello_bytes1 = plaintext_header[5..]; |
| 127 | 127 | |
| 128 | 128 | var handshake_cipher: tls.HandshakeCipher = undefined; |
| 129 | | |
| 130 | | var handshake_buf: [8000]u8 = undefined; |
| 131 | | var len: usize = 0; |
| 132 | | var i: usize = i: { |
| 133 | | const plaintext = handshake_buf[0..5]; |
| 134 | | len = try stream.readAtLeast(&handshake_buf, plaintext.len); |
| 135 | | if (len < plaintext.len) return error.EndOfStream; |
| 136 | | const ct = @intToEnum(tls.ContentType, plaintext[0]); |
| 137 | | const frag_len = mem.readIntBig(u16, plaintext[3..][0..2]); |
| 138 | | const end = plaintext.len + frag_len; |
| 139 | | if (end > handshake_buf.len) return error.TlsRecordOverflow; |
| 140 | | if (end > len) { |
| 141 | | len += try stream.readAtLeast(handshake_buf[len..], end - len); |
| 142 | | if (end > len) return error.EndOfStream; |
| 143 | | } |
| 144 | | const frag = handshake_buf[plaintext.len..end]; |
| 145 | | |
| 129 | var handshake_buffer: [8000]u8 = undefined; |
| 130 | var d: tls.Decoder = .{ .buf = &handshake_buffer }; |
| 131 | { |
| 132 | try d.readAtLeastOurAmt(stream, tls.record_header_len); |
| 133 | const ct = d.decode(tls.ContentType); |
| 134 | d.skip(2); // legacy_record_version |
| 135 | const record_len = d.decode(u16); |
| 136 | try d.readAtLeast(stream, record_len); |
| 137 | const server_hello_fragment = d.buf[d.idx..][0..record_len]; |
| 138 | var ptd = try d.sub(record_len); |
| 146 | 139 | switch (ct) { |
| 147 | 140 | .alert => { |
| 148 | | const level = @intToEnum(tls.AlertLevel, frag[0]); |
| 149 | | const desc = @intToEnum(tls.AlertDescription, frag[1]); |
| 141 | try ptd.ensure(2); |
| 142 | const level = ptd.decode(tls.AlertLevel); |
| 143 | const desc = ptd.decode(tls.AlertDescription); |
| 150 | 144 | _ = level; |
| 151 | 145 | _ = desc; |
| 152 | 146 | return error.TlsAlert; |
| 153 | 147 | }, |
| 154 | 148 | .handshake => { |
| 155 | | if (frag[0] != @enumToInt(tls.HandshakeType.server_hello)) { |
| 149 | try ptd.ensure(4); |
| 150 | const handshake_type = ptd.decode(tls.HandshakeType); |
| 151 | if (handshake_type != .server_hello) return error.TlsUnexpectedMessage; |
| 152 | const length = ptd.decode(u24); |
| 153 | var hsd = try ptd.sub(length); |
| 154 | try hsd.ensure(2 + 32 + 1 + 32 + 2 + 1 + 2); |
| 155 | const legacy_version = hsd.decode(u16); |
| 156 | const random = hsd.array(32); |
| 157 | if (mem.eql(u8, random, &tls.hello_retry_request_sequence)) { |
| 158 | // This is a HelloRetryRequest message. This client implementation |
| 159 | // does not expect to get one. |
| 156 | 160 | return error.TlsUnexpectedMessage; |
| 157 | 161 | } |
| 158 | | const length = mem.readIntBig(u24, frag[1..4]); |
| 159 | | if (4 + length != frag.len) return error.TlsBadLength; |
| 160 | | var i: usize = 4; |
| 161 | | const legacy_version = mem.readIntBig(u16, frag[i..][0..2]); |
| 162 | | i += 2; |
| 163 | | const random = frag[i..][0..32].*; |
| 164 | | i += 32; |
| 165 | | if (mem.eql(u8, &random, &tls.hello_retry_request_sequence)) { |
| 166 | | @panic("TODO handle HelloRetryRequest"); |
| 167 | | } |
| 168 | | const legacy_session_id_echo_len = frag[i]; |
| 169 | | i += 1; |
| 162 | const legacy_session_id_echo_len = hsd.decode(u8); |
| 170 | 163 | if (legacy_session_id_echo_len != 32) return error.TlsIllegalParameter; |
| 171 | | const legacy_session_id_echo = frag[i..][0..32]; |
| 164 | const legacy_session_id_echo = hsd.array(32); |
| 172 | 165 | if (!mem.eql(u8, legacy_session_id_echo, &legacy_session_id)) |
| 173 | 166 | return error.TlsIllegalParameter; |
| 174 | | i += 32; |
| 175 | | const cipher_suite_int = mem.readIntBig(u16, frag[i..][0..2]); |
| 176 | | i += 2; |
| 177 | | const cipher_suite_tag = @intToEnum(tls.CipherSuite, cipher_suite_int); |
| 178 | | const legacy_compression_method = frag[i]; |
| 179 | | i += 1; |
| 180 | | _ = legacy_compression_method; |
| 181 | | const extensions_size = mem.readIntBig(u16, frag[i..][0..2]); |
| 182 | | i += 2; |
| 183 | | if (i + extensions_size != frag.len) return error.TlsBadLength; |
| 167 | const cipher_suite_tag = hsd.decode(tls.CipherSuite); |
| 168 | hsd.skip(1); // legacy_compression_method |
| 169 | const extensions_size = hsd.decode(u16); |
| 170 | var all_extd = try hsd.sub(extensions_size); |
| 184 | 171 | var supported_version: u16 = 0; |
| 185 | 172 | var shared_key: [32]u8 = undefined; |
| 186 | 173 | var have_shared_key = false; |
| 187 | | while (i < frag.len) { |
| 188 | | const et = @intToEnum(tls.ExtensionType, mem.readIntBig(u16, frag[i..][0..2])); |
| 189 | | i += 2; |
| 190 | | const ext_size = mem.readIntBig(u16, frag[i..][0..2]); |
| 191 | | i += 2; |
| 192 | | const next_i = i + ext_size; |
| 193 | | if (next_i > frag.len) return error.TlsBadLength; |
| 174 | while (!all_extd.eof()) { |
| 175 | try all_extd.ensure(2 + 2); |
| 176 | const et = all_extd.decode(tls.ExtensionType); |
| 177 | const ext_size = all_extd.decode(u16); |
| 178 | var extd = try all_extd.sub(ext_size); |
| 194 | 179 | switch (et) { |
| 195 | 180 | .supported_versions => { |
| 196 | 181 | if (supported_version != 0) return error.TlsIllegalParameter; |
| 197 | | supported_version = mem.readIntBig(u16, frag[i..][0..2]); |
| 182 | try extd.ensure(2); |
| 183 | supported_version = extd.decode(u16); |
| 198 | 184 | }, |
| 199 | 185 | .key_share => { |
| 200 | 186 | if (have_shared_key) return error.TlsIllegalParameter; |
| 201 | 187 | have_shared_key = true; |
| 202 | | const named_group = @intToEnum(tls.NamedGroup, mem.readIntBig(u16, frag[i..][0..2])); |
| 203 | | i += 2; |
| 204 | | const key_size = mem.readIntBig(u16, frag[i..][0..2]); |
| 205 | | i += 2; |
| 206 | | |
| 188 | try extd.ensure(4); |
| 189 | const named_group = extd.decode(tls.NamedGroup); |
| 190 | const key_size = extd.decode(u16); |
| 191 | try extd.ensure(key_size); |
| 207 | 192 | switch (named_group) { |
| 208 | 193 | .x25519 => { |
| 209 | | if (key_size != 32) return error.TlsBadLength; |
| 210 | | const server_pub_key = frag[i..][0..32]; |
| 194 | if (key_size != 32) return error.TlsIllegalParameter; |
| 195 | const server_pub_key = extd.array(32); |
| 211 | 196 | |
| 212 | 197 | shared_key = crypto.dh.X25519.scalarmult( |
| 213 | 198 | x25519_kp.secret_key, |
| ... | ... | @@ -215,7 +200,7 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 215 | 200 | ) catch return error.TlsDecryptFailure; |
| 216 | 201 | }, |
| 217 | 202 | .secp256r1 => { |
| 218 | | const server_pub_key = frag[i..][0..key_size]; |
| 203 | const server_pub_key = extd.slice(key_size); |
| 219 | 204 | |
| 220 | 205 | const PublicKey = crypto.sign.ecdsa.EcdsaP256Sha256.PublicKey; |
| 221 | 206 | const pk = PublicKey.fromSec1(server_pub_key) catch { |
| ... | ... | @@ -233,14 +218,12 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 233 | 218 | }, |
| 234 | 219 | else => {}, |
| 235 | 220 | } |
| 236 | | i = next_i; |
| 237 | 221 | } |
| 238 | 222 | if (!have_shared_key) return error.TlsIllegalParameter; |
| 223 | |
| 239 | 224 | const tls_version = if (supported_version == 0) legacy_version else supported_version; |
| 240 | | switch (tls_version) { |
| 241 | | @enumToInt(tls.ProtocolVersion.tls_1_3) => {}, |
| 242 | | else => return error.TlsIllegalParameter, |
| 243 | | } |
| 225 | if (tls_version != @enumToInt(tls.ProtocolVersion.tls_1_3)) |
| 226 | return error.TlsIllegalParameter; |
| 244 | 227 | |
| 245 | 228 | switch (cipher_suite_tag) { |
| 246 | 229 | inline .AES_128_GCM_SHA256, |
| ... | ... | @@ -264,7 +247,7 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 264 | 247 | const p = &@field(handshake_cipher, @tagName(tag)); |
| 265 | 248 | p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1 |
| 266 | 249 | p.transcript_hash.update(host); // Client Hello part 2 |
| 267 | | p.transcript_hash.update(frag); // Server Hello |
| 250 | p.transcript_hash.update(server_hello_fragment); |
| 268 | 251 | const hello_hash = p.transcript_hash.peek(); |
| 269 | 252 | const zeroes = [1]u8{0} ** P.Hash.digest_length; |
| 270 | 253 | const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes); |
| ... | ... | @@ -289,8 +272,7 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 289 | 272 | }, |
| 290 | 273 | else => return error.TlsUnexpectedMessage, |
| 291 | 274 | } |
| 292 | | break :i end; |
| 293 | | }; |
| 275 | } |
| 294 | 276 | |
| 295 | 277 | // This is used for two purposes: |
| 296 | 278 | // * Detect whether a certificate is the first one presented, in which case |
| ... | ... | @@ -322,29 +304,17 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 322 | 304 | var main_cert_pub_key_len: u16 = undefined; |
| 323 | 305 | |
| 324 | 306 | while (true) { |
| 325 | | const end_hdr = i + 5; |
| 326 | | if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow; |
| 327 | | if (end_hdr > len) { |
| 328 | | len += try stream.readAtLeast(handshake_buf[len..], end_hdr - len); |
| 329 | | if (end_hdr > len) return error.EndOfStream; |
| 330 | | } |
| 331 | | const ct = @intToEnum(tls.ContentType, handshake_buf[i]); |
| 332 | | i += 1; |
| 333 | | const legacy_version = mem.readIntBig(u16, handshake_buf[i..][0..2]); |
| 334 | | i += 2; |
| 335 | | _ = legacy_version; |
| 336 | | const record_size = mem.readIntBig(u16, handshake_buf[i..][0..2]); |
| 337 | | i += 2; |
| 338 | | const end = i + record_size; |
| 339 | | if (end > handshake_buf.len) return error.TlsRecordOverflow; |
| 340 | | if (end > len) { |
| 341 | | len += try stream.readAtLeast(handshake_buf[len..], end - len); |
| 342 | | if (end > len) return error.EndOfStream; |
| 343 | | } |
| 307 | try d.readAtLeastOurAmt(stream, tls.record_header_len); |
| 308 | const record_header = d.buf[d.idx..][0..5]; |
| 309 | const ct = d.decode(tls.ContentType); |
| 310 | d.skip(2); // legacy_version |
| 311 | const record_len = d.decode(u16); |
| 312 | try d.readAtLeast(stream, record_len); |
| 313 | var record_decoder = try d.sub(record_len); |
| 344 | 314 | switch (ct) { |
| 345 | 315 | .change_cipher_spec => { |
| 346 | | if (record_size != 1) return error.TlsUnexpectedMessage; |
| 347 | | if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage; |
| 316 | try record_decoder.ensure(1); |
| 317 | if (record_decoder.decode(u8) != 0x01) return error.TlsIllegalParameter; |
| 348 | 318 | }, |
| 349 | 319 | .application_data => { |
| 350 | 320 | const cleartext_buf = &cleartext_bufs[cert_index % 2]; |
| ... | ... | @@ -352,276 +322,261 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 352 | 322 | const cleartext = switch (handshake_cipher) { |
| 353 | 323 | inline else => |*p| c: { |
| 354 | 324 | const P = @TypeOf(p.*); |
| 355 | | const ciphertext_len = record_size - P.AEAD.tag_length; |
| 356 | | const ciphertext = handshake_buf[i..][0..ciphertext_len]; |
| 357 | | i += ciphertext.len; |
| 325 | const ciphertext_len = record_len - P.AEAD.tag_length; |
| 326 | try record_decoder.ensure(ciphertext_len + P.AEAD.tag_length); |
| 327 | const ciphertext = record_decoder.slice(ciphertext_len); |
| 358 | 328 | if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow; |
| 359 | 329 | const cleartext = cleartext_buf[0..ciphertext.len]; |
| 360 | | const auth_tag = handshake_buf[i..][0..P.AEAD.tag_length].*; |
| 330 | const auth_tag = record_decoder.array(P.AEAD.tag_length).*; |
| 361 | 331 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 362 | 332 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 363 | 333 | const operand: V = pad ++ @bitCast([8]u8, big(read_seq)); |
| 364 | 334 | read_seq += 1; |
| 365 | 335 | const nonce = @as(V, p.server_handshake_iv) ^ operand; |
| 366 | | const ad = handshake_buf[end_hdr - 5 ..][0..5]; |
| 367 | | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_handshake_key) catch |
| 336 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, p.server_handshake_key) catch |
| 368 | 337 | return error.TlsBadRecordMac; |
| 369 | 338 | break :c cleartext; |
| 370 | 339 | }, |
| 371 | 340 | }; |
| 372 | 341 | |
| 373 | 342 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); |
| 374 | | switch (inner_ct) { |
| 375 | | .handshake => { |
| 376 | | var ct_i: usize = 0; |
| 377 | | while (true) { |
| 378 | | const handshake_type = @intToEnum(tls.HandshakeType, cleartext[ct_i]); |
| 379 | | ct_i += 1; |
| 380 | | const handshake_len = mem.readIntBig(u24, cleartext[ct_i..][0..3]); |
| 381 | | ct_i += 3; |
| 382 | | const next_handshake_i = ct_i + handshake_len; |
| 383 | | if (next_handshake_i > cleartext.len - 1) |
| 384 | | return error.TlsBadLength; |
| 385 | | const wrapped_handshake = cleartext[ct_i - 4 .. next_handshake_i]; |
| 386 | | const handshake = cleartext[ct_i..next_handshake_i]; |
| 387 | | switch (handshake_type) { |
| 388 | | .encrypted_extensions => { |
| 389 | | if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage; |
| 390 | | handshake_state = .certificate; |
| 391 | | switch (handshake_cipher) { |
| 392 | | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 393 | | } |
| 394 | | const total_ext_size = mem.readIntBig(u16, handshake[0..2]); |
| 395 | | var hs_i: usize = 2; |
| 396 | | const end_ext_i = 2 + total_ext_size; |
| 397 | | while (hs_i < end_ext_i) { |
| 398 | | const et = @intToEnum(tls.ExtensionType, mem.readIntBig(u16, handshake[hs_i..][0..2])); |
| 399 | | hs_i += 2; |
| 400 | | const ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]); |
| 401 | | hs_i += 2; |
| 402 | | const next_ext_i = hs_i + ext_size; |
| 403 | | switch (et) { |
| 404 | | .server_name => {}, |
| 405 | | else => {}, |
| 406 | | } |
| 407 | | hs_i = next_ext_i; |
| 408 | | } |
| 409 | | }, |
| 410 | | .certificate => cert: { |
| 411 | | switch (handshake_cipher) { |
| 412 | | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 413 | | } |
| 414 | | switch (handshake_state) { |
| 415 | | .certificate => {}, |
| 416 | | .trust_chain_established => break :cert, |
| 417 | | else => return error.TlsUnexpectedMessage, |
| 343 | if (inner_ct != .handshake) return error.TlsUnexpectedMessage; |
| 344 | |
| 345 | var ctd = tls.Decoder.fromTheirSlice(cleartext[0 .. cleartext.len - 1]); |
| 346 | while (true) { |
| 347 | try ctd.ensure(4); |
| 348 | const handshake_type = ctd.decode(tls.HandshakeType); |
| 349 | const handshake_len = ctd.decode(u24); |
| 350 | var hsd = try ctd.sub(handshake_len); |
| 351 | const wrapped_handshake = ctd.buf[ctd.idx - handshake_len - 4 .. ctd.idx]; |
| 352 | const handshake = ctd.buf[ctd.idx - handshake_len .. ctd.idx]; |
| 353 | switch (handshake_type) { |
| 354 | .encrypted_extensions => { |
| 355 | if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage; |
| 356 | handshake_state = .certificate; |
| 357 | switch (handshake_cipher) { |
| 358 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 359 | } |
| 360 | try hsd.ensure(2); |
| 361 | const total_ext_size = hsd.decode(u16); |
| 362 | var all_extd = try hsd.sub(total_ext_size); |
| 363 | while (!all_extd.eof()) { |
| 364 | try all_extd.ensure(4); |
| 365 | const et = all_extd.decode(tls.ExtensionType); |
| 366 | const ext_size = all_extd.decode(u16); |
| 367 | var extd = try all_extd.sub(ext_size); |
| 368 | _ = extd; |
| 369 | switch (et) { |
| 370 | .server_name => {}, |
| 371 | else => {}, |
| 372 | } |
| 373 | } |
| 374 | }, |
| 375 | .certificate => cert: { |
| 376 | switch (handshake_cipher) { |
| 377 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 378 | } |
| 379 | switch (handshake_state) { |
| 380 | .certificate => {}, |
| 381 | .trust_chain_established => break :cert, |
| 382 | else => return error.TlsUnexpectedMessage, |
| 383 | } |
| 384 | try hsd.ensure(1 + 4); |
| 385 | const cert_req_ctx_len = hsd.decode(u8); |
| 386 | if (cert_req_ctx_len != 0) return error.TlsIllegalParameter; |
| 387 | const certs_size = hsd.decode(u24); |
| 388 | var certs_decoder = try hsd.sub(certs_size); |
| 389 | while (!certs_decoder.eof()) { |
| 390 | try certs_decoder.ensure(3); |
| 391 | const cert_size = certs_decoder.decode(u24); |
| 392 | var certd = try certs_decoder.sub(cert_size); |
| 393 | |
| 394 | const subject_cert: Certificate = .{ |
| 395 | .buffer = certd.buf, |
| 396 | .index = @intCast(u32, certd.idx), |
| 397 | }; |
| 398 | const subject = try subject_cert.parse(); |
| 399 | if (cert_index == 0) { |
| 400 | // Verify the host on the first certificate. |
| 401 | if (!hostMatchesCommonName(host, subject.commonName())) { |
| 402 | return error.TlsCertificateHostMismatch; |
| 418 | 403 | } |
| 419 | | var hs_i: u32 = 0; |
| 420 | | const cert_req_ctx_len = handshake[hs_i]; |
| 421 | | hs_i += 1; |
| 422 | | if (cert_req_ctx_len != 0) return error.TlsIllegalParameter; |
| 423 | | const certs_size = mem.readIntBig(u24, handshake[hs_i..][0..3]); |
| 424 | | hs_i += 3; |
| 425 | | const end_certs = hs_i + certs_size; |
| 426 | | while (hs_i < end_certs) { |
| 427 | | const cert_size = mem.readIntBig(u24, handshake[hs_i..][0..3]); |
| 428 | | hs_i += 3; |
| 429 | | const end_cert = hs_i + cert_size; |
| 430 | | |
| 431 | | const subject_cert: Certificate = .{ |
| 432 | | .buffer = handshake, |
| 433 | | .index = hs_i, |
| 434 | | }; |
| 435 | | const subject = try subject_cert.parse(); |
| 436 | | if (cert_index == 0) { |
| 437 | | // Verify the host on the first certificate. |
| 438 | | if (!hostMatchesCommonName(host, subject.commonName())) { |
| 439 | | return error.TlsCertificateHostMismatch; |
| 440 | | } |
| 441 | 404 | |
| 442 | | // Keep track of the public key for |
| 443 | | // the certificate_verify message |
| 444 | | // later. |
| 445 | | main_cert_pub_key_algo = subject.pub_key_algo; |
| 446 | | const pub_key = subject.pubKey(); |
| 447 | | if (pub_key.len > main_cert_pub_key_buf.len) |
| 448 | | return error.CertificatePublicKeyInvalid; |
| 449 | | @memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len); |
| 450 | | main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len); |
| 451 | | } else { |
| 452 | | try prev_cert.verify(subject); |
| 453 | | } |
| 454 | | |
| 455 | | if (ca_bundle.verify(subject)) |_| { |
| 456 | | handshake_state = .trust_chain_established; |
| 457 | | break :cert; |
| 458 | | } else |err| switch (err) { |
| 459 | | error.CertificateIssuerNotFound => {}, |
| 460 | | else => |e| return e, |
| 461 | | } |
| 462 | | |
| 463 | | prev_cert = subject; |
| 464 | | cert_index += 1; |
| 465 | | |
| 466 | | hs_i = end_cert; |
| 467 | | const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]); |
| 468 | | hs_i += 2; |
| 469 | | hs_i += total_ext_size; |
| 470 | | } |
| 471 | | }, |
| 472 | | .certificate_verify => { |
| 473 | | switch (handshake_state) { |
| 474 | | .trust_chain_established => handshake_state = .finished, |
| 475 | | .certificate => return error.TlsCertificateNotVerified, |
| 476 | | else => return error.TlsUnexpectedMessage, |
| 477 | | } |
| 405 | // Keep track of the public key for the |
| 406 | // certificate_verify message later. |
| 407 | main_cert_pub_key_algo = subject.pub_key_algo; |
| 408 | const pub_key = subject.pubKey(); |
| 409 | if (pub_key.len > main_cert_pub_key_buf.len) |
| 410 | return error.CertificatePublicKeyInvalid; |
| 411 | @memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len); |
| 412 | main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len); |
| 413 | } else { |
| 414 | try prev_cert.verify(subject); |
| 415 | } |
| 478 | 416 | |
| 479 | | const scheme = @intToEnum(tls.SignatureScheme, mem.readIntBig(u16, handshake[0..2])); |
| 480 | | const sig_len = mem.readIntBig(u16, handshake[2..4]); |
| 481 | | if (4 + sig_len > handshake.len) return error.TlsBadLength; |
| 482 | | const encoded_sig = handshake[4..][0..sig_len]; |
| 483 | | const max_digest_len = 64; |
| 484 | | var verify_buffer = |
| 485 | | ([1]u8{0x20} ** 64) ++ |
| 486 | | "TLS 1.3, server CertificateVerify\x00".* ++ |
| 487 | | @as([max_digest_len]u8, undefined); |
| 488 | | |
| 489 | | const verify_bytes = switch (handshake_cipher) { |
| 490 | | inline else => |*p| v: { |
| 491 | | const transcript_digest = p.transcript_hash.peek(); |
| 492 | | verify_buffer[verify_buffer.len - max_digest_len ..][0..transcript_digest.len].* = transcript_digest; |
| 493 | | p.transcript_hash.update(wrapped_handshake); |
| 494 | | break :v verify_buffer[0 .. verify_buffer.len - max_digest_len + transcript_digest.len]; |
| 495 | | }, |
| 496 | | }; |
| 497 | | const main_cert_pub_key = main_cert_pub_key_buf[0..main_cert_pub_key_len]; |
| 498 | | |
| 499 | | switch (scheme) { |
| 500 | | inline .ecdsa_secp256r1_sha256, |
| 501 | | .ecdsa_secp384r1_sha384, |
| 502 | | => |comptime_scheme| { |
| 503 | | if (main_cert_pub_key_algo != .X9_62_id_ecPublicKey) |
| 504 | | return error.TlsBadSignatureScheme; |
| 505 | | const Ecdsa = SchemeEcdsa(comptime_scheme); |
| 506 | | const sig = try Ecdsa.Signature.fromDer(encoded_sig); |
| 507 | | const key = try Ecdsa.PublicKey.fromSec1(main_cert_pub_key); |
| 508 | | try sig.verify(verify_bytes, key); |
| 509 | | }, |
| 510 | | .rsa_pss_rsae_sha256 => { |
| 511 | | if (main_cert_pub_key_algo != .rsaEncryption) |
| 512 | | return error.TlsBadSignatureScheme; |
| 513 | | |
| 514 | | const Hash = crypto.hash.sha2.Sha256; |
| 515 | | const rsa = Certificate.rsa; |
| 516 | | const components = try rsa.PublicKey.parseDer(main_cert_pub_key); |
| 517 | | const exponent = components.exponent; |
| 518 | | const modulus = components.modulus; |
| 519 | | var rsa_mem_buf: [512 * 32]u8 = undefined; |
| 520 | | var fba = std.heap.FixedBufferAllocator.init(&rsa_mem_buf); |
| 521 | | const ally = fba.allocator(); |
| 522 | | switch (modulus.len) { |
| 523 | | inline 128, 256, 512 => |modulus_len| { |
| 524 | | const key = try rsa.PublicKey.fromBytes(exponent, modulus, ally); |
| 525 | | const sig = rsa.PSSSignature.fromBytes(modulus_len, encoded_sig); |
| 526 | | try rsa.PSSSignature.verify(modulus_len, sig, verify_bytes, key, Hash, ally); |
| 527 | | }, |
| 528 | | else => { |
| 529 | | return error.TlsBadRsaSignatureBitCount; |
| 530 | | }, |
| 531 | | } |
| 417 | if (ca_bundle.verify(subject)) |_| { |
| 418 | handshake_state = .trust_chain_established; |
| 419 | break :cert; |
| 420 | } else |err| switch (err) { |
| 421 | error.CertificateIssuerNotFound => {}, |
| 422 | else => |e| return e, |
| 423 | } |
| 424 | |
| 425 | prev_cert = subject; |
| 426 | cert_index += 1; |
| 427 | |
| 428 | try certs_decoder.ensure(2); |
| 429 | const total_ext_size = certs_decoder.decode(u16); |
| 430 | var all_extd = try certs_decoder.sub(total_ext_size); |
| 431 | _ = all_extd; |
| 432 | } |
| 433 | }, |
| 434 | .certificate_verify => { |
| 435 | switch (handshake_state) { |
| 436 | .trust_chain_established => handshake_state = .finished, |
| 437 | .certificate => return error.TlsCertificateNotVerified, |
| 438 | else => return error.TlsUnexpectedMessage, |
| 439 | } |
| 440 | |
| 441 | try hsd.ensure(4); |
| 442 | const scheme = hsd.decode(tls.SignatureScheme); |
| 443 | const sig_len = hsd.decode(u16); |
| 444 | try hsd.ensure(sig_len); |
| 445 | const encoded_sig = hsd.slice(sig_len); |
| 446 | const max_digest_len = 64; |
| 447 | var verify_buffer = |
| 448 | ([1]u8{0x20} ** 64) ++ |
| 449 | "TLS 1.3, server CertificateVerify\x00".* ++ |
| 450 | @as([max_digest_len]u8, undefined); |
| 451 | |
| 452 | const verify_bytes = switch (handshake_cipher) { |
| 453 | inline else => |*p| v: { |
| 454 | const transcript_digest = p.transcript_hash.peek(); |
| 455 | verify_buffer[verify_buffer.len - max_digest_len ..][0..transcript_digest.len].* = transcript_digest; |
| 456 | p.transcript_hash.update(wrapped_handshake); |
| 457 | break :v verify_buffer[0 .. verify_buffer.len - max_digest_len + transcript_digest.len]; |
| 458 | }, |
| 459 | }; |
| 460 | const main_cert_pub_key = main_cert_pub_key_buf[0..main_cert_pub_key_len]; |
| 461 | |
| 462 | switch (scheme) { |
| 463 | inline .ecdsa_secp256r1_sha256, |
| 464 | .ecdsa_secp384r1_sha384, |
| 465 | => |comptime_scheme| { |
| 466 | if (main_cert_pub_key_algo != .X9_62_id_ecPublicKey) |
| 467 | return error.TlsBadSignatureScheme; |
| 468 | const Ecdsa = SchemeEcdsa(comptime_scheme); |
| 469 | const sig = try Ecdsa.Signature.fromDer(encoded_sig); |
| 470 | const key = try Ecdsa.PublicKey.fromSec1(main_cert_pub_key); |
| 471 | try sig.verify(verify_bytes, key); |
| 472 | }, |
| 473 | .rsa_pss_rsae_sha256 => { |
| 474 | if (main_cert_pub_key_algo != .rsaEncryption) |
| 475 | return error.TlsBadSignatureScheme; |
| 476 | |
| 477 | const Hash = crypto.hash.sha2.Sha256; |
| 478 | const rsa = Certificate.rsa; |
| 479 | const components = try rsa.PublicKey.parseDer(main_cert_pub_key); |
| 480 | const exponent = components.exponent; |
| 481 | const modulus = components.modulus; |
| 482 | var rsa_mem_buf: [512 * 32]u8 = undefined; |
| 483 | var fba = std.heap.FixedBufferAllocator.init(&rsa_mem_buf); |
| 484 | const ally = fba.allocator(); |
| 485 | switch (modulus.len) { |
| 486 | inline 128, 256, 512 => |modulus_len| { |
| 487 | const key = try rsa.PublicKey.fromBytes(exponent, modulus, ally); |
| 488 | const sig = rsa.PSSSignature.fromBytes(modulus_len, encoded_sig); |
| 489 | try rsa.PSSSignature.verify(modulus_len, sig, verify_bytes, key, Hash, ally); |
| 532 | 490 | }, |
| 533 | 491 | else => { |
| 534 | | return error.TlsBadSignatureScheme; |
| 492 | return error.TlsBadRsaSignatureBitCount; |
| 535 | 493 | }, |
| 536 | 494 | } |
| 537 | 495 | }, |
| 538 | | .finished => { |
| 539 | | if (handshake_state != .finished) return error.TlsUnexpectedMessage; |
| 540 | | // This message is to trick buggy proxies into behaving correctly. |
| 541 | | const client_change_cipher_spec_msg = [_]u8{ |
| 542 | | @enumToInt(tls.ContentType.change_cipher_spec), |
| 543 | | 0x03, 0x03, // legacy protocol version |
| 544 | | 0x00, 0x01, // length |
| 545 | | 0x01, |
| 546 | | }; |
| 547 | | const app_cipher = switch (handshake_cipher) { |
| 548 | | inline else => |*p, tag| c: { |
| 549 | | const P = @TypeOf(p.*); |
| 550 | | const finished_digest = p.transcript_hash.peek(); |
| 551 | | p.transcript_hash.update(wrapped_handshake); |
| 552 | | const expected_server_verify_data = tls.hmac(P.Hmac, &finished_digest, p.server_finished_key); |
| 553 | | if (!mem.eql(u8, &expected_server_verify_data, handshake)) |
| 554 | | return error.TlsDecryptError; |
| 555 | | const handshake_hash = p.transcript_hash.finalResult(); |
| 556 | | const verify_data = tls.hmac(P.Hmac, &handshake_hash, p.client_finished_key); |
| 557 | | const out_cleartext = [_]u8{ |
| 558 | | @enumToInt(tls.HandshakeType.finished), |
| 559 | | 0, 0, verify_data.len, // length |
| 560 | | } ++ verify_data ++ [1]u8{@enumToInt(tls.ContentType.handshake)}; |
| 561 | | |
| 562 | | const wrapped_len = out_cleartext.len + P.AEAD.tag_length; |
| 563 | | |
| 564 | | var finished_msg = [_]u8{ |
| 565 | | @enumToInt(tls.ContentType.application_data), |
| 566 | | 0x03, 0x03, // legacy protocol version |
| 567 | | 0, wrapped_len, // byte length of encrypted record |
| 568 | | } ++ @as([wrapped_len]u8, undefined); |
| 569 | | |
| 570 | | const ad = finished_msg[0..5]; |
| 571 | | const ciphertext = finished_msg[5..][0..out_cleartext.len]; |
| 572 | | const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..]; |
| 573 | | const nonce = p.client_handshake_iv; |
| 574 | | P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); |
| 575 | | |
| 576 | | const both_msgs = client_change_cipher_spec_msg ++ finished_msg; |
| 577 | | try stream.writeAll(&both_msgs); |
| 578 | | |
| 579 | | const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 580 | | const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| 581 | | break :c @unionInit(tls.ApplicationCipher, @tagName(tag), .{ |
| 582 | | .client_secret = client_secret, |
| 583 | | .server_secret = server_secret, |
| 584 | | .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), |
| 585 | | .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), |
| 586 | | .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length), |
| 587 | | .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length), |
| 588 | | }); |
| 589 | | }, |
| 590 | | }; |
| 591 | | var client: Client = .{ |
| 592 | | .read_seq = 0, |
| 593 | | .write_seq = 0, |
| 594 | | .partial_cleartext_idx = 0, |
| 595 | | .partial_ciphertext_idx = 0, |
| 596 | | .partial_ciphertext_end = @intCast(u15, len - end), |
| 597 | | .received_close_notify = false, |
| 598 | | .application_cipher = app_cipher, |
| 599 | | .partially_read_buffer = undefined, |
| 600 | | }; |
| 601 | | mem.copy(u8, &client.partially_read_buffer, handshake_buf[len..end]); |
| 602 | | return client; |
| 603 | | }, |
| 604 | 496 | else => { |
| 605 | | return error.TlsUnexpectedMessage; |
| 497 | return error.TlsBadSignatureScheme; |
| 606 | 498 | }, |
| 607 | 499 | } |
| 608 | | ct_i = next_handshake_i; |
| 609 | | if (ct_i >= cleartext.len - 1) break; |
| 610 | | } |
| 611 | | }, |
| 612 | | else => { |
| 613 | | return error.TlsUnexpectedMessage; |
| 614 | | }, |
| 500 | }, |
| 501 | .finished => { |
| 502 | if (handshake_state != .finished) return error.TlsUnexpectedMessage; |
| 503 | // This message is to trick buggy proxies into behaving correctly. |
| 504 | const client_change_cipher_spec_msg = [_]u8{ |
| 505 | @enumToInt(tls.ContentType.change_cipher_spec), |
| 506 | 0x03, 0x03, // legacy protocol version |
| 507 | 0x00, 0x01, // length |
| 508 | 0x01, |
| 509 | }; |
| 510 | const app_cipher = switch (handshake_cipher) { |
| 511 | inline else => |*p, tag| c: { |
| 512 | const P = @TypeOf(p.*); |
| 513 | const finished_digest = p.transcript_hash.peek(); |
| 514 | p.transcript_hash.update(wrapped_handshake); |
| 515 | const expected_server_verify_data = tls.hmac(P.Hmac, &finished_digest, p.server_finished_key); |
| 516 | if (!mem.eql(u8, &expected_server_verify_data, handshake)) |
| 517 | return error.TlsDecryptError; |
| 518 | const handshake_hash = p.transcript_hash.finalResult(); |
| 519 | const verify_data = tls.hmac(P.Hmac, &handshake_hash, p.client_finished_key); |
| 520 | const out_cleartext = [_]u8{ |
| 521 | @enumToInt(tls.HandshakeType.finished), |
| 522 | 0, 0, verify_data.len, // length |
| 523 | } ++ verify_data ++ [1]u8{@enumToInt(tls.ContentType.handshake)}; |
| 524 | |
| 525 | const wrapped_len = out_cleartext.len + P.AEAD.tag_length; |
| 526 | |
| 527 | var finished_msg = [_]u8{ |
| 528 | @enumToInt(tls.ContentType.application_data), |
| 529 | 0x03, 0x03, // legacy protocol version |
| 530 | 0, wrapped_len, // byte length of encrypted record |
| 531 | } ++ @as([wrapped_len]u8, undefined); |
| 532 | |
| 533 | const ad = finished_msg[0..5]; |
| 534 | const ciphertext = finished_msg[5..][0..out_cleartext.len]; |
| 535 | const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..]; |
| 536 | const nonce = p.client_handshake_iv; |
| 537 | P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); |
| 538 | |
| 539 | const both_msgs = client_change_cipher_spec_msg ++ finished_msg; |
| 540 | try stream.writeAll(&both_msgs); |
| 541 | |
| 542 | const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 543 | const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| 544 | break :c @unionInit(tls.ApplicationCipher, @tagName(tag), .{ |
| 545 | .client_secret = client_secret, |
| 546 | .server_secret = server_secret, |
| 547 | .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), |
| 548 | .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), |
| 549 | .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length), |
| 550 | .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length), |
| 551 | }); |
| 552 | }, |
| 553 | }; |
| 554 | const leftover = d.rest(); |
| 555 | var client: Client = .{ |
| 556 | .read_seq = 0, |
| 557 | .write_seq = 0, |
| 558 | .partial_cleartext_idx = 0, |
| 559 | .partial_ciphertext_idx = 0, |
| 560 | .partial_ciphertext_end = @intCast(u15, leftover.len), |
| 561 | .received_close_notify = false, |
| 562 | .application_cipher = app_cipher, |
| 563 | .partially_read_buffer = undefined, |
| 564 | }; |
| 565 | mem.copy(u8, &client.partially_read_buffer, leftover); |
| 566 | return client; |
| 567 | }, |
| 568 | else => { |
| 569 | return error.TlsUnexpectedMessage; |
| 570 | }, |
| 571 | } |
| 572 | if (ctd.eof()) break; |
| 615 | 573 | } |
| 616 | 574 | }, |
| 617 | 575 | else => { |
| 618 | 576 | return error.TlsUnexpectedMessage; |
| 619 | 577 | }, |
| 620 | 578 | } |
| 621 | | i = end; |
| 622 | 579 | } |
| 623 | | |
| 624 | | return error.TlsHandshakeFailure; |
| 625 | 580 | } |
| 626 | 581 | |
| 627 | 582 | pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { |
| ... | ... | @@ -638,12 +593,12 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize { |
| 638 | 593 | inline else => |*p| l: { |
| 639 | 594 | const P = @TypeOf(p.*); |
| 640 | 595 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 641 | | const overhead_len = tls.ciphertext_record_header_len + P.AEAD.tag_length + 1; |
| 596 | const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1; |
| 642 | 597 | while (true) { |
| 643 | 598 | const encrypted_content_len = @intCast(u16, @min( |
| 644 | 599 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), |
| 645 | 600 | ciphertext_buf.len - |
| 646 | | tls.ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, |
| 601 | tls.record_header_len - P.AEAD.tag_length - ciphertext_end - 1, |
| 647 | 602 | )); |
| 648 | 603 | if (encrypted_content_len == 0) break :l overhead_len; |
| 649 | 604 | |
| ... | ... | @@ -829,7 +784,7 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 829 | 784 | |
| 830 | 785 | // Cleartext capacity of output buffer, in records, rounded up. |
| 831 | 786 | const buf_cap = (cleartext_buf_len +| (max_ciphertext_len - 1)) / max_ciphertext_len; |
| 832 | | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); |
| 787 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len); |
| 833 | 788 | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len); |
| 834 | 789 | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); |
| 835 | 790 | const actual_read_len = try stream.readv(ask_iovecs); |
| ... | ... | @@ -860,13 +815,13 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 860 | 815 | continue; |
| 861 | 816 | } |
| 862 | 817 | |
| 863 | | if (in + tls.ciphertext_record_header_len > frag.len) { |
| 818 | if (in + tls.record_header_len > frag.len) { |
| 864 | 819 | if (frag.ptr == frag1.ptr) |
| 865 | 820 | return finishRead(c, frag, in, vp.total); |
| 866 | 821 | |
| 867 | 822 | const first = frag[in..]; |
| 868 | 823 | |
| 869 | | if (frag1.len < tls.ciphertext_record_header_len) |
| 824 | if (frag1.len < tls.record_header_len) |
| 870 | 825 | return finishRead2(c, first, frag1, vp.total); |
| 871 | 826 | |
| 872 | 827 | // A record straddles the two fragments. Copy into the now-empty first fragment. |
| ... | ... | @@ -875,7 +830,7 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 875 | 830 | const record_len = (record_len_byte_0 << 8) | record_len_byte_1; |
| 876 | 831 | if (record_len > max_ciphertext_len) return error.TlsRecordOverflow; |
| 877 | 832 | |
| 878 | | const full_record_len = record_len + tls.ciphertext_record_header_len; |
| 833 | const full_record_len = record_len + tls.record_header_len; |
| 879 | 834 | const second_len = full_record_len - first.len; |
| 880 | 835 | if (frag1.len < second_len) |
| 881 | 836 | return finishRead2(c, first, frag1, vp.total); |
| ... | ... | @@ -898,14 +853,14 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 898 | 853 | const end = in + record_len; |
| 899 | 854 | if (end > frag.len) { |
| 900 | 855 | // We need the record header on the next iteration of the loop. |
| 901 | | in -= tls.ciphertext_record_header_len; |
| 856 | in -= tls.record_header_len; |
| 902 | 857 | |
| 903 | 858 | if (frag.ptr == frag1.ptr) |
| 904 | 859 | return finishRead(c, frag, in, vp.total); |
| 905 | 860 | |
| 906 | 861 | // A record straddles the two fragments. Copy into the now-empty first fragment. |
| 907 | 862 | const first = frag[in..]; |
| 908 | | const full_record_len = record_len + tls.ciphertext_record_header_len; |
| 863 | const full_record_len = record_len + tls.record_header_len; |
| 909 | 864 | const second_len = full_record_len - first.len; |
| 910 | 865 | if (frag1.len < second_len) |
| 911 | 866 | return finishRead2(c, first, frag1, vp.total); |
| ... | ... | @@ -919,7 +874,12 @@ pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iove |
| 919 | 874 | } |
| 920 | 875 | switch (ct) { |
| 921 | 876 | .alert => { |
| 922 | | @panic("TODO handle an alert here"); |
| 877 | if (in + 2 > frag.len) return error.TlsDecodeError; |
| 878 | const level = @intToEnum(tls.AlertLevel, frag[in]); |
| 879 | const desc = @intToEnum(tls.AlertDescription, frag[in + 1]); |
| 880 | _ = level; |
| 881 | _ = desc; |
| 882 | return error.TlsAlert; |
| 923 | 883 | }, |
| 924 | 884 | .application_data => { |
| 925 | 885 | const cleartext = switch (c.application_cipher) { |