mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-30 21:02:32 +01:00 
			
		
		
		
	Enhancement and fixes of "Secure" feature (#16958)
This commit is contained in:
		
							parent
							
								
									baa8d07fdb
								
							
						
					
					
						commit
						db887e63d7
					
				| @ -7,7 +7,9 @@ | ||||
| 
 | ||||
| bool preprocess_secure(uint16_t keycode, keyrecord_t *record) { | ||||
|     if (secure_is_unlocking()) { | ||||
|         if (!record->event.pressed) { | ||||
|         // !pressed will trigger on any already held keys (such as layer keys),
 | ||||
|         // and cause the request secure check to prematurely fail.
 | ||||
|         if (record->event.pressed) { | ||||
|             secure_keypress_event(record->event.key.row, record->event.key.col); | ||||
|         } | ||||
| 
 | ||||
| @ -33,7 +35,11 @@ bool process_secure(uint16_t keycode, keyrecord_t *record) { | ||||
|             secure_is_locked() ? secure_unlock() : secure_lock(); | ||||
|             return false; | ||||
|         } | ||||
|         if (keycode == SECURE_REQUEST) { | ||||
|             secure_request_unlock(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
|     return true; | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -571,3 +571,16 @@ const char *get_u16_str(uint16_t curr_num, char curr_pad) { | ||||
|     last_pad = curr_pad; | ||||
|     return get_numeric_str(buf, sizeof(buf), curr_num, curr_pad); | ||||
| } | ||||
| 
 | ||||
| #if defined(SECURE_ENABLE) | ||||
| void secure_hook_quantum(secure_status_t secure_status) { | ||||
|     // If keys are being held when this is triggered, they may not be released properly
 | ||||
|     // this can result in stuck keys, mods and layers.  To prevent that, manually
 | ||||
|     // clear these, when it is triggered.
 | ||||
| 
 | ||||
|     if (secure_status == SECURE_PENDING) { | ||||
|         clear_keyboard(); | ||||
|         layer_clear(); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -601,6 +601,7 @@ enum quantum_keycodes { | ||||
|     SECURE_LOCK, | ||||
|     SECURE_UNLOCK, | ||||
|     SECURE_TOGGLE, | ||||
|     SECURE_REQUEST, | ||||
| 
 | ||||
|     CAPS_WORD, | ||||
| 
 | ||||
|  | ||||
| @ -23,17 +23,24 @@ static secure_status_t secure_status = SECURE_LOCKED; | ||||
| static uint32_t        unlock_time   = 0; | ||||
| static uint32_t        idle_time     = 0; | ||||
| 
 | ||||
| static void secure_hook(secure_status_t secure_status) { | ||||
|     secure_hook_quantum(secure_status); | ||||
|     secure_hook_kb(secure_status); | ||||
| } | ||||
| 
 | ||||
| secure_status_t secure_get_status(void) { | ||||
|     return secure_status; | ||||
| } | ||||
| 
 | ||||
| void secure_lock(void) { | ||||
|     secure_status = SECURE_LOCKED; | ||||
|     secure_hook(secure_status); | ||||
| } | ||||
| 
 | ||||
| void secure_unlock(void) { | ||||
|     secure_status = SECURE_UNLOCKED; | ||||
|     idle_time     = timer_read32(); | ||||
|     secure_hook(secure_status); | ||||
| } | ||||
| 
 | ||||
| void secure_request_unlock(void) { | ||||
| @ -41,6 +48,7 @@ void secure_request_unlock(void) { | ||||
|         secure_status = SECURE_PENDING; | ||||
|         unlock_time   = timer_read32(); | ||||
|     } | ||||
|     secure_hook(secure_status); | ||||
| } | ||||
| 
 | ||||
| void secure_activity_event(void) { | ||||
| @ -85,3 +93,10 @@ void secure_task(void) { | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| __attribute__((weak)) bool secure_hook_user(secure_status_t secure_status) { | ||||
|     return true; | ||||
| } | ||||
| __attribute__((weak)) bool secure_hook_kb(secure_status_t secure_status) { | ||||
|     return secure_hook_user(secure_status); | ||||
| } | ||||
|  | ||||
| @ -65,3 +65,15 @@ void secure_keypress_event(uint8_t row, uint8_t col); | ||||
| /** \brief Handle various secure subsystem background tasks
 | ||||
|  */ | ||||
| void secure_task(void); | ||||
| 
 | ||||
| /** \brief quantum hook called when changing secure status device
 | ||||
|  */ | ||||
| void secure_hook_quantum(secure_status_t secure_status); | ||||
| 
 | ||||
| /** \brief user hook called when changing secure status device
 | ||||
|  */ | ||||
| bool secure_hook_user(secure_status_t secure_status); | ||||
| 
 | ||||
| /** \brief keyboard hook called when changing secure status device
 | ||||
|  */ | ||||
| bool secure_hook_kb(secure_status_t secure_status); | ||||
|  | ||||
							
								
								
									
										32
									
								
								tests/secure/config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/secure/config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| /* Copyright 2021 Stefan Kerkmann
 | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "test_common.h" | ||||
| 
 | ||||
| // clang-format off
 | ||||
| #define SECURE_UNLOCK_SEQUENCE \ | ||||
|     {                          \ | ||||
|         {0, 1},                \ | ||||
|         {0, 2},                \ | ||||
|         {0, 3},                \ | ||||
|         {0, 4}                 \ | ||||
|     } | ||||
| // clang-format on
 | ||||
| 
 | ||||
| #define SECURE_UNLOCK_TIMEOUT 20 | ||||
| #define SECURE_IDLE_TIMEOUT 50 | ||||
							
								
								
									
										20
									
								
								tests/secure/test.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/secure/test.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| # Copyright 2021 Stefan Kerkmann
 | ||||
| #
 | ||||
| # This program is free software: you can redistribute it and/or modify
 | ||||
| # it under the terms of the GNU General Public License as published by
 | ||||
| # the Free Software Foundation, either version 2 of the License, or
 | ||||
| # (at your option) any later version.
 | ||||
| #
 | ||||
| # This program is distributed in the hope that it will be useful,
 | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| # GNU General Public License for more details.
 | ||||
| #
 | ||||
| # You should have received a copy of the GNU General Public License
 | ||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| # --------------------------------------------------------------------------------
 | ||||
| # Keep this file, even if it is empty, as a marker that this folder contains tests
 | ||||
| # --------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| SECURE_ENABLE = yes | ||||
							
								
								
									
										278
									
								
								tests/secure/test_secure.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								tests/secure/test_secure.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,278 @@ | ||||
| /* Copyright 2021 Stefan Kerkmann
 | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 2 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| #include "gtest/gtest.h" | ||||
| #include "keyboard_report_util.hpp" | ||||
| #include "test_common.hpp" | ||||
| 
 | ||||
| using testing::_; | ||||
| using testing::AnyNumber; | ||||
| using testing::InSequence; | ||||
| 
 | ||||
| class Secure : public TestFixture { | ||||
|    public: | ||||
|     void SetUp() override { | ||||
|         secure_lock(); | ||||
|     } | ||||
|     // Convenience function to tap `key`.
 | ||||
|     void TapKey(KeymapKey key) { | ||||
|         key.press(); | ||||
|         run_one_scan_loop(); | ||||
|         key.release(); | ||||
|         run_one_scan_loop(); | ||||
|     } | ||||
| 
 | ||||
|     // Taps in order each key in `keys`.
 | ||||
|     template <typename... Ts> | ||||
|     void TapKeys(Ts... keys) { | ||||
|         for (KeymapKey key : {keys...}) { | ||||
|             TapKey(key); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| TEST_F(Secure, test_lock) { | ||||
|     TestDriver driver; | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0); | ||||
| 
 | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
|     secure_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
|     secure_lock(); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_timeout) { | ||||
|     TestDriver driver; | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0); | ||||
| 
 | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
|     secure_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
|     idle_for(SECURE_IDLE_TIMEOUT+1); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request) { | ||||
|     TestDriver driver; | ||||
|     auto       key_mo = KeymapKey(0, 0, 0, MO(1)); | ||||
|     auto       key_a  = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b  = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c  = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d  = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_mo, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0); | ||||
| 
 | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     secure_request_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_b, key_c, key_d); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_fail) { | ||||
|     TestDriver driver; | ||||
|     auto       key_e = KeymapKey(0, 0, 0, KC_E); | ||||
|     auto       key_a = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_e, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber()); | ||||
|     { // Expect the following reports in this order.
 | ||||
|         InSequence s; | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A))); | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B))); | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C))); | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_D))); | ||||
|     } | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     secure_request_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_e, key_a, key_b, key_c, key_d); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_timeout) { | ||||
|     TestDriver driver; | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0); | ||||
| 
 | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
|     secure_request_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     idle_for(SECURE_UNLOCK_TIMEOUT+1); | ||||
|     EXPECT_FALSE(secure_is_unlocking()); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_fail_mid) { | ||||
|     TestDriver driver; | ||||
|     auto       key_e = KeymapKey(0, 0, 0, KC_E); | ||||
|     auto       key_a = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_e, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber()); | ||||
|     { // Expect the following reports in this order.
 | ||||
|         InSequence s; | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C))); | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_D))); | ||||
|     } | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
|     secure_request_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_b, key_e, key_c, key_d); | ||||
|     EXPECT_FALSE(secure_is_unlocking()); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_fail_out_of_order) { | ||||
|     TestDriver driver; | ||||
|     auto       key_e = KeymapKey(0, 0, 0, KC_E); | ||||
|     auto       key_a = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_e, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber()); | ||||
|     { // Expect the following reports in this order.
 | ||||
|         InSequence s; | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B))); | ||||
|         EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C))); | ||||
|     } | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
|     secure_request_unlock(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_d, key_b, key_c); | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     EXPECT_FALSE(secure_is_unlocking()); | ||||
|     EXPECT_FALSE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_on_layer) { | ||||
|     TestDriver driver; | ||||
|     auto       key_mo = KeymapKey(0, 0, 0, MO(1)); | ||||
|     auto       key_a  = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b  = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c  = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d  = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_mo, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0); | ||||
| 
 | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     key_mo.press(); | ||||
|     run_one_scan_loop(); | ||||
|     secure_request_unlock(); | ||||
|     key_mo.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_b, key_c, key_d); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_mid_stroke) { | ||||
|     TestDriver driver; | ||||
|     auto       key_e = KeymapKey(0, 0, 0, KC_E); | ||||
|     auto       key_a = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_e, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_E))); | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())); | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     key_e.press(); | ||||
|     run_one_scan_loop(); | ||||
|     secure_request_unlock(); | ||||
|     key_e.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_b, key_c, key_d); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(Secure, test_unlock_request_mods) { | ||||
|     TestDriver driver; | ||||
|     auto       key_lsft = KeymapKey(0, 0, 0, KC_LSFT); | ||||
|     auto       key_a    = KeymapKey(0, 1, 0, KC_A); | ||||
|     auto       key_b    = KeymapKey(0, 2, 0, KC_B); | ||||
|     auto       key_c    = KeymapKey(0, 3, 0, KC_C); | ||||
|     auto       key_d    = KeymapKey(0, 4, 0, KC_D); | ||||
| 
 | ||||
|     set_keymap({key_lsft, key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     // Allow any number of empty reports.
 | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(key_lsft.report_code))); | ||||
|     EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())); | ||||
|     EXPECT_TRUE(secure_is_locked()); | ||||
|     key_lsft.press(); | ||||
|     run_one_scan_loop(); | ||||
|     secure_request_unlock(); | ||||
|     key_lsft.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(secure_is_unlocking()); | ||||
|     TapKeys(key_a, key_b, key_c, key_d); | ||||
|     EXPECT_TRUE(secure_is_unlocked()); | ||||
| 
 | ||||
|     testing::Mock::VerifyAndClearExpectations(&driver); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Drashna Jaelre
						Drashna Jaelre