-
Notifications
You must be signed in to change notification settings - Fork 70
/
crypto.js
237 lines (197 loc) · 5.89 KB
/
crypto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/* global console */
/* eslint-disable no-fallthrough */
/**
* @module Crypto
*
* Some high-level methods around the `crypto.subtle` API for getting
* random bytes and hashing.
*
* Example usage:
* ```js
* import { randomBytes } from 'socket:crypto'
* ```
*/
import { toBuffer } from './util.js'
import { Buffer } from './buffer.js'
import * as exports from './crypto.js'
const textDecoder = new TextDecoder()
/**
* @typedef {Uint8Array|Int8Array} TypedArray
*/
/**
* WebCrypto API
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto}
*/
export let webcrypto = globalThis.crypto?.webcrypto ?? globalThis.crypto
const pending = []
if (globalThis?.process?.versions?.node) {
pending.push(
import('node:crypto')
.then((module) => {
webcrypto = module.webcrypto
})
)
}
const sodium = {
ready: new Promise((resolve, reject) => {
import('./crypto/sodium.js')
.then((module) => module.default.libsodium)
.then((libsodium) => libsodium.ready.then(() => libsodium))
.then((libsodium) => Object.assign(sodium, libsodium))
.then(resolve, reject)
})
}
pending.push(sodium.ready)
/**
* A promise that resolves when all internals to be loaded/ready.
* @type {Promise}
*/
export const ready = Promise.all(pending)
/**
* libsodium API
* @see {@link https://doc.libsodium.org/}
* @see {@link https://github.com/jedisct1/libsodium.js}
*/
export { sodium }
/**
* Generate cryptographically strong random values into the `buffer`
* @param {TypedArray} buffer
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues}
* @return {TypedArray}
*/
export function getRandomValues (buffer, ...args) {
if (typeof webcrypto?.getRandomValues === 'function') {
return webcrypto?.getRandomValues(buffer, ...args)
}
if (sodium.randombytes_buf) {
const input = toBuffer(sodium.randombytes_buf(buffer.byteLength))
const output = toBuffer(buffer)
input.copy(output)
return buffer
}
console.warn('Missing implementation for globalThis.crypto.getRandomValues()')
return null
}
// so this is re-used instead of creating new one each rand64() call
const tmp = new Uint32Array(2)
/**
* Generate a random 64-bit number.
* @returns {BigInt} - A random 64-bit number.
*/
export function rand64 () {
getRandomValues(tmp)
return (BigInt(tmp[0]) << 32n) | BigInt(tmp[1])
}
/**
* Maximum total size of random bytes per page
*/
export const RANDOM_BYTES_QUOTA = 64 * 1024
/**
* Maximum total size for random bytes.
*/
export const MAX_RANDOM_BYTES = 0xFFFF_FFFF_FFFF
/**
* Maximum total amount of allocated per page of bytes (max/quota)
*/
export const MAX_RANDOM_BYTES_PAGES = MAX_RANDOM_BYTES / RANDOM_BYTES_QUOTA
// note: should it do Math.ceil() / Math.round()?
/**
* Generate `size` random bytes.
* @param {number} size - The number of bytes to generate. The size must not be larger than 2**31 - 1.
* @returns {Buffer} - A promise that resolves with an instance of socket.Buffer with random bytes.
*/
export function randomBytes (size) {
const buffers = []
if (size < 0 || size >= MAX_RANDOM_BYTES || !Number.isInteger(size)) {
throw Object.assign(new RangeError(
`The value of "size" is out of range. It must be >= 0 && <= ${MAX_RANDOM_BYTES}. ` +
`Received ${size}`
), {
code: 'ERR_OUT_OF_RANGE'
})
}
do {
const length = size > RANDOM_BYTES_QUOTA ? RANDOM_BYTES_QUOTA : size
const bytes = getRandomValues(new Int8Array(length))
buffers.push(toBuffer(bytes))
size = Math.max(0, size - RANDOM_BYTES_QUOTA)
} while (size > 0)
return Buffer.concat(buffers)
}
/**
* @param {string} algorithm - `SHA-1` | `SHA-256` | `SHA-384` | `SHA-512`
* @param {Buffer | TypedArray | DataView} message - An instance of socket.Buffer, TypedArray or Dataview.
* @returns {Promise<Buffer>} - A promise that resolves with an instance of socket.Buffer with the hash.
*/
export async function createDigest (algorithm, buf) {
return Buffer.from(await webcrypto.subtle.digest(algorithm, buf))
}
/**
* A murmur3 hash implementation based on https://github.com/jwerle/murmurhash.c
* that works on strings and `ArrayBuffer` views (typed arrays)
* @param {string|Uint8Array|ArrayBuffer} value
* @param {number=} [seed = 0]
* @return {number}
*/
export function murmur3 (value, seed = 0) {
let string = value
if (typeof string !== 'string') {
if (string instanceof ArrayBuffer) {
string = new Uint8Array(string)
}
if (ArrayBuffer.isView(string)) {
string = textDecoder.decode(string)
}
}
if (typeof string !== 'string') {
throw new TypeError(
'Expecting input to be a string, ArrayBuffer, or TypedArray'
)
}
let hash = seed
let len = string.length
const c1 = 0xcc9e2d51
const c2 = 0x1b873593
const r1 = 15
const r2 = 13
const m = 5
const n = 0xe6546b64
const remainder = len & 3
len = len - remainder
for (let i = 0; i < len; i += 4) {
let k = (
(string.charCodeAt(i) & 0xff) |
((string.charCodeAt(i + 1) & 0xff) << 8) |
((string.charCodeAt(i + 2) & 0xff) << 16) |
((string.charCodeAt(i + 3) & 0xff) << 24)
)
k = (k * c1) & 0xffffffff
k = (k << r1) | (k >>> (32 - r1))
k = (k * c2) & 0xffffffff
hash ^= k
hash = ((hash << r2) | (hash >>> (32 - r2))) * m + n
hash = hash & 0xffffffff
}
let k = 0
switch (remainder) {
case 3:
k ^= (string.charCodeAt(len + 2) & 0xff) << 16
case 2:
k ^= (string.charCodeAt(len + 1) & 0xff) << 8
case 1:
k ^= (string.charCodeAt(len) & 0xff)
k = (k * c1) & 0xffffffff
k = (k << r1) | (k >>> (32 - r1))
k = (k * c2) & 0xffffffff
hash ^= k
}
hash ^= len
hash ^= (hash >>> 16)
hash = (hash * 0x85ebca6b) & 0xffffffff
hash ^= (hash >>> 13)
hash = (hash * 0xc2b2ae35) & 0xffffffff
hash ^= (hash >>> 16)
// convert to unsigned 32-bit integer
return hash >>> 0
}
export default exports