node-rtc-connection 1.0.19 → 2.0.4
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/README.md +94 -85
- package/dist/index.cjs +20 -5606
- package/dist/index.mjs +25 -5598
- package/dist/types/crypto/der.d.ts +107 -0
- package/dist/types/crypto/x509.d.ts +56 -0
- package/dist/types/datachannel/RTCDataChannel.d.ts +179 -0
- package/dist/types/dtls/RTCCertificate.d.ts +163 -0
- package/dist/types/dtls/cipher.d.ts +81 -0
- package/dist/types/dtls/connection.d.ts +81 -0
- package/dist/types/dtls/prf.d.ts +29 -0
- package/dist/types/dtls/protocol.d.ts +127 -0
- package/dist/types/foundation/ByteBufferQueue.d.ts +71 -0
- package/dist/types/foundation/RTCError.d.ts +152 -0
- package/dist/types/ice/RTCIceCandidate.d.ts +161 -0
- package/dist/types/ice/ice-agent.d.ts +154 -0
- package/dist/types/ice/stun-message.d.ts +92 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/peerconnection/RTCPeerConnection.d.ts +74 -0
- package/dist/types/sctp/association.d.ts +77 -0
- package/dist/types/sctp/chunks.d.ts +200 -0
- package/dist/types/sctp/crc32c.d.ts +24 -0
- package/dist/types/sctp/datachannel-manager.d.ts +51 -0
- package/dist/types/sctp/dcep.d.ts +56 -0
- package/dist/types/sdp/RTCSessionDescription.d.ts +73 -0
- package/dist/types/sdp/sdp-utils.d.ts +103 -0
- package/dist/types/stun/stun-client.d.ts +119 -0
- package/dist/types/transport-stack.d.ts +68 -0
- package/package.json +26 -21
- package/src/crypto/der.ts +205 -0
- package/src/crypto/x509.ts +146 -0
- package/src/datachannel/RTCDataChannel.ts +388 -0
- package/src/dtls/RTCCertificate.ts +396 -0
- package/src/dtls/cipher.ts +198 -0
- package/src/dtls/connection.ts +974 -0
- package/src/dtls/prf.ts +62 -0
- package/src/dtls/protocol.ts +204 -0
- package/src/foundation/{ByteBufferQueue.js → ByteBufferQueue.ts} +74 -72
- package/src/foundation/{RTCError.js → RTCError.ts} +110 -60
- package/src/ice/{RTCIceCandidate.js → RTCIceCandidate.ts} +140 -92
- package/src/ice/ice-agent.ts +609 -0
- package/src/ice/stun-message.ts +260 -0
- package/src/index.ts +72 -0
- package/src/peerconnection/RTCPeerConnection.ts +430 -0
- package/src/sctp/association.ts +523 -0
- package/src/sctp/chunks.ts +350 -0
- package/src/sctp/crc32c.ts +57 -0
- package/src/sctp/datachannel-manager.ts +187 -0
- package/src/sctp/dcep.ts +94 -0
- package/src/sdp/{RTCSessionDescription.js → RTCSessionDescription.ts} +42 -29
- package/src/sdp/sdp-utils.ts +229 -0
- package/src/stun/{stun-client.js → stun-client.ts} +346 -187
- package/src/transport-stack.ts +165 -0
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/src/datachannel/RTCDataChannel.js +0 -354
- package/src/dtls/RTCCertificate.js +0 -310
- package/src/dtls/RTCDtlsTransport.js +0 -247
- package/src/ice/RTCIceTransport.js +0 -1018
- package/src/index.d.ts +0 -400
- package/src/index.js +0 -92
- package/src/network/network-transport.js +0 -478
- package/src/peerconnection/RTCPeerConnection.js +0 -875
- package/src/sctp/RTCSctpTransport.js +0 -253
- package/src/sdp/sdp-utils.js +0 -224
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file sdp-utils.ts
|
|
3
|
+
* @description SDP generation/parsing for a WebRTC data-channel m-section.
|
|
4
|
+
* @module sdp/sdp-utils
|
|
5
|
+
*
|
|
6
|
+
* Emits the standard application m-line with the SCTP-over-DTLS profile and
|
|
7
|
+
* conveys transport addresses through ICE candidates (not the c=/m= lines, per
|
|
8
|
+
* the WebRTC JSEP/SDP rules). This is what browsers expect.
|
|
9
|
+
*/
|
|
10
|
+
/** ICE credentials. */
|
|
11
|
+
export interface IceCredentials {
|
|
12
|
+
usernameFragment: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
/** DTLS certificate fingerprint. */
|
|
16
|
+
export interface Fingerprint {
|
|
17
|
+
algorithm: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}
|
|
20
|
+
/** A candidate descriptor as accepted by {@link buildSdp}. */
|
|
21
|
+
export interface CandidateInput {
|
|
22
|
+
sdp?: string;
|
|
23
|
+
candidate?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Options accepted by {@link buildSdp}. */
|
|
26
|
+
export interface BuildSdpOptions {
|
|
27
|
+
kind?: 'offer' | 'answer';
|
|
28
|
+
iceUfrag: string;
|
|
29
|
+
icePwd: string;
|
|
30
|
+
fingerprint?: Fingerprint;
|
|
31
|
+
setup?: string;
|
|
32
|
+
candidates?: CandidateInput[];
|
|
33
|
+
sctpPort?: number;
|
|
34
|
+
maxMessageSize?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Options accepted by {@link generateOffer} / {@link generateAnswer}. */
|
|
37
|
+
export interface GenerateOptions extends BuildSdpOptions {
|
|
38
|
+
}
|
|
39
|
+
/** Parsed ICE parameters. */
|
|
40
|
+
export interface IceParameters {
|
|
41
|
+
usernameFragment: string | null;
|
|
42
|
+
password: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** Parsed DTLS parameters. */
|
|
45
|
+
export interface DtlsParameters {
|
|
46
|
+
role: string;
|
|
47
|
+
fingerprints: Fingerprint[];
|
|
48
|
+
setup?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Parsed SCTP parameters. */
|
|
51
|
+
export interface SctpParameters {
|
|
52
|
+
port: number;
|
|
53
|
+
maxMessageSize: number;
|
|
54
|
+
}
|
|
55
|
+
/** A parsed ICE candidate. */
|
|
56
|
+
export interface ParsedCandidate {
|
|
57
|
+
candidate: string;
|
|
58
|
+
foundation: string;
|
|
59
|
+
component: number;
|
|
60
|
+
protocol: string;
|
|
61
|
+
priority: number;
|
|
62
|
+
address: string;
|
|
63
|
+
port: number;
|
|
64
|
+
type: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate ICE credentials (ufrag >= 4 chars, pwd >= 22 chars per RFC 8445).
|
|
68
|
+
* @returns {{usernameFragment:string, password:string}}
|
|
69
|
+
*/
|
|
70
|
+
export declare function generateIceCredentials(): IceCredentials;
|
|
71
|
+
/**
|
|
72
|
+
* Build the SDP for a data-channel-only session.
|
|
73
|
+
* @param {Object} o
|
|
74
|
+
* @param {'offer'|'answer'} o.kind
|
|
75
|
+
* @param {string} o.iceUfrag
|
|
76
|
+
* @param {string} o.icePwd
|
|
77
|
+
* @param {{algorithm:string,value:string}} o.fingerprint - DTLS cert fingerprint
|
|
78
|
+
* @param {string} o.setup - 'actpass' | 'active' | 'passive'
|
|
79
|
+
* @param {Array<{sdp?:string,candidate?:string}>} [o.candidates]
|
|
80
|
+
* @param {number} [o.sctpPort=5000]
|
|
81
|
+
* @param {number} [o.maxMessageSize=262144]
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildSdp(o: BuildSdpOptions): string;
|
|
85
|
+
export declare function generateOffer(opts: GenerateOptions): string;
|
|
86
|
+
export declare function generateAnswer(opts: GenerateOptions): string;
|
|
87
|
+
/** Parse ICE ufrag/pwd. */
|
|
88
|
+
export declare function parseIceParameters(sdp: string): IceParameters;
|
|
89
|
+
/** Parse DTLS setup role + fingerprints. */
|
|
90
|
+
export declare function parseDtlsParameters(sdp: string): DtlsParameters;
|
|
91
|
+
/** Parse SCTP port / max message size. */
|
|
92
|
+
export declare function parseSctpParameters(sdp: string): SctpParameters;
|
|
93
|
+
/**
|
|
94
|
+
* Parse ICE candidate lines into structured objects.
|
|
95
|
+
* @param {string} sdp
|
|
96
|
+
* @returns {Array<{candidate:string,foundation:string,component:number,protocol:string,priority:number,address:string,port:number,type:string}>}
|
|
97
|
+
*/
|
|
98
|
+
export declare function parseCandidates(sdp: string): ParsedCandidate[];
|
|
99
|
+
/**
|
|
100
|
+
* Parse a single "candidate:..." string.
|
|
101
|
+
* @param {string} str
|
|
102
|
+
*/
|
|
103
|
+
export declare function parseCandidateLine(str: string): ParsedCandidate | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file stun-client.ts
|
|
3
|
+
* @description STUN (Session Traversal Utilities for NAT) client implementation
|
|
4
|
+
* @module stun/stun-client
|
|
5
|
+
*
|
|
6
|
+
* STUN Protocol: RFC 5389
|
|
7
|
+
* TURN Protocol: RFC 5766
|
|
8
|
+
*/
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
/**
|
|
11
|
+
* Constructor options for {@link STUNClient}.
|
|
12
|
+
*/
|
|
13
|
+
interface STUNClientOptions {
|
|
14
|
+
/** STUN/TURN server address */
|
|
15
|
+
server: string;
|
|
16
|
+
/** Server port */
|
|
17
|
+
port: number;
|
|
18
|
+
/** TURN username */
|
|
19
|
+
username?: string;
|
|
20
|
+
/** TURN password */
|
|
21
|
+
credential?: string;
|
|
22
|
+
/** Transport protocol (udp/tcp) */
|
|
23
|
+
transport?: string;
|
|
24
|
+
/** Additional query parameters from URL */
|
|
25
|
+
params?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reflexive address info resolved from a STUN Binding response.
|
|
29
|
+
*/
|
|
30
|
+
interface ReflexiveAddress {
|
|
31
|
+
address: string;
|
|
32
|
+
port: number;
|
|
33
|
+
family: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Relay address info resolved from a TURN Allocate response.
|
|
37
|
+
*/
|
|
38
|
+
interface RelayAddress {
|
|
39
|
+
relayedAddress: string;
|
|
40
|
+
relayedPort: number;
|
|
41
|
+
lifetime: number;
|
|
42
|
+
type: 'relay';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Result of a TURN Refresh response.
|
|
46
|
+
*/
|
|
47
|
+
interface RefreshResult {
|
|
48
|
+
lifetime: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Result of a generic success response (CreatePermission / ChannelBind).
|
|
52
|
+
*/
|
|
53
|
+
interface OkResult {
|
|
54
|
+
ok: true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Union of every result shape a transaction may resolve with.
|
|
58
|
+
*/
|
|
59
|
+
type TransactionResult = ReflexiveAddress | RelayAddress | RefreshResult | OkResult;
|
|
60
|
+
/**
|
|
61
|
+
* @class STUNClient
|
|
62
|
+
* @description STUN/TURN client for NAT traversal
|
|
63
|
+
*/
|
|
64
|
+
declare class STUNClient extends EventEmitter {
|
|
65
|
+
#private;
|
|
66
|
+
/**
|
|
67
|
+
* Create a STUN client
|
|
68
|
+
* @param {Object} options - Client options
|
|
69
|
+
* @param {string} options.server - STUN/TURN server address
|
|
70
|
+
* @param {number} options.port - Server port
|
|
71
|
+
* @param {string} [options.username] - TURN username
|
|
72
|
+
* @param {string} [options.credential] - TURN password
|
|
73
|
+
* @param {string} [options.transport='udp'] - Transport protocol (udp/tcp)
|
|
74
|
+
* @param {Object} [options.params={}] - Additional query parameters from URL
|
|
75
|
+
*/
|
|
76
|
+
constructor(options: STUNClientOptions);
|
|
77
|
+
/**
|
|
78
|
+
* Connect to the STUN/TURN server
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
connect(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Send a STUN Binding Request to get reflexive address
|
|
84
|
+
* @returns {Promise<Object>} Reflexive address info
|
|
85
|
+
*/
|
|
86
|
+
getReflexiveAddress(): Promise<TransactionResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Send a TURN Allocate Request to get relay address
|
|
89
|
+
* @param {number} [lifetime=600] - Allocation lifetime in seconds
|
|
90
|
+
* @returns {Promise<Object>} Relay address info
|
|
91
|
+
*/
|
|
92
|
+
allocateRelay(lifetime?: number): Promise<TransactionResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Send a TURN Refresh Request to keep allocation alive
|
|
95
|
+
* @param {number} [lifetime=600] - Allocation lifetime in seconds
|
|
96
|
+
* @returns {Promise<Object>} Updated allocation info
|
|
97
|
+
*/
|
|
98
|
+
refreshAllocation(lifetime?: number): Promise<TransactionResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a TURN Permission for a peer
|
|
101
|
+
* @param {string} peerAddress - Peer IP address
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
createPermission(peerAddress: string): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Send data to a peer via TURN Send Indication
|
|
107
|
+
* @param {string} peerAddress - Peer IP address
|
|
108
|
+
* @param {number} peerPort - Peer port
|
|
109
|
+
* @param {Buffer} data - Data to send
|
|
110
|
+
* @returns {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
sendIndication(peerAddress: string, peerPort: number, data: Buffer): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Close the client
|
|
115
|
+
*/
|
|
116
|
+
close(): void;
|
|
117
|
+
}
|
|
118
|
+
export default STUNClient;
|
|
119
|
+
export { STUNClient };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file transport-stack.ts
|
|
3
|
+
* @description Composes ICE -> DTLS -> SCTP -> DCEP into one transport, the
|
|
4
|
+
* real WebRTC data-channel pipeline.
|
|
5
|
+
* @module transport-stack
|
|
6
|
+
*
|
|
7
|
+
* Wiring:
|
|
8
|
+
* IceAgent emits 'data' (non-STUN datagrams) -> DtlsConnection.handlePacket
|
|
9
|
+
* DtlsConnection.output -> IceAgent.send
|
|
10
|
+
* DtlsConnection 'data' (app records) -> SctpAssociation.receivePacket
|
|
11
|
+
* SctpAssociation 'output' -> DtlsConnection.send
|
|
12
|
+
* SctpAssociation + DataChannelManager -> RTCDataChannel
|
|
13
|
+
*
|
|
14
|
+
* The DTLS client/server role follows the negotiated a=setup; the SCTP client
|
|
15
|
+
* (INIT initiator) is the DTLS client, per RFC 8832.
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
import { IceAgent } from './ice/ice-agent';
|
|
19
|
+
import { DtlsConnection } from './dtls/connection';
|
|
20
|
+
import { SctpAssociation } from './sctp/association';
|
|
21
|
+
import { DataChannelManager, OpenRequestInfo } from './sctp/datachannel-manager';
|
|
22
|
+
import type { RTCDataChannel, RTCDataChannelInit } from './datachannel/RTCDataChannel';
|
|
23
|
+
import type { KeyObject } from 'crypto';
|
|
24
|
+
export interface TransportStackOptions {
|
|
25
|
+
/** ICE controlling vs controlled (offerer is controlling). */
|
|
26
|
+
iceRole: 'controlling' | 'controlled';
|
|
27
|
+
/** DTLS role from a=setup (active=client). */
|
|
28
|
+
dtlsRole: 'client' | 'server';
|
|
29
|
+
localUfrag: string;
|
|
30
|
+
localPwd: string;
|
|
31
|
+
certDer: Buffer;
|
|
32
|
+
privateKey: KeyObject;
|
|
33
|
+
verifyFingerprint?: (fp: {
|
|
34
|
+
algorithm: string;
|
|
35
|
+
value: string;
|
|
36
|
+
}) => boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare class TransportStack extends EventEmitter {
|
|
39
|
+
#private;
|
|
40
|
+
ice: IceAgent;
|
|
41
|
+
dtls: DtlsConnection | null;
|
|
42
|
+
sctp: SctpAssociation | null;
|
|
43
|
+
dcm: DataChannelManager | null;
|
|
44
|
+
constructor(opts: TransportStackOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Begin gathering local candidates.
|
|
47
|
+
* @param opts - { iceServers, iceTransportPolicy } forwarded to ICE.
|
|
48
|
+
*/
|
|
49
|
+
gather(opts?: {
|
|
50
|
+
iceServers?: unknown[];
|
|
51
|
+
iceTransportPolicy?: 'all' | 'relay';
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
getLocalCandidates(): ReturnType<IceAgent['getLocalCandidates']>;
|
|
54
|
+
/** Provide the peer's ICE credentials and start checks when ready. */
|
|
55
|
+
setRemote(ufrag: string, pwd: string): void;
|
|
56
|
+
addRemoteCandidate(cand: {
|
|
57
|
+
address: string;
|
|
58
|
+
port: number;
|
|
59
|
+
type?: string;
|
|
60
|
+
priority?: number;
|
|
61
|
+
}): void;
|
|
62
|
+
/** Open a locally-initiated data channel once SCTP is established. */
|
|
63
|
+
openChannel(channel: RTCDataChannel, init: RTCDataChannelInit): void;
|
|
64
|
+
/** Accept an inbound channel created from a 'datachannel-request'. */
|
|
65
|
+
acceptChannel(channel: RTCDataChannel, info: OpenRequestInfo): void;
|
|
66
|
+
isReady(): boolean;
|
|
67
|
+
close(): void;
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-rtc-connection",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "WebRTC DataChannel implementation for Node.js with STUN, TURN, NAT traversal, and encryption. Pure Node.js, no native dependencies.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
|
-
"types": "
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
11
11
|
"require": "./dist/index.cjs",
|
|
12
12
|
"import": "./dist/index.mjs"
|
|
13
13
|
}
|
|
@@ -20,20 +20,17 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "rollup -c",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
23
24
|
"prepublishOnly": "npm run build && npm test",
|
|
24
|
-
"test": "node test/run-all-tests.
|
|
25
|
-
"test:
|
|
26
|
-
"test:unit": "SKIP_INTEGRATION=1 node --test test/*.test.
|
|
27
|
-
"test:
|
|
28
|
-
"test:
|
|
29
|
-
"test:
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"example": "node examples/real-networking.js",
|
|
34
|
-
"example:simple": "node examples/simple-datachannel.js",
|
|
35
|
-
"example:stun": "node examples/with-stun-encryption.js",
|
|
36
|
-
"example:turn": "node examples/with-turn-relay.js",
|
|
25
|
+
"test": "node --import tsx test/run-all-tests.ts",
|
|
26
|
+
"test:ci": "bash scripts/ci-local.sh",
|
|
27
|
+
"test:unit": "SKIP_INTEGRATION=1 node --import tsx --test test/*.test.ts",
|
|
28
|
+
"test:coverage": "c8 node --import tsx test/run-all-tests.ts",
|
|
29
|
+
"test:coverage:check": "c8 --check-coverage node --import tsx test/run-all-tests.ts",
|
|
30
|
+
"test:watch": "node --import tsx --test --watch test/*.test.ts",
|
|
31
|
+
"example": "node --import tsx examples/browser-server.ts",
|
|
32
|
+
"example:browser": "node --import tsx examples/browser-server.ts",
|
|
33
|
+
"example:node": "node --import tsx examples/node-to-node.ts",
|
|
37
34
|
"release:patch": "npm version patch && git push && git push --tags",
|
|
38
35
|
"release:minor": "npm version minor && git push && git push --tags",
|
|
39
36
|
"release:major": "npm version major && git push && git push --tags"
|
|
@@ -59,20 +56,28 @@
|
|
|
59
56
|
"license": "MIT",
|
|
60
57
|
"type": "commonjs",
|
|
61
58
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
59
|
+
"node": ">=18.0.0"
|
|
63
60
|
},
|
|
64
61
|
"devDependencies": {
|
|
65
62
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
66
63
|
"@rollup/plugin-json": "^6.1.0",
|
|
67
64
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
68
|
-
"rollup": "^
|
|
65
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
66
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
67
|
+
"@types/node": "^25.9.1",
|
|
68
|
+
"c8": "^11.0.0",
|
|
69
|
+
"playwright": "^1.60.0",
|
|
70
|
+
"rollup": "^4.54.0",
|
|
71
|
+
"tslib": "^2.8.1",
|
|
72
|
+
"tsx": "^4.22.3",
|
|
73
|
+
"typescript": "^6.0.3"
|
|
69
74
|
},
|
|
70
75
|
"repository": {
|
|
71
76
|
"type": "git",
|
|
72
|
-
"url": "git+https://github.com/nmhung1210/
|
|
77
|
+
"url": "git+https://github.com/nmhung1210/node-rtc-connection.git"
|
|
73
78
|
},
|
|
74
79
|
"bugs": {
|
|
75
|
-
"url": "https://github.com/nmhung1210/
|
|
80
|
+
"url": "https://github.com/nmhung1210/node-rtc-connection/issues"
|
|
76
81
|
},
|
|
77
|
-
"homepage": "https://github.com/nmhung1210/
|
|
82
|
+
"homepage": "https://github.com/nmhung1210/node-rtc-connection#readme"
|
|
78
83
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file der.ts
|
|
3
|
+
* @description Minimal ASN.1 DER encoder/decoder for X.509 certificate generation.
|
|
4
|
+
* @module crypto/der
|
|
5
|
+
*
|
|
6
|
+
* Implements just enough of ITU-T X.690 DER to build and read the structures
|
|
7
|
+
* WebRTC needs: self-signed ECDSA certificates and SubjectPublicKeyInfo.
|
|
8
|
+
*
|
|
9
|
+
* All encoders return Buffers. The TLV length is always encoded in the
|
|
10
|
+
* minimal (definite, shortest-form) representation required by DER.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// ASN.1 universal tag numbers (class 0, primitive/constructed as noted).
|
|
16
|
+
export const TAG = Object.freeze({
|
|
17
|
+
BOOLEAN: 0x01,
|
|
18
|
+
INTEGER: 0x02,
|
|
19
|
+
BIT_STRING: 0x03,
|
|
20
|
+
OCTET_STRING: 0x04,
|
|
21
|
+
NULL: 0x05,
|
|
22
|
+
OID: 0x06,
|
|
23
|
+
UTF8_STRING: 0x0c,
|
|
24
|
+
PRINTABLE_STRING: 0x13,
|
|
25
|
+
IA5_STRING: 0x16,
|
|
26
|
+
UTC_TIME: 0x17,
|
|
27
|
+
GENERALIZED_TIME: 0x18,
|
|
28
|
+
SEQUENCE: 0x30, // constructed
|
|
29
|
+
SET: 0x31, // constructed
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Encode a DER length in definite, shortest form.
|
|
34
|
+
* @param {number} len
|
|
35
|
+
* @returns {Buffer}
|
|
36
|
+
*/
|
|
37
|
+
export function encodeLength(len: number): Buffer {
|
|
38
|
+
if (len < 0x80) {
|
|
39
|
+
return Buffer.from([len]);
|
|
40
|
+
}
|
|
41
|
+
const bytes: number[] = [];
|
|
42
|
+
let n = len;
|
|
43
|
+
while (n > 0) {
|
|
44
|
+
bytes.unshift(n & 0xff);
|
|
45
|
+
n >>>= 8;
|
|
46
|
+
}
|
|
47
|
+
return Buffer.from([0x80 | bytes.length, ...bytes]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap a body in a TLV with the given tag.
|
|
52
|
+
* @param {number} tag
|
|
53
|
+
* @param {Buffer} body
|
|
54
|
+
* @returns {Buffer}
|
|
55
|
+
*/
|
|
56
|
+
export function tlv(tag: number, body: Buffer): Buffer {
|
|
57
|
+
return Buffer.concat([Buffer.from([tag]), encodeLength(body.length), body]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Encode an unsigned big-endian integer (from a Buffer) as a DER INTEGER,
|
|
62
|
+
* adding a leading 0x00 when the high bit is set so it stays positive.
|
|
63
|
+
* @param {Buffer} buf - Big-endian magnitude.
|
|
64
|
+
* @returns {Buffer}
|
|
65
|
+
*/
|
|
66
|
+
export function encodeIntegerFromBuffer(buf: Buffer): Buffer {
|
|
67
|
+
let start = 0;
|
|
68
|
+
while (start < buf.length - 1 && buf[start] === 0x00) {
|
|
69
|
+
start++; // strip leading zeros (keep at least one byte)
|
|
70
|
+
}
|
|
71
|
+
let body = buf.slice(start);
|
|
72
|
+
if (body[0]! & 0x80) {
|
|
73
|
+
body = Buffer.concat([Buffer.from([0x00]), body]);
|
|
74
|
+
}
|
|
75
|
+
return tlv(TAG.INTEGER, body);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Encode a small non-negative JS integer as a DER INTEGER.
|
|
80
|
+
* @param {number} value
|
|
81
|
+
* @returns {Buffer}
|
|
82
|
+
*/
|
|
83
|
+
export function encodeInteger(value: number): Buffer {
|
|
84
|
+
if (value === 0) {
|
|
85
|
+
return tlv(TAG.INTEGER, Buffer.from([0x00]));
|
|
86
|
+
}
|
|
87
|
+
const bytes: number[] = [];
|
|
88
|
+
let n = value;
|
|
89
|
+
while (n > 0) {
|
|
90
|
+
bytes.unshift(n & 0xff);
|
|
91
|
+
n = Math.floor(n / 256);
|
|
92
|
+
}
|
|
93
|
+
if (bytes[0]! & 0x80) {
|
|
94
|
+
bytes.unshift(0x00);
|
|
95
|
+
}
|
|
96
|
+
return tlv(TAG.INTEGER, Buffer.from(bytes));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Encode an OBJECT IDENTIFIER from its dotted-decimal string.
|
|
101
|
+
* @param {string} oid - e.g. "1.2.840.10045.2.1"
|
|
102
|
+
* @returns {Buffer}
|
|
103
|
+
*/
|
|
104
|
+
export function encodeOID(oid: string): Buffer {
|
|
105
|
+
const parts = oid.split('.').map(Number);
|
|
106
|
+
if (parts.length < 2) {
|
|
107
|
+
throw new Error(`Invalid OID: ${oid}`);
|
|
108
|
+
}
|
|
109
|
+
const bytes = [40 * parts[0]! + parts[1]!];
|
|
110
|
+
for (let i = 2; i < parts.length; i++) {
|
|
111
|
+
let v = parts[i]!;
|
|
112
|
+
const stack = [v & 0x7f];
|
|
113
|
+
v = Math.floor(v / 128);
|
|
114
|
+
while (v > 0) {
|
|
115
|
+
stack.unshift((v & 0x7f) | 0x80);
|
|
116
|
+
v = Math.floor(v / 128);
|
|
117
|
+
}
|
|
118
|
+
bytes.push(...stack);
|
|
119
|
+
}
|
|
120
|
+
return tlv(TAG.OID, Buffer.from(bytes));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Encode a BIT STRING with zero unused bits.
|
|
125
|
+
* @param {Buffer} data
|
|
126
|
+
* @returns {Buffer}
|
|
127
|
+
*/
|
|
128
|
+
export function encodeBitString(data: Buffer): Buffer {
|
|
129
|
+
return tlv(TAG.BIT_STRING, Buffer.concat([Buffer.from([0x00]), data]));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Encode an OCTET STRING.
|
|
134
|
+
* @param {Buffer} data
|
|
135
|
+
* @returns {Buffer}
|
|
136
|
+
*/
|
|
137
|
+
export function encodeOctetString(data: Buffer): Buffer {
|
|
138
|
+
return tlv(TAG.OCTET_STRING, data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Encode a SEQUENCE from already-encoded components.
|
|
143
|
+
* @param {Buffer[]} components
|
|
144
|
+
* @returns {Buffer}
|
|
145
|
+
*/
|
|
146
|
+
export function encodeSequence(components: Buffer[]): Buffer {
|
|
147
|
+
return tlv(TAG.SEQUENCE, Buffer.concat(components));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Encode a SET from already-encoded components.
|
|
152
|
+
* @param {Buffer[]} components
|
|
153
|
+
* @returns {Buffer}
|
|
154
|
+
*/
|
|
155
|
+
export function encodeSet(components: Buffer[]): Buffer {
|
|
156
|
+
return tlv(TAG.SET, Buffer.concat(components));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Encode NULL.
|
|
161
|
+
* @returns {Buffer}
|
|
162
|
+
*/
|
|
163
|
+
export function encodeNull(): Buffer {
|
|
164
|
+
return tlv(TAG.NULL, Buffer.alloc(0));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Encode a UTF8String.
|
|
169
|
+
* @param {string} str
|
|
170
|
+
* @returns {Buffer}
|
|
171
|
+
*/
|
|
172
|
+
export function encodeUTF8String(str: string): Buffer {
|
|
173
|
+
return tlv(TAG.UTF8_STRING, Buffer.from(str, 'utf8'));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Encode a context-specific [n] explicit wrapper (constructed).
|
|
178
|
+
* @param {number} n - context tag number
|
|
179
|
+
* @param {Buffer} body
|
|
180
|
+
* @returns {Buffer}
|
|
181
|
+
*/
|
|
182
|
+
export function encodeExplicit(n: number, body: Buffer): Buffer {
|
|
183
|
+
return tlv(0xa0 | n, body);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Encode an X.509 time. Uses UTCTime for years < 2050, else GeneralizedTime,
|
|
188
|
+
* per RFC 5280 §4.1.2.5.
|
|
189
|
+
* @param {Date} date
|
|
190
|
+
* @returns {Buffer}
|
|
191
|
+
*/
|
|
192
|
+
export function encodeTime(date: Date): Buffer {
|
|
193
|
+
const yyyy = date.getUTCFullYear();
|
|
194
|
+
const pad = (v: number, n = 2): string => String(v).padStart(n, '0');
|
|
195
|
+
const mm = pad(date.getUTCMonth() + 1);
|
|
196
|
+
const dd = pad(date.getUTCDate());
|
|
197
|
+
const hh = pad(date.getUTCHours());
|
|
198
|
+
const mi = pad(date.getUTCMinutes());
|
|
199
|
+
const ss = pad(date.getUTCSeconds());
|
|
200
|
+
if (yyyy < 2050) {
|
|
201
|
+
const yy = pad(yyyy % 100);
|
|
202
|
+
return tlv(TAG.UTC_TIME, Buffer.from(`${yy}${mm}${dd}${hh}${mi}${ss}Z`, 'ascii'));
|
|
203
|
+
}
|
|
204
|
+
return tlv(TAG.GENERALIZED_TIME, Buffer.from(`${yyyy}${mm}${dd}${hh}${mi}${ss}Z`, 'ascii'));
|
|
205
|
+
}
|