aboutsummaryrefslogtreecommitdiff
path: root/test/crypto/KdfAesTest.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-18 23:44:18 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-18 23:44:18 +0200
commit93aa7e3aa291f3328cf34a5a2f86172f41d0affd (patch)
treeac44438e7340fde609ad6458e31e122612cf35d1 /test/crypto/KdfAesTest.mc
parent5646ae7f40dc53c9ebed0b3b6e907cc85fcd5993 (diff)
test: add cross-checked crypto/OTP vectors, unit tests and stricter screenshots
- gen_vectors.py: RFC/FIPS/NIST vectors recomputed with Python's stdlib (openssl for AES) before use, plus seeded cases around SHA padding boundaries, 2038 and 2100; generates test/data/Vectors.mc. - 31 Monkey C tests (hashes, HMAC, PBKDF2, HKDF, AES, sealed records incl. every-byte tamper detection, Base32 errors, 100 OTP and 12 Steam vectors, clock, jobs) and a benchmark; pass on all four test devices. - Screenshot tolerance 0.01 %: one changed character no longer passes. - Golden images updated for version 0.2.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test/crypto/KdfAesTest.mc')
-rw-r--r--test/crypto/KdfAesTest.mc116
1 files changed, 116 insertions, 0 deletions
diff --git a/test/crypto/KdfAesTest.mc b/test/crypto/KdfAesTest.mc
new file mode 100644
index 0000000..6587bc9
--- /dev/null
+++ b/test/crypto/KdfAesTest.mc
@@ -0,0 +1,116 @@
+// Author: cirakg & claude ai
+// Summary: PBKDF2, HKDF and AES-CBC tests (RFC 7914, RFC 5869, NIST SP 800-38A) plus padding and job edge cases.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+function pbkdf2MatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.PBKDF2.size(); i++) {
+ var r = TestSupport.row(TestVectors.PBKDF2, i);
+ var key = Kdf.pbkdf2(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.num(r, 2), TestSupport.num(r, 3));
+ ok = TestSupport.sameBytes(logger, "PBKDF2 vector " + i, key, TestSupport.bytes(r, 4)) && ok;
+ }
+ return ok;
+}
+
+// The result must not depend on how the work is split into steps.
+(:test)
+function pbkdf2StepSizeDoesNotMatter(logger as Test.Logger) as Boolean {
+ var password = Bytes.fromUtf8("123456");
+ var salt = new [16]b;
+ var oneByOne = new Kdf.Pbkdf2(password, salt, 37, 40);
+ var steps = 0;
+ while (!oneByOne.step(1)) {
+ steps++;
+ }
+ // 2 output blocks x 37 iterations, the last step returns true.
+ return Bytes.equal(oneByOne.result(), Kdf.pbkdf2(password, salt, 37, 40)) && steps == 73;
+}
+
+(:test)
+function pbkdf2RejectsBadParameters(logger as Test.Logger) as Boolean {
+ var rejected = 0;
+ var cases = [[0, 32], [1, 0], [1, 255 * 32 + 1]];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<Number>;
+ try {
+ new Kdf.Pbkdf2([1]b, [2]b, c[0], c[1]);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ }
+ return rejected == cases.size();
+}
+
+(:test)
+function pbkdf2CalibrationStaysInBounds(logger as Test.Logger) as Boolean {
+ var iterations = Kdf.calibrate(1000, 500);
+ logger.debug("calibrated iterations for 1 s in this simulator: " + iterations);
+ return iterations >= Kdf.MIN_ITERATIONS && iterations <= Kdf.MAX_ITERATIONS;
+}
+
+(:test)
+function hkdfMatchesRfcVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.HKDF.size(); i++) {
+ var r = TestSupport.row(TestVectors.HKDF, i);
+ var okm = Kdf.hkdf(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.bytes(r, 2), TestSupport.num(r, 3));
+ ok = TestSupport.sameBytes(logger, "HKDF vector " + i, okm, TestSupport.bytes(r, 4)) && ok;
+ }
+ return ok;
+}
+
+(:test)
+function aesCbcMatchesNistVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.AES_CBC.size(); i++) {
+ var r = TestSupport.row(TestVectors.AES_CBC, i);
+ var key = TestSupport.bytes(r, 0);
+ var iv = TestSupport.bytes(r, 1);
+ var plain = TestSupport.bytes(r, 2);
+ var cipher = TestSupport.bytes(r, 3);
+ ok = TestSupport.sameBytes(logger, "AES encrypt " + i, Aes.cbcRaw(key, iv, plain, true), cipher) && ok;
+ ok = TestSupport.sameBytes(logger, "AES decrypt " + i, Aes.cbcRaw(key, iv, cipher, false), plain) && ok;
+ }
+ return ok;
+}
+
+// PKCS#7 round trips for every length from 0 to 40 bytes, both key sizes.
+(:test)
+function aesPaddingRoundTrips(logger as Test.Logger) as Boolean {
+ var iv = new [16]b;
+ var keys = [new [16]b, new [32]b];
+ for (var k = 0; k < keys.size(); k++) {
+ for (var length = 0; length <= 40; length++) {
+ var plain = new [length]b;
+ for (var i = 0; i < length; i++) {
+ plain[i] = (i * 7 + length) & 0xFF;
+ }
+ var cipher = Aes.cbcEncrypt(keys[k] as ByteArray, iv, plain);
+ var back = Aes.cbcDecrypt(keys[k] as ByteArray, iv, cipher);
+ if (cipher.size() != (length / 16 + 1) * 16 || back == null || !Bytes.equal(back, plain)) {
+ logger.error("padding round trip failed at length " + length);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+(:test)
+function aesRejectsBadPaddingAndLength(logger as Test.Logger) as Boolean {
+ var key = new [32]b;
+ var iv = new [16]b;
+ // A block whose decryption ends in 0x00, which is never valid padding.
+ var badPadding = Aes.cbcRaw(key, iv, new [16]b, true);
+ var badKey = false;
+ try {
+ Aes.cbcEncrypt(new [24]b, iv, [1]b);
+ } catch (e instanceof Lang.InvalidValueException) {
+ badKey = true;
+ }
+ return Aes.cbcDecrypt(key, iv, badPadding) == null && Aes.cbcDecrypt(key, iv, new [15]b) == null
+ && Aes.cbcDecrypt(key, iv, []b) == null && badKey;
+}