]> git.mar77i.info Git - otp_nano/commitdiff
initial commit
authormar77i <mar77i@protonmail.ch>
Tue, 9 Jun 2026 05:37:34 +0000 (07:37 +0200)
committermar77i <mar77i@protonmail.ch>
Tue, 9 Jun 2026 05:37:34 +0000 (07:37 +0200)
12 files changed:
.gitignore [new file with mode: 0644]
build.sh [new file with mode: 0755]
encrypt_secrets_to_header.py [new file with mode: 0755]
main.c [new file with mode: 0644]
otp_nano.ino [new file with mode: 0644]
repl.py [new file with mode: 0755]
sha1.c [new file with mode: 0644]
sha1.h [new file with mode: 0644]
totp.c [new file with mode: 0644]
totp.h [new file with mode: 0644]
uart.c [new file with mode: 0644]
uart.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..fe4f21d
--- /dev/null
@@ -0,0 +1,6 @@
+*.bin
+*.eep
+*.elf
+*.hex
+.idea
+secrets.h
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..560399c
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+tmpdir="$(mktemp -d -p/dev/shm)"
+cleanup() {
+    rm -rf "${tmpdir}"
+}
+trap cleanup EXIT
+
+extra_args=(
+    --fqbn arduino:avr:nano
+    --build-path "${tmpdir}"
+    --build-property "compiler.c.extra_flags=-Wall -Wextra -std=c99"
+    --build-property "compiler.cpp.extra_flags=-Wall -Wextra"
+    --output-dir .
+)
+while (( $# )); do
+    case "${1}" in
+    -u|--upload)
+        extra_args+=(-u -p /dev/ttyUSB0)
+        ;;
+    esac
+    shift
+done
+arduino-cli compile "${extra_args[@]}" .
+#rm -r "${tmpdir}"
diff --git a/encrypt_secrets_to_header.py b/encrypt_secrets_to_header.py
new file mode 100755 (executable)
index 0000000..14e8575
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+
+import hmac
+import sys
+from getpass import getpass
+from hashlib import sha1
+from io import BytesIO
+from secrets import randbelow, randbits
+
+
+def advance_round_key(orig_iv, iv):
+    for i in range(len(iv)):
+        j = orig_iv[i] & 31
+        iv[j] = (iv[j] + 1) & 255
+        if iv[j] != 0:
+            break
+
+
+def scramble(iv_bytes, password, data):
+    assert not any(len(line) > 79 for line in data.split(b"\n"))
+    iv_list = list(iv_bytes)
+    round_key = iv_list[:]
+    bio = BytesIO()
+    derived = b""
+    for i in range(len(data)):
+        if i % 10 == 0:
+            if i > 0:
+                advance_round_key(iv_list, round_key)
+            derived = hmac.new(password, bytes(round_key), sha1).digest()
+            print("derived:", derived.hex(), file=sys.stderr)
+        bio.write(bytes((data[i] ^ derived[i % 10],)))
+    return bio.getvalue()
+
+
+def print_as_const(varname, data):
+    print(f"static const uint8_t {varname}[] PROGMEM = {{")
+    print("    ", end="")
+    for i, b in enumerate(data):
+        print(f"0x{b:02x}", end=",")
+        if i == len(data) - 1:
+            print()
+        elif i % 12 == 11:
+            print("\n    ", end="")
+        else:
+            print(end=" ")
+    print("};")
+
+
+def main():
+    indices = list(range(20))
+    key = bytes(
+        [
+            indices.pop(randbelow(len(indices))) | (randbits(3) << 5)
+            for _ in range(len(indices))
+        ]
+    )
+    password = getpass().encode()
+    with open(sys.argv[1], "rb") as fh:
+        scrambled_data = scramble(key, password, fh.read())
+    print("\n// secrets.h\n")
+    print("#ifndef SECRETS_H")
+    print("#define SECRETS_H\n")
+    print("#include <stdint.h>")
+    print("#include <avr/pgmspace.h>\n")
+    print_as_const("key", key)
+    print_as_const("pw_mac", hmac.new(key, password, sha1).digest())
+    print_as_const("secret", scrambled_data)
+    print("\n#endif // SECRETS_H")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..c977868
--- /dev/null
+++ b/main.c
@@ -0,0 +1,133 @@
+
+// main.c
+
+#include <stdint.h>
+#include <avr/interrupt.h>
+
+#include "totp.h"
+#include "sha1.h"
+#include "uart.h"
+
+static uint8_t password[64];
+
+static inline void wait_for_input(void) {
+    register uint8_t corr = 0;  // prevent drift (8 iterations of TCNT1 => 62500)
+    DDRB |= (1 << PB5);         // Set Digital Pin 13 (Port B, Pin 5) to OUTPUT
+    TCCR1A = 0;                 // Normal operation mode
+    TCCR1B = (1 << CS12);       // Set Prescaler to 256 (62,500 ticks per second)
+    while (!UART_bytes_available()) {
+        if (TCNT1 >= 7812 + corr) {
+            PORTB ^= (1 << PB5);  // Toggle LED
+            TCNT1 = 0;            // Reset hardware timer register back to 0
+            corr ^= 1;
+        }
+    }
+    PORTB &= ~(1 << PB5);  // Turn off LED
+    TCCR1B = 0;            // Turn off Timer 1
+}
+
+/*
+static inline void process_sha1(uint8_t *payload) {
+    struct sha1_ctx_type ctx;
+    uint8_t digest[20], i;
+    sha1_init(&ctx);
+    sha1_update(&ctx, payload, strlen(payload));
+    sha1_final(&ctx, digest);
+    for (i = 0; i < sizeof digest * 2; i++)
+        UART_write_byte(
+            "0123456789abcdef"[digest[i >> 1] >> (!(i & 1) << 2) & 15]
+        );
+}
+*/
+
+static inline uint8_t process_payload(uint8_t *payload) {
+    size_t password_len;
+    const char *reply = "NOK\n";
+    uint8_t ret = 0;
+    if (strcmp(payload, "bye") == 0)
+        return 1;
+    else if (strncmp(payload, "password ", 9) == 0) {
+        payload += 9;
+        password_len = strlen(payload);
+        if (password_len + 1 > sizeof password)
+            UART_write_string("ERROR:PASSWORDTOOLONG\n");
+        else if (check_password(payload, password_len) == 0) {
+            memcpy(password, payload, strlen(payload) + 1);
+            reply = "OK\n";
+        }
+    } else if (strncmp(payload, "gettoken ", 9) == 0) {
+        if (decrypt_secrets(password, payload + 9) == 0)
+            reply = "OK\n";
+    } else if (strncmp(payload, "dumpsecret", 9) == 0) {
+        if (dump_secrets(password) == 0)
+            reply = "OK\n";
+    } else
+        UART_write_string("ERROR:UNKNOWNCOMMAND\n");
+    UART_write_string(reply);
+    return ret;
+}
+
+void parse_serial_frame(void) {
+    enum { STATE_IDLE, STATE_PAYLOAD, STATE_CHECKSUM } state = STATE_IDLE;
+    uint8_t payload[64], payload_idx, calculated_xor, received_xor, c, esc = 0;
+    payload_idx = calculated_xor = received_xor = 0;
+    while (c = UART_read_byte_blocking()) {
+        if (state != STATE_CHECKSUM && esc == 0 && c == '\\') {
+            esc = 1;
+            continue;
+        }
+        switch (state) {
+        case STATE_IDLE:
+            if (esc == 0 && c == '$') {
+                state = STATE_PAYLOAD;
+                payload_idx = calculated_xor = 0;
+            }
+            break;
+
+        case STATE_PAYLOAD:
+            if (esc == 0 && c == '*') {
+                payload[payload_idx] = '\0';
+                state = STATE_CHECKSUM;
+                received_xor = 0;
+            } else if (payload_idx < (sizeof payload - 1)) {
+                payload[payload_idx++] = c;
+                calculated_xor = calculated_xor * 33 ^ c;
+            } else {
+                state = STATE_IDLE;
+                UART_write_string("ERROR:PAYLOAD_OVERFLOW\n");
+            }
+            break;
+
+        case STATE_CHECKSUM:
+            if (c >= '0' && c <= '9') {
+                c = received_xor * 10 + (c - '0');
+                if (c / 10 != received_xor)
+                    goto checksum_error;
+                received_xor = c;
+                break;
+            } else if (c == '\n' && calculated_xor == received_xor) {
+                if (process_payload(payload))
+                    return;
+            } else {
+checksum_error:
+                UART_write_string("ERROR:CHECKSUM_ERROR\n");
+            }
+            state = STATE_IDLE;  // Reset parser state machine
+            break;
+        }
+        if (esc == 1)
+            esc = 0;
+    }
+}
+
+int main(void) {
+    UART_init();
+    sei();
+    for (;;) {
+        wait_for_input();
+        parse_serial_frame();
+        memset(password, 0, sizeof password);
+        UART_flush();
+        UART_write_string("EYB\n");
+    }
+}
diff --git a/otp_nano.ino b/otp_nano.ino
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/repl.py b/repl.py
new file mode 100755 (executable)
index 0000000..6b309f8
--- /dev/null
+++ b/repl.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+from getpass import getpass
+from termios import (
+    B115200,
+    CS8,
+    CLOCAL,
+    CREAD,
+    CSIZE,
+    CSTOPB,
+    ECHO,
+    ECHOE,
+    ICANON,
+    ICRNL,
+    IXANY,
+    IXOFF,
+    IXON,
+    ISIG,
+    ONLCR,
+    OPOST,
+    PARENB,
+    TCSANOW,
+    tcgetattr,
+    tcsetattr,
+)
+from time import sleep, time
+from traceback import print_exc
+
+
+def configure_port(fd):
+    """Configures the raw file descriptor to 115200 baud, 8N1, non-blocking."""
+    # Get the current OS configuration attributes for the port file descriptor
+    attrs = tcgetattr(fd)
+    # Disable software flow control and mapping formatting
+    attrs[0] &= ~(IXON | IXOFF | IXANY | ICRNL)
+    # Pure raw output formatting
+    attrs[1] &= ~(OPOST | ONLCR)
+    # 8 data bits, enable receiver, ignore control lines
+    attrs[2] &= ~(PARENB | CSTOPB | CSIZE)  # No Parity, 1 Stop Bit
+    attrs[2] |= (CS8 | CLOCAL | CREAD)  # 8 Data Bits
+    # Raw input mode (Disable echo, erase, kill, interrupts)
+    attrs[3] &= ~(ICANON | ECHO | ECHOE | ISIG)
+    attrs[4] = B115200  # ospeed
+    attrs[5] = B115200  # ispeed
+    tcsetattr(fd, TCSANOW, attrs)
+
+
+def send(fd, payload):
+    checksum = 0
+    for char in payload:
+        checksum = checksum * 33 ^ ord(char)
+    for char, *rest in (("\\",), ("*",), ("$",), ("\n", "n")):
+        payload = payload.replace(char, (*rest, f"\\{char}")[0])
+    os.write(fd, f"${payload}*{checksum & 0xff}\n".encode('ascii'))
+
+
+def await_some_reply(fd, block=False):
+    received_nothing = True
+    while received_nothing:
+        sleep(0.1)
+        try:
+            response_bytes = os.read(fd, 256)
+        except BlockingIOError:
+            if block:
+                continue
+            break
+        if not response_bytes:
+            if block:
+                continue
+            break
+        received_nothing = False
+        try:
+            response = response_bytes.decode('ascii').strip()
+        except UnicodeDecodeError:
+            print("decoding failed.")
+            response = str(response_bytes)
+        print(response)
+
+
+sent_bye = False
+
+
+def one_read_eval_print(fd):
+    global sent_bye
+    try:
+        cmd = input("> ")
+    except EOFError:
+        print()
+        return False
+    if cmd == "quit":
+        return False
+    elif cmd:
+        if cmd == "password":
+            cmd = f"password {getpass()}"
+        elif cmd.startswith("gettoken "):
+            cmd = f"gettoken {int(time()) + 1} {cmd[9:]}"
+        send(fd, cmd)
+        sent_bye = cmd == "bye"
+        await_some_reply(fd, True)
+    else:
+        await_some_reply(fd, False)
+    return True
+
+
+def main():
+    configure_port(
+        # Open fd with r/w, preventing controlling tty and non-blocking
+        fd := os.open(sys.argv[1], os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
+    )
+    try:
+        while one_read_eval_print(fd):
+            pass
+    finally:
+        if not sent_bye:
+            try:
+                send(fd, "bye")
+            except OSError:
+                print_exc()
+        await_some_reply(fd)
+        os.close(fd)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/sha1.c b/sha1.c
new file mode 100644 (file)
index 0000000..2125c3a
--- /dev/null
+++ b/sha1.c
@@ -0,0 +1,162 @@
+
+// sha1.c
+
+#include <string.h>
+#include "sha1.h"
+
+#define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof (arr)[0])
+#define ROTLEFT(a, b) ((a << b) | (a >> (32 - b)))
+
+void sha1_init(struct sha1_ctx_type *ctx) {
+    *ctx = (struct sha1_ctx_type){
+        .state = {
+            .i = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 },
+        },
+        .bytelen = 0,
+    };
+}
+
+static const uint32_t k[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
+
+static void sha1_transform(struct sha1_ctx_type *ctx) {
+    struct sha1_ctx_state st;
+    uint32_t f, t, m[80];
+    uint8_t i;
+    for (i = 0; i < sizeof ctx->data.b; i += 4)
+        m[i >> 2] = (
+            ((uint32_t)ctx->data.b[i] << 24)
+            | ((uint32_t)ctx->data.b[i + 1] << 16)
+            | ((uint32_t)ctx->data.b[i + 2] << 8)
+            | (uint32_t)ctx->data.b[i + 3]
+        );
+    for (i = 16; i < 80; i++) {
+        m[i] = (m[i - 3] ^ m[i - 8] ^ m[i - 14] ^ m[i - 16]);
+        m[i] = ROTLEFT(m[i], 1);
+    }
+    st = ctx->state;
+    for (i = 0; i < 80; i++) {
+        if (i < 20)
+            f = (st.i[1] & st.i[2]) | (~st.i[1] & st.i[3]);
+        else if (i < 40)
+            f = st.i[1] ^ st.i[2] ^ st.i[3];
+        else if (i < 60)
+            f = (st.i[1] & st.i[2]) | (st.i[1] & st.i[3]) | (st.i[2] & st.i[3]);
+        else
+            f = st.i[1] ^ st.i[2] ^ st.i[3];
+        t = ROTLEFT(st.i[0], 5) + f + st.i[4] + k[i / 20] + m[i];
+        st.i[4] = st.i[3];
+        st.i[3] = st.i[2];
+        st.i[2] = ROTLEFT(st.i[1], 30);
+        st.i[1] = st.i[0];
+        st.i[0] = t;
+    }
+
+    for (i = 0; i < ARRAY_LENGTH(st.i); i++)
+        ctx->state.i[i] += st.i[i];
+}
+
+void sha1_update(struct sha1_ctx_type *ctx, const uint8_t *data, size_t len) {
+    size_t i;
+    uint8_t di;
+    for (i = 0, di = ctx->bytelen & 63; i < len; i++) {
+        ctx->data.b[di++] = data[i];
+        if (di == 64) {
+            sha1_transform(ctx);
+            di = 0;
+        }
+    }
+    ctx->bytelen += len;
+}
+
+void sha1_final(struct sha1_ctx_type *ctx, uint8_t *hash) {
+    uint8_t di = ctx->bytelen & 63, i;
+    ctx->data.b[di++] = 128;
+    if (di > 56) {
+        memset(ctx->data.b + di, 0, 64 - di);
+        sha1_transform(ctx);
+        di = 0;
+    } else
+        memset(ctx->data.b + di, 0, 56 - di);
+    ctx->data.b[56] = ctx->bytelen >> 53;
+    ctx->data.b[57] = ctx->bytelen >> 45;
+    ctx->data.b[58] = ctx->bytelen >> 37;
+    ctx->data.b[59] = ctx->bytelen >> 29;
+    ctx->data.b[60] = ctx->bytelen >> 21;
+    ctx->data.b[61] = ctx->bytelen >> 13;
+    ctx->data.b[62] = ctx->bytelen >> 5;
+    ctx->data.b[63] = ctx->bytelen << 3;
+    sha1_transform(ctx);
+    for (i = 0; i < 20; i++)
+        hash[i] = ctx->state.i[i >> 2] >> (24 - ((i & 3) << 3));
+}
+
+void sha1_hmac(
+    const uint8_t *key,
+    size_t key_len,
+    const uint8_t *data,
+    size_t data_len,
+    uint8_t *mac
+) {
+    struct sha1_ctx_type ctx;
+    uint8_t k_ipad[64], k_opad[64], tk[20], i;
+    if (key_len > 64) {
+        sha1_init(&ctx);
+        sha1_update(&ctx, key, key_len);
+        sha1_final(&ctx, tk);
+        key = tk;
+        key_len = sizeof tk;
+    }
+    for (i = 0; i < key_len; i++) {
+        k_ipad[i] = key[i] ^ 0x36;
+        k_opad[i] = key[i] ^ 0x5c;
+    }
+    memset(k_ipad + key_len, 0x36, sizeof k_ipad - key_len);
+    memset(k_opad + key_len, 0x5c, sizeof k_opad - key_len);
+    sha1_init(&ctx);
+    sha1_update(&ctx, k_ipad, 64);
+    sha1_update(&ctx, data, data_len);
+    sha1_final(&ctx, mac);
+    sha1_init(&ctx);
+    sha1_update(&ctx, k_opad, 64);
+    sha1_update(&ctx, mac, 20);
+    sha1_final(&ctx, mac);
+}
+
+void sha1_hmac_ipad_opad_init(
+    const uint8_t *key,
+    size_t key_len,
+    struct sha1_ctx_type ipad_opad_ctx[2]
+) {
+    struct sha1_ctx_type ctx;
+    uint8_t k_ipad[64], k_opad[64], tk[20], i;
+    if (key_len > 64) {
+        sha1_init(&ctx);
+        sha1_update(&ctx, key, key_len);
+        sha1_final(&ctx, tk);
+        key = tk;
+        key_len = sizeof tk;
+    }
+    for (i = 0; i < key_len; i++) {
+        k_ipad[i] = key[i] ^ 0x36;
+        k_opad[i] = key[i] ^ 0x5c;
+    }
+    memset(k_ipad + key_len, 0x36, sizeof k_ipad - key_len);
+    memset(k_opad + key_len, 0x5c, sizeof k_opad - key_len);
+    sha1_init(ipad_opad_ctx);
+    sha1_update(ipad_opad_ctx, k_ipad, 64);
+    sha1_init(&ipad_opad_ctx[1]);
+    sha1_update(&ipad_opad_ctx[1], k_opad, 64);
+}
+
+void sha1_hmac_ipad_opad_digest(
+    struct sha1_ctx_type ipad_opad_ctx[2],
+    const uint8_t *data,
+    size_t data_len,
+    uint8_t *mac
+) {
+    struct sha1_ctx_type ipad_ctx = ipad_opad_ctx[0], opad_ctx = ipad_opad_ctx[1];
+    sha1_update(&ipad_ctx, data, data_len);
+    sha1_final(&ipad_ctx, mac);
+    sha1_update(&opad_ctx, mac, 20);
+    sha1_final(&opad_ctx, mac);
+}
diff --git a/sha1.h b/sha1.h
new file mode 100644 (file)
index 0000000..abc8219
--- /dev/null
+++ b/sha1.h
@@ -0,0 +1,42 @@
+
+// sha1.h
+
+#ifndef SHA1_H
+#define SHA1_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+struct sha1_ctx_type {
+    struct sha1_ctx_data {
+        uint8_t b[64];
+    } data;
+    struct sha1_ctx_state {
+        uint32_t i[5];
+    } state;
+    uint64_t bytelen;
+};
+
+void sha1_init(struct sha1_ctx_type *ctx);
+void sha1_update(struct sha1_ctx_type *ctx, const uint8_t *data, size_t len);
+void sha1_final(struct sha1_ctx_type *ctx, uint8_t *hash);
+void sha1_hmac(
+    const uint8_t *key,
+    size_t key_len,
+    const uint8_t *data,
+    size_t data_len,
+    uint8_t *mac
+);
+void sha1_hmac_ipad_opad_init(
+    const uint8_t *key,
+    size_t key_len,
+    struct sha1_ctx_type ipad_opad_ctx[2]
+);
+void sha1_hmac_ipad_opad_digest(
+    struct sha1_ctx_type ipad_opad_ctx[2],
+    const uint8_t *data,
+    size_t data_len,
+    uint8_t *mac
+);
+
+#endif // SHA1_H
diff --git a/totp.c b/totp.c
new file mode 100644 (file)
index 0000000..eb3ecd1
--- /dev/null
+++ b/totp.c
@@ -0,0 +1,231 @@
+
+// totp.c
+
+#include <ctype.h>
+#include <string.h>
+
+#include "secrets.h"
+#include "sha1.h"
+#include "uart.h"
+
+struct decrypt_buffers {
+    struct sha1_ctx_type ipad_opad_ctx[2];
+    uint8_t orig_key[20], round_key[20], mac[20], chunk[32];
+};
+
+int check_password(uint8_t *password, size_t password_len) {
+    const uint8_t mac[20], key_copy[sizeof key], pw_mac_copy[sizeof pw_mac];
+    memcpy_P(key_copy, key, sizeof key);
+    memcpy_P(pw_mac_copy, pw_mac, sizeof pw_mac);
+    sha1_hmac(key_copy, sizeof key, password, password_len, mac);
+    return memcmp(mac, pw_mac_copy, sizeof mac);
+}
+
+static inline uint8_t b32_decode(uint8_t *buf) {
+    static const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+    uint8_t bits, *outptr, *inptr, *found_in_alphabet;
+    int8_t shift = 3;
+    /* Cycle through {-4..3} in intervals of 3 (== -5):
+     * { 3, -2, 1, -4, -1, 2, -3, 0 }
+     * This corresponds to how the shift offsets for decoded bits unfold.
+     */
+    for (
+        outptr = inptr = buf;
+        *inptr != '\n' && *inptr != '\0' && *inptr != '=';
+        inptr++
+    ) {
+        if (*inptr == ' ' || *inptr == '\t')
+            continue;
+        found_in_alphabet = strchr(alphabet, toupper(*inptr));
+        if (found_in_alphabet == NULL) {
+            UART_write_string("ERROR:INVALIDB32\n");
+            return 0;
+        }
+        bits = found_in_alphabet - alphabet;
+        switch (shift) {
+        case 0:
+            *outptr++ |= bits;
+            break;
+        case 1:
+        case 2:
+            *outptr |= bits << shift;
+            break;
+        case 3:
+            *outptr = bits << 3;
+            break;
+        default:
+            *outptr |= bits >> -shift;
+            *++outptr = bits << (shift + 8);
+            break;
+        }
+        shift += 3 - (shift > 0) * 8;
+    }
+    return outptr - buf;
+}
+
+void process_line(
+    const uint8_t *password,
+    uint8_t password_len,
+    const uint8_t *line,
+    const uint8_t *search,
+    int64_t time
+) {
+    uint32_t result;
+    uint8_t *colon = strrchr(line, ':'), *find, time_msg[sizeof time], md[20], i, len;
+    if (
+        colon == NULL
+        || search == NULL
+        || (*search && (find = strstr(line, search)) == NULL)
+    )
+        return;
+    if (!*search) {
+        UART_write_string_partial(line, colon - line);
+        UART_write_byte('\n');
+        return;
+    }
+    time /= 30;
+    for (i = 0; i < sizeof time; i++)
+        time_msg[i] = time >> ((sizeof time - 1 - i) * 8);
+    colon++;
+    if ((len = b32_decode(colon)) == 0)
+        return 1;
+    sha1_hmac(colon, len, time_msg, sizeof time_msg, md);
+    i = md[sizeof md - 1] & 0xf;
+    result = (
+        ((uint32_t)(md[i] & 0x7f) << 24)
+        | ((uint32_t)md[i + 1] << 16)
+        | ((uint32_t)md[i + 2] << 8)
+        | md[i + 3]
+    ) % 1000000;
+    UART_write_string_partial(line, colon - line);
+/*
+    UART_write_byte(' ');
+    for (i = 0; i < sizeof md * 2; i++)
+        UART_write_byte(
+            "0123456789abcdef"[md[i >> 1] >> (!(i & 1) << 2) & 15]
+        );
+    UART_write_byte(' ');
+    for (i = 0; i < len * 2; i++)
+        UART_write_byte(
+            "0123456789abcdef"[colon[i >> 1] >> (!(i & 1) << 2) & 15]
+        );
+    UART_write_byte(' ');
+    for (i = 0; i < sizeof time_msg * 2; i++)
+        UART_write_byte(
+            "0123456789abcdef"[time_msg[i >> 1] >> (!(i & 1) << 2) & 15]
+        );
+*/
+    UART_write_byte(' ');
+    UART_write_byte('0' + result / 100000 % 10);
+    UART_write_byte('0' + result / 10000 % 10);
+    UART_write_byte('0' + result / 1000 % 10);
+    UART_write_byte('0' + result / 100 % 10);
+    UART_write_byte('0' + result / 10 % 10);
+    UART_write_byte('0' + result % 10);
+    UART_write_byte('\n');
+    return 1;
+}
+
+void advance_round_key(const char *orig_key, char *round_key) {
+    uint8_t i, j;
+    for (i = 0; i < 20; i++) {
+        j = orig_key[i] & 31;
+        round_key[j]++;
+        if (round_key[j] != 0)
+            break;
+    }
+}
+
+static inline uint8_t *parse_time(const uint8_t *args, int64_t *time) {
+    int64_t a = 0, b;
+    while (*args >= '0' && *args <= '9') {
+        b = 10 * a + *args - '0';
+        if (b / 10 != a)
+            return NULL;
+        a = b;
+        args++;
+    }
+    *time = a;
+    while (*args == ' ')
+        args++;
+    return args;
+}
+
+static inline void init_decrypt_buffers(
+    struct decrypt_buffers *bufs, uint8_t *password, uint8_t password_len
+) {
+    memcpy_P(bufs->orig_key, key, 20);
+    memcpy(bufs->round_key, bufs->orig_key, 20);
+    sha1_hmac_ipad_opad_init(password, password_len, bufs->ipad_opad_ctx);
+}
+
+static inline uint8_t get_byte_from_decrypt_buffers(
+    struct decrypt_buffers *bufs, size_t i
+) {
+    if (i % sizeof bufs->chunk == 0) {
+        if (i + sizeof bufs->chunk <= sizeof secret)
+            memcpy_P(bufs->chunk, secret + i, sizeof bufs->chunk);
+        else {
+            memcpy_P(bufs->chunk, secret + i, sizeof secret - i);
+            memset(
+                bufs->chunk + sizeof secret - i,
+                0,
+                sizeof bufs->chunk - (sizeof secret - i)
+            );
+        }
+    }
+    if (i % (sizeof bufs->mac / 2) == 0) {
+        if (i > 0)
+            advance_round_key(bufs->orig_key, bufs->round_key);
+        sha1_hmac_ipad_opad_digest(
+            bufs->ipad_opad_ctx, bufs->round_key, sizeof bufs->round_key, bufs->mac
+        );
+    }
+    return bufs->chunk[i % sizeof bufs->chunk] ^ bufs->mac[i % (sizeof bufs->mac / 2)];
+}
+
+uint8_t decrypt_secrets(const uint8_t *password, const uint8_t *args) {
+    struct decrypt_buffers bufs;
+    int64_t time;
+    size_t i;
+    uint8_t round_bytes = 0, chunk_offset = 0, bytes_remaining, chunk_bytes = 0;
+    uint8_t line[80], c, line_index = 0, replied = 0;
+    uint8_t password_len = strlen(password);
+    if ((password_len = strlen(password)) == 0) {
+        UART_write_string("ERROR:NOPASSWORD\n");
+        return 1;
+    }
+    if ((args = parse_time(args, &time)) == NULL) {
+        UART_write_string("ERROR:PARSETIMEFAILED\n");
+        return 1;
+    }
+    init_decrypt_buffers(&bufs, password, password_len);
+    for (i = 0; i < sizeof secret; i++) {
+        c = get_byte_from_decrypt_buffers(&bufs, i);
+        if (c == '\n' || line_index == sizeof line - 1) {
+            line[line_index] = '\0';
+            process_line(password, password_len, line, args, time);
+            line_index = 0;
+        } else
+            line[line_index++] = c;
+    }
+    if (line_index > 0) {
+        line[line_index] = '\0';
+        process_line(password, password_len, line, args, time);
+    }
+    return 0;
+}
+
+uint8_t dump_secrets(const uint8_t *password) {
+    struct decrypt_buffers bufs;
+    size_t i;
+    uint8_t password_len = strlen(password);
+    if (password_len == 0) {
+        UART_write_string("ERROR:NOPASSWORD\n");
+        return 1;
+    }
+    init_decrypt_buffers(&bufs, password, password_len);
+    for (i = 0; i < sizeof secret; i++)
+        UART_write_byte(get_byte_from_decrypt_buffers(&bufs, i));
+    return 0;
+}
diff --git a/totp.h b/totp.h
new file mode 100644 (file)
index 0000000..45e9162
--- /dev/null
+++ b/totp.h
@@ -0,0 +1,14 @@
+
+// decrypt.h
+
+#ifndef DECRYPT_H
+#define DECRYPT_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+int check_password(uint8_t *password, size_t password_len);
+uint8_t decrypt_secrets(const uint8_t *password, const uint8_t *args);
+uint8_t dump_secrets(const uint8_t *password);
+
+#endif // DECRYPT_H
\ No newline at end of file
diff --git a/uart.c b/uart.c
new file mode 100644 (file)
index 0000000..5e4f3e1
--- /dev/null
+++ b/uart.c
@@ -0,0 +1,50 @@
+
+// uart.c
+
+#include <avr/interrupt.h>
+
+#include "uart.h"
+
+#define MYUBRR 16 // Register value for 115200 baud when U2X0 is enabled
+
+static struct {
+    volatile uint8_t data[62], head, tail;
+} buffer = { { 0 }, 0, 0 };
+
+void UART_init(void) {
+    UBRR0H = (uint8_t)(MYUBRR >> 8);         // Set baud rate High byte
+    UBRR0L = (uint8_t)MYUBRR;                // Set baud rate Low byte
+    UCSR0A |= (1 << U2X0);                   // Enable Double Speed Mode
+    // Enable Transmitter, Receiver and Receive Interrupt hardware trigger
+    UCSR0B = (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0);
+    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);  // 8 data bits, 1 stop bit
+}
+
+void UART_flush(void) {
+    buffer.head = buffer.tail = 0;
+}
+
+ISR(USART_RX_vect) {
+    uint8_t next_head = buffer.head + 1, c = UDR0;
+    if (next_head == sizeof buffer.data)
+        next_head = 0;
+    // Store in ring buffer if it isn't full
+    if (next_head != buffer.tail) {
+        buffer.data[buffer.head] = c;
+        buffer.head = next_head;
+    }
+}
+
+uint8_t UART_bytes_available(void) {
+    return buffer.head != buffer.tail;
+}
+
+uint8_t UART_read_byte(void) {
+    unsigned char c = buffer.data[buffer.tail];
+    if (buffer.tail != buffer.head) {
+        buffer.tail = buffer.tail + 1;
+        if (buffer.tail == sizeof buffer.data)
+            buffer.tail = 0;
+    }
+    return c;
+}
diff --git a/uart.h b/uart.h
new file mode 100644 (file)
index 0000000..c0e7c2b
--- /dev/null
+++ b/uart.h
@@ -0,0 +1,37 @@
+
+// uart.h
+
+#ifndef UART_H
+#define UART_H
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <avr/io.h>
+
+void UART_init(void);
+void UART_flush(void);
+uint8_t UART_bytes_available(void);
+uint8_t UART_read_byte(void);
+
+static inline uint8_t UART_read_byte_blocking(void) {
+    while (!UART_bytes_available());
+    return UART_read_byte();
+}
+
+static inline void UART_write_byte(uint8_t c) {
+    while (!(UCSR0A & (1 << UDRE0)));
+    UDR0 = c;
+}
+
+static inline void UART_write_string(const uint8_t* str) {
+    while (*str)
+        UART_write_byte(*str++);
+}
+
+static inline void UART_write_string_partial(const uint8_t* str, size_t len) {
+    size_t i;
+    for (i = 0; i < len; i++)
+        UART_write_byte(str[i]);
+}
+
+#endif // UART_H