summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builddefs/generic_features.mk1
-rw-r--r--builddefs/show_options.mk3
-rw-r--r--data/mappings/info_config.json3
-rw-r--r--data/mappings/info_rules.json1
-rw-r--r--data/schemas/keyboard.jsonschema24
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py7
-rw-r--r--lib/python/qmk/info.py56
-rw-r--r--quantum/keyboard.c4
-rw-r--r--quantum/process_keycode/process_secure.c39
-rw-r--r--quantum/process_keycode/process_secure.h15
-rw-r--r--quantum/quantum.c9
-rw-r--r--quantum/quantum.h5
-rw-r--r--quantum/quantum_keycodes.h4
-rw-r--r--quantum/secure.c87
-rw-r--r--quantum/secure.h67
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);