@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 ADDED
@@ -0,0 +1,4 @@
1
+ This project is dual licensed under MIT and Apache-2.0.
2
+
3
+ MIT: https://www.opensource.org/licenses/mit
4
+ Apache-2.0: https://www.apache.org/licenses/license-2.0
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
2
+ [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io)
3
+ [![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main\&style=flat-square)](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":"AAAA,kCAAkC;AAClC,+BAA+B;AAC/B,oDAAoD;AACpD,8EAA8E;AAC9E,0DAA0D;AAE1D,OAAO,EAAc,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAGhG,MAAM,CAAN,IAAY,OAKX;AALD,WAAY,OAAO;IACjB,sBAAW,CAAA;IACX,8BAAmB,CAAA;IACnB,kCAAuB,CAAA;IACvB,0BAAe,CAAA;AACjB,CAAC,EALW,OAAO,KAAP,OAAO,QAKlB;AAED,IAAK,eAKJ;AALD,WAAK,eAAe;IAClB,mDAAO,CAAA;IACP,2DAAW,CAAA;IACX,+DAAa,CAAA;IACb,uDAAS,CAAA;AACX,CAAC,EALI,eAAe,KAAf,eAAe,QAKnB;AAED,WAAiB,OAAO;IACT,aAAK,GAAG,GAAmB,EAAE;QACxC,OAAO,WAAW,CAAU,eAAe,CAAC,CAAA;IAC9C,CAAC,CAAA;AACH,CAAC,EAJgB,OAAO,KAAP,OAAO,QAIvB;AAMD,MAAM,KAAW,SAAS,CA6DzB;AA7DD,WAAiB,SAAS;IACxB,IAAI,MAAwB,CAAA;IAEf,eAAK,GAAG,GAAqB,EAAE;QAC1C,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,MAAM,GAAG,OAAO,CAAY,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE;gBAChD,IAAI,IAAI,CAAC,eAAe,KAAK,KAAK,EAAE,CAAC;oBACnC,CAAC,CAAC,IAAI,EAAE,CAAA;gBACV,CAAC;gBAED,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;oBACrB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;oBACX,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;gBACrC,CAAC;gBAED,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;oBACrB,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;oBACZ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;gBACnB,CAAC;gBAED,IAAI,IAAI,CAAC,eAAe,KAAK,KAAK,EAAE,CAAC;oBACnC,CAAC,CAAC,MAAM,EAAE,CAAA;gBACZ,CAAC;YACH,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE;gBACpB,MAAM,GAAG,GAAQ,EAAE,CAAA;gBAEnB,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAA;gBAE7D,OAAO,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;oBACxB,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA;oBAE3B,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC;wBAClB,KAAK,CAAC,CAAC,CAAC,CAAC;4BACP,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;4BACzC,MAAK;wBACP,CAAC;wBACD,KAAK,CAAC,CAAC,CAAC,CAAC;4BACP,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAA;4BACzB,MAAK;wBACP,CAAC;wBACD,OAAO,CAAC,CAAC,CAAC;4BACR,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;4BACxB,MAAK;wBACP,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,OAAO,GAAG,CAAA;YACZ,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAEY,gBAAM,GAAG,CAAC,GAAuB,EAAc,EAAE;QAC5D,OAAO,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAA;IAC9C,CAAC,CAAA;IAEY,gBAAM,GAAG,CAAC,GAAgC,EAAa,EAAE;QACpE,OAAO,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAA;IAC9C,CAAC,CAAA;AACH,CAAC,EA7DgB,SAAS,KAAT,SAAS,QA6DzB"}
@@ -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
+ }
@@ -0,0 +1,13 @@
1
+ syntax = "proto3";
2
+
3
+ enum KeyType {
4
+ RSA = 0;
5
+ Ed25519 = 1;
6
+ Secp256k1 = 2;
7
+ ECDSA = 3;
8
+ }
9
+
10
+ message PublicKey {
11
+ optional KeyType type = 1;
12
+ optional bytes data = 2;
13
+ }
@@ -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
+ }