]> git.mar77i.info Git - otp_nano/commitdiff
get it working fine
authormar77i <mar77i@protonmail.ch>
Thu, 25 Jun 2026 12:02:57 +0000 (14:02 +0200)
committermar77i <mar77i@protonmail.ch>
Thu, 25 Jun 2026 12:02:57 +0000 (14:02 +0200)
15 files changed:
.gitignore
build.sh
decrypt.c [new file with mode: 0644]
decrypt.h [new file with mode: 0644]
encoding.h [new file with mode: 0644]
encrypt_secrets_to_header.py [deleted file]
main.c
manage_secrets.py [new file with mode: 0755]
repl.py
sha1.c
sha1.h
totp.c [deleted file]
totp.h [deleted file]
uart.c
uart.h

index fe4f21d7efdaec3ee44a940359f4f8ec4093352c..0a3ef685a7b0f6e10fa0a9ef31aa31e8a934446c 100644 (file)
@@ -3,4 +3,5 @@
 *.elf
 *.hex
 .idea
+__pycache__/
 secrets.h
index 560399c0612b6d114f2faa0d5e8520904004e28b..481aabbf871f70a40de228fe9df0795d701190ae 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -1,6 +1,7 @@
 #!/usr/bin/env bash
 
 tmpdir="$(mktemp -d -p/dev/shm)"
+
 cleanup() {
     rm -rf "${tmpdir}"
 }
