chore(quic): add tests with invalid certs (#1297)

This commit is contained in:
vladopajic
2025-03-27 15:19:14 +01:00
committed by GitHub
parent 340ea05ae5
commit 1376f5b077
4 changed files with 127 additions and 47 deletions

View File

@@ -146,12 +146,16 @@ method close*(m: QuicMuxer) {.async: (raises: []).} =
# Transport
type QuicUpgrade = ref object of Upgrade
type CertGenerator =
proc(kp: KeyPair): CertificateX509 {.gcsafe, raises: [TLSCertificateError].}
type QuicTransport* = ref object of Transport
listener: Listener
client: QuicClient
privateKey: PrivateKey
connections: seq[P2PConnection]
rng: ref HmacDrbgContext
certGenerator: CertGenerator
proc makeCertificateVerifier(): CertificateVerifier =
proc certificateVerifier(certificatesDer: seq[seq[byte]]): bool =
@@ -171,8 +175,29 @@ proc makeCertificateVerifier(): CertificateVerifier =
return CustomCertificateVerifier.init(certificateVerifier)
func new*(_: type QuicTransport, u: Upgrade, privateKey: PrivateKey): QuicTransport =
return QuicTransport(upgrader: QuicUpgrade(ms: u.ms), privateKey: privateKey)
proc defaultCertGenerator(
kp: KeyPair
): CertificateX509 {.gcsafe, raises: [TLSCertificateError].} =
return generateX509(kp, encodingFormat = EncodingFormat.PEM)
proc new*(_: type QuicTransport, u: Upgrade, privateKey: PrivateKey): QuicTransport =
return QuicTransport(
upgrader: QuicUpgrade(ms: u.ms),
privateKey: privateKey,
certGenerator: defaultCertGenerator,
)
proc new*(
_: type QuicTransport,
u: Upgrade,
privateKey: PrivateKey,
certGenerator: CertGenerator,
): QuicTransport =
return QuicTransport(
upgrader: QuicUpgrade(ms: u.ms),
privateKey: privateKey,
certGenerator: certGenerator,
)
method handles*(transport: QuicTransport, address: MultiAddress): bool {.raises: [].} =
if not procCall Transport(transport).handles(address):
@@ -189,14 +214,13 @@ method start*(
doAssert false, "could not obtain public key"
return
let keypair = KeyPair(seckey: self.privateKey, pubkey: pubkey)
let certTuple = generate(keypair, encodingFormat = EncodingFormat.PEM)
try:
if self.rng.isNil:
self.rng = newRng()
let cert = self.certGenerator(KeyPair(seckey: self.privateKey, pubkey: pubkey))
let tlsConfig = TLSConfig.init(
certTuple[0], certTuple[1], @[alpn], Opt.some(makeCertificateVerifier())
cert.certificate, cert.privateKey, @[alpn], Opt.some(makeCertificateVerifier())
)
self.client = QuicClient.init(tlsConfig, rng = self.rng)
self.listener =
@@ -207,6 +231,8 @@ method start*(
MultiAddress.init("/quic-v1").get()
except QuicConfigError as exc:
doAssert false, "invalid quic setup: " & $exc.msg
except TLSCertificateError as exc:
raise (ref QuicTransportError)(msg: exc.msg, parent: exc)
except QuicError as exc:
raise (ref QuicTransportError)(msg: exc.msg, parent: exc)
except TransportOsError as exc:

View File

@@ -43,6 +43,11 @@ type
validFrom: Time
validTo: Time
CertificateX509* = object
certificate*: seq[byte]
# Complete ASN.1 DER content (certificate, signature algorithm and signature).
privateKey*: seq[byte] # Private key used to sign certificate
type EncodingFormat* = enum
DER
PEM
@@ -169,12 +174,12 @@ proc makeExtValues(
return (signature.toCertBuffer(), pubKeyBytes.toCertBuffer())
proc generate*(
proc generateX509*(
identityKeyPair: KeyPair,
validFrom: Time = fromUnix(157813200),
validTo: Time = fromUnix(67090165200),
encodingFormat: EncodingFormat = EncodingFormat.DER,
): tuple[raw: seq[byte], privateKey: seq[byte]] {.
): CertificateX509 {.
raises: [
KeyGenerationError, IdentitySigningError, IdentityPubKeySerializationError,
CertificateCreationError, CertificatePubKeySerializationError,
@@ -230,8 +235,7 @@ proc generate*(
cert_free_buffer(certificate)
cert_free_buffer(privKDer)
# Return the Serialized Certificate and Private Key
return (outputCertificate, outputPrivateKey)
return CertificateX509(certificate: outputCertificate, privateKey: outputPrivateKey)
proc parseCertTime*(certTime: string): Time {.raises: [TimeParseError].} =
var timeNoZone = certTime[0 ..^ 5] # removes GMT part

View File

@@ -6,35 +6,70 @@ import
stream/connection,
transports/transport,
transports/quictransport,
transports/tls/certificate,
upgrademngrs/upgrade,
multiaddress,
errors,
wire,
]
import ./helpers, ./commontransport
proc createServerAcceptConn(
server: QuicTransport
): proc(): Future[void] {.
async: (raises: [transport.TransportError, LPStreamError, CancelledError])
.} =
proc handler() {.
async: (raises: [transport.TransportError, LPStreamError, CancelledError])
.} =
let conn = await server.accept()
let stream = await getStream(QuicSession(conn), Direction.In)
var resp: array[6, byte]
await stream.readExactly(addr resp, 6)
check string.fromBytes(resp) == "client"
await stream.write("server")
await stream.close()
await server.stop()
return handler
proc invalidCertGenerator(
kp: KeyPair
): CertificateX509 {.gcsafe, raises: [TLSCertificateError].} =
try:
let keyNew = PrivateKey.random(ECDSA, (newRng())[]).get()
let pubkey = keyNew.getPublicKey().get()
# invalidKp has pubkey that does not match seckey
let invalidKp = KeyPair(seckey: kp.seckey, pubkey: pubkey)
return generateX509(invalidKp, encodingFormat = EncodingFormat.PEM)
except ResultError[crypto.CryptoError]:
raiseAssert "private key should be set"
proc createTransport(withInvalidCert: bool = false): Future[QuicTransport] {.async.} =
let ma = @[MultiAddress.init("/ip4/127.0.0.1/udp/0/quic-v1").tryGet()]
let privateKey = PrivateKey.random(ECDSA, (newRng())[]).tryGet()
let trans =
if withInvalidCert:
QuicTransport.new(Upgrade(), privateKey, invalidCertGenerator)
else:
QuicTransport.new(Upgrade(), privateKey)
await trans.start(ma)
return trans
suite "Quic transport":
asyncTest "can handle local address":
let ma = @[MultiAddress.init("/ip4/127.0.0.1/udp/0/quic-v1").tryGet()]
let privateKey = PrivateKey.random(ECDSA, (newRng())[]).tryGet()
let transport1 = QuicTransport.new(Upgrade(), privateKey)
await transport1.start(ma)
check transport1.handles(transport1.addrs[0])
await transport1.stop()
#
let trans = await createTransport()
check trans.handles(trans.addrs[0])
await trans.stop()
asyncTest "transport e2e":
let serverMA = @[MultiAddress.init("/ip4/127.0.0.1/udp/0/quic-v1").tryGet()]
let clientMA = @[MultiAddress.init("/ip4/127.0.0.1/udp/0/quic-v1").tryGet()]
let privateKey = PrivateKey.random(ECDSA, (newRng())[]).tryGet()
let server: QuicTransport = QuicTransport.new(Upgrade(), privateKey)
await server.start(serverMA)
let server = await createTransport()
asyncSpawn createServerAcceptConn(server)()
proc runClient() {.async.} =
let rng = newRng()
let privateKey = PrivateKey.random(ECDSA, (rng)[]).tryGet()
let client: QuicTransport = QuicTransport.new(Upgrade(), privateKey)
await client.start(clientMA)
let client = await createTransport()
let conn = await client.dial("", server.addrs[0])
let stream = await getStream(QuicSession(conn), Direction.Out)
await stream.write("client")
@@ -44,16 +79,31 @@ suite "Quic transport":
check string.fromBytes(resp) == "server"
await client.stop()
proc serverAcceptHandler() {.async.} =
let conn = await server.accept()
let stream = await getStream(QuicSession(conn), Direction.In)
var resp: array[6, byte]
await stream.readExactly(addr resp, 6)
check string.fromBytes(resp) == "client"
await stream.write("server")
await stream.close()
await server.stop()
asyncSpawn serverAcceptHandler()
await runClient()
asyncTest "transport e2e - invalid cert - server":
let server = await createTransport(true)
asyncSpawn createServerAcceptConn(server)()
proc runClient() {.async.} =
let client = await createTransport()
try:
discard await client.dial("", server.addrs[0])
check false # conn should be refused by client
except QuicTransportDialError:
discard
await client.stop()
await runClient()
asyncTest "transport e2e - invalid cert - client":
let server = await createTransport()
asyncSpawn createServerAcceptConn(server)()
proc runClient() {.async.} =
let client = await createTransport(true)
let connFut = client.dial("", server.addrs[0])
# conn should be refused by server
check not await connFut.withTimeout(1.seconds)
await runClient()

View File

@@ -13,8 +13,8 @@ suite "Certificate roundtrip tests":
let keypair = KeyPair.random(scheme, rng[]).tryGet()
let peerId = PeerId.init(keypair.pubkey).tryGet()
let certTuple = generate(keypair)
let cert = parse(certTuple.raw)
let certX509 = generateX509(keypair, encodingFormat = EncodingFormat.DER)
let cert = parse(certX509.certificate)
check peerId == cert.peerId()
check cert.publicKey().scheme == scheme
@@ -27,22 +27,22 @@ suite "Certificate roundtrip tests":
# past
var validFrom = (now() - 3.days).toTime()
var validTo = (now() - 1.days).toTime()
var certTuple = generate(keypair, validFrom, validTo)
var cert = parse(certTuple.raw)
var certX509 = generateX509(keypair, validFrom, validTo)
var cert = parse(certX509.certificate)
check not cert.verify()
# future
validFrom = (now() + 1.days).toTime()
validTo = (now() + 3.days).toTime()
certTuple = generate(keypair, validFrom, validTo)
cert = parse(certTuple.raw)
certX509 = generateX509(keypair, validFrom, validTo)
cert = parse(certX509.certificate)
check not cert.verify()
# twisted from-to
validFrom = (now() + 3.days).toTime()
validTo = (now() - 3.days).toTime()
certTuple = generate(keypair, validFrom, validTo)
cert = parse(certTuple.raw)
certX509 = generateX509(keypair, validFrom, validTo)
cert = parse(certX509.certificate)
check not cert.verify()
## Test vectors are equivalents to https://github.com/libp2p/specs/blob/master/tls/tls.md#test-vectors.