@libp2p/tls 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -0
- package/README.md +45 -0
- package/dist/src/index.d.ts +34 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +99 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/pb/index.d.ts +21 -0
- package/dist/src/pb/index.d.ts.map +1 -0
- package/dist/src/pb/index.js +78 -0
- package/dist/src/pb/index.js.map +1 -0
- package/dist/src/utils.d.ts +11 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +157 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/typedoc-urls.json +8 -0
- package/package.json +71 -0
- package/src/index.ts +125 -0
- package/src/pb/index.proto +13 -0
- package/src/pb/index.ts +95 -0
- package/src/utils.ts +186 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[](http://libp2p.io/)
|
|
2
|
+
[](https://discuss.libp2p.io)
|
|
3
|
+
[](https://codecov.io/gh/libp2p/js-libp2p)
|
|
4
|
+
[](https://github.com/libp2p/js-libp2p/actions/workflows/main.yml?query=branch%3Amain)
|
|
5
|
+
|
|
6
|
+
> A connection encrypter that uses TLS 1.3
|
|
7
|
+
|
|
8
|
+
# About
|
|
9
|
+
|
|
10
|
+
Implements the spec at <https://github.com/libp2p/specs/blob/master/tls/tls.md>
|
|
11
|
+
|
|
12
|
+
## Example
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { createLibp2p } from 'libp2p'
|
|
16
|
+
import { tls } from '@libp2p/tls'
|
|
17
|
+
|
|
18
|
+
const node = await createLibp2p({
|
|
19
|
+
// ...other options
|
|
20
|
+
connectionEncryption: [
|
|
21
|
+
tls()
|
|
22
|
+
]
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
# Install
|
|
27
|
+
|
|
28
|
+
```console
|
|
29
|
+
$ npm i @libp2p/tls
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
# API Docs
|
|
33
|
+
|
|
34
|
+
- <https://libp2p.github.io/js-libp2p/modules/_libp2p_tls.html>
|
|
35
|
+
|
|
36
|
+
# License
|
|
37
|
+
|
|
38
|
+
Licensed under either of
|
|
39
|
+
|
|
40
|
+
- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
|
|
41
|
+
- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)
|
|
42
|
+
|
|
43
|
+
# Contribution
|
|
44
|
+
|
|
45
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* A connection encrypter that does no connection encryption.
|
|
5
|
+
*
|
|
6
|
+
* This should not be used in production should be used for research purposes only.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createLibp2p } from 'libp2p'
|
|
12
|
+
* import { tls } from '@libp2p/tls'
|
|
13
|
+
*
|
|
14
|
+
* const node = await createLibp2p({
|
|
15
|
+
* // ...other options
|
|
16
|
+
* connectionEncryption: [
|
|
17
|
+
* tls()
|
|
18
|
+
* ]
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { ComponentLogger, ConnectionEncrypter } from '@libp2p/interface';
|
|
23
|
+
export interface TLSComponents {
|
|
24
|
+
logger: ComponentLogger;
|
|
25
|
+
}
|
|
26
|
+
export interface TLSInit {
|
|
27
|
+
/**
|
|
28
|
+
* The peer id exchange must complete within this many milliseconds
|
|
29
|
+
* (default: 1000)
|
|
30
|
+
*/
|
|
31
|
+
timeout?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function tls(init?: TLSInit): (components: TLSComponents) => ConnectionEncrypter;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,OAAO,KAAK,EAAE,eAAe,EAAuB,mBAAmB,EAA6B,MAAM,mBAAmB,CAAA;AAM7H,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA+ED,wBAAgB,GAAG,CAAE,IAAI,CAAC,EAAE,OAAO,GAAG,CAAC,UAAU,EAAE,aAAa,KAAK,mBAAmB,CAEvF"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* A connection encrypter that does no connection encryption.
|
|
5
|
+
*
|
|
6
|
+
* This should not be used in production should be used for research purposes only.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createLibp2p } from 'libp2p'
|
|
12
|
+
* import { tls } from '@libp2p/tls'
|
|
13
|
+
*
|
|
14
|
+
* const node = await createLibp2p({
|
|
15
|
+
* // ...other options
|
|
16
|
+
* connectionEncryption: [
|
|
17
|
+
* tls()
|
|
18
|
+
* ]
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { TLSSocket, connect } from 'node:tls';
|
|
23
|
+
import { UnexpectedPeerError } from '@libp2p/interface';
|
|
24
|
+
// @ts-expect-error no types
|
|
25
|
+
import itToStream from 'it-to-stream';
|
|
26
|
+
// @ts-expect-error no types
|
|
27
|
+
import streamToIt from 'stream-to-it';
|
|
28
|
+
import { generateCertificate, verifyPeerCertificate } from './utils.js';
|
|
29
|
+
const PROTOCOL = '/tls/1.0.0';
|
|
30
|
+
class TLS {
|
|
31
|
+
protocol = PROTOCOL;
|
|
32
|
+
// private readonly log: Logger
|
|
33
|
+
// private readonly timeout: number
|
|
34
|
+
// constructor (components: TLSComponents, init: TLSInit = {}) {
|
|
35
|
+
// this.log = components.logger.forComponent('libp2p:tls')
|
|
36
|
+
// this.timeout = init.timeout ?? 1000
|
|
37
|
+
// }
|
|
38
|
+
async secureInbound(localId, conn, remoteId) {
|
|
39
|
+
return this._encrypt(localId, conn, false, remoteId);
|
|
40
|
+
}
|
|
41
|
+
async secureOutbound(localId, conn, remoteId) {
|
|
42
|
+
return this._encrypt(localId, conn, true, remoteId);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Encrypt connection
|
|
46
|
+
*/
|
|
47
|
+
async _encrypt(localId, conn, isServer, remoteId) {
|
|
48
|
+
const opts = {
|
|
49
|
+
...await generateCertificate(localId),
|
|
50
|
+
isServer,
|
|
51
|
+
requestCert: true,
|
|
52
|
+
// accept self-signed certificates
|
|
53
|
+
rejectUnauthorized: false,
|
|
54
|
+
// require TLS 1.3 or later
|
|
55
|
+
minVersion: 'TLSv1.3'
|
|
56
|
+
};
|
|
57
|
+
let socket;
|
|
58
|
+
if (isServer) {
|
|
59
|
+
socket = new TLSSocket(itToStream.duplex(conn), opts);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
socket = connect({
|
|
63
|
+
socket: itToStream.duplex(conn),
|
|
64
|
+
...opts
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
function verifyRemote() {
|
|
69
|
+
const remote = socket.getPeerCertificate();
|
|
70
|
+
verifyPeerCertificate(remote.raw)
|
|
71
|
+
.then(remotePeer => {
|
|
72
|
+
if (remoteId?.equals(remotePeer) === false) {
|
|
73
|
+
throw new UnexpectedPeerError();
|
|
74
|
+
}
|
|
75
|
+
const outputStream = streamToIt.duplex(socket);
|
|
76
|
+
conn.source = outputStream.source;
|
|
77
|
+
conn.sink = outputStream.sink;
|
|
78
|
+
resolve({
|
|
79
|
+
remotePeer,
|
|
80
|
+
conn
|
|
81
|
+
});
|
|
82
|
+
})
|
|
83
|
+
.catch(err => {
|
|
84
|
+
reject(err);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
socket.on('error', err => {
|
|
88
|
+
reject(err);
|
|
89
|
+
});
|
|
90
|
+
socket.on('secure', (evt) => {
|
|
91
|
+
verifyRemote();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function tls(init) {
|
|
97
|
+
return (components) => new TLS();
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Codec } from 'protons-runtime';
|
|
2
|
+
import type { Uint8ArrayList } from 'uint8arraylist';
|
|
3
|
+
export declare enum KeyType {
|
|
4
|
+
RSA = "RSA",
|
|
5
|
+
Ed25519 = "Ed25519",
|
|
6
|
+
Secp256k1 = "Secp256k1",
|
|
7
|
+
ECDSA = "ECDSA"
|
|
8
|
+
}
|
|
9
|
+
export declare namespace KeyType {
|
|
10
|
+
const codec: () => Codec<KeyType>;
|
|
11
|
+
}
|
|
12
|
+
export interface PublicKey {
|
|
13
|
+
type?: KeyType;
|
|
14
|
+
data?: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export declare namespace PublicKey {
|
|
17
|
+
const codec: () => Codec<PublicKey>;
|
|
18
|
+
const encode: (obj: Partial<PublicKey>) => Uint8Array;
|
|
19
|
+
const decode: (buf: Uint8Array | Uint8ArrayList) => PublicKey;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pb/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,KAAK,EAAsD,MAAM,iBAAiB,CAAA;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,oBAAY,OAAO;IACjB,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,KAAK,UAAU;CAChB;AASD,yBAAiB,OAAO,CAAC;IAChB,MAAM,KAAK,QAAO,MAAM,OAAO,CAErC,CAAA;CACF;AACD,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,yBAAiB,SAAS,CAAC;IAGlB,MAAM,KAAK,QAAO,MAAM,SAAS,CAiDvC,CAAA;IAEM,MAAM,MAAM,QAAS,QAAQ,SAAS,CAAC,KAAG,UAEhD,CAAA;IAEM,MAAM,MAAM,QAAS,UAAU,GAAG,cAAc,KAAG,SAEzD,CAAA;CACF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
2
|
+
/* eslint-disable complexity */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
6
|
+
import { decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime';
|
|
7
|
+
export var KeyType;
|
|
8
|
+
(function (KeyType) {
|
|
9
|
+
KeyType["RSA"] = "RSA";
|
|
10
|
+
KeyType["Ed25519"] = "Ed25519";
|
|
11
|
+
KeyType["Secp256k1"] = "Secp256k1";
|
|
12
|
+
KeyType["ECDSA"] = "ECDSA";
|
|
13
|
+
})(KeyType || (KeyType = {}));
|
|
14
|
+
var __KeyTypeValues;
|
|
15
|
+
(function (__KeyTypeValues) {
|
|
16
|
+
__KeyTypeValues[__KeyTypeValues["RSA"] = 0] = "RSA";
|
|
17
|
+
__KeyTypeValues[__KeyTypeValues["Ed25519"] = 1] = "Ed25519";
|
|
18
|
+
__KeyTypeValues[__KeyTypeValues["Secp256k1"] = 2] = "Secp256k1";
|
|
19
|
+
__KeyTypeValues[__KeyTypeValues["ECDSA"] = 3] = "ECDSA";
|
|
20
|
+
})(__KeyTypeValues || (__KeyTypeValues = {}));
|
|
21
|
+
(function (KeyType) {
|
|
22
|
+
KeyType.codec = () => {
|
|
23
|
+
return enumeration(__KeyTypeValues);
|
|
24
|
+
};
|
|
25
|
+
})(KeyType || (KeyType = {}));
|
|
26
|
+
export var PublicKey;
|
|
27
|
+
(function (PublicKey) {
|
|
28
|
+
let _codec;
|
|
29
|
+
PublicKey.codec = () => {
|
|
30
|
+
if (_codec == null) {
|
|
31
|
+
_codec = message((obj, w, opts = {}) => {
|
|
32
|
+
if (opts.lengthDelimited !== false) {
|
|
33
|
+
w.fork();
|
|
34
|
+
}
|
|
35
|
+
if (obj.type != null) {
|
|
36
|
+
w.uint32(8);
|
|
37
|
+
KeyType.codec().encode(obj.type, w);
|
|
38
|
+
}
|
|
39
|
+
if (obj.data != null) {
|
|
40
|
+
w.uint32(18);
|
|
41
|
+
w.bytes(obj.data);
|
|
42
|
+
}
|
|
43
|
+
if (opts.lengthDelimited !== false) {
|
|
44
|
+
w.ldelim();
|
|
45
|
+
}
|
|
46
|
+
}, (reader, length) => {
|
|
47
|
+
const obj = {};
|
|
48
|
+
const end = length == null ? reader.len : reader.pos + length;
|
|
49
|
+
while (reader.pos < end) {
|
|
50
|
+
const tag = reader.uint32();
|
|
51
|
+
switch (tag >>> 3) {
|
|
52
|
+
case 1: {
|
|
53
|
+
obj.type = KeyType.codec().decode(reader);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 2: {
|
|
57
|
+
obj.data = reader.bytes();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
default: {
|
|
61
|
+
reader.skipType(tag & 7);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return obj;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return _codec;
|
|
70
|
+
};
|
|
71
|
+
PublicKey.encode = (obj) => {
|
|
72
|
+
return encodeMessage(obj, PublicKey.codec());
|
|
73
|
+
};
|
|
74
|
+
PublicKey.decode = (buf) => {
|
|
75
|
+
return decodeMessage(buf, PublicKey.codec());
|
|
76
|
+
};
|
|
77
|
+
})(PublicKey || (PublicKey = {}));
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/pb/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PeerId } from '@libp2p/interface';
|
|
2
|
+
export declare function verifyPeerCertificate(rawCertificate: Uint8Array): Promise<PeerId>;
|
|
3
|
+
export declare function generateCertificate(peerId: PeerId): Promise<{
|
|
4
|
+
cert: string;
|
|
5
|
+
key: string;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* @see https://github.com/libp2p/specs/blob/master/tls/tls.md#libp2p-public-key-extension
|
|
9
|
+
*/
|
|
10
|
+
export declare function encodeSignatureData(certPublicKey: ArrayBuffer): Uint8Array;
|
|
11
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,mBAAmB,CAAA;AAY7E,wBAAsB,qBAAqB,CAAE,cAAc,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDxF;AAED,wBAAsB,mBAAmB,CAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA8EjG;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAE,aAAa,EAAE,WAAW,GAAG,UAAU,CAQ3E"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Ed25519PublicKey, Secp256k1PublicKey, marshalPublicKey, supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys';
|
|
2
|
+
import { CodeError, InvalidCryptoExchangeError } from '@libp2p/interface';
|
|
3
|
+
import { peerIdFromKeys } from '@libp2p/peer-id';
|
|
4
|
+
import { AsnConvert } from '@peculiar/asn1-schema';
|
|
5
|
+
import * as asn1X509 from '@peculiar/asn1-x509';
|
|
6
|
+
import { Crypto } from '@peculiar/webcrypto';
|
|
7
|
+
import * as x509 from '@peculiar/x509';
|
|
8
|
+
import * as asn1js from 'asn1js';
|
|
9
|
+
import { concat as uint8ArrayConcat } from 'uint8arrays/concat';
|
|
10
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
|
|
11
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
|
|
12
|
+
import { KeyType, PublicKey } from '../src/pb/index.js';
|
|
13
|
+
const crypto = new Crypto();
|
|
14
|
+
x509.cryptoProvider.set(crypto);
|
|
15
|
+
const LIBP2P_PUBLIC_KEY_EXTENSION = '1.3.6.1.4.1.53594.1.1';
|
|
16
|
+
const CERT_PREFIX = 'libp2p-tls-handshake:';
|
|
17
|
+
// https://github.com/libp2p/go-libp2p/blob/28c0f6ab32cd69e4b18e9e4b550ef6ce059a9d1a/p2p/security/tls/crypto.go#L265
|
|
18
|
+
const CERT_VALIDITY_PERIOD_FROM = 60 * 60 * 1000; // ~1 hour
|
|
19
|
+
// https://github.com/libp2p/go-libp2p/blob/28c0f6ab32cd69e4b18e9e4b550ef6ce059a9d1a/p2p/security/tls/crypto.go#L24C28-L24C44
|
|
20
|
+
const CERT_VALIDITY_PERIOD_TO = 100 * 365 * 24 * 60 * 60 * 1000; // ~100 years
|
|
21
|
+
export async function verifyPeerCertificate(rawCertificate) {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const x509Cert = new x509.X509Certificate(rawCertificate);
|
|
24
|
+
if (x509Cert.notBefore.getTime() > now) {
|
|
25
|
+
throw new CodeError('The certificate is not valid yet', 'ERR_INVALID_CERTIFICATE');
|
|
26
|
+
}
|
|
27
|
+
if (x509Cert.notAfter.getTime() < now) {
|
|
28
|
+
throw new CodeError('The certificate has expired', 'ERR_INVALID_CERTIFICATE');
|
|
29
|
+
}
|
|
30
|
+
// TODO: assert chain is only one certificate long
|
|
31
|
+
const libp2pPublicKeyExtension = x509Cert.extensions[0];
|
|
32
|
+
if (libp2pPublicKeyExtension == null || libp2pPublicKeyExtension.type !== LIBP2P_PUBLIC_KEY_EXTENSION) {
|
|
33
|
+
throw new CodeError('The certificate did not include the libp2p public key extension', 'ERR_INVALID_CERTIFICATE');
|
|
34
|
+
}
|
|
35
|
+
const { result: libp2pKeySequence } = asn1js.fromBER(libp2pPublicKeyExtension.value);
|
|
36
|
+
// @ts-expect-error deep chain
|
|
37
|
+
const remotePeerIdPb = libp2pKeySequence.valueBlock.value[0].valueBlock.valueHex;
|
|
38
|
+
const marshalledPeerId = new Uint8Array(remotePeerIdPb, 0, remotePeerIdPb.byteLength);
|
|
39
|
+
const remotePeerId = PublicKey.decode(marshalledPeerId);
|
|
40
|
+
const remotePeerIdData = remotePeerId.data ?? new Uint8Array(0);
|
|
41
|
+
let remotePublicKey;
|
|
42
|
+
if (remotePeerId.type === KeyType.Ed25519) {
|
|
43
|
+
remotePublicKey = new Ed25519PublicKey(remotePeerIdData);
|
|
44
|
+
}
|
|
45
|
+
else if (remotePeerId.type === KeyType.Secp256k1) {
|
|
46
|
+
remotePublicKey = new Secp256k1PublicKey(remotePeerIdData);
|
|
47
|
+
}
|
|
48
|
+
else if (remotePeerId.type === KeyType.RSA) {
|
|
49
|
+
remotePublicKey = supportedKeys.rsa.unmarshalRsaPublicKey(remotePeerIdData);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new Error('Unknown or unsupported key type');
|
|
53
|
+
}
|
|
54
|
+
// @ts-expect-error deep chain
|
|
55
|
+
const remoteSignature = libp2pKeySequence.valueBlock.value[1].valueBlock.valueHex;
|
|
56
|
+
const dataToVerify = encodeSignatureData(x509Cert.publicKey.rawData);
|
|
57
|
+
const result = await remotePublicKey.verify(dataToVerify, new Uint8Array(remoteSignature, 0, remoteSignature.byteLength));
|
|
58
|
+
if (!result) {
|
|
59
|
+
throw new Error('Could not verify signature');
|
|
60
|
+
}
|
|
61
|
+
const marshalled = marshalPublicKey(remotePublicKey);
|
|
62
|
+
return peerIdFromKeys(marshalled);
|
|
63
|
+
}
|
|
64
|
+
export async function generateCertificate(peerId) {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const alg = {
|
|
67
|
+
name: 'ECDSA',
|
|
68
|
+
namedCurve: 'P-256',
|
|
69
|
+
hash: 'SHA-256'
|
|
70
|
+
};
|
|
71
|
+
const keys = await crypto.subtle.generateKey(alg, true, ['sign']);
|
|
72
|
+
const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey);
|
|
73
|
+
const dataToSign = encodeSignatureData(certPublicKeySpki);
|
|
74
|
+
if (peerId.privateKey == null) {
|
|
75
|
+
throw new InvalidCryptoExchangeError('Private key was missing from PeerId');
|
|
76
|
+
}
|
|
77
|
+
const privateKey = await unmarshalPrivateKey(peerId.privateKey);
|
|
78
|
+
const sig = await privateKey.sign(dataToSign);
|
|
79
|
+
let keyType;
|
|
80
|
+
let keyData;
|
|
81
|
+
if (peerId.publicKey == null) {
|
|
82
|
+
throw new CodeError('Public key missing from PeerId', 'ERR_INVALID_PEER_ID');
|
|
83
|
+
}
|
|
84
|
+
const publicKey = unmarshalPublicKey(peerId.publicKey);
|
|
85
|
+
if (peerId.type === 'Ed25519') {
|
|
86
|
+
// Ed25519: Only the 32 bytes of the public key
|
|
87
|
+
keyType = KeyType.Ed25519;
|
|
88
|
+
keyData = publicKey.marshal();
|
|
89
|
+
}
|
|
90
|
+
else if (peerId.type === 'secp256k1') {
|
|
91
|
+
// Secp256k1: Only the compressed form of the public key. 33 bytes.
|
|
92
|
+
keyType = KeyType.Secp256k1;
|
|
93
|
+
keyData = publicKey.marshal();
|
|
94
|
+
}
|
|
95
|
+
else if (peerId.type === 'RSA') {
|
|
96
|
+
// The rest of the keys are encoded as a SubjectPublicKeyInfo structure in PKIX, ASN.1 DER form.
|
|
97
|
+
keyType = KeyType.RSA;
|
|
98
|
+
keyData = publicKey.marshal();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new CodeError('Unknown PeerId type', 'ERR_UNKNOWN_PEER_ID_TYPE');
|
|
102
|
+
}
|
|
103
|
+
const selfCert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
104
|
+
serialNumber: uint8ArrayToString(crypto.getRandomValues(new Uint8Array(9)), 'base16'),
|
|
105
|
+
name: '',
|
|
106
|
+
notBefore: new Date(now - CERT_VALIDITY_PERIOD_FROM),
|
|
107
|
+
notAfter: new Date(now + CERT_VALIDITY_PERIOD_TO),
|
|
108
|
+
signingAlgorithm: alg,
|
|
109
|
+
keys,
|
|
110
|
+
extensions: [
|
|
111
|
+
new x509.Extension(LIBP2P_PUBLIC_KEY_EXTENSION, true, new asn1js.Sequence({
|
|
112
|
+
value: [
|
|
113
|
+
// publicKey
|
|
114
|
+
new asn1js.OctetString({
|
|
115
|
+
valueHex: PublicKey.encode({
|
|
116
|
+
type: keyType,
|
|
117
|
+
data: keyData
|
|
118
|
+
})
|
|
119
|
+
}),
|
|
120
|
+
// signature
|
|
121
|
+
new asn1js.OctetString({
|
|
122
|
+
valueHex: sig
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
}).toBER())
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
const certPrivateKeySpki = await crypto.subtle.exportKey('spki', keys.privateKey);
|
|
129
|
+
return {
|
|
130
|
+
cert: selfCert.toString(),
|
|
131
|
+
key: spkiToPEM(certPrivateKeySpki)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* @see https://github.com/libp2p/specs/blob/master/tls/tls.md#libp2p-public-key-extension
|
|
136
|
+
*/
|
|
137
|
+
export function encodeSignatureData(certPublicKey) {
|
|
138
|
+
const keyInfo = AsnConvert.parse(certPublicKey, asn1X509.SubjectPublicKeyInfo);
|
|
139
|
+
const bytes = AsnConvert.serialize(keyInfo);
|
|
140
|
+
return uint8ArrayConcat([
|
|
141
|
+
uint8ArrayFromString(CERT_PREFIX),
|
|
142
|
+
new Uint8Array(bytes, 0, bytes.byteLength)
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
function spkiToPEM(keydata) {
|
|
146
|
+
return formatAsPem(uint8ArrayToString(new Uint8Array(keydata), 'base64'));
|
|
147
|
+
}
|
|
148
|
+
function formatAsPem(str) {
|
|
149
|
+
let finalString = '-----BEGIN PRIVATE KEY-----\n';
|
|
150
|
+
while (str.length > 0) {
|
|
151
|
+
finalString += str.substring(0, 64) + '\n';
|
|
152
|
+
str = str.substring(64);
|
|
153
|
+
}
|
|
154
|
+
finalString = finalString + '-----END PRIVATE KEY-----';
|
|
155
|
+
return finalString;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"TLSComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tls.TLSComponents.html",
|
|
3
|
+
".:TLSComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tls.TLSComponents.html",
|
|
4
|
+
"TLSInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tls.TLSInit.html",
|
|
5
|
+
".:TLSInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_tls.TLSInit.html",
|
|
6
|
+
"tls": "https://libp2p.github.io/js-libp2p/functions/_libp2p_tls.tls.html",
|
|
7
|
+
".:tls": "https://libp2p.github.io/js-libp2p/functions/_libp2p_tls.tls.html"
|
|
8
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@libp2p/tls",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A connection encrypter that uses TLS 1.3",
|
|
5
|
+
"license": "Apache-2.0 OR MIT",
|
|
6
|
+
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/libp2p/js-libp2p.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/libp2p/js-libp2p/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"types": "./dist/src/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"dist",
|
|
19
|
+
"!dist/test",
|
|
20
|
+
"!**/*.tsbuildinfo"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/src/index.d.ts",
|
|
25
|
+
"import": "./dist/src/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"eslintConfig": {
|
|
29
|
+
"extends": "ipfs",
|
|
30
|
+
"parserOptions": {
|
|
31
|
+
"project": true,
|
|
32
|
+
"sourceType": "module"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"start": "node dist/src/main.js",
|
|
37
|
+
"build": "aegir build --bundle false",
|
|
38
|
+
"test": "aegir test -t node",
|
|
39
|
+
"clean": "aegir clean",
|
|
40
|
+
"generate": "protons ./src/pb/index.proto",
|
|
41
|
+
"lint": "aegir lint",
|
|
42
|
+
"test:node": "aegir test -t node --cov",
|
|
43
|
+
"dep-check": "aegir dep-check"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@libp2p/crypto": "^4.0.1",
|
|
47
|
+
"@libp2p/interface": "^1.1.2",
|
|
48
|
+
"@libp2p/peer-id": "^4.0.5",
|
|
49
|
+
"@peculiar/asn1-schema": "^2.3.8",
|
|
50
|
+
"@peculiar/asn1-x509": "^2.3.8",
|
|
51
|
+
"@peculiar/webcrypto": "^1.4.5",
|
|
52
|
+
"@peculiar/x509": "^1.9.6",
|
|
53
|
+
"asn1js": "^3.0.5",
|
|
54
|
+
"it-stream-types": "^2.0.1",
|
|
55
|
+
"it-to-stream": "^1.0.0",
|
|
56
|
+
"protons-runtime": "^5.0.0",
|
|
57
|
+
"stream-to-it": "^0.2.4",
|
|
58
|
+
"uint8arraylist": "^2.4.7",
|
|
59
|
+
"uint8arrays": "^5.0.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@libp2p/interface-compliance-tests": "^5.2.0",
|
|
63
|
+
"@libp2p/logger": "^4.0.5",
|
|
64
|
+
"@libp2p/peer-id-factory": "^4.0.5",
|
|
65
|
+
"@multiformats/multiaddr": "^12.1.10",
|
|
66
|
+
"aegir": "^42.0.0",
|
|
67
|
+
"protons": "^7.3.0",
|
|
68
|
+
"sinon": "^17.0.1"
|
|
69
|
+
},
|
|
70
|
+
"sideEffects": false
|
|
71
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* Implements the spec at https://github.com/libp2p/specs/blob/master/tls/tls.md
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createLibp2p } from 'libp2p'
|
|
10
|
+
* import { tls } from '@libp2p/tls'
|
|
11
|
+
*
|
|
12
|
+
* const node = await createLibp2p({
|
|
13
|
+
* // ...other options
|
|
14
|
+
* connectionEncryption: [
|
|
15
|
+
* tls()
|
|
16
|
+
* ]
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { TLSSocket, type TLSSocketOptions, connect } from 'node:tls'
|
|
22
|
+
import { UnexpectedPeerError } from '@libp2p/interface'
|
|
23
|
+
// @ts-expect-error no types
|
|
24
|
+
import itToStream from 'it-to-stream'
|
|
25
|
+
// @ts-expect-error no types
|
|
26
|
+
import streamToIt from 'stream-to-it'
|
|
27
|
+
import { generateCertificate, verifyPeerCertificate } from './utils.js'
|
|
28
|
+
import type { ComponentLogger, MultiaddrConnection, ConnectionEncrypter, SecuredConnection, PeerId } from '@libp2p/interface'
|
|
29
|
+
import type { Duplex } from 'it-stream-types'
|
|
30
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
31
|
+
|
|
32
|
+
const PROTOCOL = '/tls/1.0.0'
|
|
33
|
+
|
|
34
|
+
export interface TLSComponents {
|
|
35
|
+
logger: ComponentLogger
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TLSInit {
|
|
39
|
+
/**
|
|
40
|
+
* The peer id exchange must complete within this many milliseconds
|
|
41
|
+
* (default: 1000)
|
|
42
|
+
*/
|
|
43
|
+
timeout?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class TLS implements ConnectionEncrypter {
|
|
47
|
+
public protocol: string = PROTOCOL
|
|
48
|
+
// private readonly log: Logger
|
|
49
|
+
// private readonly timeout: number
|
|
50
|
+
|
|
51
|
+
// constructor (components: TLSComponents, init: TLSInit = {}) {
|
|
52
|
+
// this.log = components.logger.forComponent('libp2p:tls')
|
|
53
|
+
// this.timeout = init.timeout ?? 1000
|
|
54
|
+
// }
|
|
55
|
+
|
|
56
|
+
async secureInbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localId: PeerId, conn: Stream, remoteId?: PeerId): Promise<SecuredConnection<Stream>> {
|
|
57
|
+
return this._encrypt(localId, conn, false, remoteId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async secureOutbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localId: PeerId, conn: Stream, remoteId?: PeerId): Promise<SecuredConnection<Stream>> {
|
|
61
|
+
return this._encrypt(localId, conn, true, remoteId)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Encrypt connection
|
|
66
|
+
*/
|
|
67
|
+
async _encrypt <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localId: PeerId, conn: Stream, isServer: boolean, remoteId?: PeerId): Promise<SecuredConnection<Stream>> {
|
|
68
|
+
const opts: TLSSocketOptions = {
|
|
69
|
+
...await generateCertificate(localId),
|
|
70
|
+
isServer,
|
|
71
|
+
requestCert: true,
|
|
72
|
+
// accept self-signed certificates from clients
|
|
73
|
+
rejectUnauthorized: false,
|
|
74
|
+
// require TLS 1.3 or later
|
|
75
|
+
minVersion: 'TLSv1.3'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let socket: TLSSocket
|
|
79
|
+
|
|
80
|
+
if (isServer) {
|
|
81
|
+
socket = new TLSSocket(itToStream.duplex(conn), opts)
|
|
82
|
+
} else {
|
|
83
|
+
socket = connect({
|
|
84
|
+
socket: itToStream.duplex(conn),
|
|
85
|
+
...opts
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
function verifyRemote (): void {
|
|
91
|
+
const remote = socket.getPeerCertificate()
|
|
92
|
+
|
|
93
|
+
verifyPeerCertificate(remote.raw)
|
|
94
|
+
.then(remotePeer => {
|
|
95
|
+
if (remoteId?.equals(remotePeer) === false) {
|
|
96
|
+
throw new UnexpectedPeerError()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const outputStream = streamToIt.duplex(socket)
|
|
100
|
+
conn.source = outputStream.source
|
|
101
|
+
conn.sink = outputStream.sink
|
|
102
|
+
|
|
103
|
+
resolve({
|
|
104
|
+
remotePeer,
|
|
105
|
+
conn
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
.catch(err => {
|
|
109
|
+
reject(err)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
socket.on('error', err => {
|
|
114
|
+
reject(err)
|
|
115
|
+
})
|
|
116
|
+
socket.on('secure', (evt) => {
|
|
117
|
+
verifyRemote()
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function tls (init?: TLSInit): (components: TLSComponents) => ConnectionEncrypter {
|
|
124
|
+
return (components) => new TLS()
|
|
125
|
+
}
|
package/src/pb/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
2
|
+
/* eslint-disable complexity */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
6
|
+
|
|
7
|
+
import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime'
|
|
8
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
9
|
+
|
|
10
|
+
export enum KeyType {
|
|
11
|
+
RSA = 'RSA',
|
|
12
|
+
Ed25519 = 'Ed25519',
|
|
13
|
+
Secp256k1 = 'Secp256k1',
|
|
14
|
+
ECDSA = 'ECDSA'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
enum __KeyTypeValues {
|
|
18
|
+
RSA = 0,
|
|
19
|
+
Ed25519 = 1,
|
|
20
|
+
Secp256k1 = 2,
|
|
21
|
+
ECDSA = 3
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export namespace KeyType {
|
|
25
|
+
export const codec = (): Codec<KeyType> => {
|
|
26
|
+
return enumeration<KeyType>(__KeyTypeValues)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export interface PublicKey {
|
|
30
|
+
type?: KeyType
|
|
31
|
+
data?: Uint8Array
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export namespace PublicKey {
|
|
35
|
+
let _codec: Codec<PublicKey>
|
|
36
|
+
|
|
37
|
+
export const codec = (): Codec<PublicKey> => {
|
|
38
|
+
if (_codec == null) {
|
|
39
|
+
_codec = message<PublicKey>((obj, w, opts = {}) => {
|
|
40
|
+
if (opts.lengthDelimited !== false) {
|
|
41
|
+
w.fork()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (obj.type != null) {
|
|
45
|
+
w.uint32(8)
|
|
46
|
+
KeyType.codec().encode(obj.type, w)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (obj.data != null) {
|
|
50
|
+
w.uint32(18)
|
|
51
|
+
w.bytes(obj.data)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (opts.lengthDelimited !== false) {
|
|
55
|
+
w.ldelim()
|
|
56
|
+
}
|
|
57
|
+
}, (reader, length) => {
|
|
58
|
+
const obj: any = {}
|
|
59
|
+
|
|
60
|
+
const end = length == null ? reader.len : reader.pos + length
|
|
61
|
+
|
|
62
|
+
while (reader.pos < end) {
|
|
63
|
+
const tag = reader.uint32()
|
|
64
|
+
|
|
65
|
+
switch (tag >>> 3) {
|
|
66
|
+
case 1: {
|
|
67
|
+
obj.type = KeyType.codec().decode(reader)
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
case 2: {
|
|
71
|
+
obj.data = reader.bytes()
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
default: {
|
|
75
|
+
reader.skipType(tag & 7)
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return obj
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return _codec
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const encode = (obj: Partial<PublicKey>): Uint8Array => {
|
|
89
|
+
return encodeMessage(obj, PublicKey.codec())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => {
|
|
93
|
+
return decodeMessage(buf, PublicKey.codec())
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Ed25519PublicKey, Secp256k1PublicKey, marshalPublicKey, supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys'
|
|
2
|
+
import { CodeError, InvalidCryptoExchangeError } from '@libp2p/interface'
|
|
3
|
+
import { peerIdFromKeys } from '@libp2p/peer-id'
|
|
4
|
+
import { AsnConvert } from '@peculiar/asn1-schema'
|
|
5
|
+
import * as asn1X509 from '@peculiar/asn1-x509'
|
|
6
|
+
import { Crypto } from '@peculiar/webcrypto'
|
|
7
|
+
import * as x509 from '@peculiar/x509'
|
|
8
|
+
import * as asn1js from 'asn1js'
|
|
9
|
+
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
|
10
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
11
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
12
|
+
import { KeyType, PublicKey } from '../src/pb/index.js'
|
|
13
|
+
import type { PeerId, PublicKey as Libp2pPublicKey } from '@libp2p/interface'
|
|
14
|
+
|
|
15
|
+
const crypto = new Crypto()
|
|
16
|
+
x509.cryptoProvider.set(crypto)
|
|
17
|
+
|
|
18
|
+
const LIBP2P_PUBLIC_KEY_EXTENSION = '1.3.6.1.4.1.53594.1.1'
|
|
19
|
+
const CERT_PREFIX = 'libp2p-tls-handshake:'
|
|
20
|
+
// https://github.com/libp2p/go-libp2p/blob/28c0f6ab32cd69e4b18e9e4b550ef6ce059a9d1a/p2p/security/tls/crypto.go#L265
|
|
21
|
+
const CERT_VALIDITY_PERIOD_FROM = 60 * 60 * 1000 // ~1 hour
|
|
22
|
+
// https://github.com/libp2p/go-libp2p/blob/28c0f6ab32cd69e4b18e9e4b550ef6ce059a9d1a/p2p/security/tls/crypto.go#L24C28-L24C44
|
|
23
|
+
const CERT_VALIDITY_PERIOD_TO = 100 * 365 * 24 * 60 * 60 * 1000 // ~100 years
|
|
24
|
+
|
|
25
|
+
export async function verifyPeerCertificate (rawCertificate: Uint8Array): Promise<PeerId> {
|
|
26
|
+
const now = Date.now()
|
|
27
|
+
const x509Cert = new x509.X509Certificate(rawCertificate)
|
|
28
|
+
|
|
29
|
+
if (x509Cert.notBefore.getTime() > now) {
|
|
30
|
+
throw new CodeError('The certificate is not valid yet', 'ERR_INVALID_CERTIFICATE')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (x509Cert.notAfter.getTime() < now) {
|
|
34
|
+
throw new CodeError('The certificate has expired', 'ERR_INVALID_CERTIFICATE')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO: assert chain is only one certificate long
|
|
38
|
+
|
|
39
|
+
const libp2pPublicKeyExtension = x509Cert.extensions[0]
|
|
40
|
+
|
|
41
|
+
if (libp2pPublicKeyExtension == null || libp2pPublicKeyExtension.type !== LIBP2P_PUBLIC_KEY_EXTENSION) {
|
|
42
|
+
throw new CodeError('The certificate did not include the libp2p public key extension', 'ERR_INVALID_CERTIFICATE')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { result: libp2pKeySequence } = asn1js.fromBER(libp2pPublicKeyExtension.value)
|
|
46
|
+
|
|
47
|
+
// @ts-expect-error deep chain
|
|
48
|
+
const remotePeerIdPb = libp2pKeySequence.valueBlock.value[0].valueBlock.valueHex
|
|
49
|
+
const marshalledPeerId = new Uint8Array(remotePeerIdPb, 0, remotePeerIdPb.byteLength)
|
|
50
|
+
const remotePeerId = PublicKey.decode(marshalledPeerId)
|
|
51
|
+
const remotePeerIdData = remotePeerId.data ?? new Uint8Array(0)
|
|
52
|
+
let remotePublicKey: Libp2pPublicKey
|
|
53
|
+
|
|
54
|
+
if (remotePeerId.type === KeyType.Ed25519) {
|
|
55
|
+
remotePublicKey = new Ed25519PublicKey(remotePeerIdData)
|
|
56
|
+
} else if (remotePeerId.type === KeyType.Secp256k1) {
|
|
57
|
+
remotePublicKey = new Secp256k1PublicKey(remotePeerIdData)
|
|
58
|
+
} else if (remotePeerId.type === KeyType.RSA) {
|
|
59
|
+
remotePublicKey = supportedKeys.rsa.unmarshalRsaPublicKey(remotePeerIdData)
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error('Unknown or unsupported key type')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// @ts-expect-error deep chain
|
|
65
|
+
const remoteSignature = libp2pKeySequence.valueBlock.value[1].valueBlock.valueHex
|
|
66
|
+
const dataToVerify = encodeSignatureData(x509Cert.publicKey.rawData)
|
|
67
|
+
const result = await remotePublicKey.verify(dataToVerify, new Uint8Array(remoteSignature, 0, remoteSignature.byteLength))
|
|
68
|
+
|
|
69
|
+
if (!result) {
|
|
70
|
+
throw new Error('Could not verify signature')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const marshalled = marshalPublicKey(remotePublicKey)
|
|
74
|
+
|
|
75
|
+
return peerIdFromKeys(marshalled)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function generateCertificate (peerId: PeerId): Promise<{ cert: string, key: string }> {
|
|
79
|
+
const now = Date.now()
|
|
80
|
+
|
|
81
|
+
const alg = {
|
|
82
|
+
name: 'ECDSA',
|
|
83
|
+
namedCurve: 'P-256',
|
|
84
|
+
hash: 'SHA-256'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const keys = await crypto.subtle.generateKey(alg, true, ['sign'])
|
|
88
|
+
|
|
89
|
+
const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey)
|
|
90
|
+
const dataToSign = encodeSignatureData(certPublicKeySpki)
|
|
91
|
+
|
|
92
|
+
if (peerId.privateKey == null) {
|
|
93
|
+
throw new InvalidCryptoExchangeError('Private key was missing from PeerId')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const privateKey = await unmarshalPrivateKey(peerId.privateKey)
|
|
97
|
+
const sig = await privateKey.sign(dataToSign)
|
|
98
|
+
|
|
99
|
+
let keyType: KeyType
|
|
100
|
+
let keyData: Uint8Array
|
|
101
|
+
|
|
102
|
+
if (peerId.publicKey == null) {
|
|
103
|
+
throw new CodeError('Public key missing from PeerId', 'ERR_INVALID_PEER_ID')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const publicKey = unmarshalPublicKey(peerId.publicKey)
|
|
107
|
+
|
|
108
|
+
if (peerId.type === 'Ed25519') {
|
|
109
|
+
// Ed25519: Only the 32 bytes of the public key
|
|
110
|
+
keyType = KeyType.Ed25519
|
|
111
|
+
keyData = publicKey.marshal()
|
|
112
|
+
} else if (peerId.type === 'secp256k1') {
|
|
113
|
+
// Secp256k1: Only the compressed form of the public key. 33 bytes.
|
|
114
|
+
keyType = KeyType.Secp256k1
|
|
115
|
+
keyData = publicKey.marshal()
|
|
116
|
+
} else if (peerId.type === 'RSA') {
|
|
117
|
+
// The rest of the keys are encoded as a SubjectPublicKeyInfo structure in PKIX, ASN.1 DER form.
|
|
118
|
+
keyType = KeyType.RSA
|
|
119
|
+
keyData = publicKey.marshal()
|
|
120
|
+
} else {
|
|
121
|
+
throw new CodeError('Unknown PeerId type', 'ERR_UNKNOWN_PEER_ID_TYPE')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const selfCert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
125
|
+
serialNumber: uint8ArrayToString(crypto.getRandomValues(new Uint8Array(9)), 'base16'),
|
|
126
|
+
name: '',
|
|
127
|
+
notBefore: new Date(now - CERT_VALIDITY_PERIOD_FROM),
|
|
128
|
+
notAfter: new Date(now + CERT_VALIDITY_PERIOD_TO),
|
|
129
|
+
signingAlgorithm: alg,
|
|
130
|
+
keys,
|
|
131
|
+
extensions: [
|
|
132
|
+
new x509.Extension(LIBP2P_PUBLIC_KEY_EXTENSION, true, new asn1js.Sequence({
|
|
133
|
+
value: [
|
|
134
|
+
// publicKey
|
|
135
|
+
new asn1js.OctetString({
|
|
136
|
+
valueHex: PublicKey.encode({
|
|
137
|
+
type: keyType,
|
|
138
|
+
data: keyData
|
|
139
|
+
})
|
|
140
|
+
}),
|
|
141
|
+
// signature
|
|
142
|
+
new asn1js.OctetString({
|
|
143
|
+
valueHex: sig
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
}).toBER())
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const certPrivateKeySpki = await crypto.subtle.exportKey('spki', keys.privateKey)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
cert: selfCert.toString(),
|
|
154
|
+
key: spkiToPEM(certPrivateKeySpki)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @see https://github.com/libp2p/specs/blob/master/tls/tls.md#libp2p-public-key-extension
|
|
160
|
+
*/
|
|
161
|
+
export function encodeSignatureData (certPublicKey: ArrayBuffer): Uint8Array {
|
|
162
|
+
const keyInfo = AsnConvert.parse(certPublicKey, asn1X509.SubjectPublicKeyInfo)
|
|
163
|
+
const bytes = AsnConvert.serialize(keyInfo)
|
|
164
|
+
|
|
165
|
+
return uint8ArrayConcat([
|
|
166
|
+
uint8ArrayFromString(CERT_PREFIX),
|
|
167
|
+
new Uint8Array(bytes, 0, bytes.byteLength)
|
|
168
|
+
])
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function spkiToPEM (keydata: ArrayBuffer): string {
|
|
172
|
+
return formatAsPem(uint8ArrayToString(new Uint8Array(keydata), 'base64'))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function formatAsPem (str: string): string {
|
|
176
|
+
let finalString = '-----BEGIN PRIVATE KEY-----\n'
|
|
177
|
+
|
|
178
|
+
while (str.length > 0) {
|
|
179
|
+
finalString += str.substring(0, 64) + '\n'
|
|
180
|
+
str = str.substring(64)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
finalString = finalString + '-----END PRIVATE KEY-----'
|
|
184
|
+
|
|
185
|
+
return finalString
|
|
186
|
+
}
|