@@ -16,10 +17,15 @@ extra_args=(
 while (( $# )); do
     case "${1}" in
     -u|--upload)
-        extra_args+=(-u -p /dev/ttyUSB0)
+        if [[ -c "${2}" ]]; then
+            dev_path="${2}"
+            shift
+        else
+            dev_path=/dev/ttyUSB0
+        fi
+        extra_args+=(-u -p "${dev_path}")
         ;;
     esac
     shift
 done
 arduino-cli compile "${extra_args[@]}" .
-#rm -r "${tmpdir}"
diff --git a/decrypt.c b/decrypt.c
new file mode 100644 (file)
index 0000000..bf47543
--- /dev/null
+++ b/decrypt.c
@@ -0,0 +1,184 @@
+
+// decrypt.c
+
+#include "decrypt.h"
+#include "encoding.h"
+#include "sha1.h"
+#include "secrets.h"
+#include "uart.h"
+
+static struct {
+    struct sha1_ctx ipad_opad_ctx[2];
+    struct sha1_buf indices, round_key, mac;
+    uint8_t chunk[32], status;
+} decrypt_buffers;
+
+uint8_t init_decrypt_buffers(uint8_t *password, uint8_t password_len) {
+    int result;
+    uint16_t pick_accu = 0;
+    uint8_t i, j, pick, tmp;
+    memcpy_P(&decrypt_buffers.round_key, &iv, sizeof iv);
+    sha1_hmac(
+        &decrypt_buffers.round_key,
+        sizeof decrypt_buffers.round_key,
+        password,
+        password_len,
+        &decrypt_buffers.mac
+    );
+    if (memcmp_P(&decrypt_buffers.mac, &pw_mac, sizeof pw_mac) != 0)
+        return 1;
+    sha1_hmac_ipad_opad_init(password, password_len, decrypt_buffers.ipad_opad_ctx);
+    sha1_hmac_ipad_opad_digest(
+        decrypt_buffers.ipad_opad_ctx,
+        &decrypt_buffers.round_key,
+        sizeof decrypt_buffers.round_key,
+        &decrypt_buffers.mac
+    );
+    for (i = 0; i < 20; i++)
+        decrypt_buffers.indices.b[i] = i;
+    for (i = 20, j = i - 1; i > 0; i = j--) {
+        pick_accu += decrypt_buffers.mac.b[j];
+        pick = pick_accu % i;
+        pick_accu /= i;
+        if (j == pick)
+            continue;
+        tmp = decrypt_buffers.indices.b[j];
+        decrypt_buffers.indices.b[j] = decrypt_buffers.indices.b[pick];
+        decrypt_buffers.indices.b[pick] = tmp;
+    }
+    decrypt_buffers.status = 1;
+    return 0;
+}
+
+static inline void advance_round_key(void) {
+    uint8_t i, j, tmp;
+    for (i = 0, tmp = 1; i < 20; i++) {
+        j = decrypt_buffers.indices.b[i];
+        decrypt_buffers.round_key.b[j] += tmp;
+        tmp &= decrypt_buffers.round_key.b[j] == 0;
+    }
+    sha1_hmac_ipad_opad_digest(
+        decrypt_buffers.ipad_opad_ctx,
+        &decrypt_buffers.round_key,
+        sizeof decrypt_buffers.round_key,
+        &decrypt_buffers.mac
+    );
+}
+
+static inline uint8_t get_byte_from_decrypt_buffers(size_t i) {
+    if (i % sizeof decrypt_buffers.chunk == 0) {
+        if (i + sizeof decrypt_buffers.chunk <= sizeof secret)
+            memcpy_P(decrypt_buffers.chunk, secret + i, sizeof decrypt_buffers.chunk);
+        else {
+            memcpy_P(decrypt_buffers.chunk, secret + i, sizeof secret - i);
+            memset(
+                decrypt_buffers.chunk + sizeof secret - i,
+                0,
+                sizeof decrypt_buffers.chunk - (sizeof secret - i)
+            );
+        }
+    }
+    if (i % (sizeof decrypt_buffers.mac >> 1) == 0) {
+        if (i == 0 && decrypt_buffers.status == 2)
+            memcpy_P(&decrypt_buffers.round_key, &iv, sizeof iv);
+        advance_round_key();
+    }
+    return (
+        decrypt_buffers.chunk[i % sizeof decrypt_buffers.chunk]
+        ^ decrypt_buffers.mac.b[i % (sizeof decrypt_buffers.mac / 2)]
+    );
+}
+
+uint8_t password_is_ready(void) {
+    return decrypt_buffers.status > 0;
+}
+
+int8_t list_secrets(void) {
+    size_t i;
+    uint8_t c, colon_found = 0;
+    for (i = 0; i < sizeof secret; i++) {
+        c = get_byte_from_decrypt_buffers(i);
+        if (c == ':')
+            colon_found = 1;
+        else if (c == '\n')
+            colon_found = 0;
+        if (colon_found == 0)
+            UART_write_byte(c);
+    }
+    decrypt_buffers.status = 2;
+    return 0;
+}
+
+static inline int8_t get_secret_and_totp_line(
+    uint8_t time_msg[8],
+    uint8_t *line,
+    uint8_t line_len,
+    uint8_t colon_offset
+) {
+    struct sha1_buf mac;
+    uint32_t result;
+    ssize_t len;
+    uint8_t *ptr, i, decimal[7];
+    ptr = line + colon_offset;
+    if ((len = from_b32_inplace(ptr)) <= 0)
+        return -1;
+    sha1_hmac(ptr, len, time_msg, 8, &mac);
+    i = mac.b[sizeof mac - 1] & 0xf;
+    UART_write_string_partial(line, colon_offset);
+    UART_write_byte(' ');
+    to_decimal(
+        (
+            ((uint32_t)(mac.b[i] & 0x7f) << 24)
+            | ((uint32_t)mac.b[i + 1] << 16)
+            | ((uint32_t)mac.b[i + 2] << 8)
+            | mac.b[i + 3]
+        ) % 1000000,
+        decimal,
+        sizeof decimal
+    );
+    UART_write_string(decimal);
+    UART_write_byte('\n');
+    return 0;
+}
+
+static inline search_matches(uint8_t *search, uint8_t *line, uint8_t colon_offset) {
+    uint8_t *ptr;
+    // no search was specified
+    return (
+        search == NULL || *search == '\0' || (
+            // or a match was made excluding the :
+            (ptr = strstr(line, search)) != NULL
+            && ptr + strlen(search) < line + colon_offset
+        )
+    );
+}
+
+int8_t get_secrets_and_totp(uint8_t time_msg[8], uint8_t *search) {
+    size_t i;
+    uint8_t line[80], line_len = 0, c, colon_offset = 255;
+    for (i = 0; i < sizeof secret; i++)
+        if ((c = get_byte_from_decrypt_buffers(i)) == '\n') {
+            line[line_len] = '\0';
+            if (
+                // a : was found
+                colon_offset != 255
+                && search_matches(search, line, colon_offset)
+                && get_secret_and_totp_line(
+                    time_msg, line, line_len, colon_offset + 1
+                ) < 0
+            )
+                return -1;
+            line_len = 0;
+            colon_offset = 255;
+        } else if (c == ':') {
+            colon_offset = line_len;
+            line[line_len++] = c;
+        } else if (line_len < sizeof line - 1)
+            line[line_len++] = c;
+    decrypt_buffers.status = 2;
+    return 0;
+}
+
+void clear_decrypt_buffers(void) {
+    memset(&decrypt_buffers, 0, sizeof decrypt_buffers);
+}
diff --git a/decrypt.h b/decrypt.h
new file mode 100644 (file)
index 0000000..39a3f56
--- /dev/null
+++ b/decrypt.h
@@ -0,0 +1,15 @@
+
+// decrypt.h
+
+#ifndef DECRYPT_H
+#define DECRYPT_H
+
+#include <stdint.h>
+
+uint8_t init_decrypt_buffers(uint8_t *password, uint8_t password_len);
+uint8_t password_is_ready(void);
+int8_t list_secrets(void);
+int8_t get_secrets_and_totp(uint8_t time_msg[8], uint8_t *search);
+void clear_decrypt_buffers(void);
+
+#endif // DECRYPT_H
diff --git a/encoding.h b/encoding.h
new file mode 100644 (file)
index 0000000..a288c52
--- /dev/null
@@ -0,0 +1,122 @@
+
+// encoding.h
+
+#ifndef ENCODING_H
+#define ENCODING_H
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef int16_t ssize_t;
+
+#include "uart.h"
+
+static inline char from_hex8(char c) {
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    switch (c) {
+    case 'A':
+    case 'a':
+        return 10;
+    case 'B':
+    case 'b':
+        return 11;
+    case 'C':
+    case 'c':
+        return 12;
+    case 'D':
+    case 'd':
+        return 13;
+    case 'E':
+    case 'e':
+        return 14;
+    case 'F':
+    case 'f':
+        return 15;
+    }
+    return -1;
+}
+
+static inline ssize_t from_hex_inplace(char *s, const char *end_chars) {
+    char *ptr = s;
+    ssize_t ret = 0;
+    int8_t nib;
+    while (*ptr != '\0' && strchr(end_chars, *ptr) == NULL) {
+        if ((nib = from_hex8(*ptr++)) == -1)
+            return -1;
+        if (ret & 1)
+            s[ret >> 1] |= nib;
+        else
+            s[ret >> 1] = nib << 4;
+        ret++;
+    }
+    if (ret & 1)
+        return -1;
+    return ret >> 1;
+}
+
+static inline void to_hex(char c, char *s) {
+    const char hexdigits[] = "0123456789abcdef";
+    s[0] = hexdigits[c >> 4];
+    s[1] = hexdigits[c & 15];
+}
+
+static inline ssize_t from_b32_inplace(char *s) {
+    static const char base32_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+    uint8_t accu = 0, bits, count;
+    char *ptr = s, *found;
+    size_t pos;
+    for (pos = count = 0; s[pos] != '\n' && s[pos] != '\0' && s[pos] != '='; pos++) {
+        if (s[pos] == ' ' || s[pos] == '\t')
+            continue;
+        if ((found = strchr(base32_alphabet, toupper(s[pos]))) == NULL) {
+            UART_write_string("ERROR:INVALIDB32\n");
+            return -1;
+        }
+        bits = found - base32_alphabet;
+        switch (count++ % 8) {
+        case 0:
+            accu = bits << 3;
+            break;
+        case 1:
+            *ptr++ = accu | (bits >> 2);
+            accu = bits << 6;
+            break;
+        case 2:
+            accu |= bits << 1;
+            break;
+        case 3:
+            *ptr++ = accu | (bits >> 4);
+            accu = bits << 4;
+            break;
+        case 4:
+            *ptr++ = accu | (bits >> 1);
+            accu = bits << 7;
+            break;
+        case 5:
+            accu |= bits << 2;
+            break;
+        case 6:
+            *ptr++ = accu | (bits >> 3);
+            accu = bits << 5;
+            break;
+        case 7:
+            *ptr++ = accu | bits;
+            break;
+        }
+    }
+    return ptr - s;
+}
+
+static inline void to_decimal(uint32_t n, char *out, size_t out_len) {
+    if (out == NULL || out_len == 0)
+        return;
+    out[--out_len] = '\0';
+    while (out_len > 0) {
+        out[--out_len] = '0' + (n % 10);
+        n /= 10;
+    }
+}
+
+#endif // ENCODING_H
diff --git a/encrypt_secrets_to_header.py b/encrypt_secrets_to_header.py
deleted file mode 100755 (executable)
index 14e8575..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/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
index c977868eee160314fac3df8d28f1c3b9d672f1c3..6c35c177392f4db76765d690190fcc673d1b5845 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1,11 +1,12 @@
 
 // main.c
 
+#include <limits.h>
 #include <stdint.h>
-#include <avr/interrupt.h>
+#include <avr/io.h>
 
-#include "totp.h"
-#include "sha1.h"
+#include "decrypt.h"
+#include "encoding.h"
 #include "uart.h"
 
 static uint8_t password[64];
@@ -26,108 +27,160 @@ static inline void wait_for_input(void) {
     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]
-        );
+/* serial comms protocol:
+ *
+ * BYE -> EYB ends session X
+ * PW <hex-encoded-pw> -> OK if hmac matches else NOK X
+ * OTP LS -> list all secrets and OK X
+ * OTP GET <timestamp> <hex-encoded-filter> -> show totp digits for matches of filter
+ * OTP GET <timestamp> -> show all (unfiltered) totp digits
+ * KO -> OK (ping) X
+ * anything else -> NOK X
+ *
+ * checksum is now 16 bits, transmitted in hex
+ */
+
+int8_t process_otp_get(uint8_t *payload) {
+    uint64_t time;
+    ssize_t len;
+    int8_t i;
+    uint8_t time_msg[8];
+    if ((len = from_hex_inplace(payload, " ")) <= 0)
+        return -1;
+    time = 0;
+    for (i = 0; i < len; i++)
+        time = (time << CHAR_BIT) | payload[i];
+    time /= 30;
+    for (i = 0; i < sizeof time_msg; i++)
+        time_msg[i] = time >> ((sizeof time_msg - 1 - i) * CHAR_BIT);
+    payload += 2 * len;
+    if (*payload == ' ') {
+        payload++;
+        if ((len = from_hex_inplace(payload, "")) <= 0)
+            return -1;
+        payload[len] = '\0';
+    } else if (*payload != '\0')
+        return -1;
+    return get_secrets_and_totp(time_msg, payload);
 }
-*/
 
-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)
+int8_t process_payload(uint8_t *payload) {
+    size_t payload_len;
+    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 (strcmp(payload, "KO") == 0)
+        return 0;
+    else if (strncmp(payload, "PW ", 3) == 0) {
+        payload += 3;
+        payload_len = strlen(payload) / 2;
+        if (
+            from_hex_inplace(payload, "") > 0
+            && payload_len + 1 <= sizeof password
+            && init_decrypt_buffers(payload, payload_len) == 0
+        ) {
+            memcpy(password, payload, payload_len + 1);
+            return 0;
         }
-    } 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;
+    } else if (strncmp(payload, "OTP ", 4) == 0) {
+        payload += 4;
+        if (!password_is_ready())
+            return -1;
+        if (strcmp(payload, "LS") == 0)
+            return list_secrets();
+        else if (strncmp(payload, "GET ", 4) == 0)
+            return process_otp_get(payload + 4);
+    }
+    return -1;
 }
 
