amazon-cognito-identity-js
Version:
Amazon Cognito Identity Provider JavaScript SDK
898 lines (847 loc) • 18.8 kB
JavaScript
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);
}
);
});
});