leviathan-crypto 1.3.1 → 2.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/CLAUDE.md +129 -76
- package/README.md +166 -221
- package/SECURITY.md +89 -37
- package/dist/chacha20/cipher-suite.d.ts +4 -0
- package/dist/chacha20/cipher-suite.js +78 -0
- package/dist/chacha20/embedded.d.ts +1 -0
- package/dist/chacha20/embedded.js +27 -0
- package/dist/chacha20/index.d.ts +20 -7
- package/dist/chacha20/index.js +41 -14
- package/dist/chacha20/ops.d.ts +1 -1
- package/dist/chacha20/ops.js +19 -18
- package/dist/chacha20/pool-worker.js +77 -0
- package/dist/ct-wasm.d.ts +1 -0
- package/dist/ct-wasm.js +3 -0
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +320 -0
- package/dist/docs/architecture.md +419 -285
- package/dist/docs/argon2id.md +42 -30
- package/dist/docs/chacha20.md +218 -150
- package/dist/docs/exports.md +241 -0
- package/dist/docs/fortuna.md +65 -74
- package/dist/docs/init.md +172 -178
- package/dist/docs/loader.md +87 -132
- package/dist/docs/serpent.md +134 -565
- package/dist/docs/sha2.md +91 -103
- package/dist/docs/sha3.md +70 -36
- package/dist/docs/types.md +93 -16
- package/dist/docs/utils.md +114 -41
- package/dist/embedded/chacha20.d.ts +1 -1
- package/dist/embedded/chacha20.js +2 -1
- package/dist/embedded/kyber.d.ts +1 -0
- package/dist/embedded/kyber.js +3 -0
- package/dist/embedded/serpent.d.ts +1 -1
- package/dist/embedded/serpent.js +2 -1
- package/dist/embedded/sha2.d.ts +1 -1
- package/dist/embedded/sha2.js +2 -1
- package/dist/embedded/sha3.d.ts +1 -1
- package/dist/embedded/sha3.js +2 -1
- package/dist/errors.d.ts +10 -0
- package/dist/{serpent/seal.js → errors.js} +14 -46
- package/dist/fortuna.d.ts +2 -8
- package/dist/fortuna.js +11 -9
- package/dist/index.d.ts +25 -9
- package/dist/index.js +36 -7
- package/dist/init.d.ts +3 -7
- package/dist/init.js +18 -35
- package/dist/keccak/embedded.d.ts +1 -0
- package/dist/keccak/embedded.js +27 -0
- package/dist/keccak/index.d.ts +4 -0
- package/dist/keccak/index.js +31 -0
- package/dist/kyber/embedded.d.ts +1 -0
- package/dist/kyber/embedded.js +27 -0
- package/dist/kyber/indcpa.d.ts +49 -0
- package/dist/kyber/indcpa.js +352 -0
- package/dist/kyber/index.d.ts +38 -0
- package/dist/kyber/index.js +150 -0
- package/dist/kyber/kem.d.ts +21 -0
- package/dist/kyber/kem.js +160 -0
- package/dist/kyber/params.d.ts +14 -0
- package/dist/kyber/params.js +37 -0
- package/dist/kyber/suite.d.ts +13 -0
- package/dist/kyber/suite.js +93 -0
- package/dist/kyber/types.d.ts +98 -0
- package/dist/kyber/types.js +25 -0
- package/dist/kyber/validate.d.ts +19 -0
- package/dist/kyber/validate.js +68 -0
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +19 -4
- package/dist/loader.js +91 -25
- package/dist/serpent/cipher-suite.d.ts +4 -0
- package/dist/serpent/cipher-suite.js +121 -0
- package/dist/serpent/embedded.d.ts +1 -0
- package/dist/serpent/embedded.js +27 -0
- package/dist/serpent/index.d.ts +6 -37
- package/dist/serpent/index.js +9 -118
- package/dist/serpent/pool-worker.d.ts +1 -0
- package/dist/serpent/pool-worker.js +202 -0
- package/dist/serpent/serpent-cbc.d.ts +30 -0
- package/dist/serpent/serpent-cbc.js +136 -0
- package/dist/sha2/embedded.d.ts +1 -0
- package/dist/sha2/embedded.js +27 -0
- package/dist/sha2/hkdf.js +6 -2
- package/dist/sha2/index.d.ts +3 -2
- package/dist/sha2/index.js +3 -4
- package/dist/sha3/embedded.d.ts +1 -0
- package/dist/sha3/embedded.js +27 -0
- package/dist/sha3/index.d.ts +3 -2
- package/dist/sha3/index.js +3 -4
- package/dist/stream/constants.d.ts +6 -0
- package/dist/stream/constants.js +30 -0
- package/dist/stream/header.d.ts +9 -0
- package/dist/stream/header.js +77 -0
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +27 -0
- package/dist/stream/open-stream.d.ts +21 -0
- package/dist/stream/open-stream.js +146 -0
- package/dist/stream/seal-stream-pool.d.ts +38 -0
- package/dist/stream/seal-stream-pool.js +391 -0
- package/dist/stream/seal-stream.d.ts +20 -0
- package/dist/stream/seal-stream.js +142 -0
- package/dist/stream/seal.d.ts +9 -0
- package/dist/stream/seal.js +75 -0
- package/dist/stream/types.d.ts +24 -0
- package/dist/stream/types.js +26 -0
- package/dist/utils.d.ts +12 -7
- package/dist/utils.js +75 -19
- package/dist/wasm-source.d.ts +12 -0
- package/dist/wasm-source.js +26 -0
- package/package.json +13 -5
- package/dist/chacha20/pool.d.ts +0 -52
- package/dist/chacha20/pool.js +0 -188
- package/dist/chacha20/pool.worker.js +0 -37
- package/dist/docs/chacha20_pool.md +0 -309
- package/dist/docs/wasm.md +0 -194
- package/dist/serpent/seal.d.ts +0 -8
- package/dist/serpent/stream-pool.d.ts +0 -48
- package/dist/serpent/stream-pool.js +0 -285
- package/dist/serpent/stream-sealer.d.ts +0 -50
- package/dist/serpent/stream-sealer.js +0 -341
- package/dist/serpent/stream.d.ts +0 -28
- package/dist/serpent/stream.js +0 -205
- package/dist/serpent/stream.worker.d.ts +0 -32
- package/dist/serpent/stream.worker.js +0 -117
- /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
package/dist/loader.js
CHANGED
|
@@ -1,30 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
for (let i = 0; i < raw.length; i++)
|
|
6
|
-
out[i] = raw.charCodeAt(i);
|
|
7
|
-
return out;
|
|
8
|
-
}
|
|
9
|
-
return new Uint8Array(Buffer.from(b64, 'base64'));
|
|
1
|
+
import { base64ToBytes as _b64 } from './utils.js';
|
|
2
|
+
// Each WASM module gets its own fresh Memory — never shared between instances.
|
|
3
|
+
function makeImports() {
|
|
4
|
+
return { env: { memory: new WebAssembly.Memory({ initial: 3, maximum: 3 }) } };
|
|
10
5
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
// TS 5.9 generified Uint8Array<TArrayBuffer> with default ArrayBufferLike, which
|
|
7
|
+
// no longer satisfies BufferSource = ArrayBufferView<ArrayBuffer> | ArrayBuffer.
|
|
8
|
+
// Convert Uint8Array to a proper ArrayBuffer before calling WebAssembly APIs.
|
|
9
|
+
function toArrayBuffer(bytes) {
|
|
10
|
+
if (bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength)
|
|
11
|
+
return bytes.buffer;
|
|
12
|
+
const buf = new ArrayBuffer(bytes.byteLength);
|
|
13
|
+
new Uint8Array(buf).set(bytes);
|
|
14
|
+
return buf;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Decode a gzip+base64 embedded WASM string to raw bytes.
|
|
18
|
+
* Guards against missing DecompressionStream (Node <18, non-browser runtimes).
|
|
19
|
+
* Exported for pool worker launchers that decode blobs before spawning threads.
|
|
20
|
+
*/
|
|
21
|
+
export async function decodeWasm(b64) {
|
|
22
|
+
if (typeof DecompressionStream === 'undefined')
|
|
23
|
+
throw new Error('leviathan-crypto: DecompressionStream not available — '
|
|
24
|
+
+ 'use a URL, ArrayBuffer, or WebAssembly.Module source in this runtime');
|
|
25
|
+
const compressed = _b64(b64);
|
|
26
|
+
if (!compressed)
|
|
27
|
+
throw new Error('leviathan-crypto: corrupt embedded WASM — base64 decode failed');
|
|
28
|
+
const ds = new DecompressionStream('gzip');
|
|
29
|
+
const writer = ds.writable.getWriter();
|
|
30
|
+
const reader = ds.readable.getReader();
|
|
31
|
+
const writePromise = writer.write(compressed).then(() => writer.close());
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let done, value;
|
|
34
|
+
while ({ done, value } = await reader.read(), !done)
|
|
35
|
+
if (value)
|
|
36
|
+
chunks.push(value);
|
|
37
|
+
await writePromise;
|
|
38
|
+
const len = chunks.reduce((s, c) => s + c.length, 0);
|
|
39
|
+
const out = new Uint8Array(len);
|
|
40
|
+
let off = 0;
|
|
41
|
+
for (const c of chunks) {
|
|
42
|
+
out.set(c, off);
|
|
43
|
+
off += c.length;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
19
46
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Compile a WASM source to a Module without instantiating.
|
|
49
|
+
* Used by pool infrastructure to send compiled modules to workers.
|
|
50
|
+
*/
|
|
51
|
+
export async function compileWasm(source) {
|
|
52
|
+
if (typeof source === 'string') {
|
|
53
|
+
if (source.length === 0)
|
|
54
|
+
throw new TypeError('leviathan-crypto: invalid WasmSource — empty string');
|
|
55
|
+
return WebAssembly.compile(toArrayBuffer(await decodeWasm(source)));
|
|
56
|
+
}
|
|
57
|
+
if (source instanceof URL)
|
|
58
|
+
return WebAssembly.compileStreaming(fetch(source.href));
|
|
59
|
+
if (source instanceof ArrayBuffer)
|
|
60
|
+
return WebAssembly.compile(source);
|
|
61
|
+
if (source instanceof Uint8Array)
|
|
62
|
+
return WebAssembly.compile(toArrayBuffer(source));
|
|
63
|
+
if (source instanceof WebAssembly.Module)
|
|
64
|
+
return source;
|
|
65
|
+
if (typeof Response !== 'undefined' && source instanceof Response)
|
|
66
|
+
return WebAssembly.compileStreaming(source);
|
|
67
|
+
if (source != null && typeof source.then === 'function')
|
|
68
|
+
return WebAssembly.compileStreaming(source);
|
|
69
|
+
throw new TypeError(`leviathan-crypto: invalid WasmSource — got ${source === null ? 'null' : typeof source}`);
|
|
26
70
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Load a WASM module from any accepted source type.
|
|
73
|
+
* The loading strategy is inferred from the argument type — no mode string.
|
|
74
|
+
*
|
|
75
|
+
* Throws `TypeError` for null, numeric, or unrecognised inputs.
|
|
76
|
+
*/
|
|
77
|
+
export async function loadWasm(source) {
|
|
78
|
+
if (typeof source === 'string') {
|
|
79
|
+
if (source.length === 0)
|
|
80
|
+
throw new TypeError('leviathan-crypto: invalid WasmSource — empty string');
|
|
81
|
+
return (await WebAssembly.instantiate(toArrayBuffer(await decodeWasm(source)), makeImports())).instance;
|
|
82
|
+
}
|
|
83
|
+
if (source instanceof URL)
|
|
84
|
+
return (await WebAssembly.instantiateStreaming(fetch(source.href), makeImports())).instance;
|
|
85
|
+
if (source instanceof ArrayBuffer)
|
|
86
|
+
return (await WebAssembly.instantiate(source, makeImports())).instance;
|
|
87
|
+
if (source instanceof Uint8Array)
|
|
88
|
+
return (await WebAssembly.instantiate(toArrayBuffer(source), makeImports())).instance;
|
|
89
|
+
if (source instanceof WebAssembly.Module)
|
|
90
|
+
return WebAssembly.instantiate(source, makeImports());
|
|
91
|
+
if (typeof Response !== 'undefined' && source instanceof Response)
|
|
92
|
+
return (await WebAssembly.instantiateStreaming(source, makeImports())).instance;
|
|
93
|
+
if (source != null && typeof source.then === 'function')
|
|
94
|
+
return (await WebAssembly.instantiateStreaming(source, makeImports())).instance;
|
|
95
|
+
throw new TypeError(`leviathan-crypto: invalid WasmSource — got ${source === null ? 'null' : typeof source}`);
|
|
30
96
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/serpent/cipher-suite.ts
|
|
23
|
+
//
|
|
24
|
+
// SerpentCipher — CipherSuite implementation for the STREAM construction.
|
|
25
|
+
// 3-key HKDF derivation, HMAC-derived CBC IV, Serpent-CBC + HMAC-SHA-256.
|
|
26
|
+
// Verify-then-decrypt ordering prevents padding oracle attacks (Vaudenay 2002).
|
|
27
|
+
import { SerpentCbc } from './serpent-cbc.js';
|
|
28
|
+
import { HKDF_SHA256, HMAC_SHA256 } from '../sha2/index.js';
|
|
29
|
+
import { constantTimeEqual, wipe, concat, randomBytes } from '../utils.js';
|
|
30
|
+
import { AuthenticationError } from '../errors.js';
|
|
31
|
+
import { getInstance } from '../init.js';
|
|
32
|
+
const INFO = new TextEncoder().encode('serpent-sealstream-v2');
|
|
33
|
+
export const SerpentCipher = {
|
|
34
|
+
formatEnum: 0x02,
|
|
35
|
+
formatName: 'serpent',
|
|
36
|
+
hkdfInfo: 'serpent-sealstream-v2',
|
|
37
|
+
keySize: 32,
|
|
38
|
+
kemCtSize: 0,
|
|
39
|
+
tagSize: 32,
|
|
40
|
+
padded: true,
|
|
41
|
+
wasmModules: ['serpent', 'sha2'],
|
|
42
|
+
keygen() {
|
|
43
|
+
return randomBytes(32);
|
|
44
|
+
},
|
|
45
|
+
deriveKeys(masterKey, nonce, _kemCt) {
|
|
46
|
+
const hkdf = new HKDF_SHA256();
|
|
47
|
+
const derived = hkdf.derive(masterKey, nonce, INFO, 96);
|
|
48
|
+
hkdf.dispose();
|
|
49
|
+
// bytes[0:32]=enc_key, bytes[32:64]=mac_key, bytes[64:96]=iv_key
|
|
50
|
+
return { bytes: derived };
|
|
51
|
+
},
|
|
52
|
+
sealChunk(keys, counterNonce, chunk, aad) {
|
|
53
|
+
const encKey = keys.bytes.subarray(0, 32);
|
|
54
|
+
const macKey = keys.bytes.subarray(32, 64);
|
|
55
|
+
const ivKey = keys.bytes.subarray(64, 96);
|
|
56
|
+
const aadBytes = aad ?? new Uint8Array(0);
|
|
57
|
+
const hmac = new HMAC_SHA256();
|
|
58
|
+
// Derive IV from counter nonce
|
|
59
|
+
const ivFull = hmac.hash(ivKey, counterNonce);
|
|
60
|
+
const iv = ivFull.slice(0, 16);
|
|
61
|
+
wipe(ivFull);
|
|
62
|
+
// Encrypt: Serpent-CBC with PKCS7 padding
|
|
63
|
+
const cbc = new SerpentCbc({ dangerUnauthenticated: true });
|
|
64
|
+
const ct = cbc.encrypt(encKey, iv, chunk);
|
|
65
|
+
cbc.dispose();
|
|
66
|
+
// Compute HMAC tag: HMAC-SHA-256(mac_key, counterNonce || u32be(aad_len) || aad || ct)
|
|
67
|
+
const aadLenBuf = new Uint8Array(4);
|
|
68
|
+
new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
|
|
69
|
+
const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
|
|
70
|
+
const tag = hmac.hash(macKey, tagInput);
|
|
71
|
+
hmac.dispose();
|
|
72
|
+
wipe(iv);
|
|
73
|
+
wipe(tagInput);
|
|
74
|
+
// Output: ct || tag (IV is NOT included)
|
|
75
|
+
return concat(ct, tag);
|
|
76
|
+
},
|
|
77
|
+
openChunk(keys, counterNonce, chunk, aad) {
|
|
78
|
+
if (chunk.length < 32)
|
|
79
|
+
throw new RangeError(`chunk too short for 32-byte tag (got ${chunk.length})`);
|
|
80
|
+
const encKey = keys.bytes.subarray(0, 32);
|
|
81
|
+
const macKey = keys.bytes.subarray(32, 64);
|
|
82
|
+
const ivKey = keys.bytes.subarray(64, 96);
|
|
83
|
+
const aadBytes = aad ?? new Uint8Array(0);
|
|
84
|
+
const ct = chunk.subarray(0, chunk.length - 32);
|
|
85
|
+
const receivedTag = chunk.subarray(chunk.length - 32);
|
|
86
|
+
const hmac = new HMAC_SHA256();
|
|
87
|
+
// Derive IV from counter nonce
|
|
88
|
+
const ivFull = hmac.hash(ivKey, counterNonce);
|
|
89
|
+
const iv = ivFull.slice(0, 16);
|
|
90
|
+
wipe(ivFull);
|
|
91
|
+
// Compute expected tag: HMAC-SHA-256(mac_key, counterNonce || u32be(aad_len) || aad || ct)
|
|
92
|
+
const aadLenBuf = new Uint8Array(4);
|
|
93
|
+
new DataView(aadLenBuf.buffer).setUint32(0, aadBytes.length, false);
|
|
94
|
+
const tagInput = concat(counterNonce, aadLenBuf, aadBytes, ct);
|
|
95
|
+
const expectedTag = hmac.hash(macKey, tagInput);
|
|
96
|
+
hmac.dispose();
|
|
97
|
+
// CRITICAL: Verify HMAC BEFORE decrypting.
|
|
98
|
+
// Evaluating PKCS7 padding on unauthenticated data is a padding oracle (Vaudenay 2002).
|
|
99
|
+
if (!constantTimeEqual(expectedTag, receivedTag)) {
|
|
100
|
+
wipe(iv);
|
|
101
|
+
wipe(tagInput);
|
|
102
|
+
wipe(expectedTag);
|
|
103
|
+
getInstance('serpent').exports.wipeBuffers();
|
|
104
|
+
throw new AuthenticationError('serpent');
|
|
105
|
+
}
|
|
106
|
+
wipe(tagInput);
|
|
107
|
+
wipe(expectedTag);
|
|
108
|
+
// ONLY decrypt after authentication succeeds
|
|
109
|
+
const cbc = new SerpentCbc({ dangerUnauthenticated: true });
|
|
110
|
+
const plaintext = cbc.decrypt(encKey, iv, ct);
|
|
111
|
+
cbc.dispose();
|
|
112
|
+
wipe(iv);
|
|
113
|
+
return plaintext;
|
|
114
|
+
},
|
|
115
|
+
wipeKeys(keys) {
|
|
116
|
+
wipe(keys.bytes);
|
|
117
|
+
},
|
|
118
|
+
createPoolWorker() {
|
|
119
|
+
return new Worker(new URL('./pool-worker.js', import.meta.url), { type: 'module' });
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WASM_GZ_BASE64 as serpentWasm } from '../embedded/serpent.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ▄▄▄▄▄▄▄▄▄▄
|
|
2
|
+
// ▄████████████████████▄▄ ▒ ▄▀▀ ▒ ▒ █ ▄▀▄ ▀█▀ █ ▒ ▄▀▄ █▀▄
|
|
3
|
+
// ▄██████████████████████ ▀████▄ ▓ ▓▀ ▓ ▓ ▓ ▓▄▓ ▓ ▓▀▓ ▓▄▓ ▓ ▓
|
|
4
|
+
// ▄█████████▀▀▀ ▀███████▄▄███████▌ ▀▄ ▀▄▄ ▀▄▀ ▒ ▒ ▒ ▒ ▒ █ ▒ ▒ ▒ █
|
|
5
|
+
// ▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
|
|
6
|
+
// ████████ ███▀▀ ████▀ █▀ █▀ Leviathan Crypto Library
|
|
7
|
+
// ███████▌ ▀██▀ ███
|
|
8
|
+
// ███████ ▀███ ▀██ ▀█▄ Repository & Mirror:
|
|
9
|
+
// ▀██████ ▄▄██ ▀▀ ██▄ github.com/xero/leviathan-crypto
|
|
10
|
+
// ▀█████▄ ▄██▄ ▄▀▄▀ unpkg.com/leviathan-crypto
|
|
11
|
+
// ▀████▄ ▄██▄
|
|
12
|
+
// ▐████ ▐███ Author: xero (https://x-e.ro)
|
|
13
|
+
// ▄▄██████████ ▐███ ▄▄ License: MIT
|
|
14
|
+
// ▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
|
|
15
|
+
// ▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███ This file is provided completely
|
|
16
|
+
// ▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀ free, "as is", and without
|
|
17
|
+
// ████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄ warranty of any kind. The author
|
|
18
|
+
// █████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄████ assumes absolutely no liability
|
|
19
|
+
// ▀██████▀ ▀████▄▄▄████▀ for its {ab,mis,}use.
|
|
20
|
+
// ▀█████▀▀
|
|
21
|
+
//
|
|
22
|
+
// src/ts/serpent/embedded.ts
|
|
23
|
+
//
|
|
24
|
+
// Exports the gzip+base64 serpent WASM blob for use as a WasmSource.
|
|
25
|
+
// This is the only file in the serpent subpath that references the embedded blob.
|
|
26
|
+
// Import via `leviathan-crypto/serpent/embedded`.
|
|
27
|
+
export { WASM_GZ_BASE64 as serpentWasm } from '../embedded/serpent.js';
|
package/dist/serpent/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function serpentInit(
|
|
1
|
+
import type { WasmSource } from '../wasm-source.js';
|
|
2
|
+
export declare function serpentInit(source: WasmSource): Promise<void>;
|
|
3
|
+
export type { WasmSource };
|
|
3
4
|
export declare class Serpent {
|
|
4
5
|
private readonly x;
|
|
5
6
|
constructor();
|
|
@@ -26,39 +27,7 @@ export declare class SerpentCtr {
|
|
|
26
27
|
decryptChunk(chunk: Uint8Array): Uint8Array;
|
|
27
28
|
dispose(): void;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* **WARNING: CBC mode is unauthenticated.** Always authenticate the output
|
|
33
|
-
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
34
|
-
*/
|
|
35
|
-
export declare class SerpentCbc {
|
|
36
|
-
private readonly x;
|
|
37
|
-
constructor(opts?: {
|
|
38
|
-
dangerUnauthenticated: true;
|
|
39
|
-
});
|
|
40
|
-
private get mem();
|
|
41
|
-
/**
|
|
42
|
-
* Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
|
|
43
|
-
*
|
|
44
|
-
* @param key 16, 24, or 32 bytes
|
|
45
|
-
* @param iv 16 bytes — must be random and unique per (key, message)
|
|
46
|
-
* @param plaintext any length — PKCS7 padding applied automatically
|
|
47
|
-
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
48
|
-
*/
|
|
49
|
-
encrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
50
|
-
/**
|
|
51
|
-
* Decrypt Serpent-256 CBC + PKCS7.
|
|
52
|
-
* Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
|
|
53
|
-
*/
|
|
54
|
-
decrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
55
|
-
dispose(): void;
|
|
56
|
-
private _loadKey;
|
|
57
|
-
private _setIv;
|
|
58
|
-
}
|
|
59
|
-
export { SerpentSeal } from './seal.js';
|
|
60
|
-
export { SerpentStream, sealChunk, openChunk } from './stream.js';
|
|
61
|
-
export { SerpentStreamPool } from './stream-pool.js';
|
|
62
|
-
export type { StreamPoolOpts } from './stream-pool.js';
|
|
63
|
-
export { SerpentStreamSealer, SerpentStreamOpener } from './stream-sealer.js';
|
|
30
|
+
export { SerpentCbc } from './serpent-cbc.js';
|
|
31
|
+
export { AuthenticationError } from '../errors.js';
|
|
32
|
+
export { SerpentCipher } from './cipher-suite.js';
|
|
64
33
|
export declare function _serpentReady(): boolean;
|
package/dist/serpent/index.js
CHANGED
|
@@ -22,12 +22,10 @@
|
|
|
22
22
|
// src/ts/serpent/index.ts
|
|
23
23
|
//
|
|
24
24
|
// Public API classes for the Serpent-256 WASM module.
|
|
25
|
-
// Uses the init() module cache — call
|
|
25
|
+
// Uses the init() module cache — call serpentInit(source) before constructing.
|
|
26
26
|
import { getInstance, initModule } from '../init.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export async function serpentInit(mode = 'embedded', opts) {
|
|
30
|
-
return initModule('serpent', _embedded, mode, opts);
|
|
27
|
+
export async function serpentInit(source) {
|
|
28
|
+
return initModule('serpent', source);
|
|
31
29
|
}
|
|
32
30
|
function getExports() {
|
|
33
31
|
return getInstance('serpent').exports;
|
|
@@ -82,7 +80,7 @@ export class SerpentCtr {
|
|
|
82
80
|
x;
|
|
83
81
|
constructor(opts) {
|
|
84
82
|
if (!opts?.dangerUnauthenticated) {
|
|
85
|
-
throw new Error('leviathan-crypto: SerpentCtr is unauthenticated — use
|
|
83
|
+
throw new Error('leviathan-crypto: SerpentCtr is unauthenticated — use Seal with SerpentCipher instead. ' +
|
|
86
84
|
'To use SerpentCtr directly, pass { dangerUnauthenticated: true }.');
|
|
87
85
|
}
|
|
88
86
|
this.x = getExports();
|
|
@@ -106,8 +104,7 @@ export class SerpentCtr {
|
|
|
106
104
|
const ptOff = this.x.getChunkPtOffset();
|
|
107
105
|
const ctOff = this.x.getChunkCtOffset();
|
|
108
106
|
mem.set(chunk, ptOff);
|
|
109
|
-
|
|
110
|
-
fn(chunk.length);
|
|
107
|
+
this.x.encryptChunk_simd(chunk.length);
|
|
111
108
|
return mem.slice(ctOff, ctOff + chunk.length);
|
|
112
109
|
}
|
|
113
110
|
beginDecrypt(key, nonce) {
|
|
@@ -120,117 +117,11 @@ export class SerpentCtr {
|
|
|
120
117
|
this.x.wipeBuffers();
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
|
-
// ── PKCS7 helpers ────────────────────────────────────────────────────────────
|
|
124
|
-
function pkcs7Pad(data) {
|
|
125
|
-
const padLen = 16 - (data.length % 16); // 1..16
|
|
126
|
-
const out = new Uint8Array(data.length + padLen);
|
|
127
|
-
out.set(data);
|
|
128
|
-
out.fill(padLen, data.length);
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
|
-
function pkcs7Strip(data) {
|
|
132
|
-
if (data.length === 0)
|
|
133
|
-
throw new RangeError('empty ciphertext');
|
|
134
|
-
const padLen = data[data.length - 1];
|
|
135
|
-
if (padLen === 0 || padLen > 16)
|
|
136
|
-
throw new RangeError(`invalid PKCS7 padding byte: ${padLen}`);
|
|
137
|
-
if (padLen > data.length)
|
|
138
|
-
throw new RangeError(`invalid PKCS7 padding: pad length ${padLen} exceeds data length ${data.length}`);
|
|
139
|
-
let bad = 0;
|
|
140
|
-
for (let i = data.length - padLen; i < data.length; i++)
|
|
141
|
-
bad |= data[i] ^ padLen;
|
|
142
|
-
if (bad !== 0)
|
|
143
|
-
throw new RangeError('invalid PKCS7 padding');
|
|
144
|
-
return data.subarray(0, data.length - padLen);
|
|
145
|
-
}
|
|
146
120
|
// ── SerpentCbc ───────────────────────────────────────────────────────────────
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
* with HMAC-SHA256 (Encrypt-then-MAC) or use `XChaCha20Poly1305` instead.
|
|
152
|
-
*/
|
|
153
|
-
export class SerpentCbc {
|
|
154
|
-
x;
|
|
155
|
-
constructor(opts) {
|
|
156
|
-
if (!opts?.dangerUnauthenticated) {
|
|
157
|
-
throw new Error('leviathan-crypto: SerpentCbc is unauthenticated — use SerpentSeal instead. ' +
|
|
158
|
-
'To use SerpentCbc directly, pass { dangerUnauthenticated: true }.');
|
|
159
|
-
}
|
|
160
|
-
this.x = getExports();
|
|
161
|
-
}
|
|
162
|
-
get mem() {
|
|
163
|
-
return new Uint8Array(this.x.memory.buffer);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Encrypt plaintext with Serpent-256 CBC + PKCS7 padding.
|
|
167
|
-
*
|
|
168
|
-
* @param key 16, 24, or 32 bytes
|
|
169
|
-
* @param iv 16 bytes — must be random and unique per (key, message)
|
|
170
|
-
* @param plaintext any length — PKCS7 padding applied automatically
|
|
171
|
-
* @returns ciphertext (length = ceil((plaintext.length + 1) / 16) * 16)
|
|
172
|
-
*/
|
|
173
|
-
encrypt(key, iv, plaintext) {
|
|
174
|
-
this._loadKey(key);
|
|
175
|
-
this._setIv(iv);
|
|
176
|
-
const padded = pkcs7Pad(plaintext);
|
|
177
|
-
const output = new Uint8Array(padded.length);
|
|
178
|
-
const ptOff = this.x.getChunkPtOffset();
|
|
179
|
-
const ctOff = this.x.getChunkCtOffset();
|
|
180
|
-
const maxChunk = 65536;
|
|
181
|
-
for (let off = 0; off < padded.length; off += maxChunk) {
|
|
182
|
-
const chunk = padded.subarray(off, Math.min(off + maxChunk, padded.length));
|
|
183
|
-
this.mem.set(chunk, ptOff);
|
|
184
|
-
this.x.cbcEncryptChunk(chunk.length);
|
|
185
|
-
output.set(new Uint8Array(this.x.memory.buffer).subarray(ctOff, ctOff + chunk.length), off);
|
|
186
|
-
}
|
|
187
|
-
return output;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Decrypt Serpent-256 CBC + PKCS7.
|
|
191
|
-
* Throws if ciphertext length is not a non-zero multiple of 16 or PKCS7 is invalid.
|
|
192
|
-
*/
|
|
193
|
-
decrypt(key, iv, ciphertext) {
|
|
194
|
-
if (ciphertext.length === 0 || ciphertext.length % 16 !== 0)
|
|
195
|
-
throw new RangeError('ciphertext length must be a non-zero multiple of 16');
|
|
196
|
-
this._loadKey(key);
|
|
197
|
-
this._setIv(iv);
|
|
198
|
-
const output = new Uint8Array(ciphertext.length);
|
|
199
|
-
const ctOff = this.x.getChunkCtOffset();
|
|
200
|
-
const ptOff = this.x.getChunkPtOffset();
|
|
201
|
-
const maxChunk = 65536;
|
|
202
|
-
for (let off = 0; off < ciphertext.length; off += maxChunk) {
|
|
203
|
-
const chunk = ciphertext.subarray(off, Math.min(off + maxChunk, ciphertext.length));
|
|
204
|
-
this.mem.set(chunk, ctOff);
|
|
205
|
-
const fn = hasSIMD() ? this.x.cbcDecryptChunk_simd : this.x.cbcDecryptChunk;
|
|
206
|
-
fn(chunk.length);
|
|
207
|
-
output.set(new Uint8Array(this.x.memory.buffer).subarray(ptOff, ptOff + chunk.length), off);
|
|
208
|
-
}
|
|
209
|
-
return pkcs7Strip(output);
|
|
210
|
-
}
|
|
211
|
-
dispose() {
|
|
212
|
-
this.x.wipeBuffers();
|
|
213
|
-
}
|
|
214
|
-
_loadKey(key) {
|
|
215
|
-
if (key.length !== 16 && key.length !== 24 && key.length !== 32)
|
|
216
|
-
throw new RangeError(`Serpent key must be 16, 24, or 32 bytes (got ${key.length})`);
|
|
217
|
-
this.mem.set(key, this.x.getKeyOffset());
|
|
218
|
-
this.x.loadKey(key.length);
|
|
219
|
-
}
|
|
220
|
-
_setIv(iv) {
|
|
221
|
-
if (iv.length !== 16)
|
|
222
|
-
throw new RangeError(`CBC IV must be 16 bytes (got ${iv.length})`);
|
|
223
|
-
this.mem.set(iv, this.x.getCbcIvOffset());
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// ── SerpentSeal re-export ─────────────────────────────────────────────────────
|
|
227
|
-
export { SerpentSeal } from './seal.js';
|
|
228
|
-
// ── SerpentStream re-export ───────────────────────────────────────────────────
|
|
229
|
-
export { SerpentStream, sealChunk, openChunk } from './stream.js';
|
|
230
|
-
// ── SerpentStreamPool re-export ───────────────────────────────────────────────
|
|
231
|
-
export { SerpentStreamPool } from './stream-pool.js';
|
|
232
|
-
// ── SerpentStreamSealer / SerpentStreamOpener re-export ───────────────────────
|
|
233
|
-
export { SerpentStreamSealer, SerpentStreamOpener } from './stream-sealer.js';
|
|
121
|
+
export { SerpentCbc } from './serpent-cbc.js';
|
|
122
|
+
export { AuthenticationError } from '../errors.js';
|
|
123
|
+
// ── SerpentCipher re-export ───────────────────────────────────────────────────
|
|
124
|
+
export { SerpentCipher } from './cipher-suite.js';
|
|
234
125
|
// ── Ready check ──────────────────────────────────────────────────────────────
|
|
235
126
|
export function _serpentReady() {
|
|
236
127
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|