// Author: cirakg & claude ai // Summary: Tests for SealedBox (encrypt-then-MAC records): round trips, tamper detection, wrong key or aad, and the self-test. import Toybox.Lang; import Toybox.Test; (:test) module SealedBoxFixtures { function keys(seed as Number) as SealedBox.Keys { var master = new [32]b; for (var i = 0; i < 32; i++) { master[i] = (i * 13 + seed) & 0xFF; } return new SealedBox.Keys(master); } } (:test) function sealedBoxRoundTrips(logger as Test.Logger) as Boolean { var keys = SealedBoxFixtures.keys(1); var aad = Bytes.fromUtf8("K7F2|header"); for (var length = 0; length <= 70; length += 7) { var plain = new [length]b; for (var i = 0; i < length; i++) { plain[i] = (i * 31) & 0xFF; } var box = SealedBox.seal(keys, aad, plain); var back = SealedBox.open(keys, aad, box); if (back == null || !Bytes.equal(back, plain) || box[0] != SealedBox.VERSION) { logger.error("round trip failed at length " + length); return false; } } return true; } // Flipping any single byte of the box (version, IV, ciphertext or tag) must // make open() return null. (:test) function sealedBoxDetectsEveryTamperedByte(logger as Test.Logger) as Boolean { var keys = SealedBoxFixtures.keys(2); var aad = [9, 9]b; var box = SealedBox.seal(keys, aad, Bytes.fromUtf8("JBSWY3DPEHPK3PXP fake secret")); for (var i = 0; i < box.size(); i++) { var tampered = Bytes.copy(box); tampered[i] = tampered[i] ^ 0x01; if (SealedBox.open(keys, aad, tampered) != null) { logger.error("tampering byte " + i + " was not detected"); return false; } } return true; } (:test) function sealedBoxRejectsWrongKeyAadAndTruncation(logger as Test.Logger) as Boolean { var keys = SealedBoxFixtures.keys(3); var aad = [1, 2, 3]b; var box = SealedBox.seal(keys, aad, [42]b); // Moving a byte from the aad into the box must not verify either. var shifted = Bytes.concat([3]b, box); return SealedBox.open(SealedBoxFixtures.keys(4), aad, box) == null && SealedBox.open(keys, [1, 2]b, box) == null && SealedBox.open(keys, [1, 2]b, shifted) == null && SealedBox.open(keys, aad, box.slice(0, box.size() - 1)) == null && SealedBox.open(keys, aad, []b) == null && SealedBox.open(keys, aad, box) != null; } // The same IV gives the same box; random IVs make every seal different. (:test) function sealedBoxIvHandling(logger as Test.Logger) as Boolean { var keys = SealedBoxFixtures.keys(5); var iv = new [16]b; var a = SealedBox.sealWithIv(keys, []b, [7]b, iv); var b = SealedBox.sealWithIv(keys, []b, [7]b, iv); var c = SealedBox.seal(keys, []b, [7]b); var d = SealedBox.seal(keys, []b, [7]b); return Bytes.equal(a, b) && !Bytes.equal(c, d); } (:test) function sealedBoxKeysWipe(logger as Test.Logger) as Boolean { var keys = SealedBoxFixtures.keys(6); keys.wipe(); return Bytes.equal(keys.encKey, new [32]b) && Bytes.equal(keys.macKey, new [32]b); } (:test) function selfTestPasses(logger as Test.Logger) as Boolean { var failed = SelfTest.run(); if (failed != null) { logger.error("self-test failed: " + failed); } return failed == null; }