react-native-quick-crypto 1.1.4 → 1.1.6

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/src/hkdf.ts CHANGED
@@ -62,6 +62,14 @@ const HKDF_HASH_BYTES: Readonly<Record<string, number>> = {
62
62
  ripemd160: 20,
63
63
  };
64
64
 
65
+ function hkdfHashLen(digest: string): number {
66
+ const hashLen = HKDF_HASH_BYTES[digest.toLowerCase()];
67
+ if (hashLen === undefined) {
68
+ throw new TypeError(`Unsupported HKDF digest: ${digest}`);
69
+ }
70
+ return hashLen;
71
+ }
72
+
65
73
  function validateHkdfKeylen(digest: string, keylen: number): void {
66
74
  if (
67
75
  typeof keylen !== 'number' ||
@@ -111,6 +119,7 @@ export function hkdf(
111
119
  sanitizedSalt,
112
120
  sanitizedInfo,
113
121
  keylen,
122
+ 'full',
114
123
  )
115
124
  .then(
116
125
  res => {
@@ -146,11 +155,143 @@ export function hkdfSync(
146
155
  sanitizedSalt,
147
156
  sanitizedInfo,
148
157
  keylen,
158
+ 'full',
159
+ );
160
+
161
+ return Buffer.from(result);
162
+ }
163
+
164
+ // RFC 5869 §2.2 HKDF-Extract (sync): PRK = HMAC(salt, IKM). Salt defaults to
165
+ // a string of HashLen zeros when omitted. Returns a PRK of HashLen bytes.
166
+ export function hkdfExtractSync(
167
+ digest: string,
168
+ ikm: KeyMaterial,
169
+ salt: Salt = new Uint8Array(0),
170
+ ): Buffer {
171
+ const normalizedDigest = normalizeHashName(digest);
172
+ const hashLen = hkdfHashLen(normalizedDigest);
173
+ const sanitizedIkm = sanitizeInput(ikm, 'IKM');
174
+ const sanitizedSalt = sanitizeInput(salt, 'Salt');
175
+
176
+ const result = getNative().deriveKeySync(
177
+ normalizedDigest,
178
+ sanitizedIkm,
179
+ sanitizedSalt,
180
+ new ArrayBuffer(0),
181
+ hashLen,
182
+ 'extract',
149
183
  );
150
184
 
151
185
  return Buffer.from(result);
152
186
  }
153
187
 
188
+ // Async HKDF-Extract, mirroring `hkdf`. Unlike the sync form, `salt` is
189
+ // required here because the callback occupies the trailing argument.
190
+ export function hkdfExtract(
191
+ digest: string,
192
+ ikm: KeyMaterial,
193
+ salt: Salt,
194
+ callback: HkdfCallback,
195
+ ): void {
196
+ validateCallback(callback);
197
+
198
+ try {
199
+ const normalizedDigest = normalizeHashName(digest);
200
+ const hashLen = hkdfHashLen(normalizedDigest);
201
+ const sanitizedIkm = sanitizeInput(ikm, 'IKM');
202
+ const sanitizedSalt = sanitizeInput(salt, 'Salt');
203
+
204
+ getNative()
205
+ .deriveKey(
206
+ normalizedDigest,
207
+ sanitizedIkm,
208
+ sanitizedSalt,
209
+ new ArrayBuffer(0),
210
+ hashLen,
211
+ 'extract',
212
+ )
213
+ .then(
214
+ res => callback(null, Buffer.from(res)),
215
+ err => callback(err),
216
+ );
217
+ } catch (err) {
218
+ callback(err as Error);
219
+ }
220
+ }
221
+
222
+ // RFC 5869 §2.3 HKDF-Expand (sync): OKM = expand(PRK, info, L). `prk` must be
223
+ // at least HashLen bytes (a pseudorandom key, e.g. from hkdfExtract).
224
+ export function hkdfExpandSync(
225
+ digest: string,
226
+ prk: KeyMaterial,
227
+ info: Info,
228
+ keylen: number,
229
+ ): Buffer {
230
+ const normalizedDigest = normalizeHashName(digest);
231
+ const hashLen = hkdfHashLen(normalizedDigest);
232
+ const sanitizedPrk = sanitizeInput(prk, 'PRK');
233
+ if (sanitizedPrk.byteLength < hashLen) {
234
+ throw new RangeError(
235
+ `HKDF-Expand PRK must be at least HashLen (${hashLen}) bytes for ${digest}`,
236
+ );
237
+ }
238
+ const sanitizedInfo = sanitizeInput(info, 'Info');
239
+
240
+ validateHkdfKeylen(normalizedDigest, keylen);
241
+
242
+ const result = getNative().deriveKeySync(
243
+ normalizedDigest,
244
+ sanitizedPrk,
245
+ new ArrayBuffer(0),
246
+ sanitizedInfo,
247
+ keylen,
248
+ 'expand',
249
+ );
250
+
251
+ return Buffer.from(result);
252
+ }
253
+
254
+ // Async HKDF-Expand, mirroring `hkdf`.
255
+ export function hkdfExpand(
256
+ digest: string,
257
+ prk: KeyMaterial,
258
+ info: Info,
259
+ keylen: number,
260
+ callback: HkdfCallback,
261
+ ): void {
262
+ validateCallback(callback);
263
+
264
+ try {
265
+ const normalizedDigest = normalizeHashName(digest);
266
+ const hashLen = hkdfHashLen(normalizedDigest);
267
+ const sanitizedPrk = sanitizeInput(prk, 'PRK');
268
+ if (sanitizedPrk.byteLength < hashLen) {
269
+ throw new RangeError(
270
+ `HKDF-Expand PRK must be at least HashLen (${hashLen}) bytes for ${digest}`,
271
+ );
272
+ }
273
+ const sanitizedInfo = sanitizeInput(info, 'Info');
274
+
275
+ validateHkdfKeylen(normalizedDigest, keylen);
276
+
277
+ getNative()
278
+ .deriveKey(
279
+ normalizedDigest,
280
+ sanitizedPrk,
281
+ new ArrayBuffer(0),
282
+ sanitizedInfo,
283
+ keylen,
284
+ 'expand',
285
+ )
286
+ .then(
287
+ res => callback(null, Buffer.from(res)),
288
+ err => callback(err),
289
+ );
290
+ } catch (err) {
291
+ callback(err as Error);
292
+ }
293
+ }
294
+
154
295
  export function hkdfDeriveBits(
155
296
  algorithm: HkdfAlgorithm,
156
297
  baseKey: CryptoKey,
@@ -179,6 +320,7 @@ export function hkdfDeriveBits(
179
320
  binaryLikeToArrayBuffer(salt),
180
321
  binaryLikeToArrayBuffer(info),
181
322
  keylen,
323
+ 'full',
182
324
  );
183
325
 
184
326
  return result;
@@ -150,6 +150,7 @@ export class KeyObject {
150
150
  key: ArrayBuffer,
151
151
  format?: KFormatType,
152
152
  encoding?: KeyEncoding,
153
+ passphrase?: ArrayBuffer,
153
154
  ): KeyObject {
154
155
  if (type !== 'secret' && type !== 'public' && type !== 'private')
155
156
  throw new Error(`invalid KeyObject type: ${type}`);
@@ -172,12 +173,7 @@ export class KeyObject {
172
173
  throw new Error('invalid key type');
173
174
  }
174
175
 
175
- // If format is provided, use it (encoding is optional)
176
- if (format !== undefined) {
177
- handle.init(keyType, key, format, encoding);
178
- } else {
179
- handle.init(keyType, key);
180
- }
176
+ handle.init(keyType, key, format, encoding, passphrase);
181
177
 
182
178
  // For asymmetric keys, return the appropriate subclass
183
179
  if (type === 'public' || type === 'private') {
package/src/keys/index.ts CHANGED
@@ -114,6 +114,7 @@ function prepareAsymmetricKey(
114
114
  data: ArrayBuffer;
115
115
  format?: 'pem' | 'der';
116
116
  type?: 'pkcs1' | 'pkcs8' | 'spki' | 'sec1';
117
+ passphrase?: ArrayBuffer;
117
118
  } {
118
119
  if (key instanceof KeyObject) {
119
120
  if (isPublic) {
@@ -147,7 +148,9 @@ function prepareAsymmetricKey(
147
148
 
148
149
  if (typeof key === 'object' && 'key' in key) {
149
150
  const keyObj = key as KeyInputObject;
150
- const { key: data, format, type } = keyObj;
151
+ const { key: data, format, type, passphrase } = keyObj;
152
+ const passphraseAB =
153
+ passphrase !== undefined ? toAB(passphrase) : undefined;
151
154
 
152
155
  if (data instanceof KeyObject) {
153
156
  return prepareAsymmetricKey(data, isPublic);
@@ -167,14 +170,24 @@ function prepareAsymmetricKey(
167
170
  (typeof data === 'string' && data.includes('-----BEGIN'))) &&
168
171
  typeof data === 'string'
169
172
  ) {
170
- return { data: toAB(data), format: 'pem', type };
173
+ return {
174
+ data: toAB(data),
175
+ format: 'pem',
176
+ type,
177
+ passphrase: passphraseAB,
178
+ };
171
179
  }
172
180
 
173
181
  // Filter to only 'pem' or 'der' — JWK and raw formats are handled
174
182
  // separately via dedicated paths.
175
183
  const filteredFormat: 'pem' | 'der' | undefined =
176
184
  format === 'pem' || format === 'der' ? format : undefined;
177
- return { data: toAB(data), format: filteredFormat, type };
185
+ return {
186
+ data: toAB(data),
187
+ format: filteredFormat,
188
+ type,
189
+ passphrase: passphraseAB,
190
+ };
178
191
  }
179
192
 
180
193
  throw new Error('Invalid key input');
@@ -212,7 +225,7 @@ function createPublicKey(key: KeyInput): PublicKeyObject {
212
225
  return new PublicKeyObject(handle);
213
226
  }
214
227
 
215
- const { data, format, type } = prepareAsymmetricKey(key, true);
228
+ const { data, format, type, passphrase } = prepareAsymmetricKey(key, true);
216
229
 
217
230
  // Map format string to KFormatType enum
218
231
  let kFormat: KFormatType | undefined;
@@ -229,6 +242,7 @@ function createPublicKey(key: KeyInput): PublicKeyObject {
229
242
  data,
230
243
  kFormat,
231
244
  kType,
245
+ passphrase,
232
246
  ) as PublicKeyObject;
233
247
  }
234
248
 
@@ -249,7 +263,7 @@ function createPrivateKey(key: KeyInput): PrivateKeyObject {
249
263
  return new PrivateKeyObject(handle);
250
264
  }
251
265
 
252
- const { data, format, type } = prepareAsymmetricKey(key, false);
266
+ const { data, format, type, passphrase } = prepareAsymmetricKey(key, false);
253
267
 
254
268
  // Map format string to KFormatType enum
255
269
  let kFormat: KFormatType | undefined;
@@ -267,6 +281,7 @@ function createPrivateKey(key: KeyInput): PrivateKeyObject {
267
281
  data,
268
282
  kFormat,
269
283
  kType,
284
+ passphrase,
270
285
  ) as PrivateKeyObject;
271
286
  }
272
287
 
@@ -1,5 +1,9 @@
1
1
  import type { HybridObject } from 'react-native-nitro-modules';
2
2
 
3
+ // RFC 5869 stage: 'full' (extract+expand), 'extract' (PRK only), or
4
+ // 'expand' (from an existing PRK).
5
+ export type HkdfMode = 'full' | 'extract' | 'expand';
6
+
3
7
  export interface Hkdf extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
4
8
  deriveKeySync(
5
9
  algorithm: string,
@@ -7,6 +11,7 @@ export interface Hkdf extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
7
11
  salt: ArrayBuffer,
8
12
  info: ArrayBuffer,
9
13
  length: number,
14
+ mode: HkdfMode,
10
15
  ): ArrayBuffer;
11
16
 
12
17
  deriveKey(
@@ -15,5 +20,6 @@ export interface Hkdf extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
15
20
  salt: ArrayBuffer,
16
21
  info: ArrayBuffer,
17
22
  length: number,
23
+ mode: HkdfMode,
18
24
  ): Promise<ArrayBuffer>;
19
25
  }