UNPKG

amazon-cognito-identity-js-node

Version:

Amazon Cognito Identity Provider JavaScript SDK for Node.js

322 lines (273 loc) 10.3 kB
/*! * Copyright 2016 Amazon.com, * Inc. or its affiliates. All Rights Reserved. * * Licensed under the Amazon Software License (the "License"). * You may not use this file except in compliance with the * License. A copy of the License is located at * * http://aws.amazon.com/asl/ * * or in the "license" file accompanying this file. This file is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, express or implied. See the License * for the specific language governing permissions and * limitations under the License. */ "use strict"; var sjcl = require('sjcl'); var BigInteger = require('jsbn').BigInteger; const initN = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' + 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' + '83655D23DCA3AD961C62F356208552BB9ED529077096966D' + '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' + 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' + '15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64' + 'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B' + 'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C' + 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31' + '43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'; const newPasswordRequiredChallengeUserAttributePrefix = 'userAttributes.'; /** @class */ module.exports = class AuthenticationHelper { /** * Constructs a new AuthenticationHelper object * @param {string} PoolName Cognito user pool name. * @param {int} paranoia Random number generation paranoia level. */ constructor(PoolName, paranoia) { this.N = new BigInteger(initN, 16); this.g = new BigInteger('2'); this.k = new BigInteger(this.hexHash(`00${this.N.toString(16)}0${this.g.toString(16)}`), 16); this.paranoia = paranoia; this.smallAValue = this.generateRandomSmallA(); this.largeAValue = this.calculateA(this.smallAValue); this.infoBits = sjcl.codec.utf8String.toBits('Caldera Derived Key'); this.poolName = PoolName; } /** * @returns {BigInteger} small A, a random number */ getSmallAValue() { return this.smallAValue; } /** * @returns {BigInteger} large A, a value generated from small A */ getLargeAValue() { return this.largeAValue; } /** * helper function to generate a random big integer * @returns {BigInteger} a random value. * @private */ generateRandomSmallA() { const words = sjcl.random.randomWords(32, this.paranoia); const hexRandom = sjcl.codec.hex.fromBits(words); const randomBigInt = new BigInteger(hexRandom, 16); const smallABigInt = randomBigInt.mod(this.N); return smallABigInt; } /** * helper function to generate a random string * @returns {string} a random value. * @private */ generateRandomString() { const words = sjcl.random.randomWords(10, this.paranoia); const stringRandom = sjcl.codec.base64.fromBits(words); return stringRandom; } /** * @returns {string} Generated random value included in password hash. */ getRandomPassword() { return this.randomPassword; } /** * @returns {string} Generated random value included in devices hash. */ getSaltDevices() { return this.SaltToHashDevices; } /** * @returns {string} Value used to verify devices. */ getVerifierDevices() { return this.verifierDevices; } /** * Generate salts and compute verifier. * @param {string} deviceGroupKey Devices to generate verifier for. * @param {string} username User to generate verifier for. * @returns {void} */ generateHashDevice(deviceGroupKey, username) { this.randomPassword = this.generateRandomString(); const combinedString = `${deviceGroupKey}${username}:${this.randomPassword}`; const hashedString = this.hash(combinedString); const words = sjcl.random.randomWords(4, this.paranoia); const hexRandom = sjcl.codec.hex.fromBits(words); const saltDevices = new BigInteger(hexRandom, 16); const firstCharSalt = saltDevices.toString(16)[0]; this.SaltToHashDevices = saltDevices.toString(16); if (saltDevices.toString(16).length % 2 === 1) { this.SaltToHashDevices = `0${this.SaltToHashDevices}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharSalt) !== -1) { this.SaltToHashDevices = `00${this.SaltToHashDevices}`; } const verifierDevicesNotPadded = this.g.modPow( new BigInteger(this.hexHash(this.SaltToHashDevices + hashedString), 16), this.N); const firstCharVerifierDevices = verifierDevicesNotPadded.toString(16)[0]; this.verifierDevices = verifierDevicesNotPadded.toString(16); if (verifierDevicesNotPadded.toString(16).length % 2 === 1) { this.verifierDevices = `0${this.verifierDevices}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharVerifierDevices) !== -1) { this.verifierDevices = `00${this.verifierDevices}`; } } /** * Calculate the client's public value A = g^a%N * with the generated random number a * @param {BigInteger} a Randomly generated small A. * @returns {BigInteger} Computed large A. * @private */ calculateA(a) { const A = this.g.modPow(a, this.N); if (A.mod(this.N).toString() === '0') { throw new Error('Illegal paramater. A mod N cannot be 0.'); } return A; } /** * Calculate the client's value U which is the hash of A and B * @param {BigInteger} A Large A value. * @param {BigInteger} B Server B value. * @returns {BigInteger} Computed U value. * @private */ calculateU(A, B) { const firstCharA = A.toString(16)[0]; const firstCharB = B.toString(16)[0]; let AToHash = A.toString(16); let BToHash = B.toString(16); if (A.toString(16).length % 2 === 1) { AToHash = `0${AToHash}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharA) !== -1) { AToHash = `00${AToHash}`; } if (B.toString(16).length % 2 === 1) { BToHash = `0${BToHash}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharB) !== -1) { BToHash = `00${BToHash}`; } this.UHexHash = this.hexHash(AToHash + BToHash); const finalU = new BigInteger(this.UHexHash, 16); return finalU; } /** * Calculate a hash from a bitArray * @param {sjcl.bitArray} bitArray Value to hash. * @returns {String} Hex-encoded hash. * @private */ hash(bitArray) { const hashHex = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(bitArray)); return (new Array(64 - hashHex.length).join('0')) + hashHex; } /** * Calculate a hash from a hex string * @param {String} hexStr Value to hash. * @returns {String} Hex-encoded hash. * @private */ hexHash(hexStr) { const hashHex = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(sjcl.codec.hex.toBits(hexStr))); return (new Array(64 - hashHex.length).join('0')) + hashHex; } /** * Standard hkdf algorithm * @param {sjcl.bitArray} ikm Input key material. * @param {sjcl.bitArray} salt Salt value. * @returns {sjcl.bitArray} Strong key material. * @private */ computehkdf(ikm, salt) { const mac = new sjcl.misc.hmac(salt, sjcl.hash.sha256); mac.update(ikm); const prk = mac.digest(); const hmac = new sjcl.misc.hmac(prk, sjcl.hash.sha256); const infoBitsUpdate = sjcl.bitArray.concat( this.infoBits, sjcl.codec.utf8String.toBits(String.fromCharCode(1))); hmac.update(infoBitsUpdate); return sjcl.bitArray.clamp(hmac.digest(), 128); } /** * Calculates the final hkdf based on computed S value, and computed U value and the key * @param {String} username Username. * @param {String} password Password. * @param {BigInteger} serverBValue Server B value. * @param {BigInteger} salt Generated salt. * @returns {sjcl.bitArray} Computed HKDF value. */ getPasswordAuthenticationKey(username, password, serverBValue, salt) { if (serverBValue.mod(this.N).equals(new BigInteger('0', 16))) { throw new Error('B cannot be zero.'); } this.UValue = this.calculateU(this.largeAValue, serverBValue); if (this.UValue.equals(new BigInteger('0', 16))) { throw new Error('U cannot be zero.'); } const usernamePassword = `${this.poolName}${username}:${password}`; const usernamePasswordHash = this.hash(usernamePassword); const firstCharSalt = salt.toString(16)[0]; let SaltToHash = salt.toString(16); if (salt.toString(16).length % 2 === 1) { SaltToHash = `0${SaltToHash}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharSalt) !== -1) { SaltToHash = `00${SaltToHash}`; } const xValue = new BigInteger(this.hexHash(SaltToHash + usernamePasswordHash), 16); const gModPowXN = this.g.modPow(xValue, this.N); const intValue2 = serverBValue.subtract(this.k.multiply(gModPowXN)); const sValue = intValue2.modPow( this.smallAValue.add(this.UValue.multiply(xValue)), this.N ).mod(this.N); let SToHash = sValue.toString(16); const firstCharS = sValue.toString(16)[0]; if (sValue.toString(16).length % 2 === 1) { SToHash = `0${SToHash}`; } else if ('89ABCDEFabcdef'.indexOf(firstCharS) !== -1) { SToHash = `00${SToHash}`; } let UValueToHash = this.UHexHash; const firstCharU = this.UHexHash[0]; if (this.UHexHash.length % 2 === 1) { UValueToHash = `0${UValueToHash}`; } else if (this.UHexHash.length % 2 === 0 && '89ABCDEFabcdef'.indexOf(firstCharU) !== -1) { UValueToHash = `00${UValueToHash}`; } const hkdf = this.computehkdf( sjcl.codec.hex.toBits(SToHash), sjcl.codec.hex.toBits(UValueToHash)); return hkdf; } /** * Return constant newPasswordRequiredChallengeUserAttributePrefix * @return {newPasswordRequiredChallengeUserAttributePrefix} constant prefix value */ getNewPasswordRequiredChallengeUserAttributePrefix() { return newPasswordRequiredChallengeUserAttributePrefix; } }