-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;
+#define STARTMSG '$'
+#define STARTCHK '*'
+#define ENDMSG '\n'
+
+struct {
+    enum { STATE_IDLE, STATE_PAYLOAD, STATE_CHECKSUM } state;
+    uint16_t chksum, rchksum;
+    uint8_t payload[64], payload_idx;
+} serial_frame;
+
+void parse_serial_frame_idle(uint8_t c) {
+    if (c != STARTMSG)
+        return;
+    serial_frame.state = STATE_PAYLOAD;
+    serial_frame.payload_idx = 0;
+    serial_frame.chksum = 0;
+}
+
+void parse_serial_frame_payload(uint8_t c) {
+    if (c == STARTCHK) {
+        serial_frame.payload[serial_frame.payload_idx] = '\0';
+        serial_frame.state = STATE_CHECKSUM;
+        serial_frame.rchksum = 0;
+    } else if (serial_frame.payload_idx < (sizeof serial_frame.payload - 1)) {
+        serial_frame.payload[serial_frame.payload_idx++] = c;
+        serial_frame.chksum = serial_frame.chksum * 33 ^ c;
+    } else {
+        serial_frame.state = STATE_IDLE;
+        UART_write_string("ERROR:PAYLOAD_OVERFLOW\n");
+    }
+}
+
+uint8_t parse_serial_frame_checksum(uint8_t c) {
+    uint16_t tmp;
+    int8_t nib = from_hex8(c);
+    if (nib >= 0) {
+        tmp = (serial_frame.rchksum << 4) | nib;
+        if (tmp >> 4 == serial_frame.rchksum)
+            serial_frame.rchksum = tmp;
+        else
+            UART_write_string("ERROR:CHECKSUM_OVERFLOW\n");
+        return 0;
+    }
+    serial_frame.state = STATE_IDLE;
+    if (c == ENDMSG && serial_frame.chksum == serial_frame.rchksum)
+        switch (process_payload(serial_frame.payload)) {
+        case -1:
+            UART_write_string("NOK\n");
+            break;
+        case 0:
+            UART_write_string("OK\n");
+            break;
+        case 1:
+            return 1;
         }
-        switch (state) {
+    else {
+        UART_write_string("ERROR:CHECKSUM_ERROR\n");
+        UART_write_string("NOK\n");
+    }
+    return 0;
+}
+
+void parse_serial_frame(void) {
+    uint8_t c;
+    serial_frame.state = STATE_IDLE;
+    while (c = UART_read_byte_blocking())
+        switch (serial_frame.state) {
         case STATE_IDLE:
-            if (esc == 0 && c == '$') {
-                state = STATE_PAYLOAD;
-                payload_idx = calculated_xor = 0;
-            }
+            parse_serial_frame_idle(c);
             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");
-            }
+            parse_serial_frame_payload(c);
             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
+            if (parse_serial_frame_checksum(c) == 1)
+                return;
             break;
         }
-        if (esc == 1)
-            esc = 0;
-    }
 }
 
 int main(void) {
     UART_init();
-    sei();
     for (;;) {
         wait_for_input();
         parse_serial_frame();
+        clear_decrypt_buffers();
         memset(password, 0, sizeof password);
-        UART_flush();
+        UART_read_buffer_flush();
         UART_write_string("EYB\n");
     }
 }
