UNPKG

amazon-cognito-identity-js

Version:

Amazon Cognito Identity Provider JavaScript SDK

898 lines (847 loc) • 18.8 kB
import AuthenticationHelper from '../src/AuthenticationHelper'; import BigInteger from '../src/BigInteger'; import { Sha256 } from '@aws-crypto/sha256-js'; import { promisifyCallback } from './util'; import { bigIntError } from './constants'; const instance = new AuthenticationHelper('TestPoolName'); describe('AuthenticatorHelper for padHex ', () => { /* Test cases generated in Java with: import java.math.BigInteger; public class Main { private static final char[] HEX_ARRAY = '0123456789ABCDEF'.toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } return new String(hexChars); } public static void main(String[] args) { for(int i = -256; i <=256; i++) { byte arr[] = BigInteger.valueOf(i).toByteArray(); System.out.println('[' + i +', '' + bytesToHex(arr) + ''],'); } } } */ test.each([ [-256, 'FF00'], [-255, 'FF01'], [-254, 'FF02'], [-253, 'FF03'], [-252, 'FF04'], [-251, 'FF05'], [-250, 'FF06'], [-249, 'FF07'], [-248, 'FF08'], [-247, 'FF09'], [-246, 'FF0A'], [-245, 'FF0B'], [-244, 'FF0C'], [-243, 'FF0D'], [-242, 'FF0E'], [-241, 'FF0F'], [-240, 'FF10'], [-239, 'FF11'], [-238, 'FF12'], [-237, 'FF13'], [-236, 'FF14'], [-235, 'FF15'], [-234, 'FF16'], [-233, 'FF17'], [-232, 'FF18'], [-231, 'FF19'], [-230, 'FF1A'], [-229, 'FF1B'], [-228, 'FF1C'], [-227, 'FF1D'], [-226, 'FF1E'], [-225, 'FF1F'], [-224, 'FF20'], [-223, 'FF21'], [-222, 'FF22'], [-221, 'FF23'], [-220, 'FF24'], [-219, 'FF25'], [-218, 'FF26'], [-217, 'FF27'], [-216, 'FF28'], [-215, 'FF29'], [-214, 'FF2A'], [-213, 'FF2B'], [-212, 'FF2C'], [-211, 'FF2D'], [-210, 'FF2E'], [-209, 'FF2F'], [-208, 'FF30'], [-207, 'FF31'], [-206, 'FF32'], [-205, 'FF33'], [-204, 'FF34'], [-203, 'FF35'], [-202, 'FF36'], [-201, 'FF37'], [-200, 'FF38'], [-199, 'FF39'], [-198, 'FF3A'], [-197, 'FF3B'], [-196, 'FF3C'], [-195, 'FF3D'], [-194, 'FF3E'], [-193, 'FF3F'], [-192, 'FF40'], [-191, 'FF41'], [-190, 'FF42'], [-189, 'FF43'], [-188, 'FF44'], [-187, 'FF45'], [-186, 'FF46'], [-185, 'FF47'], [-184, 'FF48'], [-183, 'FF49'], [-182, 'FF4A'], [-181, 'FF4B'], [-180, 'FF4C'], [-179, 'FF4D'], [-178, 'FF4E'], [-177, 'FF4F'], [-176, 'FF50'], [-175, 'FF51'], [-174, 'FF52'], [-173, 'FF53'], [-172, 'FF54'], [-171, 'FF55'], [-170, 'FF56'], [-169, 'FF57'], [-168, 'FF58'], [-167, 'FF59'], [-166, 'FF5A'], [-165, 'FF5B'], [-164, 'FF5C'], [-163, 'FF5D'], [-162, 'FF5E'], [-161, 'FF5F'], [-160, 'FF60'], [-159, 'FF61'], [-158, 'FF62'], [-157, 'FF63'], [-156, 'FF64'], [-155, 'FF65'], [-154, 'FF66'], [-153, 'FF67'], [-152, 'FF68'], [-151, 'FF69'], [-150, 'FF6A'], [-149, 'FF6B'], [-148, 'FF6C'], [-147, 'FF6D'], [-146, 'FF6E'], [-145, 'FF6F'], [-144, 'FF70'], [-143, 'FF71'], [-142, 'FF72'], [-141, 'FF73'], [-140, 'FF74'], [-139, 'FF75'], [-138, 'FF76'], [-137, 'FF77'], [-136, 'FF78'], [-135, 'FF79'], [-134, 'FF7A'], [-133, 'FF7B'], [-132, 'FF7C'], [-131, 'FF7D'], [-130, 'FF7E'], [-129, 'FF7F'], [-128, '80'], [-127, '81'], [-126, '82'], [-125, '83'], [-124, '84'], [-123, '85'], [-122, '86'], [-121, '87'], [-120, '88'], [-119, '89'], [-118, '8A'], [-117, '8B'], [-116, '8C'], [-115, '8D'], [-114, '8E'], [-113, '8F'], [-112, '90'], [-111, '91'], [-110, '92'], [-109, '93'], [-108, '94'], [-107, '95'], [-106, '96'], [-105, '97'], [-104, '98'], [-103, '99'], [-102, '9A'], [-101, '9B'], [-100, '9C'], [-99, '9D'], [-98, '9E'], [-97, '9F'], [-96, 'A0'], [-95, 'A1'], [-94, 'A2'], [-93, 'A3'], [-92, 'A4'], [-91, 'A5'], [-90, 'A6'], [-89, 'A7'], [-88, 'A8'], [-87, 'A9'], [-86, 'AA'], [-85, 'AB'], [-84, 'AC'], [-83, 'AD'], [-82, 'AE'], [-81, 'AF'], [-80, 'B0'], [-79, 'B1'], [-78, 'B2'], [-77, 'B3'], [-76, 'B4'], [-75, 'B5'], [-74, 'B6'], [-73, 'B7'], [-72, 'B8'], [-71, 'B9'], [-70, 'BA'], [-69, 'BB'], [-68, 'BC'], [-67, 'BD'], [-66, 'BE'], [-65, 'BF'], [-64, 'C0'], [-63, 'C1'], [-62, 'C2'], [-61, 'C3'], [-60, 'C4'], [-59, 'C5'], [-58, 'C6'], [-57, 'C7'], [-56, 'C8'], [-55, 'C9'], [-54, 'CA'], [-53, 'CB'], [-52, 'CC'], [-51, 'CD'], [-50, 'CE'], [-49, 'CF'], [-48, 'D0'], [-47, 'D1'], [-46, 'D2'], [-45, 'D3'], [-44, 'D4'], [-43, 'D5'], [-42, 'D6'], [-41, 'D7'], [-40, 'D8'], [-39, 'D9'], [-38, 'DA'], [-37, 'DB'], [-36, 'DC'], [-35, 'DD'], [-34, 'DE'], [-33, 'DF'], [-32, 'E0'], [-31, 'E1'], [-30, 'E2'], [-29, 'E3'], [-28, 'E4'], [-27, 'E5'], [-26, 'E6'], [-25, 'E7'], [-24, 'E8'], [-23, 'E9'], [-22, 'EA'], [-21, 'EB'], [-20, 'EC'], [-19, 'ED'], [-18, 'EE'], [-17, 'EF'], [-16, 'F0'], [-15, 'F1'], [-14, 'F2'], [-13, 'F3'], [-12, 'F4'], [-11, 'F5'], [-10, 'F6'], [-9, 'F7'], [-8, 'F8'], [-7, 'F9'], [-6, 'FA'], [-5, 'FB'], [-4, 'FC'], [-3, 'FD'], [-2, 'FE'], [-1, 'FF'], [0, '00'], [1, '01'], [2, '02'], [3, '03'], [4, '04'], [5, '05'], [6, '06'], [7, '07'], [8, '08'], [9, '09'], [10, '0A'], [11, '0B'], [12, '0C'], [13, '0D'], [14, '0E'], [15, '0F'], [16, '10'], [17, '11'], [18, '12'], [19, '13'], [20, '14'], [21, '15'], [22, '16'], [23, '17'], [24, '18'], [25, '19'], [26, '1A'], [27, '1B'], [28, '1C'], [29, '1D'], [30, '1E'], [31, '1F'], [32, '20'], [33, '21'], [34, '22'], [35, '23'], [36, '24'], [37, '25'], [38, '26'], [39, '27'], [40, '28'], [41, '29'], [42, '2A'], [43, '2B'], [44, '2C'], [45, '2D'], [46, '2E'], [47, '2F'], [48, '30'], [49, '31'], [50, '32'], [51, '33'], [52, '34'], [53, '35'], [54, '36'], [55, '37'], [56, '38'], [57, '39'], [58, '3A'], [59, '3B'], [60, '3C'], [61, '3D'], [62, '3E'], [63, '3F'], [64, '40'], [65, '41'], [66, '42'], [67, '43'], [68, '44'], [69, '45'], [70, '46'], [71, '47'], [72, '48'], [73, '49'], [74, '4A'], [75, '4B'], [76, '4C'], [77, '4D'], [78, '4E'], [79, '4F'], [80, '50'], [81, '51'], [82, '52'], [83, '53'], [84, '54'], [85, '55'], [86, '56'], [87, '57'], [88, '58'], [89, '59'], [90, '5A'], [91, '5B'], [92, '5C'], [93, '5D'], [94, '5E'], [95, '5F'], [96, '60'], [97, '61'], [98, '62'], [99, '63'], [100, '64'], [101, '65'], [102, '66'], [103, '67'], [104, '68'], [105, '69'], [106, '6A'], [107, '6B'], [108, '6C'], [109, '6D'], [110, '6E'], [111, '6F'], [112, '70'], [113, '71'], [114, '72'], [115, '73'], [116, '74'], [117, '75'], [118, '76'], [119, '77'], [120, '78'], [121, '79'], [122, '7A'], [123, '7B'], [124, '7C'], [125, '7D'], [126, '7E'], [127, '7F'], [128, '0080'], [129, '0081'], [130, '0082'], [131, '0083'], [132, '0084'], [133, '0085'], [134, '0086'], [135, '0087'], [136, '0088'], [137, '0089'], [138, '008A'], [139, '008B'], [140, '008C'], [141, '008D'], [142, '008E'], [143, '008F'], [144, '0090'], [145, '0091'], [146, '0092'], [147, '0093'], [148, '0094'], [149, '0095'], [150, '0096'], [151, '0097'], [152, '0098'], [153, '0099'], [154, '009A'], [155, '009B'], [156, '009C'], [157, '009D'], [158, '009E'], [159, '009F'], [160, '00A0'], [161, '00A1'], [162, '00A2'], [163, '00A3'], [164, '00A4'], [165, '00A5'], [166, '00A6'], [167, '00A7'], [168, '00A8'], [169, '00A9'], [170, '00AA'], [171, '00AB'], [172, '00AC'], [173, '00AD'], [174, '00AE'], [175, '00AF'], [176, '00B0'], [177, '00B1'], [178, '00B2'], [179, '00B3'], [180, '00B4'], [181, '00B5'], [182, '00B6'], [183, '00B7'], [184, '00B8'], [185, '00B9'], [186, '00BA'], [187, '00BB'], [188, '00BC'], [189, '00BD'], [190, '00BE'], [191, '00BF'], [192, '00C0'], [193, '00C1'], [194, '00C2'], [195, '00C3'], [196, '00C4'], [197, '00C5'], [198, '00C6'], [199, '00C7'], [200, '00C8'], [201, '00C9'], [202, '00CA'], [203, '00CB'], [204, '00CC'], [205, '00CD'], [206, '00CE'], [207, '00CF'], [208, '00D0'], [209, '00D1'], [210, '00D2'], [211, '00D3'], [212, '00D4'], [213, '00D5'], [214, '00D6'], [215, '00D7'], [216, '00D8'], [217, '00D9'], [218, '00DA'], [219, '00DB'], [220, '00DC'], [221, '00DD'], [222, '00DE'], [223, '00DF'], [224, '00E0'], [225, '00E1'], [226, '00E2'], [227, '00E3'], [228, '00E4'], [229, '00E5'], [230, '00E6'], [231, '00E7'], [232, '00E8'], [233, '00E9'], [234, '00EA'], [235, '00EB'], [236, '00EC'], [237, '00ED'], [238, '00EE'], [239, '00EF'], [240, '00F0'], [241, '00F1'], [242, '00F2'], [243, '00F3'], [244, '00F4'], [245, '00F5'], [246, '00F6'], [247, '00F7'], [248, '00F8'], [249, '00F9'], [250, '00FA'], [251, '00FB'], [252, '00FC'], [253, '00FD'], [254, '00FE'], [255, '00FF'], [256, '0100'], ])('padHex(bigInteger.fromInt(%p))\t=== %p', (i, expected) => { const bigInt = new BigInteger(); bigInt.fromInt(i); const x = instance.padHex(bigInt); expect(x.toLowerCase()).toBe(expected.toLowerCase()); }); }); describe('Getters for AuthHelper class', () => { test('getSmallA() should match the instance variable', () => { expect(instance.getSmallAValue()).toBe(instance.smallAValue); }); test('getRandomPassword() should match instance variable', () => { expect(instance.getRandomPassword()).toBe(instance.randomPassword); }); test('getSaltDevices() should match instance variable SaltDevices', () => { expect(instance.getSaltDevices()).toBe(instance.SaltToHashDevices); }); test('getVerifierDevices() should match instance variable verifierDevices', () => { expect(instance.getVerifierDevices()).toBe(instance.verifierDevices); }); test('Constant prefix for new password challenge', () => { expect( instance.getNewPasswordRequiredChallengeUserAttributePrefix() ).toEqual('userAttributes.'); }); }); describe('getLargeAValue()', () => { afterAll(() => { jest.restoreAllMocks(); jest.clearAllMocks(); }); instance.largeAValue = null; test('happy path should callback with a calculateA bigInt', async () => { const result = await promisifyCallback(instance, 'getLargeAValue'); expect(result).toEqual(instance.largeAValue); }); test('when largeAValue exists, getLargeA should return it', async () => { expect(instance.largeAValue).not.toBe(null); await promisifyCallback(instance, 'getLargeAValue').then(res => { expect(res).toEqual(instance.largeAValue); }); }); test('mock an error from calculate A', async () => { instance.largeAValue = null; jest .spyOn(AuthenticationHelper.prototype, 'calculateA') .mockImplementationOnce((...[, callback]) => { callback(bigIntError, null); }); await promisifyCallback(instance, 'getLargeAValue').catch(e => { expect(e).toEqual(bigIntError); }); //preserving invariant of largeAValue const cb = jest.fn(); instance.getLargeAValue(cb); }); }); describe('generateRandomSmallA(), generateRandomString()', () => { test('Generate Random Small A is generating a BigInteger', () => { expect(instance.generateRandomSmallA()).toBeInstanceOf(BigInteger); }); test('Ensure that generateRandomSmallA is non deterministic', () => { const firstSmallA = instance.generateRandomSmallA(); const secondSmallA = instance.generateRandomSmallA(); expect(firstSmallA).not.toEqual(secondSmallA); }); test('Generate random strings', () => { //AuthHelper generates 40 randomBytes and convert it to a base64 string expect(instance.generateRandomString().length).toEqual(56); }); test('Generate random strings is non-deterministic', () => { expect(instance.generateRandomString()).not.toEqual( instance.generateRandomString() ); }); }); describe('generateHashDevice()', () => { test('happy path for generate hash devices should instantiate the verifierDevices of the instance', async () => { const deviceGroupKey = instance.generateRandomString(); const username = instance.generateRandomString(); expect(instance.getVerifierDevices()).toEqual(undefined); await promisifyCallback( instance, 'generateHashDevice', deviceGroupKey, username ); expect(instance.getVerifierDevices()).toEqual(instance.verifierDevices); }); test('modPow throws an error', async () => { const deviceGroupKey = instance.generateRandomString(); const username = instance.generateRandomString(); jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...args) => { args[2](bigIntError, null); }); await promisifyCallback( instance, 'generateHashDevice', deviceGroupKey, username ).catch(e => { expect(e).toEqual(bigIntError); }); }); }); describe('calculateA()', () => { const callback = jest.fn(); afterEach(() => { callback.mockClear(); }); afterAll(() => { jest.restoreAllMocks(); }); test('Calculate A happy path', async () => { const result = await promisifyCallback( instance, 'calculateA', instance.smallAValue ); //length of the big integer expect(Object.keys(result).length).toEqual(223); }); test('calculateA gets an error from g.modPow', async () => { jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...[, , callback]) => { callback(bigIntError, null); }); await promisifyCallback(instance, 'calculateA', instance.smallAValue).catch( e => { expect(e).toEqual(bigIntError); } ); }); test('A mod N equals BigInt 0 should throw an illegal parameter error', async () => { jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...[, , callback]) => { callback(null, BigInteger.ZERO); }); await promisifyCallback(instance, 'calculateA', instance.smallAValue).catch( e => { expect(e).toEqual(new Error('Illegal paramater. A mod N cannot be 0.')); } ); }); }); describe('calculateU()', () => { test("Calculate the client's value U", () => { const hexA = new BigInteger('abcd1234', 16); const hexB = new BigInteger('deadbeef', 16); const hashed = instance.hexHash( instance.padHex(hexA) + instance.padHex(hexB) ); const expected = new BigInteger(hashed, 16); const result = instance.calculateU(hexA, hexB); expect(expected).toEqual(result); }); }); describe('hexhash() and hash()', () => { test('Test hexHash function produces a valid hex string with regex', () => { const regEx = /[0-9a-f]/g; const awsCryptoHash = new Sha256(); awsCryptoHash.update('testString'); const resultFromAWSCrypto = awsCryptoHash.digestSync(); const hashHex = Buffer.from(resultFromAWSCrypto).toString('hex'); expect(regEx.test(instance.hexHash(hashHex))).toBe(true); }); test('Hashing a buffer returns a string', () => { const buf = Buffer.from('7468697320697320612074c3a97374', 'binary'); expect(typeof instance.hash(buf)).toBe('string'); }); }); describe('computehkdf()', () => { test('happy path hkdf algorithm returns a length 16 hex string', () => { const inputKey = Buffer.from('secretInputKey', 'ascii'); const salt = Buffer.from('7468697320697320612074c3a97374', 'hex'); const key = instance.computehkdf(inputKey, salt); expect(Object.keys(key).length).toEqual(16); }); }); describe('getPasswordAuthKey()', () => { const username = 'cognitoUser'; const password = 'cognitoPassword'; const badServerValue = BigInteger.ZERO; const realServerValue = new BigInteger('deadbeef', 16); const salt = new BigInteger('deadbeef', 16); afterEach(() => { jest.clearAllMocks(); }); afterAll(() => { jest.restoreAllMocks(); }); test('Happy path should computeHKDF', async () => { const result = await promisifyCallback( instance, 'getPasswordAuthenticationKey', username, password, realServerValue, salt ); expect(Object.keys(result).length).toEqual(16); }); test('failing within calculateS callback', async () => { jest .spyOn(AuthenticationHelper.prototype, 'calculateS') .mockImplementationOnce((...[, , callback]) => { callback(bigIntError, null); }); await promisifyCallback( instance, 'getPasswordAuthenticationKey', username, password, realServerValue, salt ).catch(e => { expect(e).toEqual(bigIntError); }); }); test('Getting a bad server value', async () => { await promisifyCallback( instance, 'getPasswordAuthenticationKey', username, password, badServerValue, salt ).catch(e => { expect(e).toEqual(new Error('B cannot be zero.')); }); }); test('Getting a U Value of zero', async () => { jest .spyOn(AuthenticationHelper.prototype, 'calculateU') .mockImplementationOnce(() => { return BigInteger.ZERO; }); const realServerValue = new BigInteger('deadbeef', 16); await promisifyCallback( instance, 'getPasswordAuthenticationKey', username, password, realServerValue, salt ).catch(e => { expect(e).toEqual(new Error('U cannot be zero.')); }); }); }); describe('calculateS()', () => { const xValue = new BigInteger('deadbeef', 16); const serverValue = new BigInteger('deadbeef', 16); afterEach(() => { jest.restoreAllMocks(); }); test('happy path should callback with null, and a bigInteger', async () => { instance.k = new BigInteger('deadbeef', 16); instance.UValue = instance.calculateU(instance.largeAValue, xValue); const result = await promisifyCallback( instance, 'calculateS', xValue, serverValue ); //length of the big integer expect(Object.keys(result).length).toEqual(113); }); test('modPow throws an error ', async () => { jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...args) => { args[2](bigIntError, null); }); await promisifyCallback(instance, 'calculateS', xValue, serverValue).catch( e => { expect(e).toEqual(bigIntError); } ); }); test('second modPow throws an error ', async () => { // need to mock a working modPow to then fail in the second mock jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...args) => { args[2](null, new BigInteger('deadbeef', 16)); }); jest .spyOn(BigInteger.prototype, 'modPow') .mockImplementationOnce((...args) => { args[2](bigIntError, null); }); await promisifyCallback(instance, 'calculateS', xValue, serverValue).catch( e => { expect(e).toEqual(bigIntError); } ); }); });