~ruther/qmk_firmware

92a61aa0cd9a28056e6979f01a72b8742702dbfe — Joel Challis 3 years ago ae4d518
Implement XAP 'secure' core requirements (#16843)

Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Stefan Kerkmann <karlk90@pm.me>
M builddefs/generic_features.mk => builddefs/generic_features.mk +1 -0
@@ 32,6 32,7 @@ GENERIC_FEATURES = \
    KEY_OVERRIDE \
    LEADER \
    PROGRAMMABLE_BUTTON \
    SECURE \
    SPACE_CADET \
    SWAP_HANDS \
    TAP_DANCE \

M builddefs/show_options.mk => builddefs/show_options.mk +2 -1
@@ 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)"

M data/mappings/info_config.json => data/mappings/info_config.json +3 -0
@@ 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"},

M data/mappings/info_rules.json => data/mappings/info_rules.json +1 -0
@@ 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"}

M data/schemas/keyboard.jsonschema => data/schemas/keyboard.jsonschema +24 -0
@@ 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,

M lib/python/qmk/cli/generate/config_h.py => lib/python/qmk/cli/generate/config_h.py +6 -1
@@ 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))} }}')

M lib/python/qmk/info.py => lib/python/qmk/info.py +45 -11
@@ 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)

M quantum/keyboard.c => quantum/keyboard.c +4 -0
@@ 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

A quantum/process_keycode/process_secure.c => quantum/process_keycode/process_secure.c +39 -0
@@ 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

A quantum/process_keycode/process_secure.h => quantum/process_keycode/process_secure.h +15 -0
@@ 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);

M quantum/quantum.c => quantum/quantum.c +9 -0
@@ 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

M quantum/quantum.h => quantum/quantum.h +5 -0
@@ 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

M quantum/quantum_keycodes.h => quantum/quantum_keycodes.h +4 -0
@@ 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
};

A quantum/secure.c => quantum/secure.c +87 -0
@@ 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
}

A quantum/secure.h => quantum/secure.h +67 -0
@@ 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);