From c075aafdf381ff3f6be64e4d6188bb84f974d01d Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Fri, 16 Jun 2023 17:59:37 +1000 Subject: [PATCH] feat: Added custom TLS verification and secured event - custom verification and secure event - checking header for events [ci skip] --- src/QUICClient.ts | 16 ++- src/QUICConnection.ts | 134 +++++++++++++++++++- src/QUICServer.ts | 6 + src/config.ts | 4 + src/errors.ts | 5 + src/events.ts | 13 ++ src/native/napi/config.rs | 11 +- src/native/types.ts | 7 +- src/types.ts | 3 + tests/QUICClient.test.ts | 251 ++++++++++++++++++++++++++++++++++++-- 10 files changed, 428 insertions(+), 22 deletions(-) diff --git a/src/QUICClient.ts b/src/QUICClient.ts index 194e96db..31b81d70 100644 --- a/src/QUICClient.ts +++ b/src/QUICClient.ts @@ -1,4 +1,4 @@ -import type { Crypto, Host, Hostname, Port } from './types'; +import type { Crypto, Host, Hostname, Port, VerifyCallback } from './types'; import type { Config } from './native/types'; import type { QUICConfig } from './config'; import type QUICConnectionMap from './QUICConnectionMap'; @@ -58,6 +58,7 @@ class QUICClient extends EventTarget { maxReadableStreamBytes, maxWritableStreamBytes, keepaliveIntervalTime, + verifyCallback, logger = new Logger(`${this.name}`), config = {}, }: { @@ -76,6 +77,7 @@ class QUICClient extends EventTarget { maxReadableStreamBytes?: number; maxWritableStreamBytes?: number; keepaliveIntervalTime?: number; + verifyCallback?: VerifyCallback; logger?: Logger; config?: Partial; }) { @@ -164,17 +166,22 @@ class QUICClient extends EventTarget { logger: logger.getChild( `${QUICConnection.name} ${scid.toString().slice(32)}`, ), + verifyCallback, + }); + connection.addEventListener('error', handleConnectionError, { + once: true, }); - connection.addEventListener('error', handleConnectionError, { once: true }); logger.debug('CLIENT TRIGGER SEND'); // This will not raise an error await connection.send(); // This will wait to be established, while also rejecting on error try { - await Promise.race([connection.establishedP, errorP]); + await Promise.race([ + Promise.all([connection.establishedP, connection.securedP]), + errorP, + ]); } catch (e) { logger.error(e.toString()); - // Console.error(e); logger.debug(`Is shared?: ${isSocketShared}`); // Waiting for connection to destroy if (connection[destroyed] === false) { @@ -194,6 +201,7 @@ class QUICClient extends EventTarget { // Stop our own socket await socket.stop(); } + // Error.captureStackTrace(e); throw e; } diff --git a/src/QUICConnection.ts b/src/QUICConnection.ts index 526ca967..befbebb9 100644 --- a/src/QUICConnection.ts +++ b/src/QUICConnection.ts @@ -3,7 +3,7 @@ import type QUICConnectionMap from './QUICConnectionMap'; import type QUICConnectionId from './QUICConnectionId'; // This is specialized type import type { QUICConfig } from './config'; -import type { Host, Port, RemoteInfo, StreamId } from './types'; +import type { Host, Port, RemoteInfo, StreamId, VerifyCallback } from './types'; import type { Connection, ConnectionErrorCode, SendInfo } from './native/types'; import type { StreamCodeToReason, StreamReasonToCode } from './types'; import type { ConnectionMetadata } from './types'; @@ -21,7 +21,8 @@ import { quiche } from './native'; import * as events from './events'; import * as utils from './utils'; import * as errors from './errors'; -import { promise } from './utils'; +import { never, promise } from './utils'; +import { Type } from './native/types'; /** * Think of this as equivalent to `net.Socket`. @@ -94,6 +95,14 @@ class QUICConnection extends EventTarget { protected _remoteHost: Host; protected _remotePort: Port; + protected _customVerified = false; + protected _shortReceived = false; + protected _shortSent = false; + protected _secured = false; + protected _count = 0; + protected _securedP = promise(); + protected verifyCallback: VerifyCallback | undefined; + /** * Create QUICConnection by connecting to a server */ @@ -107,6 +116,7 @@ class QUICConnection extends EventTarget { new Error(`${type.toString()} ${code.toString()}`), maxReadableStreamBytes, maxWritableStreamBytes, + verifyCallback, logger = new Logger(`${this.name} ${scid}`), }: { scid: QUICConnectionId; @@ -117,6 +127,7 @@ class QUICConnection extends EventTarget { codeToReason?: StreamCodeToReason; maxReadableStreamBytes?: number; maxWritableStreamBytes?: number; + verifyCallback: VerifyCallback | undefined; logger?: Logger; }) { logger.info(`Connect ${this.name}`); @@ -148,6 +159,7 @@ class QUICConnection extends EventTarget { codeToReason, maxReadableStreamBytes, maxWritableStreamBytes, + verifyCallback, logger, }); socket.connectionMap.set(connection.connectionId, connection); @@ -169,6 +181,7 @@ class QUICConnection extends EventTarget { new Error(`${type.toString()} ${code.toString()}`), maxReadableStreamBytes, maxWritableStreamBytes, + verifyCallback, logger = new Logger(`${this.name} ${scid}`), }: { scid: QUICConnectionId; @@ -180,6 +193,7 @@ class QUICConnection extends EventTarget { codeToReason?: StreamCodeToReason; maxReadableStreamBytes?: number; maxWritableStreamBytes?: number; + verifyCallback: VerifyCallback | undefined; logger?: Logger; }): Promise { logger.info(`Accept ${this.name}`); @@ -211,6 +225,7 @@ class QUICConnection extends EventTarget { codeToReason, maxReadableStreamBytes, maxWritableStreamBytes, + verifyCallback, logger, }); socket.connectionMap.set(connection.connectionId, connection); @@ -228,6 +243,7 @@ class QUICConnection extends EventTarget { codeToReason, maxReadableStreamBytes, maxWritableStreamBytes, + verifyCallback, logger, }: { type: 'client' | 'server'; @@ -239,6 +255,7 @@ class QUICConnection extends EventTarget { codeToReason: StreamCodeToReason; maxReadableStreamBytes: number | undefined; maxWritableStreamBytes: number | undefined; + verifyCallback: VerifyCallback | undefined; logger: Logger; }) { super(); @@ -254,6 +271,7 @@ class QUICConnection extends EventTarget { this.codeToReason = codeToReason; this.maxReadableStreamBytes = maxReadableStreamBytes; this.maxWritableStreamBytes = maxWritableStreamBytes; + this.verifyCallback = verifyCallback; // Sets the timeout on the first this.checkTimeout(); @@ -315,6 +333,10 @@ class QUICConnection extends EventTarget { }; } + public get securedP() { + return this._securedP.p; + } + /** * This provides the ability to destroy with a specific error. This will wait for the connection to fully drain. */ @@ -373,7 +395,7 @@ class QUICConnection extends EventTarget { await this.closedP; this.logger.debug('closeP resolved'); this.connectionMap.delete(this.connectionId); - // Checking if timed out + // Emit error if timed out if (this.conn.isTimedOut()) { this.logger.error('Connection timed out'); this.dispatchEvent( @@ -382,6 +404,43 @@ class QUICConnection extends EventTarget { }), ); } + // Emit error if peer error + const peerError = this.conn.peerError(); + if (peerError != null) { + this.logger.info( + `Connection errored out with peerError ${Buffer.from( + peerError.reason, + ).toString()}(${peerError.errorCode})`, + ); + this.dispatchEvent( + new events.QUICConnectionErrorEvent({ + detail: new errors.ErrorQUICConnectionFailure( + `Connection errored out with peerError ${Buffer.from( + peerError.reason, + ).toString()}(${peerError.errorCode})`, + ), + }), + ); + } + + const localError = this.conn.localError(); + if (localError != null) { + this.logger.info( + `connection failed with localError ${Buffer.from( + localError.reason, + ).toString()}(${localError.errorCode})`, + ); + this.dispatchEvent( + new events.QUICConnectionErrorEvent({ + detail: new errors.ErrorQUICConnectionFailure( + `connection failed with localError ${Buffer.from( + localError.reason, + ).toString()}(${localError.errorCode})`, + ), + }), + ); + } + this.dispatchEvent(new events.QUICConnectionDestroyEvent()); // Clean up timeout if it's still running if (this.timer != null) { @@ -450,6 +509,30 @@ class QUICConnection extends EventTarget { } return; } + + // Checking if the packet was a short frame. + // Short indicates that the peer has completed TLS verification + if (!this._shortReceived) { + const header = quiche.Header.fromSlice(data, quiche.MAX_CONN_ID_LEN); + if (header.ty === Type.Short) { + this._shortReceived = true; + } + } + + if ( + !this._secured && + this._shortReceived && + this._shortReceived && + !this.conn.isDraining() + ) { + if (this._count >= 1) { + this._secured = true; + this._securedP.resolveP(); + this.dispatchEvent(new events.QUICConnectionRemoteSecureEvent()); + } + this._count += 1; + } + this.dispatchEvent(new events.QUICConnectionRecvEvent()); // Here we can resolve our promises! if (this.conn.isEstablished()) { @@ -607,6 +690,51 @@ class QUICConnection extends EventTarget { ); return; } + + // Handling custom TLS verification, this must be done after the following conditions. + // 1. Connection established. + // 2. Certs available. + // 3. Sent after connection has established. + if ( + !this._customVerified && + this.conn.isEstablished() && + this.conn.peerCertChain() != null + ) { + this._customVerified = true; + const peerCerts = this.conn.peerCertChain(); + if (peerCerts == null) never(); + const peerCertsPem = peerCerts.map((c) => + utils.certificateDERToPEM(c), + ); + // Dispatching certs available event + this.dispatchEvent(new events.QUICConnectionRemoteCertEvent()); + try { + if (this.verifyCallback != null) this.verifyCallback(peerCertsPem); + this.conn.sendAckEliciting(); + } catch (e) { + // Force the connection to end. + // Error 304 indicates cert chain failed verification. + // Error 372 indicates cert chain was missing. + this.conn.close( + false, + 304, + Buffer.from(`Custom TLSFail: ${e.message}`), + ); + } + } + + // Check the header type + if (!this._shortSent) { + const header = quiche.Header.fromSlice( + sendBuffer, + quiche.MAX_CONN_ID_LEN, + ); + if (header.ty === Type.Short) { + // Short was sent, locally secured + this._shortSent = true; + } + } + try { this.logger.debug( `ATTEMPTING SEND ${sendLength} bytes to ${sendInfo.to.port}:${sendInfo.to.host}`, diff --git a/src/QUICServer.ts b/src/QUICServer.ts index 2d79133b..9d1dbcab 100644 --- a/src/QUICServer.ts +++ b/src/QUICServer.ts @@ -7,6 +7,7 @@ import type { RemoteInfo, StreamCodeToReason, StreamReasonToCode, + VerifyCallback, } from './types'; import type { Header } from './native/types'; import type QUICConnectionMap from './QUICConnectionMap'; @@ -52,6 +53,7 @@ class QUICServer extends EventTarget { protected maxWritableStreamBytes?: number | undefined; protected keepaliveIntervalTime?: number | undefined; protected connectionMap: QUICConnectionMap; + protected verifyCallback: VerifyCallback | undefined; /** * Handle QUIC socket errors @@ -78,6 +80,7 @@ class QUICServer extends EventTarget { maxReadableStreamBytes, maxWritableStreamBytes, keepaliveIntervalTime, + verifyCallback, logger, }: { crypto: { @@ -95,6 +98,7 @@ class QUICServer extends EventTarget { maxReadableStreamBytes?: number; maxWritableStreamBytes?: number; keepaliveIntervalTime?: number; + verifyCallback?: VerifyCallback; logger?: Logger; }) { super(); @@ -125,6 +129,7 @@ class QUICServer extends EventTarget { this.maxReadableStreamBytes = maxReadableStreamBytes; this.maxWritableStreamBytes = maxWritableStreamBytes; this.keepaliveIntervalTime = keepaliveIntervalTime; + this.verifyCallback = verifyCallback; } @ready(new errors.ErrorQUICServerNotRunning()) @@ -304,6 +309,7 @@ class QUICServer extends EventTarget { logger: this.logger.getChild( `${QUICConnection.name} ${scid.toString().slice(32)}-${clientConnRef}`, ), + verifyCallback: this.verifyCallback, }); connection.setKeepAlive(this.keepaliveIntervalTime); diff --git a/src/config.ts b/src/config.ts index 58147d0e..2be9ce19 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,6 +21,7 @@ type QUICConfig = { verifyFromPemFile: string | undefined; supportedPrivateKeyAlgos: string | undefined; verifyPeer: boolean; + verifyAllowFail: boolean; logKeys: string | undefined; grease: boolean; maxIdleTimeout: number; @@ -43,6 +44,7 @@ const clientDefault: QUICConfig = { supportedPrivateKeyAlgos: supportedPrivateKeyAlgosDefault, logKeys: undefined, verifyPeer: true, + verifyAllowFail: false, grease: true, maxIdleTimeout: 5000, maxRecvUdpPayloadSize: quiche.MAX_DATAGRAM_SIZE, @@ -64,6 +66,7 @@ const serverDefault: QUICConfig = { supportedPrivateKeyAlgos: supportedPrivateKeyAlgosDefault, logKeys: undefined, verifyPeer: false, + verifyAllowFail: false, grease: true, maxIdleTimeout: 5000, maxRecvUdpPayloadSize: quiche.MAX_DATAGRAM_SIZE, @@ -95,6 +98,7 @@ function buildQuicheConfig(config: QUICConfig): QuicheConfig { config.supportedPrivateKeyAlgos ?? null, config.verifyPem != null ? Buffer.from(config.verifyPem) : null, config.verifyPeer, + config.verifyAllowFail, ); if (config.tlsConfig != null && 'certChainFromPemFile' in config.tlsConfig) { if (config.tlsConfig?.certChainFromPemFile != null) { diff --git a/src/errors.ts b/src/errors.ts index 8917183a..17940ea8 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -74,6 +74,10 @@ class ErrorQUICConnectionTLSFailure extends ErrorQUICConnection { static description = 'QUIC connection had failure with TLS negotiation'; } +class ErrorQUICConnectionFailure extends ErrorQUICConnection { + static description = 'QUIC connection failed unexpectedly'; +} + class ErrorQUICStream extends ErrorQUIC { static description = 'QUIC Stream error'; } @@ -122,6 +126,7 @@ export { ErrorQUICConnectionDestroyed, ErrorQUICConnectionTimeout, ErrorQUICConnectionTLSFailure, + ErrorQUICConnectionFailure, ErrorQUICStream, ErrorQUICStreamDestroyed, ErrorQUICStreamLocked, diff --git a/src/events.ts b/src/events.ts index 47132f03..e6ca0511 100644 --- a/src/events.ts +++ b/src/events.ts @@ -100,6 +100,17 @@ class QUICConnectionErrorEvent extends Event { this.detail = options.detail; } } +class QUICConnectionRemoteCertEvent extends Event { + constructor(options?: EventInit) { + super('remoteCert', options); + } +} + +class QUICConnectionRemoteSecureEvent extends Event { + constructor(options?: EventInit) { + super('remoteSecure', options); + } +} class QUICStreamReadableEvent extends Event { constructor(options?: EventInit) { @@ -148,6 +159,8 @@ export { QUICConnectionRecvEvent, QUICConnectionDestroyEvent, QUICConnectionErrorEvent, + QUICConnectionRemoteCertEvent, + QUICConnectionRemoteSecureEvent, QUICStreamReadableEvent, QUICStreamWritableEvent, QUICStreamDestroyEvent, diff --git a/src/native/napi/config.rs b/src/native/napi/config.rs index bb200391..0542a17b 100644 --- a/src/native/napi/config.rs +++ b/src/native/napi/config.rs @@ -53,6 +53,7 @@ impl Config { supported_key_algos: Option, ca_cert_pem: Option, verify_peer: bool, + verify_allow_fail: bool, ) -> Result { let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new( boring::ssl::SslMethod::tls(), @@ -61,7 +62,15 @@ impl Config { )?; let verify_value = if verify_peer {boring::ssl::SslVerifyMode::PEER | boring::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT } else { boring::ssl::SslVerifyMode::NONE }; - ssl_ctx_builder.set_verify(verify_value); + ssl_ctx_builder.set_verify_callback(verify_value, move |pre_verify, _| { + // Override any validation errors, this is needed so we can request certs but validate them + // manually. + if verify_allow_fail { + true + } else { + pre_verify + } + }); // Processing and adding the cert chain if let Some(cert_pem) = cert_pem { let x509_cert_chain = boring::x509::X509::stack_from_pem( diff --git a/src/native/types.ts b/src/native/types.ts index f6c3b20c..de8d7fe3 100644 --- a/src/native/types.ts +++ b/src/native/types.ts @@ -49,6 +49,7 @@ interface ConfigConstructor { supportedKeyAlgos: string | null, ca_cert_pem: Uint8Array | null, verify_peer: boolean, + verifyAllowFail: boolean, ): Config; } @@ -312,11 +313,9 @@ type PathStatsIter = { [Symbol.iterator](): Iterator; }; +export { CongestionControlAlgorithm, Shutdown, Type, ConnectionErrorCode }; + export type { - CongestionControlAlgorithm, - Shutdown, - Type, - ConnectionErrorCode, ConnectionError, Stats, HostPort as Host, diff --git a/src/types.ts b/src/types.ts index 75dd4303..910915a0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -96,6 +96,8 @@ type ConnectionMetadata = { remotePort: Port; }; +type VerifyCallback = (certs: Array) => void; + export type { Opaque, Callback, @@ -113,4 +115,5 @@ export type { StreamReasonToCode, StreamCodeToReason, ConnectionMetadata, + VerifyCallback, }; diff --git a/tests/QUICClient.test.ts b/tests/QUICClient.test.ts index 66a8cd6a..7d2979e8 100644 --- a/tests/QUICClient.test.ts +++ b/tests/QUICClient.test.ts @@ -15,7 +15,7 @@ import { sleep } from './utils'; import * as fixtures from './fixtures/certFixtures'; describe(QUICClient.name, () => { - const logger = new Logger(`${QUICClient.name} Test`, LogLevel.WARN, [ + const logger = new Logger(`${QUICClient.name} Test`, LogLevel.INFO, [ new StreamHandler( formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, ), @@ -353,8 +353,7 @@ describe(QUICClient.name, () => { }, { numRuns: 10 }, ); - // Fixme: client verification works regardless of certs - testProp.skip( + testProp( 'client verification succeeds', [tlsConfigWithCaArb, tlsConfigWithCaArb], async (tlsConfigProm1, tlsConfigProm2) => { @@ -482,8 +481,7 @@ describe(QUICClient.name, () => { }, { numRuns: 3 }, ); - // Fixme: client verification works regardless of certs - testProp.skip( + testProp( 'graceful failure verifying client', [tlsConfigWithCaArb, tlsConfigWithCaArb], async (tlsConfigProm1, tlsConfigProm2) => { @@ -499,9 +497,16 @@ describe(QUICClient.name, () => { }); testsUtils.extractSocket(server, sockets); const handleConnectionEventProm = promise(); + const destroyedProm = promise(); server.addEventListener( 'connection', - handleConnectionEventProm.resolveP, + (event: events.QUICServerConnectionEvent) => { + handleConnectionEventProm.resolveP(event); + const serverConn = event.detail; + serverConn.addEventListener('destroy', () => { + destroyedProm.resolveP(); + }); + }, ); await server.start({ host: '127.0.0.1' as Host, @@ -521,10 +526,10 @@ describe(QUICClient.name, () => { }), ).toReject(); await handleConnectionEventProm.p; - // Expect connection on the server to have ended - // @ts-ignore: kidnap protected property - const connectionMap = server.connectionMap; - expect(connectionMap.serverConnections.size).toBe(0); + void sleep(5000).then(() => { + destroyedProm.rejectP(Error('Timed out waiting for destroy')); + }); + await destroyedProm.p; await server.stop(); }, { numRuns: 3 }, @@ -1399,4 +1404,230 @@ describe(QUICClient.name, () => { ); }); }); + describe('custom TLS verification', () => { + testProp( + 'server succeeds custom verification', + [tlsConfigWithCaArb], + async (tlsConfigsProm) => { + const tlsConfigs = await tlsConfigsProm; + const server = new QUICServer({ + crypto, + logger: logger.getChild(QUICServer.name), + config: { + tlsConfig: tlsConfigs.tlsConfig, + verifyPeer: false, + }, + }); + testsUtils.extractSocket(server, sockets); + const handleConnectionEventProm = promise(); + server.addEventListener( + 'connection', + handleConnectionEventProm.resolveP, + ); + await server.start({ + host: '127.0.0.1' as Host, + }); + // Connection should succeed + const verifyProm = promise | undefined>(); + const client = await QUICClient.createQUICClient({ + host: '127.0.0.1' as Host, + port: server.port, + localHost: '127.0.0.1' as Host, + crypto, + logger: logger.getChild(QUICClient.name), + config: { + verifyPeer: true, + verifyAllowFail: true, + }, + verifyCallback: (certs) => { + verifyProm.resolveP(certs); + }, + }); + testsUtils.extractSocket(client, sockets); + await handleConnectionEventProm.p; + await expect(verifyProm.p).toResolve(); + await client.destroy(); + await server.stop(); + }, + { numRuns: 5 }, + ); + testProp( + 'server fails custom verification', + [tlsConfigWithCaArb], + async (tlsConfigsProm) => { + const tlsConfigs = await tlsConfigsProm; + const server = new QUICServer({ + crypto, + logger: logger.getChild(QUICServer.name), + config: { + tlsConfig: tlsConfigs.tlsConfig, + verifyPeer: false, + }, + }); + testsUtils.extractSocket(server, sockets); + const handleConnectionEventProm = promise(); + server.addEventListener( + 'connection', + (event: events.QUICServerConnectionEvent) => + handleConnectionEventProm.resolveP(event.detail), + ); + await server.start({ + host: '127.0.0.1' as Host, + }); + // Connection should fail + const clientProm = QUICClient.createQUICClient({ + host: '127.0.0.1' as Host, + port: server.port, + localHost: '127.0.0.1' as Host, + crypto, + logger: logger.getChild(QUICClient.name), + config: { + verifyPeer: true, + verifyAllowFail: true, + }, + verifyCallback: () => { + throw Error('SOME ERROR'); + }, + }); + clientProm.catch(() => {}); + + const serverConn = await handleConnectionEventProm.p; + + const destroyProm = promise(); + serverConn.addEventListener('destroy', () => destroyProm.resolveP()); + const errorProm = promise(); + errorProm.p.catch(() => {}); + serverConn.addEventListener( + 'error', + (event: events.QUICConnectionErrorEvent) => { + errorProm.rejectP(event.detail); + }, + ); + await destroyProm.p; + // Server connection fails with connection failure + await expect(errorProm.p).rejects.toThrow( + errors.ErrorQUICConnectionFailure, + ); + await expect(clientProm).rejects.toThrow( + errors.ErrorQUICConnectionFailure, + ); + + await server.stop(); + }, + { numRuns: 5 }, + ); + testProp( + 'client succeeds custom verification', + [tlsConfigWithCaArb], + async (tlsConfigsProm) => { + const tlsConfigs = await tlsConfigsProm; + const verifyProm = promise | undefined>(); + const server = new QUICServer({ + crypto, + logger: logger.getChild(QUICServer.name), + config: { + tlsConfig: tlsConfigs.tlsConfig, + verifyPeer: true, + verifyAllowFail: true, + }, + verifyCallback: (certs) => { + verifyProm.resolveP(certs); + }, + }); + testsUtils.extractSocket(server, sockets); + const handleConnectionEventProm = promise(); + server.addEventListener( + 'connection', + handleConnectionEventProm.resolveP, + ); + await server.start({ + host: '127.0.0.1' as Host, + }); + // Connection should succeed + const client = await QUICClient.createQUICClient({ + host: '127.0.0.1' as Host, + port: server.port, + localHost: '127.0.0.1' as Host, + crypto, + logger: logger.getChild(QUICClient.name), + config: { + verifyPeer: false, + tlsConfig: tlsConfigs.tlsConfig, + }, + }); + testsUtils.extractSocket(client, sockets); + await handleConnectionEventProm.p; + await expect(verifyProm.p).toResolve(); + await client.destroy(); + await server.stop(); + }, + { numRuns: 5 }, + ); + testProp( + 'client fails custom verification', + [tlsConfigWithCaArb], + async (tlsConfigsProm) => { + const tlsConfigs = await tlsConfigsProm; + const server = new QUICServer({ + crypto, + logger: logger.getChild(QUICServer.name), + config: { + tlsConfig: tlsConfigs.tlsConfig, + verifyPeer: true, + verifyAllowFail: true, + }, + verifyCallback: () => { + throw Error('SOME ERROR'); + }, + }); + testsUtils.extractSocket(server, sockets); + const handleConnectionEventProm = promise(); + server.addEventListener( + 'connection', + (event: events.QUICServerConnectionEvent) => + handleConnectionEventProm.resolveP(event.detail), + ); + await server.start({ + host: '127.0.0.1' as Host, + }); + // Connection should fail + const clientProm = QUICClient.createQUICClient({ + host: '127.0.0.1' as Host, + port: server.port, + localHost: '127.0.0.1' as Host, + crypto, + logger: logger.getChild(QUICClient.name), + config: { + tlsConfig: tlsConfigs.tlsConfig, + verifyPeer: false, + }, + }); + clientProm.catch(() => {}); + + const serverConn = await handleConnectionEventProm.p; + + const destroyProm = promise(); + serverConn.addEventListener('destroy', () => destroyProm.resolveP()); + const errorProm = promise(); + errorProm.p.catch(() => {}); + serverConn.addEventListener( + 'error', + (event: events.QUICConnectionErrorEvent) => { + errorProm.rejectP(event.detail); + }, + ); + await destroyProm.p; + // Server connection fails with connection failure + await expect(errorProm.p).rejects.toThrow( + errors.ErrorQUICConnectionFailure, + ); + await expect(clientProm).rejects.toThrow( + errors.ErrorQUICConnectionFailure, + ); + + await server.stop(); + }, + { numRuns: 5 }, + ); + }); });