Skip to content

Commit 953efbc

Browse files
committed
fix: separating the websockets code to its own domain
[ci skip]
1 parent bf3627b commit 953efbc

13 files changed

Lines changed: 302 additions & 225 deletions

File tree

src/clientRPC/errors.ts

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,28 @@
11
import { ErrorPolykey, sysexits } from '../errors';
22

3-
class ErrorClient<T> extends ErrorPolykey<T> {}
3+
class ErrorRPC<T> extends ErrorPolykey<T> {}
44

5-
class ErrorClientClient<T> extends ErrorClient<T> {}
5+
class ErrorRPCClient<T> extends ErrorRPC<T> {}
66

7-
class ErrorClientDestroyed<T> extends ErrorClientClient<T> {
8-
static description = 'ClientClient has been destroyed';
9-
exitCode = sysexits.USAGE;
10-
}
11-
12-
class ErrorClientInvalidHost<T> extends ErrorClientClient<T> {
13-
static description = 'Host must be a valid IPv4 or IPv6 address string';
14-
exitCode = sysexits.USAGE;
15-
}
16-
17-
class ErrorClientConnectionFailed<T> extends ErrorClientClient<T> {
18-
static description = 'Failed to establish connection to server';
19-
exitCode = sysexits.UNAVAILABLE;
20-
}
21-
22-
class ErrorClientConnectionTimedOut<T> extends ErrorClientClient<T> {
23-
static description = 'Connection timed out';
24-
exitCode = sysexits.UNAVAILABLE;
25-
}
26-
27-
class ErrorClientConnectionEndedEarly<T> extends ErrorClientClient<T> {
28-
static description = 'Connection ended before stream ended';
29-
exitCode = sysexits.UNAVAILABLE;
30-
}
31-
32-
class ErrorClientServer<T> extends ErrorClient<T> {}
33-
34-
class ErrorServerPortUnavailable<T> extends ErrorClientServer<T> {
35-
static description = 'Failed to bind a free port';
36-
exitCode = sysexits.UNAVAILABLE;
37-
}
38-
39-
class ErrorServerSendFailed<T> extends ErrorClientServer<T> {
40-
static description = 'Failed to send message';
41-
exitCode = sysexits.UNAVAILABLE;
7+
class ErrorClientAuthMissing<T> extends ErrorRPCClient<T> {
8+
static description = 'Authorisation metadata is required but missing';
9+
exitCode = sysexits.NOPERM;
4210
}
4311

44-
class ErrorServerReadableBufferLimit<T> extends ErrorClientServer<T> {
45-
static description = 'Readable buffer is full, messages received too quickly';
12+
class ErrorClientAuthFormat<T> extends ErrorRPCClient<T> {
13+
static description = 'Authorisation metadata has invalid format';
4614
exitCode = sysexits.USAGE;
4715
}
4816

49-
class ErrorServerConnectionEndedEarly<T> extends ErrorClientServer<T> {
50-
static description = 'Connection ended before stream ended';
51-
exitCode = sysexits.UNAVAILABLE;
17+
class ErrorClientAuthDenied<T> extends ErrorRPCClient<T> {
18+
static description = 'Authorisation metadata is incorrect or expired';
19+
exitCode = sysexits.NOPERM;
5220
}
5321

5422
export {
55-
ErrorClientClient,
56-
ErrorClientDestroyed,
57-
ErrorClientInvalidHost,
58-
ErrorClientConnectionFailed,
59-
ErrorClientConnectionTimedOut,
60-
ErrorClientConnectionEndedEarly,
61-
ErrorClientServer,
62-
ErrorServerPortUnavailable,
63-
ErrorServerSendFailed,
64-
ErrorServerReadableBufferLimit,
65-
ErrorServerConnectionEndedEarly,
23+
ErrorRPC,
24+
ErrorRPCClient,
25+
ErrorClientAuthMissing,
26+
ErrorClientAuthFormat,
27+
ErrorClientAuthDenied,
6628
};

src/clientRPC/utils.ts

Lines changed: 6 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import type { SessionToken } from '../sessions/types';
2-
import type KeyRing from '../keys/KeyRing';
3-
import type SessionManager from '../sessions/SessionManager';
41
import type { RPCRequestParams } from './types';
5-
import type { JsonRpcRequest } from '../RPC/types';
6-
import type { Certificate } from 'keys/types';
7-
import type { DetailedPeerCertificate } from 'tls';
8-
import type { NodeId } from 'ids/index';
9-
import * as x509 from '@peculiar/x509';
10-
import * as clientErrors from '../client/errors';
11-
import * as networkErrors from '../network/errors';
12-
import * as keysUtils from '../keys/utils/index';
2+
import type SessionManager from 'sessions/SessionManager';
3+
import type KeyRing from 'keys/KeyRing';
4+
import type { JsonRpcRequest } from 'RPC/types';
5+
import type { SessionToken } from 'sessions/types';
6+
import * as clientErrors from './errors';
137

148
async function authenticate(
159
sessionManager: SessionManager,
@@ -60,149 +54,4 @@ function encodeAuthFromPassword(password: string): string {
6054
return `Basic ${encoded}`;
6155
}
6256

63-
function detailedToCertChain(
64-
cert: DetailedPeerCertificate,
65-
): Array<Certificate> {
66-
const certChain: Array<Certificate> = [];
67-
let currentCert = cert;
68-
while (true) {
69-
certChain.unshift(new x509.X509Certificate(currentCert.raw));
70-
if (currentCert === currentCert.issuerCertificate) break;
71-
currentCert = currentCert.issuerCertificate;
72-
}
73-
return certChain;
74-
}
75-
76-
/**
77-
* Verify the server certificate chain when connecting to it from a client
78-
* This is a custom verification intended to verify that the server owned
79-
* the relevant NodeId.
80-
* It is possible that the server has a new NodeId. In that case we will
81-
* verify that the new NodeId is the true descendant of the target NodeId.
82-
*/
83-
async function verifyServerCertificateChain(
84-
nodeIds: Array<NodeId>,
85-
certChain: Array<Certificate>,
86-
): Promise<NodeId> {
87-
if (!certChain.length) {
88-
throw new networkErrors.ErrorCertChainEmpty(
89-
'No certificates available to verify',
90-
);
91-
}
92-
if (!nodeIds.length) {
93-
throw new networkErrors.ErrorConnectionNodesEmpty(
94-
'No nodes were provided to verify against',
95-
);
96-
}
97-
const now = new Date();
98-
let certClaim: Certificate | null = null;
99-
let certClaimIndex: number | null = null;
100-
let verifiedNodeId: NodeId | null = null;
101-
for (let certIndex = 0; certIndex < certChain.length; certIndex++) {
102-
const cert = certChain[certIndex];
103-
if (now < cert.notBefore || now > cert.notAfter) {
104-
throw new networkErrors.ErrorCertChainDateInvalid(
105-
'Chain certificate date is invalid',
106-
{
107-
data: {
108-
cert,
109-
certIndex,
110-
notBefore: cert.notBefore,
111-
notAfter: cert.notAfter,
112-
now,
113-
},
114-
},
115-
);
116-
}
117-
const certNodeId = keysUtils.certNodeId(cert);
118-
if (certNodeId == null) {
119-
throw new networkErrors.ErrorCertChainNameInvalid(
120-
'Chain certificate common name attribute is missing',
121-
{
122-
data: {
123-
cert,
124-
certIndex,
125-
},
126-
},
127-
);
128-
}
129-
const certPublicKey = keysUtils.certPublicKey(cert);
130-
if (certPublicKey == null) {
131-
throw new networkErrors.ErrorCertChainKeyInvalid(
132-
'Chain certificate public key is missing',
133-
{
134-
data: {
135-
cert,
136-
certIndex,
137-
},
138-
},
139-
);
140-
}
141-
if (!(await keysUtils.certNodeSigned(cert))) {
142-
throw new networkErrors.ErrorCertChainSignatureInvalid(
143-
'Chain certificate does not have a valid node-signature',
144-
{
145-
data: {
146-
cert,
147-
certIndex,
148-
nodeId: keysUtils.publicKeyToNodeId(certPublicKey),
149-
commonName: certNodeId,
150-
},
151-
},
152-
);
153-
}
154-
for (const nodeId of nodeIds) {
155-
if (certNodeId.equals(nodeId)) {
156-
// Found the certificate claiming the nodeId
157-
certClaim = cert;
158-
certClaimIndex = certIndex;
159-
verifiedNodeId = nodeId;
160-
}
161-
}
162-
// If cert is found then break out of loop
163-
if (verifiedNodeId != null) break;
164-
}
165-
if (certClaimIndex == null || certClaim == null || verifiedNodeId == null) {
166-
throw new networkErrors.ErrorCertChainUnclaimed(
167-
'Node IDs is not claimed by any certificate',
168-
{
169-
data: { nodeIds },
170-
},
171-
);
172-
}
173-
if (certClaimIndex > 0) {
174-
let certParent: Certificate;
175-
let certChild: Certificate;
176-
for (let certIndex = certClaimIndex; certIndex > 0; certIndex--) {
177-
certParent = certChain[certIndex];
178-
certChild = certChain[certIndex - 1];
179-
if (
180-
!keysUtils.certIssuedBy(certParent, certChild) ||
181-
!(await keysUtils.certSignedBy(
182-
certParent,
183-
keysUtils.certPublicKey(certChild)!,
184-
))
185-
) {
186-
throw new networkErrors.ErrorCertChainBroken(
187-
'Chain certificate is not signed by parent certificate',
188-
{
189-
data: {
190-
cert: certChild,
191-
certIndex: certIndex - 1,
192-
certParent,
193-
},
194-
},
195-
);
196-
}
197-
}
198-
}
199-
return verifiedNodeId;
200-
}
201-
202-
export {
203-
authenticate,
204-
decodeAuth,
205-
encodeAuthFromPassword,
206-
detailedToCertChain,
207-
verifyServerCertificateChain,
208-
};
57+
export { authenticate, decodeAuth, encodeAuthFromPassword };

src/websockets/errors.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { ErrorPolykey, sysexits } from '../errors';
2+
3+
class ErrorWebSocket<T> extends ErrorPolykey<T> {}
4+
5+
class ErrorWebSocketClient<T> extends ErrorWebSocket<T> {}
6+
7+
class ErrorClientDestroyed<T> extends ErrorWebSocketClient<T> {
8+
static description = 'ClientClient has been destroyed';
9+
exitCode = sysexits.USAGE;
10+
}
11+
12+
class ErrorClientInvalidHost<T> extends ErrorWebSocketClient<T> {
13+
static description = 'Host must be a valid IPv4 or IPv6 address string';
14+
exitCode = sysexits.USAGE;
15+
}
16+
17+
class ErrorClientConnectionFailed<T> extends ErrorWebSocketClient<T> {
18+
static description = 'Failed to establish connection to server';
19+
exitCode = sysexits.UNAVAILABLE;
20+
}
21+
22+
class ErrorClientConnectionTimedOut<T> extends ErrorWebSocketClient<T> {
23+
static description = 'Connection timed out';
24+
exitCode = sysexits.UNAVAILABLE;
25+
}
26+
27+
class ErrorClientConnectionEndedEarly<T> extends ErrorWebSocketClient<T> {
28+
static description = 'Connection ended before stream ended';
29+
exitCode = sysexits.UNAVAILABLE;
30+
}
31+
32+
class ErrorWebSocketServer<T> extends ErrorWebSocket<T> {}
33+
34+
class ErrorServerPortUnavailable<T> extends ErrorWebSocketServer<T> {
35+
static description = 'Failed to bind a free port';
36+
exitCode = sysexits.UNAVAILABLE;
37+
}
38+
39+
class ErrorServerSendFailed<T> extends ErrorWebSocketServer<T> {
40+
static description = 'Failed to send message';
41+
exitCode = sysexits.UNAVAILABLE;
42+
}
43+
44+
class ErrorServerReadableBufferLimit<T> extends ErrorWebSocketServer<T> {
45+
static description = 'Readable buffer is full, messages received too quickly';
46+
exitCode = sysexits.USAGE;
47+
}
48+
49+
class ErrorServerConnectionEndedEarly<T> extends ErrorWebSocketServer<T> {
50+
static description = 'Connection ended before stream ended';
51+
exitCode = sysexits.UNAVAILABLE;
52+
}
53+
54+
/**
55+
* Used for certificate verification
56+
*/
57+
class ErrorCertChain<T> extends ErrorWebSocket<T> {}
58+
59+
class ErrorCertChainEmpty<T> extends ErrorCertChain<T> {
60+
static description = 'Certificate chain is empty';
61+
exitCode = sysexits.PROTOCOL;
62+
}
63+
64+
class ErrorCertChainUnclaimed<T> extends ErrorCertChain<T> {
65+
static description = 'The target node id is not claimed by any certificate';
66+
exitCode = sysexits.PROTOCOL;
67+
}
68+
69+
class ErrorCertChainBroken<T> extends ErrorCertChain<T> {
70+
static description = 'The signature chain is broken';
71+
exitCode = sysexits.PROTOCOL;
72+
}
73+
74+
class ErrorCertChainDateInvalid<T> extends ErrorCertChain<T> {
75+
static description = 'Certificate in the chain is expired';
76+
exitCode = sysexits.PROTOCOL;
77+
}
78+
79+
class ErrorCertChainNameInvalid<T> extends ErrorCertChain<T> {
80+
static description = 'Certificate is missing the common name';
81+
exitCode = sysexits.PROTOCOL;
82+
}
83+
84+
class ErrorCertChainKeyInvalid<T> extends ErrorCertChain<T> {
85+
static description = 'Certificate public key does not generate the Node ID';
86+
exitCode = sysexits.PROTOCOL;
87+
}
88+
89+
class ErrorCertChainSignatureInvalid<T> extends ErrorCertChain<T> {
90+
static description = 'Certificate self-signed signature is invalid';
91+
exitCode = sysexits.PROTOCOL;
92+
}
93+
94+
class ErrorConnectionNodesEmpty<T> extends ErrorWebSocket<T> {
95+
static description = 'Nodes list to verify against was empty';
96+
exitCode = sysexits.USAGE;
97+
}
98+
99+
export {
100+
ErrorWebSocketClient,
101+
ErrorClientDestroyed,
102+
ErrorClientInvalidHost,
103+
ErrorClientConnectionFailed,
104+
ErrorClientConnectionTimedOut,
105+
ErrorClientConnectionEndedEarly,
106+
ErrorWebSocketServer,
107+
ErrorServerPortUnavailable,
108+
ErrorServerSendFailed,
109+
ErrorServerReadableBufferLimit,
110+
ErrorServerConnectionEndedEarly,
111+
ErrorCertChainEmpty,
112+
ErrorCertChainUnclaimed,
113+
ErrorCertChainBroken,
114+
ErrorCertChainDateInvalid,
115+
ErrorCertChainNameInvalid,
116+
ErrorCertChainKeyInvalid,
117+
ErrorCertChainSignatureInvalid,
118+
ErrorConnectionNodesEmpty,
119+
};

0 commit comments

Comments
 (0)