salty-crypto 0.0.5 → 0.1.1
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 +24 -21
- package/browser-demo.html +2 -2
- package/dist/salty-crypto.d.ts +277 -227
- package/dist/salty-crypto.js +1 -1
- package/package.json +1 -1
- package/speed.ts +22 -0
- package/src/aead/chacha20poly1305.ts +85 -0
- package/src/aead.ts +50 -94
- package/src/bytes.ts +29 -0
- package/src/{chacha20.ts → cipher/chacha20.ts} +37 -23
- package/src/cipher.ts +20 -0
- package/src/{x25519.ts → dh/x25519.ts} +0 -0
- package/src/dh.ts +30 -0
- package/src/{blake2.ts → hash/blake2s.ts} +11 -8
- package/src/{poly1305.ts → hash/poly1305.ts} +36 -29
- package/src/hash.ts +21 -0
- package/src/hkdf.ts +25 -0
- package/src/hmac.ts +24 -0
- package/src/index.ts +46 -9
- package/src/noise/algorithms.ts +26 -0
- package/src/noise/cipherstate.ts +38 -0
- package/src/noise/handshake.ts +240 -0
- package/src/{patterns.ts → noise/patterns.ts} +12 -1
- package/src/noise/profiles.ts +13 -0
- package/src/noise/rekey.ts +13 -0
- package/src/noise.ts +15 -365
- package/src/nonce.ts +23 -0
- package/test/tests/aead.test.ts +10 -10
- package/test/tests/blake2.test.ts +3 -4
- package/test/tests/chacha20.test.ts +11 -12
- package/test/tests/noise.test.ts +17 -19
- package/test/tests/poly1305.test.ts +2 -3
- package/src/profiles.ts +0 -59
package/src/noise.ts
CHANGED
|
@@ -1,368 +1,18 @@
|
|
|
1
1
|
/// SPDX-License-Identifier: MIT
|
|
2
2
|
/// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static get MAX(): Nonce {
|
|
22
|
-
return new Nonce(0xffffffff, 0xffffffff);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function bytesXor(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
27
|
-
const len = Math.min(a.byteLength, b.byteLength);
|
|
28
|
-
const r = new Uint8Array(len);
|
|
29
|
-
for (let i = 0; i < len; i++) r[i] = a[i] ^ b[i];
|
|
30
|
-
return r;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function bytesAppend(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
34
|
-
const r = new Uint8Array(a.byteLength + b.byteLength);
|
|
35
|
-
r.set(a, 0);
|
|
36
|
-
r.set(b, a.byteLength);
|
|
37
|
-
return r;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const EMPTY_BYTES = new Uint8Array(0);
|
|
41
|
-
|
|
42
|
-
export type HMAC = (key: Uint8Array, data: Uint8Array) => Uint8Array;
|
|
43
|
-
|
|
44
|
-
function makeHMAC(algorithms: NoiseProtocolAlgorithms): HMAC {
|
|
45
|
-
const HMAC_IPAD = new Uint8Array(algorithms.hashBlocklen()); HMAC_IPAD.fill(0x36);
|
|
46
|
-
const HMAC_OPAD = new Uint8Array(algorithms.hashBlocklen()); HMAC_OPAD.fill(0x5c);
|
|
47
|
-
return (key0, data) => {
|
|
48
|
-
const key = algorithms._padOrHash(key0, algorithms.hashBlocklen());
|
|
49
|
-
return algorithms.hash(bytesAppend(bytesXor(key, HMAC_OPAD),
|
|
50
|
-
algorithms.hash(bytesAppend(bytesXor(key, HMAC_IPAD),
|
|
51
|
-
data))));
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export abstract class NoiseProtocolAlgorithms {
|
|
56
|
-
readonly dhlen: number;
|
|
57
|
-
readonly hmac: HMAC;
|
|
58
|
-
|
|
59
|
-
constructor (hmac?: HMAC) {
|
|
60
|
-
const tmp = this.generateKeypair();
|
|
61
|
-
this.dhlen = this.dh(tmp, tmp.public).byteLength;
|
|
62
|
-
this.hmac = hmac ?? makeHMAC(this);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
abstract dhName(): string;
|
|
66
|
-
abstract generateKeypair(): DHKeyPair;
|
|
67
|
-
abstract dh(kp: DHKeyPair, pk: Uint8Array): Uint8Array;
|
|
68
|
-
|
|
69
|
-
abstract cipherName(): string;
|
|
70
|
-
abstract encrypt(key: DataView, nonce: Nonce, p: Uint8Array, associated_data?: Uint8Array): Uint8Array;
|
|
71
|
-
abstract decrypt(key: DataView, nonce: Nonce, c: Uint8Array, associated_data?: Uint8Array): Uint8Array;
|
|
72
|
-
|
|
73
|
-
abstract hashName(): string;
|
|
74
|
-
abstract hash(data: Uint8Array): Uint8Array;
|
|
75
|
-
abstract hashBlocklen(): number;
|
|
76
|
-
|
|
77
|
-
rekey(k: DataView): DataView {
|
|
78
|
-
return new DataView(this.encrypt(k, Nonce.MAX, new Uint8Array(32)).buffer);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
_padOrHash(bs0: Uint8Array, len: number): Uint8Array {
|
|
82
|
-
const bs = bs0.byteLength > len ? this.hash(bs0) : bs0;
|
|
83
|
-
return bytesAppend(bs, new Uint8Array(len - bs.byteLength));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
hkdf(chainingKey: Uint8Array, input: Uint8Array, numOutputs: 2): [Uint8Array, Uint8Array];
|
|
87
|
-
hkdf(chainingKey: Uint8Array, input: Uint8Array, numOutputs: 3): [Uint8Array, Uint8Array, Uint8Array];
|
|
88
|
-
hkdf(chainingKey: Uint8Array, input: Uint8Array, numOutputs: 2 | 3): Uint8Array[] {
|
|
89
|
-
const tempKey = this.hmac(chainingKey, input);
|
|
90
|
-
const o1 = this.hmac(tempKey, Uint8Array.from([1]));
|
|
91
|
-
const o2 = this.hmac(tempKey, bytesAppend(o1, Uint8Array.from([2])));
|
|
92
|
-
switch (numOutputs) {
|
|
93
|
-
case 2: return [o1, o2];
|
|
94
|
-
case 3: return [o1, o2, this.hmac(tempKey, bytesAppend(o2, Uint8Array.from([3])))];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
matchingPattern(protocol_name: string): string | null {
|
|
99
|
-
const r = new RegExp(`^Noise_([A-Za-z0-9+]+)_${this.dhName()}_${this.cipherName()}_${this.hashName()}$`);
|
|
100
|
-
const m = r.exec(protocol_name);
|
|
101
|
-
if (m === null) return null;
|
|
102
|
-
return m[1];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface HandshakePattern {
|
|
107
|
-
name: string; // e.g. "NNpsk2"
|
|
108
|
-
baseName: string; // e.g. "NN"
|
|
109
|
-
messages: Token[][];
|
|
110
|
-
initiatorPreMessage: PreMessage;
|
|
111
|
-
responderPreMessage: PreMessage;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export class CipherState {
|
|
115
|
-
view: DataView | null = null;
|
|
116
|
-
nonce = new Nonce();
|
|
117
|
-
|
|
118
|
-
constructor (public algorithms: NoiseProtocolAlgorithms,
|
|
119
|
-
key?: Uint8Array)
|
|
120
|
-
{
|
|
121
|
-
if (key !== void 0) this.view = new DataView(key.buffer);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
encrypt(plaintext: Uint8Array, associated_data?: Uint8Array): Uint8Array {
|
|
125
|
-
if (this.view === null) return plaintext;
|
|
126
|
-
const ciphertext = this.algorithms.encrypt(this.view, this.nonce, plaintext, associated_data);
|
|
127
|
-
this.nonce.increment();
|
|
128
|
-
return ciphertext;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
decrypt(ciphertext: Uint8Array, associated_data?: Uint8Array): Uint8Array {
|
|
132
|
-
if (this.view === null) return ciphertext;
|
|
133
|
-
const plaintext = this.algorithms.decrypt(this.view, this.nonce, ciphertext, associated_data);
|
|
134
|
-
this.nonce.increment();
|
|
135
|
-
return plaintext;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
rekey() {
|
|
139
|
-
if (this.view === null) return;
|
|
140
|
-
this.view = this.algorithms.rekey(this.view);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export type Role = 'initiator' | 'responder';
|
|
145
|
-
|
|
146
|
-
export type NoiseProtocolOptions = {
|
|
147
|
-
prologue?: Uint8Array,
|
|
148
|
-
staticKeypair?: DHKeyPair,
|
|
149
|
-
remoteStaticPublicKey?: Uint8Array,
|
|
150
|
-
pregeneratedEphemeralKeypair?: DHKeyPair,
|
|
151
|
-
remotePregeneratedEphemeralPublicKey?: Uint8Array,
|
|
152
|
-
preSharedKeys?: Uint8Array[],
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export type KeyTransferToken = 'e' | 's';
|
|
156
|
-
export type KeyMixToken = 'ee' | 'es' | 'se' | 'ss' | 'psk';
|
|
157
|
-
export type Token = KeyTransferToken | KeyMixToken;
|
|
158
|
-
export type PreMessage = ['e'] | ['s'] | ['e', 's'] | [];
|
|
159
|
-
|
|
160
|
-
export type TransportState = { send: CipherState, recv: CipherState };
|
|
161
|
-
|
|
162
|
-
export class NoiseHandshake {
|
|
163
|
-
staticKeypair: DHKeyPair;
|
|
164
|
-
remoteStaticPublicKey: Uint8Array | null;
|
|
165
|
-
ephemeralKeypair: DHKeyPair;
|
|
166
|
-
remoteEphemeralPublicKey: Uint8Array | null;
|
|
167
|
-
preSharedKeys?: Uint8Array[];
|
|
168
|
-
stepIndex = 0;
|
|
169
|
-
cipherState: CipherState;
|
|
170
|
-
chainingKey: Uint8Array;
|
|
171
|
-
handshakeHash: Uint8Array;
|
|
172
|
-
|
|
173
|
-
constructor (public algorithms: NoiseProtocolAlgorithms,
|
|
174
|
-
public pattern: HandshakePattern,
|
|
175
|
-
public role: Role,
|
|
176
|
-
options: NoiseProtocolOptions = {})
|
|
177
|
-
{
|
|
178
|
-
this.staticKeypair = options.staticKeypair ?? this.algorithms.generateKeypair();
|
|
179
|
-
this.remoteStaticPublicKey = options.remoteStaticPublicKey ?? null;
|
|
180
|
-
this.ephemeralKeypair = options.pregeneratedEphemeralKeypair ?? this.algorithms.generateKeypair();
|
|
181
|
-
this.remoteEphemeralPublicKey = options.remotePregeneratedEphemeralPublicKey ?? null;
|
|
182
|
-
this.preSharedKeys = options.preSharedKeys;
|
|
183
|
-
if (this.preSharedKeys) {
|
|
184
|
-
this.preSharedKeys = this.preSharedKeys.slice();
|
|
185
|
-
if (this.preSharedKeys.length === 0) this.preSharedKeys = void 0;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const protocolName = new TextEncoder().encode(
|
|
189
|
-
'Noise_' + this.pattern.name +
|
|
190
|
-
'_' + this.algorithms.dhName() +
|
|
191
|
-
'_' + this.algorithms.cipherName() +
|
|
192
|
-
'_' + this.algorithms.hashName());
|
|
193
|
-
|
|
194
|
-
this.cipherState = new CipherState(this.algorithms);
|
|
195
|
-
this.chainingKey = this.algorithms._padOrHash(
|
|
196
|
-
protocolName,
|
|
197
|
-
this.algorithms.hash(EMPTY_BYTES).byteLength);
|
|
198
|
-
this.handshakeHash = this.chainingKey;
|
|
199
|
-
|
|
200
|
-
this.mixHash(options.prologue ?? EMPTY_BYTES);
|
|
201
|
-
this.pattern.initiatorPreMessage.forEach(t => this.mixHash(t === 'e'
|
|
202
|
-
? (this.isInitiator ? this.ephemeralKeypair.public : this.remoteEphemeralPublicKey!)
|
|
203
|
-
: (this.isInitiator ? this.staticKeypair.public : this.remoteStaticPublicKey!)));
|
|
204
|
-
this.pattern.responderPreMessage.forEach(t => this.mixHash(t === 'e'
|
|
205
|
-
? (!this.isInitiator ? this.ephemeralKeypair.public : this.remoteEphemeralPublicKey!)
|
|
206
|
-
: (!this.isInitiator ? this.staticKeypair.public : this.remoteStaticPublicKey!)));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
get isInitiator(): boolean {
|
|
210
|
-
return this.role === 'initiator';
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
mixHash(data: Uint8Array) {
|
|
214
|
-
this.handshakeHash = this.algorithms.hash(bytesAppend(this.handshakeHash, data));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
mixKey(input: Uint8Array) {
|
|
218
|
-
const [newCk, k] = this.algorithms.hkdf(this.chainingKey, input, 2);
|
|
219
|
-
this.chainingKey = newCk;
|
|
220
|
-
this.cipherState = new CipherState(this.algorithms, k);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
mixKeyAndHashNextPSK() {
|
|
224
|
-
const psk = this.preSharedKeys!.shift()!;
|
|
225
|
-
const [newCk, tempH, k] = this.algorithms.hkdf(this.chainingKey, psk, 3);
|
|
226
|
-
this.chainingKey = newCk;
|
|
227
|
-
this.mixHash(tempH);
|
|
228
|
-
this.cipherState = new CipherState(this.algorithms, k);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
encryptAndHash(p: Uint8Array) {
|
|
232
|
-
const c = this.cipherState.encrypt(p, this.handshakeHash);
|
|
233
|
-
this.mixHash(c);
|
|
234
|
-
return c;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
decryptAndHash(c: Uint8Array) {
|
|
238
|
-
const p = this.cipherState.decrypt(c, this.handshakeHash);
|
|
239
|
-
this.mixHash(c);
|
|
240
|
-
return p;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
_split(): TransportState | null {
|
|
244
|
-
if (this.stepIndex < this.pattern.messages.length) {
|
|
245
|
-
return null;
|
|
246
|
-
} else {
|
|
247
|
-
let [kI, kR] = this.algorithms.hkdf(this.chainingKey, EMPTY_BYTES, 2)
|
|
248
|
-
.map(k => new CipherState(this.algorithms, k));
|
|
249
|
-
return this.isInitiator ? { send: kI, recv: kR } : { send: kR, recv: kI };
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
_nextStep(): Token[] {
|
|
254
|
-
if (this.stepIndex >= this.pattern.messages.length) {
|
|
255
|
-
throw new Error("Handshake already complete, cannot continue");
|
|
256
|
-
}
|
|
257
|
-
return this.pattern.messages[this.stepIndex++];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
_processKeyMixToken(t: KeyMixToken) {
|
|
261
|
-
switch (t) {
|
|
262
|
-
case 'ee':
|
|
263
|
-
this.mixKey(this.algorithms.dh(this.ephemeralKeypair, this.remoteEphemeralPublicKey!));
|
|
264
|
-
break;
|
|
265
|
-
|
|
266
|
-
case 'es':
|
|
267
|
-
this.mixKey(this.isInitiator
|
|
268
|
-
? this.algorithms.dh(this.ephemeralKeypair, this.remoteStaticPublicKey!)
|
|
269
|
-
: this.algorithms.dh(this.staticKeypair, this.remoteEphemeralPublicKey!));
|
|
270
|
-
break;
|
|
271
|
-
|
|
272
|
-
case 'se':
|
|
273
|
-
this.mixKey(!this.isInitiator
|
|
274
|
-
? this.algorithms.dh(this.ephemeralKeypair, this.remoteStaticPublicKey!)
|
|
275
|
-
: this.algorithms.dh(this.staticKeypair, this.remoteEphemeralPublicKey!));
|
|
276
|
-
break;
|
|
277
|
-
|
|
278
|
-
case 'ss':
|
|
279
|
-
this.mixKey(this.algorithms.dh(this.staticKeypair, this.remoteStaticPublicKey!));
|
|
280
|
-
break;
|
|
281
|
-
|
|
282
|
-
case 'psk':
|
|
283
|
-
this.mixKeyAndHashNextPSK();
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
writeMessage(payload: Uint8Array): { packet: Uint8Array, finished: TransportState | null } {
|
|
289
|
-
const pieces = [];
|
|
290
|
-
this._nextStep().forEach(t => {
|
|
291
|
-
switch (t) {
|
|
292
|
-
case 'e':
|
|
293
|
-
pieces.push(this.ephemeralKeypair.public);
|
|
294
|
-
this.mixHash(this.ephemeralKeypair.public);
|
|
295
|
-
if (this.preSharedKeys) this.mixKey(this.ephemeralKeypair.public);
|
|
296
|
-
break;
|
|
297
|
-
|
|
298
|
-
case 's':
|
|
299
|
-
pieces.push(this.encryptAndHash(this.staticKeypair.public));
|
|
300
|
-
break;
|
|
301
|
-
|
|
302
|
-
default:
|
|
303
|
-
this._processKeyMixToken(t);
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
pieces.push(this.encryptAndHash(payload));
|
|
308
|
-
|
|
309
|
-
let packet: Uint8Array;
|
|
310
|
-
if (pieces.length === 1) {
|
|
311
|
-
packet = pieces[0];
|
|
312
|
-
} else {
|
|
313
|
-
packet = new Uint8Array(pieces.reduce((ac, p) => ac + p.byteLength, 0));
|
|
314
|
-
let offset = 0;
|
|
315
|
-
pieces.forEach(p => { packet.set(p, offset); offset += p.byteLength; });
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return { packet, finished: this._split() };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
readMessage(packet: Uint8Array): { message: Uint8Array, finished: TransportState | null } {
|
|
322
|
-
const take = (n: number): Uint8Array => {
|
|
323
|
-
const bs = packet.slice(0, n);
|
|
324
|
-
packet = packet.subarray(n);
|
|
325
|
-
return bs;
|
|
326
|
-
};
|
|
327
|
-
this._nextStep().forEach(t => {
|
|
328
|
-
switch (t) {
|
|
329
|
-
case 'e':
|
|
330
|
-
this.remoteEphemeralPublicKey = take(this.algorithms.dhlen);
|
|
331
|
-
this.mixHash(this.remoteEphemeralPublicKey);
|
|
332
|
-
if (this.preSharedKeys) this.mixKey(this.remoteEphemeralPublicKey);
|
|
333
|
-
break;
|
|
334
|
-
|
|
335
|
-
case 's':
|
|
336
|
-
this.remoteStaticPublicKey = this.decryptAndHash(take(
|
|
337
|
-
this.algorithms.dhlen + (this.cipherState.view ? 16 : 0)));
|
|
338
|
-
break;
|
|
339
|
-
|
|
340
|
-
default:
|
|
341
|
-
this._processKeyMixToken(t);
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const message = this.decryptAndHash(packet);
|
|
347
|
-
return { message, finished: this._split() };
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async completeHandshake(writePacket: (packet: Uint8Array) => Promise<void>,
|
|
351
|
-
readPacket: () => Promise<Uint8Array>,
|
|
352
|
-
handleMessage = async (_m: Uint8Array): Promise<void> => {},
|
|
353
|
-
produceMessage = async (): Promise<Uint8Array> => new Uint8Array(0))
|
|
354
|
-
: Promise<TransportState>
|
|
355
|
-
{
|
|
356
|
-
const W = async (): Promise<TransportState> => {
|
|
357
|
-
const { packet, finished } = this.writeMessage(await produceMessage());
|
|
358
|
-
await writePacket(packet);
|
|
359
|
-
return finished || R();
|
|
360
|
-
};
|
|
361
|
-
const R = async (): Promise<TransportState> => {
|
|
362
|
-
const { message, finished } = this.readMessage(await readPacket());
|
|
363
|
-
await handleMessage(message);
|
|
364
|
-
return finished || W();
|
|
365
|
-
};
|
|
366
|
-
return (this.isInitiator ? W() : R());
|
|
367
|
-
}
|
|
368
|
-
}
|
|
4
|
+
export { Algorithms, matchPattern } from './noise/algorithms';
|
|
5
|
+
export { CipherState } from './noise/cipherstate';
|
|
6
|
+
export { Role, HandshakeOptions, TransportState, Handshake } from './noise/handshake';
|
|
7
|
+
export {
|
|
8
|
+
HandshakePattern,
|
|
9
|
+
KeyMixToken,
|
|
10
|
+
KeyTransferToken,
|
|
11
|
+
PATTERNS,
|
|
12
|
+
PreMessage,
|
|
13
|
+
Token,
|
|
14
|
+
isOneWay,
|
|
15
|
+
lookupPattern,
|
|
16
|
+
} from './noise/patterns';
|
|
17
|
+
export { Noise_25519_ChaChaPoly_BLAKE2s } from './noise/profiles';
|
|
18
|
+
export { Rekey } from './noise/rekey';
|
package/src/nonce.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// SPDX-License-Identifier: MIT
|
|
2
|
+
/// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
3
|
+
|
|
4
|
+
export class Nonce {
|
|
5
|
+
constructor(public lo = 0, public hi = 0, public extra = 0) {}
|
|
6
|
+
|
|
7
|
+
increment() {
|
|
8
|
+
const oldLo = this.lo;
|
|
9
|
+
const newLo = (oldLo + 1) | 0;
|
|
10
|
+
this.lo = newLo;
|
|
11
|
+
if (newLo < oldLo) this.hi = (this.hi + 1) | 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
reset(lo = 0, hi = 0, extra = 0) {
|
|
15
|
+
this.lo = lo;
|
|
16
|
+
this.hi = hi;
|
|
17
|
+
this.extra = extra;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get MAX(): Nonce {
|
|
21
|
+
return new Nonce(0xffffffff, 0xffffffff);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/test/tests/aead.test.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const { AEAD_CHACHA20_POLY1305_TAGBYTES, aead_encrypt_detached, aead_decrypt_detached } = AEAD;
|
|
1
|
+
import { ChaCha20Poly1305_RFC8439, Nonce } from '../../dist/salty-crypto.js';
|
|
3
2
|
import { it, expect } from '../harness';
|
|
4
3
|
|
|
5
4
|
it('section 2.8.2 from rfc 8439', () => {
|
|
@@ -18,10 +17,7 @@ it('section 2.8.2 from rfc 8439', () => {
|
|
|
18
17
|
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
|
|
19
18
|
]).buffer);
|
|
20
19
|
|
|
21
|
-
const nonce = new
|
|
22
|
-
0x07, 0x00, 0x00, 0x00,
|
|
23
|
-
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
|
24
|
-
]).buffer);
|
|
20
|
+
const nonce = new Nonce(0x43424140, 0x47464544, 0x7);
|
|
25
21
|
|
|
26
22
|
const expectedEncrypted = new Uint8Array([
|
|
27
23
|
0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
|
|
@@ -34,8 +30,9 @@ it('section 2.8.2 from rfc 8439', () => {
|
|
|
34
30
|
0x61, 0x16,
|
|
35
31
|
]);
|
|
36
32
|
|
|
37
|
-
const tag = new Uint8Array(
|
|
38
|
-
|
|
33
|
+
const tag = new Uint8Array(ChaCha20Poly1305_RFC8439.TAGBYTES);
|
|
34
|
+
ChaCha20Poly1305_RFC8439.encrypt_detached(
|
|
35
|
+
sunscreen, sunscreen, sunscreen.byteLength, tag, key, nonce, associated_data);
|
|
39
36
|
expect(sunscreen).toEqual(expectedEncrypted);
|
|
40
37
|
expect(tag).toEqual(new Uint8Array([
|
|
41
38
|
0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a,
|
|
@@ -43,10 +40,13 @@ it('section 2.8.2 from rfc 8439', () => {
|
|
|
43
40
|
]));
|
|
44
41
|
|
|
45
42
|
const sunscreen2 = Uint8Array.from(sunscreen);
|
|
46
|
-
expect(
|
|
43
|
+
expect(ChaCha20Poly1305_RFC8439.decrypt_detached(
|
|
44
|
+
sunscreen2, sunscreen2, sunscreen2.byteLength, tag, key, nonce, associated_data))
|
|
45
|
+
.toBe(true);
|
|
47
46
|
expect(new TextDecoder().decode(sunscreen2)).toBe(sunscreen_str);
|
|
48
47
|
|
|
49
48
|
tag[0]++;
|
|
50
|
-
expect(
|
|
49
|
+
expect(ChaCha20Poly1305_RFC8439.decrypt_detached(
|
|
50
|
+
sunscreen, sunscreen, sunscreen.byteLength, tag, key, nonce, associated_data)).toBe(false);
|
|
51
51
|
expect(sunscreen).toEqual(expectedEncrypted);
|
|
52
52
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const { BLAKE2s } = BLAKE2;
|
|
1
|
+
import { BLAKE2s } from '../../dist/salty-crypto.js';
|
|
3
2
|
import { it, expect } from '../harness';
|
|
4
3
|
|
|
5
4
|
it('Appendix B of RFC 7693', () => {
|
|
@@ -28,8 +27,8 @@ it('Appendix E of RFC 7693', () => {
|
|
|
28
27
|
[16, 20, 28, 32].forEach(outlen => {
|
|
29
28
|
[0, 3, 64, 65, 255, 1024].forEach(inlen => {
|
|
30
29
|
const input = seq(inlen, inlen);
|
|
31
|
-
ctx.update(BLAKE2s.digest(input, outlen));
|
|
32
|
-
ctx.update(BLAKE2s.digest(input, outlen,
|
|
30
|
+
ctx.update(BLAKE2s.digest(input, void 0, outlen));
|
|
31
|
+
ctx.update(BLAKE2s.digest(input, seq(outlen, outlen), outlen));
|
|
33
32
|
});
|
|
34
33
|
});
|
|
35
34
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChaCha20
|
|
1
|
+
import { ChaCha20, INTERNALS, Nonce } from '../../dist/salty-crypto.js';
|
|
2
|
+
const { chacha20_quarter_round, chacha20_block } = INTERNALS.cipher.chacha20;
|
|
2
3
|
import { it, expect } from '../harness';
|
|
3
4
|
|
|
4
5
|
it('chacha20_quarter_round 1', () => {
|
|
@@ -7,7 +8,7 @@ it('chacha20_quarter_round 1', () => {
|
|
|
7
8
|
s[1] = 0x01020304;
|
|
8
9
|
s[2] = 0x9b8d6f43;
|
|
9
10
|
s[3] = 0x01234567;
|
|
10
|
-
|
|
11
|
+
chacha20_quarter_round(s, 0, 1, 2, 3);
|
|
11
12
|
expect(Array.from(s)).toEqual([0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb]);
|
|
12
13
|
});
|
|
13
14
|
|
|
@@ -18,7 +19,7 @@ it('chacha20_quarter_round 2', () => {
|
|
|
18
19
|
0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963,
|
|
19
20
|
0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320,
|
|
20
21
|
]);
|
|
21
|
-
|
|
22
|
+
chacha20_quarter_round(s, 2, 7, 8, 13);
|
|
22
23
|
expect(s).toEqual(Uint32Array.from([
|
|
23
24
|
0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a,
|
|
24
25
|
0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2,
|
|
@@ -28,18 +29,18 @@ it('chacha20_quarter_round 2', () => {
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
it('chacha20_block', () => {
|
|
31
|
-
const key8 = new Uint8Array(
|
|
32
|
+
const key8 = new Uint8Array(ChaCha20.KEYBYTES);
|
|
32
33
|
for (let i = 0; i < key8.length; i++) key8[i] = i;
|
|
33
34
|
const key = new DataView(key8.buffer);
|
|
34
35
|
|
|
35
|
-
const nonce8 = new Uint8Array(
|
|
36
|
+
const nonce8 = new Uint8Array(ChaCha20.NONCEBYTES);
|
|
36
37
|
nonce8[3] = 0x09;
|
|
37
38
|
nonce8[7] = 0x4a;
|
|
38
39
|
const nonce = new DataView(nonce8.buffer);
|
|
39
40
|
|
|
40
41
|
const block = 1;
|
|
41
42
|
|
|
42
|
-
const output =
|
|
43
|
+
const output = chacha20_block(key, block, nonce);
|
|
43
44
|
expect(output).toEqual(Uint32Array.from([
|
|
44
45
|
0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3,
|
|
45
46
|
0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3,
|
|
@@ -49,13 +50,11 @@ it('chacha20_block', () => {
|
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
it('chacha20', () => {
|
|
52
|
-
const key8 = new Uint8Array(
|
|
53
|
+
const key8 = new Uint8Array(ChaCha20.KEYBYTES);
|
|
53
54
|
for (let i = 0; i < key8.length; i++) key8[i] = i;
|
|
54
55
|
const key = new DataView(key8.buffer);
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
-
nonce8[7] = 0x4a;
|
|
58
|
-
const nonce = new DataView(nonce8.buffer);
|
|
57
|
+
const nonce = new Nonce(0x4a000000, 0, 0);
|
|
59
58
|
|
|
60
59
|
const initial_counter = 1;
|
|
61
60
|
|
|
@@ -63,7 +62,7 @@ it('chacha20', () => {
|
|
|
63
62
|
const sunscreen = new TextEncoder().encode(sunscreen_str);
|
|
64
63
|
const output = new Uint8Array(sunscreen.byteLength);
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
ChaCha20.stream_xor(key, nonce, sunscreen, output, initial_counter);
|
|
67
66
|
expect(output).toEqual(Uint8Array.from([
|
|
68
67
|
0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
|
|
69
68
|
0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
|
|
@@ -76,6 +75,6 @@ it('chacha20', () => {
|
|
|
76
75
|
]));
|
|
77
76
|
|
|
78
77
|
// Test in-place encryption
|
|
79
|
-
|
|
78
|
+
ChaCha20.stream_xor(key, nonce, sunscreen, sunscreen, initial_counter);
|
|
80
79
|
expect(sunscreen).toEqual(output);
|
|
81
80
|
});
|
package/test/tests/noise.test.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { scalarMultBase } = X25519;
|
|
13
|
-
|
|
1
|
+
import {
|
|
2
|
+
Algorithms,
|
|
3
|
+
DHKeyPair,
|
|
4
|
+
Handshake,
|
|
5
|
+
INTERNALS,
|
|
6
|
+
Noise_25519_ChaChaPoly_BLAKE2s,
|
|
7
|
+
TransportState,
|
|
8
|
+
isOneWay,
|
|
9
|
+
lookupPattern,
|
|
10
|
+
matchPattern,
|
|
11
|
+
} from '../../dist/salty-crypto.js';
|
|
14
12
|
import { describe, it, expect } from '../harness';
|
|
15
13
|
|
|
16
14
|
import fs from 'fs';
|
|
@@ -75,30 +73,30 @@ function hex(bs: Uint8Array): string {
|
|
|
75
73
|
function skToKeypair(sk: Uint8Array | undefined): DHKeyPair | undefined {
|
|
76
74
|
if (sk === void 0) return void 0;
|
|
77
75
|
return {
|
|
78
|
-
public: scalarMultBase(sk),
|
|
76
|
+
public: INTERNALS.dh.x25519.scalarMultBase(sk),
|
|
79
77
|
secret: sk,
|
|
80
78
|
};
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
const unit = (v: string | undefined): [string] | undefined => v === void 0 ? void 0 : [v];
|
|
84
82
|
|
|
85
|
-
async function testsuite_test(t: Test, algorithms:
|
|
83
|
+
async function testsuite_test(t: Test, algorithms: Algorithms) {
|
|
86
84
|
const isOld = 'name' in t;
|
|
87
|
-
const patternName = algorithms
|
|
85
|
+
const patternName = matchPattern(algorithms, isOld ? t.name : t.protocol_name);
|
|
88
86
|
if (!patternName) return;
|
|
89
87
|
const pattern = lookupPattern(patternName);
|
|
90
88
|
if (!pattern) return;
|
|
91
89
|
const oneWay = isOneWay(pattern);
|
|
92
90
|
|
|
93
91
|
await it(pattern.name, async () => {
|
|
94
|
-
const I = new
|
|
92
|
+
const I = new Handshake(algorithms, pattern, 'initiator', {
|
|
95
93
|
prologue: unhex(t.init_prologue),
|
|
96
94
|
staticKeypair: skToKeypair(unhex(t.init_static)),
|
|
97
95
|
remoteStaticPublicKey: unhex(t.init_remote_static),
|
|
98
96
|
pregeneratedEphemeralKeypair: skToKeypair(unhex(t.init_ephemeral)),
|
|
99
97
|
preSharedKeys: (isOld ? unit(t.init_psk) : t.init_psks)?.map(k => unhex(k)),
|
|
100
98
|
});
|
|
101
|
-
const R = new
|
|
99
|
+
const R = new Handshake(algorithms, pattern, 'responder', {
|
|
102
100
|
prologue: unhex(t.resp_prologue),
|
|
103
101
|
staticKeypair: skToKeypair(unhex(t.resp_static)),
|
|
104
102
|
remoteStaticPublicKey: unhex(t.resp_remote_static),
|
|
@@ -137,7 +135,7 @@ async function testsuite_test(t: Test, algorithms: NoiseProtocolAlgorithms) {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
(async () => {
|
|
140
|
-
const algorithms =
|
|
138
|
+
const algorithms = Noise_25519_ChaChaPoly_BLAKE2s;
|
|
141
139
|
const load = (n: string) => JSON.parse(fs.readFileSync(path.join('test-vectors', n), 'utf-8'));
|
|
142
140
|
|
|
143
141
|
await describe('https://github.com/mcginty/snow/', async () => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Poly1305
|
|
2
|
-
const { Poly1305 } = P;
|
|
1
|
+
import { Poly1305 } from '../../dist/salty-crypto.js';
|
|
3
2
|
|
|
4
3
|
import { it, expect } from '../harness';
|
|
5
4
|
|
|
@@ -11,7 +10,7 @@ it('section 2.5.2 from rfc 8439', () => {
|
|
|
11
10
|
0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b,
|
|
12
11
|
]);
|
|
13
12
|
const message = new TextEncoder().encode("Cryptographic Forum Research Group");
|
|
14
|
-
expect(Poly1305.digest(
|
|
13
|
+
expect(Poly1305.digest(message, key)).toEqual(Uint8Array.from([
|
|
15
14
|
0xa8, 0x06, 0x1d, 0xc1, 0x30, 0x51, 0x36, 0xc6,
|
|
16
15
|
0xc2, 0x2b, 0x8b, 0xaf, 0x0c, 0x01, 0x27, 0xa9,
|
|
17
16
|
]));
|