aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/crypto/Kdf.mc94
-rw-r--r--source/ui/HomeDelegate.mc16
-rw-r--r--source/ui/HomeView.mc42
3 files changed, 76 insertions, 76 deletions
diff --git a/source/crypto/Kdf.mc b/source/crypto/Kdf.mc
index 07c97ba..5bace1c 100644
--- a/source/crypto/Kdf.mc
+++ b/source/crypto/Kdf.mc
@@ -25,8 +25,15 @@ module Kdf {
// keeps the simulator (a PC) from choosing a count a watch can't afford.
const MIN_ITERATIONS = 2000;
const MAX_ITERATIONS = 200000;
- // Iterations per job step: small enough for the 120,000-bytecode watchdog.
- const ITERATIONS_PER_STEP = 200;
+ // Iterations per job step. The watchdog allows 120,000 bytecodes per
+ // event on the Forerunner 255/255S (240,000 elsewhere); 200 iterations of
+ // the byte-wise version tripped it there in the simulator (M4). With the
+ // word-wise XOR this keeps a wide margin; the UI flow derives PINs on the
+ // Forerunner 255S profile in every `make screenshots`.
+ const ITERATIONS_PER_STEP = 100;
+
+ // decodeNumber/encodeNumber options for one 32-bit word.
+ typedef WordOptions as {:offset as Number, :endianness as Lang.Endian};
// PBKDF2-HMAC-SHA256 as a resumable job (RFC 8018 section 5.2).
class Pbkdf2 extends Jobs.Job {
@@ -39,8 +46,20 @@ module Kdf {
private var _block as Number = 1; // current output block, 1-based
private var _done as Number = 0; // iterations done for the current block
private var _u as ByteArray = []b;
- private var _t as ByteArray = []b;
private var _out as ByteArray = []b;
+ // T of the current block as eight 32-bit words: XOR-ing words takes
+ // far fewer bytecodes than XOR-ing 32 bytes, which matters for the
+ // per-event watchdog budget (see ITERATIONS_PER_STEP).
+ private var _t0 as Number = 0;
+ private var _t1 as Number = 0;
+ private var _t2 as Number = 0;
+ private var _t3 as Number = 0;
+ private var _t4 as Number = 0;
+ private var _t5 as Number = 0;
+ private var _t6 as Number = 0;
+ private var _t7 as Number = 0;
+ // decodeNumber options for word 0..7, built once.
+ private var _w as Array<WordOptions> = [] as Array<WordOptions>;
// iterations >= 1; 1 <= length <= 255 * 32 bytes.
function initialize(password as ByteArray, salt as ByteArray, iterations as Number, length as Number) {
@@ -56,44 +75,83 @@ module Kdf {
_iterations = iterations;
_length = length;
_blockCount = (length + SHA256_BYTES - 1) / SHA256_BYTES;
+ for (var k = 0; k < 8; k++) {
+ _w.add({:offset => k * 4, :endianness => Lang.ENDIAN_BIG});
+ }
}
// Run up to `budget` iterations. Returns true when the key is complete.
function step(budget as Number) as Boolean {
var left = budget;
+ var f = Lang.NUMBER_FORMAT_SINT32;
+ var w = _w;
while (left > 0 && _block <= _blockCount) {
if (_done == 0) {
- // U1 = HMAC(P, S || INT(i))
+ // U1 = HMAC(P, S || INT(i)); T = U1
_mac.update(Bytes.concat(_salt, Bytes.uint32BigEndian(_block)));
_u = _mac.digest();
- _t = Bytes.copy(_u);
+ var u = _u;
+ _t0 = u.decodeNumber(f, w[0]) as Number;
+ _t1 = u.decodeNumber(f, w[1]) as Number;
+ _t2 = u.decodeNumber(f, w[2]) as Number;
+ _t3 = u.decodeNumber(f, w[3]) as Number;
+ _t4 = u.decodeNumber(f, w[4]) as Number;
+ _t5 = u.decodeNumber(f, w[5]) as Number;
+ _t6 = u.decodeNumber(f, w[6]) as Number;
+ _t7 = u.decodeNumber(f, w[7]) as Number;
} else {
// Uj = HMAC(P, Uj-1); T ^= Uj. The previous Uj isn't wiped
// every iteration: it is useless without the password, and
- // a wipe loop per iteration would halve the iterations the
- // watch can afford in its time budget. The final values
- // are wiped below.
+ // a wipe per iteration would cut the iterations the watch
+ // can afford. The final values are wiped below.
_mac.update(_u);
_u = _mac.digest();
- var t = _t;
var u = _u;
- for (var i = 0; i < SHA256_BYTES; i++) {
- t[i] = t[i] ^ u[i];
- }
+ _t0 ^= u.decodeNumber(f, w[0]) as Number;
+ _t1 ^= u.decodeNumber(f, w[1]) as Number;
+ _t2 ^= u.decodeNumber(f, w[2]) as Number;
+ _t3 ^= u.decodeNumber(f, w[3]) as Number;
+ _t4 ^= u.decodeNumber(f, w[4]) as Number;
+ _t5 ^= u.decodeNumber(f, w[5]) as Number;
+ _t6 ^= u.decodeNumber(f, w[6]) as Number;
+ _t7 ^= u.decodeNumber(f, w[7]) as Number;
}
_done++;
left--;
if (_done == _iterations) {
- _out.addAll(_t);
- Bytes.wipe(_t);
- Bytes.wipe(_u);
- _block++;
- _done = 0;
+ finishBlock();
}
}
return _block > _blockCount;
}
+ // Append T to the output and clear it.
+ private function finishBlock() as Void {
+ var t = new [SHA256_BYTES]b;
+ var words = [_t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7];
+ for (var k = 0; k < 8; k++) {
+ t.encodeNumber(words[k] as Number, Lang.NUMBER_FORMAT_SINT32, _w[k]);
+ words[k] = 0;
+ }
+ _out.addAll(t);
+ Bytes.wipe(t);
+ Bytes.wipe(_u);
+ clearWords();
+ _block++;
+ _done = 0;
+ }
+
+ private function clearWords() as Void {
+ _t0 = 0;
+ _t1 = 0;
+ _t2 = 0;
+ _t3 = 0;
+ _t4 = 0;
+ _t5 = 0;
+ _t6 = 0;
+ _t7 = 0;
+ }
+
// The derived key; only valid after step() returned true.
function result() as ByteArray {
return _out.slice(0, _length);
@@ -102,9 +160,9 @@ module Kdf {
// Wipe every intermediate value.
function wipe() as Void {
Bytes.wipe(_u);
- Bytes.wipe(_t);
Bytes.wipe(_out);
Bytes.wipe(_salt);
+ clearWords();
}
}
diff --git a/source/ui/HomeDelegate.mc b/source/ui/HomeDelegate.mc
deleted file mode 100644
index fe23ae6..0000000
--- a/source/ui/HomeDelegate.mc
+++ /dev/null
@@ -1,16 +0,0 @@
-// Author: cirakg & claude ai
-// Summary: Input handling for the home screen.
-//
-// HomeDelegate: input handling for the home screen.
-//
-// M0 has no actions yet. Inheriting BehaviorDelegate keeps the default
-// behaviours, so BACK (button or swipe) leaves the app on every device.
-
-import Toybox.WatchUi;
-
-class HomeDelegate extends WatchUi.BehaviorDelegate {
-
- function initialize() {
- BehaviorDelegate.initialize();
- }
-}
diff --git a/source/ui/HomeView.mc b/source/ui/HomeView.mc
deleted file mode 100644
index f73c783..0000000
--- a/source/ui/HomeView.mc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Author: cirakg & claude ai
-// Summary: M0 placeholder home screen showing the app name and version.
-//
-// HomeView: the M0 placeholder home screen. It draws the app name and version
-// in the centre of the screen. Milestone M4 replaces it with the entry list.
-//
-// Performance note: everything that allocates (loading the name string,
-// building the version text) happens once in initialize(), not in onUpdate().
-
-import Toybox.Graphics;
-import Toybox.Lang;
-import Toybox.WatchUi;
-
-class HomeView extends WatchUi.View {
-
- private var _title as String;
- private var _version as String;
- // Set after the first complete draw, so start-up diagnostics run once.
- private var _firstFrameDone as Boolean = false;
-
- function initialize() {
- View.initialize();
- _title = WatchUi.loadResource(Rez.Strings.AppName) as String;
- _version = "v" + AppInfo.VERSION;
- }
-
- function onUpdate(dc as Graphics.Dc) as Void {
- var centerX = dc.getWidth() / 2;
- var centerY = dc.getHeight() / 2;
- var justify = Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER;
-
- dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
- dc.clear();
- dc.drawText(centerX, centerY, Graphics.FONT_MEDIUM, _title, justify);
- dc.drawText(centerX, centerY + dc.getFontHeight(Graphics.FONT_MEDIUM), Graphics.FONT_XTINY, _version, justify);
-
- if (!_firstFrameDone) {
- _firstFrameDone = true;
- Diag.firstFrame();
- }
- }
-}