@wishknish/knishio-client-js 0.9.4 → 1.1.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/README.md +13 -0
- package/dist/client.cjs.js +38 -40
- package/dist/client.cjs.js.map +1 -1
- package/dist/client.es.mjs +6660 -9256
- package/dist/client.es.mjs.map +1 -1
- package/dist/client.iife.js +38 -40
- package/dist/client.iife.js.map +1 -1
- package/package.json +10 -11
- package/src/AuthToken.js +25 -3
- package/src/KnishIOClient.js +54 -15
- package/src/Molecule.js +15 -7
- package/src/Wallet.js +137 -30
- package/src/exception/SecretStorageException.js +10 -0
- package/src/index.js +11 -1
- package/src/libraries/urql/UrqlClientWrapper.js +7 -2
- package/src/storage/FileStorageBackend.js +199 -0
- package/src/storage/MemorySecretStorageProvider.js +69 -1
- package/src/storage/NonExtractableKeySecretStorageProvider.js +514 -0
- package/src/storage/WebAuthnPrfSecretStorageProvider.js +646 -0
- package/src/storage/WebCryptoSecretStorageProvider.js +107 -129
- package/src/storage/WebStorageBackend.js +117 -0
- package/src/storage/index.js +25 -3
- package/src/storage/secretEnvelope.js +208 -0
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import SecretStorageException from '../exception/SecretStorageException.js'
|
|
50
|
+
import { zeroizeBytes } from '../libraries/secureMemory.js'
|
|
51
|
+
|
|
52
|
+
export const ENVELOPE_ALGORITHM = 'AES-GCM'
|
|
53
|
+
export const DEFAULT_ITERATIONS = 100000
|
|
54
|
+
export const SECRET_KEY_PREFIX = 'knishio:secret:'
|
|
55
|
+
export const RECOVERY_KEY_PREFIX = 'knishio:recovery:'
|
|
56
|
+
const GCM_IV_LENGTH = 12
|
|
57
|
+
const SALT_LENGTH = 16
|
|
58
|
+
|
|
59
|
+
const textEncoder = new TextEncoder()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Helper to convert Uint8Array to base64
|
|
63
|
+
*
|
|
64
|
+
* @param {Uint8Array} bytes
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export function uint8ArrayToBase64 (bytes) {
|
|
68
|
+
let binary = ''
|
|
69
|
+
const len = bytes.byteLength
|
|
70
|
+
for (let i = 0; i < len; i++) {
|
|
71
|
+
const byte = bytes[i]
|
|
72
|
+
if (byte !== undefined) {
|
|
73
|
+
binary += String.fromCharCode(byte)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return btoa(binary)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Helper to convert base64 to Uint8Array
|
|
81
|
+
*
|
|
82
|
+
* @param {string} base64
|
|
83
|
+
* @returns {Uint8Array}
|
|
84
|
+
*/
|
|
85
|
+
export function base64ToUint8Array (base64) {
|
|
86
|
+
const binary = atob(base64)
|
|
87
|
+
const len = binary.length
|
|
88
|
+
const bytes = new Uint8Array(len)
|
|
89
|
+
for (let i = 0; i < len; i++) {
|
|
90
|
+
bytes[i] = binary.charCodeAt(i)
|
|
91
|
+
}
|
|
92
|
+
return bytes
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
|
|
97
|
+
*
|
|
98
|
+
* @param {string} passphrase
|
|
99
|
+
* @param {Uint8Array} salt
|
|
100
|
+
* @param {number} [iterations]
|
|
101
|
+
* @returns {Promise<CryptoKey>}
|
|
102
|
+
*/
|
|
103
|
+
export async function deriveEnvelopeKey (passphrase, salt, iterations = DEFAULT_ITERATIONS) {
|
|
104
|
+
if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
|
|
105
|
+
throw new SecretStorageException('WebCrypto API is not available')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const passphraseBytes = textEncoder.encode(passphrase)
|
|
109
|
+
try {
|
|
110
|
+
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
111
|
+
'raw',
|
|
112
|
+
passphraseBytes,
|
|
113
|
+
'PBKDF2',
|
|
114
|
+
false,
|
|
115
|
+
['deriveKey']
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return await globalThis.crypto.subtle.deriveKey(
|
|
119
|
+
{
|
|
120
|
+
name: 'PBKDF2',
|
|
121
|
+
salt,
|
|
122
|
+
iterations,
|
|
123
|
+
hash: 'SHA-256'
|
|
124
|
+
},
|
|
125
|
+
baseKey,
|
|
126
|
+
{ name: 'AES-GCM', length: 256 },
|
|
127
|
+
false,
|
|
128
|
+
['encrypt', 'decrypt']
|
|
129
|
+
)
|
|
130
|
+
} finally {
|
|
131
|
+
zeroizeBytes(passphraseBytes)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Seal a secret string into an EncryptedSecretPayload envelope
|
|
137
|
+
*
|
|
138
|
+
* @param {string} secret
|
|
139
|
+
* @param {string} passphrase
|
|
140
|
+
* @param {object} metadata
|
|
141
|
+
* @returns {Promise<object>}
|
|
142
|
+
*/
|
|
143
|
+
export async function sealEnvelope (secret, passphrase, metadata) {
|
|
144
|
+
if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
|
|
145
|
+
throw new SecretStorageException('WebCrypto API is not available')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const salt = new Uint8Array(SALT_LENGTH)
|
|
149
|
+
const iv = new Uint8Array(GCM_IV_LENGTH)
|
|
150
|
+
globalThis.crypto.getRandomValues(salt)
|
|
151
|
+
globalThis.crypto.getRandomValues(iv)
|
|
152
|
+
|
|
153
|
+
const key = await deriveEnvelopeKey(passphrase, salt, DEFAULT_ITERATIONS)
|
|
154
|
+
const secretBytes = textEncoder.encode(secret)
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
158
|
+
{
|
|
159
|
+
name: ENVELOPE_ALGORITHM,
|
|
160
|
+
iv
|
|
161
|
+
},
|
|
162
|
+
key,
|
|
163
|
+
secretBytes
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
|
|
167
|
+
return {
|
|
168
|
+
version: 1,
|
|
169
|
+
ciphertext,
|
|
170
|
+
iv: uint8ArrayToBase64(iv),
|
|
171
|
+
salt: uint8ArrayToBase64(salt),
|
|
172
|
+
algorithm: ENVELOPE_ALGORITHM,
|
|
173
|
+
iterations: DEFAULT_ITERATIONS,
|
|
174
|
+
metadata
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
zeroizeBytes(secretBytes)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Open an EncryptedSecretPayload envelope with a passphrase, returning the decrypted secret bytes
|
|
183
|
+
*
|
|
184
|
+
* @param {object} payload
|
|
185
|
+
* @param {string} passphrase
|
|
186
|
+
* @returns {Promise<Uint8Array>}
|
|
187
|
+
*/
|
|
188
|
+
export async function openEnvelope (payload, passphrase) {
|
|
189
|
+
if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
|
|
190
|
+
throw new SecretStorageException('WebCrypto API is not available')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const salt = base64ToUint8Array(payload.salt)
|
|
194
|
+
const iv = base64ToUint8Array(payload.iv)
|
|
195
|
+
const ciphertext = base64ToUint8Array(payload.ciphertext)
|
|
196
|
+
|
|
197
|
+
const key = await deriveEnvelopeKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS)
|
|
198
|
+
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
199
|
+
{
|
|
200
|
+
name: ENVELOPE_ALGORITHM,
|
|
201
|
+
iv
|
|
202
|
+
},
|
|
203
|
+
key,
|
|
204
|
+
ciphertext
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return new Uint8Array(decryptedBuffer)
|
|
208
|
+
}
|