summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDrashna Jaelre <drashna@live.com>2022-05-14 15:07:08 -0700
committerGitHub <noreply@github.com>2022-05-14 23:07:08 +0100
commitdb887e63d708925ad759e3504a6bc9ceef4aeb8f (patch)
tree7366242dc8834ff5ddb761ff162fa444bf64653f
parentbaa8d07fdb32a35f9ff5020d655271b01e057ddc (diff)
Enhancement and fixes of "Secure" feature (#16958)
-rw-r--r--quantum/process_keycode/process_secure.c10
-rw-r--r--quantum/quantum.c13
-rw-r--r--quantum/quantum_keycodes.h1
-rw-r--r--quantum/secure.c15
-rw-r--r--quantum/secure.h12
-rw-r--r--tests/secure/config.h32
-rw-r--r--tests/secure/test.mk20
-rw-r--r--tests/secure/test_secure.cpp278
8 files changed, 379 insertions, 2 deletions
diff --git a/quantum/process_keycode/process_secure.c b/quantum/process_keycode/process_secure.c
index 827ace597a..3224104c99 100644
--- a/quantum/process_keycode/process_secure.c
+++ b/quantum/process_keycode/process_secure.c
@@ -7,7 +7,9 @@
7 7
8bool preprocess_secure(uint16_t keycode, keyrecord_t *record) { 8bool preprocess_secure(uint16_t keycode, keyrecord_t *record) {
9 if (secure_is_unlocking()) { 9 if (secure_is_unlocking()) {
10 if (!record->event.pressed) { 10 // !pressed will trigger on any already held keys (such as layer keys),
11 // and cause the request secure check to prematurely fail.
12 if (record->event.pressed) {
11 secure_keypress_event(record->event.key.row, record->event.key.col); 13 secure_keypress_event(record->event.key.row, record->event.key.col);
12 } 14 }
13 15
@@ -33,7 +35,11 @@ bool process_secure(uint16_t keycode, keyrecord_t *record) {
33 secure_is_locked() ? secure_unlock() : secure_lock(); 35 secure_is_locked() ? secure_unlock() : secure_lock();
34 return false; 36 return false;
35 } 37 }
38 if (keycode == SECURE_REQUEST) {
39 secure_request_unlock();
40 return false;
41 }
36 } 42 }
37#endif 43#endif
38 return true; 44 return true;
39} \ No newline at end of file 45}
diff --git a/quantum/quantum.c b/quantum/quantum.c
index b54b46760c..ac3e2d90b4 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -571,3 +571,16 @@ const char *get_u16_str(uint16_t curr_num, char curr_pad) {
571 last_pad = curr_pad; 571 last_pad = curr_pad;
572 return get_numeric_str(buf, sizeof(buf), curr_num, curr_pad); 572 return get_numeric_str(buf, sizeof(buf), curr_num, curr_pad);
573} 573}
574
575#if defined(SECURE_ENABLE)
576void secure_hook_quantum(secure_status_t secure_status) {
577 // If keys are being held when this is triggered, they may not be released properly
578 // this can result in stuck keys, mods and layers. To prevent that, manually
579 // clear these, when it is triggered.
580
581 if (secure_status == SECURE_PENDING) {
582 clear_keyboard();
583 layer_clear();
584 }
585}
586#endif
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index 2f8ee2322e..40355d799a 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -601,6 +601,7 @@ enum quantum_keycodes {
601 SECURE_LOCK, 601 SECURE_LOCK,
602 SECURE_UNLOCK, 602 SECURE_UNLOCK,
603 SECURE_TOGGLE, 603 SECURE_TOGGLE,
604 SECURE_REQUEST,
604 605
605 CAPS_WORD, 606 CAPS_WORD,
606 607
diff --git a/quantum/secure.c b/quantum/secure.c
index 00048bd6dd..f07f6af2cb 100644
--- a/quantum/secure.c
+++ b/quantum/secure.c
@@ -23,17 +23,24 @@ static secure_status_t secure_status = SECURE_LOCKED;
23static uint32_t unlock_time = 0; 23static uint32_t unlock_time = 0;
24static uint32_t idle_time = 0; 24static uint32_t idle_time = 0;
25 25
26static void secure_hook(secure_status_t secure_status) {
27 secure_hook_quantum(secure_status);
28 secure_hook_kb(secure_status);
29}
30
26secure_status_t secure_get_status(void) { 31secure_status_t secure_get_status(void) {
27 return secure_status; 32 return secure_status;
28} 33}
29 34
30void secure_lock(void) { 35void secure_lock(void) {
31 secure_status = SECURE_LOCKED; 36 secure_status = SECURE_LOCKED;
37 secure_hook(secure_status);
32} 38}
33 39
34void secure_unlock(void) { 40void secure_unlock(void) {
35 secure_status = SECURE_UNLOCKED; 41 secure_status = SECURE_UNLOCKED;
36 idle_time = timer_read32(); 42 idle_time = timer_read32();
43 secure_hook(secure_status);
37} 44}
38 45
39void secure_request_unlock(void) { 46void secure_request_unlock(void) {
@@ -41,6 +48,7 @@ void secure_request_unlock(void) {
41 secure_status = SECURE_PENDING; 48 secure_status = SECURE_PENDING;
42 unlock_time = timer_read32(); 49 unlock_time = timer_read32();
43 } 50 }
51 secure_hook(secure_status);
44} 52}
45 53
46void secure_activity_event(void) { 54void secure_activity_event(void) {
@@ -85,3 +93,10 @@ void secure_task(void) {
85 } 93 }
86#endif 94#endif
87} 95}
96
97__attribute__((weak)) bool secure_hook_user(secure_status_t secure_status) {
98 return true;
99}
100__attribute__((weak)) bool secure_hook_kb(secure_status_t secure_status) {
101 return secure_hook_user(secure_status);
102}
diff --git a/quantum/secure.h b/quantum/secure.h
index 04507fd5b1..bb2ba50f31 100644
--- a/quantum/secure.h
+++ b/quantum/secure.h
@@ -65,3 +65,15 @@ void secure_keypress_event(uint8_t row, uint8_t col);
65/** \brief Handle various secure subsystem background tasks 65/** \brief Handle various secure subsystem background tasks
66 */ 66 */
67void secure_task(void); 67void secure_task(void);
68
69/** \brief quantum hook called when changing secure status device
70 */
71void secure_hook_quantum(secure_status_t secure_status);
72
73/** \brief user hook called when changing secure status device
74 */
75bool secure_hook_user(secure_status_t secure_status);
76
77/** \brief keyboard hook called when changing secure status device
78 */
79bool secure_hook_kb(secure_status_t secure_status);
diff --git a/tests/secure/config.h b/tests/secure/config.h
new file mode 100644
index 0000000000..3cfbc6cb14
--- /dev/null
+++ b/tests/secure/config.h
@@ -0,0 +1,32 @@
1/* Copyright 2021 Stefan Kerkmann
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19#include "test_common.h"
20
21// clang-format off
22#define SECURE_UNLOCK_SEQUENCE \
23 { \
24 {0, 1}, \
25 {0, 2}, \
26 {0, 3}, \
27 {0, 4} \
28 }
29// clang-format on
30
31#define SECURE_UNLOCK_TIMEOUT 20
32#define SECURE_IDLE_TIMEOUT 50
diff --git a/tests/secure/test.mk b/tests/secure/test.mk
new file mode 100644
index 0000000000..ea406493be
--- /dev/null
+++ b/tests/secure/test.mk
@@ -0,0 +1,20 @@
1# Copyright 2021 Stefan Kerkmann
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16# --------------------------------------------------------------------------------
17# Keep this file, even if it is empty, as a marker that this folder contains tests
18# --------------------------------------------------------------------------------
19
20SECURE_ENABLE = yes
diff --git a/tests/secure/test_secure.cpp b/tests/secure/test_secure.cpp
new file mode 100644
index 0000000000..87055ebb7f
--- /dev/null
+++ b/tests/secure/test_secure.cpp
@@ -0,0 +1,278 @@
1/* Copyright 2021 Stefan Kerkmann
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18#include "keyboard_report_util.hpp"
19#include "test_common.hpp"
20
21using testing::_;
22using testing::AnyNumber;
23using testing::InSequence;
24
25class Secure : public TestFixture {
26 public:
27 void SetUp() override {
28 secure_lock();
29 }
30 // Convenience function to tap `key`.
31 void TapKey(KeymapKey key) {
32 key.press();
33 run_one_scan_loop();
34 key.release();
35 run_one_scan_loop();
36 }
37
38 // Taps in order each key in `keys`.
39 template <typename... Ts>
40 void TapKeys(Ts... keys) {
41 for (KeymapKey key : {keys...}) {
42 TapKey(key);
43 }
44 }
45};
46
47TEST_F(Secure, test_lock) {
48 TestDriver driver;
49
50 // Allow any number of empty reports.
51 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0);
52
53 EXPECT_FALSE(secure_is_unlocked());
54 secure_unlock();
55 EXPECT_TRUE(secure_is_unlocked());
56 run_one_scan_loop();
57 EXPECT_TRUE(secure_is_unlocked());
58 secure_lock();
59 EXPECT_FALSE(secure_is_unlocked());
60
61 testing::Mock::VerifyAndClearExpectations(&driver);
62}
63
64TEST_F(Secure, test_unlock_timeout) {
65 TestDriver driver;
66
67 // Allow any number of empty reports.
68 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0);
69
70 EXPECT_FALSE(secure_is_unlocked());
71 secure_unlock();
72 EXPECT_TRUE(secure_is_unlocked());
73 idle_for(SECURE_IDLE_TIMEOUT+1);
74 EXPECT_FALSE(secure_is_unlocked());
75
76 testing::Mock::VerifyAndClearExpectations(&driver);
77}
78
79TEST_F(Secure, test_unlock_request) {
80 TestDriver driver;
81 auto key_mo = KeymapKey(0, 0, 0, MO(1));
82 auto key_a = KeymapKey(0, 1, 0, KC_A);
83 auto key_b = KeymapKey(0, 2, 0, KC_B);
84 auto key_c = KeymapKey(0, 3, 0, KC_C);
85 auto key_d = KeymapKey(0, 4, 0, KC_D);
86
87 set_keymap({key_mo, key_a, key_b, key_c, key_d});
88
89 // Allow any number of empty reports.
90 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0);
91
92 EXPECT_TRUE(secure_is_locked());
93 secure_request_unlock();
94 EXPECT_TRUE(secure_is_unlocking());
95 TapKeys(key_a, key_b, key_c, key_d);
96 EXPECT_TRUE(secure_is_unlocked());
97
98 testing::Mock::VerifyAndClearExpectations(&driver);
99}
100
101TEST_F(Secure, test_unlock_request_fail) {
102 TestDriver driver;
103 auto key_e = KeymapKey(0, 0, 0, KC_E);
104 auto key_a = KeymapKey(0, 1, 0, KC_A);
105 auto key_b = KeymapKey(0, 2, 0, KC_B);
106 auto key_c = KeymapKey(0, 3, 0, KC_C);
107 auto key_d = KeymapKey(0, 4, 0, KC_D);
108
109 set_keymap({key_e, key_a, key_b, key_c, key_d});
110
111 // Allow any number of empty reports.
112 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber());
113 { // Expect the following reports in this order.
114 InSequence s;
115 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
116 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B)));
117 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C)));
118 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_D)));
119 }
120 EXPECT_TRUE(secure_is_locked());
121 secure_request_unlock();
122 EXPECT_TRUE(secure_is_unlocking());
123 TapKeys(key_e, key_a, key_b, key_c, key_d);
124 EXPECT_FALSE(secure_is_unlocked());
125
126 testing::Mock::VerifyAndClearExpectations(&driver);
127}
128
129TEST_F(Secure, test_unlock_request_timeout) {
130 TestDriver driver;
131
132 // Allow any number of empty reports.
133 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0);
134
135 EXPECT_FALSE(secure_is_unlocked());
136 secure_request_unlock();
137 EXPECT_TRUE(secure_is_unlocking());
138 idle_for(SECURE_UNLOCK_TIMEOUT+1);
139 EXPECT_FALSE(secure_is_unlocking());
140 EXPECT_FALSE(secure_is_unlocked());
141
142 testing::Mock::VerifyAndClearExpectations(&driver);
143}
144
145
146TEST_F(Secure, test_unlock_request_fail_mid) {
147 TestDriver driver;
148 auto key_e = KeymapKey(0, 0, 0, KC_E);
149 auto key_a = KeymapKey(0, 1, 0, KC_A);
150 auto key_b = KeymapKey(0, 2, 0, KC_B);
151 auto key_c = KeymapKey(0, 3, 0, KC_C);
152 auto key_d = KeymapKey(0, 4, 0, KC_D);
153
154 set_keymap({key_e, key_a, key_b, key_c, key_d});
155
156 // Allow any number of empty reports.
157 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber());
158 { // Expect the following reports in this order.
159 InSequence s;
160 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C)));
161 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_D)));
162 }
163 EXPECT_FALSE(secure_is_unlocked());
164 secure_request_unlock();
165 EXPECT_TRUE(secure_is_unlocking());
166 TapKeys(key_a, key_b, key_e, key_c, key_d);
167 EXPECT_FALSE(secure_is_unlocking());
168 EXPECT_FALSE(secure_is_unlocked());
169
170 testing::Mock::VerifyAndClearExpectations(&driver);
171}
172
173TEST_F(Secure, test_unlock_request_fail_out_of_order) {
174 TestDriver driver;
175 auto key_e = KeymapKey(0, 0, 0, KC_E);
176 auto key_a = KeymapKey(0, 1, 0, KC_A);
177 auto key_b = KeymapKey(0, 2, 0, KC_B);
178 auto key_c = KeymapKey(0, 3, 0, KC_C);
179 auto key_d = KeymapKey(0, 4, 0, KC_D);
180
181 set_keymap({key_e, key_a, key_b, key_c, key_d});
182
183 // Allow any number of empty reports.
184 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber());
185 { // Expect the following reports in this order.
186 InSequence s;
187 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_B)));
188 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_C)));
189 }
190 EXPECT_FALSE(secure_is_unlocked());
191 secure_request_unlock();
192 EXPECT_TRUE(secure_is_unlocking());
193 TapKeys(key_a, key_d, key_b, key_c);
194 EXPECT_TRUE(secure_is_locked());
195 EXPECT_FALSE(secure_is_unlocking());
196 EXPECT_FALSE(secure_is_unlocked());
197
198 testing::Mock::VerifyAndClearExpectations(&driver);
199}
200
201TEST_F(Secure, test_unlock_request_on_layer) {
202 TestDriver driver;
203 auto key_mo = KeymapKey(0, 0, 0, MO(1));
204 auto key_a = KeymapKey(0, 1, 0, KC_A);
205 auto key_b = KeymapKey(0, 2, 0, KC_B);
206 auto key_c = KeymapKey(0, 3, 0, KC_C);
207 auto key_d = KeymapKey(0, 4, 0, KC_D);
208
209 set_keymap({key_mo, key_a, key_b, key_c, key_d});
210
211 // Allow any number of empty reports.
212 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(0);
213
214 EXPECT_TRUE(secure_is_locked());
215 key_mo.press();
216 run_one_scan_loop();
217 secure_request_unlock();
218 key_mo.release();
219 run_one_scan_loop();
220 EXPECT_TRUE(secure_is_unlocking());
221 TapKeys(key_a, key_b, key_c, key_d);
222 EXPECT_TRUE(secure_is_unlocked());
223 EXPECT_FALSE(layer_state_is(1));
224
225 testing::Mock::VerifyAndClearExpectations(&driver);
226}
227
228TEST_F(Secure, test_unlock_request_mid_stroke) {
229 TestDriver driver;
230 auto key_e = KeymapKey(0, 0, 0, KC_E);
231 auto key_a = KeymapKey(0, 1, 0, KC_A);
232 auto key_b = KeymapKey(0, 2, 0, KC_B);
233 auto key_c = KeymapKey(0, 3, 0, KC_C);
234 auto key_d = KeymapKey(0, 4, 0, KC_D);
235
236 set_keymap({key_e, key_a, key_b, key_c, key_d});
237
238 // Allow any number of empty reports.
239 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_E)));
240 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport()));
241 EXPECT_TRUE(secure_is_locked());
242 key_e.press();
243 run_one_scan_loop();
244 secure_request_unlock();
245 key_e.release();
246 run_one_scan_loop();
247 EXPECT_TRUE(secure_is_unlocking());
248 TapKeys(key_a, key_b, key_c, key_d);
249 EXPECT_TRUE(secure_is_unlocked());
250
251 testing::Mock::VerifyAndClearExpectations(&driver);
252}
253
254TEST_F(Secure, test_unlock_request_mods) {
255 TestDriver driver;
256 auto key_lsft = KeymapKey(0, 0, 0, KC_LSFT);
257 auto key_a = KeymapKey(0, 1, 0, KC_A);
258 auto key_b = KeymapKey(0, 2, 0, KC_B);
259 auto key_c = KeymapKey(0, 3, 0, KC_C);
260 auto key_d = KeymapKey(0, 4, 0, KC_D);
261
262 set_keymap({key_lsft, key_a, key_b, key_c, key_d});
263
264 // Allow any number of empty reports.
265 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(key_lsft.report_code)));
266 EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport()));
267 EXPECT_TRUE(secure_is_locked());
268 key_lsft.press();
269 run_one_scan_loop();
270 secure_request_unlock();
271 key_lsft.release();
272 run_one_scan_loop();
273 EXPECT_TRUE(secure_is_unlocking());
274 TapKeys(key_a, key_b, key_c, key_d);
275 EXPECT_TRUE(secure_is_unlocked());
276
277 testing::Mock::VerifyAndClearExpectations(&driver);
278}