xref: /third_party/node/lib/internal/crypto/mac.js (revision 1cb0ef41)
1'use strict';
2
3const {
4  ArrayFrom,
5  SafeSet,
6} = primordials;
7
8const {
9  HmacJob,
10  KeyObjectHandle,
11  kCryptoJobAsync,
12  kSignJobModeSign,
13  kSignJobModeVerify,
14} = internalBinding('crypto');
15
16const {
17  getBlockSize,
18  hasAnyNotIn,
19  jobPromise,
20  normalizeHashName,
21  validateBitLength,
22  validateKeyOps,
23  kHandle,
24  kKeyObject,
25} = require('internal/crypto/util');
26
27const {
28  lazyDOMException,
29  promisify,
30} = require('internal/util');
31
32const {
33  generateKey: _generateKey,
34} = require('internal/crypto/keygen');
35
36const {
37  InternalCryptoKey,
38  SecretKeyObject,
39  createSecretKey,
40} = require('internal/crypto/keys');
41
42const generateKey = promisify(_generateKey);
43
44async function hmacGenerateKey(algorithm, extractable, keyUsages) {
45  const { hash, name } = algorithm;
46  let { length } = algorithm;
47
48  if (length === undefined)
49    length = getBlockSize(hash.name);
50
51  validateBitLength(length, 'algorithm.length', true);
52
53  const usageSet = new SafeSet(keyUsages);
54  if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
55    throw lazyDOMException(
56      'Unsupported key usage for an HMAC key',
57      'SyntaxError');
58  }
59
60  const key = await generateKey('hmac', { length }).catch((err) => {
61    throw lazyDOMException(
62      'The operation failed for an operation-specific reason',
63      { name: 'OperationError', cause: err });
64  });
65
66  return new InternalCryptoKey(
67    key,
68    { name, length, hash: { name: hash.name } },
69    ArrayFrom(usageSet),
70    extractable);
71}
72
73function getAlgorithmName(hash) {
74  switch (hash) {
75    case 'SHA-1': // Fall through
76    case 'SHA-256': // Fall through
77    case 'SHA-384': // Fall through
78    case 'SHA-512': // Fall through
79      return `HS${hash.slice(4)}`;
80    default:
81      throw lazyDOMException('Unsupported digest algorithm', 'DataError');
82  }
83}
84
85async function hmacImportKey(
86  format,
87  keyData,
88  algorithm,
89  extractable,
90  keyUsages) {
91  const usagesSet = new SafeSet(keyUsages);
92  if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
93    throw lazyDOMException(
94      'Unsupported key usage for an HMAC key',
95      'SyntaxError');
96  }
97  let keyObject;
98  switch (format) {
99    case 'raw': {
100      const checkLength = keyData.byteLength * 8;
101
102      if (checkLength === 0 || algorithm.length === 0)
103        throw lazyDOMException('Zero-length key is not supported', 'DataError');
104
105      // The Web Crypto spec allows for key lengths that are not multiples of
106      // 8. We don't. Our check here is stricter than that defined by the spec
107      // in that we require that algorithm.length match keyData.length * 8 if
108      // algorithm.length is specified.
109      if (algorithm.length !== undefined &&
110          algorithm.length !== checkLength) {
111        throw lazyDOMException('Invalid key length', 'DataError');
112      }
113
114      keyObject = createSecretKey(keyData);
115      break;
116    }
117    case 'jwk': {
118      if (!keyData.kty)
119        throw lazyDOMException('Invalid keyData', 'DataError');
120
121      if (keyData.kty !== 'oct')
122        throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
123
124      if (usagesSet.size > 0 &&
125          keyData.use !== undefined &&
126          keyData.use !== 'sig') {
127        throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
128      }
129
130      validateKeyOps(keyData.key_ops, usagesSet);
131
132      if (keyData.ext !== undefined &&
133          keyData.ext === false &&
134          extractable === true) {
135        throw lazyDOMException(
136          'JWK "ext" Parameter and extractable mismatch',
137          'DataError');
138      }
139
140      if (keyData.alg !== undefined) {
141        if (keyData.alg !== getAlgorithmName(algorithm.hash.name))
142          throw lazyDOMException(
143            'JWK "alg" does not match the requested algorithm',
144            'DataError');
145      }
146
147      const handle = new KeyObjectHandle();
148      handle.initJwk(keyData);
149      keyObject = new SecretKeyObject(handle);
150      break;
151    }
152    default:
153      throw lazyDOMException(`Unable to import HMAC key with format ${format}`);
154  }
155
156  const { length } = keyObject[kHandle].keyDetail({});
157
158  return new InternalCryptoKey(
159    keyObject, {
160      name: 'HMAC',
161      hash: algorithm.hash,
162      length,
163    },
164    keyUsages,
165    extractable);
166}
167
168function hmacSignVerify(key, data, algorithm, signature) {
169  const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
170  return jobPromise(() => new HmacJob(
171    kCryptoJobAsync,
172    mode,
173    normalizeHashName(key.algorithm.hash.name),
174    key[kKeyObject][kHandle],
175    data,
176    signature));
177}
178
179module.exports = {
180  hmacImportKey,
181  hmacGenerateKey,
182  hmacSignVerify,
183};
184