Bladeren bron

Implement XAP 'secure' core requirements (#16843)

Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Stefan Kerkmann <karlk90@pm.me>
Joel Challis 3 jaren geleden
bovenliggende
commit
92a61aa0cd

+ 1 - 0
builddefs/generic_features.mk

@@ -32,6 +32,7 @@ GENERIC_FEATURES = \
     KEY_OVERRIDE \
     LEADER \
     PROGRAMMABLE_BUTTON \
+    SECURE \
     SPACE_CADET \
     SWAP_HANDS \
     TAP_DANCE \

+ 2 - 1
builddefs/show_options.mk

@@ -80,7 +80,8 @@ OTHER_OPTION_NAMES = \
   LED_MIRRORED \
   RGBLIGHT_FULL_POWER \
   LTO_ENABLE \
-  PROGRAMMABLE_BUTTON_ENABLE
+  PROGRAMMABLE_BUTTON_ENABLE \
+  SECURE_ENABLE
 
 define NAME_ECHO
        @printf "  %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"

+ 3 - 0
data/mappings/info_config.json

@@ -78,6 +78,9 @@
     "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
     "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
     "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
+    "SECURE_UNLOCK_SEQUENCE": {"info_key": "secure.unlock_sequence", "value_type": "array.array.int", "to_json": false},
+    "SECURE_UNLOCK_TIMEOUT": {"info_key": "secure.unlock_timeout", "value_type": "int"},
+    "SECURE_IDLE_TIMEOUT": {"info_key": "secure.idle_timeout", "value_type": "int"},
     "SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
     "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
     "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},

+ 1 - 0
data/mappings/info_rules.json

@@ -20,6 +20,7 @@
     "MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
     "NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
     "PIN_COMPATIBLE": {"info_key": "pin_compatible"},
+    "SECURE_ENABLE": {"info_key": "secure.enabled", "value_type": "bool"},
     "SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
     "SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false},
     "WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"}

+ 24 - 0
data/schemas/keyboard.jsonschema

@@ -244,6 +244,30 @@
                 }
             }
         },
