1'use strict';
2
3const {
4  FunctionPrototypeCall,
5} = primordials;
6
7const {
8  HKDFJob,
9  kCryptoJobAsync,
10  kCryptoJobSync,
11} = internalBinding('crypto');
12
13const {
14  validateFunction,
15  validateInteger,
16  validateString,
17} = require('internal/validators');
18
19const { kMaxLength } = require('buffer');
20
21const {
22  normalizeHashName,
23  toBuf,
24  validateByteSource,
25  kKeyObject,
26} = require('internal/crypto/util');
27
28const {
29  createSecretKey,
30  isKeyObject,
31} = require('internal/crypto/keys');
32
33const {
34  lazyDOMException,
35  promisify,
36} = require('internal/util');
37
38const {
39  isAnyArrayBuffer,
40  isArrayBufferView,
41} = require('internal/util/types');
42
43const {
44  codes: {
45    ERR_INVALID_ARG_TYPE,
46    ERR_OUT_OF_RANGE,
47  },
48  hideStackFrames,
49} = require('internal/errors');
50
51const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
52  validateString(hash, 'digest');
53  key = prepareKey(key);
54  salt = validateByteSource(salt, 'salt');
55  info = validateByteSource(info, 'info');
56
57  validateInteger(length, 'length', 0, kMaxLength);
58
59  if (info.byteLength > 1024) {
60    throw ERR_OUT_OF_RANGE(
61      'info',
62      'must not contain more than 1024 bytes',
63      info.byteLength);
64  }
65
66  return {
67    hash,
68    key,
69    salt,
70    info,
71    length,
72  };
73});
74
75function prepareKey(key) {
76  if (isKeyObject(key))
77    return key;
78
79  if (isAnyArrayBuffer(key))
80    return createSecretKey(key);
81
82  key = toBuf(key);
83
84  if (!isArrayBufferView(key)) {
85    throw new ERR_INVALID_ARG_TYPE(
86      'ikm',
87      [
88        'string',
89        'SecretKeyObject',
90        'ArrayBuffer',
91        'TypedArray',
92        'DataView',
93        'Buffer',
94      ],
95      key);
96  }
97
98  return createSecretKey(key);
99}
100
101function hkdf(hash, key, salt, info, length, callback) {
102  ({
103    hash,
104    key,
105    salt,
106    info,
107    length,
108  } = validateParameters(hash, key, salt, info, length));
109
110  validateFunction(callback, 'callback');
111
112  const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length);
113
114  job.ondone = (error, bits) => {
115    if (error) return FunctionPrototypeCall(callback, job, error);
116    FunctionPrototypeCall(callback, job, null, bits);
117  };
118
119  job.run();
120}
121
122function hkdfSync(hash, key, salt, info, length) {
123  ({
124    hash,
125    key,
126    salt,
127    info,
128    length,
129  } = validateParameters(hash, key, salt, info, length));
130
131  const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length);
132  const { 0: err, 1: bits } = job.run();
133  if (err !== undefined)
134    throw err;
135
136  return bits;
137}
138
139const hkdfPromise = promisify(hkdf);
140async function hkdfDeriveBits(algorithm, baseKey, length) {
141  const { hash, salt, info } = algorithm;
142
143  if (length === 0)
144    throw lazyDOMException('length cannot be zero', 'OperationError');
145  if (length === null)
146    throw lazyDOMException('length cannot be null', 'OperationError');
147  if (length % 8) {
148    throw lazyDOMException(
149      'length must be a multiple of 8',
150      'OperationError');
151  }
152
153  try {
154    return await hkdfPromise(
155      normalizeHashName(hash.name), baseKey[kKeyObject], salt, info, length / 8,
156    );
157  } catch (err) {
158    throw lazyDOMException(
159      'The operation failed for an operation-specific reason',
160      { name: 'OperationError', cause: err });
161  }
162}
163
164module.exports = {
165  hkdf,
166  hkdfSync,
167  hkdfDeriveBits,
168};
169