From b7ed9adb6c41089da75d258d7148fa87d2f76d20 Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Mon, 22 Nov 2021 21:20:57 -0500 Subject: Add initial TOTP watch face impl Vendor code from https://github.com/Netthaw/TOTP-MCU to do the heavy lifting of computing SHA-1 and HMAC and the rest of TOTP Also implement a date_time to unix timestamp method --- movement/watch_faces/complications/totp_face.c | 137 +++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 movement/watch_faces/complications/totp_face.c (limited to 'movement/watch_faces/complications/totp_face.c') diff --git a/movement/watch_faces/complications/totp_face.c b/movement/watch_faces/complications/totp_face.c new file mode 100644 index 00000000..2e8b5f66 --- /dev/null +++ b/movement/watch_faces/complications/totp_face.c @@ -0,0 +1,137 @@ +/** + * + * TODO: + * - this ONLY works if watch is set to UTC, probably worth including a TZ offset setting since it can be used by beats as well + * - show how long code is valid for in upper right corner of LCD + * - optimize code so that we don't calculating a new unix timestamp every second AND a new TOTP code + * - Support for multiple codes + */ +#include +#include +#include "totp_face.h" +#include "watch.h" +#include "TOTP.h" + +// test key: JBSWY3DPEHPK3PXP +// Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex +// Use https://totp.danhersam.com/ to generate test codes for verification +uint8_t hmacKey[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0xde, 0xad, 0xbe, 0xef}; // Secret key + +void totp_face_setup(movement_settings_t *settings, void ** context_ptr) { + (void) settings; + (void) context_ptr; + TOTP(hmacKey, sizeof(hmacKey), 30); +} + +void totp_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +/** + * @brief Get unix timestamp from component parts + * + * @param year + * @param month + * @param day + * @param hour + * @param minute + * @param second + * @return uint32_t + * + * Based on code by Josh Haberman for upb + * from https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html + * + * Essentially we need to calculate how many days have occured since year 0 + * including leap years! The following code does some clever calculations + * of the number of february's then offsets based on how many leap years + * there have been + * + * Once we have the number of days in the year, it's easy enough to add how + * many days have happened in the current year, then convert that to seconds + */ +uint32_t current_unix_time(uint32_t year, uint32_t month, uint32_t day, + uint32_t hour, uint32_t minute, uint32_t second) { + uint16_t DAYS_SO_FAR[] = { + 0, // Jan + 31, // Feb + 59, // March + 90, // April + 120, // May + 151, // June + 181, // July + 212, // August + 243, // September + 273, // October + 304, // November + 334 // December + }; + + + uint32_t year_adj = year + 4800; + uint32_t febs = year_adj - (month <= 2 ? 1 : 0); /* Februaries since base. */ + uint32_t leap_days = 1 + (febs / 4) - (febs / 100) + (febs / 400); + uint32_t days = 365 * year_adj + leap_days + DAYS_SO_FAR[month - 1] + day - 1; + days -= 2472692; /* Adjust to Unix epoch. */ + + uint32_t timestamp = days * 86400; + timestamp += hour * 3600; + timestamp += minute * 60; + timestamp += second; + + return timestamp; +} + +uint32_t current_unix_time_from_rtc() { + watch_date_time date_time = watch_rtc_get_date_time(); + return current_unix_time( + date_time.unit.year + 2020, // year is stored starting in 2020 + date_time.unit.month, + date_time.unit.day, + date_time.unit.hour, + date_time.unit.minute, + date_time.unit.second + ); +} + +bool totp_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + char buf[14]; + watch_date_time date_time = watch_rtc_get_date_time(); + + uint32_t ts; + uint32_t code; + switch (event.event_type) { + case EVENT_ACTIVATE: + case EVENT_TICK: + ts = current_unix_time_from_rtc(); + code = getCodeFromTimestamp(ts); + sprintf(buf, "2f %lu", code); + + watch_display_string(buf, 0); + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + movement_illuminate_led(); + break; + case EVENT_TIMEOUT: + // go home + break; + case EVENT_ALARM_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_UP: + case EVENT_ALARM_LONG_PRESS: + default: + break; + } + + return true; +} + +void totp_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} \ No newline at end of file -- cgit v1.2.3