node-ikev2 0.1.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.
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Message = void 0;
4
+ const header_1 = require("./header");
5
+ const payload_1 = require("./payload");
6
+ /**
7
+ * IKEv2 Message class
8
+ * @class
9
+ * @property {Header} header - IKEv2 header
10
+ * @property {Payload[]} payloads - IKEv2 payloads
11
+ */
12
+ class Message {
13
+ /**
14
+ * @constructor
15
+ * @param {Header} header - IKEv2 header
16
+ * @param {Payload[]} payloads - IKEv2 payloads
17
+ */
18
+ constructor(header, payloads) {
19
+ this.header = header;
20
+ this.payloads = payloads;
21
+ }
22
+ static getBuffer(packet) {
23
+ if (typeof packet === "string") {
24
+ if (packet.length === 0) {
25
+ throw new Error("Hex string cannot be empty");
26
+ }
27
+ if (!/^[0-9a-fA-F]+$/.test(packet)) {
28
+ throw new Error("Invalid hex string format");
29
+ }
30
+ return Buffer.from(packet, "hex");
31
+ }
32
+ else if (Buffer.isBuffer(packet)) {
33
+ return packet;
34
+ }
35
+ else {
36
+ throw new Error("Packet must be a Buffer or hex string");
37
+ }
38
+ }
39
+ /**
40
+ * Parses IKEv2 message
41
+ * @param buffer - Buffer containing the message
42
+ * @param headerOnly - If only header is needed (default: false)
43
+ * @returns {Message}
44
+ */
45
+ static parse(packet, headerOnly = false) {
46
+ try {
47
+ // Input validation
48
+ if (!packet) {
49
+ throw new Error("Packet data is required");
50
+ }
51
+ let buffer = this.getBuffer(packet);
52
+ // Validate minimum packet size
53
+ if (buffer.length < header_1.Header.headerLength) {
54
+ throw new Error(`Packet too short. Expected at least ${header_1.Header.headerLength} bytes, got ${buffer.length}`);
55
+ }
56
+ const header = header_1.Header.parse(buffer);
57
+ // If only header is needed
58
+ if (headerOnly) {
59
+ return new Message(header, []);
60
+ }
61
+ // Validate that packet length matches header length
62
+ if (buffer.length < header.length) {
63
+ throw new Error(`Packet length mismatch. Header indicates ${header.length} bytes, but packet has ${buffer.length} bytes`);
64
+ }
65
+ let nextPayload = header.nextPayload;
66
+ let offset = header_1.Header.headerLength;
67
+ const payloads = [];
68
+ let nextPayloadClass = payload_1.payloadTypeMapping[nextPayload];
69
+ if (!nextPayloadClass) {
70
+ throw new Error(`Unknown payload type: ${nextPayload}`);
71
+ }
72
+ // Parse first payload
73
+ const firstPayload = nextPayloadClass.parse(buffer.subarray(header_1.Header.headerLength, buffer.length));
74
+ payloads.push(firstPayload);
75
+ offset += firstPayload.length;
76
+ // Validate payload length
77
+ if (offset > buffer.length) {
78
+ throw new Error(`Payload length exceeds packet size. Offset: ${offset}, Packet size: ${buffer.length}`);
79
+ }
80
+ if (!(firstPayload instanceof payload_1.PayloadSK)) {
81
+ nextPayload = firstPayload.nextPayload;
82
+ nextPayloadClass = payload_1.payloadTypeMapping[nextPayload];
83
+ // Parse subsequent payloads
84
+ while (offset < buffer.length &&
85
+ nextPayloadClass &&
86
+ nextPayload !== payload_1.payloadType.NONE) {
87
+ // Validate we have enough data for the next payload
88
+ if (offset + 4 > buffer.length) {
89
+ throw new Error(`Insufficient data for payload header at offset ${offset}`);
90
+ }
91
+ const payload = nextPayloadClass.parse(buffer.subarray(offset, buffer.length));
92
+ payloads.push(payload);
93
+ offset += payload.length;
94
+ nextPayload = payload.nextPayload;
95
+ nextPayloadClass = payload_1.payloadTypeMapping[nextPayload];
96
+ // Validate payload length
97
+ if (offset > buffer.length) {
98
+ throw new Error(`Payload length exceeds packet size. Offset: ${offset}, Packet size: ${buffer.length}`);
99
+ }
100
+ }
101
+ }
102
+ return new Message(header, payloads);
103
+ }
104
+ catch (error) {
105
+ if (error instanceof Error) {
106
+ throw new Error(`Failed to parse IKEv2 message: ${error.message}`);
107
+ }
108
+ throw new Error("Failed to parse IKEv2 message: Unknown error");
109
+ }
110
+ }
111
+ /**
112
+ * Serializes IKEv2 message from JSON to Buffer
113
+ * @param {Record<string, any>} json - JSON representation of the message
114
+ * @returns {Buffer}
115
+ */
116
+ static serializeJSON(json) {
117
+ try {
118
+ // Input validation
119
+ if (!json) {
120
+ throw new Error("JSON data is required");
121
+ }
122
+ if (!json.header) {
123
+ throw new Error("Message header is required");
124
+ }
125
+ if (!Array.isArray(json.payloads)) {
126
+ throw new Error("Message payloads must be an array");
127
+ }
128
+ const header = header_1.Header.serializeJSON(json.header);
129
+ const payloads = json.payloads.map((payload, index) => {
130
+ if (!payload) {
131
+ throw new Error(`Payload at index ${index} is null or undefined`);
132
+ }
133
+ if (typeof payload.type === "undefined") {
134
+ throw new Error(`Payload at index ${index} is missing type field`);
135
+ }
136
+ const type = payload.type;
137
+ const payloadClass = payload_1.payloadTypeMapping[type];
138
+ if (!payloadClass) {
139
+ throw new Error(`Unknown payload type: ${type} at index ${index}`);
140
+ }
141
+ return payloadClass.serializeJSON(payload);
142
+ });
143
+ return Buffer.concat([header, ...payloads]);
144
+ }
145
+ catch (error) {
146
+ if (error instanceof Error) {
147
+ throw new Error(`Failed to serialize IKEv2 message: ${error.message}`);
148
+ }
149
+ throw new Error("Failed to serialize IKEv2 message: Unknown error");
150
+ }
151
+ }
152
+ /**
153
+ * Serializes IKEv2 message
154
+ * @returns {Buffer}
155
+ */
156
+ serialize() {
157
+ // Resolve the nextPayload fields, to ensure that we send a valid message
158
+ if (this.payloads.length > 0) {
159
+ this.header.nextPayload = this.payloads[0].type;
160
+ for (let i = 0; i < this.payloads.length - 1; i++) {
161
+ if (this.payloads[i].type === payload_1.payloadType.SK) {
162
+ throw new Error("SK payload must be the last payload in the message");
163
+ }
164
+ this.payloads[i].nextPayload = this.payloads[i + 1].type;
165
+ }
166
+ if (this.payloads[this.payloads.length - 1].type !== payload_1.payloadType.SK) {
167
+ // The SK payload is special: its nextPayload indicates the first inner payload type. It also must be the
168
+ // last payload in the message.
169
+ this.payloads[this.payloads.length - 1].nextPayload = payload_1.payloadType.NONE;
170
+ }
171
+ }
172
+ else {
173
+ this.header.nextPayload = payload_1.payloadType.NONE;
174
+ }
175
+ // Serialize the payloads first, since we need their lengths to compute the total message length
176
+ const payloadsBuffers = this.payloads.map((payload) => payload.serialize());
177
+ const payloadsBuffer = Buffer.concat(payloadsBuffers);
178
+ // Update header length
179
+ this.header.length = header_1.Header.headerLength + payloadsBuffer.length;
180
+ // Serialize header
181
+ const headerBuffer = this.header.serialize();
182
+ // Concatenate header and payloads
183
+ return Buffer.concat([headerBuffer, payloadsBuffer]);
184
+ }
185
+ /**
186
+ * Returns a JSON representation of the message
187
+ * @returns {Record<string, any>}
188
+ */
189
+ toJSON() {
190
+ return {
191
+ header: this.header.toJSON(),
192
+ payloads: this.payloads.map((payload) => payload.toJSON()),
193
+ };
194
+ }
195
+ /**
196
+ * Returns a string representation of the message
197
+ * @returns {string}
198
+ */
199
+ toString() {
200
+ const header = this.header.toString();
201
+ const payloads = this.payloads
202
+ .map((payload) => payload.toString())
203
+ .join(",\n");
204
+ return `Header:\n ${header}\nPayloads:\n ${payloads}`;
205
+ }
206
+ /**
207
+ * Gets the payloads of the given type
208
+ * @param type
209
+ * @returns Payload[] | undefined
210
+ */
211
+ getPayloads(type) {
212
+ return this.payloads.filter((payload) => payload.type === type);
213
+ }
214
+ /**
215
+ * Gets the payload of the given type
216
+ * @param type
217
+ * @returns Payload | undefined
218
+ */
219
+ getPayload(type) {
220
+ var _a;
221
+ return (_a = this.getPayloads(type)) === null || _a === void 0 ? void 0 : _a[0];
222
+ }
223
+ /**
224
+ * Verifies the integrity checksum data of the given packet using the provided verifyFunction. Should be called on
225
+ * whenever you detect that the SK payload is present.
226
+ *
227
+ * The SK payload is always the last one in the message, such that the Integrity Checksum Data is at the end of the
228
+ * packet.
229
+ *
230
+ * Might want to verify that the SK payload is the last one in the message before calling this function. Else it will
231
+ * always fail.
232
+ *
233
+ * @param iv the IV used for the encryption of the SK payload, obtained from the decrypt function.
234
+ * @param packet the original input packet, as passed to the parse() function
235
+ * @param verifyFunction
236
+ * @returns
237
+ */
238
+ static verifyIntegrityChecksumData(iv, packet, verifyFunction) {
239
+ let buffer = this.getBuffer(packet);
240
+ if (buffer.length < header_1.Header.headerLength) {
241
+ throw new Error(`Packet too short for Integrity Checksum Data. Expected at least ${header_1.Header.headerLength} bytes, got ${buffer.length}`);
242
+ }
243
+ return verifyFunction(iv, buffer);
244
+ }
245
+ /**
246
+ * Updated the integrity checksum data of the given packet using the provided computeFunction. Should be called on the
247
+ * entire serialized packet, when an SK was included (and it was the last payload, hence the Integrity Checksum Data
248
+ * is supposed to be in the last bytes of the packet).
249
+ *
250
+ * Please verify that the SK payload is the last one in the message before calling this function.
251
+ *
252
+ * @param packet
253
+ * @param integrityChecksumDataLength
254
+ * @param signFunction
255
+ * @returns
256
+ */
257
+ static updateIntegrityChecksumData(iv, packet, signFunction) {
258
+ if (packet.length < header_1.Header.headerLength) {
259
+ throw new Error(`Packet too short for Integrity Checksum Data. Expected at least ${header_1.Header.headerLength} bytes, got ${packet.length}`);
260
+ }
261
+ return signFunction(iv, packet);
262
+ }
263
+ }
264
+ exports.Message = Message;