UNPKG

amazon-cognito-identity-js

Version:

Amazon Cognito Identity Provider JavaScript SDK

346 lines (316 loc) • 13 kB
/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import { Buffer } from 'buffer'; import WordArray from './utils/WordArray'; import { Sha256 } from '@aws-crypto/sha256-js'; /** * Returns a Buffer with a sequence of random nBytes * * @param {number} nBytes * @returns {Buffer} fixed-length sequence of random bytes */ function randomBytes(nBytes) { return Buffer.from(new WordArray().random(nBytes).toString(), 'hex'); } import BigInteger from './BigInteger'; /** * Tests if a hex string has it most significant bit set (case-insensitive regex) */ var HEX_MSB_REGEX = /^[89a-f]/i; var initN = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' + 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' + '83655D23DCA3AD961C62F356208552BB9ED529077096966D' + '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' + 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' + '15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64' + 'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' + 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B' + 'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C' + 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31' + '43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'; var newPasswordRequiredChallengeUserAttributePrefix = 'userAttributes.'; /** @class */ var AuthenticationHelper = /*#__PURE__*/function () { /** * Constructs a new AuthenticationHelper object * @param {string} PoolName Cognito user pool name. */ function AuthenticationHelper(PoolName) { this.N = new BigInteger(initN, 16); this.g = new BigInteger('2', 16); this.k = new BigInteger(this.hexHash("" + this.padHex(this.N) + this.padHex(this.g)), 16); this.smallAValue = this.generateRandomSmallA(); this.getLargeAValue(function () {}); this.infoBits = Buffer.from('Caldera Derived Key', 'utf8'); this.poolName = PoolName; } /** * @returns {BigInteger} small A, a random number */ var _proto = AuthenticationHelper.prototype; _proto.getSmallAValue = function getSmallAValue() { return this.smallAValue; } /** * @param {nodeCallback<BigInteger>} callback Called with (err, largeAValue) * @returns {void} */; _proto.getLargeAValue = function getLargeAValue(callback) { var _this = this; if (this.largeAValue) { callback(null, this.largeAValue); } else { this.calculateA(this.smallAValue, function (err, largeAValue) { if (err) { callback(err, null); } _this.largeAValue = largeAValue; callback(null, _this.largeAValue); }); } } /** * helper function to generate a random big integer * @returns {BigInteger} a random value. * @private */; _proto.generateRandomSmallA = function generateRandomSmallA() { // This will be interpreted as a postive 128-bit integer var hexRandom = randomBytes(128).toString('hex'); var randomBigInt = new BigInteger(hexRandom, 16); // There is no need to do randomBigInt.mod(this.N - 1) as N (3072-bit) is > 128 bytes (1024-bit) return randomBigInt; } /** * helper function to generate a random string * @returns {string} a random value. * @private */; _proto.generateRandomString = function generateRandomString() { return randomBytes(40).toString('base64'); } /** * @returns {string} Generated random value included in password hash. */; _proto.getRandomPassword = function getRandomPassword() { return this.randomPassword; } /** * @returns {string} Generated random value included in devices hash. */; _proto.getSaltDevices = function getSaltDevices() { return this.SaltToHashDevices; } /** * @returns {string} Value used to verify devices. */; _proto.getVerifierDevices = function 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. * @param {nodeCallback<null>} callback Called with (err, null) * @returns {void} */; _proto.generateHashDevice = function generateHashDevice(deviceGroupKey, username, callback) { var _this2 = this; this.randomPassword = this.generateRandomString(); var combinedString = "" + deviceGroupKey + username + ":" + this.randomPassword; var hashedString = this.hash(combinedString); var hexRandom = randomBytes(16).toString('hex'); // The random hex will be unambiguously represented as a postive integer this.SaltToHashDevices = this.padHex(new BigInteger(hexRandom, 16)); this.g.modPow(new BigInteger(this.hexHash(this.SaltToHashDevices + hashedString), 16), this.N, function (err, verifierDevicesNotPadded) { if (err) { callback(err, null); } _this2.verifierDevices = _this2.padHex(verifierDevicesNotPadded); callback(null, null); }); } /** * Calculate the client's public value A = g^a%N * with the generated random number a * @param {BigInteger} a Randomly generated small A. * @param {nodeCallback<BigInteger>} callback Called with (err, largeAValue) * @returns {void} * @private */; _proto.calculateA = function calculateA(a, callback) { var _this3 = this; this.g.modPow(a, this.N, function (err, A) { if (err) { callback(err, null); } if (A.mod(_this3.N).equals(BigInteger.ZERO)) { callback(new Error('Illegal paramater. A mod N cannot be 0.'), null); } callback(null, 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 */; _proto.calculateU = function calculateU(A, B) { this.UHexHash = this.hexHash(this.padHex(A) + this.padHex(B)); var finalU = new BigInteger(this.UHexHash, 16); return finalU; } /** * Calculate a hash from a bitArray * @param {Buffer} buf Value to hash. * @returns {String} Hex-encoded hash. * @private */; _proto.hash = function hash(buf) { var awsCryptoHash = new Sha256(); awsCryptoHash.update(buf); var resultFromAWSCrypto = awsCryptoHash.digestSync(); var hashHex = Buffer.from(resultFromAWSCrypto).toString('hex'); 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 */; _proto.hexHash = function hexHash(hexStr) { return this.hash(Buffer.from(hexStr, 'hex')); } /** * Standard hkdf algorithm * @param {Buffer} ikm Input key material. * @param {Buffer} salt Salt value. * @returns {Buffer} Strong key material. * @private */; _proto.computehkdf = function computehkdf(ikm, salt) { var infoBitsBuffer = Buffer.concat([this.infoBits, Buffer.from(String.fromCharCode(1), 'utf8')]); var awsCryptoHash = new Sha256(salt); awsCryptoHash.update(ikm); var resultFromAWSCryptoPrk = awsCryptoHash.digestSync(); var awsCryptoHashHmac = new Sha256(resultFromAWSCryptoPrk); awsCryptoHashHmac.update(infoBitsBuffer); var resultFromAWSCryptoHmac = awsCryptoHashHmac.digestSync(); var hashHexFromAWSCrypto = resultFromAWSCryptoHmac; var currentHex = hashHexFromAWSCrypto.slice(0, 16); return currentHex; } /** * 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. * @param {nodeCallback<Buffer>} callback Called with (err, hkdfValue) * @returns {void} */; _proto.getPasswordAuthenticationKey = function getPasswordAuthenticationKey(username, password, serverBValue, salt, callback) { var _this4 = this; if (serverBValue.mod(this.N).equals(BigInteger.ZERO)) { throw new Error('B cannot be zero.'); } this.UValue = this.calculateU(this.largeAValue, serverBValue); if (this.UValue.equals(BigInteger.ZERO)) { throw new Error('U cannot be zero.'); } var usernamePassword = "" + this.poolName + username + ":" + password; var usernamePasswordHash = this.hash(usernamePassword); var xValue = new BigInteger(this.hexHash(this.padHex(salt) + usernamePasswordHash), 16); this.calculateS(xValue, serverBValue, function (err, sValue) { if (err) { callback(err, null); } var hkdf = _this4.computehkdf(Buffer.from(_this4.padHex(sValue), 'hex'), Buffer.from(_this4.padHex(_this4.UValue), 'hex')); callback(null, hkdf); }); } /** * Calculates the S value used in getPasswordAuthenticationKey * @param {BigInteger} xValue Salted password hash value. * @param {BigInteger} serverBValue Server B value. * @param {nodeCallback<string>} callback Called on success or error. * @returns {void} */; _proto.calculateS = function calculateS(xValue, serverBValue, callback) { var _this5 = this; this.g.modPow(xValue, this.N, function (err, gModPowXN) { if (err) { callback(err, null); } var intValue2 = serverBValue.subtract(_this5.k.multiply(gModPowXN)); intValue2.modPow(_this5.smallAValue.add(_this5.UValue.multiply(xValue)), _this5.N, function (err2, result) { if (err2) { callback(err2, null); } callback(null, result.mod(_this5.N)); }); }); } /** * Return constant newPasswordRequiredChallengeUserAttributePrefix * @return {newPasswordRequiredChallengeUserAttributePrefix} constant prefix value */; _proto.getNewPasswordRequiredChallengeUserAttributePrefix = function getNewPasswordRequiredChallengeUserAttributePrefix() { return newPasswordRequiredChallengeUserAttributePrefix; } /** * Returns an unambiguous, even-length hex string of the two's complement encoding of an integer. * * It is compatible with the hex encoding of Java's BigInteger's toByteArray(), wich returns a * byte array containing the two's-complement representation of a BigInteger. The array contains * the minimum number of bytes required to represent the BigInteger, including at least one sign bit. * * Examples showing how ambiguity is avoided by left padding with: * "00" (for positive values where the most-significant-bit is set) * "FF" (for negative values where the most-significant-bit is set) * * padHex(bigInteger.fromInt(-236)) === "FF14" * padHex(bigInteger.fromInt(20)) === "14" * * padHex(bigInteger.fromInt(-200)) === "FF38" * padHex(bigInteger.fromInt(56)) === "38" * * padHex(bigInteger.fromInt(-20)) === "EC" * padHex(bigInteger.fromInt(236)) === "00EC" * * padHex(bigInteger.fromInt(-56)) === "C8" * padHex(bigInteger.fromInt(200)) === "00C8" * * @param {BigInteger} bigInt Number to encode. * @returns {String} even-length hex string of the two's complement encoding. */; _proto.padHex = function padHex(bigInt) { if (!(bigInt instanceof BigInteger)) { throw new Error('Not a BigInteger'); } var isNegative = bigInt.compareTo(BigInteger.ZERO) < 0; /* Get a hex string for abs(bigInt) */ var hexStr = bigInt.abs().toString(16); /* Pad hex to even length if needed */ hexStr = hexStr.length % 2 !== 0 ? "0" + hexStr : hexStr; /* Prepend "00" if the most significant bit is set */ hexStr = HEX_MSB_REGEX.test(hexStr) ? "00" + hexStr : hexStr; if (isNegative) { /* Flip the bits of the representation */ var invertedNibbles = hexStr.split('').map(function (x) { var invertedNibble = ~parseInt(x, 16) & 0xf; return '0123456789ABCDEF'.charAt(invertedNibble); }).join(''); /* After flipping the bits, add one to get the 2's complement representation */ var flippedBitsBI = new BigInteger(invertedNibbles, 16).add(BigInteger.ONE); hexStr = flippedBitsBI.toString(16); /* For hex strings starting with 'FF8', 'FF' can be dropped, e.g. 0xFFFF80=0xFF80=0x80=-128 Any sequence of '1' bits on the left can always be substituted with a single '1' bit without changing the represented value. This only happens in the case when the input is 80...00 */ if (hexStr.toUpperCase().startsWith('FF8')) { hexStr = hexStr.substring(2); } } return hexStr; }; return AuthenticationHelper; }(); export { AuthenticationHelper as default };