UNPKG

9.7 kBJavaScriptView Raw
1/*!
2 * Copyright 2016 Amazon.com,
3 * Inc. or its affiliates. All Rights Reserved.
4 *
5 * Licensed under the Amazon Software License (the "License").
6 * You may not use this file except in compliance with the
7 * License. A copy of the License is located at
8 *
9 * http://aws.amazon.com/asl/
10 *
11 * or in the "license" file accompanying this file. This file is
12 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13 * CONDITIONS OF ANY KIND, express or implied. See the License
14 * for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18import { Buffer } from 'buffer';
19import CryptoJS from 'crypto-js/core';
20import 'crypto-js/lib-typedarrays'; // necessary for crypto js
21import SHA256 from 'crypto-js/sha256';
22import HmacSHA256 from 'crypto-js/hmac-sha256';
23
24const randomBytes = function(nBytes) {
25 return Buffer.from(CryptoJS.lib.WordArray.random(nBytes).toString(), 'hex');
26};
27
28import BigInteger from './BigInteger';
29
30const initN =
31 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' +
32 '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' +
33 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' +
34 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
35 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' +
36 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' +
37 '83655D23DCA3AD961C62F356208552BB9ED529077096966D' +
38 '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' +
39 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' +
40 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' +
41 '15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64' +
42 'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' +
43 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B' +
44 'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C' +
45 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31' +
46 '43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
47
48const newPasswordRequiredChallengeUserAttributePrefix = 'userAttributes.';
49
50/** @class */
51export default class AuthenticationHelper {
52 /**
53 * Constructs a new AuthenticationHelper object
54 * @param {string} PoolName Cognito user pool name.
55 */
56 constructor(PoolName) {
57 this.N = new BigInteger(initN, 16);
58 this.g = new BigInteger('2', 16);
59 this.k = new BigInteger(
60 this.hexHash(`00${this.N.toString(16)}0${this.g.toString(16)}`),
61 16
62 );
63
64 this.smallAValue = this.generateRandomSmallA();
65 this.getLargeAValue(() => {});
66
67 this.infoBits = Buffer.from('Caldera Derived Key', 'utf8');
68
69 this.poolName = PoolName;
70 }
71
72 /**
73 * @returns {BigInteger} small A, a random number
74 */
75 getSmallAValue() {
76 return this.smallAValue;
77 }
78
79 /**
80 * @param {nodeCallback<BigInteger>} callback Called with (err, largeAValue)
81 * @returns {void}
82 */
83 getLargeAValue(callback) {
84 if (this.largeAValue) {
85 callback(null, this.largeAValue);
86 } else {
87 this.calculateA(this.smallAValue, (err, largeAValue) => {
88 if (err) {
89 callback(err, null);
90 }
91
92 this.largeAValue = largeAValue;
93 callback(null, this.largeAValue);
94 });
95 }
96 }
97
98 /**
99 * helper function to generate a random big integer
100 * @returns {BigInteger} a random value.
101 * @private
102 */
103 generateRandomSmallA() {
104 const hexRandom = randomBytes(128).toString('hex');
105
106 const randomBigInt = new BigInteger(hexRandom, 16);
107 const smallABigInt = randomBigInt.mod(this.N);
108
109 return smallABigInt;
110 }
111
112 /**
113 * helper function to generate a random string
114 * @returns {string} a random value.
115 * @private
116 */
117 generateRandomString() {
118 return randomBytes(40).toString('base64');
119 }
120
121 /**
122 * @returns {string} Generated random value included in password hash.
123 */
124 getRandomPassword() {
125 return this.randomPassword;
126 }
127
128 /**
129 * @returns {string} Generated random value included in devices hash.
130 */
131 getSaltDevices() {
132 return this.SaltToHashDevices;
133 }
134
135 /**
136 * @returns {string} Value used to verify devices.
137 */
138 getVerifierDevices() {
139 return this.verifierDevices;
140 }
141
142 /**
143 * Generate salts and compute verifier.
144 * @param {string} deviceGroupKey Devices to generate verifier for.
145 * @param {string} username User to generate verifier for.
146 * @param {nodeCallback<null>} callback Called with (err, null)
147 * @returns {void}
148 */
149 generateHashDevice(deviceGroupKey, username, callback) {
150 this.randomPassword = this.generateRandomString();
151 const combinedString = `${deviceGroupKey}${username}:${this.randomPassword}`;
152 const hashedString = this.hash(combinedString);
153
154 const hexRandom = randomBytes(16).toString('hex');
155 this.SaltToHashDevices = this.padHex(new BigInteger(hexRandom, 16));
156
157 this.g.modPow(
158 new BigInteger(this.hexHash(this.SaltToHashDevices + hashedString), 16),
159 this.N,
160 (err, verifierDevicesNotPadded) => {
161 if (err) {
162 callback(err, null);
163 }
164
165 this.verifierDevices = this.padHex(verifierDevicesNotPadded);
166 callback(null, null);
167 }
168 );
169 }
170
171 /**
172 * Calculate the client's public value A = g^a%N
173 * with the generated random number a
174 * @param {BigInteger} a Randomly generated small A.
175 * @param {nodeCallback<BigInteger>} callback Called with (err, largeAValue)
176 * @returns {void}
177 * @private
178 */
179 calculateA(a, callback) {
180 this.g.modPow(a, this.N, (err, A) => {
181 if (err) {
182 callback(err, null);
183 }
184
185 if (A.mod(this.N).equals(BigInteger.ZERO)) {
186 callback(new Error('Illegal paramater. A mod N cannot be 0.'), null);
187 }
188
189 callback(null, A);
190 });
191 }
192
193 /**
194 * Calculate the client's value U which is the hash of A and B
195 * @param {BigInteger} A Large A value.
196 * @param {BigInteger} B Server B value.
197 * @returns {BigInteger} Computed U value.
198 * @private
199 */
200 calculateU(A, B) {
201 this.UHexHash = this.hexHash(this.padHex(A) + this.padHex(B));
202 const finalU = new BigInteger(this.UHexHash, 16);
203
204 return finalU;
205 }
206
207 /**
208 * Calculate a hash from a bitArray
209 * @param {Buffer} buf Value to hash.
210 * @returns {String} Hex-encoded hash.
211 * @private
212 */
213 hash(buf) {
214 const str =
215 buf instanceof Buffer ? CryptoJS.lib.WordArray.create(buf) : buf;
216 const hashHex = SHA256(str).toString();
217
218 return new Array(64 - hashHex.length).join('0') + hashHex;
219 }
220
221 /**
222 * Calculate a hash from a hex string
223 * @param {String} hexStr Value to hash.
224 * @returns {String} Hex-encoded hash.
225 * @private
226 */
227 hexHash(hexStr) {
228 return this.hash(Buffer.from(hexStr, 'hex'));
229 }
230
231 /**
232 * Standard hkdf algorithm
233 * @param {Buffer} ikm Input key material.
234 * @param {Buffer} salt Salt value.
235 * @returns {Buffer} Strong key material.
236 * @private
237 */
238 computehkdf(ikm, salt) {
239 const infoBitsWordArray = CryptoJS.lib.WordArray.create(
240 Buffer.concat([
241 this.infoBits,
242 Buffer.from(String.fromCharCode(1), 'utf8'),
243 ])
244 );
245 const ikmWordArray =
246 ikm instanceof Buffer ? CryptoJS.lib.WordArray.create(ikm) : ikm;
247 const saltWordArray =
248 salt instanceof Buffer ? CryptoJS.lib.WordArray.create(salt) : salt;
249
250 const prk = HmacSHA256(ikmWordArray, saltWordArray);
251 const hmac = HmacSHA256(infoBitsWordArray, prk);
252 return Buffer.from(hmac.toString(), 'hex').slice(0, 16);
253 }
254
255 /**
256 * Calculates the final hkdf based on computed S value, and computed U value and the key
257 * @param {String} username Username.
258 * @param {String} password Password.
259 * @param {BigInteger} serverBValue Server B value.
260 * @param {BigInteger} salt Generated salt.
261 * @param {nodeCallback<Buffer>} callback Called with (err, hkdfValue)
262 * @returns {void}
263 */
264 getPasswordAuthenticationKey(
265 username,
266 password,
267 serverBValue,
268 salt,
269 callback
270 ) {
271 if (serverBValue.mod(this.N).equals(BigInteger.ZERO)) {
272 throw new Error('B cannot be zero.');
273 }
274
275 this.UValue = this.calculateU(this.largeAValue, serverBValue);
276
277 if (this.UValue.equals(BigInteger.ZERO)) {
278 throw new Error('U cannot be zero.');
279 }
280
281 const usernamePassword = `${this.poolName}${username}:${password}`;
282 const usernamePasswordHash = this.hash(usernamePassword);
283
284 const xValue = new BigInteger(
285 this.hexHash(this.padHex(salt) + usernamePasswordHash),
286 16
287 );
288 this.calculateS(xValue, serverBValue, (err, sValue) => {
289 if (err) {
290 callback(err, null);
291 }
292
293 const hkdf = this.computehkdf(
294 Buffer.from(this.padHex(sValue), 'hex'),
295 Buffer.from(this.padHex(this.UValue.toString(16)), 'hex')
296 );
297
298 callback(null, hkdf);
299 });
300 }
301
302 /**
303 * Calculates the S value used in getPasswordAuthenticationKey
304 * @param {BigInteger} xValue Salted password hash value.
305 * @param {BigInteger} serverBValue Server B value.
306 * @param {nodeCallback<string>} callback Called on success or error.
307 * @returns {void}
308 */
309 calculateS(xValue, serverBValue, callback) {
310 this.g.modPow(xValue, this.N, (err, gModPowXN) => {
311 if (err) {
312 callback(err, null);
313 }
314
315 const intValue2 = serverBValue.subtract(this.k.multiply(gModPowXN));
316 intValue2.modPow(
317 this.smallAValue.add(this.UValue.multiply(xValue)),
318 this.N,
319 (err2, result) => {
320 if (err2) {
321 callback(err2, null);
322 }
323
324 callback(null, result.mod(this.N));
325 }
326 );
327 });
328 }
329
330 /**
331 * Return constant newPasswordRequiredChallengeUserAttributePrefix
332 * @return {newPasswordRequiredChallengeUserAttributePrefix} constant prefix value
333 */
334 getNewPasswordRequiredChallengeUserAttributePrefix() {
335 return newPasswordRequiredChallengeUserAttributePrefix;
336 }
337
338 /**
339 * Converts a BigInteger (or hex string) to hex format padded with zeroes for hashing
340 * @param {BigInteger|String} bigInt Number or string to pad.
341 * @returns {String} Padded hex string.
342 */
343 padHex(bigInt) {
344 let hashStr = bigInt.toString(16);
345 if (hashStr.length % 2 === 1) {
346 hashStr = `0${hashStr}`;
347 } else if ('89ABCDEFabcdef'.indexOf(hashStr[0]) !== -1) {
348 hashStr = `00${hashStr}`;
349 }
350 return hashStr;
351 }
352}