diff options
| -rw-r--r-- | builddefs/generic_features.mk | 1 | ||||
| -rw-r--r-- | builddefs/show_options.mk | 3 | ||||
| -rw-r--r-- | data/mappings/info_config.json | 3 | ||||
| -rw-r--r-- | data/mappings/info_rules.json | 1 | ||||
| -rw-r--r-- | data/schemas/keyboard.jsonschema | 24 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/config_h.py | 7 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 56 | ||||
| -rw-r--r-- | quantum/keyboard.c | 4 | ||||
| -rw-r--r-- | quantum/process_keycode/process_secure.c | 39 | ||||
| -rw-r--r-- | quantum/process_keycode/process_secure.h | 15 | ||||
| -rw-r--r-- | quantum/quantum.c | 9 | ||||
| -rw-r--r-- | quantum/quantum.h | 5 | ||||
| -rw-r--r-- | quantum/quantum_keycodes.h | 4 | ||||
| -rw-r--r-- | quantum/secure.c | 87 | ||||
| -rw-r--r-- | quantum/secure.h | 67 | 
15 files changed, 312 insertions, 13 deletions
| diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 53d4e16fd4..0475a2ff09 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -32,6 +32,7 @@ GENERIC_FEATURES = \      KEY_OVERRIDE \      LEADER \      PROGRAMMABLE_BUTTON \ +    SECURE \      SPACE_CADET \      SWAP_HANDS \      TAP_DANCE \ diff --git a/builddefs/show_options.mk b/builddefs/show_options.mk index b30399a56c..16b69ef0ea 100644 --- a/builddefs/show_options.mk +++ b/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)" diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json index 2121741d19..02ad3226c2 100644 --- a/data/mappings/info_config.json +++ b/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"}, diff --git a/data/mappings/info_rules.json b/data/mappings/info_rules.json index 237e9f1024..4b0fde5629 100644 --- a/data/mappings/info_rules.json +++ b/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"} diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 68a02420a4..6b1e543e48 100644 --- a/data/schemas/keyboard.jsonschema +++ b/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, diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 20c9595ed7..893892c479 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/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))} }}') diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index c5a7d33848..49d1054519 100644 --- a/lib/python/qmk/info.py +++ b/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) diff --git a/quantum/keyboard.c b/quantum/keyboard.c index fc8a2fe8e3..63236f0b20 100644 --- a/quantum/keyboard.c +++ b/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 diff --git a/quantum/process_keycode/process_secure.c b/quantum/process_keycode/process_secure.c new file mode 100644 index 0000000000..827ace597a --- /dev/null +++ b/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; +}
\ No newline at end of file diff --git a/quantum/process_keycode/process_secure.h b/quantum/process_keycode/process_secure.h new file mode 100644 index 0000000000..2814264b92 --- /dev/null +++ b/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); diff --git a/quantum/quantum.c b/quantum/quantum.c index d4e91ddd37..673ea91b11 100644 --- a/quantum/quantum.c +++ b/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 diff --git a/quantum/quantum.h b/quantum/quantum.h index 9ce3c1f5d6..d021e7c05c 100644 --- a/quantum/quantum.h +++ b/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 diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index dacfe5bdcd..c7b4ea593f 100644 --- a/quantum/quantum_keycodes.h +++ b/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  }; diff --git a/quantum/secure.c b/quantum/secure.c new file mode 100644 index 0000000000..00048bd6dd --- /dev/null +++ b/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 +} diff --git a/quantum/secure.h b/quantum/secure.h new file mode 100644 index 0000000000..04507fd5b1 --- /dev/null +++ b/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); | 
