aboutsummaryrefslogtreecommitdiff
path: root/source/util/Bytes.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-19 15:27:37 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-19 15:27:37 +0200
commitdd5d0f68726fe56ee4d199867d414aea81905cc4 (patch)
tree1cb40b024ac13f454633198ad50cbc55f7283f39 /source/util/Bytes.mc
parent94181d58cf66c5b7b263fd435a344f1cd8872cff (diff)
feat(storage): add the encrypted vault, PIN keys, lockout, session and migrations
Records are one ByteArray each: a readable TLV header plus an encrypt-then-MAC box whose aad binds the entry ID and the header. The device key seals unlocked entries and pending items; a random master key, wrapped under a PBKDF2 PIN key, seals locked ones. Wrong PINs are counted before the check, with delays on the trusted clock and an uptime fallback. Bytes.toUtf8 validates UTF-8 first, because the native conversion crashes on invalid input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/util/Bytes.mc')
-rw-r--r--source/util/Bytes.mc60
1 files changed, 60 insertions, 0 deletions
diff --git a/source/util/Bytes.mc b/source/util/Bytes.mc
index 1008fd2..a32475a 100644
--- a/source/util/Bytes.mc
+++ b/source/util/Bytes.mc
@@ -47,6 +47,66 @@ module Bytes {
}) as ByteArray;
}
+ // Decode UTF-8 bytes to a string. Empty input gives "". Malformed UTF-8
+ // or a NUL byte gives null (callers treat that as damaged data). The
+ // bytes are checked first because convertEncodedString stops the app
+ // with an uncatchable system error on invalid UTF-8.
+ function toUtf8(data as ByteArray) as String? {
+ if (data.size() == 0) {
+ return "";
+ }
+ if (!isValidUtf8(data)) {
+ return null;
+ }
+ return StringUtil.convertEncodedString(data, {
+ :fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
+ :toRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
+ :encoding => StringUtil.CHAR_ENCODING_UTF8
+ }) as String;
+ }
+
+ // True for well-formed UTF-8 (RFC 3629) without NUL bytes: no overlong
+ // forms, no surrogates (U+D800-DFFF), nothing above U+10FFFF.
+ function isValidUtf8(data as ByteArray) as Boolean {
+ var i = 0;
+ var n = data.size();
+ while (i < n) {
+ var b = data[i];
+ var more = 0;
+ var min = 0;
+ var cp = 0;
+ if (b == 0) {
+ return false;
+ } else if (b < 0x80) {
+ i++;
+ continue;
+ } else if (b >= 0xC2 && b <= 0xDF) {
+ more = 1; min = 0x80; cp = b & 0x1F;
+ } else if (b >= 0xE0 && b <= 0xEF) {
+ more = 2; min = 0x800; cp = b & 0x0F;
+ } else if (b >= 0xF0 && b <= 0xF4) {
+ more = 3; min = 0x10000; cp = b & 0x07;
+ } else {
+ return false;
+ }
+ if (i + more >= n) {
+ return false;
+ }
+ for (var k = 1; k <= more; k++) {
+ var c = data[i + k];
+ if ((c & 0xC0) != 0x80) {
+ return false;
+ }
+ cp = (cp << 6) | (c & 0x3F);
+ }
+ if (cp < min || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) {
+ return false;
+ }
+ i += more + 1;
+ }
+ return true;
+ }
+
// A new array holding a followed by b; neither input is changed.
function concat(a as ByteArray, b as ByteArray) as ByteArray {
var out = new [0]b;