1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import { Buffer } from 'buffer';
|
19 | import CryptoJS from 'crypto-js/core';
|
20 | import 'crypto-js/lib-typedarrays';
|
21 | import SHA256 from 'crypto-js/sha256';
|
22 | import HmacSHA256 from 'crypto-js/hmac-sha256';
|
23 |
|
24 | const randomBytes = function(nBytes) {
|
25 | return Buffer.from(CryptoJS.lib.WordArray.random(nBytes).toString(), 'hex');
|
26 | };
|
27 |
|
28 | import BigInteger from './BigInteger';
|
29 |
|
30 | const 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 |
|
48 | const newPasswordRequiredChallengeUserAttributePrefix = 'userAttributes.';
|
49 |
|
50 |
|
51 | export default class AuthenticationHelper {
|
52 | |
53 |
|
54 |
|
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 |
|
74 |
|
75 | getSmallAValue() {
|
76 | return this.smallAValue;
|
77 | }
|
78 |
|
79 | |
80 |
|
81 |
|
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 |
|
100 |
|
101 |
|
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 |
|
114 |
|
115 |
|
116 |
|
117 | generateRandomString() {
|
118 | return randomBytes(40).toString('base64');
|
119 | }
|
120 |
|
121 | |
122 |
|
123 |
|
124 | getRandomPassword() {
|
125 | return this.randomPassword;
|
126 | }
|
127 |
|
128 | |
129 |
|
130 |
|
131 | getSaltDevices() {
|
132 | return this.SaltToHashDevices;
|
133 | }
|
134 |
|
135 | |
136 |
|
137 |
|
138 | getVerifierDevices() {
|
139 | return this.verifierDevices;
|
140 | }
|
141 |
|
142 | |
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
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 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
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 |
|
195 |
|
196 |
|
197 |
|
198 |
|
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 |
|
209 |
|
210 |
|
211 |
|
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 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | hexHash(hexStr) {
|
228 | return this.hash(Buffer.from(hexStr, 'hex'));
|
229 | }
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
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 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
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 |
|
304 |
|
305 |
|
306 |
|
307 |
|
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 |
|
332 |
|
333 |
|
334 | getNewPasswordRequiredChallengeUserAttributePrefix() {
|
335 | return newPasswordRequiredChallengeUserAttributePrefix;
|
336 | }
|
337 |
|
338 | |
339 |
|
340 |
|
341 |
|
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 | }
|