Skip to content

Commit 01f7548

Browse files
author
Richard Wheeler
committed
net.quic: 13a - independently verify CertificateVerify's ECDSA signature
Adds real cryptographic verification of encode_certificate_verify's output via crypto.ecdsa's PublicKey.verify() -- which already exists (vlib/crypto/ ecdsa/ecdsa.v:340) and was previously missed by an incomplete grep for the wrong receiver variable name, not actually absent. The prior round-trip test only confirmed wire framing and non-constancy; this closes the real gap: proving the signed content, key, and DER encoding all genuinely agree, not just that the bytes look plausible. Two new assertions: a wrong-transcript-hash/signature pairing must NOT verify (rules out a check that ignores the content), and a signature must be rejected by a DIFFERENT key's public half (rules out a verify() that accepts anything). This is a same-library round trip (OpenSSL signs, OpenSSL verifies) -- independent-library cross-verification via a real peer's mbedTLS still isn't exercised, since this repo has no EC certificate fixture to build an mbedtls_pk_context from. That remaining gap is unchanged and still documented; only the previously-incorrect "no PublicKey.verify() exists at all" claim is fixed. Full net.quic suite 55/55.
1 parent 9ecb42e commit 01f7548

1 file changed

Lines changed: 47 additions & 11 deletions

File tree

vlib/net/quic/tls13_certificate_test.v

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,18 +274,22 @@ fn test_encode_certificate_rejects_entry_with_extensions() {
274274

275275
// test_encode_certificate_verify_round_trips_through_parse_certificate_verify
276276
// verifies the WIRE FRAMING (algorithm field, signature length prefix) is
277-
// correct and that the signature this function produces is plausible ECDSA
278-
// DER output -- non-empty, and different for different transcript hashes
279-
// (a constant/garbage signature would fail this). It does NOT
280-
// cryptographically verify the signature against the public key: this
281-
// codebase has no PublicKey.verify() exposed by crypto.ecdsa and no EC
282-
// certificate fixture to build an mbedtls_pk_context from, the SAME
283-
// documented gap Phase 2c's own x509_standalone_signature_test.v already
284-
// states ("No EC private key exists anywhere in this repo, so the ECDSA
285-
// path is tested only via rejecting an incompatible key") -- not silently
286-
// skipped, stated here for the same reason.
277+
// correct AND cryptographically verifies the produced signature via
278+
// crypto.ecdsa's own PublicKey.verify() -- proving encode_certificate_verify
279+
// actually signs the right content (certificate_verify_signed_content's
280+
// output) with the right key, not just that it produces plausible-looking
281+
// bytes. This is a same-library round trip (OpenSSL signs, OpenSSL
282+
// verifies), not independent-library cross-verification the way this
283+
// module's client-side chain verification eventually gets from a real
284+
// peer's mbedTLS -- this repo has no EC certificate fixture to build an
285+
// mbedtls_pk_context from for that, the same gap Phase 2c's own
286+
// x509_standalone_signature_test.v documents for the identical reason. What
287+
// IS proven here: the signed content, the key, and the DER encoding all
288+
// actually agree -- the exact seam this function's own code introduces, as
289+
// opposed to crypto.ecdsa's sign/verify primitives themselves, which are
290+
// pre-existing and already used elsewhere in this codebase.
287291
fn test_encode_certificate_verify_round_trips_through_parse_certificate_verify() {
288-
_, priv_key := ecdsa.generate_key()!
292+
pub_key, priv_key := ecdsa.generate_key()!
289293
transcript_hash := []u8{len: 32, init: 0x01}
290294

291295
msg := encode_certificate_verify(sig_scheme_ecdsa_secp256r1_sha256, priv_key, transcript_hash)!
@@ -297,6 +301,9 @@ fn test_encode_certificate_verify_round_trips_through_parse_certificate_verify()
297301
assert result.algorithm == sig_scheme_ecdsa_secp256r1_sha256
298302
assert result.signature.len > 0
299303

304+
signed_content := certificate_verify_signed_content(.server, transcript_hash)
305+
assert pub_key.verify(signed_content, result.signature, hash_config: .with_recommended_hash)!
306+
300307
other_transcript_hash := []u8{len: 32, init: 0x02}
301308
other_msg := encode_certificate_verify(sig_scheme_ecdsa_secp256r1_sha256, priv_key,
302309
other_transcript_hash)!
@@ -305,6 +312,35 @@ fn test_encode_certificate_verify_round_trips_through_parse_certificate_verify()
305312
other_result := parse_certificate_verify(other_parsed.body)!
306313
assert other_consumed == other_msg.len
307314
assert other_result.signature != result.signature
315+
316+
other_signed_content := certificate_verify_signed_content(.server, other_transcript_hash)
317+
assert pub_key.verify(other_signed_content, other_result.signature,
318+
hash_config: .with_recommended_hash
319+
)!
320+
// Cross-wired inputs must NOT verify -- confirms verify() is actually
321+
// checking the content, not just the key/signature pair in isolation.
322+
assert !pub_key.verify(signed_content, other_result.signature,
323+
hash_config: .with_recommended_hash
324+
)!
325+
}
326+
327+
// test_encode_certificate_verify_signature_rejected_by_wrong_public_key
328+
// confirms a signature this function produces is rejected by a DIFFERENT
329+
// key's public half -- the negative-space complement to the positive
330+
// verification above, ruling out a verify() that accepts anything.
331+
fn test_encode_certificate_verify_signature_rejected_by_wrong_public_key() {
332+
_, priv_key := ecdsa.generate_key()!
333+
other_pub_key, _ := ecdsa.generate_key()!
334+
transcript_hash := []u8{len: 32, init: 0x03}
335+
336+
msg := encode_certificate_verify(sig_scheme_ecdsa_secp256r1_sha256, priv_key, transcript_hash)!
337+
parsed_msg, _ := parse_handshake_message(msg)!
338+
result := parse_certificate_verify(parsed_msg.body)!
339+
340+
signed_content := certificate_verify_signed_content(.server, transcript_hash)
341+
assert !other_pub_key.verify(signed_content, result.signature,
342+
hash_config: .with_recommended_hash
343+
)!
308344
}
309345

310346
fn test_encode_certificate_verify_rejects_unimplemented_algorithm() {

0 commit comments

Comments
 (0)