+        "secure": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "enabled": {"type": "boolean"},
+                "unlock_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "unlock_sequence": {
+                    "type": "array",
+                    "minLength": 1,
+                    "maxLength": 5,
+                    "items": {
+                        "type": "array",
+                        "minItems": 2,
+                        "maxItems": 2,
+                        "items": {
+                            "type": "number",
+                            "min": 0,
+                            "multipleOf": 1
+                        }
+                    }
+                }
+            }
+        },
         "split": {
             "type": "object",
             "additionalProperties": false,

+ 6 - 1
lib/python/qmk/cli/generate/config_h.py

@@ -94,7 +94,12 @@ def generate_config_items(kb_info_json, config_h_lines):
         except KeyError:
             continue
 
-        if key_type.startswith('array'):
+        if key_type.startswith('array.array'):
+            config_h_lines.append('')
+            config_h_lines.append(f'#ifndef {config_key}')
+            config_h_lines.append(f'#   define {config_key} {{ {", ".join(["{" + ",".join(list(map(str, x))) + "}" for x in config_value])} }}')
+            config_h_lines.append(f'#endif // {config_key}')
+        elif key_type.startswith('array'):
             config_h_lines.append('')
             config_h_lines.append(f'#ifndef {config_key}')
             config_h_lines.append(f'#   define {config_key} {{ {", ".join(map(str, config_value))} }}')

+ 45 - 11
lib/python/qmk/info.py

@@ -168,28 +168,46 @@ def _extract_pins(pins):
     return [_pin_name(pin) for pin in pins.split(',')]
 
 
-def _extract_direct_matrix(direct_pins):
-    """
+def _extract_2d_array(raw):
+    """Return a 2d array of strings
     """
-    direct_pin_array = []
+    out_array = []
 
-    while direct_pins[-1] != '}':
-        direct_pins = direct_pins[:-1]
+    while raw[-1] != '}':
+        raw = raw[:-1]
 
-    for row in direct_pins.split('},{'):
+    for row in raw.split('},{'):
         if row.startswith('{'):
             row = row[1:]
 
         if row.endswith('}'):
             row = row[:-1]
 
-        direct_pin_array.append([])
+        out_array.append([])
+
+        for val in row.split(','):
+            out_array[-1].append(val)
+
+    return out_array
+
+
+def _extract_2d_int_array(raw):
+    """Return a 2d array of ints
+    """
+    ret = _extract_2d_array(raw)
+
+    return [list(map(int, x)) for x in ret]
 
-        for pin in row.split(','):
-            if pin == 'NO_PIN':
-                pin = None
 
-            direct_pin_array[-1].append(pin)
+def _extract_direct_matrix(direct_pins):
+    """extract direct_matrix
+    """
+    direct_pin_array = _extract_2d_array(direct_pins)
+
+    for i in range(len(direct_pin_array)):
+        for j in range(len(direct_pin_array[i])):
+            if direct_pin_array[i][j] == 'NO_PIN':
+                direct_pin_array[i][j] = None
 
     return direct_pin_array
 
@@ -207,6 +225,21 @@ def _extract_audio(info_data, config_c):
         info_data['audio'] = {'pins': audio_pins}
 
 
+def _extract_secure_unlock(info_data, config_c):
+    """Populate data about the secure unlock sequence
+    """
+    unlock = config_c.get('SECURE_UNLOCK_SEQUENCE', '').replace(' ', '')[1:-1]
+    if unlock:
+        unlock_array = _extract_2d_int_array(unlock)
+        if 'secure' not in info_data:
+            info_data['secure'] = {}
+
+        if 'unlock_sequence' in info_data['secure']:
+            _log_warning(info_data, 'Secure unlock sequence is specified in both config.h (SECURE_UNLOCK_SEQUENCE) and info.json (secure.unlock_sequence) (Value: %s), the config.h value wins.' % info_data['secure']['unlock_sequence'])
+
+        info_data['secure']['unlock_sequence'] = unlock_array
+
+
 def _extract_split_main(info_data, config_c):
     """Populate data about the split configuration
     """
@@ -466,6 +499,7 @@ def _extract_config_h(info_data, config_c):
     # Pull data that easily can't be mapped in json
     _extract_matrix_info(info_data, config_c)
     _extract_audio(info_data, config_c)
+    _extract_secure_unlock(info_data, config_c)
     _extract_split_main(info_data, config_c)
     _extract_split_transport(info_data, config_c)
     _extract_split_right_pins(info_data, config_c)

+ 4 - 0
quantum/keyboard.c

@@ -562,6 +562,10 @@ void quantum_task(void) {
 #ifdef AUTO_SHIFT_ENABLE
     autoshift_matrix_scan();
 #endif
+
+#ifdef SECURE_ENABLE
+    secure_task();
+#endif
 }
 
 /** \brief Keyboard task: Do keyboard routine jobs

+ 39 - 0
quantum/process_keycode/process_secure.c

@@ -0,0 +1,39 @@
+// Copyright 2022 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "secure.h"
+#include "process_secure.h"
+#include "quantum_keycodes.h"
+
+bool preprocess_secure(uint16_t keycode, keyrecord_t *record) {
+    if (secure_is_unlocking()) {
+        if (!record->event.pressed) {
+            secure_keypress_event(record->event.key.row, record->event.key.col);
+        }
+
+        // Normal keypresses should be disabled until the sequence is completed
+        return false;
+    }
+
+    return true;
+}
+
+bool process_secure(uint16_t keycode, keyrecord_t *record) {
+#ifndef SECURE_DISABLE_KEYCODES
+    if (!record->event.pressed) {
+        if (keycode == SECURE_LOCK) {
+            secure_lock();
+            return false;
+        }
+        if (keycode == SECURE_UNLOCK) {
+            secure_unlock();
+            return false;
+        }
+        if (keycode == SECURE_TOGGLE) {
+            secure_is_locked() ? secure_unlock() : secure_lock();
+            return false;
+        }
+    }
+#endif
+    return true;
+}

+ 15 - 0
quantum/process_keycode/process_secure.h

@@ -0,0 +1,15 @@
+// Copyright 2022 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdbool.h>
+#include "action.h"
+
+/** \brief Intercept keycodes and detect unlock sequences
+ */
+bool preprocess_secure(uint16_t keycode, keyrecord_t *record);
+
+/** \brief Handle any secure specific keycodes
+ */
+bool process_secure(uint16_t keycode, keyrecord_t *record);

+ 9 - 0
quantum/quantum.c

@@ -212,6 +212,12 @@ bool process_record_quantum(keyrecord_t *record) {
     //   return false;
     // }
 
+#if defined(SECURE_ENABLE)
+    if (!preprocess_secure(keycode, record)) {
+        return false;
+    }
+#endif
+
 #ifdef VELOCIKEY_ENABLE
     if (velocikey_enabled() && record->event.pressed) {
         velocikey_accelerate();
@@ -247,6 +253,9 @@ bool process_record_quantum(keyrecord_t *record) {
             process_record_via(keycode, record) &&
 #endif
             process_record_kb(keycode, record) &&
+#if defined(SECURE_ENABLE)
+            process_secure(keycode, record) &&
+#endif
 #if defined(SEQUENCER_ENABLE)
             process_sequencer(keycode, record) &&
 #endif

+ 5 - 0
quantum/quantum.h

@@ -200,6 +200,11 @@ extern layer_state_t layer_state;
 #    include "process_dynamic_macro.h"
 #endif
 
+#ifdef SECURE_ENABLE
+#    include "secure.h"
+#    include "process_secure.h"
+#endif
+
 #ifdef DYNAMIC_KEYMAP_ENABLE
 #    include "dynamic_keymap.h"
 #endif

+ 4 - 0
quantum/quantum_keycodes.h

@@ -597,6 +597,10 @@ enum quantum_keycodes {
 
     QK_MAKE,
 
+    SECURE_LOCK,
+    SECURE_UNLOCK,
+    SECURE_TOGGLE,
+
     // Start of custom keycode range for keyboards and keymaps - always leave at the end
     SAFE_RANGE
 };

+ 87 - 0
quantum/secure.c

@@ -0,0 +1,87 @@
+// Copyright 2022 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "secure.h"
+#include "timer.h"
+
+#ifndef SECURE_UNLOCK_TIMEOUT
+#    define SECURE_UNLOCK_TIMEOUT 5000
+#endif
+
+#ifndef SECURE_IDLE_TIMEOUT
+#    define SECURE_IDLE_TIMEOUT 60000
+#endif
+
+#ifndef SECURE_UNLOCK_SEQUENCE
+#    define SECURE_UNLOCK_SEQUENCE \
+        {                          \
+            { 0, 0 }               \
+        }
+#endif
+
+static secure_status_t secure_status = SECURE_LOCKED;
+static uint32_t        unlock_time   = 0;
+static uint32_t        idle_time     = 0;
+
+secure_status_t secure_get_status(void) {
+    return secure_status;
+}
+
+void secure_lock(void) {
+    secure_status = SECURE_LOCKED;
+}
+
+void secure_unlock(void) {
+    secure_status = SECURE_UNLOCKED;
+    idle_time     = timer_read32();
+}
+
+void secure_request_unlock(void) {
+    if (secure_status == SECURE_LOCKED) {
+        secure_status = SECURE_PENDING;
+        unlock_time   = timer_read32();
+    }
+}
+
+void secure_activity_event(void) {
+    if (secure_status == SECURE_UNLOCKED) {
+        idle_time = timer_read32();
+    }
+}
+
+void secure_keypress_event(uint8_t row, uint8_t col) {
+    static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE;
+    static const uint8_t sequence_len  = sizeof(sequence) / sizeof(sequence[0]);
+
+    static uint8_t offset = 0;
+    if ((sequence[offset][0] == row) && (sequence[offset][1] == col)) {
+        offset++;
+        if (offset == sequence_len) {
+            offset = 0;
+            secure_unlock();
+        }
+    } else {
+        offset = 0;
+        secure_lock();
+    }
+}
+
+void secure_task(void) {
+#if SECURE_UNLOCK_TIMEOUT != 0
+    // handle unlock timeout
+    if (secure_status == SECURE_PENDING) {
+        if (timer_elapsed32(unlock_time) >= SECURE_UNLOCK_TIMEOUT) {
+            secure_lock();
+        }
+    }
+#endif
+
+#if SECURE_IDLE_TIMEOUT != 0
+    // handle idle timeout
+    if (secure_status == SECURE_UNLOCKED) {
+        if (timer_elapsed32(idle_time) >= SECURE_IDLE_TIMEOUT) {
+            secure_lock();
+        }
+    }
+#endif
+}

+ 67 - 0
quantum/secure.h

@@ -0,0 +1,67 @@
+// Copyright 2022 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+/** \file
+ *
+ * Exposes a set of functionality to act as a virtual padlock for your device
+ * ... As long as that padlock is made of paper and its currently raining.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/** \brief Available secure states
+ */
+typedef enum {
+    SECURE_LOCKED,
+    SECURE_PENDING,
+    SECURE_UNLOCKED,
+} secure_status_t;
+
+/** \brief Query current secure state
+ */
+secure_status_t secure_get_status(void);
+
+/** \brief Helper to check if unlocking is currently locked
+ */
+#define secure_is_locked() (secure_get_status() == SECURE_LOCKED)
+
+/** \brief Helper to check if unlocking is currently in progress
+ */
+#define secure_is_unlocking() (secure_get_status() == SECURE_PENDING)
+
+/** \brief Helper to check if unlocking is currently unlocked
+ */
+#define secure_is_unlocked() (secure_get_status() == SECURE_UNLOCKED)
+
+/** \brief Lock down the device
+ */
+void secure_lock(void);
+
+/** \brief Force unlock the device
+ *
+ * \warning bypasses user unlock sequence
+ */
+void secure_unlock(void);
+
+/** \brief Begin listening for an unlock sequence
+ */
+void secure_request_unlock(void);
+
+/** \brief Flag to the secure subsystem that user activity has happened
+ *
+ * Call when some user activity has happened and the device should remain unlocked
+ */
+void secure_activity_event(void);
+
+/** \brief Flag to the secure subsystem that user has triggered a keypress
+ *
+ * Call to trigger processing of the unlock sequence
+ */
+void secure_keypress_event(uint8_t row, uint8_t col);
+
+/** \brief Handle various secure subsystem background tasks
+ */
+void secure_task(void);