Compare commits
17 Commits
not_workin
...
main
Author | SHA1 | Date |
---|---|---|
kirbylife | 01d7651212 | |
kirbylife | 8e71f26261 | |
kirbylife | d9d5e832d2 | |
kirbylife | b99737a7a7 | |
kirbylife | 0636253f06 | |
kirbylife | 8705519c0b | |
kirbylife | 298117ee90 | |
kirbylife | c8f14b7b19 | |
kirbylife | f83638bfae | |
kirbylife | 4396e6a43d | |
kirbylife | dbc75352ac | |
kirbylife | e6974cffb2 | |
kirbylife | 5098e7b453 | |
kirbylife | 432a770887 | |
kirbylife | e34838c985 | |
kirbylife | 12232bfba9 | |
kirbylife | 28a17491e7 |
|
@ -5,7 +5,7 @@ version = 3
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arduino-hal"
|
name = "arduino-hal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
|
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atmega-hal",
|
"atmega-hal",
|
||||||
"avr-device",
|
"avr-device",
|
||||||
|
@ -19,7 +19,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atmega-hal"
|
name = "atmega-hal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
|
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avr-device",
|
"avr-device",
|
||||||
"avr-hal-generic",
|
"avr-hal-generic",
|
||||||
|
@ -33,9 +33,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "avr-device"
|
name = "avr-device"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "deb45711b7227cb3f799c40c4e0e8beea1765acff8d06a14bb3479c1a549d8b0"
|
checksum = "644690f9699830f0efce38ca9d41c7ad51a6ec1829364dd272638938c953673a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avr-device-macros",
|
"avr-device-macros",
|
||||||
"bare-metal",
|
"bare-metal",
|
||||||
|
@ -45,9 +45,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "avr-device-macros"
|
name = "avr-device-macros"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87c428a2384981d8460668c48c766ff18e7e91d2d467f7b103151984ee952d38"
|
checksum = "ba8b49941a5148cec432d5180d205871f0132f9f15b7225a9f3ad9e2bae026cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -57,7 +57,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "avr-hal-generic"
|
name = "avr-hal-generic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/rahix/avr-hal#fb609ab0d14c5a0a44e2dff3e5e514cb612a529a"
|
source = "git+https://github.com/rahix/avr-hal#e29a7be5b1fa9d490ded11e539d345af041ccef6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avr-device",
|
"avr-device",
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
|
@ -76,12 +76,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "binascii"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
@ -107,8 +101,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ds323x"
|
name = "ds323x"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/kirbylife/ds323x-rs?branch=fix-set-day-format#e204b77494f921cfc276c9a66e6e9ba95dc4e142"
|
||||||
checksum = "057e63d03a3beb83d3c9b7a19b799c60e203b0c53f1bbec6a715804907f73a2b"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
"rtcc",
|
"rtcc",
|
||||||
|
@ -130,6 +123,11 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
|
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac-sha1-compact"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "git+https://github.com/kirbylife/rust-hmac-sha1#a3c4116837efb8a850fb0d03985831ce4dc2bc12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -184,9 +182,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.54"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
|
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -220,20 +218,14 @@ name = "rustytoken"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arduino-hal",
|
"arduino-hal",
|
||||||
"binascii",
|
|
||||||
"chrono",
|
|
||||||
"ds323x",
|
"ds323x",
|
||||||
|
"embedded-hal",
|
||||||
|
"hmac-sha1-compact",
|
||||||
|
"nb 1.1.0",
|
||||||
"panic-halt",
|
"panic-halt",
|
||||||
"sha1_smol",
|
|
||||||
"ufmt 0.2.0",
|
"ufmt 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha1_smol"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -4,17 +4,17 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arduino-hal = { git = "https://github.com/rahix/avr-hal", features = ["arduino-uno"] }
|
arduino-hal = { git = "https://github.com/rahix/avr-hal", features = ["arduino-nano"] }
|
||||||
|
ds323x = { git = "https://github.com/kirbylife/ds323x-rs", branch = "fix-set-day-format" }
|
||||||
|
hmac-sha1-compact = { git = "https://github.com/kirbylife/rust-hmac-sha1" }
|
||||||
panic-halt = "0.2.0"
|
panic-halt = "0.2.0"
|
||||||
sha1_smol = "1.0.0"
|
embedded-hal = "0.2.7"
|
||||||
binascii = { version = "0.1", default-features = false, features = ["decode"] }
|
nb = "1.1.0"
|
||||||
chrono = { version = "0.4.24", default-features = false }
|
|
||||||
ds323x = "0.5.0"
|
|
||||||
ufmt = "0.2.0"
|
ufmt = "0.2.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
strip = true
|
strip = true
|
||||||
opt-level = "z"
|
opt-level = "s"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
39
README.md
39
README.md
|
@ -1,5 +1,38 @@
|
||||||
# RustyToken
|
# RustyToken (I'm open to new names)
|
||||||
|
|
||||||
## Converts any Arduino into a TOTP keys
|
IT's a **TOY** utility to make a TOTP keychain.
|
||||||
|
This it's a **TOY**, Please don't keep your top priority keys here.
|
||||||
|
I disclaim any responsability for anything that may happen to your TOTP keys.
|
||||||
|
|
||||||
You can build a TOTP Key with an Arduino
|
## Hardware
|
||||||
|
To make your own RustyToken you'll need:
|
||||||
|
- 1 Arduino (UNO/Nano)
|
||||||
|
- 1 RTC DS3231
|
||||||
|
- 1 Screen STN LCD 16x2 without i2c interface
|
||||||
|
- 2 push buttons
|
||||||
|
- 1 220 ohms
|
||||||
|
- A lot of wire
|
||||||
|
|
||||||
|
## Software
|
||||||
|
To build the firmware for your Arduino you'll need:
|
||||||
|
- A linux machine (I don't know if this compile on another OS)
|
||||||
|
- All the rust env tools (rustc, cargo, rustup)
|
||||||
|
- The Rust's nightly version
|
||||||
|
- all the AVR env build tools (avr-gcc, avr-binutils, avr-libc, avrdude)
|
||||||
|
- Python3 to use the tool
|
||||||
|
|
||||||
|
## How to build and load the firmware
|
||||||
|
To build the firmware you execute:
|
||||||
|
```Bash
|
||||||
|
$ ./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
And to upload it to the Arduino, connect it to the computer and execute:
|
||||||
|
```Bash
|
||||||
|
$ ./upload.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
IF this step fails, edit the `upload.sh` script and change the `/dev/ttyUSB0` to the Serial port assiged to your Arduino (You can check this path with the Arduino IDE)
|
||||||
|
|
||||||
|
## Diagram
|
||||||
|
![Diagrama de RustyToken](imgs/diagram.png)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
|
@ -0,0 +1,63 @@
|
||||||
|
use embedded_hal::digital::v2::InputPin;
|
||||||
|
|
||||||
|
pub struct Button<'a, P> {
|
||||||
|
pin: &'a P,
|
||||||
|
state: bool,
|
||||||
|
is_pullup: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum Event {
|
||||||
|
PressDown,
|
||||||
|
PressUp,
|
||||||
|
Pressed,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: InputPin> Button<'a, P> {
|
||||||
|
pub fn new(pin: &'a P, is_pullup: bool) -> Self {
|
||||||
|
let mut state = pin.is_high().map_err(|_| ()).unwrap();
|
||||||
|
if is_pullup {
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
pin,
|
||||||
|
state,
|
||||||
|
is_pullup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_pin(&self) -> bool {
|
||||||
|
let mut inc = 0;
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let mut state = self.pin.is_high().map_err(|_| ()).unwrap();
|
||||||
|
if self.is_pullup {
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
if state {
|
||||||
|
inc += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inc > 70
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) -> Event {
|
||||||
|
let actual_state = self.read_pin();
|
||||||
|
|
||||||
|
match (self.state, actual_state) {
|
||||||
|
(false, false) => Event::None,
|
||||||
|
(true, false) => {
|
||||||
|
self.state = !self.state;
|
||||||
|
Event::PressUp
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
self.state = !self.state;
|
||||||
|
Event::PressDown
|
||||||
|
}
|
||||||
|
(true, true) => Event::Pressed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,40 +1,16 @@
|
||||||
use sha1_smol::Sha1;
|
use hmac_sha1_compact::HMAC;
|
||||||
|
|
||||||
const BLOCK_SIZE: usize = 64;
|
pub const INTERVAL: u64 = 30;
|
||||||
|
|
||||||
pub fn hmac_sha1(key: &[u8], message: &[u8], output: &mut [u8]) {
|
pub fn generate_otp_token(private_key: &[u8], actual_time: u64) -> u32 {
|
||||||
// Preprocess the key.
|
let interval = actual_time / INTERVAL;
|
||||||
let mut key_padded = [0u8; BLOCK_SIZE];
|
let msg = interval.to_be_bytes();
|
||||||
if key.len() > BLOCK_SIZE {
|
|
||||||
let mut hash = Sha1::new();
|
|
||||||
hash.update(key);
|
|
||||||
let key_digest = hash.digest().bytes();
|
|
||||||
key_padded[..20].copy_from_slice(&key_digest);
|
|
||||||
} else {
|
|
||||||
key_padded[..key.len()].copy_from_slice(key);
|
|
||||||
}
|
|
||||||
for byte in &mut key_padded {
|
|
||||||
*byte ^= 0x36;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the inner padded key and the message.
|
let hmac_digest = HMAC::mac(&msg, private_key);
|
||||||
let mut hash = Sha1::new();
|
|
||||||
hash.update(&key_padded[..]);
|
|
||||||
hash.update(message);
|
|
||||||
let mut inner_hash = [0u8; 20];
|
|
||||||
inner_hash.copy_from_slice(&hash.digest().bytes());
|
|
||||||
|
|
||||||
// Preprocess the key again.
|
let start = (hmac_digest[19] & 0xF) as usize;
|
||||||
for byte in &mut key_padded {
|
let bytes: [u8; 4] = hmac_digest[start..start + 4].try_into().unwrap();
|
||||||
*byte ^= 0x36 ^ 0x5c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the outer padded key and the inner hash.
|
let raw_token = u32::from_be_bytes(bytes);
|
||||||
let mut hash = Sha1::new();
|
((raw_token & 0x7F_FF_FF_FF) % 1_000_000) as u32
|
||||||
hash.update(&key_padded[..]);
|
|
||||||
hash.update(&inner_hash[..]);
|
|
||||||
let hmac_digest = hash.digest().bytes();
|
|
||||||
|
|
||||||
// Copy the result into the output buffer.
|
|
||||||
output.copy_from_slice(&hmac_digest);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
// This code was extracted from http://git.musl-libc.org/cgit/musl/tree/src/time?h=v0.9.15
|
||||||
|
|
||||||
|
pub struct Datetime {
|
||||||
|
pub year: u16,
|
||||||
|
pub month: u8,
|
||||||
|
pub day: u8,
|
||||||
|
pub hours: u8,
|
||||||
|
pub minutes: u8,
|
||||||
|
pub seconds: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Datetime {
|
||||||
|
pub fn unix_epoch(&self) -> u64 {
|
||||||
|
let year: i64 = self.year as i64 - 1900;
|
||||||
|
let month: i64 = self.month as i64 - 1;
|
||||||
|
|
||||||
|
let (mut t, is_leap) = year_to_secs(year);
|
||||||
|
t += month_to_secs(month, is_leap);
|
||||||
|
t += 86400 * (self.day as i64 - 1);
|
||||||
|
t += 3600 * self.hours as i64;
|
||||||
|
t += 60 * self.minutes as i64;
|
||||||
|
t += self.seconds as i64;
|
||||||
|
t as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_ds3231<T: ds323x::Rtcc>(rtc: &mut T) -> Self {
|
||||||
|
let year = rtc.year().map_err(|_| ()).unwrap();
|
||||||
|
let month = rtc.month().map_err(|_| ()).unwrap();
|
||||||
|
let day = rtc.day().map_err(|_| ()).unwrap();
|
||||||
|
let hours = match rtc.hours().map_err(|_| ()).unwrap() {
|
||||||
|
ds323x::Hours::AM(val) => val,
|
||||||
|
ds323x::Hours::PM(val) => val + 12,
|
||||||
|
ds323x::Hours::H24(val) => val,
|
||||||
|
};
|
||||||
|
let minutes = rtc.minutes().map_err(|_| ()).unwrap();
|
||||||
|
let seconds = rtc.seconds().map_err(|_| ()).unwrap();
|
||||||
|
|
||||||
|
Datetime {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day,
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn year_to_secs(year: i64) -> (i64, bool) {
|
||||||
|
let is_leap: bool;
|
||||||
|
let res: i64;
|
||||||
|
|
||||||
|
let mut cycles: i64;
|
||||||
|
let centuries: i64;
|
||||||
|
let mut leaps: i64;
|
||||||
|
let mut rem: i64;
|
||||||
|
|
||||||
|
cycles = (year - 100) / 400;
|
||||||
|
rem = (year - 100) % 400;
|
||||||
|
if rem < 0 {
|
||||||
|
cycles -= 1;
|
||||||
|
rem += 400;
|
||||||
|
}
|
||||||
|
if rem == 0 {
|
||||||
|
is_leap = true;
|
||||||
|
centuries = 0;
|
||||||
|
leaps = 0;
|
||||||
|
} else {
|
||||||
|
if rem >= 200 {
|
||||||
|
if rem >= 300 {
|
||||||
|
centuries = 3;
|
||||||
|
rem -= 300;
|
||||||
|
} else {
|
||||||
|
centuries = 2;
|
||||||
|
rem -= 200;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if rem >= 100 {
|
||||||
|
centuries = 1;
|
||||||
|
rem -= 100;
|
||||||
|
} else {
|
||||||
|
centuries = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rem == 0 {
|
||||||
|
is_leap = false;
|
||||||
|
leaps = 0;
|
||||||
|
} else {
|
||||||
|
leaps = rem / 4;
|
||||||
|
rem %= 4;
|
||||||
|
is_leap = rem == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leaps += 97 * cycles + 24 * centuries - (is_leap as i64);
|
||||||
|
|
||||||
|
res = (year - 100) * 31536000 + leaps * 86400 + 946684800 + 86400;
|
||||||
|
|
||||||
|
(res, is_leap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn month_to_secs(month: i64, is_leap: bool) -> i64 {
|
||||||
|
const SECS_THROUGH_MONTH: [i64; 12] = [
|
||||||
|
0, // Jan
|
||||||
|
31 * 86400, // Feb
|
||||||
|
59 * 86400, // Mar
|
||||||
|
90 * 86400, // Apr
|
||||||
|
120 * 86400, // May
|
||||||
|
151 * 86400, // Jun
|
||||||
|
181 * 86400, // Jul
|
||||||
|
212 * 86400, // Aug
|
||||||
|
243 * 86400, // Sep
|
||||||
|
273 * 86400, // Oct
|
||||||
|
304 * 86400, // Nov
|
||||||
|
334 * 86400, // Dec
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut t = SECS_THROUGH_MONTH[month as usize];
|
||||||
|
if is_leap && month >= 2 {
|
||||||
|
t += 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
t
|
||||||
|
}
|
242
src/main.rs
242
src/main.rs
|
@ -1,70 +1,35 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
|
mod button;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
mod datetime;
|
||||||
|
mod screen;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
use arduino_hal::default_serial;
|
use arduino_hal::default_serial;
|
||||||
use arduino_hal::delay_ms;
|
use arduino_hal::delay_ms;
|
||||||
use crypto::hmac_sha1;
|
// use arduino_hal::delay_ms;
|
||||||
use ds323x::DateTimeAccess;
|
use arduino_hal::prelude::*;
|
||||||
|
use crypto::generate_otp_token;
|
||||||
|
use crypto::INTERVAL;
|
||||||
|
use ds323x::Rtcc;
|
||||||
|
use nb::block;
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
|
use storage::SECRET_KEY_MAX_LEN;
|
||||||
|
use storage::SECRET_KEY_NAME_LEN;
|
||||||
|
|
||||||
const INTERVAL: i64 = 30;
|
const ENDL: u8 = 0;
|
||||||
const SECRET_KEY_MAX_LEN: usize = 32;
|
const OK: u8 = 1;
|
||||||
|
const ERROR: u8 = 2;
|
||||||
fn process_secret<'a>(secret: &str, output: &mut [u8]) -> usize {
|
const HANDSHAKE: u8 = 3;
|
||||||
// RFC 6238 Doesn't specify a max length for the secret keys, So I chose 32
|
const SET_TIMESTAMP: u8 = 10;
|
||||||
// You can change the max length by modifying the const
|
const ADD_TOKEN: u8 = 20;
|
||||||
let mut result = [0u8; SECRET_KEY_MAX_LEN];
|
const DELETE_TOKEN: u8 = 30;
|
||||||
let mut inc = 0;
|
const GET_TOKENS: u8 = 40;
|
||||||
let offset = match 8 - (secret.len() % 8) {
|
const SOFT_WIPE_TOKENS: u8 = 50;
|
||||||
8 => 0,
|
const HARD_WIPE_TOKENS: u8 = 60;
|
||||||
n => n,
|
const EXIT: u8 = 255;
|
||||||
};
|
|
||||||
|
|
||||||
for c in secret.chars() {
|
|
||||||
result[inc] = c.to_ascii_uppercase() as u8;
|
|
||||||
inc += 1;
|
|
||||||
}
|
|
||||||
for _ in 0..offset {
|
|
||||||
result[inc] = '=' as u8;
|
|
||||||
inc += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let decoded = match binascii::b32decode(&result[..inc], output).ok() {
|
|
||||||
Some(bytes) => bytes,
|
|
||||||
None => "".as_bytes(), // TODO: Implement a way to display errors
|
|
||||||
};
|
|
||||||
decoded.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_otp_token(private_key: &str, actual_time: i64) -> u32 {
|
|
||||||
let mut output = [0u8; 50];
|
|
||||||
let private_key_len = process_secret(private_key, &mut output);
|
|
||||||
|
|
||||||
let interval = actual_time / INTERVAL;
|
|
||||||
let msg = interval.to_be_bytes();
|
|
||||||
|
|
||||||
let mut hmac_digest = [0u8; 20];
|
|
||||||
hmac_sha1(&output[..private_key_len], &msg, &mut hmac_digest);
|
|
||||||
|
|
||||||
let start = (hmac_digest[19] & 0xF) as usize;
|
|
||||||
let bytes: [u8; 4] = hmac_digest[start..start + 4].try_into().unwrap();
|
|
||||||
|
|
||||||
let raw_token = u32::from_be_bytes(bytes);
|
|
||||||
((raw_token & 0x7F_FF_FF_FF) % 1_000_000) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn token_to_bytes(mut token: u32, output: &mut [u8]) {
|
|
||||||
let mut inc = 0;
|
|
||||||
while token > 0 {
|
|
||||||
output[inc] = (token % 10) as u8 + b'0';
|
|
||||||
token /= 10;
|
|
||||||
inc += 1;
|
|
||||||
}
|
|
||||||
output.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[arduino_hal::entry]
|
#[arduino_hal::entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
@ -73,26 +38,163 @@ fn main() -> ! {
|
||||||
|
|
||||||
let mut led = pins.d13.into_output();
|
let mut led = pins.d13.into_output();
|
||||||
let mut serial = default_serial!(dp, pins, 9600);
|
let mut serial = default_serial!(dp, pins, 9600);
|
||||||
let i2c = arduino_hal::I2c::new(
|
|
||||||
|
let mut rs = pins.d9.into_output();
|
||||||
|
let mut en = pins.d8.into_output();
|
||||||
|
let mut d4 = pins.d5.into_output();
|
||||||
|
let mut d5 = pins.d4.into_output();
|
||||||
|
let mut d6 = pins.d3.into_output();
|
||||||
|
let mut d7 = pins.d2.into_output();
|
||||||
|
|
||||||
|
let mut display = screen::StnScreen::new(&mut rs, &mut en, &mut d4, &mut d5, &mut d6, &mut d7);
|
||||||
|
display.clear();
|
||||||
|
|
||||||
|
let i2c_clock = arduino_hal::I2c::new(
|
||||||
dp.TWI,
|
dp.TWI,
|
||||||
pins.a4.into_pull_up_input(),
|
pins.a4.into_pull_up_input(), // SDA
|
||||||
pins.a5.into_pull_up_input(),
|
pins.a5.into_pull_up_input(), // SCL
|
||||||
50000,
|
50000,
|
||||||
);
|
);
|
||||||
let mut rtc = ds323x::Ds323x::new_ds3231(i2c);
|
let mut rtc = ds323x::Ds323x::new_ds3231(i2c_clock);
|
||||||
|
|
||||||
let timestamp = rtc.datetime().unwrap().timestamp();
|
let mut eeprom = arduino_hal::Eeprom::new(dp.EEPROM);
|
||||||
let timestamp = 0;
|
let mut tokens = storage::Tokens::new(&mut eeprom);
|
||||||
|
|
||||||
ufmt::uwriteln!(&mut serial, "timestamp: {}", timestamp).unwrap();
|
let up = pins.a2.into_pull_up_input();
|
||||||
|
let mut up_button = button::Button::new(&up, true);
|
||||||
|
let down = pins.a1.into_pull_up_input();
|
||||||
|
let mut down_button = button::Button::new(&down, true);
|
||||||
|
|
||||||
// let token = generate_otp_token("holaaaaamundo", timestamp.try_into().unwrap());
|
up_button.update();
|
||||||
let token = generate_otp_token("aaaaaaaa", timestamp);
|
if up_button.update() == button::Event::Pressed || tokens.current.is_none() {
|
||||||
|
display.write_str("Connected to");
|
||||||
|
display.set_cursor(0, 1);
|
||||||
|
display.write_str("USB...");
|
||||||
|
loop {
|
||||||
|
// Waiting from a command from the tool
|
||||||
|
let cmd = block!(serial.read()).unwrap_or(u8::MAX);
|
||||||
|
|
||||||
ufmt::uwriteln!(&mut serial, "token: {}", token).unwrap();
|
match cmd {
|
||||||
|
HANDSHAKE => serial.write(OK).unwrap(),
|
||||||
|
SET_TIMESTAMP => {
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
let year = block!(serial.read()).unwrap();
|
||||||
|
let month = block!(serial.read()).unwrap();
|
||||||
|
let day = block!(serial.read()).unwrap();
|
||||||
|
let hours = block!(serial.read()).unwrap();
|
||||||
|
let minutes = block!(serial.read()).unwrap();
|
||||||
|
let seconds = block!(serial.read()).unwrap();
|
||||||
|
|
||||||
|
rtc.set_year(year as u16 + 2000).unwrap();
|
||||||
|
rtc.set_month(month).unwrap();
|
||||||
|
rtc.set_day(day).unwrap();
|
||||||
|
rtc.set_hours(ds323x::Hours::H24(hours)).unwrap();
|
||||||
|
rtc.set_minutes(minutes).unwrap();
|
||||||
|
rtc.set_seconds(seconds).unwrap();
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
}
|
||||||
|
ADD_TOKEN => match tokens.search_free() {
|
||||||
|
Some(index) => {
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
let mut name_buff = [0u8; SECRET_KEY_NAME_LEN as usize];
|
||||||
|
for i in 0..name_buff.len() {
|
||||||
|
let ch = block!(serial.read()).unwrap();
|
||||||
|
if ch == ENDL {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
name_buff[i] = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut key_buff = [0u8; SECRET_KEY_MAX_LEN as usize];
|
||||||
|
for i in 0..key_buff.len() {
|
||||||
|
let ch = block!(serial.read()).unwrap();
|
||||||
|
if ch == ENDL {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
key_buff[i] = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match tokens.write(index, &name_buff, &key_buff) {
|
||||||
|
Ok(_) => serial.write(OK).unwrap(),
|
||||||
|
Err(_) => serial.write(ERROR).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => serial.write(ERROR).unwrap(),
|
||||||
|
},
|
||||||
|
SOFT_WIPE_TOKENS => {
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
let deleted_tokens = tokens.soft_wipe_all_tokens();
|
||||||
|
serial.write(deleted_tokens).unwrap();
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
}
|
||||||
|
HARD_WIPE_TOKENS => {
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
tokens.hard_wipe_all_tokens();
|
||||||
|
serial.write(OK).unwrap();
|
||||||
|
}
|
||||||
|
EXIT => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_time = 0;
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
led.toggle();
|
let timestamp = datetime::Datetime::from_ds3231(&mut rtc).unix_epoch();
|
||||||
delay_ms(1000);
|
|
||||||
|
if up_button.update() == button::Event::PressUp {
|
||||||
|
changed = true;
|
||||||
|
tokens.next();
|
||||||
|
}
|
||||||
|
if down_button.update() == button::Event::PressUp {
|
||||||
|
changed = true;
|
||||||
|
tokens.prev();
|
||||||
|
}
|
||||||
|
let curr_time = INTERVAL - (timestamp % INTERVAL);
|
||||||
|
if curr_time == last_time {
|
||||||
|
delay_ms(10);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
last_time = curr_time;
|
||||||
|
display.set_cursor(12, 1);
|
||||||
|
display.write_str(" ");
|
||||||
|
display.set_cursor(12, 1);
|
||||||
|
display.write_u8(curr_time as u8);
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
display.set_cursor(0, 0);
|
||||||
|
display.write_str(" ");
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match tokens.current {
|
||||||
|
Some(index) => {
|
||||||
|
let mut buff_name = [0u8; SECRET_KEY_NAME_LEN as usize];
|
||||||
|
let mut buff_key = [0u8; SECRET_KEY_MAX_LEN as usize];
|
||||||
|
match tokens.read(index, &mut buff_name, &mut buff_key) {
|
||||||
|
Ok((len_name, len_key)) => {
|
||||||
|
let name = core::str::from_utf8(&buff_name[..len_name]).unwrap();
|
||||||
|
let key = &buff_key[..len_key];
|
||||||
|
|
||||||
|
let token = generate_otp_token(key, timestamp);
|
||||||
|
|
||||||
|
display.set_cursor(0, 0);
|
||||||
|
display.write_str(name);
|
||||||
|
display.set_cursor(0, 1);
|
||||||
|
display.write_token(token);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
display.clear();
|
||||||
|
display.set_cursor(0, 0);
|
||||||
|
display.write_str("ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
changed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
use arduino_hal::delay_ms;
|
||||||
|
use arduino_hal::delay_us;
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
|
||||||
|
// The CMDs codes are documented here:
|
||||||
|
// https://www.openhacks.com/uploadsproductos/eone-1602a1.pdf
|
||||||
|
const INIT_CMD: u8 = 0x03; // 0b00000011
|
||||||
|
const MODE_4_BITS_CMD: u8 = 0x02; // 0b00000010
|
||||||
|
const DISPLAY_MODE_16X2_CMD: u8 = 0x28; // 0b00101000
|
||||||
|
const ENABLE_SCREEN_CMD: u8 = 0x0C; // 0b00001100
|
||||||
|
const CLEAN_SCREEN_CMD: u8 = 0x01; // 0b00000001
|
||||||
|
const SHIFT_LEFT_CMD: u8 = 0x10; // 0b10000
|
||||||
|
const SHIFT_RIGHT_CMD: u8 = 0x14; // 0b10100
|
||||||
|
const MOVE_CURSOR_CMD: u8 = 0x80; // 0b10000000
|
||||||
|
|
||||||
|
const ROW_OFFSETS: [u8; 2] = [0x00, 0x40];
|
||||||
|
|
||||||
|
pub enum Direction {
|
||||||
|
Left = SHIFT_LEFT_CMD as isize,
|
||||||
|
Right = SHIFT_RIGHT_CMD as isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StnScreen<'a, RS, EN, D4, D5, D6, D7> {
|
||||||
|
rs: &'a mut RS,
|
||||||
|
en: &'a mut EN,
|
||||||
|
d4: &'a mut D4,
|
||||||
|
d5: &'a mut D5,
|
||||||
|
d6: &'a mut D6,
|
||||||
|
d7: &'a mut D7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
'a,
|
||||||
|
RS: OutputPin,
|
||||||
|
EN: OutputPin,
|
||||||
|
D4: OutputPin,
|
||||||
|
D5: OutputPin,
|
||||||
|
D6: OutputPin,
|
||||||
|
D7: OutputPin,
|
||||||
|
> StnScreen<'a, RS, EN, D4, D5, D6, D7>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
rs: &'a mut RS,
|
||||||
|
en: &'a mut EN,
|
||||||
|
d4: &'a mut D4,
|
||||||
|
d5: &'a mut D5,
|
||||||
|
d6: &'a mut D6,
|
||||||
|
d7: &'a mut D7,
|
||||||
|
) -> Self {
|
||||||
|
let mut display = StnScreen {
|
||||||
|
rs,
|
||||||
|
en,
|
||||||
|
d4,
|
||||||
|
d5,
|
||||||
|
d6,
|
||||||
|
d7,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initializing of LCM on page 16
|
||||||
|
// https://z3d9b7u8.stackpathcdn.com/pdf-down/L/C/D/LCD-1602A_CA.pdf
|
||||||
|
delay_ms(16);
|
||||||
|
display.write_nibble(INIT_CMD, false);
|
||||||
|
delay_ms(5);
|
||||||
|
display.write_nibble(INIT_CMD, false);
|
||||||
|
delay_us(100);
|
||||||
|
display.write_nibble(INIT_CMD, false);
|
||||||
|
delay_us(100);
|
||||||
|
display.write_nibble(MODE_4_BITS_CMD, false);
|
||||||
|
delay_us(100);
|
||||||
|
display.send_cmd(DISPLAY_MODE_16X2_CMD, false);
|
||||||
|
display.send_cmd(ENABLE_SCREEN_CMD, false);
|
||||||
|
display.clear();
|
||||||
|
|
||||||
|
display
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pulse_enable(&mut self) {
|
||||||
|
self.en.set_low().ok();
|
||||||
|
delay_us(1);
|
||||||
|
self.en.set_high().ok();
|
||||||
|
delay_us(1);
|
||||||
|
self.en.set_low().ok();
|
||||||
|
delay_us(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_cmd(&mut self, cmd: u8, is_data: bool) {
|
||||||
|
self.write_nibble((cmd >> 4) & 0x0F, is_data);
|
||||||
|
self.write_nibble(cmd & 0x0F, is_data);
|
||||||
|
delay_ms(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_nibble(&mut self, nibble: u8, is_data: bool) {
|
||||||
|
self.rs.set_state(is_data.into()).ok();
|
||||||
|
self.d4.set_state(last_bit(nibble >> 0).into()).ok();
|
||||||
|
self.d5.set_state(last_bit(nibble >> 1).into()).ok();
|
||||||
|
self.d6.set_state(last_bit(nibble >> 2).into()).ok();
|
||||||
|
self.d7.set_state(last_bit(nibble >> 3).into()).ok();
|
||||||
|
self.pulse_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.send_cmd(CLEAN_SCREEN_CMD, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shift(&mut self, dir: Direction) {
|
||||||
|
let cmd = dir as u8;
|
||||||
|
self.send_cmd(cmd, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor(&mut self, col: u8, row: u8) {
|
||||||
|
let row_offset = ROW_OFFSETS[row as usize];
|
||||||
|
let address_position = row_offset | col;
|
||||||
|
self.send_cmd(MOVE_CURSOR_CMD | address_position, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_char(&mut self, c: char) {
|
||||||
|
let c = c as u8;
|
||||||
|
self.write_nibble((c >> 4) & 0x0F, true);
|
||||||
|
self.write_nibble(c & 0x0F, true);
|
||||||
|
delay_us(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_str<S: AsRef<str>>(&mut self, s: S) {
|
||||||
|
let s = s.as_ref();
|
||||||
|
for c in s.chars() {
|
||||||
|
self.write_char(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_token(&mut self, mut token: u32) {
|
||||||
|
let mut buff = [0u8; 7];
|
||||||
|
for (n, chr) in buff.iter_mut().enumerate() {
|
||||||
|
*chr = if n == 3 {
|
||||||
|
'-' as u8
|
||||||
|
} else {
|
||||||
|
let ch = (token % 10) as u8 + '0' as u8;
|
||||||
|
token /= 10;
|
||||||
|
ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buff.reverse();
|
||||||
|
let s = core::str::from_utf8(&buff).unwrap();
|
||||||
|
self.write_str(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u8(&mut self, mut num: u8) {
|
||||||
|
if num == 0 {
|
||||||
|
self.write_char('0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buff = [0u8; 3];
|
||||||
|
let num_len = num.ilog10() as usize;
|
||||||
|
for i in (0..=num_len).rev() {
|
||||||
|
buff[i] = num % 10 + '0' as u8;
|
||||||
|
num /= 10;
|
||||||
|
}
|
||||||
|
self.write_str(core::str::from_utf8(&buff[..=num_len]).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u32(&mut self, mut num: u32) {
|
||||||
|
if num == 0 {
|
||||||
|
self.write_char('0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buff = [0u8; 10];
|
||||||
|
let num_len = num.ilog10() as usize;
|
||||||
|
for i in (0..=num_len).rev() {
|
||||||
|
buff[i] = (num % 10) as u8 + '0' as u8;
|
||||||
|
num /= 10;
|
||||||
|
}
|
||||||
|
self.write_str(core::str::from_utf8(&buff[..=num_len]).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_bit(n: u8) -> bool {
|
||||||
|
(n & 1) != 0
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
use arduino_hal::eeprom::Eeprom;
|
||||||
|
|
||||||
|
pub const SECRET_KEY_MAX_LEN: u16 = 32;
|
||||||
|
pub const SECRET_KEY_NAME_LEN: u16 = 16;
|
||||||
|
pub const SECRET_KEY_FULL_LEN: u16 = SECRET_KEY_MAX_LEN + SECRET_KEY_NAME_LEN;
|
||||||
|
pub const ENDL: u8 = 0;
|
||||||
|
|
||||||
|
pub struct Tokens<'a> {
|
||||||
|
mem: &'a mut Eeprom,
|
||||||
|
pub current: Option<u16>,
|
||||||
|
capacity: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Tokens<'a> {
|
||||||
|
pub fn new(mem: &'a mut Eeprom) -> Self {
|
||||||
|
let capacity = mem.capacity() / SECRET_KEY_FULL_LEN;
|
||||||
|
|
||||||
|
let mut tokens = Tokens {
|
||||||
|
mem,
|
||||||
|
capacity,
|
||||||
|
current: None,
|
||||||
|
};
|
||||||
|
tokens.current = tokens.first();
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_free(&self) -> Option<u16> {
|
||||||
|
for n in 0..self.capacity {
|
||||||
|
let index = SECRET_KEY_FULL_LEN * n;
|
||||||
|
if self.mem.read_byte(index) == ENDL {
|
||||||
|
return Some(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first(&self) -> Option<u16> {
|
||||||
|
for n in 0..self.capacity {
|
||||||
|
let index = SECRET_KEY_FULL_LEN * n;
|
||||||
|
if self.mem.read_byte(index) != ENDL {
|
||||||
|
return Some(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Option<u16> {
|
||||||
|
let mut index = self.current.unwrap();
|
||||||
|
for _ in 0..self.capacity {
|
||||||
|
index = (index + 1) % self.capacity;
|
||||||
|
if self.mem.read_byte(index * SECRET_KEY_FULL_LEN) != ENDL {
|
||||||
|
self.current = Some(index);
|
||||||
|
return Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self) -> Option<u16> {
|
||||||
|
let mut index = self.current.unwrap();
|
||||||
|
for _ in 0..self.capacity {
|
||||||
|
index = (index - 1) % self.capacity;
|
||||||
|
if self.mem.read_byte(index * SECRET_KEY_FULL_LEN) != ENDL {
|
||||||
|
self.current = Some(index);
|
||||||
|
return Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, index: u16, name: &mut [u8], key: &mut [u8]) -> Result<(usize, usize), ()> {
|
||||||
|
name.fill(0);
|
||||||
|
key.fill(0);
|
||||||
|
let index_name = index * SECRET_KEY_FULL_LEN;
|
||||||
|
let index_key = (index * SECRET_KEY_FULL_LEN) + SECRET_KEY_NAME_LEN;
|
||||||
|
|
||||||
|
self.mem.read(index_name, name).map_err(|_| ())?;
|
||||||
|
self.mem.read(index_key, key).map_err(|_| ())?;
|
||||||
|
|
||||||
|
let len_name = name
|
||||||
|
.iter()
|
||||||
|
.position(|&n| n == 0)
|
||||||
|
.unwrap_or(SECRET_KEY_NAME_LEN as usize);
|
||||||
|
let len_key = key
|
||||||
|
.iter()
|
||||||
|
.position(|&n| n == 0)
|
||||||
|
.unwrap_or(SECRET_KEY_MAX_LEN as usize);
|
||||||
|
Ok((len_name, len_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, index: u16, name: &[u8], key: &[u8]) -> Result<u16, ()> {
|
||||||
|
let index_name = index * SECRET_KEY_FULL_LEN;
|
||||||
|
let index_key = (index * SECRET_KEY_FULL_LEN) + SECRET_KEY_NAME_LEN;
|
||||||
|
|
||||||
|
self.mem.write(index_name, name).map_err(|_| ())?;
|
||||||
|
self.mem.write(index_key, key).map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&mut self, index: u16) -> Option<u16> {
|
||||||
|
// The Arduino's EEPROM memory has a maximum number of write cycles.
|
||||||
|
// To keep writes to a minimum, only the first byte of the token name is set to 0
|
||||||
|
// and wipe all the secret key
|
||||||
|
let index_token = index * SECRET_KEY_FULL_LEN;
|
||||||
|
let index_key_start = index_token + SECRET_KEY_NAME_LEN;
|
||||||
|
let index_key_end = index_key_start + SECRET_KEY_MAX_LEN - 1;
|
||||||
|
self.mem.write_byte(index_token, ENDL);
|
||||||
|
for index in index_key_start..index_key_end {
|
||||||
|
self.mem.write_byte(index, ENDL);
|
||||||
|
}
|
||||||
|
Some(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn soft_wipe_all_tokens(&mut self) -> u8 {
|
||||||
|
let mut inc = 0;
|
||||||
|
|
||||||
|
for n in 0..self.capacity {
|
||||||
|
let index = SECRET_KEY_FULL_LEN * n;
|
||||||
|
if self.mem.read_byte(index) != ENDL {
|
||||||
|
self.delete(index);
|
||||||
|
inc += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hard_wipe_all_tokens(&mut self) {
|
||||||
|
for index in 0..self.mem.capacity() {
|
||||||
|
self.mem.write_byte(index, ENDL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from time import sleep
|
||||||
|
from typing import Any
|
||||||
|
import base64
|
||||||
|
import serial
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ENDL = bytes([0])
|
||||||
|
OK = bytes([1])
|
||||||
|
ERROR = bytes([2])
|
||||||
|
HANDSHAKE = bytes([3])
|
||||||
|
SET_TIMESTAMP = bytes([10])
|
||||||
|
ADD_TOKEN = bytes([20])
|
||||||
|
DELETE_TOKEN = bytes([30])
|
||||||
|
GET_TOKENS = bytes([40])
|
||||||
|
SOFT_WIPE_TOKENS = bytes([50])
|
||||||
|
HARD_WIPE_TOKENS = bytes([60])
|
||||||
|
EXIT = bytes([255])
|
||||||
|
|
||||||
|
def loop_input(msg: str, valid_values: Any) -> str:
|
||||||
|
valid_values = list(map(str, valid_values))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = input(msg)
|
||||||
|
|
||||||
|
if data not in valid_values:
|
||||||
|
print(f"'{data}' isn't a valid value, please enter a value in {valid_values}")
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return data
|
||||||
|
|
||||||
|
def b(n: int) -> bytes:
|
||||||
|
return bytes([n])
|
||||||
|
|
||||||
|
def process_secret(secret: str) -> bytes:
|
||||||
|
offset = 8 - (len(secret) % 8)
|
||||||
|
if offset != 8:
|
||||||
|
secret += "=" * offset
|
||||||
|
return base64.b32decode(secret, casefold=True)
|
||||||
|
|
||||||
|
def get_datetime_items() -> dict[str, bytes]:
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"year": b(now.year - 2000),
|
||||||
|
"month": b(now.month),
|
||||||
|
"day": b(now.day),
|
||||||
|
"hours": b(now.hour),
|
||||||
|
"minutes": b(now.minute),
|
||||||
|
"seconds": b(now.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
def main(argv: list[str]):
|
||||||
|
port = argv[-1]
|
||||||
|
|
||||||
|
conn = serial.Serial(port=port, baudrate=9600)
|
||||||
|
print("UP + Reset to enable the USB connection")
|
||||||
|
input("Press Enter if the device in the USB mode...")
|
||||||
|
|
||||||
|
conn.write(HANDSHAKE)
|
||||||
|
sleep(0.1)
|
||||||
|
res = conn.read()
|
||||||
|
if res != OK:
|
||||||
|
print("A handshake could not be performed")
|
||||||
|
print("Check the connection with your Arduino")
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
print("Handshake successfully performed")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("What do you want to do?")
|
||||||
|
print("1) Update timestamp")
|
||||||
|
print("2) Add a new token")
|
||||||
|
print("3) Remove a token")
|
||||||
|
print("4) SOFT wipe all tokens")
|
||||||
|
print("5) HARD WIPE ALL THE TOKENS")
|
||||||
|
print("6) EXIT")
|
||||||
|
|
||||||
|
opt = loop_input(">>> ", range(1, 7))
|
||||||
|
|
||||||
|
# Update Timestamp
|
||||||
|
if opt == "1":
|
||||||
|
conn.write(SET_TIMESTAMP)
|
||||||
|
sleep(0.1)
|
||||||
|
resp = conn.read()
|
||||||
|
sleep(0.1)
|
||||||
|
if resp == ERROR:
|
||||||
|
print(f"Error in the communication: Error {resp}")
|
||||||
|
continue
|
||||||
|
date_items = get_datetime_items();
|
||||||
|
conn.write(date_items["year"])
|
||||||
|
conn.write(date_items["month"])
|
||||||
|
conn.write(date_items["day"])
|
||||||
|
conn.write(date_items["hours"])
|
||||||
|
conn.write(date_items["minutes"])
|
||||||
|
conn.write(date_items["seconds"])
|
||||||
|
resp = conn.read()
|
||||||
|
if resp != OK:
|
||||||
|
print(f"Error in the communication: Error {resp}")
|
||||||
|
else:
|
||||||
|
print("Timestamp updated successfully!")
|
||||||
|
# Add token
|
||||||
|
elif opt == "2":
|
||||||
|
conn.write(ADD_TOKEN)
|
||||||
|
sleep(0.1)
|
||||||
|
resp = conn.read()
|
||||||
|
if resp == ERROR:
|
||||||
|
print("The memory of the device is full")
|
||||||
|
continue
|
||||||
|
name = input("Enter the name of the new token (16 chars max):\n>>> ")
|
||||||
|
name = name.strip()[:16].encode("ascii")
|
||||||
|
|
||||||
|
key = input("Enter the OTP secret key (32 chars max):\n>>> ")
|
||||||
|
key = process_secret(key.strip()[:32])
|
||||||
|
|
||||||
|
for ch in name:
|
||||||
|
conn.write(b(ch))
|
||||||
|
conn.write(ENDL)
|
||||||
|
for ch in key:
|
||||||
|
conn.write(b(ch))
|
||||||
|
conn.write(ENDL)
|
||||||
|
|
||||||
|
resp = conn.read()
|
||||||
|
if resp == ERROR:
|
||||||
|
print("Error trying to add the token, try again")
|
||||||
|
else:
|
||||||
|
print("Token added successfully!")
|
||||||
|
# Soft wipe tokens
|
||||||
|
elif opt == "4":
|
||||||
|
conn.write(SOFT_WIPE_TOKENS)
|
||||||
|
sleep(0.1)
|
||||||
|
_ = conn.read()
|
||||||
|
deleted_tokens = conn.read()
|
||||||
|
resp = conn.read()
|
||||||
|
if resp == OK:
|
||||||
|
print(f"{deleted_tokens} tokens wipped successfully!")
|
||||||
|
else:
|
||||||
|
print("Error!!")
|
||||||
|
# Hard wipe tokens
|
||||||
|
elif opt == "5":
|
||||||
|
resp = loop_input("This will erase all the EEPROM, do you want to continue? [Y/N]", ["y", "Y", "n", "N"]).upper()
|
||||||
|
if resp == "Y":
|
||||||
|
conn.write(HARD_WIPE_TOKENS)
|
||||||
|
sleep(0.1)
|
||||||
|
_ = conn.read()
|
||||||
|
resp = conn.read()
|
||||||
|
if resp == OK:
|
||||||
|
print("All the Eeprom erased successfully!")
|
||||||
|
else:
|
||||||
|
print("Error!!")
|
||||||
|
elif opt == "6":
|
||||||
|
conn.write(EXIT)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
attrs = sys.argv
|
||||||
|
if len(attrs) == 1:
|
||||||
|
print("You need to specify the Arduino's serial port")
|
||||||
|
print("Example:")
|
||||||
|
print(f"python {attrs[0]} /dev/ttyUSB0")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sys.exit(main(attrs[1:]))
|
|
@ -1 +1,2 @@
|
||||||
avrdude -p atmega328p -c arduino -P /dev/ttyACM0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
|
# avrdude -p atmega328p -c arduino -P /dev/ttyACM0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
|
||||||
|
avrdude -p atmega328p -c arduino -P /dev/ttyUSB0 -U flash:w:target/avr-atmega328p/release/rustytoken.elf:e
|
||||||
|
|
Loading…
Reference in New Issue