]> git.mar77i.info Git - otp_nano/commitdiff
allow creating secrets.h from OTP DUMP output master
authormar77i <mar77i@protonmail.ch>
Sat, 27 Jun 2026 07:21:10 +0000 (09:21 +0200)
committermar77i <mar77i@protonmail.ch>
Sat, 27 Jun 2026 07:21:33 +0000 (09:21 +0200)
decrypt.c
decrypt.h
encoding.h
main.c
manage_secrets.py
repl.py
sha1.c

index bf47543569015573c59feb80eef978e15cfe2b17..7c6b9e478cc3cecaca87f48c3138cb7620e27d90 100644 (file)
--- a/decrypt.c
+++ b/decrypt.c
@@ -93,7 +93,7 @@ uint8_t password_is_ready(void) {
     return decrypt_buffers.status > 0;
 }
 
     return decrypt_buffers.status > 0;
 }
 
-int8_t list_secrets(void) {
+void list_secrets(void) {
     size_t i;
     uint8_t c, colon_found = 0;
     for (i = 0; i < sizeof secret; i++) {
     size_t i;
     uint8_t c, colon_found = 0;
     for (i = 0; i < sizeof secret; i++) {
@@ -106,7 +106,30 @@ int8_t list_secrets(void) {
             UART_write_byte(c);
     }
     decrypt_buffers.status = 2;
             UART_write_byte(c);
     }
     decrypt_buffers.status = 2;
-    return 0;
+}
+
+void dump_secrets(void) {
+    size_t i;
+    uint8_t b;
+    UART_write_string("{\"iv\":\"");
+    for (i = 0; i < sizeof iv; i++) {
+        b = pgm_read_byte(iv.b + i);
+        UART_write_byte("0123456789abcdef"[b >> 4]);
+        UART_write_byte("0123456789abcdef"[b & 15]);
+    }
+    UART_write_string("\",\n\"pw_mac\":\"");
+    for (i = 0; i < sizeof pw_mac; i++) {
+        b = pgm_read_byte(pw_mac.b + i);
+        UART_write_byte("0123456789abcdef"[b >> 4]);
+        UART_write_byte("0123456789abcdef"[b & 15]);
+    }
+    UART_write_string("\",\n\"secret\":\"");
+    for (i = 0; i < sizeof secret; i++) {
+        b = pgm_read_byte(secret + i);
+        UART_write_byte("0123456789abcdef"[b >> 4]);
+        UART_write_byte("0123456789abcdef"[b & 15]);
+    }
+    UART_write_string("\"}\n");
 }
 
 static inline int8_t get_secret_and_totp_line(
 }
 
 static inline int8_t get_secret_and_totp_line(
@@ -120,8 +143,10 @@ static inline int8_t get_secret_and_totp_line(
     ssize_t len;
     uint8_t *ptr, i, decimal[7];
     ptr = line + colon_offset;
     ssize_t len;
     uint8_t *ptr, i, decimal[7];
     ptr = line + colon_offset;
-    if ((len = from_b32_inplace(ptr)) <= 0)
+    if ((len = from_b32_inplace(ptr)) <= 0) {
+        UART_write_string("ERROR:INVALIDB32\n");
         return -1;
         return -1;
+    }
     sha1_hmac(ptr, len, time_msg, 8, &mac);
     i = mac.b[sizeof mac - 1] & 0xf;
     UART_write_string_partial(line, colon_offset);
     sha1_hmac(ptr, len, time_msg, 8, &mac);
     i = mac.b[sizeof mac - 1] & 0xf;
     UART_write_string_partial(line, colon_offset);
index 39a3f560b478c3e07a60c91143176c5378e0113c..a09cb76113d172132056b475ffd086ecbcde611c 100644 (file)
--- a/decrypt.h
+++ b/decrypt.h
@@ -8,7 +8,8 @@
 
 uint8_t init_decrypt_buffers(uint8_t *password, uint8_t password_len);
 uint8_t password_is_ready(void);
 
 uint8_t init_decrypt_buffers(uint8_t *password, uint8_t password_len);
 uint8_t password_is_ready(void);
-int8_t list_secrets(void);
+void list_secrets(void);
+void dump_secrets(void);
 int8_t get_secrets_and_totp(uint8_t time_msg[8], uint8_t *search);
 void clear_decrypt_buffers(void);
 
 int8_t get_secrets_and_totp(uint8_t time_msg[8], uint8_t *search);
 void clear_decrypt_buffers(void);
 
index b01ac1eaa2b627e28ce61ff54b0ffcb1832f355b..d14050134bc671a9b52b8a3f606916959e2cc9ec 100644 (file)
@@ -4,14 +4,11 @@
 #ifndef ENCODING_H
 #define ENCODING_H
 
 #ifndef ENCODING_H
 #define ENCODING_H
 
-#include <ctype.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <stdlib.h>
-#include <string.h>
 
 typedef int16_t ssize_t;
 
 
 typedef int16_t ssize_t;
 
-#include "uart.h"
-
 static inline char from_hex8(char c) {
     if (c >= '0' && c <= '9')
         return c - '0';
 static inline char from_hex8(char c) {
     if (c >= '0' && c <= '9')
         return c - '0';
@@ -86,10 +83,8 @@ static inline ssize_t from_b32_inplace(char *s) {
     for (; (val = b32_lut[s[pos]]) != 0xfd; pos++) {
         if (val == 0xfe)
             continue;
     for (; (val = b32_lut[s[pos]]) != 0xfd; pos++) {
         if (val == 0xfe)
             continue;
-        else if (val == 0) {
-            UART_write_string("ERROR:INVALIDB32\n");
+        else if (val == 0)
             return -1;
             return -1;
-        }
         val &= 31;
         bits_left -= 5;
         if (bits_left < 0) {
         val &= 31;
         bits_left -= 5;
         if (bits_left < 0) {
diff --git a/main.c b/main.c
index 6c35c177392f4db76765d690190fcc673d1b5845..076ce52c2f620178f3edd38d2b6272442efbc161 100644 (file)
--- a/main.c
+++ b/main.c
@@ -2,7 +2,6 @@
 // main.c
 
 #include <limits.h>
 // main.c
 
 #include <limits.h>
-#include <stdint.h>
 #include <avr/io.h>
 
 #include "decrypt.h"
 #include <avr/io.h>
 
 #include "decrypt.h"
@@ -85,9 +84,13 @@ int8_t process_payload(uint8_t *payload) {
         payload += 4;
         if (!password_is_ready())
             return -1;
         payload += 4;
         if (!password_is_ready())
             return -1;
-        if (strcmp(payload, "LS") == 0)
-            return list_secrets();
-        else if (strncmp(payload, "GET ", 4) == 0)
+        if (strcmp(payload, "LS") == 0) {
+            list_secrets();
+            return 0;
+        } else if (strcmp(payload, "DUMP") == 0) {
+            dump_secrets();
+            return 0;
+        } else if (strncmp(payload, "GET ", 4) == 0)
             return process_otp_get(payload + 4);
     }
     return -1;
             return process_otp_get(payload + 4);
     }
     return -1;
index 5320c3ac04ef9c14ec5b9e1d97149d0f7464e5de..e32d2af45245dcebf5cc1e5c1df3e4ce151f9c14 100755 (executable)
@@ -1,6 +1,8 @@
 #!/usr/bin/env python3
 
 import hmac
 #!/usr/bin/env python3
 
 import hmac
+import json
+import sys
 from argparse import ArgumentParser
 from getpass import getpass
 from hashlib import sha1
 from argparse import ArgumentParser
 from getpass import getpass
 from hashlib import sha1
@@ -29,6 +31,9 @@ class CryptoManager:
         self.password = password
         self.indices = self.init_indices(self.get_hash(self.password, self.iv))
 
         self.password = password
         self.indices = self.init_indices(self.get_hash(self.password, self.iv))
 
+    def pw_mac(self):
+        return self.get_hash(self.iv, self.password)
+
     def init_indices(self, first_key) -> list[int]:
         indices = list(range(20))
         pick_accu = 0
     def init_indices(self, first_key) -> list[int]:
         indices = list(range(20))
         pick_accu = 0
@@ -55,24 +60,22 @@ class CryptoManager:
             opad_ba[i] ^= c
         return sha1(ipad_ba), sha1(opad_ba)
 
             opad_ba[i] ^= c
         return sha1(ipad_ba), sha1(opad_ba)
 
-    def scramble(self, data):
-        bio = BytesIO()
+    def scramble(self, data, fh=None):
+        if fh is None:
+            fh = BytesIO()
         round_key = self.iv
         ipad_template, opad_template = self.prepare_ipad_opad()
         for i in range(0, len(data), 10):
         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]
             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()
+            fh.write(bytes(a ^ b for a, b in zip(block, opad.digest()[:len(block)])))
+        return fh
 
 
 
 
-class HeaderReader:
+class HeaderCodec:
     def __init__(self):
         self.iv_bio = BytesIO()
         self.pw_mac_bio = BytesIO()
     def __init__(self):
         self.iv_bio = BytesIO()
         self.pw_mac_bio = BytesIO()
@@ -114,13 +117,16 @@ class HeaderReader:
         if done:
             self.current_bio = None
 
         if done:
             self.current_bio = None
 
-    def parse(self, fh):
+    @classmethod
+    def parse(cls, fh):
+        header = cls()
         for line in fh:
             line = line.strip()
         for line in fh:
             line = line.strip()
-            if self.current_bio is None:
-                self.find_header(line)
+            if header.current_bio is None:
+                header.find_header(line)
             else:
             else:
-                self.add_bytes(line)
+                header.add_bytes(line)
+        return header
 
     @property
     def iv(self):
 
     @property
     def iv(self):
@@ -140,103 +146,117 @@ class HeaderReader:
         assert len(secret) > 0
         return secret
 
         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
 
     @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)
+    @classmethod
+    def print_as_const_sha_buf(cls, fh, varname, data):
+        print(f"static const struct sha1_buf {varname} PROGMEM = {{", file=fh)
+        print(f"{cls.indent(4)}{{\n{cls.indent(8)}", end="", file=fh)
         last = len(data) - 1
         for i, b in enumerate(data):
         last = len(data) - 1
         for i, b in enumerate(data):
-            print(f"0x{b:02x}", end=",", file=self.fh)
+            print(f"0x{b:02x}", end=",", file=fh)
             if i == last:
             if i == last:
-                print(f"\n{self.indent(4)}}}", file=self.fh)
+                print(f"\n{cls.indent(4)}}}", file=fh)
             elif i % 12 == 11:
             elif i % 12 == 11:
-                print(f"\n{self.indent(8)}", end="", file=self.fh)
+                print(f"\n{cls.indent(8)}", end="", file=fh)
             else:
             else:
-                print(end=" ", file=self.fh)
-        print("};\n", file=self.fh)
+                print(end=" ", file=fh)
+        print("};\n", file=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)
+    @classmethod
+    def print_as_const(cls, fh, varname, data):
+        print(f"static const uint8_t {varname}[] PROGMEM = {{", file=fh)
+        print(f"{cls.indent(4)}", end="", file=fh)
         last = len(data) - 1
         for i, b in enumerate(data):
         last = len(data) - 1
         for i, b in enumerate(data):
-            print(f"0x{b:02x}", end=",", file=self.fh)
+            print(f"0x{b:02x}", end=",", file=fh)
             if i == last:
             if i == last:
-                print(file=self.fh)
+                print(file=fh)
             elif i % 12 == 11:
             elif i % 12 == 11:
-                print(f"\n{self.indent(4)}", end="", file=self.fh)
+                print(f"\n{cls.indent(4)}", end="", file=fh)
             else:
             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)
+                print(end=" ", file=fh)
+        print("};\n", file=fh)
+
+    @classmethod
+    def write(cls, fh, iv, pw_mac, secret):
+        print("\n// secrets.h\n", file=fh)
+        print("#ifndef SECRETS_H", file=fh)
+        print("#define SECRETS_H\n", file=fh)
+        print("#include <stdint.h>", file=fh)
+        print("#include <avr/pgmspace.h>\n", file=fh)
+        print('#include "sha1.h"\n', file=fh)
+        cls.print_as_const_sha_buf(fh, "iv", iv)
+        cls.print_as_const_sha_buf(fh, "pw_mac", pw_mac)
+        cls.print_as_const(fh, "secret", secret)
+        print("#endif // SECRETS_H", file=fh)
+
+
+def cleanup_secret(infh):
+    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()
 
 
 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():
 
 
 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:
         with secrets_h.open("rt") as fh:
-            header.parse(fh)
+            header = HeaderCodec.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:
         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))
+        crypto_manager = CryptoManager(token_bytes(20), password)
+        HeaderCodec.write(
+            outfh,
+            crypto_manager.iv,
+            crypto_manager.pw_mac(),
+            crypto_manager.scramble(cleanup_secret(infh)).getvalue(),
+        )
 
 def decrypt_secrets(project_dir, outfile):
     password = getpass().encode()
     secrets_h = project_dir / "secrets.h"
     assert secrets_h.exists()
 
 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:
     with secrets_h.open("rt") as fh:
-        header.parse(fh)
+        header = HeaderCodec.parse(fh)
     assert CryptoManager.get_hash(header.iv, password) == header.pw_mac
     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))
+    outfile_path = Path(outfile)
+    assert outfile_path.absolute() != secrets_h.absolute(), (
+        "Don't decrypt into secrets.h"
+    )
+    with outfile_path.open("wb") as fh:
+        CryptoManager(header.iv, password).scramble(header.secret, fh)
 
 
 
 
-def main():
+def from_dump(project_dir, outfile):
+    print("reading dump > ", end="")
+    sys.stdout.flush()
+    data = json.load(sys.stdin)
+    iv = bytes.fromhex(data["iv"])
+    pw_mac = bytes.fromhex(data["pw_mac"])
+    secret = bytes.fromhex(data["secret"])
+    print("done")
+    if outfile is None:
+        outfile = project_dir / "secrets.h"
+    else:
+        outfile = Path(outfile)
+    with outfile.open("wt") as fh:
+        HeaderCodec.write(fh, iv, pw_mac, secret)
+
+
+def parse_args():
     ap = ArgumentParser()
     sp = ap.add_subparsers(dest="action", required=True, help="Actions")
     ep = sp.add_parser("encrypt")
     ap = ArgumentParser()
     sp = ap.add_subparsers(dest="action", required=True, help="Actions")
     ep = sp.add_parser("encrypt")
@@ -244,12 +264,20 @@ def main():
     ep.add_argument("--new-password", action="store_true")
     dp = sp.add_parser("decrypt")
     dp.add_argument("outfile")
     ep.add_argument("--new-password", action="store_true")
     dp = sp.add_parser("decrypt")
     dp.add_argument("outfile")
-    args = ap.parse_args()
+    mp = sp.add_parser("from_dump")
+    mp.add_argument("outfile", nargs="?", default=None)
+    return ap.parse_args()
+
+
+def main():
+    args = 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)
     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)
+    elif args.action == "from_dump":
+        from_dump(project_dir, args.outfile)
 
 
 if __name__ == "__main__":
 
 
 if __name__ == "__main__":
diff --git a/repl.py b/repl.py
index 288bed9b60740bc961519f46ac0abb3dd2bd5008..349eafb4d530c8695ff3e4f68538589ab990e877 100755 (executable)
--- a/repl.py
+++ b/repl.py
@@ -3,6 +3,7 @@
 import os
 import sys
 from getpass import getpass
 import os
 import sys
 from getpass import getpass
+from io import BytesIO
 from termios import (
     B115200,
     CS8,
 from termios import (
     B115200,
     CS8,
@@ -78,37 +79,46 @@ class ReplManager:
 
     def print_reply(self, line):
         try:
 
     def print_reply(self, line):
         try:
-            response = line.decode('ascii').strip()
+            response = line.decode('ascii')
         except UnicodeDecodeError:
             print("decoding failed.")
             response = str(line)
         print(response)
 
     def await_some_reply(self, wait_for_ok=False, timeout=0.3):
         except UnicodeDecodeError:
             print("decoding failed.")
             response = str(line)
         print(response)
 
     def await_some_reply(self, wait_for_ok=False, timeout=0.3):
-        ba = bytearray()
+        bio = BytesIO()
         stop_time = time() + timeout
         stop_time = time() + timeout
-        done = False
-        while not done:
+        while time() < stop_time:
             sleep(0.1)
             t = time()
             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:
+            r = 0
+            while True:
+                try:
+                    received = os.read(self.fd, 4096)
+                except BlockingIOError:
+                    received = b""
+                if not received:
+                    break
+                r += len(received)
+                bio.write(received)
+                stop_time = t + (timeout if wait_for_ok else 0.3)
+            if r == 0:
                 continue
                 continue
-            ba += received
-            stop_time = t + (timeout if wait_for_ok else 0.3)
-            while b"\n" in ba:
-                line, rest = ba.split(b"\n", 1)
+            value = bio.getvalue()
+            bio.seek(0)
+            bio.truncate()
+            while b"\n" in value:
+                line, value = value.split(b"\n", 1)
                 self.print_reply(line)
                 self.print_reply(line)
-                ba = rest
                 if wait_for_ok and line in (b"OK", b"NOK", b"EYB"):
                 if wait_for_ok and line in (b"OK", b"NOK", b"EYB"):
-                    done = True
+                    stop_time = t
                     break
                     break
-        if len(ba) > 0:
-            self.print_reply(ba)
+            bio.write(value)
+        value = bio.getvalue()
+        bio.seek(0)
+        bio.truncate()
+        if len(value) > 0:
+            self.print_reply(value)
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         if self.last_cmd != "BYE":
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         if self.last_cmd != "BYE":
diff --git a/sha1.c b/sha1.c
index eab857fa90530cbdaaec52ea54135f7b73b885d9..5982b7d9809f107296d5896517b3c7db58f06792 100644 (file)
--- a/sha1.c
+++ b/sha1.c
@@ -1,7 +1,6 @@
 
 // sha1.c
 
 
 // sha1.c
 
-#include <string.h>
 #include "sha1.h"
 
 #define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof (arr)[0])
 #include "sha1.h"
 
 #define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof (arr)[0])