Skip to content

Commit f3204cf

Browse files
committed
WIP
1 parent 4c4abaf commit f3204cf

File tree

5 files changed

+256
-28
lines changed

5 files changed

+256
-28
lines changed

src/QUICServer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,17 @@ class QUICServer extends EventTarget {
146146
public async start({
147147
host = '::' as Host,
148148
port = 0 as Port,
149+
reuseAddr,
149150
}: {
150151
host?: Host | Hostname;
151152
port?: Port;
153+
reuseAddr?: boolean;
152154
} = {}) {
153155
let address: string;
154156
if (!this.isSocketShared) {
155157
address = utils.buildAddress(host, port);
156158
this.logger.info(`Start ${this.constructor.name} on ${address}`);
157-
await this.socket.start({ host, port });
159+
await this.socket.start({ host, port, reuseAddr });
158160
this.socket.addEventListener('error', this.handleQUICSocketError);
159161
address = utils.buildAddress(this.socket.host, this.socket.port);
160162
} else {
@@ -193,6 +195,9 @@ class QUICServer extends EventTarget {
193195
this.logger.info(`Stopped ${this.constructor.name} on ${address}`);
194196
}
195197

198+
/**
199+
* @internal
200+
*/
196201
public async connectionNew(
197202
data: Buffer,
198203
remoteInfo: RemoteInfo,

src/QUICSocket.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,8 @@ class QUICSocket extends EventTarget {
7272
}
7373
return;
7474
}
75-
76-
// Apparently if it is a UDP datagram
77-
// it could be a QUIC datagram, and not part of any connection
78-
// However I don't know how the DCID would work in a QUIC dgram
79-
// We have not explored this yet
80-
8175
// Destination Connection ID is the ID the remote peer chose for us.
8276
const dcid = new QUICConnectionId(header.dcid);
83-
8477
// Derive our SCID using HMAC signing.
8578
const scid = new QUICConnectionId(
8679
await this.crypto.ops.sign(
@@ -90,12 +83,10 @@ class QUICSocket extends EventTarget {
9083
0,
9184
quiche.MAX_CONN_ID_LEN,
9285
);
93-
9486
const remoteInfo_ = {
9587
host: remoteInfo.address as Host,
9688
port: remoteInfo.port as Port,
9789
};
98-
9990
// Now both must be checked
10091
let conn: QUICConnection;
10192
if (!this.connectionMap.has(dcid) && !this.connectionMap.has(scid)) {
@@ -207,10 +198,12 @@ class QUICSocket extends EventTarget {
207198
public async start({
208199
host = '::' as Host,
209200
port = 0 as Port,
201+
reuseAddr = false,
210202
ipv6Only = false,
211203
}: {
212204
host?: Host | Hostname;
213205
port?: Port;
206+
reuseAddr?: boolean;
214207
ipv6Only?: boolean;
215208
} = {}): Promise<void> {
216209
let address = utils.buildAddress(host, port);
@@ -223,7 +216,7 @@ class QUICSocket extends EventTarget {
223216
);
224217
this.socket = dgram.createSocket({
225218
type: udpType,
226-
reuseAddr: false,
219+
reuseAddr,
227220
ipv6Only,
228221
});
229222
this.socketBind = utils.promisify(this.socket.bind).bind(this.socket);

src/config.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ export type TlsConfig =
1616
};
1717

1818
type QUICConfig = {
19+
// This is the same style as TLS in node.js, where Uint8Array or strings can be used
20+
ca?: string | Array<string> | Uint8Array | Array<Uint8Array>;
21+
22+
1923
tlsConfig: TlsConfig | undefined;
20-
verifyPem: string | undefined;
21-
verifyFromPemFile: string | undefined;
24+
25+
// verifyPem: string | undefined;
26+
// verifyFromPemFile: string | undefined;
27+
2228
supportedPrivateKeyAlgos: string | undefined;
2329
verifyPeer: boolean;
2430
logKeys: string | undefined;
@@ -37,9 +43,10 @@ type QUICConfig = {
3743
};
3844

3945
const clientDefault: QUICConfig = {
46+
ca: undefined,
4047
tlsConfig: undefined,
41-
verifyPem: undefined,
42-
verifyFromPemFile: undefined,
48+
// verifyPem: undefined,
49+
// verifyFromPemFile: undefined,
4350
supportedPrivateKeyAlgos: supportedPrivateKeyAlgosDefault,
4451
logKeys: undefined,
4552
verifyPeer: true,
@@ -58,9 +65,11 @@ const clientDefault: QUICConfig = {
5865
};
5966

6067
const serverDefault: QUICConfig = {
68+
ca: undefined,
6169
tlsConfig: undefined,
62-
verifyPem: undefined,
63-
verifyFromPemFile: undefined,
70+
71+
// verifyPem: undefined,
72+
// verifyFromPemFile: undefined,
6473
supportedPrivateKeyAlgos: supportedPrivateKeyAlgosDefault,
6574
logKeys: undefined,
6675
verifyPeer: false,
@@ -89,11 +98,35 @@ function buildQuicheConfig(config: QUICConfig): QuicheConfig {
8998
privKeyPem = Buffer.from(config.tlsConfig.privKeyPem);
9099
}
91100
}
101+
102+
// Ok let's see
103+
let caBuffer;
104+
if (config.ca != null) {
105+
const textDecoder = new TextDecoder('utf-8');
106+
const textEncoder = new TextEncoder();
107+
let caPEMString = '';
108+
if (typeof config.ca === 'string') {
109+
caPEMString = config.ca.trim() + '\n';
110+
} else if (config.ca instanceof Uint8Array) {
111+
caPEMString = textDecoder.decode(config.ca).trim() + '\n';
112+
} else if (Array.isArray(config.ca)) {
113+
for (const c of config.ca) {
114+
if (typeof c === 'string') {
115+
caPEMString += c.trim() + '\n';
116+
} else {
117+
caPEMString += textDecoder.decode(c).trim() + '\n';
118+
}
119+
}
120+
}
121+
caBuffer = textEncoder.encode(caPEMString);
122+
}
123+
124+
92125
const quicheConfig: QuicheConfig = quiche.Config.withBoringSslCtx(
93126
certChainPem,
94127
privKeyPem,
95128
config.supportedPrivateKeyAlgos ?? null,
96-
config.verifyPem != null ? Buffer.from(config.verifyPem) : null,
129+
caBuffer,
97130
config.verifyPeer,
98131
);
99132
if (config.tlsConfig != null && 'certChainFromPemFile' in config.tlsConfig) {
@@ -106,9 +139,9 @@ function buildQuicheConfig(config: QUICConfig): QuicheConfig {
106139
quicheConfig.loadPrivKeyFromPemFile(config.tlsConfig.privKeyFromPemFile);
107140
}
108141
}
109-
if (config.verifyFromPemFile != null) {
110-
quicheConfig.loadVerifyLocationsFromFile(config.verifyFromPemFile);
111-
}
142+
// if (config.verifyFromPemFile != null) {
143+
// quicheConfig.loadVerifyLocationsFromFile(config.verifyFromPemFile);
144+
// }
112145
if (config.logKeys != null) {
113146
quicheConfig.logKeys();
114147
}

tests/QUICServer.test.ts

Lines changed: 199 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,212 @@
1-
import QUICServer from '@/QUICServer';
1+
import type { X509Certificate } from '@peculiar/x509';
2+
import type { Crypto, Host, Port } from '@/types';
3+
import type { QUICConfig } from '@/config';
4+
import dgram from 'dgram';
25
import Logger, { LogLevel, StreamHandler, formatting } from '@matrixai/logger';
6+
import QUICServer from '@/QUICServer';
7+
import QUICConnectionId from '@/QUICConnectionId';
8+
import QUICConnection from '@/QUICConnection';
9+
import QUICSocket from '@/QUICSocket';
10+
import { clientDefault, buildQuicheConfig } from '@/config';
11+
import { quiche } from '@/native';
12+
import * as utils from '@/utils';
13+
import * as testsUtils from './utils';
314

415
describe(QUICServer.name, () => {
5-
616
const logger = new Logger(`${QUICServer.name} Test`, LogLevel.WARN, [
717
new StreamHandler(
818
formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`,
919
),
1020
]);
11-
describe('', async () => {
21+
let keyPairRSA: {
22+
publicKey: JsonWebKey;
23+
privateKey: JsonWebKey;
24+
};
25+
let certRSA: X509Certificate;
26+
let keyPairRSAPEM: {
27+
publicKey: string;
28+
privateKey: string;
29+
};
30+
let certRSAPEM: string;
31+
let keyPairECDSA: {
32+
publicKey: JsonWebKey;
33+
privateKey: JsonWebKey;
34+
};
35+
let certECDSA: X509Certificate;
36+
let keyPairECDSAPEM: {
37+
publicKey: string;
38+
privateKey: string;
39+
};
40+
let certECDSAPEM: string;
41+
let keyPairEd25519: {
42+
publicKey: JsonWebKey;
43+
privateKey: JsonWebKey;
44+
};
45+
let certEd25519: X509Certificate;
46+
let keyPairEd25519PEM: {
47+
publicKey: string;
48+
privateKey: string;
49+
};
50+
let certEd25519PEM: string;
51+
beforeAll(async () => {
52+
keyPairRSA = await testsUtils.generateKeyPairRSA();
53+
certRSA = await testsUtils.generateCertificate({
54+
certId: '0',
55+
subjectKeyPair: keyPairRSA,
56+
issuerPrivateKey: keyPairRSA.privateKey,
57+
duration: 60 * 60 * 24 * 365 * 10,
58+
});
59+
keyPairRSAPEM = await testsUtils.keyPairRSAtoPEM(keyPairRSA);
60+
certRSAPEM = testsUtils.certToPEM(certRSA);
61+
keyPairECDSA = await testsUtils.generateKeyPairECDSA();
62+
certECDSA = await testsUtils.generateCertificate({
63+
certId: '0',
64+
subjectKeyPair: keyPairECDSA,
65+
issuerPrivateKey: keyPairECDSA.privateKey,
66+
duration: 60 * 60 * 24 * 365 * 10,
67+
});
68+
keyPairECDSAPEM = await testsUtils.keyPairECDSAtoPEM(keyPairECDSA);
69+
certECDSAPEM = testsUtils.certToPEM(certECDSA);
70+
keyPairEd25519 = await testsUtils.generateKeyPairEd25519();
71+
certEd25519 = await testsUtils.generateCertificate({
72+
certId: '0',
73+
subjectKeyPair: keyPairEd25519,
74+
issuerPrivateKey: keyPairEd25519.privateKey,
75+
duration: 60 * 60 * 24 * 365 * 10,
76+
});
77+
keyPairEd25519PEM = await testsUtils.keyPairEd25519ToPEM(keyPairEd25519);
78+
certEd25519PEM = testsUtils.certToPEM(certEd25519);
79+
});
80+
// This has to be setup asynchronously due to key generation
81+
let crypto: {
82+
key: ArrayBuffer;
83+
ops: Crypto;
84+
};
85+
beforeEach(async () => {
86+
crypto = {
87+
key: await testsUtils.generateKeyHMAC(),
88+
ops: {
89+
sign: testsUtils.signHMAC,
90+
verify: testsUtils.verifyHMAC,
91+
randomBytes: testsUtils.randomBytes,
92+
},
93+
};
94+
});
95+
describe('start and stop', () => {
96+
test('with RSA', async () => {
97+
const quicServer = new QUICServer({
98+
crypto,
99+
config: {
100+
tlsConfig: {
101+
privKeyPem: keyPairRSAPEM.privateKey,
102+
certChainPem: certRSAPEM,
103+
}
104+
},
105+
logger: logger.getChild('QUICServer'),
106+
});
107+
await quicServer.start();
108+
// Default to dual-stack
109+
expect(quicServer.host).toBe('::');
110+
expect(typeof quicServer.port).toBe('number');
111+
await quicServer.stop();
112+
});
113+
test('with ECDSA', async () => {
114+
const quicServer = new QUICServer({
115+
crypto,
116+
config: {
117+
tlsConfig: {
118+
privKeyPem: keyPairECDSAPEM.privateKey,
119+
certChainPem: certECDSAPEM,
120+
}
121+
},
122+
logger: logger.getChild('QUICServer'),
123+
});
124+
await quicServer.start();
125+
// Default to dual-stack
126+
expect(quicServer.host).toBe('::');
127+
expect(typeof quicServer.port).toBe('number');
128+
await quicServer.stop();
129+
});
130+
test('with Ed25519', async () => {
131+
const quicServer = new QUICServer({
132+
crypto,
133+
config: {
134+
tlsConfig: {
135+
privKeyPem: keyPairEd25519PEM.privateKey,
136+
certChainPem: certEd25519PEM,
137+
}
138+
},
139+
logger: logger.getChild('QUICServer'),
140+
});
141+
await quicServer.start();
142+
// Default to dual-stack
143+
expect(quicServer.host).toBe('::');
144+
expect(typeof quicServer.port).toBe('number');
145+
await quicServer.stop();
146+
});
147+
});
148+
test('bootstrapping a new connection', async () => {
149+
const quicServer = new QUICServer({
150+
crypto,
151+
config: {
152+
tlsConfig: {
153+
privKeyPem: keyPairEd25519PEM.privateKey,
154+
certChainPem: certEd25519PEM,
155+
}
156+
},
157+
logger: logger.getChild('QUICServer'),
158+
});
159+
await quicServer.start();
160+
161+
162+
163+
const scidBuffer = new ArrayBuffer(quiche.MAX_CONN_ID_LEN);
164+
await crypto.ops.randomBytes(scidBuffer);
165+
const scid = new QUICConnectionId(scidBuffer);
166+
167+
const socket = new QUICSocket({
168+
crypto,
169+
resolveHostname: utils.resolveHostname,
170+
logger: logger.getChild(QUICSocket.name)
171+
});
172+
await socket.start();
173+
174+
// const config = buildQuicheConfig({
175+
// ...clientDefault
176+
// });
177+
// Here we want to VERIFY the peer
178+
// If we use the same certificate
179+
// then it should be consider as if it is trusted!
180+
181+
const quicConfig: QUICConfig = {
182+
...clientDefault,
183+
verifyPeer: true
184+
};
185+
186+
187+
const connection = await QUICConnection.connectQUICConnection({
188+
scid,
189+
socket,
190+
191+
remoteInfo: {
192+
host: utils.resolvesZeroIP(quicServer.host),
193+
port: quicServer.port,
194+
},
195+
196+
config: quicConfig,
197+
198+
});
199+
200+
201+
await socket.stop();
202+
await quicServer.stop();
203+
204+
// We can run with several rsa keypairs and certificates
12205

13206
});
207+
describe('updating configuration', () => {
14208

209+
// We want to test changing the configuration over time
15210

211+
});
16212
});

0 commit comments

Comments
 (0)