diff --git a/manage_secrets.py b/manage_secrets.py
new file mode 100755 (executable)
index 0000000..5320c3a
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+
+import hmac
+from argparse import ArgumentParser
+from getpass import getpass
+from hashlib import sha1
+from io import BytesIO
+from pathlib import Path
+from secrets import token_bytes
+
+
+class CryptoManager:
+    @staticmethod
+    def get_hash(key, data):
+        return hmac.new(key, data, sha1).digest()
+
+    def advance_round_key(self, round_key: bytearray | bytes) -> bytearray:
+        if isinstance(round_key, bytes):
+            round_key = bytearray(round_key)
+        tmp = 1
+        for i in range(len(round_key)):
+            j = self.indices[i]
+            round_key[j] = (round_key[j] + tmp) & 255
+            tmp &= round_key[j] == 0
+        return round_key
+
+    def __init__(self, iv: bytes, password: bytes):
+        self.iv = iv
+        self.password = password
+        self.indices = self.init_indices(self.get_hash(self.password, self.iv))
+
+    def init_indices(self, first_key) -> list[int]:
+        indices = list(range(20))
+        pick_accu = 0
+        i = 20
+        while i > 0:
+            j = i - 1
+            pick_accu = (pick_accu + first_key[j]) & 0xffff
+            pick_accu, pick = divmod(pick_accu, i)
+            if j != pick:
+                indices[j], indices[pick] = indices[pick], indices[j]
+            i = j
+        return indices
+
+    def prepare_ipad_opad(self):
+        pad_len = 64
+        ipad_ba = bytearray(b"\x36" * pad_len)
+        opad_ba = bytearray(b"\x5c" * pad_len)
+        if len(self.password) > pad_len:
+            password = sha1(self.password).digest()
+        else:
+            password = self.password
+        for i, c in enumerate(password):
+            ipad_ba[i] ^= c
+            opad_ba[i] ^= c
+        return sha1(ipad_ba), sha1(opad_ba)
+
+    def scramble(self, data):
+        bio = BytesIO()
+        round_key = self.iv
+        ipad_template, opad_template = self.prepare_ipad_opad()
+        for i in range(0, len(data), 10):
+            #derived = self.get_hash(
+            #    self.password, round_key := self.advance_round_key(round_key)
+            #)
+            round_key = self.advance_round_key(round_key)
+            ipad, opad = ipad_template.copy(), opad_template.copy()
+            ipad.update(round_key)
+            opad.update(ipad.digest())
+            block = data[i:i + 10]
+            bio.write(bytes(a ^ b for a, b in zip(block, opad.digest()[:len(block)])))
+        return bio.getvalue()
+
+
+class HeaderReader:
+    def __init__(self):
+        self.iv_bio = BytesIO()
+        self.pw_mac_bio = BytesIO()
+        self.secret_bio = BytesIO()
+        self.current_bio = None
+        self.brace_level = 0
+
+    def find_header(self, line):
+        assert self.brace_level == 0
+        parts = line.rsplit(maxsplit=4)
+        if parts[2:] != ["PROGMEM", "=", "{"]:
+            return
+        self.brace_level = line.count("{")
+        current_key = parts[1]
+        if (pos := current_key.find("[")) >= 0:
+            assert current_key[pos:] == "[]", current_key
+            current_key = current_key[:pos]
+        current_attr = f"{current_key}_bio"
+        assert current_key != "current"
+        self.current_bio = getattr(self, current_attr)
+
+    def add_bytes(self, line):
+        pos = line.find(";")
+        done = pos >= 0
+        if done:
+            line = line[:pos].rstrip()
+        for part in line.split(","):
+            part = part.strip()
+            while part.startswith("{"):
+                part = part[1:].lstrip()
+                self.brace_level += 1
+            while part.endswith("}"):
+                self.brace_level -= 1
+                part = part[:-1].rstrip()
+            if not part:
+                continue
+            assert part.startswith("0x"), part
+            self.current_bio.write(bytes((int(part, 16),)))
+        if done:
+            self.current_bio = None
+
+    def parse(self, fh):
+        for line in fh:
+            line = line.strip()
+            if self.current_bio is None:
+                self.find_header(line)
+            else:
+                self.add_bytes(line)
+
+    @property
+    def iv(self):
+        iv = self.iv_bio.getvalue()
+        assert len(iv) == 20
+        return iv
+
+    @property
+    def pw_mac(self):
+        pw_mac = self.pw_mac_bio.getvalue()
+        assert len(pw_mac) == 20
+        return pw_mac
+
+    @property
+    def secret(self):
+        secret = self.secret_bio.getvalue()
+        assert len(secret) > 0
+        return secret
+
+
+def cleanup_secret(infh):
+    return infh.read()
+    bio = BytesIO()
+    for line in infh:
+        line = line.strip()
+        if not line:
+            continue
+        assert len(line) < 79 and b":" in line
+        bio.write(line)
+        bio.write(b"\n")
+    return bio.getvalue()
+
+
+class HeaderWriter:
+    @staticmethod
+    def indent(i):
+        return " " * i
+
+    def __init__(self, fh, password):
+        self.fh = fh
+        self.iv = token_bytes(20)
+        self.password = password
+
+    def print_as_const_sha_buf(self, varname, data):
+        print(f"static const struct sha1_buf {varname} PROGMEM = {{", file=self.fh)
+        print(f"{self.indent(4)}{{\n{self.indent(8)}", end="", file=self.fh)
+        last = len(data) - 1
+        for i, b in enumerate(data):
+            print(f"0x{b:02x}", end=",", file=self.fh)
+            if i == last:
+                print(f"\n{self.indent(4)}}}", file=self.fh)
+            elif i % 12 == 11:
+                print(f"\n{self.indent(8)}", end="", file=self.fh)
+            else:
+                print(end=" ", file=self.fh)
+        print("};\n", file=self.fh)
+
+    def print_as_const(self, varname, data):
+        print(f"static const uint8_t {varname}[] PROGMEM = {{", file=self.fh)
+        print(f"{self.indent(4)}", end="", file=self.fh)
+        last = len(data) - 1
+        for i, b in enumerate(data):
+            print(f"0x{b:02x}", end=",", file=self.fh)
+            if i == last:
+                print(file=self.fh)
+            elif i % 12 == 11:
+                print(f"\n{self.indent(4)}", end="", file=self.fh)
+            else:
+                print(end=" ", file=self.fh)
+        print("};\n", file=self.fh)
+
+    def write(self, secret):
+        print("\n// secrets.h\n", file=self.fh)
+        print("#ifndef SECRETS_H", file=self.fh)
+        print("#define SECRETS_H\n", file=self.fh)
+        print("#include <stdint.h>", file=self.fh)
+        print("#include <avr/pgmspace.h>\n", file=self.fh)
+        print('#include "sha1.h"\n', file=self.fh)
+        self.print_as_const_sha_buf("iv", self.iv)
+        self.print_as_const_sha_buf(
+            "pw_mac", CryptoManager.get_hash(self.iv, self.password)
+        )
+        self.print_as_const(
+            "secret",
+            CryptoManager(self.iv, self.password).scramble(secret),
+        )
+        print("#endif // SECRETS_H", file=self.fh)
+
+
+def encrypt_secrets(project_dir, infile, new_password):
+    password = getpass().encode()
+    secrets_h = project_dir / "secrets.h"
+    if not new_password and secrets_h.exists():
+        header = HeaderReader()
+        with secrets_h.open("rt") as fh:
+            header.parse(fh)
+        assert CryptoManager.get_hash(header.iv, password) == header.pw_mac
+    else:
+        other = getpass("Confirm password: ").encode()
+        assert password == other
+    with Path(infile).open("rb") as infh, secrets_h.open("wt") as outfh:
+        HeaderWriter(outfh, password).write(cleanup_secret(infh))
+
+def decrypt_secrets(project_dir, outfile):
+    password = getpass().encode()
+    secrets_h = project_dir / "secrets.h"
+    assert secrets_h.exists()
+    header = HeaderReader()
+    with secrets_h.open("rt") as fh:
+        header.parse(fh)
+    assert CryptoManager.get_hash(header.iv, password) == header.pw_mac
+    with Path(outfile).open("wb") as fh:
+        fh.write(CryptoManager(header.iv, password).scramble(header.secret))
+
+
+def main():
+    ap = ArgumentParser()
+    sp = ap.add_subparsers(dest="action", required=True, help="Actions")
+    ep = sp.add_parser("encrypt")
+    ep.add_argument("infile")
+    ep.add_argument("--new-password", action="store_true")
+    dp = sp.add_parser("decrypt")
+    dp.add_argument("outfile")
+    args = ap.parse_args()
+    project_dir = Path(__file__).parent.absolute()
+    if args.action == "encrypt":
+        encrypt_secrets(project_dir, args.infile, args.new_password)
+    elif args.action == "decrypt":
+        decrypt_secrets(project_dir, args.outfile)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/repl.py b/repl.py
index 440aa46ddd63732deaf25a5ff59813c7c8dd9370..288bed9b60740bc961519f46ac0abb3dd2bd5008 100755 (executable)
--- a/repl.py
+++ b/repl.py
@@ -29,116 +29,124 @@ 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 print_line(line):
-    try:
-        response = line.decode('ascii').strip()
-    except UnicodeDecodeError:
-        print("decoding failed.")
-        response = str(line)
-    print(response)
-
-
-def await_some_reply(fd, wait_for_ok=False, timeout=5):
-    ba = bytearray()
-    stop_time = time() + (timeout if wait_for_ok else 0.3)
-    while True:
-        sleep(0.1)
+def big_endian_hex(i):
+    return i.to_bytes((i.bit_length() + 7) >> 3 or 1, "big").hex()
+
+
+class ReplManager:
+    def configure_port(self):
+        """Configures the raw file descriptor to 115200 baud, 8N1, non-blocking."""
+        # Get the current OS configuration attributes for the port file descriptor
+        attrs = tcgetattr(self.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(self.fd, TCSANOW, attrs)
+
+    def __init__(self, dev_path):
+        self.dev_path = dev_path
+        self.fd = -1
+        self.last_cmd = None
+
+    def __enter__(self):
+        self.fd = os.open(self.dev_path, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
+        self.configure_port()
+        return self
+
+    def send(self, payload):
+        checksum = 0
+        for char in payload:
+            checksum = checksum * 33 ^ ord(char)
+            assert char not in "$*\n"
+        os.write(self.fd, f"${payload}*{checksum & 0xffff:x}\n".encode('ascii'))
+
+    def get_cmd(self):
         try:
-            received = os.read(fd, 256)
-        except BlockingIOError:
-            if wait_for_ok and time() < stop_time:
+            cmd = input("> ")
+        except (EOFError, KeyboardInterrupt):
+            print()
+            return "QUIT"
+        return cmd.strip()
+
+    def print_reply(self, line):
+        try:
+            response = line.decode('ascii').strip()
+        except UnicodeDecodeError:
+            print("decoding failed.")
+            response = str(line)
+        print(response)
+
+    def await_some_reply(self, wait_for_ok=False, timeout=0.3):
+        ba = bytearray()
+        stop_time = time() + timeout
+        done = False
+        while not done:
+            sleep(0.1)
+            t = time()
+            try:
+                received = os.read(self.fd, 256)
+            except BlockingIOError:
+                received = b""
+            done |= t >= stop_time
+            if not received or done:
                 continue
-            break
-        if received:
             ba += received
-            stop_time = time() + (timeout if wait_for_ok else 0.3)
-        elif time() >= stop_time:
-            if wait_for_ok:
+            stop_time = t + (timeout if wait_for_ok else 0.3)
+            while b"\n" in ba:
+                line, rest = ba.split(b"\n", 1)
+                self.print_reply(line)
+                ba = rest
+                if wait_for_ok and line in (b"OK", b"NOK", b"EYB"):
+                    done = True
+                    break
+        if len(ba) > 0:
+            self.print_reply(ba)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if self.last_cmd != "BYE":
+            try:
+                self.send("BYE")
+            except OSError:
+                print_exc()
+            finally:
+                self.await_some_reply()
+        os.close(self.fd)
+        self.fd = -1
+
+    def run(self):
+        while (cmd := self.get_cmd()) != "QUIT":
+            if not cmd:
+                self.await_some_reply(False)
                 continue
-            break
-        while b"\n" in ba:
-            line, *rest = ba.split(b"\n", 1)
-            print_line(line)
-            if len(rest) and len(rest[0]):
-                ba = rest[0]
-            elif wait_for_ok and line in (b"OK", b"NOK"):
-                return
-            else:
-                ba = bytearray()
-                break
-
-
-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:]}"
-        elif cmd == "dumpsecrets":
-            print("This command is not supported by repl.")
-            return False
-        send(fd, cmd)
-        sent_bye = cmd == "bye"
-        await_some_reply(fd, True)
-    else:
-        await_some_reply(fd, False)
-    return True
+            elif cmd == "PW":
+                cmd = f"PW {getpass().encode().hex()}"
+            elif cmd == "OTP GET" or cmd.startswith("OTP GET "):
+                rest = cmd[8:]
+                if rest:
+                    rest = f" {rest.encode().hex()}"
+                cmd = f"OTP GET {big_endian_hex(int(time() + 1))}{rest}"
+            self.send(cmd)
+            self.last_cmd = cmd
+            self.await_some_reply(True, 5)
 
 
 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 len(sys.argv) == 1:
