diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-19 15:27:37 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-19 15:27:37 +0200 |
| commit | dd5d0f68726fe56ee4d199867d414aea81905cc4 (patch) | |
| tree | 1cb40b024ac13f454633198ad50cbc55f7283f39 /source/util/Bytes.mc | |
| parent | 94181d58cf66c5b7b263fd435a344f1cd8872cff (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.mc | 60 |
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; |
