4 const assert
= require('assert');
5 const HOTP
= require('../lib/hotp');
7 describe('HOTP', function () {
11 { count: 0n
, hmac: Buffer
.from('cc93cf18508d94934c64b65d8ba7667fb7cde4b0', 'hex'), hex: '4c93cf18', dec: 1284755224, hotp: '755224' },
12 { count: 1n
, hmac: Buffer
.from('75a48a19d4cbe100644e8ac1397eea747a2d33ab', 'hex'), hex: '41397eea', dec: 1094287082, hotp: '287082' },
13 { count: 2n
, hmac: Buffer
.from('0bacb7fa082fef30782211938bc1c5e70416ff44', 'hex'), hex: '82fef30', dec: 137359152, hotp: '359152' },
14 { count: 3n
, hmac: Buffer
.from('66c28227d03a2d5529262ff016a1e6ef76557ece', 'hex'), hex: '66ef7655', dec: 1726969429, hotp: '969429' },
15 { count: 4n
, hmac: Buffer
.from('a904c900a64b35909874b33e61c5938a8e15ed1c', 'hex'), hex: '61c5938a', dec: 1640338314, hotp: '338314' },
16 { count: 5n
, hmac: Buffer
.from('a37e783d7b7233c083d4f62926c7a25f238d0316', 'hex'), hex: '33c083d4', dec: 868254676, hotp: '254676' },
17 { count: 6n
, hmac: Buffer
.from('bc9cd28561042c83f219324d3c607256c03272ae', 'hex'), hex: '7256c032', dec: 1918287922, hotp: '287922' },
18 { count: 7n
, hmac: Buffer
.from('a4fb960c0bc06e1eabb804e5b397cdc4b45596fa', 'hex'), hex: '4e5b397', dec: 82162583, hotp: '162583' },
19 { count: 8n
, hmac: Buffer
.from('1b3c89f65e6c9e883012052823443f048b4332db', 'hex'), hex: '2823443f', dec: 673399871, hotp: '399871' },
20 { count: 9n
, hmac: Buffer
.from('1637409809a679dc698207310c8c7fc07290d9e5', 'hex'), hex: '2679dc69', dec: 645520489, hotp: '520489' },
23 beforeEach(function () {
25 key: Buffer
.from('12345678901234567890'),
27 hotp
= new HOTP(options
);
30 describe('constructor', function () {
31 it('covers invalid key', function () {
32 options
.key
= Buffer
.from('blah');
33 assert
.throws(() => new HOTP(options
), RangeError
);
35 it('covers invalid algorithm', function () {
36 options
.algorithm
= 'md5';
37 assert
.throws(() => new HOTP(options
), RangeError
);
39 it('covers missing options', function () {
40 assert
.throws(() => new HOTP());
42 it('covers counter type conversion', function () {
44 hotp
= new HOTP(options
);
45 assert
.strictEqual(hotp
.counter
, 4n
);
47 it('covers key specified as buffer', function () {
48 options
.keyEncoding
= 'buffer';
49 hotp
= new HOTP(options
);
52 it('covers key specified as base32', function () {
53 options
.key
= 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';
54 options
.keyEncoding
= 'base32';
55 hotp
= new HOTP(options
);
58 it('covers key specified as ascii', function () {
59 options
.key
= '12345678901234567890';
60 options
.keyEncoding
= 'ascii';
61 hotp
= new HOTP(options
);
66 it('_hmac sanity', function () {
67 for (const test
of tests
) {
68 const result
= hotp
._hmac(test
.count
);
69 assert
.strictEqual(Buffer
.compare(result
, test
.hmac
), 0, `count: ${test.count} result: ${result.toString('hex')} expected: ${test.hmac.toString('hex')}`);
73 it('_truncate sanity', function () {
74 for (const test
of tests
) {
75 const result
= hotp
._truncate(test
.count
);
76 assert
.strictEqual(result
, test
.dec
, `count: ${test.count} result: ${result} expected: ${test.dec} (hex: ${result.toString(16)} hexpected: ${test.hex})`);
78 }); // _truncate sanity
80 it('generate', function () {
81 for (const test
of tests
) {
82 const result
= hotp
.generate();
83 assert
.strictEqual(result
, test
.hotp
, `count: ${test.count} htop.counter: ${hotp.counter} result: ${result} expected: ${test.hotp}`);
84 assert
.strictEqual(hotp
.counter
, test
.count
+ 1n
);
88 it('covers generate with count', function () {
89 const result
= hotp
.generate(3n
);
90 assert
.strictEqual(result
, tests
[3].hotp
);
91 }); // generate coverage
93 describe('validate', function () {
94 it('accepts correct hotp', function () {
95 const result
= hotp
.validate(tests
[2].hotp
, 2n
);
96 assert
.strictEqual(result
, true);
100 describe('createKey', function () {
101 const algorithm
= 'sha1';
102 it('creates a sha1 key by default', async
function () {
103 const key
= await HOTP
.createKey();
104 assert
.strictEqual(key
.length
, 40);
106 it('creates a sha1 key', async
function () {
107 const key
= await HOTP
.createKey(algorithm
);
108 assert
.strictEqual(key
.length
, 40);
110 it('creates a sha1 key in base32 encoding', async
function () {
111 const key
= await HOTP
.createKey(algorithm
, 'base32');
112 assert
.strictEqual(key
.length
, 32);
114 it('creates a sha1 key in alternate encoding', async
function () {
115 const key
= await HOTP
.createKey(algorithm
, 'base64');
116 assert
.strictEqual(key
.length
, 28);
118 it('creates a sha1 key as a buffer', async
function () {
119 const key
= await HOTP
.createKey(algorithm
, 'buffer');
120 assert(Buffer
.isBuffer(key
));
122 it('rejects unknown algorithm', async
function () {
123 await assert
.rejects(() => HOTP
.createKey('md5'), RangeError
);
127 describe('createKeySVG', function () {
128 let options
, key
, keyEncoding
;
129 beforeEach(function () {
132 accountname: 'test@example.com',
136 keyEncoding
= undefined;
138 it('creates an svg from base32 key', function () {
139 key
= 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';
140 keyEncoding
= 'base32';
141 const { secret
, svg
, uri
} = HOTP
.createKeySVG(options
, key
, keyEncoding
);
143 assert(uri
.includes('hotp'));
144 assert
.strictEqual(secret
, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
146 it('creates an svg from buffer key', function () {
147 key
= Buffer
.from('12345678901234567890');
148 keyEncoding
= 'buffer';
149 const { secret
, svg
, uri
} = HOTP
.createKeySVG(options
, key
, keyEncoding
);
151 assert(uri
.includes('hotp'));
152 assert
.strictEqual(secret
, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
154 it('creates an svg from buffer key', function () {
155 key
= Buffer
.from('12345678901234567890');
156 const { secret
, svg
, uri
} = HOTP
.createKeySVG(options
, key
, keyEncoding
);
158 assert(uri
.includes('hotp'));
159 assert
.strictEqual(secret
, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
161 it('creates an svg from ascii key', function () {
162 key
= '12345678901234567890';
163 keyEncoding
= 'ascii';
164 const { secret
, svg
, uri
} = HOTP
.createKeySVG(options
, key
, keyEncoding
);
166 assert(uri
.includes('hotp'));
167 assert
.strictEqual(secret
, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
171 describe('_qrURI', function () {
173 beforeEach(function () {
175 secret: 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
176 accountname: 'test@example.com',
181 it('requires accountname', function () {
182 delete options
.accountname
;
183 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
185 it('requires secret', function () {
186 delete options
.secret
;
187 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
189 it('requires counter', function () {
190 delete options
.counter
;
191 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
193 it('requires secret', function () {
194 delete options
.secret
;
195 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
197 it('requires known digits', function () {
199 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
201 it('requires known algorithm', function () {
202 options
.algorithm
= 'md5';
203 assert
.throws(() => HOTP
._qrURI(options
), RangeError
);
205 it('covers no issuer', function () {
206 delete options
.issuer
;
207 const uri
= HOTP
._qrURI(options
);
208 assert(!uri
.includes('issuer'));
210 it('covers params', function () {
212 options
.algorithm
= 'sha1';
213 const uri
= HOTP
._qrURI(options
);
214 assert(uri
.includes('digits'));
215 assert(uri
.includes('SHA1'));