+        dev_path = "/dev/ttyUSB0"
+    elif len(sys.argv) == 2:
+        dev_path = sys.argv[1]
+    else:
+        raise ValueError("Invalid arguments")
+    with ReplManager(dev_path) as repl:
+        repl.run()
 
 
 if __name__ == "__main__":
diff --git a/sha1.c b/sha1.c
index 2125c3aff19822e9f2282e4ac023dade7fc8a4ec..eab857fa90530cbdaaec52ea54135f7b73b885d9 100644 (file)
--- a/sha1.c
+++ b/sha1.c
@@ -7,8 +7,8 @@
 #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){
+void sha1_init(struct sha1_ctx *ctx) {
+    *ctx = (struct sha1_ctx){
         .state = {
             .i = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 },
         },
@@ -18,7 +18,7 @@ void sha1_init(struct sha1_ctx_type *ctx) {
 
 static const uint32_t k[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
 
-static void sha1_transform(struct sha1_ctx_type *ctx) {
+static void sha1_transform(struct sha1_ctx *ctx) {
     struct sha1_ctx_state st;
     uint32_t f, t, m[80];
     uint8_t i;
@@ -55,7 +55,7 @@ static void sha1_transform(struct sha1_ctx_type *ctx) {
         ctx->state.i[i] += st.i[i];
 }
 
-void sha1_update(struct sha1_ctx_type *ctx, const uint8_t *data, size_t len) {
+void sha1_update(struct sha1_ctx *ctx, const uint8_t *data, size_t len) {
     size_t i;
     uint8_t di;
     for (i = 0, di = ctx->bytelen & 63; i < len; i++) {
@@ -68,7 +68,7 @@ void sha1_update(struct sha1_ctx_type *ctx, const uint8_t *data, size_t len) {
     ctx->bytelen += len;
 }
 
-void sha1_final(struct sha1_ctx_type *ctx, uint8_t *hash) {
+void sha1_final(struct sha1_ctx *ctx, struct sha1_buf *hash) {
     uint8_t di = ctx->bytelen & 63, i;
     ctx->data.b[di++] = 128;
     if (di > 56) {
@@ -87,7 +87,7 @@ void sha1_final(struct sha1_ctx_type *ctx, uint8_t *hash) {
     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));
+        hash->b[i] = ctx->state.i[i >> 2] >> (24 - ((i & 3) << 3));
 }
 
 void sha1_hmac(
@@ -95,9 +95,9 @@ void sha1_hmac(
     size_t key_len,
     const uint8_t *data,
     size_t data_len,
-    uint8_t *mac
+    struct sha1_buf *mac
 ) {
-    struct sha1_ctx_type ctx;
+    struct sha1_ctx ctx;
     uint8_t k_ipad[64], k_opad[64], tk[20], i;
     if (key_len > 64) {
         sha1_init(&ctx);
@@ -125,9 +125,9 @@ void sha1_hmac(
 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 ipad_opad_ctx[2]
 ) {
-    struct sha1_ctx_type ctx;
+    struct sha1_ctx ctx;
     uint8_t k_ipad[64], k_opad[64], tk[20], i;
     if (key_len > 64) {
         sha1_init(&ctx);
@@ -143,18 +143,18 @@ void sha1_hmac_ipad_opad_init(
     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_update(ipad_opad_ctx, k_ipad, sizeof k_ipad);
     sha1_init(&ipad_opad_ctx[1]);
-    sha1_update(&ipad_opad_ctx[1], k_opad, 64);
+    sha1_update(&ipad_opad_ctx[1], k_opad, sizeof k_opad);
 }
 
 void sha1_hmac_ipad_opad_digest(
-    struct sha1_ctx_type ipad_opad_ctx[2],
+    struct sha1_ctx ipad_opad_ctx[2],
     const uint8_t *data,
     size_t data_len,
-    uint8_t *mac
+    struct sha1_buf *mac
 ) {
-    struct sha1_ctx_type ipad_ctx = ipad_opad_ctx[0], opad_ctx = ipad_opad_ctx[1];
+    struct sha1_ctx 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);
diff --git a/sha1.h b/sha1.h
index abc821979b8f94e46b69939372bfcd346afd3f86..b1a1eb612b958febe772d8ecf903c0745ff0f84f 100644 (file)
--- a/sha1.h
+++ b/sha1.h
@@ -7,7 +7,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 
-struct sha1_ctx_type {
+struct sha1_ctx {
     struct sha1_ctx_data {
         uint8_t b[64];
     } data;
@@ -16,27 +16,30 @@ struct sha1_ctx_type {
     } state;
     uint64_t bytelen;
 };
+struct sha1_buf {
+    uint8_t b[20];
+};
 
-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_init(struct sha1_ctx *ctx);
+void sha1_update(struct sha1_ctx *ctx, const uint8_t *data, size_t len);
+void sha1_final(struct sha1_ctx *ctx, struct sha1_buf *hash);
 void sha1_hmac(
     const uint8_t *key,
     size_t key_len,
     const uint8_t *data,
     size_t data_len,
-    uint8_t *mac
+    struct sha1_buf *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 ipad_opad_ctx[2]
 );
 void sha1_hmac_ipad_opad_digest(
-    struct sha1_ctx_type ipad_opad_ctx[2],
+    struct sha1_ctx ipad_opad_ctx[2],
     const uint8_t *data,
     size_t data_len,
-    uint8_t *mac
+    struct sha1_buf *mac
 );
 
 #endif // SHA1_H
diff --git a/totp.c b/totp.c
deleted file mode 100644 (file)
index eb3ecd1..0000000
--- a/totp.c
+++ /dev/null
@@ -1,231 +0,0 @@
-
-// 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
deleted file mode 100644 (file)
index 45e9162..0000000
--- a/totp.h
+++ /dev/null
@@ -1,14 +0,0 @@
-
-// 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
index 5e4f3e10cc9021596479b33e723203af54a64d50..f0544a516c2d8e9118093b25bd570aa90eb49687 100644 (file)
--- a/uart.c
+++ b/uart.c
@@ -9,7 +9,7 @@
 
 static struct {
     volatile uint8_t data[62], head, tail;
-} buffer = { { 0 }, 0, 0 };
+} UART_read_buffer = { { 0 }, 0, 0 };
 
 void UART_init(void) {
     UBRR0H = (uint8_t)(MYUBRR >> 8);         // Set baud rate High byte
@@ -18,33 +18,34 @@ void UART_init(void) {
     // 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
+    sei();
 }
 
-void UART_flush(void) {
-    buffer.head = buffer.tail = 0;
+void UART_read_buffer_flush(void) {
+    UART_read_buffer.head = UART_read_buffer.tail = 0;
 }
 
 ISR(USART_RX_vect) {
-    uint8_t next_head = buffer.head + 1, c = UDR0;
-    if (next_head == sizeof buffer.data)
+    uint8_t next_head = UART_read_buffer.head + 1, c = UDR0;
+    if (next_head == sizeof UART_read_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;
+    if (next_head != UART_read_buffer.tail) {
+        UART_read_buffer.data[UART_read_buffer.head] = c;
+        UART_read_buffer.head = next_head;
     }
 }
 
 uint8_t UART_bytes_available(void) {
-    return buffer.head != buffer.tail;
+    return UART_read_buffer.head != UART_read_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;
+    unsigned char c = UART_read_buffer.data[UART_read_buffer.tail];
+    if (UART_read_buffer.tail != UART_read_buffer.head) {
+        UART_read_buffer.tail = UART_read_buffer.tail + 1;
+        if (UART_read_buffer.tail == sizeof UART_read_buffer.data)
+            UART_read_buffer.tail = 0;
     }
     return c;
 }
diff --git a/uart.h b/uart.h
index c0e7c2b209800346dddd43ed6e8084bf82fba098..cdf903b916105ffa012617fa8572a56e9dac41e3 100644 (file)
--- a/uart.h
+++ b/uart.h
@@ -4,12 +4,12 @@
 #ifndef UART_H
 #define UART_H
 
-#include <stdlib.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <avr/io.h>
 
 void UART_init(void);
-void UART_flush(void);
+void UART_read_buffer_flush(void);
 uint8_t UART_bytes_available(void);
 uint8_t UART_read_byte(void);
 
@@ -23,15 +23,11 @@ static inline void UART_write_byte(uint8_t c) {
     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++)
+    for (i = 0; len ? (i < len) : str[i]; i++)
         UART_write_byte(str[i]);
 }
+#define UART_write_string(s) UART_write_string_partial((s), 0)
 
 #endif // UART_H