Skip to content

Commit 3a89045

Browse files
committed
WIP
1 parent b2b6973 commit 3a89045

File tree

3 files changed

+191
-1
lines changed

3 files changed

+191
-1
lines changed

src/utils/utils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,32 @@ function bufferSplit(
319319
return output;
320320
}
321321

322+
/**
323+
* Zero-copy wraps ArrayBuffer-like objects into Buffer
324+
* This supports ArrayBuffer, TypedArrays and the NodeJS Buffer
325+
*/
326+
function bufferWrap(
327+
array: ArrayBuffer,
328+
offset?: number,
329+
length?: number,
330+
): Buffer {
331+
if (Buffer.isBuffer(array)) {
332+
return array;
333+
} else if (ArrayBuffer.isView(array)) {
334+
return Buffer.from(
335+
array.buffer,
336+
offset ?? array.byteOffset,
337+
length ?? array.byteLength
338+
);
339+
} else {
340+
return Buffer.from(
341+
array,
342+
offset,
343+
length
344+
);
345+
}
346+
}
347+
322348
function debounce<P extends any[]>(
323349
f: (...params: P) => any,
324350
timeout: number = 0,
@@ -419,4 +445,5 @@ export {
419445
isAsyncGenerator,
420446
lexiPackBuffer,
421447
lexiUnpackBuffer,
448+
bufferWrap,
422449
};

test-bootstrapping.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as jest from 'jest';
21
import * as jose from 'jose';
32
import { hkdf, KeyObject, webcrypto } from 'crypto';
43
import * as bip39 from '@scure/bip39';

test-dek.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import * as jose from 'jose';
2+
import { hkdf, KeyObject, webcrypto } from 'crypto';
3+
import * as bip39 from '@scure/bip39';
4+
import { wordlist } from '@scure/bip39/wordlists/english';
5+
6+
import * as nobleEd25519 from '@noble/ed25519';
7+
import * as nobleHashesUtils from '@noble/hashes/utils';
8+
9+
import * as base64 from 'multiformats/bases/base64';
10+
import * as noblePbkdf2 from '@noble/hashes/pbkdf2';
11+
import * as nobleHkdf from '@noble/hashes/hkdf';
12+
13+
import { sha512 as nobleSha512 } from '@noble/hashes/sha512';
14+
import { sha256 as nobleSha256 } from '@noble/hashes/sha256';
15+
import { isTypedArray } from 'util/types';
16+
17+
type Assert = (condition: unknown) => asserts condition;
18+
const assert: Assert = (condition) => {
19+
if (condition == false) throw new Error('Invalid assertion');
20+
};
21+
22+
23+
/**
24+
* Zero-copy wraps ArrayBuffer-like objects into Buffer
25+
* This supports ArrayBuffer, TypedArrays and NodeJS Buffer
26+
*/
27+
function bufferWrap(
28+
array: ArrayBuffer,
29+
offset?: number,
30+
length?: number,
31+
): Buffer {
32+
if (Buffer.isBuffer(array)) {
33+
return array;
34+
} else if (ArrayBuffer.isView(array)) {
35+
return Buffer.from(
36+
array.buffer,
37+
offset ?? array.byteOffset,
38+
length ?? array.byteLength
39+
);
40+
} else {
41+
return Buffer.from(
42+
array,
43+
offset,
44+
length
45+
);
46+
}
47+
}
48+
49+
50+
/**
51+
* This is limited to 65,536 bytes of random data
52+
* Stream this call, if you want more
53+
*/
54+
function getRandomBytesSync(size: number): Buffer {
55+
return webcrypto.getRandomValues(
56+
Buffer.allocUnsafe(size)
57+
);
58+
}
59+
60+
// @ts-ignore - this overrides the random source used by @noble and @scure libraries
61+
nobleHashesUtils.randomBytes = (size: number = 32) => getRandomBytesSync(size);
62+
nobleEd25519.utils.randomBytes = (size: number = 32) => getRandomBytesSync(size);
63+
64+
async function encryptWithKey(
65+
key: CryptoKey,
66+
plainText: ArrayBuffer
67+
): Promise<Buffer> {
68+
const iv = getRandomBytesSync(16);
69+
const data = await webcrypto.subtle.encrypt(
70+
{
71+
name: 'AES-GCM',
72+
iv,
73+
tagLength: 128,
74+
},
75+
key,
76+
plainText
77+
);
78+
return Buffer.concat([
79+
iv,
80+
bufferWrap(data)
81+
]);
82+
}
83+
84+
async function decryptWithKey(
85+
key: CryptoKey,
86+
cipherText: ArrayBuffer
87+
): Promise<Buffer | undefined> {
88+
const cipherText_ = bufferWrap(cipherText);
89+
if (cipherText_.byteLength < 32) {
90+
return;
91+
}
92+
const iv = cipherText_.subarray(0, 16);
93+
const data = cipherText_.subarray(16);
94+
let plainText: ArrayBuffer;
95+
try {
96+
plainText = await webcrypto.subtle.decrypt(
97+
{
98+
name: 'AES-GCM',
99+
iv,
100+
tagLength: 128
101+
},
102+
key,
103+
data
104+
);
105+
} catch (e) {
106+
// This means algorithm is incorrectly setup
107+
if (e.name === 'InvalidAccessError') {
108+
throw e;
109+
}
110+
// Otherwise the key is wrong
111+
// or the data is wrong
112+
return;
113+
}
114+
return bufferWrap(plainText);
115+
}
116+
117+
async function main () {
118+
119+
const databaseKey = getRandomBytesSync(32);
120+
121+
const databaseKeyJWK = {
122+
alg: "A256GCM",
123+
kty: "oct",
124+
k: base64.base64url.baseEncode(databaseKey),
125+
ext: true,
126+
key_ops: ["encrypt", "decrypt"],
127+
};
128+
129+
const databaseCryptoKey = await webcrypto.subtle.importKey(
130+
'jwk',
131+
databaseKeyJWK,
132+
'AES-GCM',
133+
true,
134+
databaseKeyJWK.key_ops as Array<webcrypto.KeyUsage>
135+
);
136+
137+
const cipherText = await encryptWithKey(
138+
databaseCryptoKey,
139+
Buffer.from('hello world')
140+
);
141+
142+
// Try with incorrect key
143+
// const databaseCryptoKey2 = await webcrypto.subtle.importKey(
144+
// 'raw',
145+
// getRandomBytesSync(16),
146+
// 'AES-GCM',
147+
// true,
148+
// databaseKeyJWK.key_ops as Array<webcrypto.KeyUsage>
149+
// );
150+
151+
const plainText = await decryptWithKey(
152+
databaseCryptoKey,
153+
cipherText
154+
);
155+
156+
console.log(plainText?.toString());
157+
158+
// Now we do key wrapping
159+
160+
161+
162+
}
163+
164+
void main();

0 commit comments

Comments
 (0)