|
7 | 7 | * |
8 | 8 | * Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved. |
9 | 9 | */ |
10 | | -const forge = require('./forge') |
11 | | -require('./md') |
12 | | -require('./util') |
13 | 10 |
|
14 | | -/* HMAC API */ |
15 | | -const hmac = module.exports = forge.hmac = forge.hmac || {} |
| 11 | +import { ByteStringBuffer, createBuffer } from "./utils" |
| 12 | + |
| 13 | +type MessageDigest = { |
| 14 | + start: () => MessageDigest |
| 15 | + update: (msg: string | ByteStringBuffer, encoding?: string) => MessageDigest |
| 16 | + digest: () => ByteStringBuffer |
| 17 | + blockLength: number |
| 18 | +} |
| 19 | + |
| 20 | +type NativeBuffer = Buffer | Uint8Array | ArrayBuffer |
| 21 | + |
| 22 | +type HMACInput = string | ByteStringBuffer | NativeBuffer |
| 23 | + |
| 24 | +interface HMAC { |
| 25 | + start: (md: MessageDigest, key: HMACInput) => void |
| 26 | + update: (bytes: HMACInput) => void |
| 27 | + getMac: () => ByteStringBuffer |
| 28 | + digest: () => ByteStringBuffer |
| 29 | +} |
| 30 | + |
| 31 | +interface HMACModule { |
| 32 | + create: () => HMAC |
| 33 | +} |
16 | 34 |
|
17 | 35 | /** |
18 | 36 | * Creates an HMAC object that uses the given message digest object. |
19 | 37 | * |
20 | 38 | * @return an HMAC object. |
21 | 39 | */ |
22 | | -hmac.create = function () { |
| 40 | +function create(): HMAC { |
23 | 41 | // the hmac key to use |
24 | | - let _key = null |
| 42 | + let _key: ByteStringBuffer | null = null |
25 | 43 |
|
26 | 44 | // the message digest to use |
27 | | - let _md = null |
| 45 | + let _md: MessageDigest | null = null |
28 | 46 |
|
29 | 47 | // the inner padding |
30 | | - let _ipadding = null |
| 48 | + let _ipadding: string | null = null |
31 | 49 |
|
32 | 50 | // the outer padding |
33 | | - let _opadding = null |
| 51 | + let _opadding: string | null = null |
34 | 52 |
|
35 | 53 | // hmac context |
36 | | - const ctx = {} |
37 | | - |
38 | | - /** |
39 | | - * Starts or restarts the HMAC with the given key and message digest. |
40 | | - * |
41 | | - * @param md the message digest to use, null to reuse the previous one, |
42 | | - * a string to use builtin 'sha1', 'md5', 'sha256'. |
43 | | - * @param key the key to use as a string, array of bytes, byte buffer, |
44 | | - * or null to reuse the previous key. |
45 | | - */ |
46 | | - ctx.start = function (md, key) { |
47 | | - if (md !== null) { |
48 | | - if (typeof md === 'string') { |
49 | | - // create builtin message digest |
50 | | - md = md.toLowerCase() |
51 | | - if (md in forge.md.algorithms) { |
52 | | - _md = forge.md.algorithms[md].create() |
53 | | - } |
54 | | - else { |
55 | | - throw new Error(`Unknown hash algorithm "${md}"`) |
56 | | - } |
| 54 | + const ctx: HMAC = { |
| 55 | + start: (md: MessageDigest, key: HMACInput) => { |
| 56 | + if (!md) { |
| 57 | + throw new TypeError('"md" argument is required') |
57 | 58 | } |
58 | | - else { |
59 | | - // store message digest |
60 | | - _md = md |
| 59 | + |
| 60 | + _md = md |
| 61 | + |
| 62 | + if (key === null) { |
| 63 | + if (!_key) { |
| 64 | + throw new TypeError('Key is required for first call to start()') |
| 65 | + } |
| 66 | + key = _key |
61 | 67 | } |
62 | | - } |
63 | 68 |
|
64 | | - if (key === null) { |
65 | | - // reuse previous key |
66 | | - key = _key |
67 | | - } |
68 | | - else { |
| 69 | + // convert key to ByteStringBuffer |
| 70 | + let keyBuffer: ByteStringBuffer |
69 | 71 | if (typeof key === 'string') { |
70 | | - // convert string into byte buffer |
71 | | - key = forge.util.createBuffer(key) |
| 72 | + keyBuffer = createBuffer(key) |
| 73 | + } |
| 74 | + else if (key instanceof ByteStringBuffer) { |
| 75 | + keyBuffer = key |
72 | 76 | } |
73 | | - else if (forge.util.isArray(key)) { |
74 | | - // convert byte array into byte buffer |
75 | | - var tmp = key |
76 | | - key = forge.util.createBuffer() |
77 | | - for (var i = 0; i < tmp.length; ++i) { |
78 | | - key.putByte(tmp[i]) |
| 77 | + else if (key instanceof Uint8Array || key instanceof Buffer || key instanceof ArrayBuffer) { |
| 78 | + keyBuffer = createBuffer() |
| 79 | + const view = key instanceof ArrayBuffer ? new Uint8Array(key) : key |
| 80 | + for (let i = 0; i < view.length; ++i) { |
| 81 | + keyBuffer.putByte(view[i]) |
79 | 82 | } |
80 | 83 | } |
| 84 | + else { |
| 85 | + throw new TypeError( |
| 86 | + '"key" must be a string, ByteStringBuffer, Buffer, Uint8Array, or ArrayBuffer', |
| 87 | + ) |
| 88 | + } |
81 | 89 |
|
82 | 90 | // if key is longer than blocksize, hash it |
83 | | - let keylen = key.length() |
| 91 | + let keylen = keyBuffer.length() |
84 | 92 | if (keylen > _md.blockLength) { |
85 | 93 | _md.start() |
86 | | - _md.update(key.bytes()) |
87 | | - key = _md.digest() |
| 94 | + _md.update(keyBuffer) |
| 95 | + keyBuffer = _md.digest() |
88 | 96 | } |
89 | 97 |
|
90 | 98 | // mix key into inner and outer padding |
91 | 99 | // ipadding = [0x36 * blocksize] ^ key |
92 | 100 | // opadding = [0x5C * blocksize] ^ key |
93 | | - _ipadding = forge.util.createBuffer() |
94 | | - _opadding = forge.util.createBuffer() |
95 | | - keylen = key.length() |
96 | | - for (var i = 0; i < keylen; ++i) { |
97 | | - var tmp = key.at(i) |
98 | | - _ipadding.putByte(0x36 ^ tmp) |
99 | | - _opadding.putByte(0x5C ^ tmp) |
| 101 | + const ipadding = createBuffer() |
| 102 | + const opadding = createBuffer() |
| 103 | + keylen = keyBuffer.length() |
| 104 | + |
| 105 | + for (let i = 0; i < keylen; ++i) { |
| 106 | + const tmp = keyBuffer.at(i) |
| 107 | + ipadding.putByte(0x36 ^ tmp) |
| 108 | + opadding.putByte(0x5C ^ tmp) |
100 | 109 | } |
101 | 110 |
|
102 | 111 | // if key is shorter than blocksize, add additional padding |
103 | 112 | if (keylen < _md.blockLength) { |
104 | | - var tmp = _md.blockLength - keylen |
105 | | - for (var i = 0; i < tmp; ++i) { |
106 | | - _ipadding.putByte(0x36) |
107 | | - _opadding.putByte(0x5C) |
| 113 | + const remaining = _md.blockLength - keylen |
| 114 | + for (let i = 0; i < remaining; ++i) { |
| 115 | + ipadding.putByte(0x36) |
| 116 | + opadding.putByte(0x5C) |
108 | 117 | } |
109 | 118 | } |
110 | | - _key = key |
111 | | - _ipadding = _ipadding.bytes() |
112 | | - _opadding = _opadding.bytes() |
113 | | - } |
114 | 119 |
|
115 | | - // digest is done like so: hash(opadding | hash(ipadding | message)) |
| 120 | + _key = keyBuffer |
| 121 | + _ipadding = ipadding.bytes() |
| 122 | + _opadding = opadding.bytes() |
116 | 123 |
|
117 | | - // prepare to do inner hash |
118 | | - // hash(ipadding | message) |
119 | | - _md.start() |
120 | | - _md.update(_ipadding) |
121 | | - } |
| 124 | + // digest is done like so: hash(opadding | hash(ipadding | message)) |
| 125 | + // prepare to do inner hash |
| 126 | + // hash(ipadding | message) |
| 127 | + _md.start() |
| 128 | + _md.update(_ipadding) |
| 129 | + }, |
122 | 130 |
|
123 | | - /** |
124 | | - * Updates the HMAC with the given message bytes. |
125 | | - * |
126 | | - * @param bytes the bytes to update with. |
127 | | - */ |
128 | | - ctx.update = function (bytes) { |
129 | | - _md.update(bytes) |
130 | | - } |
| 131 | + update: (bytes: HMACInput) => { |
| 132 | + if (!_md) { |
| 133 | + throw new Error('HMAC not started. Call start() first.') |
| 134 | + } |
| 135 | + |
| 136 | + // convert bytes to ByteStringBuffer if needed |
| 137 | + if (bytes instanceof ByteStringBuffer || typeof bytes === 'string') { |
| 138 | + _md.update(bytes) |
| 139 | + } |
| 140 | + else { |
| 141 | + const buffer = createBuffer() |
| 142 | + const view = bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes |
| 143 | + for (let i = 0; i < view.length; ++i) { |
| 144 | + buffer.putByte(view[i]) |
| 145 | + } |
| 146 | + _md.update(buffer.bytes()) |
| 147 | + } |
| 148 | + }, |
| 149 | + |
| 150 | + getMac: () => { |
| 151 | + if (!_md || !_opadding) { |
| 152 | + throw new Error('HMAC not started. Call start() first.') |
| 153 | + } |
131 | 154 |
|
132 | | - /** |
133 | | - * Produces the Message Authentication Code (MAC). |
134 | | - * |
135 | | - * @return a byte buffer containing the digest value. |
136 | | - */ |
137 | | - ctx.getMac = function () { |
138 | | - // digest is done like so: hash(opadding | hash(ipadding | message)) |
139 | | - // here we do the outer hashing |
140 | | - const inner = _md.digest().bytes() |
141 | | - _md.start() |
142 | | - _md.update(_opadding) |
143 | | - _md.update(inner) |
144 | | - return _md.digest() |
| 155 | + // digest is done like so: hash(opadding | hash(ipadding | message)) |
| 156 | + // here we do the outer hashing |
| 157 | + const inner = _md.digest().bytes() |
| 158 | + _md.start() |
| 159 | + _md.update(_opadding) |
| 160 | + _md.update(inner) |
| 161 | + return _md.digest() |
| 162 | + }, |
| 163 | + |
| 164 | + digest() { |
| 165 | + return this.getMac() |
| 166 | + } |
145 | 167 | } |
146 | | - // alias for getMac |
147 | | - ctx.digest = ctx.getMac |
148 | 168 |
|
149 | 169 | return ctx |
150 | 170 | } |
| 171 | + |
| 172 | +// Export the HMAC implementation |
| 173 | +export const hmac: HMACModule = { |
| 174 | + create, |
| 175 | +} |
0 commit comments