1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
// Author: cirakg & claude ai
// Summary: Base32, HOTP, TOTP and Steam tests (RFC 4648, RFC 4226, RFC 6238, seeded vectors incl. 2038 and 2100).
import Toybox.Lang;
import Toybox.Test;
(:test)
function base32MatchesVectors(logger as Test.Logger) as Boolean {
var ok = true;
for (var i = 0; i < TestVectors.BASE32.size(); i++) {
var r = TestSupport.row(TestVectors.BASE32, i);
var data = TestSupport.bytes(r, 0);
var text = TestSupport.str(r, 1);
ok = TestSupport.sameString(logger, "encode " + i, Base32.encode(data), text) && ok;
var decoded = Base32.decode(text);
if (!(decoded instanceof Lang.ByteArray) || !Bytes.equal(decoded, data)) {
logger.error("decode " + i + " failed");
ok = false;
}
}
return ok;
}
// Lower case, spaces, tabs, hyphens and missing padding are all accepted.
(:test)
function base32IsLenient(logger as Test.Logger) as Boolean {
var expected = Bytes.fromUtf8("foobar");
var inputs = ["mzxw6ytboi", "MZXW 6YTB OI", "mzxw-6ytb-oi======", "MZXW6YTB\tOI"];
for (var i = 0; i < inputs.size(); i++) {
var decoded = Base32.decode(inputs[i] as String);
if (!(decoded instanceof Lang.ByteArray) || !Bytes.equal(decoded, expected)) {
logger.error("lenient input " + i + " rejected");
return false;
}
}
return true;
}
// Errors name the right position (1-based) or say it's a length problem (0).
(:test)
function base32ReportsErrors(logger as Test.Logger) as Boolean {
var cases = [["MZX1W6", 4], ["MZ X0", 5], ["MZ=X", 4], ["M", 0], ["MZX", 0], ["MY=", 0], ["MZÄW", 3]];
for (var i = 0; i < cases.size(); i++) {
var c = cases[i] as Array;
var input = c[0] as String;
var result = Base32.decode(input);
if (!(result instanceof Base32.DecodeError) || result.position != (c[1] as Number)) {
logger.error("case " + i + " (" + input + ") not reported as expected");
return false;
}
logger.debug(input + " -> " + result.message);
}
return true;
}
// RFC 4226, RFC 6238 and seeded HOTP/TOTP vectors (period 0 means HOTP).
(:test)
function otpMatchesVectors(logger as Test.Logger) as Boolean {
var ok = true;
for (var i = 0; i < TestVectors.OTP.size(); i++) {
var r = TestSupport.row(TestVectors.OTP, i);
var alg = TestSupport.alg(r, 0);
var secret = TestSupport.bytes(r, 1);
var time = TestSupport.long(r, 2);
var period = TestSupport.num(r, 3);
var digits = TestSupport.num(r, 4);
var code = period == 0 ? Otp.hotp(alg, secret, time, digits) : Otp.totp(alg, secret, time, period, digits);
ok = TestSupport.sameString(logger, "OTP vector " + i, code, TestSupport.str(r, 5)) && ok;
}
logger.debug(TestVectors.OTP.size() + " OTP vectors");
return ok;
}
(:test)
function steamMatchesVectors(logger as Test.Logger) as Boolean {
var ok = true;
for (var i = 0; i < TestVectors.STEAM.size(); i++) {
var r = TestSupport.row(TestVectors.STEAM, i);
var code = Otp.steam(TestSupport.bytes(r, 0), TestSupport.long(r, 1));
ok = TestSupport.sameString(logger, "Steam vector " + i, code, TestSupport.str(r, 2)) && ok;
}
return ok;
}
// Period boundaries: the counter changes exactly at k * period.
(:test)
function totpPeriodBoundaries(logger as Test.Logger) as Boolean {
return Otp.totpCounter(29l, 30) == 0l && Otp.totpCounter(30l, 30) == 1l && Otp.totpCounter(31l, 30) == 1l
&& Otp.secondsRemaining(29l, 30) == 1 && Otp.secondsRemaining(30l, 30) == 30
&& Otp.totpCounter(-5l, 30) == 0l && Otp.secondsRemaining(-5l, 30) == 30
&& Otp.totpCounter(4294967295l, 30) == 143165576l;
}
(:test)
function otpRejectsBadParameters(logger as Test.Logger) as Boolean {
var rejected = 0;
try {
Otp.hotp(Hashes.SHA1, [1]b, 0l, 5);
} catch (e instanceof Lang.InvalidValueException) {
rejected++;
}
try {
Otp.hotp(Hashes.SHA1, [1]b, 0l, 9);
} catch (e instanceof Lang.InvalidValueException) {
rejected++;
}
try {
Otp.totp(Hashes.SHA1, [1]b, 0l, 0, 6);
} catch (e instanceof Lang.InvalidValueException) {
rejected++;
}
try {
Otp.totp(Hashes.SHA1, [1]b, 0l, 301, 6);
} catch (e instanceof Lang.InvalidValueException) {
rejected++;
}
return rejected == 4;
}
|