@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.
@@ -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
+ }