one 2.6.19 → 2.6.21
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/cpp/HybridOneCrypto.cpp +327 -1
- package/cpp/HybridOneCrypto.hpp +8 -0
- package/dist/cjs/index.cjs +25 -22
- package/dist/cjs/index.native.cjs +25 -22
- package/dist/cjs/index.native.js +25 -22
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/one.cjs +1 -0
- package/dist/cjs/one.native.cjs +1 -0
- package/dist/cjs/one.native.js +1 -0
- package/dist/cjs/one.native.js.map +1 -1
- package/dist/cjs/platform/crypto/aes.cjs +259 -0
- package/dist/cjs/platform/crypto/aes.native.cjs +343 -0
- package/dist/cjs/platform/crypto/aes.native.js +345 -0
- package/dist/cjs/platform/crypto/aes.native.js.map +1 -0
- package/dist/cjs/platform/crypto/bytes.cjs +132 -0
- package/dist/cjs/platform/crypto/bytes.native.cjs +175 -0
- package/dist/cjs/platform/crypto/bytes.native.js +177 -0
- package/dist/cjs/platform/crypto/bytes.native.js.map +1 -0
- package/dist/cjs/platform/crypto/digest-api.cjs +65 -0
- package/dist/cjs/platform/crypto/digest-api.native.cjs +69 -0
- package/dist/cjs/platform/crypto/digest-api.native.js +71 -0
- package/dist/cjs/platform/crypto/digest-api.native.js.map +1 -0
- package/dist/cjs/platform/crypto/digest.cjs +26 -15
- package/dist/cjs/platform/crypto/digest.native.cjs +26 -15
- package/dist/cjs/platform/crypto/digest.native.js +26 -15
- package/dist/cjs/platform/crypto/digest.native.js.map +1 -1
- package/dist/cjs/platform/crypto/index.cjs +19 -1
- package/dist/cjs/platform/crypto/index.native.cjs +39 -1
- package/dist/cjs/platform/crypto/index.native.js +39 -1
- package/dist/cjs/platform/crypto/index.native.js.map +1 -1
- package/dist/cjs/platform/crypto/types.cjs +16 -0
- package/dist/cjs/platform/crypto/types.native.cjs +18 -0
- package/dist/cjs/platform/crypto/types.native.js +20 -0
- package/dist/cjs/platform/crypto/types.native.js.map +1 -0
- package/dist/cjs/platform/crypto/web.cjs +73 -0
- package/dist/cjs/platform/extras.cjs +4 -0
- package/dist/cjs/platform/extras.native.cjs +4 -0
- package/dist/cjs/platform/extras.native.js +4 -0
- package/dist/cjs/platform/extras.native.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.native.js +2 -2
- package/dist/esm/one.mjs +2 -1
- package/dist/esm/one.mjs.map +1 -1
- package/dist/esm/one.native.js +2 -1
- package/dist/esm/one.native.js.map +1 -1
- package/dist/esm/platform/crypto/aes.mjs +236 -0
- package/dist/esm/platform/crypto/aes.mjs.map +1 -0
- package/dist/esm/platform/crypto/aes.native.js +318 -0
- package/dist/esm/platform/crypto/aes.native.js.map +1 -0
- package/dist/esm/platform/crypto/bytes.mjs +104 -0
- package/dist/esm/platform/crypto/bytes.mjs.map +1 -0
- package/dist/esm/platform/crypto/bytes.native.js +145 -0
- package/dist/esm/platform/crypto/bytes.native.js.map +1 -0
- package/dist/esm/platform/crypto/digest-api.mjs +42 -0
- package/dist/esm/platform/crypto/digest-api.mjs.map +1 -0
- package/dist/esm/platform/crypto/digest-api.native.js +44 -0
- package/dist/esm/platform/crypto/digest-api.native.js.map +1 -0
- package/dist/esm/platform/crypto/digest.mjs +23 -15
- package/dist/esm/platform/crypto/digest.mjs.map +1 -1
- package/dist/esm/platform/crypto/digest.native.js +23 -15
- package/dist/esm/platform/crypto/digest.native.js.map +1 -1
- package/dist/esm/platform/crypto/index.mjs +14 -1
- package/dist/esm/platform/crypto/index.mjs.map +1 -1
- package/dist/esm/platform/crypto/index.native.js +34 -1
- package/dist/esm/platform/crypto/index.native.js.map +1 -1
- package/dist/esm/platform/crypto/types.mjs +0 -0
- package/dist/esm/platform/crypto/types.native.js +0 -0
- package/dist/esm/platform/crypto/web.mjs +50 -0
- package/dist/esm/platform/crypto/web.mjs.map +1 -0
- package/dist/esm/platform/extras.mjs +2 -1
- package/dist/esm/platform/extras.native.js +2 -1
- package/nitrogen/generated/shared/c++/HybridOneCryptoSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridOneCryptoSpec.hpp +4 -1
- package/package.json +12 -12
- package/src/index.ts +1 -0
- package/src/one.ts +3 -0
- package/src/platform/crypto/aes.ts +345 -0
- package/src/platform/crypto/bytes.ts +120 -0
- package/src/platform/crypto/digest-api.ts +50 -0
- package/src/platform/crypto/digest.ts +24 -15
- package/src/platform/crypto/index.native.ts +68 -0
- package/src/platform/crypto/index.ts +31 -0
- package/src/platform/crypto/types.ts +64 -0
- package/src/platform/crypto/web.ts +69 -0
- package/src/platform/extras.ts +15 -0
- package/src/platform/specs/OneCrypto.nitro.ts +19 -0
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/one.d.ts +2 -1
- package/types/one.d.ts.map +1 -1
- package/types/platform/crypto/aes.d.ts +211 -0
- package/types/platform/crypto/aes.d.ts.map +1 -0
- package/types/platform/crypto/bytes.d.ts +8 -0
- package/types/platform/crypto/bytes.d.ts.map +1 -0
- package/types/platform/crypto/digest-api.d.ts +9 -0
- package/types/platform/crypto/digest-api.d.ts.map +1 -0
- package/types/platform/crypto/digest.d.ts +5 -0
- package/types/platform/crypto/digest.d.ts.map +1 -1
- package/types/platform/crypto/index.d.ts +127 -0
- package/types/platform/crypto/index.d.ts.map +1 -1
- package/types/platform/crypto/index.native.d.ts +127 -0
- package/types/platform/crypto/index.native.d.ts.map +1 -1
- package/types/platform/crypto/types.d.ts +43 -0
- package/types/platform/crypto/types.d.ts.map +1 -0
- package/types/platform/crypto/web.d.ts +5 -0
- package/types/platform/crypto/web.d.ts.map +1 -0
- package/types/platform/extras.d.ts +2 -0
- package/types/platform/extras.d.ts.map +1 -1
- package/types/platform/specs/OneCrypto.nitro.d.ts +2 -0
- package/types/platform/specs/OneCrypto.nitro.d.ts.map +1 -1
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { base64ToBytes, bytesToBase64, bytesToHex, concatBytes, exactBytes, hexToBytes } from './bytes'
|
|
2
|
+
import type {
|
|
3
|
+
AESDecryptOptions,
|
|
4
|
+
AESEncryptOptions,
|
|
5
|
+
AESKeySize,
|
|
6
|
+
AESSealedDataConfig,
|
|
7
|
+
BinaryInput,
|
|
8
|
+
} from './types'
|
|
9
|
+
|
|
10
|
+
export type { AESDecryptOptions, AESEncryptOptions, AESKeySize, AESSealedDataConfig, BinaryInput }
|
|
11
|
+
|
|
12
|
+
// expo-crypto's AES-GCM api over an injected engine: native is the shared
|
|
13
|
+
// C++ hybrid object, web is SubtleCrypto. seal returns ciphertext with the
|
|
14
|
+
// tag appended; open takes that form. everything else (key and sealed-data
|
|
15
|
+
// handling, encodings, validation) is shared here so both entries behave
|
|
16
|
+
// identically and the logic unit-tests in node.
|
|
17
|
+
export interface AesEngine {
|
|
18
|
+
seal(
|
|
19
|
+
key: Uint8Array<ArrayBuffer>,
|
|
20
|
+
iv: Uint8Array<ArrayBuffer>,
|
|
21
|
+
plaintext: Uint8Array<ArrayBuffer>,
|
|
22
|
+
additionalData: Uint8Array<ArrayBuffer> | undefined,
|
|
23
|
+
tagLength: number
|
|
24
|
+
): Promise<Uint8Array<ArrayBuffer>>
|
|
25
|
+
open(
|
|
26
|
+
key: Uint8Array<ArrayBuffer>,
|
|
27
|
+
iv: Uint8Array<ArrayBuffer>,
|
|
28
|
+
ciphertextWithTag: Uint8Array<ArrayBuffer>,
|
|
29
|
+
additionalData: Uint8Array<ArrayBuffer> | undefined,
|
|
30
|
+
tagLength: number
|
|
31
|
+
): Promise<Uint8Array<ArrayBuffer>>
|
|
32
|
+
random(bytes: Uint8Array<ArrayBuffer>): void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const KEY_SIZES: readonly AESKeySize[] = [128, 192, 256]
|
|
36
|
+
const TAG_LENGTHS: readonly number[] = [16, 15, 14, 13, 12, 8, 4]
|
|
37
|
+
const DEFAULT_IV_LENGTH = 12
|
|
38
|
+
const DEFAULT_TAG_LENGTH = 16
|
|
39
|
+
|
|
40
|
+
export function assertTagLength(tagLength: number, verb: string): void {
|
|
41
|
+
if (!TAG_LENGTHS.includes(tagLength)) {
|
|
42
|
+
throw new Error(`${verb}: tagLength must be one of: ${TAG_LENGTHS.join(', ')}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function toBytes(input: BinaryInput, verb: string): Uint8Array<ArrayBuffer> {
|
|
47
|
+
if (typeof input === 'string') return base64ToBytes(input, verb)
|
|
48
|
+
if (input instanceof ArrayBuffer) return new Uint8Array(input.slice(0))
|
|
49
|
+
if (ArrayBuffer.isView(input) && input.buffer instanceof ArrayBuffer) {
|
|
50
|
+
return exactBytes(new Uint8Array(input.buffer, input.byteOffset, input.byteLength))
|
|
51
|
+
}
|
|
52
|
+
throw new TypeError(`${verb}: expected a base64 string, Uint8Array, or ArrayBuffer`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type SealedParts = {
|
|
56
|
+
iv: Uint8Array<ArrayBuffer>
|
|
57
|
+
ciphertext: Uint8Array<ArrayBuffer>
|
|
58
|
+
tag: Uint8Array<ArrayBuffer>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sealedParts = new WeakMap<object, SealedParts>()
|
|
62
|
+
const keyBytes = new WeakMap<object, Uint8Array<ArrayBuffer>>()
|
|
63
|
+
|
|
64
|
+
function encodeParts(bytes: Uint8Array, encoding: 'base64' | 'bytes' | undefined) {
|
|
65
|
+
return encoding === 'base64' ? bytesToBase64(bytes) : exactBytes(bytes)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createAesApi(engine: AesEngine) {
|
|
69
|
+
class AESEncryptionKey {
|
|
70
|
+
readonly size: AESKeySize
|
|
71
|
+
|
|
72
|
+
// construct through generate or import; direct construction still
|
|
73
|
+
// validates, so a key object can never hold a non-AES length.
|
|
74
|
+
constructor(bytes: Uint8Array<ArrayBuffer>) {
|
|
75
|
+
if (!KEY_SIZES.includes((bytes.length * 8) as AESKeySize)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Crypto.AESEncryptionKey: key must be 128, 192, or 256 bits, got ${bytes.length * 8}`
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
this.size = (bytes.length * 8) as AESKeySize
|
|
81
|
+
keyBytes.set(this, exactBytes(bytes))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static async generate(size: AESKeySize = 256): Promise<AESEncryptionKey> {
|
|
85
|
+
if (!KEY_SIZES.includes(size)) {
|
|
86
|
+
throw new Error(`Crypto.AESEncryptionKey.generate: size must be one of: ${KEY_SIZES.join(', ')}`)
|
|
87
|
+
}
|
|
88
|
+
const bytes = new Uint8Array(size / 8)
|
|
89
|
+
engine.random(bytes)
|
|
90
|
+
return new AESEncryptionKey(bytes)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static async import(bytes: Uint8Array): Promise<AESEncryptionKey>
|
|
94
|
+
static async import(encoded: string, encoding: 'hex' | 'base64'): Promise<AESEncryptionKey>
|
|
95
|
+
static async import(
|
|
96
|
+
input: Uint8Array | string,
|
|
97
|
+
encoding?: 'hex' | 'base64'
|
|
98
|
+
): Promise<AESEncryptionKey> {
|
|
99
|
+
const verb = 'Crypto.AESEncryptionKey.import'
|
|
100
|
+
let bytes: Uint8Array<ArrayBuffer>
|
|
101
|
+
if (typeof input === 'string') {
|
|
102
|
+
if (encoding === 'hex') bytes = hexToBytes(input, verb)
|
|
103
|
+
else if (encoding === 'base64') bytes = base64ToBytes(input, verb)
|
|
104
|
+
else throw new Error(`${verb}: encoding must be 'hex' or 'base64'`)
|
|
105
|
+
} else if (input instanceof Uint8Array) {
|
|
106
|
+
bytes = exactBytes(input)
|
|
107
|
+
} else {
|
|
108
|
+
throw new TypeError(`${verb}: expected bytes or an encoded string`)
|
|
109
|
+
}
|
|
110
|
+
if (!KEY_SIZES.includes((bytes.length * 8) as AESKeySize)) {
|
|
111
|
+
throw new Error(`${verb}: key must be 128, 192, or 256 bits, got ${bytes.length * 8}`)
|
|
112
|
+
}
|
|
113
|
+
return new AESEncryptionKey(bytes)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async bytes(): Promise<Uint8Array<ArrayBuffer>> {
|
|
117
|
+
return exactBytes(keyBytes.get(this)!)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async encoded(encoding: 'hex' | 'base64'): Promise<string> {
|
|
121
|
+
const bytes = keyBytes.get(this)!
|
|
122
|
+
if (encoding === 'hex') return bytesToHex(bytes)
|
|
123
|
+
if (encoding === 'base64') return bytesToBase64(bytes)
|
|
124
|
+
throw new Error(`Crypto.AESEncryptionKey.encoded: encoding must be 'hex' or 'base64'`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class AESSealedData {
|
|
129
|
+
// construct through fromCombined or fromParts. direct construction still
|
|
130
|
+
// validates, so sealed data can never carry an empty iv or a tag gcm
|
|
131
|
+
// does not allow.
|
|
132
|
+
constructor(parts: SealedParts) {
|
|
133
|
+
if (parts.iv.length === 0) throw new Error('Crypto.AESSealedData: iv must not be empty')
|
|
134
|
+
assertTagLength(parts.tag.length, 'Crypto.AESSealedData')
|
|
135
|
+
sealedParts.set(this, parts)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static fromCombined(combined: BinaryInput, config: AESSealedDataConfig = {}): AESSealedData {
|
|
139
|
+
const verb = 'Crypto.AESSealedData.fromCombined'
|
|
140
|
+
const ivLength = config.ivLength ?? DEFAULT_IV_LENGTH
|
|
141
|
+
const tagLength = config.tagLength ?? DEFAULT_TAG_LENGTH
|
|
142
|
+
assertTagLength(tagLength, verb)
|
|
143
|
+
if (!Number.isInteger(ivLength) || ivLength <= 0) {
|
|
144
|
+
throw new Error(`${verb}: ivLength must be a positive integer`)
|
|
145
|
+
}
|
|
146
|
+
const bytes = toBytes(combined, verb)
|
|
147
|
+
if (bytes.length < ivLength + tagLength) {
|
|
148
|
+
throw new Error(`${verb}: combined data is shorter than its iv and tag`)
|
|
149
|
+
}
|
|
150
|
+
return new AESSealedData({
|
|
151
|
+
iv: exactBytes(bytes.subarray(0, ivLength)),
|
|
152
|
+
ciphertext: exactBytes(bytes.subarray(ivLength, bytes.length - tagLength)),
|
|
153
|
+
tag: exactBytes(bytes.subarray(bytes.length - tagLength)),
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static fromParts(iv: BinaryInput, ciphertext: BinaryInput, tag: BinaryInput): AESSealedData
|
|
158
|
+
static fromParts(
|
|
159
|
+
iv: BinaryInput,
|
|
160
|
+
ciphertextWithTag: BinaryInput,
|
|
161
|
+
tagLength?: number
|
|
162
|
+
): AESSealedData
|
|
163
|
+
static fromParts(
|
|
164
|
+
ivInput: BinaryInput,
|
|
165
|
+
ciphertextInput: BinaryInput,
|
|
166
|
+
tagOrLength?: BinaryInput | number
|
|
167
|
+
): AESSealedData {
|
|
168
|
+
const verb = 'Crypto.AESSealedData.fromParts'
|
|
169
|
+
const iv = toBytes(ivInput, verb)
|
|
170
|
+
const ciphertext = toBytes(ciphertextInput, verb)
|
|
171
|
+
if (tagOrLength === undefined || typeof tagOrLength === 'number') {
|
|
172
|
+
const tagLength = tagOrLength ?? DEFAULT_TAG_LENGTH
|
|
173
|
+
assertTagLength(tagLength, verb)
|
|
174
|
+
if (ciphertext.length < tagLength) {
|
|
175
|
+
throw new Error(`${verb}: ciphertext is shorter than its tag`)
|
|
176
|
+
}
|
|
177
|
+
return new AESSealedData({
|
|
178
|
+
iv,
|
|
179
|
+
ciphertext: exactBytes(ciphertext.subarray(0, ciphertext.length - tagLength)),
|
|
180
|
+
tag: exactBytes(ciphertext.subarray(ciphertext.length - tagLength)),
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
const tag = toBytes(tagOrLength, verb)
|
|
184
|
+
assertTagLength(tag.length, verb)
|
|
185
|
+
return new AESSealedData({ iv, ciphertext, tag })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// internal: aesEncrypt builds sealed data from engine output without
|
|
189
|
+
// going through the public parsers (which would just re-split it).
|
|
190
|
+
static fromSeal(iv: Uint8Array<ArrayBuffer>, ciphertextWithTag: Uint8Array, tagLength: number) {
|
|
191
|
+
return new AESSealedData({
|
|
192
|
+
iv,
|
|
193
|
+
ciphertext: exactBytes(ciphertextWithTag.subarray(0, ciphertextWithTag.length - tagLength)),
|
|
194
|
+
tag: exactBytes(ciphertextWithTag.subarray(ciphertextWithTag.length - tagLength)),
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get ivSize(): number {
|
|
199
|
+
return sealedParts.get(this)!.iv.length
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get tagSize(): number {
|
|
203
|
+
return sealedParts.get(this)!.tag.length
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get combinedSize(): number {
|
|
207
|
+
const parts = sealedParts.get(this)!
|
|
208
|
+
return parts.iv.length + parts.ciphertext.length + parts.tag.length
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async iv(encoding: 'base64'): Promise<string>
|
|
212
|
+
async iv(encoding?: 'bytes'): Promise<Uint8Array<ArrayBuffer>>
|
|
213
|
+
async iv(encoding?: 'base64' | 'bytes') {
|
|
214
|
+
return encodeParts(sealedParts.get(this)!.iv, encoding)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async tag(encoding: 'base64'): Promise<string>
|
|
218
|
+
async tag(encoding?: 'bytes'): Promise<Uint8Array<ArrayBuffer>>
|
|
219
|
+
async tag(encoding?: 'base64' | 'bytes') {
|
|
220
|
+
return encodeParts(sealedParts.get(this)!.tag, encoding)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async ciphertext(options: {
|
|
224
|
+
encoding: 'base64'
|
|
225
|
+
includeTag: boolean
|
|
226
|
+
}): Promise<string>
|
|
227
|
+
async ciphertext(options: {
|
|
228
|
+
encoding: 'bytes'
|
|
229
|
+
includeTag: boolean
|
|
230
|
+
}): Promise<Uint8Array<ArrayBuffer>>
|
|
231
|
+
async ciphertext(options: { encoding: 'base64' | 'bytes'; includeTag: boolean }) {
|
|
232
|
+
const parts = sealedParts.get(this)!
|
|
233
|
+
const bytes = options?.includeTag
|
|
234
|
+
? concatBytes([parts.ciphertext, parts.tag])
|
|
235
|
+
: parts.ciphertext
|
|
236
|
+
return encodeParts(bytes, options?.encoding)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async combined(encoding: 'base64'): Promise<string>
|
|
240
|
+
async combined(encoding?: 'bytes'): Promise<Uint8Array<ArrayBuffer>>
|
|
241
|
+
async combined(encoding?: 'base64' | 'bytes') {
|
|
242
|
+
const parts = sealedParts.get(this)!
|
|
243
|
+
return encodeParts(concatBytes([parts.iv, parts.ciphertext, parts.tag]), encoding)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function keyMaterial(key: AESEncryptionKey, verb: string): Promise<Uint8Array<ArrayBuffer>> {
|
|
248
|
+
const own = keyBytes.get(key)
|
|
249
|
+
if (own) return exactBytes(own)
|
|
250
|
+
// a key minted by the other entry in the same bundle: read it back
|
|
251
|
+
// through its public shape instead of rejecting a foreign instance.
|
|
252
|
+
if (key && typeof key.bytes === 'function') return exactBytes(await key.bytes())
|
|
253
|
+
throw new TypeError(`${verb}: expected an AESEncryptionKey`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function partsOf(sealed: AESSealedData, verb: string): Promise<SealedParts> {
|
|
257
|
+
const own = sealedParts.get(sealed)
|
|
258
|
+
if (own) {
|
|
259
|
+
return {
|
|
260
|
+
iv: exactBytes(own.iv),
|
|
261
|
+
ciphertext: exactBytes(own.ciphertext),
|
|
262
|
+
tag: exactBytes(own.tag),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (sealed && typeof sealed.iv === 'function' && typeof sealed.combined === 'function') {
|
|
266
|
+
const iv = exactBytes(await sealed.iv())
|
|
267
|
+
const tag = exactBytes(await sealed.tag())
|
|
268
|
+
const ciphertext = exactBytes(await sealed.ciphertext({ encoding: 'bytes', includeTag: false }))
|
|
269
|
+
return { iv, ciphertext, tag }
|
|
270
|
+
}
|
|
271
|
+
throw new TypeError(`${verb}: expected an AESSealedData`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function aesEncrypt(
|
|
275
|
+
plaintext: BinaryInput,
|
|
276
|
+
key: AESEncryptionKey,
|
|
277
|
+
options: AESEncryptOptions = {}
|
|
278
|
+
): Promise<AESSealedData> {
|
|
279
|
+
const verb = 'Crypto.aesEncrypt'
|
|
280
|
+
const tagLength = options.tagLength ?? DEFAULT_TAG_LENGTH
|
|
281
|
+
assertTagLength(tagLength, verb)
|
|
282
|
+
let iv: Uint8Array<ArrayBuffer>
|
|
283
|
+
const nonce = options.nonce
|
|
284
|
+
if (nonce && 'bytes' in nonce && nonce.bytes !== undefined) {
|
|
285
|
+
iv = toBytes(nonce.bytes, verb)
|
|
286
|
+
if (iv.length === 0) throw new Error(`${verb}: nonce bytes must not be empty`)
|
|
287
|
+
} else {
|
|
288
|
+
const length = nonce && 'length' in nonce && nonce.length !== undefined ? nonce.length : DEFAULT_IV_LENGTH
|
|
289
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
290
|
+
throw new Error(`${verb}: nonce length must be a positive integer`)
|
|
291
|
+
}
|
|
292
|
+
iv = new Uint8Array(length)
|
|
293
|
+
engine.random(iv)
|
|
294
|
+
}
|
|
295
|
+
const additionalData =
|
|
296
|
+
options.additionalData === undefined ? undefined : toBytes(options.additionalData, verb)
|
|
297
|
+
const plainBytes = toBytes(plaintext, verb)
|
|
298
|
+
const sealed = await engine.seal(
|
|
299
|
+
await keyMaterial(key, verb),
|
|
300
|
+
iv,
|
|
301
|
+
plainBytes,
|
|
302
|
+
additionalData,
|
|
303
|
+
tagLength
|
|
304
|
+
)
|
|
305
|
+
// the apple engine always seals with a 16-byte tag, as expo-crypto does
|
|
306
|
+
// there: gcm ciphertext is plaintext-sized, so the sealed length says
|
|
307
|
+
// which tag the platform actually produced.
|
|
308
|
+
const actualTag = sealed.length - plainBytes.length
|
|
309
|
+
return AESSealedData.fromSeal(iv, sealed, actualTag > 0 ? actualTag : tagLength)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function aesDecrypt(
|
|
313
|
+
sealedData: AESSealedData,
|
|
314
|
+
key: AESEncryptionKey,
|
|
315
|
+
options: AESDecryptOptions & { output: 'base64' }
|
|
316
|
+
): Promise<string>
|
|
317
|
+
async function aesDecrypt(
|
|
318
|
+
sealedData: AESSealedData,
|
|
319
|
+
key: AESEncryptionKey,
|
|
320
|
+
options?: AESDecryptOptions
|
|
321
|
+
): Promise<Uint8Array<ArrayBuffer>>
|
|
322
|
+
async function aesDecrypt(
|
|
323
|
+
sealedData: AESSealedData,
|
|
324
|
+
key: AESEncryptionKey,
|
|
325
|
+
options: AESDecryptOptions = {}
|
|
326
|
+
): Promise<string | Uint8Array<ArrayBuffer>> {
|
|
327
|
+
const verb = 'Crypto.aesDecrypt'
|
|
328
|
+
const parts = await partsOf(sealedData, verb)
|
|
329
|
+
const additionalData =
|
|
330
|
+
options.additionalData === undefined ? undefined : toBytes(options.additionalData, verb)
|
|
331
|
+
const plaintext = await engine.open(
|
|
332
|
+
await keyMaterial(key, verb),
|
|
333
|
+
parts.iv,
|
|
334
|
+
concatBytes([parts.ciphertext, parts.tag]),
|
|
335
|
+
additionalData,
|
|
336
|
+
parts.tag.length
|
|
337
|
+
)
|
|
338
|
+
return options.output === 'base64' ? bytesToBase64(plaintext) : plaintext
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { AESEncryptionKey, AESSealedData, aesEncrypt, aesDecrypt }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export type AESEncryptionKey = InstanceType<ReturnType<typeof createAesApi>['AESEncryptionKey']>
|
|
345
|
+
export type AESSealedData = InstanceType<ReturnType<typeof createAesApi>['AESSealedData']>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// pure byte encodings for the crypto api: hex, base64 and utf-8 without
|
|
2
|
+
// depending on host globals (Hermes has no TextEncoder and atob/btoa are
|
|
3
|
+
// string-in/string-out with latin1 semantics, so binary hosted there would
|
|
4
|
+
// corrupt bytes above 127).
|
|
5
|
+
|
|
6
|
+
const HEX_DIGITS = '0123456789abcdef'
|
|
7
|
+
|
|
8
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
9
|
+
let out = ''
|
|
10
|
+
for (const byte of bytes) out += HEX_DIGITS[byte >> 4] + HEX_DIGITS[byte & 15]
|
|
11
|
+
return out
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hexToBytes(hex: string, verb: string): Uint8Array<ArrayBuffer> {
|
|
15
|
+
if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) {
|
|
16
|
+
throw new Error(`${verb}: expected an even-length hex string`)
|
|
17
|
+
}
|
|
18
|
+
const out = new Uint8Array(hex.length / 2)
|
|
19
|
+
for (let index = 0; index < out.length; index++) {
|
|
20
|
+
out[index] = parseInt(hex.slice(index * 2, index * 2 + 2), 16)
|
|
21
|
+
}
|
|
22
|
+
return out
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
26
|
+
const BASE64_VALUES = new Int16Array(128).fill(-1)
|
|
27
|
+
for (let index = 0; index < BASE64_ALPHABET.length; index++) {
|
|
28
|
+
BASE64_VALUES[BASE64_ALPHABET.charCodeAt(index)] = index
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function bytesToBase64(bytes: Uint8Array): string {
|
|
32
|
+
let out = ''
|
|
33
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
34
|
+
const first = bytes[index]
|
|
35
|
+
const second = bytes[index + 1]
|
|
36
|
+
const third = bytes[index + 2]
|
|
37
|
+
out += BASE64_ALPHABET[first >> 2]
|
|
38
|
+
out += BASE64_ALPHABET[((first & 3) << 4) | ((second ?? 0) >> 4)]
|
|
39
|
+
out += second === undefined ? '=' : BASE64_ALPHABET[((second & 15) << 2) | ((third ?? 0) >> 6)]
|
|
40
|
+
out += third === undefined ? '=' : BASE64_ALPHABET[third & 63]
|
|
41
|
+
}
|
|
42
|
+
return out
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// strict: standard alphabet, length a multiple of four, padding only at the
|
|
46
|
+
// end. anything else throws instead of decoding to a best guess.
|
|
47
|
+
export function base64ToBytes(base64: string, verb: string): Uint8Array<ArrayBuffer> {
|
|
48
|
+
if (base64.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(base64)) {
|
|
49
|
+
throw new Error(`${verb}: expected a base64 string`)
|
|
50
|
+
}
|
|
51
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0
|
|
52
|
+
if (base64.slice(0, base64.length - padding).includes('=')) {
|
|
53
|
+
throw new Error(`${verb}: expected a base64 string`)
|
|
54
|
+
}
|
|
55
|
+
const out = new Uint8Array((base64.length / 4) * 3 - padding)
|
|
56
|
+
let at = 0
|
|
57
|
+
for (let index = 0; index < base64.length; index += 4) {
|
|
58
|
+
const values = [0, 1, 2, 3].map((offset) => {
|
|
59
|
+
const code = base64.charCodeAt(index + offset)
|
|
60
|
+
return code === 61 ? 0 : BASE64_VALUES[code]
|
|
61
|
+
})
|
|
62
|
+
if (values.some((value) => value < 0)) throw new Error(`${verb}: expected a base64 string`)
|
|
63
|
+
if (at < out.length) out[at++] = (values[0] << 2) | (values[1] >> 4)
|
|
64
|
+
if (at < out.length) out[at++] = ((values[1] & 15) << 4) | (values[2] >> 2)
|
|
65
|
+
if (at < out.length) out[at++] = ((values[2] & 3) << 6) | values[3]
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// utf-8 encoding for digestString. unpaired surrogates encode as the
|
|
71
|
+
// replacement character, matching TextEncoder.
|
|
72
|
+
export function utf8Encode(text: string): Uint8Array<ArrayBuffer> {
|
|
73
|
+
const bytes: number[] = []
|
|
74
|
+
for (let index = 0; index < text.length; index++) {
|
|
75
|
+
let code = text.charCodeAt(index)
|
|
76
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
77
|
+
const next = text.charCodeAt(index + 1)
|
|
78
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
79
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00)
|
|
80
|
+
index++
|
|
81
|
+
} else {
|
|
82
|
+
code = 0xfffd
|
|
83
|
+
}
|
|
84
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
85
|
+
code = 0xfffd
|
|
86
|
+
}
|
|
87
|
+
if (code < 0x80) {
|
|
88
|
+
bytes.push(code)
|
|
89
|
+
} else if (code < 0x800) {
|
|
90
|
+
bytes.push(0xc0 | (code >> 6), 0x80 | (code & 63))
|
|
91
|
+
} else if (code < 0x10000) {
|
|
92
|
+
bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 63), 0x80 | (code & 63))
|
|
93
|
+
} else {
|
|
94
|
+
bytes.push(
|
|
95
|
+
0xf0 | (code >> 18),
|
|
96
|
+
0x80 | ((code >> 12) & 63),
|
|
97
|
+
0x80 | ((code >> 6) & 63),
|
|
98
|
+
0x80 | (code & 63)
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new Uint8Array(bytes)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// a fresh exact-size copy: what native receives, so a caller mutating its
|
|
106
|
+
// view after the call cannot change bytes in flight.
|
|
107
|
+
export function exactBytes(view: Uint8Array): Uint8Array<ArrayBuffer> {
|
|
108
|
+
return new Uint8Array(view)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function concatBytes(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
|
|
112
|
+
const length = parts.reduce((sum, part) => sum + part.length, 0)
|
|
113
|
+
const out = new Uint8Array(length)
|
|
114
|
+
let at = 0
|
|
115
|
+
for (const part of parts) {
|
|
116
|
+
out.set(part, at)
|
|
117
|
+
at += part.length
|
|
118
|
+
}
|
|
119
|
+
return out
|
|
120
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { bytesToBase64, bytesToHex, utf8Encode } from './bytes'
|
|
2
|
+
import { bufferSourceRange, type DigestSource } from './digest'
|
|
3
|
+
import type { DigestAlgorithm, DigestStringOptions } from './types'
|
|
4
|
+
|
|
5
|
+
// expo-crypto's digest contract over the shared digest source: algorithm
|
|
6
|
+
// names are Expo's strings, unknown ones throw at the js boundary on web and
|
|
7
|
+
// native alike, and digestString hashes the string's utf-8 bytes.
|
|
8
|
+
export const DIGEST_ALGORITHMS: readonly DigestAlgorithm[] = [
|
|
9
|
+
'MD5',
|
|
10
|
+
'SHA-1',
|
|
11
|
+
'SHA-256',
|
|
12
|
+
'SHA-384',
|
|
13
|
+
'SHA-512',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export function assertDigestAlgorithm(algorithm: DigestAlgorithm, verb: string): void {
|
|
17
|
+
if (!DIGEST_ALGORITHMS.includes(algorithm)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`${verb}: unknown algorithm ${JSON.stringify(algorithm)}. Expected one of: ${DIGEST_ALGORITHMS.join(', ')}`
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createDigestApi(source: DigestSource) {
|
|
25
|
+
async function digest(algorithm: DigestAlgorithm, data: BufferSource): Promise<ArrayBuffer> {
|
|
26
|
+
assertDigestAlgorithm(algorithm, 'Crypto.digest')
|
|
27
|
+
const { buffer, offset, length } = bufferSourceRange(data, 'Crypto.digest')
|
|
28
|
+
return source(algorithm, buffer, offset, length)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function digestString(
|
|
32
|
+
algorithm: DigestAlgorithm,
|
|
33
|
+
data: string,
|
|
34
|
+
options: DigestStringOptions = {}
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
assertDigestAlgorithm(algorithm, 'Crypto.digestString')
|
|
37
|
+
if (typeof data !== 'string') {
|
|
38
|
+
throw new TypeError('Crypto.digestString: data must be a string')
|
|
39
|
+
}
|
|
40
|
+
const encoding = options?.encoding ?? 'hex'
|
|
41
|
+
if (encoding !== 'hex' && encoding !== 'base64') {
|
|
42
|
+
throw new Error(`Crypto.digestString: encoding must be 'hex' or 'base64'`)
|
|
43
|
+
}
|
|
44
|
+
const bytes = utf8Encode(data)
|
|
45
|
+
const hashed = new Uint8Array(await source(algorithm, bytes.buffer, 0, bytes.length))
|
|
46
|
+
return encoding === 'base64' ? bytesToBase64(hashed) : bytesToHex(hashed)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { digest, digestString }
|
|
50
|
+
}
|
|
@@ -21,6 +21,29 @@ function namedError(name: string, message: string): Error {
|
|
|
21
21
|
return error
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// the exact byte range a BufferSource names, or a TypeError. detached
|
|
25
|
+
// buffers throw from the view construction, before any native call.
|
|
26
|
+
export function bufferSourceRange(
|
|
27
|
+
data: BufferSource,
|
|
28
|
+
verb: string
|
|
29
|
+
): { buffer: ArrayBuffer; offset: number; length: number } {
|
|
30
|
+
let buffer: ArrayBuffer
|
|
31
|
+
let offset = 0
|
|
32
|
+
let length: number
|
|
33
|
+
if (data instanceof ArrayBuffer) {
|
|
34
|
+
buffer = data
|
|
35
|
+
length = data.byteLength
|
|
36
|
+
} else if (ArrayBuffer.isView(data) && data.buffer instanceof ArrayBuffer) {
|
|
37
|
+
buffer = data.buffer
|
|
38
|
+
offset = data.byteOffset
|
|
39
|
+
length = data.byteLength
|
|
40
|
+
} else {
|
|
41
|
+
throw new TypeError(`${verb}: expected an ArrayBuffer or view.`)
|
|
42
|
+
}
|
|
43
|
+
new Uint8Array(buffer, offset, length)
|
|
44
|
+
return { buffer, offset, length }
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
export function installDigestPolyfill(
|
|
25
48
|
source: DigestSource,
|
|
26
49
|
target: DigestTarget = globalThis
|
|
@@ -31,21 +54,7 @@ export function installDigestPolyfill(
|
|
|
31
54
|
algorithm: AlgorithmIdentifier,
|
|
32
55
|
data: BufferSource
|
|
33
56
|
): Promise<ArrayBuffer> => {
|
|
34
|
-
|
|
35
|
-
let offset = 0
|
|
36
|
-
let length: number
|
|
37
|
-
if (data instanceof ArrayBuffer) {
|
|
38
|
-
buffer = data
|
|
39
|
-
length = data.byteLength
|
|
40
|
-
} else if (ArrayBuffer.isView(data) && data.buffer instanceof ArrayBuffer) {
|
|
41
|
-
buffer = data.buffer
|
|
42
|
-
offset = data.byteOffset
|
|
43
|
-
length = data.byteLength
|
|
44
|
-
} else {
|
|
45
|
-
throw new TypeError('crypto.subtle.digest: expected an ArrayBuffer or view.')
|
|
46
|
-
}
|
|
47
|
-
// reject detached buffers before entering native code, including empty views.
|
|
48
|
-
new Uint8Array(buffer, offset, length)
|
|
57
|
+
const { buffer, offset, length } = bufferSourceRange(data, 'crypto.subtle.digest')
|
|
49
58
|
let name: string
|
|
50
59
|
if (
|
|
51
60
|
algorithm !== null &&
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
import { createAesApi, type AesEngine } from './aes'
|
|
1
2
|
import { installDigestPolyfill } from './digest'
|
|
3
|
+
import { createDigestApi } from './digest-api'
|
|
2
4
|
import { NitroModules } from 'react-native-nitro-modules'
|
|
3
5
|
import type { OneCrypto } from '../specs/OneCrypto.nitro'
|
|
4
6
|
import { installCryptoPolyfill, type RandomSource } from './random'
|
|
7
|
+
import type { Crypto as CryptoApi } from './types'
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
AESDecryptOptions,
|
|
11
|
+
AESEncryptOptions,
|
|
12
|
+
AESKeySize,
|
|
13
|
+
AESSealedDataConfig,
|
|
14
|
+
BinaryInput,
|
|
15
|
+
CryptoApi,
|
|
16
|
+
DigestAlgorithm,
|
|
17
|
+
DigestEncoding,
|
|
18
|
+
DigestStringOptions,
|
|
19
|
+
GCMNonceParam,
|
|
20
|
+
} from './types'
|
|
21
|
+
export type { AESEncryptionKey as AESEncryptionKeyType, AESSealedData as AESSealedDataType } from './aes'
|
|
5
22
|
|
|
6
23
|
// secure random from the platform csprng (arc4random_buf on both) through the
|
|
7
24
|
// OneCrypto c++ hybrid object, created on first use and cached. bytes land in
|
|
@@ -25,6 +42,57 @@ const nativeSource: RandomSource = {
|
|
|
25
42
|
// lacks them. no-ops when the binary has no OneCrypto: installing a throwing
|
|
26
43
|
// source would only replace a clear missing-crypto error with a confusing
|
|
27
44
|
// one at the call site.
|
|
45
|
+
// the public One.Crypto api over the same hybrid object: throwing module
|
|
46
|
+
// access is right here, unlike installCrypto below. a caller asking for a
|
|
47
|
+
// digest or a seal must learn the binary predates it, not get a fake.
|
|
48
|
+
function required(): OneCrypto {
|
|
49
|
+
if (!NitroModules.hasHybridObject('OneCrypto')) {
|
|
50
|
+
throw new Error('Crypto needs a native build that includes one')
|
|
51
|
+
}
|
|
52
|
+
return native()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const aesEngine: AesEngine = {
|
|
56
|
+
async seal(key, iv, plaintext, additionalData, tagLength) {
|
|
57
|
+
const sealed = await required().aesEncrypt(
|
|
58
|
+
key.buffer,
|
|
59
|
+
iv.buffer,
|
|
60
|
+
plaintext.buffer,
|
|
61
|
+
additionalData?.buffer,
|
|
62
|
+
tagLength
|
|
63
|
+
)
|
|
64
|
+
return new Uint8Array(sealed)
|
|
65
|
+
},
|
|
66
|
+
async open(key, iv, ciphertextWithTag, additionalData, tagLength) {
|
|
67
|
+
const opened = await required().aesDecrypt(
|
|
68
|
+
key.buffer,
|
|
69
|
+
iv.buffer,
|
|
70
|
+
ciphertextWithTag.buffer,
|
|
71
|
+
additionalData?.buffer,
|
|
72
|
+
tagLength
|
|
73
|
+
)
|
|
74
|
+
return new Uint8Array(opened)
|
|
75
|
+
},
|
|
76
|
+
random(bytes) {
|
|
77
|
+
required().fillRandomBytes(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const aes = createAesApi(aesEngine)
|
|
82
|
+
const digestApi = createDigestApi((algorithm, buffer, offset, length) =>
|
|
83
|
+
required().digest(algorithm, buffer, offset, length)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
export const AESEncryptionKey = aes.AESEncryptionKey
|
|
87
|
+
export const AESSealedData = aes.AESSealedData
|
|
88
|
+
|
|
89
|
+
export const Crypto: CryptoApi = Object.freeze({
|
|
90
|
+
digest: digestApi.digest,
|
|
91
|
+
digestString: digestApi.digestString,
|
|
92
|
+
aesEncrypt: aes.aesEncrypt,
|
|
93
|
+
aesDecrypt: aes.aesDecrypt,
|
|
94
|
+
})
|
|
95
|
+
|
|
28
96
|
export function installCrypto(): void {
|
|
29
97
|
if (!NitroModules.hasHybridObject('OneCrypto')) {
|
|
30
98
|
return
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
import { createAesApi } from './aes'
|
|
2
|
+
import { createDigestApi } from './digest-api'
|
|
1
3
|
import { formatUuidV4, installCryptoPolyfill, type RandomSource } from './random'
|
|
4
|
+
import type { Crypto as CryptoApi } from './types'
|
|
5
|
+
import { webAesEngine, webDigestSource } from './web'
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
AESDecryptOptions,
|
|
9
|
+
AESEncryptOptions,
|
|
10
|
+
AESKeySize,
|
|
11
|
+
AESSealedDataConfig,
|
|
12
|
+
BinaryInput,
|
|
13
|
+
CryptoApi,
|
|
14
|
+
DigestAlgorithm,
|
|
15
|
+
DigestEncoding,
|
|
16
|
+
DigestStringOptions,
|
|
17
|
+
GCMNonceParam,
|
|
18
|
+
} from './types'
|
|
19
|
+
export type { AESEncryptionKey as AESEncryptionKeyType, AESSealedData as AESSealedDataType } from './aes'
|
|
2
20
|
|
|
3
21
|
// web entry. same shape as the native entry, but the random source is the
|
|
4
22
|
// platform crypto itself: installCrypto only fills genuinely missing
|
|
@@ -23,3 +41,16 @@ const source: RandomSource = {
|
|
|
23
41
|
export function installCrypto(): void {
|
|
24
42
|
installCryptoPolyfill(source)
|
|
25
43
|
}
|
|
44
|
+
|
|
45
|
+
const aes = createAesApi(webAesEngine)
|
|
46
|
+
const digestApi = createDigestApi(webDigestSource)
|
|
47
|
+
|
|
48
|
+
export const AESEncryptionKey = aes.AESEncryptionKey
|
|
49
|
+
export const AESSealedData = aes.AESSealedData
|
|
50
|
+
|
|
51
|
+
export const Crypto: CryptoApi = Object.freeze({
|
|
52
|
+
digest: digestApi.digest,
|
|
53
|
+
digestString: digestApi.digestString,
|
|
54
|
+
aesEncrypt: aes.aesEncrypt,
|
|
55
|
+
aesDecrypt: aes.aesDecrypt,
|
|
56
|
+
})
|