From 14f91a6e155b5c05c9e945d2d6c4b40ecb8312b5 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 19 May 2019 12:24:15 -0400 Subject: [PATCH 001/139] add screenshots of tests passing --- docs/solo/images/conforms.PNG | Bin 0 -> 137512 bytes docs/solo/images/solo_conforms.PNG | Bin 0 -> 132439 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/solo/images/conforms.PNG create mode 100644 docs/solo/images/solo_conforms.PNG diff --git a/docs/solo/images/conforms.PNG b/docs/solo/images/conforms.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5aaec6f695cf47a5871aa6808180e434133a5343 GIT binary patch literal 137512 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

#A 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%0D?teA_VZ^h>uR6N0~RPVLM)>=1Wb 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 Ib4q9e0LZ){rT_o{ literal 0 HcmV?d00001 diff --git a/docs/solo/images/solo_conforms.PNG b/docs/solo/images/solo_conforms.PNG new file mode 100644 index 0000000000000000000000000000000000000000..03e3bf12dcbee85b45b30a1711611c84edaa558c GIT binary patch literal 132439 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%0Al1`&+@seK&cY?}+zh_^`SrIzQm^PeVcNS?o_v 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 E0IhR8G5`Po literal 0 HcmV?d00001 From bddd60c0805fa030ba0e53e697cc9c4100ad3185 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 27 May 2019 13:54:29 -0400 Subject: [PATCH 002/139] use persisted key info --- fido2/ctap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index d46a664..5f604ac 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -1987,5 +1987,5 @@ void ctap_reset() memset(PIN_CODE_HASH,0,sizeof(PIN_CODE_HASH)); ctap_reset_key_agreement(); - crypto_reset_master_secret(); + crypto_load_master_secret(STATE.key_space); } From 3f225f362f52e71e840256ba7496e807a31e12b5 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Wed, 29 May 2019 15:11:18 -0700 Subject: [PATCH 003/139] Update building.md Adding `solo` as a prerequesite, it's required by `make build-hacker` to merge the hex files. --- docs/solo/building.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/solo/building.md b/docs/solo/building.md index eed67c8..0947743 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -10,6 +10,8 @@ Install the [latest ARM compiler toolchain](https://developer.arm.com/open-sourc You can also install the ARM toolchain using a package manager like `apt-get` or `pacman`, but be warned they might be out of date. Typically it will be called `gcc-arm-none-eabi binutils-arm-none-eabi`. +Install `solo-python` usually with `pip3 install solo-python`. The `solo` python application may also be used for [programming](#programming). + To program your build, you'll need one of the following programs. - [openocd](http://openocd.org) From c2216929a904975968327d6661db8c15ebbec742 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Fri, 14 Jun 2019 00:19:14 +0200 Subject: [PATCH 004/139] Create SECURITY.md --- SECURITY.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2ba3dde --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +## Supported Versions + +We fix security issues as soon as they are found, and release firmware updates. +Each such release is accompanied by release notes, see . + +The latest version can be determined using the file . + +To update your key: +- either visit , or +- use our commandline tool : +``` +solo key update [--secure|--hacker] +``` + +## Reporting a Vulnerability + +To report vulnerabilities you have found: + +- preferably contact [@conor1](https://keybase.io/conor1), [@0x0ece](https://keybase.io/0x0ece) or [@nickray](https://keybase.io/nickray) via Keybase, or +- send us e-mail using OpenPGP to [security@solokeys.com](mailto:security@solokeys.com). + + + +We do not currently run a paid bug bounty program, but are happy to provide you with a bunch of Solo keys in recognition of your findings. + +## Mailing List + +Join our release notification mailing list to be informed about each release: + +https://sendy.solokeys.com/subscription?f=9MLIqMDmox1Ucz89C892Kq09IqYMM7OB8UrBrkvtTkDI763QF3L5PMYlRhlVNo2AI892mO From 751b2fd69cf5bd59f0544d74a881dbaed7b8d920 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 28 Jun 2019 12:16:59 +0300 Subject: [PATCH 005/139] add nfc device search --- tools/testing/tests/tester.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 10cd1d3..b77f690 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -63,8 +63,17 @@ class Tester: self.client = tester.client def find_device(self,): + print("--- HID ---") print(list(CtapHidDevice.list_devices())) dev = next(CtapHidDevice.list_devices(), None) + if not dev: + try: + from fido2.nfc import CtapNfcDevice + print("--- NFC ---") + print(list(CtapNfcDevice.list_devices())) + dev = next(CtapNfcDevice.list_devices(), None) + except: + print("NFC devices is not supported") if not dev: raise RuntimeError("No FIDO device found") self.dev = dev From 2c500fe25ae47864235db2c69ae97b97dc7c964f Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 28 Jun 2019 12:32:52 +0300 Subject: [PATCH 006/139] check pyscard module first --- tools/testing/tests/tester.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index b77f690..9864417 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -1,4 +1,4 @@ -import time, struct +import time, struct, sys from fido2.hid import CtapHidDevice from fido2.client import Fido2Client @@ -68,6 +68,8 @@ class Tester: dev = next(CtapHidDevice.list_devices(), None) if not dev: try: + if 'pyscard' not in sys.modules: + print('You have not installed pyscard module') from fido2.nfc import CtapNfcDevice print("--- NFC ---") print(list(CtapNfcDevice.list_devices())) From d1722b85af7a61a14df76edc6982397dec77f2dd Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 2 Jul 2019 19:45:46 +0300 Subject: [PATCH 007/139] add library not found error --- tools/testing/tests/tester.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 9864417..69fe808 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -68,14 +68,14 @@ class Tester: dev = next(CtapHidDevice.list_devices(), None) if not dev: try: - if 'pyscard' not in sys.modules: - print('You have not installed pyscard module') from fido2.nfc import CtapNfcDevice print("--- NFC ---") print(list(CtapNfcDevice.list_devices())) dev = next(CtapNfcDevice.list_devices(), None) - except: - print("NFC devices is not supported") + except ModuleNotFoundError: + print("One of NFC library is not installed properly.") + except Exception as e: + print("NFC devices is not supported", e, e.__class__.__name__) if not dev: raise RuntimeError("No FIDO device found") self.dev = dev From 795cf5c4a1653ae811761913d8e3da35e69eded4 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 2 Jul 2019 19:55:04 +0300 Subject: [PATCH 008/139] selecting NFC key works --- tools/testing/tests/tester.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 69fe808..21759ee 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -68,14 +68,12 @@ class Tester: dev = next(CtapHidDevice.list_devices(), None) if not dev: try: - from fido2.nfc import CtapNfcDevice + from fido2.pcsc import CtapPcscDevice print("--- NFC ---") - print(list(CtapNfcDevice.list_devices())) - dev = next(CtapNfcDevice.list_devices(), None) - except ModuleNotFoundError: + print(list(CtapPcscDevice.list_devices())) + dev = next(CtapPcscDevice.list_devices(), None) + except (ModuleNotFoundError, ImportError) as e: print("One of NFC library is not installed properly.") - except Exception as e: - print("NFC devices is not supported", e, e.__class__.__name__) if not dev: raise RuntimeError("No FIDO device found") self.dev = dev From 91c77da179b22861bd987778c85d79241f34597a Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 00:43:51 +0300 Subject: [PATCH 009/139] cbor.loads changed to cbor.decode_from --- tools/testing/tests/fido2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 778cfb2..94a361d 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -78,7 +78,7 @@ def TestCborKeysSorted(cbor_obj): # https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form if isinstance(cbor_obj, bytes): - cbor_obj = cbor.loads(cbor_obj)[0] + cbor_obj = cbor.decode_from(cbor_obj)[0] if isinstance(cbor_obj, dict): l = [x for x in cbor_obj] @@ -341,8 +341,8 @@ class FIDO2Tests(Tester): def test_get_info(self,): with Test("Get info"): info = self.ctap.get_info() - print(bytes(info)) - print(cbor.loads(bytes(info))) + print("data:", bytes(info)) + print("decoded:", cbor.decode_from(bytes(info))) with Test("Check FIDO2 string is in VERSIONS field"): assert "FIDO_2_0" in info.versions From 2d72e02051ed23e9be0115300b1565c6e84d5837 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 01:03:34 +0300 Subject: [PATCH 010/139] remove unused lib --- tools/testing/tests/tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 21759ee..4091ce2 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -1,4 +1,4 @@ -import time, struct, sys +import time, struct from fido2.hid import CtapHidDevice from fido2.client import Fido2Client From ff53bb1e32b608455d0131f928bac815058b8cf1 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 01:16:55 +0300 Subject: [PATCH 011/139] fix style --- tools/testing/tests/tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 4091ce2..6b0df00 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -72,7 +72,7 @@ class Tester: print("--- NFC ---") print(list(CtapPcscDevice.list_devices())) dev = next(CtapPcscDevice.list_devices(), None) - except (ModuleNotFoundError, ImportError) as e: + except (ModuleNotFoundError, ImportError): print("One of NFC library is not installed properly.") if not dev: raise RuntimeError("No FIDO device found") From b42e990f675518b7836c096030d095bccad04283 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 01:39:38 +0300 Subject: [PATCH 012/139] format fix --- tools/testing/tests/tester.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 6b0df00..99f26f6 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -69,6 +69,7 @@ class Tester: if not dev: try: from fido2.pcsc import CtapPcscDevice + print("--- NFC ---") print(list(CtapPcscDevice.list_devices())) dev = next(CtapPcscDevice.list_devices(), None) From b41cd5d5b872609c63906fd517e5d0577e4f459f Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 17:54:53 +0300 Subject: [PATCH 013/139] add nfc test force flag --- tools/testing/main.py | 10 ++++++++-- tools/testing/tests/tester.py | 11 +++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/testing/main.py b/tools/testing/main.py index 7e105e0..437e938 100644 --- a/tools/testing/main.py +++ b/tools/testing/main.py @@ -19,7 +19,9 @@ from tests import Tester, FIDO2Tests, U2FTests, HIDTests, SoloTests if __name__ == "__main__": if len(sys.argv) < 2: - print("Usage: %s [sim] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") + print("Usage: %s [sim] [nfc] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") + print(" sim - test via UDP simulation backend only") + print(" nfc - test via NFC interface only") sys.exit(0) t = Tester() @@ -31,7 +33,11 @@ if __name__ == "__main__": t.set_sim(True) t.set_user_count(10) - t.find_device() + nfcOnly = False + if "nfc" in sys.argv: + nfcOnly = True + + t.find_device(nfcOnly) if "solo" in sys.argv: SoloTests(t).run() diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 99f26f6..dbc88f9 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -62,10 +62,13 @@ class Tester: self.ctap1 = tester.ctap1 self.client = tester.client - def find_device(self,): - print("--- HID ---") - print(list(CtapHidDevice.list_devices())) - dev = next(CtapHidDevice.list_devices(), None) + def find_device(self, nfcInterfaceOnly = False): + dev = None + if not nfcInterfaceOnly: + print("--- HID ---") + print(list(CtapHidDevice.list_devices())) + dev = next(CtapHidDevice.list_devices(), None) + if not dev: try: from fido2.pcsc import CtapPcscDevice From b61e5db73672622110aeaf838ec6fc9fc4dd628e Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 17:57:27 +0300 Subject: [PATCH 014/139] style --- tools/testing/tests/tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index dbc88f9..011c1c2 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -62,7 +62,7 @@ class Tester: self.ctap1 = tester.ctap1 self.client = tester.client - def find_device(self, nfcInterfaceOnly = False): + def find_device(self, nfcInterfaceOnly=False): dev = None if not nfcInterfaceOnly: print("--- HID ---") From 8c2e2386a923cf4bd905e94bfb982879cd591936 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 20:35:50 +0300 Subject: [PATCH 015/139] fix `NFC applet selection does not work correctly` #213 --- targets/stm32l432/src/nfc.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 51e34bb..d9a5eca 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -385,25 +385,47 @@ void rblock_acknowledge() nfc_write_frame(buf,1); } +// international AID = RID:PIX +// RID length == 5 bytes +// usually aid length must be between 5 and 16 bytes +int applet_cmp(uint8_t * aid, int len, uint8_t * const_aid, int const_len) +{ + if (len > const_len) + return 10; + + // if international AID + if (const_aid[0] & 0xf0 == 0xa0) + { + if (len < 5) + return 11; + return memcmp(aid, const_aid, MIN(len, const_len)); + } else { + if (len != const_len) + return 11; + + return memcmp(aid, const_aid, const_len); + } +} + // Selects application. Returns 1 if success, 0 otherwise int select_applet(uint8_t * aid, int len) { - if (memcmp(aid,AID_FIDO,sizeof(AID_FIDO)) == 0) + if (applet_cmp(aid, len, AID_FIDO, sizeof(AID_FIDO) - 1) == 0) { NFC_STATE.selected_applet = APP_FIDO; return APP_FIDO; } - else if (memcmp(aid,AID_NDEF_TYPE_4,sizeof(AID_NDEF_TYPE_4)) == 0) + else if (applet_cmp(aid, len, AID_NDEF_TYPE_4, sizeof(AID_NDEF_TYPE_4) - 1) == 0) { NFC_STATE.selected_applet = APP_NDEF_TYPE_4; return APP_NDEF_TYPE_4; } - else if (memcmp(aid,AID_CAPABILITY_CONTAINER,sizeof(AID_CAPABILITY_CONTAINER)) == 0) + else if (applet_cmp(aid, len, AID_CAPABILITY_CONTAINER, sizeof(AID_CAPABILITY_CONTAINER) - 1) == 0) { NFC_STATE.selected_applet = APP_CAPABILITY_CONTAINER; return APP_CAPABILITY_CONTAINER; } - else if (memcmp(aid,AID_NDEF_TAG,sizeof(AID_NDEF_TAG)) == 0) + else if (applet_cmp(aid, len, AID_NDEF_TAG, sizeof(AID_NDEF_TAG) - 1) == 0) { NFC_STATE.selected_applet = APP_NDEF_TAG; return APP_NDEF_TAG; From 4dc6bcf7716d4e6ba7f4bf95867d05298088af27 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Wed, 3 Jul 2019 23:01:37 +0300 Subject: [PATCH 016/139] apdu decode sketch --- targets/stm32l432/src/nfc.c | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index d9a5eca..44c2d01 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -435,24 +435,27 @@ int select_applet(uint8_t * aid, int len) void nfc_process_iblock(uint8_t * buf, int len) { - APDU_HEADER * apdu = (APDU_HEADER *)(buf + 1); - uint8_t * payload = buf + 1 + 5; - uint8_t plen = apdu->lc; int selected; CTAP_RESPONSE ctap_resp; int status; + + APDU_STRUCT apdu; + apdu_decode(buf + 1, len - 1, &apdu); + uint16_t plen = apdu.lc; + //APDU_HEADER * apdu = (APDU_HEADER *)(buf + 1); + //uint8_t * payload = buf + 1 + 5; ---- apdu.data printf1(TAG_NFC,"Iblock: "); dump_hex1(TAG_NFC, buf, len); // TODO this needs to be organized better - switch(apdu->ins) + switch(apdu.ins) { case APDU_INS_SELECT: if (plen > len - 6) { - printf1(TAG_ERR, "Truncating APDU length %d\r\n", apdu->lc); - plen = len-6; + printf1(TAG_ERR, "Truncating APDU length %d\r\n", apdu.lc); + plen = len - 6; } // if (apdu->p1 == 0 && apdu->p2 == 0x0c) // { @@ -468,7 +471,7 @@ void nfc_process_iblock(uint8_t * buf, int len) // } // else { - selected = select_applet(payload, plen); + selected = select_applet(apdu.data, apdu.lc); if (selected == APP_FIDO) { // block = buf[0] & 1; @@ -487,7 +490,7 @@ void nfc_process_iblock(uint8_t * buf, int len) else { nfc_write_response(buf[0], SW_FILE_NOT_FOUND); - printf1(TAG_NFC, "NOT selected\r\n"); dump_hex1(TAG_NFC,payload, plen); + printf1(TAG_NFC, "NOT selected\r\n"); dump_hex1(TAG_NFC, apdu.data, apdu.lc); } } break; @@ -511,9 +514,9 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F Register command.\r\n"); - if (plen != 64) + if (apdu.lc != 64) { - printf1(TAG_NFC, "U2F Register request length error. len=%d.\r\n", plen); + printf1(TAG_NFC, "U2F Register request length error. len=%d.\r\n", apdu.lc); nfc_write_response(buf[0], SW_WRONG_LENGTH); return; } @@ -535,9 +538,6 @@ void nfc_process_iblock(uint8_t * buf, int len) // printf1(TAG_NFC, "U2F resp len: %d\r\n", ctap_resp.length); - - - printf1(TAG_NFC,"U2F Register answered %d (took %d)\r\n", millis(), timestamp()); break; @@ -549,10 +549,10 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F Authenticate command.\r\n"); - if (plen != 64 + 1 + buf[6 + 64]) + if (apdu.lc != 64 + 1 + buf[6 + 64]) { delay(5); - printf1(TAG_NFC, "U2F Authenticate request length error. len=%d keyhlen=%d.\r\n", plen, buf[6 + 64]); + printf1(TAG_NFC, "U2F Authenticate request length error. len=%d keyhlen=%d.\r\n", apdu.lc, buf[6 + 64]); nfc_write_response(buf[0], SW_WRONG_LENGTH); return; } @@ -579,7 +579,7 @@ void nfc_process_iblock(uint8_t * buf, int len) WTX_on(WTX_TIME_DEFAULT); ctap_response_init(&ctap_resp); - status = ctap_request(payload, plen, &ctap_resp); + status = ctap_request(apdu.data, apdu.lc, &ctap_resp); if (!WTX_off()) return; @@ -603,14 +603,13 @@ void nfc_process_iblock(uint8_t * buf, int len) case APDU_INS_READ_BINARY: - switch(NFC_STATE.selected_applet) { case APP_CAPABILITY_CONTAINER: printf1(TAG_NFC,"APP_CAPABILITY_CONTAINER\r\n"); if (plen > 15) { - printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu->lc); + printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu.lc); plen = 15; } nfc_write_response_ex(buf[0], (uint8_t *)&NFC_CC, plen, SW_SUCCESS); @@ -620,7 +619,7 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC,"APP_NDEF_TAG\r\n"); if (plen > (sizeof(NDEF_SAMPLE) - 1)) { - printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu->lc); + printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu.lc); plen = sizeof(NDEF_SAMPLE) - 1; } nfc_write_response_ex(buf[0], NDEF_SAMPLE, plen, SW_SUCCESS); @@ -634,7 +633,7 @@ void nfc_process_iblock(uint8_t * buf, int len) break; default: - printf1(TAG_NFC, "Unknown INS %02x\r\n", apdu->ins); + printf1(TAG_NFC, "Unknown INS %02x\r\n", apdu.ins); nfc_write_response(buf[0], SW_INS_INVALID); break; } From a51c9192b178c7ad389de1e8d63760cb2ceea02c Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 16:27:33 +0300 Subject: [PATCH 017/139] add apdu_decode --- fido2/apdu.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ fido2/apdu.h | 26 +++++++++++ 2 files changed, 145 insertions(+) create mode 100644 fido2/apdu.c diff --git a/fido2/apdu.c b/fido2/apdu.c new file mode 100644 index 0000000..b2516ea --- /dev/null +++ b/fido2/apdu.c @@ -0,0 +1,119 @@ +// Copyright 2019 SoloKeys Developers +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +// iso7816:2013. 5.3.2 Decoding conventions for command bodies + +#include "apdu.h" + +int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) +{ + EXT_APDU_HEADER *hapdu = (EXT_APDU_HEADER *)data; + + apdu->cla = hapdu->cla; + apdu->ins = hapdu->ins; + apdu->p1 = hapdu->p1; + apdu->p2 = hapdu->p2; + + apdu->lc = 0; + apdu->data = NULL; + apdu->le = 0; + apdu->extended_apdu = false; + apdu->case_type = 0x00; + + uint8_t b0 = hapdu->lc[0]; + + // case 1 + if (len == 4) + { + apdu->case_type = 0x01; + } + + // case 2S (Le) + if (len == 5 && b0 != 0) + { + apdu->case_type = 0x02; + apdu->le = b0; + if (!apdu->le) + apdu->le = 0x100; + } + + // case 3S (Lc + data) + if (len == 5U + b0 && b0 != 0) + { + apdu->case_type = 0x13; + apdu->lc = (hapdu->lc[1] << 8) + hapdu->lc[2]; + } + + // case 4S (Lc + data + Le) + if (len == 5U + b0 + 1U && b0 != 0) + { + apdu->case_type = 0x04; + apdu->lc = b0; + apdu->le = data[len - 1]; + if (!apdu->le) + apdu->le = 0x100; + } + + // extended length apdu + if (len >= 7 && b0 == 0) + { + uint16_t extlen = (hapdu->lc[1] << 8) + hapdu->lc[2]; + + // case 2E (Le) - extended + if (len == 7) + { + apdu->case_type = 0x12; + apdu->extended_apdu = true; + apdu->le = extlen; + if (!apdu->le) + apdu->le = 0x10000; + } + + // case 3E (Lc + data) - extended + if (len == 7U + extlen) + { + apdu->case_type = 0x13; + apdu->lc = extlen; + } + + // case 4E (Lc + data + Le) - extended 2-byte Le + if (len == 7U + extlen + 2U) + { + apdu->case_type = 0x14; + apdu->lc = extlen; + apdu->le = (data[len - 2] << 8) + data[len - 1]; + if (!apdu->le) + apdu->le = 0x10000; + } + + // case 4E (Lc + data + Le) - extended 3-byte Le + if (len == 7U + extlen + 3U && data[len - 3] == 0) + { + apdu->case_type = 0x24; + apdu->lc = extlen; + apdu->le = (data[len - 2] << 8) + data[len - 1]; + if (!apdu->le) + apdu->le = 0x10000; + } + } + + if (!apdu->case_type) + return 1; + + if (apdu->lc) + { + if (apdu->extended_apdu) + { + apdu->data = data + 5; + } else { + apdu->data = data + 7; + } + + } + + return 0; +} diff --git a/fido2/apdu.h b/fido2/apdu.h index d9687c7..ff3940c 100644 --- a/fido2/apdu.h +++ b/fido2/apdu.h @@ -2,6 +2,8 @@ #define _APDU_H_ #include +#include +#include typedef struct { @@ -12,6 +14,30 @@ typedef struct uint8_t lc; } __attribute__((packed)) APDU_HEADER; +typedef struct +{ + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t lc[3]; +} __attribute__((packed)) EXT_APDU_HEADER; + +typedef struct +{ + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint16_t lc; + uint8_t *data; + uint32_t le; + bool extended_apdu; + uint8_t case_type; +} __attribute__((packed)) APDU_STRUCT; + +extern int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu); + #define APDU_FIDO_U2F_REGISTER 0x01 #define APDU_FIDO_U2F_AUTHENTICATE 0x02 #define APDU_FIDO_U2F_VERSION 0x03 From 236498ee03997cc937a591c6934bbeec190480b7 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 16:27:57 +0300 Subject: [PATCH 018/139] add make --- targets/stm32l432/build/application.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/stm32l432/build/application.mk b/targets/stm32l432/build/application.mk index e482f87..848887f 100644 --- a/targets/stm32l432/build/application.mk +++ b/targets/stm32l432/build/application.mk @@ -7,7 +7,7 @@ SRC += src/startup_stm32l432xx.s src/system_stm32l4xx.c SRC += $(DRIVER_LIBS) $(USB_LIB) # FIDO2 lib -SRC += ../../fido2/util.c ../../fido2/u2f.c ../../fido2/test_power.c +SRC += ../../fido2/apdu.c ../../fido2/util.c ../../fido2/u2f.c ../../fido2/test_power.c SRC += ../../fido2/stubs.c ../../fido2/log.c ../../fido2/ctaphid.c ../../fido2/ctap.c SRC += ../../fido2/ctap_parse.c ../../fido2/main.c SRC += ../../fido2/extensions/extensions.c ../../fido2/extensions/solo.c From d2c85881e612d0e205f18da800b797e10b7646bb Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 16:29:30 +0300 Subject: [PATCH 019/139] applet selection and apdu check --- targets/stm32l432/src/nfc.c | 49 ++++++++++++++----------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 44c2d01..9a2a859 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -394,7 +394,7 @@ int applet_cmp(uint8_t * aid, int len, uint8_t * const_aid, int const_len) return 10; // if international AID - if (const_aid[0] & 0xf0 == 0xa0) + if ((const_aid[0] & 0xf0) == 0xa0) { if (len < 5) return 11; @@ -410,22 +410,22 @@ int applet_cmp(uint8_t * aid, int len, uint8_t * const_aid, int const_len) // Selects application. Returns 1 if success, 0 otherwise int select_applet(uint8_t * aid, int len) { - if (applet_cmp(aid, len, AID_FIDO, sizeof(AID_FIDO) - 1) == 0) + if (applet_cmp(aid, len, (uint8_t *)AID_FIDO, sizeof(AID_FIDO) - 1) == 0) { NFC_STATE.selected_applet = APP_FIDO; return APP_FIDO; } - else if (applet_cmp(aid, len, AID_NDEF_TYPE_4, sizeof(AID_NDEF_TYPE_4) - 1) == 0) + else if (applet_cmp(aid, len, (uint8_t *)AID_NDEF_TYPE_4, sizeof(AID_NDEF_TYPE_4) - 1) == 0) { NFC_STATE.selected_applet = APP_NDEF_TYPE_4; return APP_NDEF_TYPE_4; } - else if (applet_cmp(aid, len, AID_CAPABILITY_CONTAINER, sizeof(AID_CAPABILITY_CONTAINER) - 1) == 0) + else if (applet_cmp(aid, len, (uint8_t *)AID_CAPABILITY_CONTAINER, sizeof(AID_CAPABILITY_CONTAINER) - 1) == 0) { NFC_STATE.selected_applet = APP_CAPABILITY_CONTAINER; return APP_CAPABILITY_CONTAINER; } - else if (applet_cmp(aid, len, AID_NDEF_TAG, sizeof(AID_NDEF_TAG) - 1) == 0) + else if (applet_cmp(aid, len, (uint8_t *)AID_NDEF_TAG, sizeof(AID_NDEF_TAG) - 1) == 0) { NFC_STATE.selected_applet = APP_NDEF_TAG; return APP_NDEF_TAG; @@ -438,12 +438,10 @@ void nfc_process_iblock(uint8_t * buf, int len) int selected; CTAP_RESPONSE ctap_resp; int status; + uint16_t reslen; APDU_STRUCT apdu; apdu_decode(buf + 1, len - 1, &apdu); - uint16_t plen = apdu.lc; - //APDU_HEADER * apdu = (APDU_HEADER *)(buf + 1); - //uint8_t * payload = buf + 1 + 5; ---- apdu.data printf1(TAG_NFC,"Iblock: "); dump_hex1(TAG_NFC, buf, len); @@ -452,11 +450,6 @@ void nfc_process_iblock(uint8_t * buf, int len) switch(apdu.ins) { case APDU_INS_SELECT: - if (plen > len - 6) - { - printf1(TAG_ERR, "Truncating APDU length %d\r\n", apdu.lc); - plen = len - 6; - } // if (apdu->p1 == 0 && apdu->p2 == 0x0c) // { // printf1(TAG_NFC,"Select NDEF\r\n"); @@ -527,9 +520,9 @@ void nfc_process_iblock(uint8_t * buf, int len) // WTX_on(WTX_TIME_DEFAULT); // SystemClock_Config_LF32(); // delay(300); - if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_FAST);; + if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_FAST); u2f_request_nfc(&buf[1], len, &ctap_resp); - if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_IDLE);; + if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); // if (!WTX_off()) // return; @@ -572,7 +565,7 @@ void nfc_process_iblock(uint8_t * buf, int len) case APDU_FIDO_NFCCTAP_MSG: if (NFC_STATE.selected_applet != APP_FIDO) { nfc_write_response(buf[0], SW_INS_INVALID); - break; + return; } printf1(TAG_NFC, "FIDO2 CTAP message. %d\r\n", timestamp()); @@ -602,36 +595,32 @@ void nfc_process_iblock(uint8_t * buf, int len) break; case APDU_INS_READ_BINARY: - + // response length + reslen = apdu.le & 0xffff; switch(NFC_STATE.selected_applet) { case APP_CAPABILITY_CONTAINER: printf1(TAG_NFC,"APP_CAPABILITY_CONTAINER\r\n"); - if (plen > 15) - { - printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu.lc); - plen = 15; - } - nfc_write_response_ex(buf[0], (uint8_t *)&NFC_CC, plen, SW_SUCCESS); + if (reslen == 0 || reslen > sizeof(NFC_CC) - 1) + reslen = sizeof(NFC_CC) - 1; + nfc_write_response_ex(buf[0], (uint8_t *)&NFC_CC, reslen, SW_SUCCESS); ams_wait_for_tx(10); break; case APP_NDEF_TAG: printf1(TAG_NFC,"APP_NDEF_TAG\r\n"); - if (plen > (sizeof(NDEF_SAMPLE) - 1)) - { - printf1(TAG_ERR, "Truncating requested CC length %d\r\n", apdu.lc); - plen = sizeof(NDEF_SAMPLE) - 1; - } - nfc_write_response_ex(buf[0], NDEF_SAMPLE, plen, SW_SUCCESS); + if (reslen == 0 || reslen > sizeof(NDEF_SAMPLE) - 1) + reslen = sizeof(NDEF_SAMPLE) - 1; + nfc_write_response_ex(buf[0], NDEF_SAMPLE, reslen, SW_SUCCESS); ams_wait_for_tx(10); break; default: + nfc_write_response(buf[0], SW_FILE_NOT_FOUND); printf1(TAG_ERR, "No binary applet selected!\r\n"); return; break; } - break; + default: printf1(TAG_NFC, "Unknown INS %02x\r\n", apdu.ins); nfc_write_response(buf[0], SW_INS_INVALID); From 88a8eba4245a0a4b2bc598e8a2ed16c37f83e90b Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 16:32:11 +0300 Subject: [PATCH 020/139] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e80f85c..41478aa 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,5 @@ targets/*/docs/ main builds/* +tools/testing/.idea/* +tools/testing/tests/__pycache__/* From 26bc8a2889722a7a1f683c42d4551d18556bc8f1 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 17:27:03 +0300 Subject: [PATCH 021/139] apdu decoding works --- fido2/apdu.c | 4 ++-- targets/stm32l432/src/nfc.c | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/fido2/apdu.c b/fido2/apdu.c index b2516ea..2e8123f 100644 --- a/fido2/apdu.c +++ b/fido2/apdu.c @@ -44,8 +44,8 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) // case 3S (Lc + data) if (len == 5U + b0 && b0 != 0) { - apdu->case_type = 0x13; - apdu->lc = (hapdu->lc[1] << 8) + hapdu->lc[2]; + apdu->case_type = 0x03; + apdu->lc = b0; } // case 4S (Lc + data + Le) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 9a2a859..54c3859 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -440,12 +440,24 @@ void nfc_process_iblock(uint8_t * buf, int len) int status; uint16_t reslen; - APDU_STRUCT apdu; - apdu_decode(buf + 1, len - 1, &apdu); - printf1(TAG_NFC,"Iblock: "); dump_hex1(TAG_NFC, buf, len); + uint8_t apdu_offset = 1; + // NAD following + if (buf[0] & 0x04) apdu_offset++; + // CID following + if (buf[0] & 0x08) apdu_offset++; + + APDU_STRUCT apdu; + if (apdu_decode(buf + apdu_offset, len - apdu_offset, &apdu)) { + printf1(TAG_NFC,"apdu decode error\n"); + nfc_write_response(buf[0], SW_COND_USE_NOT_SATISFIED); + return; + } + printf1(TAG_NFC,"apdu ok. %scase=%02x cla=%02x ins=%02x p1=%02x p2=%02x lc=%d le=%d\n", + apdu.extended_apdu ? "[e]":"", apdu.case_type, apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.lc, apdu.le); + // TODO this needs to be organized better switch(apdu.ins) { From 75b1d9cd016cfefc857df81721a814abf8592c9a Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 17:38:34 +0300 Subject: [PATCH 022/139] offset calc refactoring --- targets/stm32l432/src/nfc.c | 18 ++++++++++++------ targets/stm32l432/src/nfc.h | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 54c3859..7e65def 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -14,6 +14,16 @@ #define IS_IRQ_ACTIVE() (1 == (LL_GPIO_ReadInputPort(SOLO_AMS_IRQ_PORT) & SOLO_AMS_IRQ_PIN)) +uint8_t p14443_block_offset(uint8_t pcb) { + uint8_t offset = 1; + // NAD following + if (pcb & 0x04) offset++; + // CID following + if (pcb & 0x08) offset++; + + return offset; +} + // Capability container const CAPABILITY_CONTAINER NFC_CC = { .cclen_hi = 0x00, .cclen_lo = 0x0f, @@ -443,14 +453,10 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC,"Iblock: "); dump_hex1(TAG_NFC, buf, len); - uint8_t apdu_offset = 1; - // NAD following - if (buf[0] & 0x04) apdu_offset++; - // CID following - if (buf[0] & 0x08) apdu_offset++; + uint8_t block_offset = p14443_block_offset(buf[0]); APDU_STRUCT apdu; - if (apdu_decode(buf + apdu_offset, len - apdu_offset, &apdu)) { + if (apdu_decode(buf + block_offset, len - block_offset, &apdu)) { printf1(TAG_NFC,"apdu decode error\n"); nfc_write_response(buf[0], SW_COND_USE_NOT_SATISFIED); return; diff --git a/targets/stm32l432/src/nfc.h b/targets/stm32l432/src/nfc.h index 9871537..f284a8e 100644 --- a/targets/stm32l432/src/nfc.h +++ b/targets/stm32l432/src/nfc.h @@ -40,6 +40,8 @@ typedef struct #define NFC_CMD_SBLOCK 0xc0 #define IS_SBLOCK(x) ( (((x) & 0xc0) == NFC_CMD_SBLOCK) && (((x) & 0x02) == 0x02) ) +extern uint8_t p14443_block_offset(uint8_t pcb); + #define NFC_SBLOCK_DESELECT 0x30 #define NFC_SBLOCK_WTX 0x30 From 4845d2c1725b2905cf479bfe0ed50189d788b9ea Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 17:52:00 +0300 Subject: [PATCH 023/139] fix 14443 apdu decode and select --- fido2/apdu.c | 6 +++--- targets/stm32l432/src/nfc.c | 11 +++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/fido2/apdu.c b/fido2/apdu.c index 2e8123f..3eacdef 100644 --- a/fido2/apdu.c +++ b/fido2/apdu.c @@ -33,7 +33,7 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) } // case 2S (Le) - if (len == 5 && b0 != 0) + if (len == 5) { apdu->case_type = 0x02; apdu->le = b0; @@ -108,9 +108,9 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) { if (apdu->extended_apdu) { - apdu->data = data + 5; - } else { apdu->data = data + 7; + } else { + apdu->data = data + 5; } } diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 7e65def..0097a64 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -457,11 +457,11 @@ void nfc_process_iblock(uint8_t * buf, int len) APDU_STRUCT apdu; if (apdu_decode(buf + block_offset, len - block_offset, &apdu)) { - printf1(TAG_NFC,"apdu decode error\n"); + printf1(TAG_NFC,"apdu decode error\r\n"); nfc_write_response(buf[0], SW_COND_USE_NOT_SATISFIED); return; } - printf1(TAG_NFC,"apdu ok. %scase=%02x cla=%02x ins=%02x p1=%02x p2=%02x lc=%d le=%d\n", + printf1(TAG_NFC,"apdu ok. %scase=%02x cla=%02x ins=%02x p1=%02x p2=%02x lc=%d le=%d\r\n", apdu.extended_apdu ? "[e]":"", apdu.case_type, apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.lc, apdu.le); // TODO this needs to be organized better @@ -485,11 +485,6 @@ void nfc_process_iblock(uint8_t * buf, int len) selected = select_applet(apdu.data, apdu.lc); if (selected == APP_FIDO) { - // block = buf[0] & 1; - // block = NFC_STATE.block_num; - // block = !block; - // NFC_STATE.block_num = block; - // NFC_STATE.block_num = block; nfc_write_response_ex(buf[0], (uint8_t *)"U2F_V2", 6, SW_SUCCESS); printf1(TAG_NFC, "FIDO applet selected.\r\n"); } @@ -501,7 +496,7 @@ void nfc_process_iblock(uint8_t * buf, int len) else { nfc_write_response(buf[0], SW_FILE_NOT_FOUND); - printf1(TAG_NFC, "NOT selected\r\n"); dump_hex1(TAG_NFC, apdu.data, apdu.lc); + printf1(TAG_NFC, "NOT selected "); dump_hex1(TAG_NFC, apdu.data, apdu.lc); } } break; From f2ebaf6abe6e45ea3a17ad071017e3486d838520 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 19:14:26 +0300 Subject: [PATCH 024/139] invalid cla and r-block works --- fido2/apdu.h | 1 + targets/stm32l432/src/nfc.c | 90 ++++++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/fido2/apdu.h b/fido2/apdu.h index ff3940c..44029b3 100644 --- a/fido2/apdu.h +++ b/fido2/apdu.h @@ -51,6 +51,7 @@ extern int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu); #define SW_COND_USE_NOT_SATISFIED 0x6985 #define SW_FILE_NOT_FOUND 0x6a82 #define SW_INS_INVALID 0x6d00 // Instruction code not supported or invalid +#define SW_CLA_INVALID 0x6e00 #define SW_INTERNAL_EXCEPTION 0x6f00 #endif //_APDU_H_ diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 0097a64..4cfa7ca 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -171,14 +171,18 @@ bool nfc_write_response_ex(uint8_t req0, uint8_t * data, uint8_t len, uint16_t r if (len > 32 - 3) return false; - res[0] = NFC_CMD_IBLOCK | (req0 & 3); + res[0] = NFC_CMD_IBLOCK | (req0 & 0x0f); + res[1] = 0; + res[2] = 0; + + uint8_t block_offset = p14443_block_offset(req0); if (len && data) - memcpy(&res[1], data, len); + memcpy(&res[block_offset], data, len); - res[len + 1] = resp >> 8; - res[len + 2] = resp & 0xff; - nfc_write_frame(res, 3 + len); + res[len + block_offset + 0] = resp >> 8; + res[len + block_offset + 1] = resp & 0xff; + nfc_write_frame(res, block_offset + len + 2); return true; } @@ -192,21 +196,24 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) { uint8_t res[32 + 2]; int sendlen = 0; - uint8_t iBlock = NFC_CMD_IBLOCK | (req0 & 3); + uint8_t iBlock = NFC_CMD_IBLOCK | (req0 & 0x0f); + uint8_t block_offset = p14443_block_offset(req0); if (len <= 31) { uint8_t res[32] = {0}; - res[0] = iBlock; + res[0] = iBlock; if (len && data) - memcpy(&res[1], data, len); - nfc_write_frame(res, len + 1); + memcpy(&res[block_offset], data, len); + nfc_write_frame(res, len + block_offset); } else { do { // transmit I block - int vlen = MIN(31, len - sendlen); - res[0] = iBlock; - memcpy(&res[1], &data[sendlen], vlen); + int vlen = MIN(32 - block_offset, len - sendlen); + res[0] = iBlock; + res[1] = 0; + res[2] = 0; + memcpy(&res[block_offset], &data[sendlen], vlen); // if not a last block if (vlen + sendlen < len) @@ -215,7 +222,7 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) } // send data - nfc_write_frame(res, vlen + 1); + nfc_write_frame(res, vlen + block_offset); sendlen += vlen; // wait for transmit (32 bytes aprox 2,5ms) @@ -236,9 +243,10 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) break; } - if (reclen != 1) + uint8_t rblock_offset = p14443_block_offset(res[0]); + if (reclen != rblock_offset) { - printf1(TAG_NFC, "R block length error. len: %d. %d/%d \r\n", reclen,sendlen,len); + printf1(TAG_NFC, "R block length error. len: %d. %d/%d \r\n", reclen, sendlen, len); dump_hex1(TAG_NFC, recbuf, reclen); break; } @@ -387,12 +395,18 @@ int answer_rats(uint8_t parameter) return 0; } -void rblock_acknowledge() +void rblock_acknowledge(uint8_t req0, bool ack) { - uint8_t buf[32]; + uint8_t buf[32] = {0}; + + uint8_t block_offset = p14443_block_offset(req0); NFC_STATE.block_num = !NFC_STATE.block_num; - buf[0] = NFC_CMD_RBLOCK | NFC_STATE.block_num; - nfc_write_frame(buf,1); + + buf[0] = NFC_CMD_RBLOCK | (req0 & 0x0f); + if (ack) + buf[0] |= NFC_CMD_RBLOCK_ACK; + + nfc_write_frame(buf, block_offset); } // international AID = RID:PIX @@ -464,6 +478,13 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC,"apdu ok. %scase=%02x cla=%02x ins=%02x p1=%02x p2=%02x lc=%d le=%d\r\n", apdu.extended_apdu ? "[e]":"", apdu.case_type, apdu.cla, apdu.ins, apdu.p1, apdu.p2, apdu.lc, apdu.le); + // check CLA + if (apdu.cla != 0x00 && apdu.cla != 0x80) { + printf1(TAG_NFC, "Unknown CLA %02x\r\n", apdu.cla); + nfc_write_response(buf[0], SW_CLA_INVALID); + return; + } + // TODO this needs to be organized better switch(apdu.ins) { @@ -639,8 +660,6 @@ void nfc_process_iblock(uint8_t * buf, int len) nfc_write_response(buf[0], SW_INS_INVALID); break; } - - } static uint8_t ibuf[1024]; @@ -654,7 +673,7 @@ void clear_ibuf() void nfc_process_block(uint8_t * buf, unsigned int len) { - + printf1(TAG_NFC, "-----\r\n"); if (!len) return; @@ -664,6 +683,7 @@ void nfc_process_block(uint8_t * buf, unsigned int len) } else if (IS_IBLOCK(buf[0])) { + uint8_t block_offset = p14443_block_offset(buf[0]); if (buf[0] & 0x10) { printf1(TAG_NFC_APDU, "NFC_CMD_IBLOCK chaining blen=%d len=%d\r\n", ibuflen, len); @@ -677,27 +697,27 @@ void nfc_process_block(uint8_t * buf, unsigned int len) printf1(TAG_NFC_APDU,"i> "); dump_hex1(TAG_NFC_APDU, buf, len); - if (len) + if (len > block_offset) { - memcpy(&ibuf[ibuflen], &buf[1], len - 1); - ibuflen += len - 1; + memcpy(&ibuf[ibuflen], &buf[block_offset], len - block_offset); + ibuflen += len - block_offset; } // send R block - uint8_t rb = NFC_CMD_RBLOCK | NFC_CMD_RBLOCK_ACK | (buf[0] & 3); - nfc_write_frame(&rb, 1); + rblock_acknowledge(buf[0], true); } else { if (ibuflen) { - if (len) + if (len > block_offset) { - memcpy(&ibuf[ibuflen], &buf[1], len - 1); - ibuflen += len - 1; + memcpy(&ibuf[ibuflen], &buf[block_offset], len - block_offset); + ibuflen += len - block_offset; } - memmove(&ibuf[1], ibuf, ibuflen); - ibuf[0] = buf[0]; - ibuflen++; + // add last chaining to top of the block + memmove(&ibuf[block_offset], ibuf, ibuflen); + memmove(ibuf, buf, block_offset); + ibuflen += block_offset; printf1(TAG_NFC_APDU, "NFC_CMD_IBLOCK chaining last block. blen=%d len=%d\r\n", ibuflen, len); @@ -706,7 +726,6 @@ void nfc_process_block(uint8_t * buf, unsigned int len) nfc_process_iblock(ibuf, ibuflen); } else { - // printf1(TAG_NFC, "NFC_CMD_IBLOCK\r\n"); nfc_process_iblock(buf, len); } clear_ibuf(); @@ -714,7 +733,7 @@ void nfc_process_block(uint8_t * buf, unsigned int len) } else if (IS_RBLOCK(buf[0])) { - rblock_acknowledge(); + rblock_acknowledge(buf[0], false); printf1(TAG_NFC, "NFC_CMD_RBLOCK\r\n"); } else if (IS_SBLOCK(buf[0])) @@ -733,6 +752,7 @@ void nfc_process_block(uint8_t * buf, unsigned int len) else { printf1(TAG_NFC, "NFC_CMD_SBLOCK, Unknown. len[%d]\r\n", len); + nfc_write_response(buf[0], SW_COND_USE_NOT_SATISFIED); } dump_hex1(TAG_NFC, buf, len); } From cb76c34ed2a8fd45d2a005901b1aa24a6ebc9740 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 19:45:09 +0300 Subject: [PATCH 025/139] fix addressing --- targets/stm32l432/src/nfc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 4cfa7ca..c06f6cf 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -554,17 +554,16 @@ void nfc_process_iblock(uint8_t * buf, int len) // WTX_on(WTX_TIME_DEFAULT); // SystemClock_Config_LF32(); // delay(300); - if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_FAST); - u2f_request_nfc(&buf[1], len, &ctap_resp); - if (device_is_nfc()) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); + u2f_request_nfc(&buf[block_offset], len - block_offset, &ctap_resp); + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); // if (!WTX_off()) // return; + printf1(TAG_NFC, "U2F resp len: %d\r\n", ctap_resp.length); printf1(TAG_NFC,"U2F Register P2 took %d\r\n", timestamp()); nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); - // printf1(TAG_NFC, "U2F resp len: %d\r\n", ctap_resp.length); - printf1(TAG_NFC,"U2F Register answered %d (took %d)\r\n", millis(), timestamp()); break; @@ -586,7 +585,7 @@ void nfc_process_iblock(uint8_t * buf, int len) timestamp(); // WTX_on(WTX_TIME_DEFAULT); - u2f_request_nfc(&buf[1], len, &ctap_resp); + u2f_request_nfc(&buf[block_offset], len - block_offset, &ctap_resp); // if (!WTX_off()) // return; From d1df8b8b77f6de5f2e02fde00a02f37fb8b83b91 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 19:54:00 +0300 Subject: [PATCH 026/139] u2f authenticate fix --- targets/stm32l432/src/nfc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index c06f6cf..16d41aa 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -575,10 +575,10 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F Authenticate command.\r\n"); - if (apdu.lc != 64 + 1 + buf[6 + 64]) + if (apdu.lc != 64 + 1 + apdu.data[64]) { delay(5); - printf1(TAG_NFC, "U2F Authenticate request length error. len=%d keyhlen=%d.\r\n", apdu.lc, buf[6 + 64]); + printf1(TAG_NFC, "U2F Authenticate request length error. len=%d keyhlen=%d.\r\n", apdu.lc, apdu.data[64]); nfc_write_response(buf[0], SW_WRONG_LENGTH); return; } From 151e1d0e9ba46f63c066011e3a1ce4f1fc7b4aeb Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 20:09:47 +0300 Subject: [PATCH 027/139] fix some errors in tests --- tools/testing/tests/tester.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 011c1c2..d05185f 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -51,6 +51,7 @@ class Tester: self.host = "examplo.org" self.user_count = 10 self.is_sim = False + self.nfc_interface_only = False; if tester: self.initFromTester(tester) @@ -61,9 +62,11 @@ class Tester: self.ctap = tester.ctap self.ctap1 = tester.ctap1 self.client = tester.client + self.nfc_interface_only = tester.nfc_interface_only def find_device(self, nfcInterfaceOnly=False): dev = None + self.nfc_interface_only = nfcInterfaceOnly if not nfcInterfaceOnly: print("--- HID ---") print(list(CtapHidDevice.list_devices())) @@ -102,7 +105,7 @@ class Tester: else: print("Please reboot authentictor and hit enter") input() - self.find_device() + self.find_device(self.nfc_interface_only) def send_data(self, cmd, data): if not isinstance(data, bytes): @@ -196,7 +199,7 @@ class Tester: print("You must power cycle authentictor. Hit enter when done.") input() time.sleep(0.2) - self.find_device() + self.find_device(self.nfc_interface_only) self.ctap.reset() def testMC(self, test, *args, **kwargs): From 2272e69e15d68ed63a6497b71bbb3fcc53f04195 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 20:14:24 +0300 Subject: [PATCH 028/139] fix tests --- tools/testing/tests/fido2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 94a361d..88fbe9d 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -223,7 +223,7 @@ class FIDO2Tests(Tester): with Test("Get shared secret"): key_agreement, shared_secret = ( - self.client.pin_protocol._init_shared_secret() + self.client.pin_protocol.get_shared_secret() ) cipher = Cipher( algorithms.AES(shared_secret), From 4d9285085fca9a7834989e05b6aa23f0bacccf93 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 20:42:24 +0300 Subject: [PATCH 029/139] fix tests --- tools/testing/tests/fido2.py | 4 +--- tools/testing/tests/tester.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 88fbe9d..3b5b557 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -222,9 +222,7 @@ class FIDO2Tests(Tester): ) with Test("Get shared secret"): - key_agreement, shared_secret = ( - self.client.pin_protocol.get_shared_secret() - ) + key_agreement, shared_secret = self.client.pin_protocol.get_shared_secret() cipher = Cipher( algorithms.AES(shared_secret), modes.CBC(b"\x00" * 16), diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index d05185f..ab7c279 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -51,7 +51,7 @@ class Tester: self.host = "examplo.org" self.user_count = 10 self.is_sim = False - self.nfc_interface_only = False; + self.nfc_interface_only = False if tester: self.initFromTester(tester) From 315b6564ab002abc4eef1f035f5513e1763f9ec6 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Thu, 4 Jul 2019 23:12:31 +0300 Subject: [PATCH 030/139] u2f works with extended apdu and now user presence not needs if request come from nfc and power from usb --- fido2/device.h | 2 ++ fido2/u2f.c | 10 +++++----- fido2/u2f.h | 2 +- targets/stm32l432/src/device.c | 7 ++++++- targets/stm32l432/src/nfc.c | 6 ++++-- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/fido2/device.h b/fido2/device.h index dfb95ec..0c11fe8 100644 --- a/fido2/device.h +++ b/fido2/device.h @@ -105,6 +105,8 @@ void device_set_clock_rate(DEVICE_CLOCK_RATE param); #define NFC_IS_AVAILABLE 2 int device_is_nfc(); +void request_from_nfc(bool request_active); + void device_init_button(); #endif diff --git a/fido2/u2f.c b/fido2/u2f.c index 14cb848..7999ab2 100644 --- a/fido2/u2f.c +++ b/fido2/u2f.c @@ -113,14 +113,14 @@ end: printf1(TAG_U2F,"u2f resp: "); dump_hex1(TAG_U2F, _u2f_resp->data, _u2f_resp->length); } -void u2f_request_nfc(uint8_t * req, int len, CTAP_RESPONSE * resp) +void u2f_request_nfc(uint8_t * header, uint8_t * data, int datalen, CTAP_RESPONSE * resp) { - if (len < 5 || !req) + if (!header) return; - uint32_t alen = req[4]; - - u2f_request_ex((APDU_HEADER *)req, &req[5], alen, resp); + request_from_nfc(true); // disable presence test + u2f_request_ex((APDU_HEADER *)header, data, datalen, resp); + request_from_nfc(false); // enable presence test } void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp) diff --git a/fido2/u2f.h b/fido2/u2f.h index 9055b36..ad73cc9 100644 --- a/fido2/u2f.h +++ b/fido2/u2f.h @@ -101,7 +101,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp); // u2f_request send a U2F message to NFC protocol // @req data with iso7816 apdu message // @len data length -void u2f_request_nfc(uint8_t * req, int len, CTAP_RESPONSE * resp); +void u2f_request_nfc(uint8_t * header, uint8_t * data, int datalen, CTAP_RESPONSE * resp); int8_t u2f_authenticate_credential(struct u2f_key_handle * kh, uint8_t * appid); diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index ae441fc..6289189 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -43,6 +43,7 @@ uint32_t __last_update = 0; extern PCD_HandleTypeDef hpcd; static int _NFC_status = 0; static bool isLowFreq = 0; +static bool _RequestComeFromNFC = false; // #define IS_BUTTON_PRESSED() (0 == (LL_GPIO_ReadInputPort(SOLO_BUTTON_PORT) & SOLO_BUTTON_PIN)) static int is_physical_button_pressed() @@ -57,6 +58,10 @@ static int is_touch_button_pressed() int (*IS_BUTTON_PRESSED)() = is_physical_button_pressed; +void request_from_nfc(bool request_active) { + _RequestComeFromNFC = request_active; +} + // Timer6 overflow handler. happens every ~90ms. void TIM6_DAC_IRQHandler() { @@ -491,7 +496,7 @@ static int handle_packets() int ctap_user_presence_test(uint32_t up_delay) { int ret; - if (device_is_nfc() == NFC_IS_ACTIVE) + if (device_is_nfc() == NFC_IS_ACTIVE || _RequestComeFromNFC) { return 1; } diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 16d41aa..2c1b34d 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -555,7 +555,7 @@ void nfc_process_iblock(uint8_t * buf, int len) // SystemClock_Config_LF32(); // delay(300); if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); - u2f_request_nfc(&buf[block_offset], len - block_offset, &ctap_resp); + u2f_request_nfc(&buf[block_offset], apdu.data, apdu.lc, &ctap_resp); if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); // if (!WTX_off()) // return; @@ -585,7 +585,7 @@ void nfc_process_iblock(uint8_t * buf, int len) timestamp(); // WTX_on(WTX_TIME_DEFAULT); - u2f_request_nfc(&buf[block_offset], len - block_offset, &ctap_resp); + u2f_request_nfc(&buf[block_offset], apdu.data, apdu.lc, &ctap_resp); // if (!WTX_off()) // return; @@ -604,8 +604,10 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "FIDO2 CTAP message. %d\r\n", timestamp()); WTX_on(WTX_TIME_DEFAULT); + request_from_nfc(true); ctap_response_init(&ctap_resp); status = ctap_request(apdu.data, apdu.lc, &ctap_resp); + request_from_nfc(false); if (!WTX_off()) return; From 24a006068d93b3fa7cda1cb8d2129de1a64d37d3 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 5 Jul 2019 12:25:46 +0300 Subject: [PATCH 031/139] fix extended apdu decode --- fido2/apdu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fido2/apdu.c b/fido2/apdu.c index 3eacdef..df89099 100644 --- a/fido2/apdu.c +++ b/fido2/apdu.c @@ -77,6 +77,7 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) if (len == 7U + extlen) { apdu->case_type = 0x13; + apdu->extended_apdu = true; apdu->lc = extlen; } @@ -84,6 +85,7 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) if (len == 7U + extlen + 2U) { apdu->case_type = 0x14; + apdu->extended_apdu = true; apdu->lc = extlen; apdu->le = (data[len - 2] << 8) + data[len - 1]; if (!apdu->le) @@ -94,6 +96,7 @@ int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu) if (len == 7U + extlen + 3U && data[len - 3] == 0) { apdu->case_type = 0x24; + apdu->extended_apdu = true; apdu->lc = extlen; apdu->le = (data[len - 2] << 8) + data[len - 1]; if (!apdu->le) From ed9689435d7a2c7309c3b03ad9b165fbdf72315d Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 5 Jul 2019 12:33:23 +0300 Subject: [PATCH 032/139] APDU_FIDO_U2F_VERSION --- targets/stm32l432/src/nfc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 2c1b34d..520e8dd 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -530,7 +530,8 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F GetVersion command.\r\n"); - nfc_write_response_ex(buf[0], (uint8_t *)"U2F_V2", 6, SW_SUCCESS); + u2f_request_nfc(&buf[block_offset], apdu.data, apdu.lc, &ctap_resp); + nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); break; case APDU_FIDO_U2F_REGISTER: From fa9408d5d63b87a9bb34c17bf3df781752c10e5f Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 5 Jul 2019 12:39:32 +0300 Subject: [PATCH 033/139] fix u2f tests --- tools/testing/tests/u2f.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py index d1e8033..367ee4a 100644 --- a/tools/testing/tests/u2f.py +++ b/tools/testing/tests/u2f.py @@ -42,12 +42,14 @@ class U2FTests(Tester): with Test("Check bad INS"): try: self.ctap1.send_apdu(0, 0, 0, 0, b"") + assert False except ApduError as e: assert e.code == 0x6D00 with Test("Check bad CLA"): try: self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") + assert False except ApduError as e: assert e.code == 0x6E00 From 533ce3923785774e467c09585723b7b3693a9dc4 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Sat, 6 Jul 2019 00:15:21 +0300 Subject: [PATCH 034/139] fix nfc_cc length --- targets/stm32l432/src/nfc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 520e8dd..4a7429d 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -637,8 +637,8 @@ void nfc_process_iblock(uint8_t * buf, int len) { case APP_CAPABILITY_CONTAINER: printf1(TAG_NFC,"APP_CAPABILITY_CONTAINER\r\n"); - if (reslen == 0 || reslen > sizeof(NFC_CC) - 1) - reslen = sizeof(NFC_CC) - 1; + if (reslen == 0 || reslen > sizeof(NFC_CC)) + reslen = sizeof(NFC_CC); nfc_write_response_ex(buf[0], (uint8_t *)&NFC_CC, reslen, SW_SUCCESS); ams_wait_for_tx(10); break; From 89e00482e4b6a62dbeb2e97214500f2e84ab2f37 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Sat, 6 Jul 2019 12:52:23 +0300 Subject: [PATCH 035/139] some improvements --- targets/stm32l432/src/nfc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 4a7429d..609b104 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -122,6 +122,7 @@ bool ams_receive_with_timeout(uint32_t timeout_ms, uint8_t * data, int maxlen, i while (tstart + timeout_ms > millis()) { uint8_t int0 = ams_read_reg(AMS_REG_INT0); + if (int0) process_int0(int0); uint8_t buffer_status2 = ams_read_reg(AMS_REG_BUF2); if (buffer_status2 && (int0 & AMS_INT_RXE)) @@ -389,7 +390,12 @@ int answer_rats(uint8_t parameter) nfc_write_frame(res, sizeof(res)); - ams_wait_for_tx(10); + if (!ams_wait_for_tx(10)) + { + printf1(TAG_NFC, "RATS TX timeout.\r\n"); + ams_write_command(AMS_CMD_DEFAULT); + return 1; + } return 0; From a1eedc004893e94ec5a81ad82f50bfc5f7a35041 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Sat, 6 Jul 2019 13:09:19 +0300 Subject: [PATCH 036/139] small fix --- targets/stm32l432/src/nfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 609b104..a223378 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -244,7 +244,7 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) break; } - uint8_t rblock_offset = p14443_block_offset(res[0]); + uint8_t rblock_offset = p14443_block_offset(recbuf[0]); if (reclen != rblock_offset) { printf1(TAG_NFC, "R block length error. len: %d. %d/%d \r\n", reclen, sendlen, len); From 15fc39faede6fb3c20bbf18fe1a86939bd251212 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 8 Jul 2019 17:58:57 +0300 Subject: [PATCH 037/139] added text how to obtain source code --- docs/solo/building.md | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/solo/building.md b/docs/solo/building.md index 0947743..76890cf 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -1,9 +1,11 @@ +# Building solo + To build, develop and debug the firmware for the STM32L432. This will work for Solo Hacker, the Nucleo development board, or your own homemade Solo. There exists a development board [NUCLEO-L432KC](https://www.st.com/en/evaluation-tools/nucleo-l432kc.html) you can use; The board does contain a debugger, so all you need is a USB cable (and some [udev](/udev) [rules](https://rust-embedded.github.io/book/intro/install/linux.html#udev-rules)). -# Prerequisites +## Prerequisites Install the [latest ARM compiler toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) for your system. We recommend getting the latest compilers from ARM. @@ -14,11 +16,25 @@ Install `solo-python` usually with `pip3 install solo-python`. The `solo` python To program your build, you'll need one of the following programs. -- [openocd](http://openocd.org) -- [stlink](https://github.com/texane/stlink) -- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html) +- [openocd](http://openocd.org) +- [stlink](https://github.com/texane/stlink) +- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html) -# Compilation +## Obtain source code and solo tool + +Source code can be downloaded from: + +- [github releases list](https://github.com/solokeys/solo/releases) +- [github repository](https://github.com/solokeys/solo) + +**solo** tool can be downloaded from: + +- from python programs [repository](https://pypi.org/project/solo-python/) `pip install solo-python` +- from installing prerequisites `pip3 install -r tools/requirements.txt` +- github repository: [repository](https://github.com/solokeys/solo-python) +- installation python enviroment witn command `make venv` from root directory of source code + +## Compilation Enter the `stm32l4xx` target directory. @@ -82,8 +98,7 @@ make build-release-locked Programming `all.hex` will cause the device to permanently lock itself. - -# Programming +## Programming It's recommended to test a debug/hacker build first to make sure Solo is working as expected. Then you can switch to a locked down build, which cannot be reprogrammed as easily (or not at all!). @@ -97,7 +112,7 @@ pip3 install -r tools/requirements.txt If you're on Windows, you must also install [libusb](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/). -## Pre-programmed Solo Hacker +### Pre-programmed Solo Hacker If your Solo device is already programmed (it flashes green when powered), we recommend programming it using the Solo bootloader. @@ -120,7 +135,7 @@ If something bad happens, you can always boot the Solo bootloader by doing the f If you hold the button for an additional 5 seconds, it will boot to the ST DFU (device firmware update). Don't use the ST DFU unless you know what you're doing. -## ST USB DFU +### ST USB DFU If your Solo has never been programmed, it will boot the ST USB DFU. The LED is turned off and it enumerates as "STM BOOTLOADER". @@ -138,7 +153,7 @@ Make sure to program `all.hex`, as this contains both the bootloader and the Sol If all goes well, you should see a slow-flashing green light. -## Solo Hacker vs Solo +### Solo Hacker vs Solo A Solo hacker device doesn't need to be in bootloader mode to be programmed, it will automatically switch. @@ -146,7 +161,7 @@ Solo (locked) needs the button to be held down when plugged in to boot to the bo A locked Solo will only accept signed updates. -## Signed updates +### Signed updates If this is not a device with a hacker build, you can only program signed updates. @@ -164,7 +179,7 @@ solo sign /path/to/signing-key.pem /path/to/solo.hex /output-path/to/firmware.js If your Solo isn't locked, you can always reprogram it using a debugger connected directly to the token. -# Permanently locking the device +## Permanently locking the device If you plan to be using your Solo for real, you should lock it permanently. This prevents someone from connecting a debugger to your token and stealing credentials. From 645ca6a5a0f726d3a2437ae39902f3449a434d47 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 8 Jul 2019 18:12:28 +0300 Subject: [PATCH 038/139] add 3-space list --- docs/solo/building.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/solo/building.md b/docs/solo/building.md index 76890cf..ee7a335 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -16,23 +16,23 @@ Install `solo-python` usually with `pip3 install solo-python`. The `solo` python To program your build, you'll need one of the following programs. -- [openocd](http://openocd.org) -- [stlink](https://github.com/texane/stlink) -- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html) +- [openocd](http://openocd.org) +- [stlink](https://github.com/texane/stlink) +- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html) ## Obtain source code and solo tool Source code can be downloaded from: -- [github releases list](https://github.com/solokeys/solo/releases) -- [github repository](https://github.com/solokeys/solo) +- [github releases list](https://github.com/solokeys/solo/releases) +- [github repository](https://github.com/solokeys/solo) **solo** tool can be downloaded from: -- from python programs [repository](https://pypi.org/project/solo-python/) `pip install solo-python` -- from installing prerequisites `pip3 install -r tools/requirements.txt` -- github repository: [repository](https://github.com/solokeys/solo-python) -- installation python enviroment witn command `make venv` from root directory of source code +- from python programs [repository](https://pypi.org/project/solo-python/) `pip install solo-python` +- from installing prerequisites `pip3 install -r tools/requirements.txt` +- github repository: [repository](https://github.com/solokeys/solo-python) +- installation python enviroment witn command `make venv` from root directory of source code ## Compilation From 9e95b0075c8d2884aab2c356a3b3d8dad10723c3 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 8 Jul 2019 21:54:36 -0400 Subject: [PATCH 039/139] default no serial printing --- targets/stm32l432/src/app.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/stm32l432/src/app.h b/targets/stm32l432/src/app.h index 3bcfac2..308e5f6 100644 --- a/targets/stm32l432/src/app.h +++ b/targets/stm32l432/src/app.h @@ -31,7 +31,7 @@ // #define DISABLE_CTAPHID_WINK // #define DISABLE_CTAPHID_CBOR -#define ENABLE_SERIAL_PRINTING +// #define ENABLE_SERIAL_PRINTING #if defined(SOLO_HACKER) #define SOLO_PRODUCT_NAME "Solo Hacker " SOLO_VERSION From 10bf4242e1e52dadf3f8ee8aa34f978897a95fa1 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 8 Jul 2019 21:54:48 -0400 Subject: [PATCH 040/139] fail with more import related info --- tools/testing/tests/tester.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index ab7c279..a99dda5 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -73,14 +73,12 @@ class Tester: dev = next(CtapHidDevice.list_devices(), None) if not dev: - try: - from fido2.pcsc import CtapPcscDevice + from fido2.pcsc import CtapPcscDevice + + print("--- NFC ---") + print(list(CtapPcscDevice.list_devices())) + dev = next(CtapPcscDevice.list_devices(), None) - print("--- NFC ---") - print(list(CtapPcscDevice.list_devices())) - dev = next(CtapPcscDevice.list_devices(), None) - except (ModuleNotFoundError, ImportError): - print("One of NFC library is not installed properly.") if not dev: raise RuntimeError("No FIDO device found") self.dev = dev From 303c42901a24e3e935818bca9720f5f0f012ebd7 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 15 Jul 2019 11:32:02 -0400 Subject: [PATCH 041/139] limit length of wLength --- targets/stm32l432/lib/usbd/usbd_hid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/targets/stm32l432/lib/usbd/usbd_hid.c b/targets/stm32l432/lib/usbd/usbd_hid.c index bf32911..c96436b 100644 --- a/targets/stm32l432/lib/usbd/usbd_hid.c +++ b/targets/stm32l432/lib/usbd/usbd_hid.c @@ -342,6 +342,7 @@ static uint8_t USBD_HID_Setup (USBD_HandleTypeDef *pdev, uint8_t *pbuf = NULL; uint16_t status_info = 0U; USBD_StatusTypeDef ret = USBD_OK; + req->wLength = req->wLength & 0x7f; switch (req->bmRequest & USB_REQ_TYPE_MASK) { @@ -386,6 +387,7 @@ static uint8_t USBD_HID_Setup (USBD_HandleTypeDef *pdev, break; case USB_REQ_GET_DESCRIPTOR: + req->wLength = req->wLength & 0x7f; if(req->wValue >> 8 == HID_REPORT_DESC) { len = MIN(HID_FIDO_REPORT_DESC_SIZE , req->wLength); From 235785b2257d620c8f5672df7e6163b16dee19e3 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 17 Jul 2019 23:42:56 +0200 Subject: [PATCH 042/139] Bump stable version to 2.4.0 --- STABLE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STABLE_VERSION b/STABLE_VERSION index 276cbf9..197c4d5 100644 --- a/STABLE_VERSION +++ b/STABLE_VERSION @@ -1 +1 @@ -2.3.0 +2.4.0 From f5d50e001d6b058ae095411412610bb9383215c0 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 26 Jul 2019 19:00:07 -0400 Subject: [PATCH 043/139] test assertions work post reboot --- tools/testing/tests/fido2.py | 34 ++++++++++++++++++++++++++++++++++ tools/testing/tests/u2f.py | 10 ++++++++++ 2 files changed, 44 insertions(+) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 3b5b557..7bacaed 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -743,6 +743,40 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.SUCCESS, ) + with Test("Check assertion is correct"): + credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) + prev_auth.verify(cdh, credential_data.public_key) + assert ( + prev_auth.credential["id"] + == prev_reg.auth_data.credential_data.credential_id + ) + + self.reboot() + + prev_auth = self.testGA( + "Send GA request after reboot, expect success", + rp["id"], + cdh, + allow_list, + expectedError=CtapError.ERR.SUCCESS, + ) + + with Test("Check assertion is correct"): + credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) + prev_auth.verify(cdh, credential_data.public_key) + assert ( + prev_auth.credential["id"] + == prev_reg.auth_data.credential_data.credential_id + ) + + prev_auth = self.testGA( + "Send GA request, expect success", + rp["id"], + cdh, + allow_list, + expectedError=CtapError.ERR.SUCCESS, + ) + with Test("Test auth_data is 37 bytes"): assert len(prev_auth.auth_data) == 37 diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py index 367ee4a..96d7fd8 100644 --- a/tools/testing/tests/u2f.py +++ b/tools/testing/tests/u2f.py @@ -78,6 +78,16 @@ class U2FTests(Tester): auth = self.authenticate(chal, appid, regs[i].key_handle) auth.verify(appid, chal, regs[i].public_key) + self.reboot() + + for i in range(0, self.user_count): + with Test( + "Post reboot, Checking previous registration %d/%d" + % (i + 1, self.user_count) + ): + auth = self.authenticate(chal, appid, regs[i].key_handle) + auth.verify(appid, chal, regs[i].public_key) + print("Check that all previous credentials are registered...") for i in range(0, self.user_count): with Test("Check that previous credential %d is registered" % i): From df2cff23501b9c0657dd5ecf89f02c6635795e14 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 26 Jul 2019 23:49:55 -0400 Subject: [PATCH 044/139] patch hmac final to use correct key --- fido2/crypto.h | 1 + targets/stm32l432/src/crypto.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/fido2/crypto.h b/fido2/crypto.h index 4fc54a2..e9e4433 100644 --- a/fido2/crypto.h +++ b/fido2/crypto.h @@ -38,6 +38,7 @@ void generate_private_key(uint8_t * data, int len, uint8_t * data2, int len2, ui void crypto_ecc256_make_key_pair(uint8_t * pubkey, uint8_t * privkey); void crypto_ecc256_shared_secret(const uint8_t * pubkey, const uint8_t * privkey, uint8_t * shared_secret); +#define CRYPTO_TRANSPORT_KEY2 ((uint8_t*)2) #define CRYPTO_TRANSPORT_KEY ((uint8_t*)1) #define CRYPTO_MASTER_KEY ((uint8_t*)0) diff --git a/targets/stm32l432/src/crypto.c b/targets/stm32l432/src/crypto.c index 31812d4..33fef68 100644 --- a/targets/stm32l432/src/crypto.c +++ b/targets/stm32l432/src/crypto.c @@ -157,6 +157,11 @@ void crypto_sha256_hmac_final(uint8_t * key, uint32_t klen, uint8_t * hmac) key = master_secret; klen = sizeof(master_secret)/2; } + else if (key == CRYPTO_TRANSPORT_KEY2) + { + key = transport_secret; + klen = 32; + } if(klen > 64) From 1cd1b3c29553ecf9f06045acb43595ee17acdd9b Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 26 Jul 2019 23:50:23 -0400 Subject: [PATCH 045/139] check attestation signature on all MC requests --- tools/testing/tests/tester.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index a99dda5..129022c 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -2,6 +2,7 @@ import time, struct from fido2.hid import CtapHidDevice from fido2.client import Fido2Client +from fido2.attestation import Attestation from fido2.ctap1 import CTAP1 from fido2.utils import Timeout @@ -201,7 +202,19 @@ class Tester: self.ctap.reset() def testMC(self, test, *args, **kwargs): - return self.testFunc(self.ctap.make_credential, test, *args, **kwargs) + attestation_object = self.testFunc( + self.ctap.make_credential, test, *args, **kwargs + ) + if attestation_object: + print(attestation_object) + verifier = Attestation.for_type(attestation_object.fmt) + client_data = args[0] + verifier().verify( + attestation_object.att_statement, + attestation_object.auth_data, + client_data, + ) + return attestation_object def testGA(self, test, *args, **kwargs): return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs) From ca66b6e43b3ee9378522a4cf668f62c8bef4b507 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 26 Jul 2019 23:51:39 -0400 Subject: [PATCH 046/139] verify signature for hmac-secret --- tools/testing/tests/fido2.py | 6 +++++- tools/testing/tests/tester.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 7bacaed..339826a 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -211,7 +211,7 @@ class FIDO2Tests(Tester): assert "hmac-secret" in reg.auth_data.extensions assert reg.auth_data.extensions["hmac-secret"] == True - reg = self.testMC( + self.testMC( "Send MC with fake extension set to true, expect SUCCESS", cdh, rp, @@ -278,6 +278,10 @@ class FIDO2Tests(Tester): assert shannon_entropy(ext["hmac-secret"]) > 5.4 assert shannon_entropy(key) > 5.4 + with Test("Check that the assertion is valid"): + credential_data = AttestedCredentialData(reg.auth_data.credential_data) + auth.verify(cdh, credential_data.public_key) + salt_enc, salt_auth = get_salt_params((salt3,)) auth = self.testGA( diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 129022c..764bcca 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -206,7 +206,6 @@ class Tester: self.ctap.make_credential, test, *args, **kwargs ) if attestation_object: - print(attestation_object) verifier = Attestation.for_type(attestation_object.fmt) client_data = args[0] verifier().verify( From f17faca689904c91fb42165264f927b01adc6a49 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 26 Jul 2019 23:53:20 -0400 Subject: [PATCH 047/139] use correct size for auth_data for signature --- fido2/ctap.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 5f604ac..5a69037 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -355,9 +355,9 @@ static int ctap_make_extensions(CTAP_extensions * ext, uint8_t * ext_encoder_buf } // Generate credRandom - crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY, 0, credRandom); + crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY2, 0, credRandom); crypto_sha256_update((uint8_t*)&ext->hmac_secret.credential->id, sizeof(CredentialId)); - crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY, 0, credRandom); + crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY2, 0, credRandom); // Decrypt saltEnc crypto_aes256_init(shared_secret, NULL); @@ -605,7 +605,6 @@ int ctap_calculate_signature(uint8_t * data, int datalen, uint8_t * clientDataHa crypto_sha256_final(hashbuf); crypto_ecc256_sign(hashbuf, 32, sigbuf); - return ctap_encode_der_sig(sigbuf,sigder); } @@ -1056,7 +1055,7 @@ uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cr else #endif { - sigder_sz = ctap_calculate_signature(auth_data_buf, sizeof(CTAP_authDataHeader), clientDataHash, auth_data_buf, sigbuf, sigder); + sigder_sz = ctap_calculate_signature(auth_data_buf, auth_data_buf_sz, clientDataHash, auth_data_buf, sigbuf, sigder); } { From b47854c3355345dc174af042fe395f269e5ea267 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 28 Jul 2019 21:41:11 -0400 Subject: [PATCH 048/139] use error code PIN_AUTH_INVALID --- fido2/ctap.c | 4 ++-- tools/testing/tests/fido2.py | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 5a69037..7413c17 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -704,7 +704,7 @@ uint8_t ctap_make_credential(CborEncoder * encoder, uint8_t * request, int lengt { return CTAP2_ERR_OPERATION_DENIED; } - return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_INVALID : CTAP2_ERR_PIN_NOT_SET; + return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_AUTH_INVALID : CTAP2_ERR_PIN_NOT_SET; } if ((MC.paramsParsed & MC_requiredMask) != MC_requiredMask) { @@ -1140,7 +1140,7 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) { return CTAP2_ERR_OPERATION_DENIED; } - return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_INVALID : CTAP2_ERR_PIN_NOT_SET; + return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_AUTH_INVALID : CTAP2_ERR_PIN_NOT_SET; } if (GA.pinAuthPresent) { diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 339826a..dc76df3 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -1134,7 +1134,10 @@ class FIDO2Tests(Tester): rp["id"], cdh, other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_NOT_SET, + expectedError=[ + CtapError.ERR.PIN_AUTH_INVALID, + CtapError.ERR.NO_CREDENTIALS, + ], ) with Test("Setting pin code, expect SUCCESS"): @@ -1148,14 +1151,17 @@ class FIDO2Tests(Tester): user, key_params, other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_INVALID, + expectedError=CtapError.ERR.PIN_AUTH_INVALID, ) self.testGA( "Send MC request with new pin auth", rp["id"], cdh, other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_INVALID, + expectedError=[ + CtapError.ERR.PIN_AUTH_INVALID, + CtapError.ERR.NO_CREDENTIALS, + ], ) self.testReset() @@ -1311,13 +1317,13 @@ class FIDO2Tests(Tester): self.testReset() - self.test_get_info() - - self.test_get_assertion() - - self.test_make_credential() - - self.test_rk(None) + # self.test_get_info() + # + # self.test_get_assertion() + # + # self.test_make_credential() + # + # self.test_rk(None) self.test_client_pin() From 78e3b291c28cdf789231f5656180a86e9ff18528 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 28 Jul 2019 22:10:56 -0400 Subject: [PATCH 049/139] make sure device status is set in all user presence tests --- fido2/ctap.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 7413c17..28a62fc 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -432,6 +432,12 @@ static unsigned int get_credential_id_size(CTAP_credentialDescriptor * cred) return sizeof(CredentialId); } +static int ctap2_user_presence_test() +{ + device_set_status(CTAPHID_STATUS_UPNEEDED); + return ctap_user_presence_test(CTAP2_UP_DELAY_MS); +} + static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * auth_data_buf, uint32_t * len, CTAP_credInfo * credInfo) { CborEncoder cose_key; @@ -459,11 +465,9 @@ static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * au count = auth_data_update_count(&authData->head); - device_set_status(CTAPHID_STATUS_UPNEEDED); - int but; - but = ctap_user_presence_test(CTAP2_UP_DELAY_MS); + but = ctap2_user_presence_test(CTAP2_UP_DELAY_MS); if (!but) { @@ -473,7 +477,7 @@ static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * au { return CTAP2_ERR_KEEPALIVE_CANCEL; } - device_set_status(CTAPHID_STATUS_PROCESSING); + // device_set_status(CTAPHID_STATUS_PROCESSING); authData->head.flags = (but << 0); authData->head.flags |= (ctap_is_pin_set() << 2); @@ -700,7 +704,7 @@ uint8_t ctap_make_credential(CborEncoder * encoder, uint8_t * request, int lengt } if (MC.pinAuthEmpty) { - if (!ctap_user_presence_test(CTAP2_UP_DELAY_MS)) + if (!ctap2_user_presence_test(CTAP2_UP_DELAY_MS)) { return CTAP2_ERR_OPERATION_DENIED; } @@ -1136,7 +1140,7 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) if (GA.pinAuthEmpty) { - if (!ctap_user_presence_test(CTAP2_UP_DELAY_MS)) + if (!ctap2_user_presence_test(CTAP2_UP_DELAY_MS)) { return CTAP2_ERR_OPERATION_DENIED; } @@ -1646,7 +1650,7 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) break; case CTAP_RESET: printf1(TAG_CTAP,"CTAP_RESET\n"); - if (ctap_user_presence_test(CTAP2_UP_DELAY_MS)) + if (ctap2_user_presence_test(CTAP2_UP_DELAY_MS)) { ctap_reset(); } From 690d7c716ab20f8d461a11a42b00ad624fc9bb64 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 29 Jul 2019 12:39:59 -0400 Subject: [PATCH 050/139] move CTAPHID_STATUS_PROCESSING to after UP --- fido2/ctap.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 28a62fc..dea0f3f 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -477,7 +477,8 @@ static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * au { return CTAP2_ERR_KEEPALIVE_CANCEL; } - // device_set_status(CTAPHID_STATUS_PROCESSING); + + device_set_status(CTAPHID_STATUS_PROCESSING); authData->head.flags = (but << 0); authData->head.flags |= (ctap_is_pin_set() << 2); @@ -1607,7 +1608,6 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) switch(cmd) { case CTAP_MAKE_CREDENTIAL: - device_set_status(CTAPHID_STATUS_PROCESSING); printf1(TAG_CTAP,"CTAP_MAKE_CREDENTIAL\n"); timestamp(); status = ctap_make_credential(&encoder, pkt_raw, length); @@ -1618,7 +1618,6 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) break; case CTAP_GET_ASSERTION: - device_set_status(CTAPHID_STATUS_PROCESSING); printf1(TAG_CTAP,"CTAP_GET_ASSERTION\n"); timestamp(); status = ctap_get_assertion(&encoder, pkt_raw, length); From 17a170bb907013ea9222192a68e057c31c3aad28 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 29 Jul 2019 14:58:30 -0400 Subject: [PATCH 051/139] Update STABLE_VERSION --- STABLE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STABLE_VERSION b/STABLE_VERSION index 197c4d5..8e8299d 100644 --- a/STABLE_VERSION +++ b/STABLE_VERSION @@ -1 +1 @@ -2.4.0 +2.4.2 From 420d052ac9c930d80e8e6266283416f5315ee64a Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 8 Jan 2019 20:07:50 +0100 Subject: [PATCH 052/139] Describe running Solo on the Nucleo32 board Signed-off-by: Szczepan Zalega --- docs/solo/nucleo32-board.md | 204 ++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 205 insertions(+) create mode 100644 docs/solo/nucleo32-board.md diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md new file mode 100644 index 0000000..ab7d91d --- /dev/null +++ b/docs/solo/nucleo32-board.md @@ -0,0 +1,204 @@ +# Nucleo32 board preparation +Additional steps are required to run the firmware on the Nucleo32 board. + +## USB-A cable +Board does not provide an USB cable / socket for the target MCU communication. +Own provided USB plug has to be connected in the following way: + + +| PIN / Arduino PIN | MCU leg | USB wire color | Signal | +|---|---|---|---| +| D10 / PA11 | 21 | white | D-| +| D2 / PA12 | 22 | green | D+ | +| GND (near D2) | ------- | black | GND | +| **not connected** | ------- | red | 5V | + +Each USB plug pin should be connected via the wire in a color defined by the standard. It might be confirmed with a +multimeter for additional safety. USB plug description: + +| PIN | USB wire color | Signal | +|---|---|---| +| 4 | black | GND | +| 3 | green | D+ | +| 2 | white | D-| +| 1 | red | 5V | + +See this [USB plug] image, and Wikipedia's [USB plug description]. + +Plug in [USB-A_schematic.pdf] has wrong wire order, registered as [solo-hw#1]. + +[solo-hw#1]: https://github.com/solokeys/solo-hw/issues/1 +[USB plug]: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/USB.svg/1200px-USB.svg.png +[USB plug description]: https://en.wikipedia.org/wiki/USB#Receptacle_(socket)_identification + +The power is taken from the debugger / board (unless the board is configured in another way). +Make sure 5V is not connected, and is covered from contacting with the board elements. + +Based on [USB-A_schematic.pdf]. + +## Firmware modification +Following patch has to be applied to skip the user presence confirmation, for tests. Might be applied at a later stage. +```text +diff --git a/targets/stm32l432/src/app.h b/targets/stm32l432/src/app.h +index c14a7ed..c89c3b5 100644 +--- a/targets/stm32l432/src/app.h ++++ b/targets/stm32l432/src/app.h +@@ -71,6 +71,6 @@ void hw_init(void); + #define SOLO_BUTTON_PIN LL_GPIO_PIN_0 + + #define SKIP_BUTTON_CHECK_WITH_DELAY 0 +-#define SKIP_BUTTON_CHECK_FAST 0 ++#define SKIP_BUTTON_CHECK_FAST 1 + + #endif +``` +It is possible to provide a button and connect it to the MCU pins, as instructed in [USB-A_schematic.pdf]: +```text +PA0 / pin 6 --> button --> GND +``` +In that case the mentioned patch would not be required. + +[USB-A_schematic.pdf]: https://github.com/solokeys/solo-hw/releases/download/1.2/USB-A_schematic.pdf + + +# Development environment setup +Environment: Fedora 29 x64, Linux 4.19.9 + +See https://docs.solokeys.io/solo/building/ for the original guide. Here details not included there will be covered. + +## Install ARM tools +1. Download current [ARM tools] package: [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]. +2. Extract the archive. +3. Add full path to the `./bin` directory as first entry to the `$PATH` variable, +as in `~/gcc-arm/gcc-arm-none-eabi-8-2018-q4-major/bin/:$PATH`. + +[ARM tools]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads +[gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]: https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2?revision=d830f9dd-cd4f-406d-8672-cca9210dd220?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2018-q4-major +## Install flashing software +ST provides a CLI flashing tool - `STM32_Programmer_CLI`. It can be downloaded directly from the vendor's site: +1. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), +go to bottom page and from STM32CubeProg row select Download button. +2. Unzip contents of the archive. +3. Run *Linux setup +4. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located +5. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. + +# Building and flashing +## Building +Please follow https://docs.solokeys.io/solo/building/, as the build way changes rapidly. +Currently (8.1.19) to build the firmware, following lines should be executed +```bash +# while in the main project directory +cd targets/stm32l432 +make cbor +make build-hacker DEBUG=1 +``` + +Note: `DEBUG=2` stops the device initialization, until a serial client will be attached to its virtual port. +Do not use it, if you do not plan to do so. + +## Flashing via the Makefile command +```bash +# while in the main project directory +# create Python virtual environment with required packages, and activate +make env3 +. env3/bin/activate +# Run flashing +cd ./targets/stm32l442 +make flash + # which runs: + # flash: solo.hex bootloader.hex + # python merge_hex.py solo.hex bootloader.hex all.hex (intelhex library required) + # STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect + # STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst +``` + + +# Testing + +## Internal +Project-provided tests. +### Simulated device +A simulated device is provided to test the HID layer. +#### Build +```bash +make clean +cd tinycbor +make +cd .. +make env2 +``` + +#### Execution +```bash +# run simulated device (will create a network UDP server) +./main +# run test 1 +./env2/bin/python tools/ctap_test.py +# run test 2 (or other files in the examples directory) +./env2/bin/python python-fido2/examples/credential.py +``` + +### Real device +```bash +# while in the main project directory +# not passing as of 8.1.19, due to test solution issues +make fido2-test +``` + +## External + +### FIDO2 test sites +1. https://webauthn.bin.coffee/ +2. https://github.com/apowers313/fido2-server-demo/ +3. https://webauthn.org/ + +### U2F test sites +1. https://u2f.bin.coffee/ +2. https://demo.yubico.com/u2f + +### FIDO2 standalone clients +1. https://github.com/Nitrokey/u2f-ref-code +2. https://github.com/Yubico/libfido2 +3. https://github.com/Yubico/python-fido2 +4. https://github.com/google/pyu2f + + +# USB serial console reading +Device opens an USB-emulated serial port to output its messages. While Nucleo board offers such already, +the Solo device provides its own. +- Provided Python tool +```bash +python3 ../../tools/solotool.py monitor /dev/solokey-serial +``` +- External application +```bash +sudo picocom -b 115200 /dev/solokey-serial +``` + +where `/dev/solokey-serial` is an udev symlink to `/dev/ttyACM1`. + +# Other + +## Dumping firmware + +Size is calculated using bash arithmetic. +```bash +STM32_Programmer_CLI -c port=SWD -halt -u 0x0 $((256*1024)) current.hex +``` + +## Software reset + +```bash +STM32_Programmer_CLI -c port=SWD -rst +``` + +## Installing required Python packages +Client script requires some Python packages, which could be easily installed locally to the project +via the Makefile command. It is sufficient to run: +```bash +make env3 +``` + + + diff --git a/mkdocs.yml b/mkdocs.yml index 2d69cbc..3e289d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,7 @@ nav: - Home: solo/index.md - FIDO2 Implementation: solo/fido2-impl.md - Build instructions: solo/building.md + - Running on Nucleo32 board: solo/nucleo32-board.md - Signed update process: solo/signed-updates.md - Code documentation: solo/code-overview.md - Contributing Code: solo/contributing.md From be37ed46f7bcf080744467e6a526cad031cd2fef Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 8 Jan 2019 20:33:35 +0100 Subject: [PATCH 053/139] Add instruction for manual flashing of the Nucleo board Signed-off-by: Szczepan Zalega --- docs/solo/nucleo32-board.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index ab7d91d..cdbafb3 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -113,6 +113,12 @@ make flash # STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst ``` +## Manual flashing +In case you already have a firmware to flash (named `all.hex`), please run the following: +```bash +STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect +STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst +``` # Testing From 09d450ed02f213fac86b08532aa3a7d6889eb55a Mon Sep 17 00:00:00 2001 From: Wessel dR Date: Sun, 4 Aug 2019 23:44:37 +0200 Subject: [PATCH 054/139] Little typo Fixes https://github.com/Nitrokey/nitrokey-fido2-firmware/pull/19 --- docs/solo/nucleo32-board.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index cdbafb3..1e26ba5 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -104,7 +104,7 @@ Do not use it, if you do not plan to do so. make env3 . env3/bin/activate # Run flashing -cd ./targets/stm32l442 +cd ./targets/stm32l432 make flash # which runs: # flash: solo.hex bootloader.hex @@ -207,4 +207,3 @@ make env3 ``` - From db479850a6da83af2b28d921a2731387c246aa53 Mon Sep 17 00:00:00 2001 From: Wessel dR Date: Mon, 5 Aug 2019 10:05:04 +0200 Subject: [PATCH 055/139] OsX path for STM32_Programmer_CLI Added how to add the STM32_Programmer_CLI to your OsX path Fixes https://github.com/Nitrokey/nitrokey-fido2-firmware/pull/20 --- docs/solo/nucleo32-board.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index 1e26ba5..25d103e 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -83,6 +83,12 @@ go to bottom page and from STM32CubeProg row select Download button. 4. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located 5. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. +If you're on OsX and installed the STM32CubeProg, you need to add the following to your path: +```bash +# ~/.bash_profile +export PATH="/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin/":$PATH +``` + # Building and flashing ## Building Please follow https://docs.solokeys.io/solo/building/, as the build way changes rapidly. @@ -206,4 +212,3 @@ via the Makefile command. It is sufficient to run: make env3 ``` - From a8c7c43e14d3efb06e601c8ea856391a866adcc6 Mon Sep 17 00:00:00 2001 From: Wessel dR Date: Tue, 6 Aug 2019 12:40:05 +0200 Subject: [PATCH 056/139] Cleaned nucleo32-board.md The original pull request #65 was not completely compliant. hopefully this one is. --- docs/solo/nucleo32-board.md | 249 ++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/solo/nucleo32-board.md diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md new file mode 100644 index 0000000..7ca6202 --- /dev/null +++ b/docs/solo/nucleo32-board.md @@ -0,0 +1,249 @@ +# Nucleo32 board preparation + +Additional steps are required to run the firmware on the Nucleo32 board. + +## USB-A cable + +Board does not provide an USB cable / socket for the target MCU communication. +Own provided USB plug has to be connected in the following way: + +| PIN / Arduino PIN | MCU leg | USB wire color | Signal | +| ----------------- | ------- | -------------- | ------ | +| D10 / PA11 | 21 | white | D- | +| D2 / PA12 | 22 | green | D+ | +| GND (near D2) | ------- | black | GND | +| **not connected** | ------- | red | 5V | + +Each USB plug pin should be connected via the wire in a color defined by the standard. It might be confirmed with a +multimeter for additional safety. USB plug description: + +| PIN | USB wire color | Signal | +| --- | -------------- | ------ | +| 4 | black | GND | +| 3 | green | D+ | +| 2 | white | D- | +| 1 | red | 5V | + +See this [USB plug] image, and Wikipedia's [USB plug description]. + +Plug in [USB-A_schematic.pdf] has wrong wire order, registered as [solo-hw#1]. + +[solo-hw#1]: https://github.com/solokeys/solo-hw/issues/1 + +[usb plug]: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/USB.svg/1200px-USB.svg.png + +[usb plug description]: https://en.wikipedia.org/wiki/USB#Receptacle_(socket)_identification + +The power is taken from the debugger / board (unless the board is configured in another way). +Make sure 5V is not connected, and is covered from contacting with the board elements. + +Based on [USB-A_schematic.pdf]. + +## Firmware modification + +Following patch has to be applied to skip the user presence confirmation, for tests. Might be applied at a later stage. + +```text +diff --git a/targets/stm32l432/src/app.h b/targets/stm32l432/src/app.h +index c14a7ed..c89c3b5 100644 +--- a/targets/stm32l432/src/app.h ++++ b/targets/stm32l432/src/app.h +@@ -71,6 +71,6 @@ void hw_init(void); + #define SOLO_BUTTON_PIN LL_GPIO_PIN_0 + + #define SKIP_BUTTON_CHECK_WITH_DELAY 0 +-#define SKIP_BUTTON_CHECK_FAST 0 ++#define SKIP_BUTTON_CHECK_FAST 1 + + #endif +``` + +It is possible to provide a button and connect it to the MCU pins, as instructed in [USB-A_schematic.pdf]: + +```text +PA0 / pin 6 --> button --> GND +``` + +In that case the mentioned patch would not be required. + +[usb-a_schematic.pdf]: https://github.com/solokeys/solo-hw/releases/download/1.2/USB-A_schematic.pdf + +# Development environment setup + +Environment: Fedora 29 x64, Linux 4.19.9 + +See for the original guide. Here details not included there will be covered. + +## Install ARM tools + +1. Download current [ARM tools] package: [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]. +2. Extract the archive. +3. Add full path to the `./bin` directory as first entry to the `$PATH` variable, + as in `~/gcc-arm/gcc-arm-none-eabi-8-2018-q4-major/bin/:$PATH`. + +[arm tools]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads + +[gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]: https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2?revision=d830f9dd-cd4f-406d-8672-cca9210dd220?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2018-q4-major + +## Install flashing software + +ST provides a CLI flashing tool - `STM32_Programmer_CLI`. It can be downloaded directly from the vendor's site: +1\. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), +go to bottom page and from STM32CubeProg row select Download button. +2\. Unzip contents of the archive. +3\. Run \*Linux setup +4\. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located +5\. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. + +If you're on OsX and installed the STM32CubeProg, you need to add the following to your path: + +```bash +# ~/.bash_profile +export PATH="/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin/":$PATH +``` + +# Building and flashing + +## Building + +Please follow , as the build way changes rapidly. +Currently (8.1.19) to build the firmware, following lines should be executed + +```bash +# while in the main project directory +cd targets/stm32l432 +make cbor +make build-hacker DEBUG=1 +``` + +Note: `DEBUG=2` stops the device initialization, until a serial client will be attached to its virtual port. +Do not use it, if you do not plan to do so. + +## Flashing via the Makefile command + +```bash +# while in the main project directory +# create Python virtual environment with required packages, and activate +make env3 +. env3/bin/activate +# Run flashing +cd ./targets/stm32l432 +make flash + # which runs: + # flash: solo.hex bootloader.hex + # python merge_hex.py solo.hex bootloader.hex all.hex (intelhex library required) + # STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect + # STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst +``` + +## Manual flashing + +In case you already have a firmware to flash (named `all.hex`), please run the following: + +```bash +STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect +STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst +``` + +# Testing + +## Internal + +Project-provided tests. + +### Simulated device + +A simulated device is provided to test the HID layer. + +#### Build + +```bash +make clean +cd tinycbor +make +cd .. +make env2 +``` + +#### Execution + +```bash +# run simulated device (will create a network UDP server) +./main +# run test 1 +./env2/bin/python tools/ctap_test.py +# run test 2 (or other files in the examples directory) +./env2/bin/python python-fido2/examples/credential.py +``` + +### Real device + +```bash +# while in the main project directory +# not passing as of 8.1.19, due to test solution issues +make fido2-test +``` + +## External + +### FIDO2 test sites + +1. +2. +3. + +### U2F test sites + +1. +2. + +### FIDO2 standalone clients + +1. +2. +3. +4. + +# USB serial console reading + +Device opens an USB-emulated serial port to output its messages. While Nucleo board offers such already, +the Solo device provides its own. + +- Provided Python tool + +```bash +python3 ../../tools/solotool.py monitor /dev/solokey-serial +``` + +- External application + +```bash +sudo picocom -b 115200 /dev/solokey-serial +``` + +where `/dev/solokey-serial` is an udev symlink to `/dev/ttyACM1`. + +# Other + +## Dumping firmware + +Size is calculated using bash arithmetic. + +```bash +STM32_Programmer_CLI -c port=SWD -halt -u 0x0 $((256*1024)) current.hex +``` + +## Software reset + +```bash +STM32_Programmer_CLI -c port=SWD -rst +``` + +## Installing required Python packages + +Client script requires some Python packages, which could be easily installed locally to the project +via the Makefile command. It is sufficient to run: + +```bash +make env3 +``` From e2738d11d36161dd9f34688abe70b7c55d9d885e Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 6 Aug 2019 18:50:05 +0800 Subject: [PATCH 057/139] remove tests --- tools/testing/main.py | 64 -- tools/testing/tests/__init__.py | 11 - tools/testing/tests/fido2.py | 1334 ------------------------------- tools/testing/tests/hid.py | 252 ------ tools/testing/tests/solo.py | 83 -- tools/testing/tests/tester.py | 230 ------ tools/testing/tests/u2f.py | 133 --- tools/testing/tests/util.py | 12 - 8 files changed, 2119 deletions(-) delete mode 100644 tools/testing/main.py delete mode 100644 tools/testing/tests/__init__.py delete mode 100644 tools/testing/tests/fido2.py delete mode 100644 tools/testing/tests/hid.py delete mode 100644 tools/testing/tests/solo.py delete mode 100644 tools/testing/tests/tester.py delete mode 100644 tools/testing/tests/u2f.py delete mode 100644 tools/testing/tests/util.py diff --git a/tools/testing/main.py b/tools/testing/main.py deleted file mode 100644 index 437e938..0000000 --- a/tools/testing/main.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2019 SoloKeys Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. -# - -# Script for testing correctness of CTAP2/CTAP1 security token - -import sys - -from solo.fido2 import force_udp_backend -from tests import Tester, FIDO2Tests, U2FTests, HIDTests, SoloTests - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: %s [sim] [nfc] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") - print(" sim - test via UDP simulation backend only") - print(" nfc - test via NFC interface only") - sys.exit(0) - - t = Tester() - t.set_user_count(3) - - if "sim" in sys.argv: - print("Using UDP backend.") - force_udp_backend() - t.set_sim(True) - t.set_user_count(10) - - nfcOnly = False - if "nfc" in sys.argv: - nfcOnly = True - - t.find_device(nfcOnly) - - if "solo" in sys.argv: - SoloTests(t).run() - - if "u2f" in sys.argv: - U2FTests(t).run() - - if "fido2" in sys.argv: - # t.test_fido2() - FIDO2Tests(t).run() - - # hid tests are a bit invasive and should be done last - if "hid" in sys.argv: - HIDTests(t).run() - - if "bootloader" in sys.argv: - if t.is_sim: - raise RuntimeError("Cannot test bootloader in simulation yet.") - # print("Put device in bootloader mode and then hit enter") - # input() - # t.test_bootloader() - - # t.test_responses() - # t.test_fido2_brute_force() diff --git a/tools/testing/tests/__init__.py b/tools/testing/tests/__init__.py deleted file mode 100644 index 23cf5a8..0000000 --- a/tools/testing/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import fido2 -from . import hid -from . import solo -from . import u2f -from . import tester - -FIDO2Tests = fido2.FIDO2Tests -HIDTests = hid.HIDTests -U2FTests = u2f.U2FTests -SoloTests = solo.SoloTests -Tester = tester.Tester diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py deleted file mode 100644 index dc76df3..0000000 --- a/tools/testing/tests/fido2.py +++ /dev/null @@ -1,1334 +0,0 @@ -from __future__ import print_function, absolute_import, unicode_literals -import time -from random import randint -import array -from functools import cmp_to_key - -from fido2 import cbor -from fido2.ctap import CtapError - -from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData -from fido2.utils import sha256, hmac_sha256 -from fido2.attestation import Attestation - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - -from .u2f import U2FTests -from .tester import Tester, Test -from .util import shannon_entropy - -rp = {"id": "examplo.org", "name": "ExaRP"} -rp2 = {"id": "solokeys.com", "name": "ExaRP"} -user = {"id": b"usee_od", "name": "AB User"} -user1 = {"id": b"1234567890", "name": "Conor Patrick"} -user2 = {"id": b"oiewhfoi", "name": "Han Solo"} -user3 = {"id": b"23ohfpjwo@@", "name": "John Smith"} -challenge = "Y2hhbGxlbmdl" -pin_protocol = 1 -key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}] -cdh = b"123456789abcdef0123456789abcdef0" - - -def VerifyAttestation(attest, data): - verifier = Attestation.for_type(attest.fmt) - verifier().verify(attest.att_statement, attest.auth_data, data.hash) - - -def cbor_key_to_representative(key): - if isinstance(key, int): - if key >= 0: - return (0, key) - return (1, -key) - elif isinstance(key, bytes): - return (2, key) - elif isinstance(key, str): - return (3, key) - else: - raise ValueError(key) - - -def cbor_str_cmp(a, b): - if isinstance(a, str) or isinstance(b, str): - a = a.encode("utf8") - b = b.encode("utf8") - - if len(a) == len(b): - for x, y in zip(a, b): - if x != y: - return x - y - return 0 - else: - return len(a) - len(b) - - -def cmp_cbor_keys(a, b): - a = cbor_key_to_representative(a) - b = cbor_key_to_representative(b) - if a[0] != b[0]: - return a[0] - b[0] - if a[0] in (2, 3): - return cbor_str_cmp(a[1], b[1]) - else: - return (a[1] > b[1]) - (a[1] < b[1]) - - -def TestCborKeysSorted(cbor_obj): - # Cbor canonical ordering of keys. - # https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form - - if isinstance(cbor_obj, bytes): - cbor_obj = cbor.decode_from(cbor_obj)[0] - - if isinstance(cbor_obj, dict): - l = [x for x in cbor_obj] - else: - l = cbor_obj - - l_sorted = sorted(l[:], key=cmp_to_key(cmp_cbor_keys)) - - for i in range(len(l)): - - if not isinstance(l[i], (str, int)): - raise ValueError(f"Cbor map key {l[i]} must be int or str for CTAP2") - - if l[i] != l_sorted[i]: - raise ValueError(f"Cbor map item {i}: {l[i]} is out of order") - - return l - - -# hot patch cbor map parsing to test the order of keys in map -_load_map_old = cbor.load_map - - -def _load_map_new(ai, data): - values, data = _load_map_old(ai, data) - TestCborKeysSorted(values) - return values, data - - -cbor.load_map = _load_map_new -cbor._DESERIALIZERS[5] = _load_map_new - - -class FIDO2Tests(Tester): - def __init__(self, tester=None): - super().__init__(tester) - self.self_test() - - def self_test(self,): - cbor_key_list_sorted = [ - 0, - 1, - 1, - 2, - 3, - -1, - -2, - "b", - "c", - "aa", - "aaa", - "aab", - "baa", - "bbb", - ] - with Test("Self test CBOR sorting"): - TestCborKeysSorted(cbor_key_list_sorted) - - with Test("Self test CBOR sorting integers", catch=ValueError): - TestCborKeysSorted([1, 0]) - - with Test("Self test CBOR sorting major type", catch=ValueError): - TestCborKeysSorted([-1, 0]) - - with Test("Self test CBOR sorting strings", catch=ValueError): - TestCborKeysSorted(["bb", "a"]) - - with Test("Self test CBOR sorting same length strings", catch=ValueError): - TestCborKeysSorted(["ab", "aa"]) - - def run(self,): - self.test_fido2() - - def test_fido2_simple(self, pin_token=None): - creds = [] - exclude_list = [] - PIN = pin_token - - fake_id1 = array.array("B", [randint(0, 255) for i in range(0, 150)]).tobytes() - fake_id2 = array.array("B", [randint(0, 255) for i in range(0, 73)]).tobytes() - - exclude_list.append({"id": fake_id1, "type": "public-key"}) - exclude_list.append({"id": fake_id2, "type": "public-key"}) - - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=[] - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - print("Register time: %d ms" % (t2 - t1)) - - cred = attest.auth_data.credential_data - creds.append(cred) - - allow_list = [{"id": creds[0].credential_id, "type": "public-key"}] - t1 = time.time() * 1000 - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, allow_list, pin=PIN - ) - t2 = time.time() * 1000 - assertions[0].verify(client_data.hash, creds[0].public_key) - - print("Assertion time: %d ms" % (t2 - t1)) - - def test_extensions(self,): - - salt1 = b"\x5a" * 32 - salt2 = b"\x96" * 32 - salt3 = b"\x03" * 32 - - # self.testReset() - - with Test("Get info has hmac-secret"): - info = self.ctap.get_info() - assert "hmac-secret" in info.extensions - - reg = self.testMC( - "Send MC with hmac-secret ext set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"extensions": {"hmac-secret": True}, "options": {"rk": True}}, - ) - - with Test("Check 'hmac-secret' is set to true in auth_data extensions"): - assert reg.auth_data.extensions - assert "hmac-secret" in reg.auth_data.extensions - assert reg.auth_data.extensions["hmac-secret"] == True - - self.testMC( - "Send MC with fake extension set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"extensions": {"tetris": True}}, - ) - - with Test("Get shared secret"): - key_agreement, shared_secret = self.client.pin_protocol.get_shared_secret() - cipher = Cipher( - algorithms.AES(shared_secret), - modes.CBC(b"\x00" * 16), - default_backend(), - ) - - def get_salt_params(salts): - enc = cipher.encryptor() - salt_enc = b"" - for salt in salts: - salt_enc += enc.update(salt) - salt_enc += enc.finalize() - - salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] - return salt_enc, salt_auth - - for salt_list in ((salt1,), (salt1, salt2)): - salt_enc, salt_auth = get_salt_params(salt_list) - - auth = self.testGA( - "Send GA request with %d salts hmac-secret, expect success" - % len(salt_list), - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: salt_auth} - } - }, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test( - "Check that hmac-secret is in auth_data extensions and has %d bytes" - % (len(salt_list) * 32) - ): - ext = auth.auth_data.extensions - assert ext - assert "hmac-secret" in ext - assert isinstance(ext["hmac-secret"], bytes) - assert len(ext["hmac-secret"]) == len(salt_list) * 32 - - with Test("Check that shannon_entropy of hmac-secret is good"): - ext = auth.auth_data.extensions - dec = cipher.decryptor() - key = dec.update(ext["hmac-secret"]) + dec.finalize() - - print(shannon_entropy(ext["hmac-secret"])) - if len(salt_list) == 1: - assert shannon_entropy(ext["hmac-secret"]) > 4.6 - assert shannon_entropy(key) > 4.6 - if len(salt_list) == 2: - assert shannon_entropy(ext["hmac-secret"]) > 5.4 - assert shannon_entropy(key) > 5.4 - - with Test("Check that the assertion is valid"): - credential_data = AttestedCredentialData(reg.auth_data.credential_data) - auth.verify(cdh, credential_data.public_key) - - salt_enc, salt_auth = get_salt_params((salt3,)) - - auth = self.testGA( - "Send GA request with hmac-secret missing keyAgreement, expect error", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {2: salt_enc, 3: salt_auth}}}, - ) - auth = self.testGA( - "Send GA request with hmac-secret missing saltAuth, expect MISSING_PARAMETER", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {1: key_agreement, 2: salt_enc}}}, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - auth = self.testGA( - "Send GA request with hmac-secret missing saltEnc, expect MISSING_PARAMETER", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {1: key_agreement, 3: salt_auth}}}, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - bad_auth = list(salt_auth[:]) - bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1 - bad_auth = bytes(bad_auth) - - auth = self.testGA( - "Send GA request with hmac-secret containing bad saltAuth, expect EXTENSION_FIRST", - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: bad_auth} - } - }, - expectedError=CtapError.ERR.EXTENSION_FIRST, - ) - - salt4 = b"\x5a" * 16 - salt5 = b"\x96" * 64 - for salt_list in ((salt4,), (salt4, salt5)): - salt_enc, salt_auth = get_salt_params(salt_list) - - salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] - auth = self.testGA( - "Send GA request with incorrect salt length %d, expect INVALID_LENGTH" - % len(salt_enc), - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: salt_auth} - } - }, - expectedError=CtapError.ERR.INVALID_LENGTH, - ) - - def test_get_info(self,): - with Test("Get info"): - info = self.ctap.get_info() - print("data:", bytes(info)) - print("decoded:", cbor.decode_from(bytes(info))) - - with Test("Check FIDO2 string is in VERSIONS field"): - assert "FIDO_2_0" in info.versions - - with Test("Check pin protocols field"): - if len(info.pin_protocols): - assert sum(info.pin_protocols) > 0 - - with Test("Check options field"): - for x in info.options: - assert info.options[x] in [True, False] - - if "uv" in info.options: - if info.options["uv"]: - self.testMC( - "Send MC request with uv set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - other={"options": {"uv": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - if "up" in info.options: - if info.options["up"]: - self.testMC( - "Send MC request with up set to true, expect INVALID_OPTION", - cdh, - rp, - user, - key_params, - other={"options": {"up": True}}, - expectedError=CtapError.ERR.INVALID_OPTION, - ) - - def test_make_credential(self,): - - prev_reg = self.testMC( - "Send MC request, expect success", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - ) - - allow_list = [ - { - "id": prev_reg.auth_data.credential_data.credential_id, - "type": "public-key", - } - ] - with Test("Check attestation format is correct"): - assert prev_reg.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"] - - with Test("Check auth_data is at least 77 bytes"): - assert len(prev_reg.auth_data) >= 77 - - self.testMC( - "Send MC request with missing clientDataHash, expect error", - None, - rp, - user, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with integer for clientDataHash, expect error", - 5, - rp, - user, - key_params, - ) - - self.testMC( - "Send MC request with missing user, expect error", - cdh, - rp, - None, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with bytearray user, expect error", - cdh, - rp, - b"1234abcd", - key_params, - ) - - self.testMC( - "Send MC request with missing RP, expect error", - cdh, - None, - user, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with bytearray RP, expect error", - cdh, - b"1234abcd", - user, - key_params, - ) - - self.testMC( - "Send MC request with missing pubKeyCredParams, expect error", - cdh, - rp, - user, - None, - ) - - self.testMC( - "Send MC request with incorrect pubKeyCredParams, expect error", - cdh, - rp, - user, - b"2356", - ) - - self.testMC( - "Send MC request with incorrect excludeList, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": 8}, - ) - - self.testMC( - "Send MC request with incorrect extensions, expect error", - cdh, - rp, - user, - key_params, - other={"extensions": 8}, - ) - - self.testMC( - "Send MC request with incorrect options, expect error", - cdh, - rp, - user, - key_params, - other={"options": 8}, - ) - - self.testMC( - "Send MC request with bad RP.name", - cdh, - {"id": self.host, "name": 8, "icon": "icon"}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad RP.id", - cdh, - {"id": 8, "name": "name", "icon": "icon"}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad RP.icon", - cdh, - {"id": self.host, "name": "name", "icon": 8}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad user.name", - cdh, - rp, - {"id": b"usee_od", "name": 8}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.id", - cdh, - rp, - {"id": "usee_od", "name": "name"}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.displayName", - cdh, - rp, - {"id": "usee_od", "name": "name", "displayName": 8}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.icon", - cdh, - rp, - {"id": "usee_od", "name": "name", "icon": 8}, - key_params, - ) - - self.testMC( - "Send MC request with non-map pubKeyCredParams item", - cdh, - rp, - user, - ["wrong"], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item missing type field", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM}], - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bad type field", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM, "type": b"public-key"}], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item missing alg", - cdh, - rp, - user, - [{"type": "public-key"}], - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bad alg", - cdh, - rp, - user, - [{"alg": "7", "type": "public-key"}], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bogus alg, expect UNSUPPORTED_ALGORITHM", - cdh, - rp, - user, - [{"alg": 1234, "type": "public-key"}], - expectedError=CtapError.ERR.UNSUPPORTED_ALGORITHM, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bogus type, expect UNSUPPORTED_ALGORITHM", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM, "type": "rot13"}], - expectedError=CtapError.ERR.UNSUPPORTED_ALGORITHM, - ) - - self.testMC( - "Send MC request with excludeList item with bogus type, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"exclude_list": [{"id": b"1234", "type": "rot13"}]}, - ) - - self.testMC( - "Send MC request with excludeList item with bogus type, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={ - "exclude_list": [ - {"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"} - ] - }, - ) - - self.testMC( - "Send MC request with excludeList with bad item, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": ["1234"]}, - ) - - self.testMC( - "Send MC request with excludeList with item missing type field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"id": b"1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item missing id field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": "public-key"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item containing bad id field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": "public-key", "id": "1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item containing bad type field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": b"public-key", "id": b"1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList containing previous registration, expect CREDENTIAL_EXCLUDED", - cdh, - rp, - user, - key_params, - other={ - "exclude_list": [ - { - "type": "public-key", - "id": prev_reg.auth_data.credential_data.credential_id, - } - ] - }, - expectedError=CtapError.ERR.CREDENTIAL_EXCLUDED, - ) - - self.testMC( - "Send MC request with unknown option, expect SUCCESS", - cdh, - rp, - user, - key_params, - other={"options": {"unknown": False}}, - expectedError=CtapError.ERR.SUCCESS, - ) - - self.testReset() - - self.testGA( - "Send GA request with reset auth, expect NO_CREDENTIALS", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) - - def test_get_assertion(self,): - - self.testReset() - - prev_reg = self.testMC( - "Send MC request, expect success", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - ) - - allow_list = [ - { - "id": prev_reg.auth_data.credential_data.credential_id, - "type": "public-key", - } - ] - - prev_auth = self.testGA( - "Send GA request, expect success", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check assertion is correct"): - credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) - prev_auth.verify(cdh, credential_data.public_key) - assert ( - prev_auth.credential["id"] - == prev_reg.auth_data.credential_data.credential_id - ) - - self.reboot() - - prev_auth = self.testGA( - "Send GA request after reboot, expect success", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check assertion is correct"): - credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) - prev_auth.verify(cdh, credential_data.public_key) - assert ( - prev_auth.credential["id"] - == prev_reg.auth_data.credential_data.credential_id - ) - - prev_auth = self.testGA( - "Send GA request, expect success", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test auth_data is 37 bytes"): - assert len(prev_auth.auth_data) == 37 - - with Test("Test that auth_data.rpIdHash is correct"): - assert sha256(rp["id"].encode()) == prev_auth.auth_data.rp_id_hash - - with Test("Check that AT flag is not set"): - assert (prev_auth.auth_data.flags & 0xF8) == 0 - - with Test("Test that user, credential and numberOfCredentials are not present"): - assert prev_auth.user == None - assert prev_auth.number_of_credentials == None - - self.testGA( - "Send GA request with empty allow_list, expect NO_CREDENTIALS", - rp["id"], - cdh, - [], - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) - - # apply bit flip - badid = list(prev_reg.auth_data.credential_data.credential_id[:]) - badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1 - badid = bytes(badid) - - self.testGA( - "Send GA request with corrupt credId in allow_list, expect NO_CREDENTIALS", - rp["id"], - cdh, - [{"id": badid, "type": "public-key"}], - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) - - self.testGA( - "Send GA request with missing RPID, expect MISSING_PARAMETER", - None, - cdh, - allow_list, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testGA( - "Send GA request with bad RPID, expect error", - {"type": "wrong"}, - cdh, - allow_list, - ) - - self.testGA( - "Send GA request with missing clientDataHash, expect MISSING_PARAMETER", - rp["id"], - None, - allow_list, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testGA( - "Send GA request with bad clientDataHash, expect error", - rp["id"], - {"type": "wrong"}, - allow_list, - ) - - self.testGA( - "Send GA request with bad allow_list, expect error", - rp["id"], - cdh, - {"type": "wrong"}, - ) - - self.testGA( - "Send GA request with bad item in allow_list, expect error", - rp["id"], - cdh, - allow_list + ["wrong"], - ) - - self.testGA( - "Send GA request with unknown option, expect SUCCESS", - rp["id"], - cdh, - allow_list, - other={"options": {"unknown": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - with Test("Get info"): - info = self.ctap.get_info() - - if "uv" in info.options: - if info.options["uv"]: - res = self.testGA( - "Send GA request with uv set to true, expect SUCCESS", - rp["id"], - cdh, - allow_list, - other={"options": {"uv": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - with Test("Check that UV flag is set in response"): - assert res.auth_data.flags & (1 << 2) - if "up" in info.options: - if info.options["up"]: - res = self.testGA( - "Send GA request with up set to true, expect SUCCESS", - rp["id"], - cdh, - allow_list, - other={"options": {"up": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - with Test("Check that UP flag is set in response"): - assert res.auth_data.flags & 1 - - self.testGA( - "Send GA request with bogus type item in allow_list, expect SUCCESS", - rp["id"], - cdh, - allow_list + [{"type": "rot13", "id": b"1234"}], - expectedError=CtapError.ERR.SUCCESS, - ) - - self.testGA( - "Send GA request with item missing type field in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"id": b"1234"}], - ) - - self.testGA( - "Send GA request with item containing bad type field in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key", "id": b"1234"}], - ) - - self.testGA( - "Send GA request with item containing bad id in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key", "id": 42}], - ) - - self.testGA( - "Send GA request with item missing id in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key"}], - ) - - self.testReset() - - appid = sha256(rp["id"].encode("utf8")) - chal = sha256(challenge.encode("utf8")) - with Test("Send CTAP1 register request"): - u2f = U2FTests(self) - reg = u2f.register(chal, appid) - reg.verify(appid, chal) - - with Test("Authenticate CTAP1"): - auth = u2f.authenticate(chal, appid, reg.key_handle) - auth.verify(appid, chal, reg.public_key) - - auth = self.testGA( - "Authenticate CTAP1 registration with CTAP2", - rp["id"], - cdh, - [{"id": reg.key_handle, "type": "public-key"}], - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check assertion is correct"): - credential_data = AttestedCredentialData.from_ctap1( - reg.key_handle, reg.public_key - ) - auth.verify(cdh, credential_data.public_key) - assert auth.credential["id"] == reg.key_handle - - def test_rk(self, pin_code=None): - - pin_auth = None - if pin_code: - pin_protocol = 1 - else: - pin_protocol = None - if pin_code: - with Test("Set pin code"): - self.client.pin_protocol.set_pin(pin_code) - pin_token = self.client.pin_protocol.get_pin_token(pin_code) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - self.testMC( - "Send MC request with rk option set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - other={ - "options": {"rk": True}, - "pin_auth": pin_auth, - "pin_protocol": pin_protocol, - }, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Get info"): - info = self.ctap.get_info() - - options = {"rk": True} - if "uv" in info.options and info.options["uv"]: - options["uv"] = False - - for i, x in enumerate([user1, user2, user3]): - self.testMC( - "Send MC request with rk option set to true, expect SUCCESS %d/3" - % (i + 1), - cdh, - rp2, - x, - key_params, - other={ - "options": options, - "pin_auth": pin_auth, - "pin_protocol": pin_protocol, - }, - expectedError=CtapError.ERR.SUCCESS, - ) - - auth1 = self.testGA( - "Send GA request with no allow_list, expect SUCCESS", - rp2["id"], - cdh, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check that there are 3 credentials returned"): - assert auth1.number_of_credentials == 3 - - with Test("Get the next 2 assertions"): - auth2 = self.ctap.get_next_assertion() - auth3 = self.ctap.get_next_assertion() - - if not pin_code: - with Test("Check only the user ID was returned"): - assert "id" in auth1.user.keys() and len(auth1.user.keys()) == 1 - assert "id" in auth2.user.keys() and len(auth2.user.keys()) == 1 - assert "id" in auth3.user.keys() and len(auth3.user.keys()) == 1 - else: - with Test("Check that all user info was returned"): - for x in (auth1, auth2, auth3): - for y in ("name", "icon", "displayName", "id"): - if y not in x.user.keys(): - print("FAIL: %s was not in user: " % y, x.user) - - with Test("Send an extra getNextAssertion request, expect error"): - try: - self.ctap.get_next_assertion() - assert 0 - except CtapError as e: - print(e) - - def test_client_pin(self,): - pin1 = "1234567890" - self.test_rk(pin1) - - # PinProtocolV1 - res = self.testCP( - "Test getKeyAgreement, expect SUCCESS", - pin_protocol, - PinProtocolV1.CMD.GET_KEY_AGREEMENT, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test getKeyAgreement has appropriate fields"): - key = res[1] - assert "Is public key" and key[1] == 2 - assert "Is P256" and key[-1] == 1 - assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25 - - assert "Right key" and len(key[-3]) == 32 and isinstance(key[-3], bytes) - - with Test("Test setting a new pin"): - pin2 = "qwertyuiop\x11\x22\x33\x00123" - self.client.pin_protocol.change_pin(pin1, pin2) - - with Test("Test getting new pin_auth"): - pin_token = self.client.pin_protocol.get_pin_token(pin2) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - res_mc = self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check UV flag is set"): - assert res_mc.auth_data.flags & (1 << 2) - - res_ga = self.testGA( - "Send GA request with pinAuth, expect SUCCESS", - rp["id"], - cdh, - [ - { - "type": "public-key", - "id": res_mc.auth_data.credential_data.credential_id, - } - ], - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check UV flag is set"): - assert res_ga.auth_data.flags & (1 << 2) - - res_ga = self.testGA( - "Send GA request with no pinAuth, expect SUCCESS", - rp["id"], - cdh, - [ - { - "type": "public-key", - "id": res_mc.auth_data.credential_data.credential_id, - } - ], - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check UV flag is NOT set"): - assert not (res_ga.auth_data.flags & (1 << 2)) - - self.testReset() - - with Test("Test sending zero-length pin_auth, expect PIN_NOT_SET"): - self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_NOT_SET, - ) - self.testGA( - "Send MC request with new pin auth", - rp["id"], - cdh, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=[ - CtapError.ERR.PIN_AUTH_INVALID, - CtapError.ERR.NO_CREDENTIALS, - ], - ) - - with Test("Setting pin code, expect SUCCESS"): - self.client.pin_protocol.set_pin(pin1) - - with Test("Test sending zero-length pin_auth, expect PIN_INVALID"): - self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_AUTH_INVALID, - ) - self.testGA( - "Send MC request with new pin auth", - rp["id"], - cdh, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=[ - CtapError.ERR.PIN_AUTH_INVALID, - CtapError.ERR.NO_CREDENTIALS, - ], - ) - - self.testReset() - with Test("Setting pin code >63 bytes, expect POLICY_VIOLATION "): - try: - self.client.pin_protocol.set_pin("A" * 64) - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_POLICY_VIOLATION - - with Test("Get pin token when no pin is set, expect PIN_NOT_SET"): - try: - self.client.pin_protocol.get_pin_token(pin1) - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_NOT_SET - - with Test("Get change pin when no pin is set, expect PIN_NOT_SET"): - try: - self.client.pin_protocol.change_pin(pin1, "1234") - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_NOT_SET - - with Test("Setting pin code and get pin_token, expect SUCCESS"): - self.client.pin_protocol.set_pin(pin1) - pin_token = self.client.pin_protocol.get_pin_token(pin1) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - with Test("Get info and assert that clientPin is set to true"): - info = self.ctap.get_info() - assert info.options["clientPin"] - - with Test("Test setting pin again fails"): - try: - self.client.pin_protocol.set_pin(pin1) - assert 0 - except CtapError as e: - print(e) - - res_mc = self.testMC( - "Send MC request with no pin_auth, expect PIN_REQUIRED", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.PIN_REQUIRED, - ) - - res_mc = self.testGA( - "Send GA request with no pin_auth, expect NO_CREDENTIALS", - rp["id"], - cdh, - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) - - res = self.testCP( - "Test getRetries, expect SUCCESS", - pin_protocol, - PinProtocolV1.CMD.GET_RETRIES, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check there is 8 pin attempts left"): - assert res[3] == 8 - - # Flip 1 bit - pin_wrong = list(pin1) - c = pin1[len(pin1) // 2] - - pin_wrong[len(pin1) // 2] = chr(ord(c) ^ 1) - pin_wrong = "".join(pin_wrong) - - for i in range(1, 3): - self.testPP( - "Get pin_token with wrong pin code, expect PIN_INVALID (%d/2)" % i, - pin_wrong, - expectedError=CtapError.ERR.PIN_INVALID, - ) - print("Check there is %d pin attempts left" % (8 - i)) - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == (8 - i) - print("Pass") - - for i in range(1, 3): - self.testPP( - "Get pin_token with wrong pin code, expect PIN_AUTH_BLOCKED %d/2" % i, - pin_wrong, - expectedError=CtapError.ERR.PIN_AUTH_BLOCKED, - ) - - self.reboot() - - with Test("Get pin_token, expect SUCCESS"): - pin_token = self.client.pin_protocol.get_pin_token(pin1) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - res_mc = self.testMC( - "Send MC request with correct pin_auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test getRetries resets to 8"): - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == (8) - - for i in range(1, 10): - err = CtapError.ERR.PIN_INVALID - if i in (3, 6): - err = CtapError.ERR.PIN_AUTH_BLOCKED - elif i >= 8: - err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID] - self.testPP( - "Lock out authentictor and check correct error codes %d/9" % i, - pin_wrong, - expectedError=err, - ) - - attempts = 8 - i - if i > 8: - attempts = 0 - - with Test("Check there is %d pin attempts left" % attempts): - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == attempts - - if err == CtapError.ERR.PIN_AUTH_BLOCKED: - self.reboot() - - res_mc = self.testMC( - "Send MC request with correct pin_auth, expect error", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - ) - - self.reboot() - - self.testPP( - "Get pin_token with correct pin code, expect PIN_BLOCKED", - pin1, - expectedError=CtapError.ERR.PIN_BLOCKED, - ) - - def test_fido2(self,): - - self.testReset() - - # self.test_get_info() - # - # self.test_get_assertion() - # - # self.test_make_credential() - # - # self.test_rk(None) - - self.test_client_pin() - - self.testReset() - - self.test_extensions() - - print("Done") diff --git a/tools/testing/tests/hid.py b/tools/testing/tests/hid.py deleted file mode 100644 index 74d9d92..0000000 --- a/tools/testing/tests/hid.py +++ /dev/null @@ -1,252 +0,0 @@ -import sys, os, time -from binascii import hexlify - -from fido2.hid import CTAPHID -from fido2.ctap import CtapError - -from .tester import Tester, Test - - -class HIDTests(Tester): - def __init__(self, tester=None): - super().__init__(tester) - self.check_timeouts = False - - def set_check_timeouts(self, en): - self.check_timeouts = en - - def run(self,): - self.test_long_ping() - self.test_hid(self.check_timeouts) - - def test_long_ping(self): - amt = 1000 - pingdata = os.urandom(amt) - with Test("Send %d byte ping" % amt): - try: - t1 = time.time() * 1000 - r = self.send_data(CTAPHID.PING, pingdata) - t2 = time.time() * 1000 - delt = t2 - t1 - # if (delt < 140 ): - # raise RuntimeError('Fob is too fast (%d ms)' % delt) - if delt > 555 * (amt / 1000): - raise RuntimeError("Fob is too slow (%d ms)" % delt) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError: - raise RuntimeError("ping failed") - - sys.stdout.flush() - - def test_hid(self, check_timeouts=False): - if check_timeouts: - with Test("idle"): - try: - cmd, resp = self.recv_raw() - except socket.timeout: - pass - - with Test("init"): - r = self.send_data(CTAPHID.INIT, "\x11\x11\x11\x11\x11\x11\x11\x11") - - with Test("100 byte ping"): - pingdata = os.urandom(100) - try: - r = self.send_data(CTAPHID.PING, pingdata) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError as e: - print("100 byte Ping failed:", e) - raise RuntimeError("ping failed") - - self.test_long_ping() - - with Test("Wink"): - r = self.send_data(CTAPHID.WINK, "") - - with Test("CBOR msg with no data"): - try: - r = self.send_data(CTAPHID.CBOR, "") - if len(r) > 1 or r[0] == 0: - raise RuntimeError("Cbor is supposed to have payload") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_LENGTH - - with Test("No data in U2F msg"): - try: - r = self.send_data(CTAPHID.MSG, "") - print(hexlify(r)) - if len(r) > 2: - raise RuntimeError("MSG is supposed to have payload") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_LENGTH - - with Test("Use init command to resync"): - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - - with Test("Invalid HID command"): - try: - r = self.send_data(0x66, "") - raise RuntimeError("Invalid command did not return error") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_COMMAND - - with Test("Sending packet with too large of a length."): - self.send_raw("\x81\x1d\xba\x00") - cmd, resp = self.recv_raw() - Tester.check_error(resp, CtapError.ERR.INVALID_LENGTH) - - r = self.send_data(CTAPHID.PING, "\x44" * 200) - with Test("Sending packets that skip a sequence number."): - self.send_raw("\x81\x04\x90") - self.send_raw("\x00") - self.send_raw("\x01") - # skip 2 - self.send_raw("\x03") - cmd, resp = self.recv_raw() - Tester.check_error(resp, CtapError.ERR.INVALID_SEQ) - - with Test("Resync and send ping"): - try: - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - pingdata = os.urandom(100) - r = self.send_data(CTAPHID.PING, pingdata) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError as e: - raise RuntimeError("resync fail: ", e) - - with Test("Send ping and abort it"): - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - try: - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - except CtapError as e: - raise RuntimeError("resync fail: ", e) - - with Test("Send ping and abort it with different cid, expect timeout"): - oldcid = self.cid() - newcid = "\x11\x22\x33\x44" - self.send_raw("\x81\x10\x00") - self.send_raw("\x00") - self.send_raw("\x01") - self.set_cid(newcid) - self.send_raw( - "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88" - ) # init from different cid - print("wait for init response") - cmd, r = self.recv_raw() # init response - assert cmd == 0x86 - self.set_cid(oldcid) - if check_timeouts: - # print('wait for timeout') - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - - with Test("Test timeout"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - t1 = time.time() * 1000 - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - cmd, r = self.recv_raw() # timeout response - t2 = time.time() * 1000 - delt = t2 - t1 - assert cmd == 0xBF - assert r[0] == CtapError.ERR.TIMEOUT - assert delt < 1000 and delt > 400 - - with Test("Test not cont"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - self.send_raw("\x81\x10\x00") # init packet - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_SEQ - - if check_timeouts: - with Test("Check random cont ignored"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x01\x10\x00") - try: - cmd, r = self.recv_raw() # timeout response - except socket.timeout: - pass - - with Test("Check busy"): - t1 = time.time() * 1000 - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - oldcid = self.cid() - newcid = "\x11\x22\x33\x44" - self.send_raw("\x81\x04\x00") - self.set_cid(newcid) - self.send_raw("\x81\x04\x00") - cmd, r = self.recv_raw() # busy response - t2 = time.time() * 1000 - assert t2 - t1 < 100 - assert cmd == 0xBF - assert r[0] == CtapError.ERR.CHANNEL_BUSY - - self.set_cid(oldcid) - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - assert r[0] == CtapError.ERR.TIMEOUT - - with Test("Check busy interleaved"): - cid1 = "\x11\x22\x33\x44" - cid2 = "\x01\x22\x33\x44" - self.set_cid(cid2) - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.set_cid(cid1) - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x81\x00\x63") # echo 99 bytes first channel - - self.set_cid(cid2) # send ping on 2nd channel - self.send_raw("\x81\x00\x63") - Tester.delay(0.1) - self.send_raw("\x00") - - cmd, r = self.recv_raw() # busy response - - self.set_cid(cid1) # finish 1st channel ping - self.send_raw("\x00") - - self.set_cid(cid2) - - assert cmd == 0xBF - assert r[0] == CtapError.ERR.CHANNEL_BUSY - - self.set_cid(cid1) - cmd, r = self.recv_raw() # ping response - assert cmd == 0x81 - assert len(r) == 0x63 - - if check_timeouts: - with Test("Test idle, wait for timeout"): - sys.stdout.flush() - try: - cmd, resp = self.recv_raw() - except socket.timeout: - pass - - with Test("Test cid 0 is invalid"): - self.set_cid("\x00\x00\x00\x00") - self.send_raw( - "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00" - ) - cmd, r = self.recv_raw() # timeout - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_CHANNEL - - with Test("Test invalid broadcast cid use"): - self.set_cid("\xff\xff\xff\xff") - self.send_raw( - "\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\xff\xff\xff\xff" - ) - cmd, r = self.recv_raw() # timeout - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_CHANNEL diff --git a/tools/testing/tests/solo.py b/tools/testing/tests/solo.py deleted file mode 100644 index 8853f0d..0000000 --- a/tools/testing/tests/solo.py +++ /dev/null @@ -1,83 +0,0 @@ -from solo.client import SoloClient -from solo.commands import SoloExtension - -from fido2.ctap1 import ApduError -from fido2.utils import sha256 - -from .util import shannon_entropy -from .tester import Tester, Test - - -class SoloTests(Tester): - def __init__(self, tester=None): - super().__init__(tester) - - def run(self,): - self.test_solo() - - def test_solo(self,): - """ - Solo specific tests - """ - # RNG command - sc = SoloClient() - sc.find_device(self.dev) - sc.use_u2f() - memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) - - total = 1024 * 16 - with Test("Gathering %d random bytes..." % total): - entropy = b"" - while len(entropy) < total: - entropy += sc.get_rng() - - with Test("Test entropy is close to perfect"): - s = shannon_entropy(entropy) - assert s > 7.98 - print("Entropy is %.5f bits per byte." % s) - - with Test("Test Solo version command"): - assert len(sc.solo_version()) == 3 - - with Test("Test bootloader is not active"): - try: - sc.write_flash(memmap[0], b"1234") - except ApduError: - pass - - sc.exchange = sc.exchange_fido2 - - req = SoloClient.format_request(SoloExtension.version, 0, b"A" * 16) - a = sc.ctap2.get_assertion( - sc.host, b"B" * 32, [{"id": req, "type": "public-key"}] - ) - - with Test("Test custom command returned valid assertion"): - assert a.auth_data.rp_id_hash == sha256(sc.host.encode("utf8")) - assert a.credential["id"] == req - assert (a.auth_data.flags & 0x5) == 0x5 - - with Test("Test Solo version and random commands with fido2 layer"): - assert len(sc.solo_version()) == 3 - sc.get_rng() - - def test_bootloader(self,): - sc = SoloClient() - sc.find_device(self.dev) - sc.use_u2f() - - memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) - data = b"A" * 64 - - with Test("Test version command"): - assert len(sc.bootloader_version()) == 3 - - with Test("Test write command"): - sc.write_flash(memmap[0], data) - - for addr in (memmap[0] - 8, memmap[0] - 4, memmap[1], memmap[1] - 8): - with Test("Test out of bounds write command at 0x%04x" % addr): - try: - sc.write_flash(addr, data) - except CtapError as e: - assert e.code == CtapError.ERR.NOT_ALLOWED diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py deleted file mode 100644 index 764bcca..0000000 --- a/tools/testing/tests/tester.py +++ /dev/null @@ -1,230 +0,0 @@ -import time, struct - -from fido2.hid import CtapHidDevice -from fido2.client import Fido2Client -from fido2.attestation import Attestation -from fido2.ctap1 import CTAP1 -from fido2.utils import Timeout - -from fido2.ctap import CtapError - - -def ForceU2F(client, device): - client.ctap = CTAP1(device) - client.pin_protocol = None - client._do_make_credential = client._ctap1_make_credential - client._do_get_assertion = client._ctap1_get_assertion - - -class Packet(object): - def __init__(self, data): - self.data = data - - def ToWireFormat(self,): - return self.data - - @staticmethod - def FromWireFormat(pkt_size, data): - return Packet(data) - - -class Test: - def __init__(self, msg, catch=None): - self.msg = msg - self.catch = catch - - def __enter__(self,): - print(self.msg) - - def __exit__(self, a, b, c): - if self.catch is None: - print("Pass") - elif isinstance(b, self.catch): - print("Pass") - return b - else: - raise RuntimeError(f"Expected exception {self.catch} did not occur.") - - -class Tester: - def __init__(self, tester=None): - self.origin = "https://examplo.org" - self.host = "examplo.org" - self.user_count = 10 - self.is_sim = False - self.nfc_interface_only = False - if tester: - self.initFromTester(tester) - - def initFromTester(self, tester): - self.user_count = tester.user_count - self.is_sim = tester.is_sim - self.dev = tester.dev - self.ctap = tester.ctap - self.ctap1 = tester.ctap1 - self.client = tester.client - self.nfc_interface_only = tester.nfc_interface_only - - def find_device(self, nfcInterfaceOnly=False): - dev = None - self.nfc_interface_only = nfcInterfaceOnly - if not nfcInterfaceOnly: - print("--- HID ---") - print(list(CtapHidDevice.list_devices())) - dev = next(CtapHidDevice.list_devices(), None) - - if not dev: - from fido2.pcsc import CtapPcscDevice - - print("--- NFC ---") - print(list(CtapPcscDevice.list_devices())) - dev = next(CtapPcscDevice.list_devices(), None) - - if not dev: - raise RuntimeError("No FIDO device found") - self.dev = dev - self.client = Fido2Client(dev, self.origin) - self.ctap = self.client.ctap2 - self.ctap1 = CTAP1(dev) - - # consume timeout error - # cmd,resp = self.recv_raw() - - def set_user_count(self, count): - self.user_count = count - - def set_sim(self, b): - self.is_sim = b - - def reboot(self,): - if self.is_sim: - print("Sending restart command...") - self.send_magic_reboot() - Tester.delay(0.25) - else: - print("Please reboot authentictor and hit enter") - input() - self.find_device(self.nfc_interface_only) - - def send_data(self, cmd, data): - if not isinstance(data, bytes): - data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) - with Timeout(1.0) as event: - return self.dev.call(cmd, data, event) - - def send_raw(self, data, cid=None): - if cid is None: - cid = self.dev._dev.cid - elif not isinstance(cid, bytes): - cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - if not isinstance(data, bytes): - data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) - data = cid + data - l = len(data) - if l != 64: - pad = "\x00" * (64 - l) - pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad]) - data = data + pad - data = list(data) - assert len(data) == 64 - self.dev._dev.InternalSendPacket(Packet(data)) - - def send_magic_reboot(self,): - """ - For use in simulation and testing. Random bytes that authentictor should detect - and then restart itself. - """ - magic_cmd = ( - b"\xac\x10\x52\xca\x95\xe5\x69\xde\x69\xe0\x2e\xbf" - + b"\xf3\x33\x48\x5f\x13\xf9\xb2\xda\x34\xc5\xa8\xa3" - + b"\x40\x52\x66\x97\xa9\xab\x2e\x0b\x39\x4d\x8d\x04" - + b"\x97\x3c\x13\x40\x05\xbe\x1a\x01\x40\xbf\xf6\x04" - + b"\x5b\xb2\x6e\xb7\x7a\x73\xea\xa4\x78\x13\xf6\xb4" - + b"\x9a\x72\x50\xdc" - ) - self.dev._dev.InternalSendPacket(Packet(magic_cmd)) - - def cid(self,): - return self.dev._dev.cid - - def set_cid(self, cid): - if not isinstance(cid, (bytes, bytearray)): - cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - self.dev._dev.cid = cid - - def recv_raw(self,): - with Timeout(1.0): - cmd, payload = self.dev._dev.InternalRecv() - return cmd, payload - - def check_error(data, err=None): - assert len(data) == 1 - if err is None: - if data[0] != 0: - raise CtapError(data[0]) - elif data[0] != err: - raise ValueError("Unexpected error: %02x" % data[0]) - - def testFunc(self, func, test, *args, **kwargs): - with Test(test): - res = None - expectedError = kwargs.get("expectedError", None) - otherArgs = kwargs.get("other", {}) - try: - res = func(*args, **otherArgs) - if expectedError != CtapError.ERR.SUCCESS: - raise RuntimeError("Expected error to occur for test: %s" % test) - except CtapError as e: - if expectedError is not None: - cond = e.code != expectedError - if isinstance(expectedError, list): - cond = e.code not in expectedError - else: - expectedError = [expectedError] - if cond: - raise RuntimeError( - f"Got error code {hex(e.code)}, expected {[hex(x) for x in expectedError]}" - ) - else: - print(e) - return res - - def testReset(self,): - print("Resetting Authenticator...") - try: - self.ctap.reset() - except CtapError: - # Some authenticators need a power cycle - print("You must power cycle authentictor. Hit enter when done.") - input() - time.sleep(0.2) - self.find_device(self.nfc_interface_only) - self.ctap.reset() - - def testMC(self, test, *args, **kwargs): - attestation_object = self.testFunc( - self.ctap.make_credential, test, *args, **kwargs - ) - if attestation_object: - verifier = Attestation.for_type(attestation_object.fmt) - client_data = args[0] - verifier().verify( - attestation_object.att_statement, - attestation_object.auth_data, - client_data, - ) - return attestation_object - - def testGA(self, test, *args, **kwargs): - return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs) - - def testCP(self, test, *args, **kwargs): - return self.testFunc(self.ctap.client_pin, test, *args, **kwargs) - - def testPP(self, test, *args, **kwargs): - return self.testFunc( - self.client.pin_protocol.get_pin_token, test, *args, **kwargs - ) - - def delay(secs): - time.sleep(secs) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py deleted file mode 100644 index 96d7fd8..0000000 --- a/tools/testing/tests/u2f.py +++ /dev/null @@ -1,133 +0,0 @@ -from fido2.ctap1 import CTAP1, ApduError, APDU -from fido2.utils import sha256 -from fido2.client import _call_polling - -from .tester import Tester, Test - - -class U2FTests(Tester): - def __init__(self, tester=None): - super().__init__(tester) - - def run(self,): - self.test_u2f() - - def register(self, chal, appid): - reg_data = _call_polling(0.25, None, None, self.ctap1.register, chal, appid) - return reg_data - - def authenticate(self, chal, appid, key_handle, check_only=False): - auth_data = _call_polling( - 0.25, - None, - None, - self.ctap1.authenticate, - chal, - appid, - key_handle, - check_only=check_only, - ) - return auth_data - - def test_u2f(self,): - chal = sha256(b"AAA") - appid = sha256(b"BBB") - lastc = 0 - - regs = [] - - with Test("Check version"): - assert self.ctap1.get_version() == "U2F_V2" - - with Test("Check bad INS"): - try: - self.ctap1.send_apdu(0, 0, 0, 0, b"") - assert False - except ApduError as e: - assert e.code == 0x6D00 - - with Test("Check bad CLA"): - try: - self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") - assert False - except ApduError as e: - assert e.code == 0x6E00 - - for i in range(0, self.user_count): - with Test( - "U2F reg + auth %d/%d (count: %02x)" % (i + 1, self.user_count, lastc) - ): - reg = self.register(chal, appid) - reg.verify(appid, chal) - auth = self.authenticate(chal, appid, reg.key_handle) - auth.verify(appid, chal, reg.public_key) - - regs.append(reg) - # check endianness - if lastc: - assert (auth.counter - lastc) < 10 - lastc = auth.counter - if lastc > 0x80000000: - print("WARNING: counter is unusually high: %04x" % lastc) - assert 0 - - for i in range(0, self.user_count): - with Test( - "Checking previous registration %d/%d" % (i + 1, self.user_count) - ): - auth = self.authenticate(chal, appid, regs[i].key_handle) - auth.verify(appid, chal, regs[i].public_key) - - self.reboot() - - for i in range(0, self.user_count): - with Test( - "Post reboot, Checking previous registration %d/%d" - % (i + 1, self.user_count) - ): - auth = self.authenticate(chal, appid, regs[i].key_handle) - auth.verify(appid, chal, regs[i].public_key) - - print("Check that all previous credentials are registered...") - for i in range(0, self.user_count): - with Test("Check that previous credential %d is registered" % i): - try: - auth = self.ctap1.authenticate( - chal, appid, regs[i].key_handle, check_only=True - ) - except ApduError as e: - # Indicates that key handle is registered - assert e.code == APDU.USE_NOT_SATISFIED - - with Test("Check an incorrect key handle is not registered"): - kh = bytearray(regs[0].key_handle) - kh[0] = kh[0] ^ (0x40) - try: - self.ctap1.authenticate(chal, appid, kh, check_only=True) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign with incorrect key handle"): - try: - self.ctap1.authenticate(chal, appid, kh) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign using an incorrect keyhandle length"): - try: - kh = regs[0].key_handle - self.ctap1.authenticate(chal, appid, kh[: len(kh) // 2]) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign using an incorrect appid"): - badid = bytearray(appid) - badid[0] = badid[0] ^ (0x40) - try: - auth = self.ctap1.authenticate(chal, badid, regs[0].key_handle) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA diff --git a/tools/testing/tests/util.py b/tools/testing/tests/util.py deleted file mode 100644 index 94c3c45..0000000 --- a/tools/testing/tests/util.py +++ /dev/null @@ -1,12 +0,0 @@ -import math - - -def shannon_entropy(data): - s = 0.0 - total = len(data) - for x in range(0, 256): - freq = data.count(x) - p = freq / total - if p > 0: - s -= p * math.log2(p) - return s From f9f1e96c73f65f456467bcd8b5fa2abd69b75bc5 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 6 Aug 2019 18:54:57 +0800 Subject: [PATCH 058/139] Update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index b243ea1..f3b61e3 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,7 @@ Run the Solo application: ./main ``` -In another shell, you can run client software, for example our tests: -```bash -python tools/ctap_test.py sim fido2 -``` +In another shell, you can run our [test suite](https://github.com/solokeys/fido2-tests). You can find more details in our [documentation](https://docs.solokeys.io/solo/), including how to build on the the NUCLEO-L432KC development board. From 29b2032dae7edf0fb2ae297bd3eda1bc19ef772a Mon Sep 17 00:00:00 2001 From: Wessel de Roode Date: Tue, 6 Aug 2019 13:06:06 +0200 Subject: [PATCH 059/139] fixing final-definitions and misc --- docs/solo/nucleo32-board.md | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index 7ca6202..008839d 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -28,12 +28,6 @@ See this [USB plug] image, and Wikipedia's [USB plug description]. Plug in [USB-A_schematic.pdf] has wrong wire order, registered as [solo-hw#1]. -[solo-hw#1]: https://github.com/solokeys/solo-hw/issues/1 - -[usb plug]: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/USB.svg/1200px-USB.svg.png - -[usb plug description]: https://en.wikipedia.org/wiki/USB#Receptacle_(socket)_identification - The power is taken from the debugger / board (unless the board is configured in another way). Make sure 5V is not connected, and is covered from contacting with the board elements. @@ -66,15 +60,13 @@ PA0 / pin 6 --> button --> GND In that case the mentioned patch would not be required. -[usb-a_schematic.pdf]: https://github.com/solokeys/solo-hw/releases/download/1.2/USB-A_schematic.pdf - -# Development environment setup +## Development environment setup Environment: Fedora 29 x64, Linux 4.19.9 See for the original guide. Here details not included there will be covered. -## Install ARM tools +### Install ARM tools 1. Download current [ARM tools] package: [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]. 2. Extract the archive. @@ -85,15 +77,15 @@ See for the original guide. Here detai [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]: https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2?revision=d830f9dd-cd4f-406d-8672-cca9210dd220?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2018-q4-major -## Install flashing software +### Install flashing software ST provides a CLI flashing tool - `STM32_Programmer_CLI`. It can be downloaded directly from the vendor's site: -1\. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), +1. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), go to bottom page and from STM32CubeProg row select Download button. -2\. Unzip contents of the archive. -3\. Run \*Linux setup -4\. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located -5\. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. +2. Unzip contents of the archive. +3. Run \*Linux setup +4. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located +5. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. If you're on OsX and installed the STM32CubeProg, you need to add the following to your path: @@ -102,9 +94,9 @@ If you're on OsX and installed the STM32CubeProg, you need to add the following export PATH="/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin/":$PATH ``` -# Building and flashing +## Building and flashing -## Building +### Building Please follow , as the build way changes rapidly. Currently (8.1.19) to build the firmware, following lines should be executed @@ -119,7 +111,7 @@ make build-hacker DEBUG=1 Note: `DEBUG=2` stops the device initialization, until a serial client will be attached to its virtual port. Do not use it, if you do not plan to do so. -## Flashing via the Makefile command +### Flashing via the Makefile command ```bash # while in the main project directory @@ -136,7 +128,7 @@ make flash # STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst ``` -## Manual flashing +### Manual flashing In case you already have a firmware to flash (named `all.hex`), please run the following: @@ -145,17 +137,17 @@ STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst ``` -# Testing +## Testing -## Internal +### Internal Project-provided tests. -### Simulated device +#### Simulated device A simulated device is provided to test the HID layer. -#### Build +##### Build ```bash make clean @@ -165,7 +157,7 @@ cd .. make env2 ``` -#### Execution +##### Execution ```bash # run simulated device (will create a network UDP server) @@ -176,7 +168,7 @@ make env2 ./env2/bin/python python-fido2/examples/credential.py ``` -### Real device +#### Real device ```bash # while in the main project directory @@ -184,27 +176,27 @@ make env2 make fido2-test ``` -## External +### External -### FIDO2 test sites +#### FIDO2 test sites 1. 2. 3. -### U2F test sites +#### U2F test sites 1. 2. -### FIDO2 standalone clients +#### FIDO2 standalone clients 1. 2. 3. 4. -# USB serial console reading +## USB serial console reading Device opens an USB-emulated serial port to output its messages. While Nucleo board offers such already, the Solo device provides its own. @@ -223,9 +215,9 @@ sudo picocom -b 115200 /dev/solokey-serial where `/dev/solokey-serial` is an udev symlink to `/dev/ttyACM1`. -# Other +## Other -## Dumping firmware +### Dumping firmware Size is calculated using bash arithmetic. @@ -233,13 +225,13 @@ Size is calculated using bash arithmetic. STM32_Programmer_CLI -c port=SWD -halt -u 0x0 $((256*1024)) current.hex ``` -## Software reset +### Software reset ```bash STM32_Programmer_CLI -c port=SWD -rst ``` -## Installing required Python packages +### Installing required Python packages Client script requires some Python packages, which could be easily installed locally to the project via the Makefile command. It is sufficient to run: @@ -247,3 +239,11 @@ via the Makefile command. It is sufficient to run: ```bash make env3 ``` + +[solo-hw#1]: https://github.com/solokeys/solo-hw/issues/1 + +[usb plug]: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/USB.svg/1200px-USB.svg.png + +[usb plug description]: https://en.wikipedia.org/wiki/USB#Receptacle_(socket)_identification + +[usb-a_schematic.pdf]: https://github.com/solokeys/solo-hw/releases/download/1.2/USB-A_schematic.pdf From 6dfe42e48679c1f8463cb4b302630a1342113f03 Mon Sep 17 00:00:00 2001 From: Wessel de Roode Date: Tue, 6 Aug 2019 13:11:51 +0200 Subject: [PATCH 060/139] All cleaned up now ? --- docs/solo/nucleo32-board.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index 008839d..e48312d 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -69,23 +69,21 @@ See for the original guide. Here detai ### Install ARM tools 1. Download current [ARM tools] package: [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]. + 2. Extract the archive. + 3. Add full path to the `./bin` directory as first entry to the `$PATH` variable, as in `~/gcc-arm/gcc-arm-none-eabi-8-2018-q4-major/bin/:$PATH`. -[arm tools]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads - -[gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]: https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2?revision=d830f9dd-cd4f-406d-8672-cca9210dd220?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2018-q4-major - ### Install flashing software ST provides a CLI flashing tool - `STM32_Programmer_CLI`. It can be downloaded directly from the vendor's site: -1. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), +1\. Go to [download site URL](https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stm32cubeprog.html), go to bottom page and from STM32CubeProg row select Download button. -2. Unzip contents of the archive. -3. Run \*Linux setup -4. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located -5. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. +2\. Unzip contents of the archive. +3\. Run \*Linux setup +4\. In installation directory go to ./bin - there the ./STM32_Programmer_CLI is located +5\. Add symlink to the STM32 CLI binary to .local/bin. Make sure the latter it is in $PATH. If you're on OsX and installed the STM32CubeProg, you need to add the following to your path: @@ -247,3 +245,7 @@ make env3 [usb plug description]: https://en.wikipedia.org/wiki/USB#Receptacle_(socket)_identification [usb-a_schematic.pdf]: https://github.com/solokeys/solo-hw/releases/download/1.2/USB-A_schematic.pdf + +[arm tools]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads + +[gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]: https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2?revision=d830f9dd-cd4f-406d-8672-cca9210dd220?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,8-2018-q4-major From 02a51454b773a628e6da02bdcfe7efe386320ed4 Mon Sep 17 00:00:00 2001 From: Wessel de Roode Date: Wed, 7 Aug 2019 18:35:41 +0200 Subject: [PATCH 061/139] Added OsX arm install, updated FIDO2 test site links --- docs/solo/nucleo32-board.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/solo/nucleo32-board.md b/docs/solo/nucleo32-board.md index e48312d..ac579d2 100644 --- a/docs/solo/nucleo32-board.md +++ b/docs/solo/nucleo32-board.md @@ -66,7 +66,7 @@ Environment: Fedora 29 x64, Linux 4.19.9 See for the original guide. Here details not included there will be covered. -### Install ARM tools +### Install ARM tools Linux 1. Download current [ARM tools] package: [gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2]. @@ -75,6 +75,13 @@ See for the original guide. Here detai 3. Add full path to the `./bin` directory as first entry to the `$PATH` variable, as in `~/gcc-arm/gcc-arm-none-eabi-8-2018-q4-major/bin/:$PATH`. +### Install ARM tools OsX using brew package manager + +```bash +brew tap ArmMbed/homebrew-formulae +brew install arm-none-eabi-gcc +``` + ### Install flashing software ST provides a CLI flashing tool - `STM32_Programmer_CLI`. It can be downloaded directly from the vendor's site: @@ -114,8 +121,8 @@ Do not use it, if you do not plan to do so. ```bash # while in the main project directory # create Python virtual environment with required packages, and activate -make env3 -. env3/bin/activate +make venv +. venv/bin/activate # Run flashing cd ./targets/stm32l432 make flash @@ -178,8 +185,8 @@ make fido2-test #### FIDO2 test sites -1. -2. +1. +2. 3. #### U2F test sites From c249148d8033581894b1f14fbbca56ae34367f80 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 21:26:36 +0000 Subject: [PATCH 062/139] docs: update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index f3b61e3..3787643 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) +[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -132,3 +133,19 @@ You may use Solo documentation under the terms of the CC-BY-SA 4.0 license # Where To Buy Solo You can buy Solo, Solo Tap, and Solo for Hackers at [solokeys.com](https://solokeys.com). + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + +
Szczepan Zalega
Szczepan Zalega

💻
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file From c18a08b14b814c1803a73b1d1fa487976e518b6f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 21:26:37 +0000 Subject: [PATCH 063/139] docs: create .all-contributorsrc --- .all-contributorsrc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .all-contributorsrc diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..36978dd --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,23 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "szszszsz", + "name": "Szczepan Zalega", + "avatar_url": "https://avatars0.githubusercontent.com/u/17005426?v=4", + "profile": "https://github.com/szszszsz", + "contributions": [ + "code" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "solo", + "projectOwner": "solokeys", + "repoType": "github", + "repoHost": "https://github.com" +} From b4b59a6d4df33487031d1ad8924eb0ff9aa97330 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 7 Aug 2019 23:39:11 +0200 Subject: [PATCH 064/139] Merge all-contributors with existing Contributors section --- README.md | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3787643..edfcc63 100644 --- a/README.md +++ b/README.md @@ -105,14 +105,26 @@ You can find more details in our [documentation](https://docs.solokeys.io/solo/) Check out our [official documentation](https://docs.solokeys.io/solo/). -# Contributors +# Contributors ✨ Solo is an upgrade to [U2F Zero](https://github.com/conorpp/u2f-zero). It was born from Conor's passion for making secure hardware, and from our shared belief that security should be open to be trustworthy, in hardware like in software. -Contributors are welcome. The ultimate goal is to have a FIDO2 security key supporting USB, NFC, and BLE interfaces, that can run on a variety of MCUs. - +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! +The ultimate goal is to have a FIDO2 security key supporting USB, NFC, and BLE interfaces, that can run on a variety of MCUs. Look at the issues to see what is currently being worked on. Feel free to add issues as well. +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + +
Szczepan Zalega
Szczepan Zalega

💻
+ + + # License @@ -134,18 +146,3 @@ You may use Solo documentation under the terms of the CC-BY-SA 4.0 license You can buy Solo, Solo Tap, and Solo for Hackers at [solokeys.com](https://solokeys.com). -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - -
Szczepan Zalega
Szczepan Zalega

💻
- - - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file From 66befb96b86554ae3b6a577ebbe75eb4f1d8775f Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 7 Aug 2019 23:55:44 +0200 Subject: [PATCH 065/139] Clarify that code contributions are assumed to be dual-licensed --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edfcc63..3f74ebf 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ Solo is fully open source. All software, unless otherwise noted, is dual licensed under Apache 2.0 and MIT. You may use Solo software under the terms of either the Apache 2.0 license or MIT license. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + All hardware, unless otherwise noted, is dual licensed under CERN and CC-BY-SA. You may use Solo hardware under the terms of either the CERN 2.1 license or CC-BY-SA 4.0 license. From 2e67a5f7726c561c853dc4b24c1eab3e09a5dd1e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 21:58:16 +0000 Subject: [PATCH 066/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f74ebf..664b1e0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -120,6 +120,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d +
Szczepan Zalega
Szczepan Zalega

💻
Wessel dR
Wessel dR

📖
From 25bf9d8e5452daacffcbd3f0ebbab17ddc484d9e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 21:58:17 +0000 Subject: [PATCH 067/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 36978dd..083cf5f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -13,6 +13,15 @@ "contributions": [ "code" ] + }, + { + "login": "Wesseldr", + "name": "Wessel dR", + "avatar_url": "https://avatars1.githubusercontent.com/u/4012809?v=4", + "profile": "https://github.com/Wesseldr", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From 70c176e9ef59b184238d0c54a695bd5ad65b03d8 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:23:25 +0000 Subject: [PATCH 068/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 664b1e0..398f9a1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -121,6 +121,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Szczepan Zalega
Szczepan Zalega

💻 Wessel dR
Wessel dR

📖 + Oleg Moiseenko
Oleg Moiseenko

💻 From 81a67eda24e791255011ef77fdb1cc1ece36243b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:23:26 +0000 Subject: [PATCH 069/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 083cf5f..b707748 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -22,6 +22,15 @@ "contributions": [ "doc" ] + }, + { + "login": "merlokk", + "name": "Oleg Moiseenko", + "avatar_url": "https://avatars2.githubusercontent.com/u/807634?v=4", + "profile": "http://www.lotteam.com", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, From c37638d4cb64ca973cac5a672bac472fca5b86b4 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:24:53 +0000 Subject: [PATCH 070/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 664b1e0..7d5dcab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -121,6 +121,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Szczepan Zalega
Szczepan Zalega

💻 Wessel dR
Wessel dR

📖 + Adam Langley
Adam Langley

🐛 💻 From 018311cfabe3d35a89a5b25c236e7650d8d5d959 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:24:54 +0000 Subject: [PATCH 071/139] docs: update .all-contributorsrc --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 083cf5f..8065c4f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -22,6 +22,16 @@ "contributions": [ "doc" ] + }, + { + "login": "agl", + "name": "Adam Langley", + "avatar_url": "https://avatars3.githubusercontent.com/u/21203?v=4", + "profile": "https://www.imperialviolet.org", + "contributions": [ + "bug", + "code" + ] } ], "contributorsPerLine": 7, From a53dfb28129ea83a3cafa5e447e4a16b89f783c5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:32:05 +0000 Subject: [PATCH 072/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e6fee3..db3ff68 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -123,6 +123,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Wessel dR
Wessel dR

📖 Adam Langley
Adam Langley

🐛 💻 Oleg Moiseenko
Oleg Moiseenko

💻 + Alex Seigler
Alex Seigler

🐛 From 86c6b3cbc8d6783a0f09196b3d7fd083616ed14f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:32:05 +0000 Subject: [PATCH 073/139] docs: update .all-contributorsrc --- .all-contributorsrc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index dffce20..e5ba545 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -33,7 +33,7 @@ "code" ] }, - { + { "login": "merlokk", "name": "Oleg Moiseenko", "avatar_url": "https://avatars2.githubusercontent.com/u/807634?v=4", @@ -41,6 +41,15 @@ "contributions": [ "code" ] + }, + { + "login": "aseigler", + "name": "Alex Seigler", + "avatar_url": "https://avatars1.githubusercontent.com/u/6605560?v=4", + "profile": "https://github.com/aseigler", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, From 4f317863e9fccf05026a9d7b4f2bd510c32eb23c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:33:39 +0000 Subject: [PATCH 074/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db3ff68..2e63cca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -124,6 +124,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Adam Langley
Adam Langley

🐛 💻 Oleg Moiseenko
Oleg Moiseenko

💻 Alex Seigler
Alex Seigler

🐛 + Dominik Schürmann
Dominik Schürmann

🐛 From 763995763fea5595b49e83a2a1c420ae3f34a47f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:33:40 +0000 Subject: [PATCH 075/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index e5ba545..8cb59d5 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -50,6 +50,15 @@ "contributions": [ "bug" ] + }, + { + "login": "dschuermann", + "name": "Dominik Schürmann", + "avatar_url": "https://avatars3.githubusercontent.com/u/321888?v=4", + "profile": "https://www.cotech.de/services/", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, From 9174c00abfb891bfbaf3e2e0e3238a9598f8d155 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:35:53 +0000 Subject: [PATCH 076/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e63cca..2758c89 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -125,6 +125,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Oleg Moiseenko
Oleg Moiseenko

💻 Alex Seigler
Alex Seigler

🐛 Dominik Schürmann
Dominik Schürmann

🐛 + Ernie Hershey
Ernie Hershey

📖 From 434f2f89ec63e64dcf022ed41d8f34b6ba4cd208 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:35:54 +0000 Subject: [PATCH 077/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8cb59d5..f8e773a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -59,6 +59,15 @@ "contributions": [ "bug" ] + }, + { + "login": "ehershey", + "name": "Ernie Hershey", + "avatar_url": "https://avatars0.githubusercontent.com/u/286008?v=4", + "profile": "https://github.com/ehershey", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From 832f06b6b498b08c74edf8424924b2772a8b299e Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:37:53 +0000 Subject: [PATCH 078/139] docs: update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2758c89..0b81237 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -127,6 +127,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Dominik Schürmann
Dominik Schürmann

🐛 Ernie Hershey
Ernie Hershey

📖 + + Andrea Corna
Andrea Corna

🚇 + From c2312fa69a3c2f0d0ff8ba7505495c416c006da5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:37:54 +0000 Subject: [PATCH 079/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index f8e773a..676d8d4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -68,6 +68,15 @@ "contributions": [ "doc" ] + }, + { + "login": "YakBizzarro", + "name": "Andrea Corna", + "avatar_url": "https://avatars1.githubusercontent.com/u/767740?v=4", + "profile": "https://github.com/YakBizzarro", + "contributions": [ + "infra" + ] } ], "contributorsPerLine": 7, From 1cd669224650d2f76c5874291c1673b2363e9b48 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:40:46 +0000 Subject: [PATCH 080/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b81237..2e38934 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -129,6 +129,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Andrea Corna
Andrea Corna

🚇 + Paul Jimenez
Paul Jimenez

🚇 💻 From b5e592adc95eda2bdcb144cb623a50dce4d453d5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:40:47 +0000 Subject: [PATCH 081/139] docs: update .all-contributorsrc --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 676d8d4..fc9cee6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -77,6 +77,16 @@ "contributions": [ "infra" ] + }, + { + "login": "pjz", + "name": "Paul Jimenez", + "avatar_url": "https://avatars3.githubusercontent.com/u/11100?v=4", + "profile": "https://place.org/~pj/", + "contributions": [ + "infra", + "code" + ] } ], "contributorsPerLine": 7, From 1a3aad9761d0220c9ffeafcf145ce99057a59428 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:42:16 +0000 Subject: [PATCH 082/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e38934..31bd401 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -130,6 +130,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Andrea Corna
Andrea Corna

🚇 Paul Jimenez
Paul Jimenez

🚇 💻 + yparitcher
yparitcher

🤔 🚧 From 04570aaf1508cf95cd08cdf97aed00169f1840f7 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:42:17 +0000 Subject: [PATCH 083/139] docs: update .all-contributorsrc --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index fc9cee6..faf2d35 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -87,6 +87,16 @@ "infra", "code" ] + }, + { + "login": "yparitcher", + "name": "yparitcher", + "avatar_url": "https://avatars0.githubusercontent.com/u/38916402?v=4", + "profile": "https://github.com/yparitcher", + "contributions": [ + "ideas", + "maintenance" + ] } ], "contributorsPerLine": 7, From 7d84caa754dca88c9189768668b847caf2c277ab Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:42:56 +0000 Subject: [PATCH 084/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31bd401..b566436 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -131,6 +131,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Andrea Corna
Andrea Corna

🚇 Paul Jimenez
Paul Jimenez

🚇 💻 yparitcher
yparitcher

🤔 🚧 + StoyanDimitrov
StoyanDimitrov

📖 From 7cfd0ccb193a84b0340b7906def7167c82ce91f5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:42:57 +0000 Subject: [PATCH 085/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index faf2d35..c978398 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -97,6 +97,15 @@ "ideas", "maintenance" ] + }, + { + "login": "StoyanDimitrov", + "name": "StoyanDimitrov", + "avatar_url": "https://avatars1.githubusercontent.com/u/10962709?v=4", + "profile": "https://github.com/StoyanDimitrov", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From e95233bb5fde6209b5461fe5a8a3b3cf321d450a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:44:16 +0000 Subject: [PATCH 086/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b566436..44a1087 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -132,6 +132,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Paul Jimenez
Paul Jimenez

🚇 💻 yparitcher
yparitcher

🤔 🚧 StoyanDimitrov
StoyanDimitrov

📖 + alphathegeek
alphathegeek

🤔 From 9be192b82f261a99a7ba7bd038d9ac844bcdef0f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:44:17 +0000 Subject: [PATCH 087/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index c978398..d02bbc0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -106,6 +106,15 @@ "contributions": [ "doc" ] + }, + { + "login": "alphathegeek", + "name": "alphathegeek", + "avatar_url": "https://avatars2.githubusercontent.com/u/51253712?v=4", + "profile": "https://github.com/alphathegeek", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, From e854184a112214d63561aed82431f9db3d781843 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:45:56 +0000 Subject: [PATCH 088/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44a1087..411c226 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -133,6 +133,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d yparitcher
yparitcher

🤔 🚧 StoyanDimitrov
StoyanDimitrov

📖 alphathegeek
alphathegeek

🤔 + Radoslav Gerganov
Radoslav Gerganov

🤔 💻 From a760bed29f23d0a43570fbf61e9e5a2de5e42a93 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:45:57 +0000 Subject: [PATCH 089/139] docs: update .all-contributorsrc --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d02bbc0..edb6b75 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -115,6 +115,16 @@ "contributions": [ "ideas" ] + }, + { + "login": "rgerganov", + "name": "Radoslav Gerganov", + "avatar_url": "https://avatars2.githubusercontent.com/u/271616?v=4", + "profile": "https://xakcop.com", + "contributions": [ + "ideas", + "code" + ] } ], "contributorsPerLine": 7, From 2e14ac5b11c6982ad93fb1319da1e05107367dc8 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:49:28 +0000 Subject: [PATCH 090/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44a1087..3aabaff 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -133,6 +133,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d yparitcher
yparitcher

🤔 🚧 StoyanDimitrov
StoyanDimitrov

📖 alphathegeek
alphathegeek

🤔 + Manuel Domke
Manuel Domke

🤔 💻 From 53275e018266c8cf27d0ebddf76516eb5bd91b1c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:49:29 +0000 Subject: [PATCH 091/139] docs: update .all-contributorsrc --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d02bbc0..6658c21 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -115,6 +115,16 @@ "contributions": [ "ideas" ] + }, + { + "login": "manuel-domke", + "name": "Manuel Domke", + "avatar_url": "https://avatars3.githubusercontent.com/u/10274356?v=4", + "profile": "http://13-37.org", + "contributions": [ + "ideas", + "code" + ] } ], "contributorsPerLine": 7, From d350f9a63831c4b60b1327bf28651f05cac6c1d4 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Thu, 8 Aug 2019 00:50:53 +0200 Subject: [PATCH 092/139] Update .all-contributorsrc --- .all-contributorsrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6658c21..5c8c414 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -123,7 +123,8 @@ "profile": "http://13-37.org", "contributions": [ "ideas", - "code" + "code", + "business", ] } ], From c050b8221017ea3cc5e809714ceaf5e1449f40f5 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Thu, 8 Aug 2019 00:53:23 +0200 Subject: [PATCH 093/139] editing fail --- .all-contributorsrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 5c8c414..cd20b4f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -124,7 +124,7 @@ "contributions": [ "ideas", "code", - "business", + "business" ] } ], From b2ac7b5479fff0060a03f4dcac290e6d80b55664 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:53:46 +0000 Subject: [PATCH 094/139] docs: update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3aabaff..6ff35bc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -133,7 +133,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d yparitcher
yparitcher

🤔 🚧 StoyanDimitrov
StoyanDimitrov

📖 alphathegeek
alphathegeek

🤔 - Manuel Domke
Manuel Domke

🤔 💻 + Manuel Domke
Manuel Domke

🤔 💻 💼 + Piotr Esden-Tempski
Piotr Esden-Tempski

💼 From d630254224102c39a3e62256a0a65203fb54cb12 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:53:47 +0000 Subject: [PATCH 095/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index cd20b4f..0ba7c08 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -126,6 +126,15 @@ "code", "business" ] + }, + { + "login": "esden", + "name": "Piotr Esden-Tempski", + "avatar_url": "https://avatars3.githubusercontent.com/u/17334?v=4", + "profile": "http://1bitsquared.com", + "contributions": [ + "business" + ] } ], "contributorsPerLine": 7, From 11763b6502f86a4705fd0b13fa6b00c72f38b321 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:59:26 +0000 Subject: [PATCH 096/139] docs: update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2109eea..c9de224 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -135,7 +135,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d alphathegeek
alphathegeek

🤔 Radoslav Gerganov
Radoslav Gerganov

🤔 💻 Manuel Domke
Manuel Domke

🤔 💻 💼 + + Piotr Esden-Tempski
Piotr Esden-Tempski

💼 + f.m3hm00d
f.m3hm00d

📖 From f7fd30b9ae37c9a857cea535c8420d09e48bdb13 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 22:59:27 +0000 Subject: [PATCH 097/139] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index c9041c6..3288aba 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -145,6 +145,15 @@ "contributions": [ "business" ] + }, + { + "login": "m3hm00d", + "name": "f.m3hm00d", + "avatar_url": "https://avatars1.githubusercontent.com/u/42179593?v=4", + "profile": "https://github.com/m3hm00d", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, From ea1969ca942fe95a12ebef701b04f54d2f9da151 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 23:05:01 +0000 Subject: [PATCH 098/139] docs: update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9de224..a4a37a6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) [![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) @@ -139,6 +139,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Piotr Esden-Tempski
Piotr Esden-Tempski

💼 f.m3hm00d
f.m3hm00d

📖 + Richard Hughes
Richard Hughes

🤔 💻 🚇 🔧 From f6c7dbdbdda45740f7eb037b54d550a569ba801b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 23:05:02 +0000 Subject: [PATCH 099/139] docs: update .all-contributorsrc --- .all-contributorsrc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3288aba..ff4bd9b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -154,6 +154,18 @@ "contributions": [ "doc" ] + }, + { + "login": "hughsie", + "name": "Richard Hughes", + "avatar_url": "https://avatars0.githubusercontent.com/u/151380?v=4", + "profile": "http://blogs.gnome.org/hughsie/", + "contributions": [ + "ideas", + "code", + "infra", + "tool" + ] } ], "contributorsPerLine": 7, From e0955c190ea9c751e154f28ca0aada28747aee9d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 23:14:17 +0000 Subject: [PATCH 100/139] docs: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4a37a6..dae22e1 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - + From e1528cb248538fa002bbd9d842c7ecb776aececc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 23:14:18 +0000 Subject: [PATCH 101/139] docs: update .all-contributorsrc --- .all-contributorsrc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index ff4bd9b..aed6148 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -11,7 +11,9 @@ "avatar_url": "https://avatars0.githubusercontent.com/u/17005426?v=4", "profile": "https://github.com/szszszsz", "contributions": [ - "code" + "code", + "doc", + "ideas" ] }, { From 349a84dcec87c2a040a8d0c71503f11ff163f2b3 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 17:59:31 +0800 Subject: [PATCH 102/139] cache button press for 2s --- targets/stm32l432/build/common.mk | 2 +- targets/stm32l432/lib/stm32l4xx_ll_exti.c | 290 +++++ targets/stm32l432/lib/stm32l4xx_ll_exti.h | 1361 +++++++++++++++++++++ targets/stm32l432/src/device.c | 11 + targets/stm32l432/src/init.c | 26 +- 5 files changed, 1675 insertions(+), 15 deletions(-) create mode 100644 targets/stm32l432/lib/stm32l4xx_ll_exti.c create mode 100644 targets/stm32l432/lib/stm32l4xx_ll_exti.h diff --git a/targets/stm32l432/build/common.mk b/targets/stm32l432/build/common.mk index 7c0ae32..9e09ffa 100644 --- a/targets/stm32l432/build/common.mk +++ b/targets/stm32l432/build/common.mk @@ -6,7 +6,7 @@ AR=$(PREFIX)arm-none-eabi-ar DRIVER_LIBS := lib/stm32l4xx_hal_pcd.c lib/stm32l4xx_hal_pcd_ex.c lib/stm32l4xx_ll_gpio.c \ lib/stm32l4xx_ll_rcc.c lib/stm32l4xx_ll_rng.c lib/stm32l4xx_ll_tim.c \ lib/stm32l4xx_ll_usb.c lib/stm32l4xx_ll_utils.c lib/stm32l4xx_ll_pwr.c \ - lib/stm32l4xx_ll_usart.c lib/stm32l4xx_ll_spi.c + lib/stm32l4xx_ll_usart.c lib/stm32l4xx_ll_spi.c lib/stm32l4xx_ll_exti.c USB_LIB := lib/usbd/usbd_cdc.c lib/usbd/usbd_cdc_if.c lib/usbd/usbd_composite.c \ lib/usbd/usbd_conf.c lib/usbd/usbd_core.c lib/usbd/usbd_ioreq.c \ diff --git a/targets/stm32l432/lib/stm32l4xx_ll_exti.c b/targets/stm32l432/lib/stm32l4xx_ll_exti.c new file mode 100644 index 0000000..5c52247 --- /dev/null +++ b/targets/stm32l432/lib/stm32l4xx_ll_exti.c @@ -0,0 +1,290 @@ +/** + ****************************************************************************** + * @file stm32l4xx_ll_exti.c + * @author MCD Application Team + * @brief EXTI LL module driver. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2017 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ +#if defined(USE_FULL_LL_DRIVER) + +/* Includes ------------------------------------------------------------------*/ +#include "stm32l4xx_ll_exti.h" +#ifdef USE_FULL_ASSERT +#include "stm32_assert.h" +#else +#define assert_param(expr) ((void)0U) +#endif + +/** @addtogroup STM32L4xx_LL_Driver + * @{ + */ + +#if defined (EXTI) + +/** @defgroup EXTI_LL EXTI + * @{ + */ + +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/* Private macros ------------------------------------------------------------*/ +/** @addtogroup EXTI_LL_Private_Macros + * @{ + */ + +#define IS_LL_EXTI_LINE_0_31(__VALUE__) (((__VALUE__) & ~LL_EXTI_LINE_ALL_0_31) == 0x00000000U) +#define IS_LL_EXTI_LINE_32_63(__VALUE__) (((__VALUE__) & ~LL_EXTI_LINE_ALL_32_63) == 0x00000000U) + +#define IS_LL_EXTI_MODE(__VALUE__) (((__VALUE__) == LL_EXTI_MODE_IT) \ + || ((__VALUE__) == LL_EXTI_MODE_EVENT) \ + || ((__VALUE__) == LL_EXTI_MODE_IT_EVENT)) + + +#define IS_LL_EXTI_TRIGGER(__VALUE__) (((__VALUE__) == LL_EXTI_TRIGGER_NONE) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_RISING) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_FALLING) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_RISING_FALLING)) + +/** + * @} + */ + +/* Private function prototypes -----------------------------------------------*/ + +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup EXTI_LL_Exported_Functions + * @{ + */ + +/** @addtogroup EXTI_LL_EF_Init + * @{ + */ + +/** + * @brief De-initialize the EXTI registers to their default reset values. + * @retval An ErrorStatus enumeration value: + * - 0x00: EXTI registers are de-initialized + */ +uint32_t LL_EXTI_DeInit(void) +{ + /* Interrupt mask register set to default reset values */ + LL_EXTI_WriteReg(IMR1, 0xFF820000U); + /* Event mask register set to default reset values */ + LL_EXTI_WriteReg(EMR1, 0x00000000U); + /* Rising Trigger selection register set to default reset values */ + LL_EXTI_WriteReg(RTSR1, 0x00000000U); + /* Falling Trigger selection register set to default reset values */ + LL_EXTI_WriteReg(FTSR1, 0x00000000U); + /* Software interrupt event register set to default reset values */ + LL_EXTI_WriteReg(SWIER1, 0x00000000U); + /* Pending register clear */ + LL_EXTI_WriteReg(PR1, 0x007DFFFFU); + + /* Interrupt mask register 2 set to default reset values */ +#if defined(LL_EXTI_LINE_40) + LL_EXTI_WriteReg(IMR2, 0x00000187U); +#else + LL_EXTI_WriteReg(IMR2, 0x00000087U); +#endif + /* Event mask register 2 set to default reset values */ + LL_EXTI_WriteReg(EMR2, 0x00000000U); + /* Rising Trigger selection register 2 set to default reset values */ + LL_EXTI_WriteReg(RTSR2, 0x00000000U); + /* Falling Trigger selection register 2 set to default reset values */ + LL_EXTI_WriteReg(FTSR2, 0x00000000U); + /* Software interrupt event register 2 set to default reset values */ + LL_EXTI_WriteReg(SWIER2, 0x00000000U); + /* Pending register 2 clear */ + LL_EXTI_WriteReg(PR2, 0x00000078U); + + return 0x00u; +} + +/** + * @brief Initialize the EXTI registers according to the specified parameters in EXTI_InitStruct. + * @param EXTI_InitStruct pointer to a @ref LL_EXTI_InitTypeDef structure. + * @retval An ErrorStatus enumeration value: + * - 0x00: EXTI registers are initialized + * - any other calue : wrong configuration + */ +uint32_t LL_EXTI_Init(LL_EXTI_InitTypeDef *EXTI_InitStruct) +{ + uint32_t status = 0x00u; + + /* Check the parameters */ + assert_param(IS_LL_EXTI_LINE_0_31(EXTI_InitStruct->Line_0_31)); + assert_param(IS_LL_EXTI_LINE_32_63(EXTI_InitStruct->Line_32_63)); + assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->LineCommand)); + assert_param(IS_LL_EXTI_MODE(EXTI_InitStruct->Mode)); + + /* ENABLE LineCommand */ + if (EXTI_InitStruct->LineCommand != DISABLE) + { + assert_param(IS_LL_EXTI_TRIGGER(EXTI_InitStruct->Trigger)); + + /* Configure EXTI Lines in range from 0 to 31 */ + if (EXTI_InitStruct->Line_0_31 != LL_EXTI_LINE_NONE) + { + switch (EXTI_InitStruct->Mode) + { + case LL_EXTI_MODE_IT: + /* First Disable Event on provided Lines */ + LL_EXTI_DisableEvent_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableIT_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_MODE_EVENT: + /* First Disable IT on provided Lines */ + LL_EXTI_DisableIT_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Event on provided Lines */ + LL_EXTI_EnableEvent_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_MODE_IT_EVENT: + /* Directly Enable IT & Event on provided Lines */ + LL_EXTI_EnableIT_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_EnableEvent_0_31(EXTI_InitStruct->Line_0_31); + break; + default: + status = 0x01u; + break; + } + if (EXTI_InitStruct->Trigger != LL_EXTI_TRIGGER_NONE) + { + switch (EXTI_InitStruct->Trigger) + { + case LL_EXTI_TRIGGER_RISING: + /* First Disable Falling Trigger on provided Lines */ + LL_EXTI_DisableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Rising Trigger on provided Lines */ + LL_EXTI_EnableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_TRIGGER_FALLING: + /* First Disable Rising Trigger on provided Lines */ + LL_EXTI_DisableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Falling Trigger on provided Lines */ + LL_EXTI_EnableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_TRIGGER_RISING_FALLING: + LL_EXTI_EnableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_EnableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + default: + status |= 0x02u; + break; + } + } + } + /* Configure EXTI Lines in range from 32 to 63 */ + if (EXTI_InitStruct->Line_32_63 != LL_EXTI_LINE_NONE) + { + switch (EXTI_InitStruct->Mode) + { + case LL_EXTI_MODE_IT: + /* First Disable Event on provided Lines */ + LL_EXTI_DisableEvent_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableIT_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_MODE_EVENT: + /* First Disable IT on provided Lines */ + LL_EXTI_DisableIT_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable Event on provided Lines */ + LL_EXTI_EnableEvent_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_MODE_IT_EVENT: + /* Directly Enable IT & Event on provided Lines */ + LL_EXTI_EnableIT_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_EnableEvent_32_63(EXTI_InitStruct->Line_32_63); + break; + default: + status |= 0x04u; + break; + } + if (EXTI_InitStruct->Trigger != LL_EXTI_TRIGGER_NONE) + { + switch (EXTI_InitStruct->Trigger) + { + case LL_EXTI_TRIGGER_RISING: + /* First Disable Falling Trigger on provided Lines */ + LL_EXTI_DisableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_TRIGGER_FALLING: + /* First Disable Rising Trigger on provided Lines */ + LL_EXTI_DisableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable Falling Trigger on provided Lines */ + LL_EXTI_EnableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_TRIGGER_RISING_FALLING: + LL_EXTI_EnableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_EnableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + default: + status = ERROR; + break; + } + } + } + } + /* DISABLE LineCommand */ + else + { + /* De-configure EXTI Lines in range from 0 to 31 */ + LL_EXTI_DisableIT_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_DisableEvent_0_31(EXTI_InitStruct->Line_0_31); + /* De-configure EXTI Lines in range from 32 to 63 */ + LL_EXTI_DisableIT_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_DisableEvent_32_63(EXTI_InitStruct->Line_32_63); + } + + return status; +} + +/** + * @brief Set each @ref LL_EXTI_InitTypeDef field to default value. + * @param EXTI_InitStruct Pointer to a @ref LL_EXTI_InitTypeDef structure. + * @retval None + */ +void LL_EXTI_StructInit(LL_EXTI_InitTypeDef *EXTI_InitStruct) +{ + EXTI_InitStruct->Line_0_31 = LL_EXTI_LINE_NONE; + EXTI_InitStruct->Line_32_63 = LL_EXTI_LINE_NONE; + EXTI_InitStruct->LineCommand = DISABLE; + EXTI_InitStruct->Mode = LL_EXTI_MODE_IT; + EXTI_InitStruct->Trigger = LL_EXTI_TRIGGER_FALLING; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +#endif /* defined (EXTI) */ + +/** + * @} + */ + +#endif /* USE_FULL_LL_DRIVER */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/stm32l432/lib/stm32l4xx_ll_exti.h b/targets/stm32l432/lib/stm32l4xx_ll_exti.h new file mode 100644 index 0000000..245f7fd --- /dev/null +++ b/targets/stm32l432/lib/stm32l4xx_ll_exti.h @@ -0,0 +1,1361 @@ +/** + ****************************************************************************** + * @file stm32l4xx_ll_exti.h + * @author MCD Application Team + * @brief Header file of EXTI LL module. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2017 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32L4xx_LL_EXTI_H +#define __STM32L4xx_LL_EXTI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32l4xx.h" + +/** @addtogroup STM32L4xx_LL_Driver + * @{ + */ + +#if defined (EXTI) + +/** @defgroup EXTI_LL EXTI + * @{ + */ + +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/* Private Macros ------------------------------------------------------------*/ +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_Private_Macros EXTI Private Macros + * @{ + */ +/** + * @} + */ +#endif /*USE_FULL_LL_DRIVER*/ +/* Exported types ------------------------------------------------------------*/ +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_ES_INIT EXTI Exported Init structure + * @{ + */ +typedef struct +{ + + uint32_t Line_0_31; /*!< Specifies the EXTI lines to be enabled or disabled for Lines in range 0 to 31 + This parameter can be any combination of @ref EXTI_LL_EC_LINE */ + + uint32_t Line_32_63; /*!< Specifies the EXTI lines to be enabled or disabled for Lines in range 32 to 63 + This parameter can be any combination of @ref EXTI_LL_EC_LINE */ + + FunctionalState LineCommand; /*!< Specifies the new state of the selected EXTI lines. + This parameter can be set either to ENABLE or DISABLE */ + + uint8_t Mode; /*!< Specifies the mode for the EXTI lines. + This parameter can be a value of @ref EXTI_LL_EC_MODE. */ + + uint8_t Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines. + This parameter can be a value of @ref EXTI_LL_EC_TRIGGER. */ +} LL_EXTI_InitTypeDef; + +/** + * @} + */ +#endif /*USE_FULL_LL_DRIVER*/ + +/* Exported constants --------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Constants EXTI Exported Constants + * @{ + */ + +/** @defgroup EXTI_LL_EC_LINE LINE + * @{ + */ +#define LL_EXTI_LINE_0 EXTI_IMR1_IM0 /*!< Extended line 0 */ +#define LL_EXTI_LINE_1 EXTI_IMR1_IM1 /*!< Extended line 1 */ +#define LL_EXTI_LINE_2 EXTI_IMR1_IM2 /*!< Extended line 2 */ +#define LL_EXTI_LINE_3 EXTI_IMR1_IM3 /*!< Extended line 3 */ +#define LL_EXTI_LINE_4 EXTI_IMR1_IM4 /*!< Extended line 4 */ +#define LL_EXTI_LINE_5 EXTI_IMR1_IM5 /*!< Extended line 5 */ +#define LL_EXTI_LINE_6 EXTI_IMR1_IM6 /*!< Extended line 6 */ +#define LL_EXTI_LINE_7 EXTI_IMR1_IM7 /*!< Extended line 7 */ +#define LL_EXTI_LINE_8 EXTI_IMR1_IM8 /*!< Extended line 8 */ +#define LL_EXTI_LINE_9 EXTI_IMR1_IM9 /*!< Extended line 9 */ +#define LL_EXTI_LINE_10 EXTI_IMR1_IM10 /*!< Extended line 10 */ +#define LL_EXTI_LINE_11 EXTI_IMR1_IM11 /*!< Extended line 11 */ +#define LL_EXTI_LINE_12 EXTI_IMR1_IM12 /*!< Extended line 12 */ +#define LL_EXTI_LINE_13 EXTI_IMR1_IM13 /*!< Extended line 13 */ +#define LL_EXTI_LINE_14 EXTI_IMR1_IM14 /*!< Extended line 14 */ +#define LL_EXTI_LINE_15 EXTI_IMR1_IM15 /*!< Extended line 15 */ +#if defined(EXTI_IMR1_IM16) +#define LL_EXTI_LINE_16 EXTI_IMR1_IM16 /*!< Extended line 16 */ +#endif +#define LL_EXTI_LINE_17 EXTI_IMR1_IM17 /*!< Extended line 17 */ +#if defined(EXTI_IMR1_IM18) +#define LL_EXTI_LINE_18 EXTI_IMR1_IM18 /*!< Extended line 18 */ +#endif +#define LL_EXTI_LINE_19 EXTI_IMR1_IM19 /*!< Extended line 19 */ +#if defined(EXTI_IMR1_IM20) +#define LL_EXTI_LINE_20 EXTI_IMR1_IM20 /*!< Extended line 20 */ +#endif +#if defined(EXTI_IMR1_IM21) +#define LL_EXTI_LINE_21 EXTI_IMR1_IM21 /*!< Extended line 21 */ +#endif +#if defined(EXTI_IMR1_IM22) +#define LL_EXTI_LINE_22 EXTI_IMR1_IM22 /*!< Extended line 22 */ +#endif +#define LL_EXTI_LINE_23 EXTI_IMR1_IM23 /*!< Extended line 23 */ +#if defined(EXTI_IMR1_IM24) +#define LL_EXTI_LINE_24 EXTI_IMR1_IM24 /*!< Extended line 24 */ +#endif +#if defined(EXTI_IMR1_IM25) +#define LL_EXTI_LINE_25 EXTI_IMR1_IM25 /*!< Extended line 25 */ +#endif +#if defined(EXTI_IMR1_IM26) +#define LL_EXTI_LINE_26 EXTI_IMR1_IM26 /*!< Extended line 26 */ +#endif +#if defined(EXTI_IMR1_IM27) +#define LL_EXTI_LINE_27 EXTI_IMR1_IM27 /*!< Extended line 27 */ +#endif +#if defined(EXTI_IMR1_IM28) +#define LL_EXTI_LINE_28 EXTI_IMR1_IM28 /*!< Extended line 28 */ +#endif +#if defined(EXTI_IMR1_IM29) +#define LL_EXTI_LINE_29 EXTI_IMR1_IM29 /*!< Extended line 29 */ +#endif +#if defined(EXTI_IMR1_IM30) +#define LL_EXTI_LINE_30 EXTI_IMR1_IM30 /*!< Extended line 30 */ +#endif +#if defined(EXTI_IMR1_IM31) +#define LL_EXTI_LINE_31 EXTI_IMR1_IM31 /*!< Extended line 31 */ +#endif +#define LL_EXTI_LINE_ALL_0_31 EXTI_IMR1_IM /*!< All Extended line not reserved*/ + +#define LL_EXTI_LINE_32 EXTI_IMR2_IM32 /*!< Extended line 32 */ +#if defined(EXTI_IMR2_IM33) +#define LL_EXTI_LINE_33 EXTI_IMR2_IM33 /*!< Extended line 33 */ +#endif +#if defined(EXTI_IMR2_IM34) +#define LL_EXTI_LINE_34 EXTI_IMR2_IM34 /*!< Extended line 34 */ +#endif +#if defined(EXTI_IMR2_IM35) +#define LL_EXTI_LINE_35 EXTI_IMR2_IM35 /*!< Extended line 35 */ +#endif +#if defined(EXTI_IMR2_IM36) +#define LL_EXTI_LINE_36 EXTI_IMR2_IM36 /*!< Extended line 36 */ +#endif +#if defined(EXTI_IMR2_IM37) +#define LL_EXTI_LINE_37 EXTI_IMR2_IM37 /*!< Extended line 37 */ +#endif +#if defined(EXTI_IMR2_IM38) +#define LL_EXTI_LINE_38 EXTI_IMR2_IM38 /*!< Extended line 38 */ +#endif +#if defined(EXTI_IMR2_IM39) +#define LL_EXTI_LINE_39 EXTI_IMR2_IM39 /*!< Extended line 39 */ +#endif +#if defined(EXTI_IMR2_IM40) +#define LL_EXTI_LINE_40 EXTI_IMR2_IM40 /*!< Extended line 40 */ +#endif +#define LL_EXTI_LINE_ALL_32_63 EXTI_IMR2_IM /*!< All Extended line not reserved*/ + + +#define LL_EXTI_LINE_ALL (0xFFFFFFFFU) /*!< All Extended line */ + +#if defined(USE_FULL_LL_DRIVER) +#define LL_EXTI_LINE_NONE (0x00000000U) /*!< None Extended line */ +#endif /*USE_FULL_LL_DRIVER*/ + +/** + * @} + */ + + +#if defined(USE_FULL_LL_DRIVER) + +/** @defgroup EXTI_LL_EC_MODE Mode + * @{ + */ +#define LL_EXTI_MODE_IT ((uint8_t)0x00U) /*!< Interrupt Mode */ +#define LL_EXTI_MODE_EVENT ((uint8_t)0x01U) /*!< Event Mode */ +#define LL_EXTI_MODE_IT_EVENT ((uint8_t)0x02U) /*!< Interrupt & Event Mode */ +/** + * @} + */ + +/** @defgroup EXTI_LL_EC_TRIGGER Edge Trigger + * @{ + */ +#define LL_EXTI_TRIGGER_NONE ((uint8_t)0x00U) /*!< No Trigger Mode */ +#define LL_EXTI_TRIGGER_RISING ((uint8_t)0x01U) /*!< Trigger Rising Mode */ +#define LL_EXTI_TRIGGER_FALLING ((uint8_t)0x02U) /*!< Trigger Falling Mode */ +#define LL_EXTI_TRIGGER_RISING_FALLING ((uint8_t)0x03U) /*!< Trigger Rising & Falling Mode */ + +/** + * @} + */ + + +#endif /*USE_FULL_LL_DRIVER*/ + + +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Macros EXTI Exported Macros + * @{ + */ + +/** @defgroup EXTI_LL_EM_WRITE_READ Common Write and read registers Macros + * @{ + */ + +/** + * @brief Write a value in EXTI register + * @param __REG__ Register to be written + * @param __VALUE__ Value to be written in the register + * @retval None + */ +#define LL_EXTI_WriteReg(__REG__, __VALUE__) WRITE_REG(EXTI->__REG__, (__VALUE__)) + +/** + * @brief Read a value in EXTI register + * @param __REG__ Register to be read + * @retval Register value + */ +#define LL_EXTI_ReadReg(__REG__) READ_REG(EXTI->__REG__) +/** + * @} + */ + + +/** + * @} + */ + + + +/* Exported functions --------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Functions EXTI Exported Functions + * @{ + */ +/** @defgroup EXTI_LL_EF_IT_Management IT_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Interrupt request for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_EnableIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableIT_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->IMR1, ExtiLine); +} +/** + * @brief Enable ExtiLine Interrupt request for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_EnableIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableIT_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->IMR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Interrupt request for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_DisableIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableIT_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->IMR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Interrupt request for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_DisableIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableIT_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->IMR2, ExtiLine); +} + +/** + * @brief Indicate if ExtiLine Interrupt request is enabled for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_IsEnabledIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledIT_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->IMR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Indicate if ExtiLine Interrupt request is enabled for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_IsEnabledIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledIT_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->IMR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Event_Management Event_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Event request for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_EnableEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableEvent_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->EMR1, ExtiLine); + +} + +/** + * @brief Enable ExtiLine Event request for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_EnableEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableEvent_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->EMR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Event request for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_DisableEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableEvent_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->EMR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Event request for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_DisableEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableEvent_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->EMR2, ExtiLine); +} + +/** + * @brief Indicate if ExtiLine Event request is enabled for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_IsEnabledEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledEvent_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->EMR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); + +} + +/** + * @brief Indicate if ExtiLine Event request is enabled for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_IsEnabledEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledEvent_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->EMR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Rising_Trigger_Management Rising_Trigger_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Rising Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR1 RTx LL_EXTI_EnableRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableRisingTrig_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->RTSR1, ExtiLine); + +} + +/** + * @brief Enable ExtiLine Rising Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set.Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR2 RTx LL_EXTI_EnableRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableRisingTrig_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->RTSR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Rising Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR1 RTx LL_EXTI_DisableRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableRisingTrig_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->RTSR1, ExtiLine); + +} + +/** + * @brief Disable ExtiLine Rising Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR2 RTx LL_EXTI_DisableRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableRisingTrig_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->RTSR2, ExtiLine); +} + +/** + * @brief Check if rising edge trigger is enabled for Lines in range 0 to 31 + * @rmtoll RTSR1 RTx LL_EXTI_IsEnabledRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledRisingTrig_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->RTSR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if rising edge trigger is enabled for Lines in range 32 to 63 + * @rmtoll RTSR2 RTx LL_EXTI_IsEnabledRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledRisingTrig_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->RTSR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Falling_Trigger_Management Falling_Trigger_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Falling Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll FTSR1 FTx LL_EXTI_EnableFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableFallingTrig_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->FTSR1, ExtiLine); +} + +/** + * @brief Enable ExtiLine Falling Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll FTSR2 FTx LL_EXTI_EnableFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableFallingTrig_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->FTSR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Falling Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for the same interrupt line. + * In this case, both generate a trigger condition. + * @rmtoll FTSR1 FTx LL_EXTI_DisableFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableFallingTrig_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->FTSR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Falling Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for the same interrupt line. + * In this case, both generate a trigger condition. + * @rmtoll FTSR2 FTx LL_EXTI_DisableFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableFallingTrig_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->FTSR2, ExtiLine); +} + +/** + * @brief Check if falling edge trigger is enabled for Lines in range 0 to 31 + * @rmtoll FTSR1 FTx LL_EXTI_IsEnabledFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledFallingTrig_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->FTSR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if falling edge trigger is enabled for Lines in range 32 to 63 + * @rmtoll FTSR2 FTx LL_EXTI_IsEnabledFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledFallingTrig_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->FTSR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Software_Interrupt_Management Software_Interrupt_Management + * @{ + */ + +/** + * @brief Generate a software Interrupt Event for Lines in range 0 to 31 + * @note If the interrupt is enabled on this line in the EXTI_IMR1, writing a 1 to + * this bit when it is at '0' sets the corresponding pending bit in EXTI_PR1 + * resulting in an interrupt request generation. + * This bit is cleared by clearing the corresponding bit in the EXTI_PR1 + * register (by writing a 1 into the bit) + * @rmtoll SWIER1 SWIx LL_EXTI_GenerateSWI_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_GenerateSWI_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->SWIER1, ExtiLine); +} + +/** + * @brief Generate a software Interrupt Event for Lines in range 32 to 63 + * @note If the interrupt is enabled on this line inthe EXTI_IMR2, writing a 1 to + * this bit when it is at '0' sets the corresponding pending bit in EXTI_PR2 + * resulting in an interrupt request generation. + * This bit is cleared by clearing the corresponding bit in the EXTI_PR2 + * register (by writing a 1 into the bit) + * @rmtoll SWIER2 SWIx LL_EXTI_GenerateSWI_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_GenerateSWI_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->SWIER2, ExtiLine); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Flag_Management Flag_Management + * @{ + */ + +/** + * @brief Check if the ExtLine Flag is set or not for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_IsActiveFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsActiveFlag_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->PR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if the ExtLine Flag is set or not for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_IsActiveFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsActiveFlag_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->PR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Read ExtLine Combination Flag for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_ReadFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval @note This bit is set when the selected edge event arrives on the interrupt + */ +__STATIC_INLINE uint32_t LL_EXTI_ReadFlag_0_31(uint32_t ExtiLine) +{ + return (uint32_t)(READ_BIT(EXTI->PR1, ExtiLine)); +} + +/** + * @brief Read ExtLine Combination Flag for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_ReadFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval @note This bit is set when the selected edge event arrives on the interrupt + */ +__STATIC_INLINE uint32_t LL_EXTI_ReadFlag_32_63(uint32_t ExtiLine) +{ + return (uint32_t)(READ_BIT(EXTI->PR2, ExtiLine)); +} + +/** + * @brief Clear ExtLine Flags for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_ClearFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_ClearFlag_0_31(uint32_t ExtiLine) +{ + WRITE_REG(EXTI->PR1, ExtiLine); +} + +/** + * @brief Clear ExtLine Flags for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_ClearFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_ClearFlag_32_63(uint32_t ExtiLine) +{ + WRITE_REG(EXTI->PR2, ExtiLine); +} + + +/** + * @} + */ + +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_EF_Init Initialization and de-initialization functions + * @{ + */ + +uint32_t LL_EXTI_Init(LL_EXTI_InitTypeDef *EXTI_InitStruct); +uint32_t LL_EXTI_DeInit(void); +void LL_EXTI_StructInit(LL_EXTI_InitTypeDef *EXTI_InitStruct); + + +/** + * @} + */ +#endif /* USE_FULL_LL_DRIVER */ + +/** + * @} + */ + +/** + * @} + */ + +#endif /* EXTI */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32L4xx_LL_EXTI_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index 6289189..3618563 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -38,6 +38,7 @@ void wait_for_usb_tether(); uint32_t __90_ms = 0; +uint32_t __last_button_press_time = 0; uint32_t __device_status = 0; uint32_t __last_update = 0; extern PCD_HandleTypeDef hpcd; @@ -83,6 +84,11 @@ void TIM6_DAC_IRQHandler() } #endif } +void EXTI0_IRQHandler(void) +{ + EXTI->PR1 = EXTI->PR1; + __last_button_press_time = millis(); +} // Global USB interrupt handler void USB_IRQHandler(void) @@ -500,6 +506,11 @@ int ctap_user_presence_test(uint32_t up_delay) { return 1; } + // "cache" button presses for 2 seconds. + if (millis() - __last_button_press_time < 2000) + { + return 1; + } #if SKIP_BUTTON_CHECK_WITH_DELAY int i=500; while(i--) diff --git a/targets/stm32l432/src/init.c b/targets/stm32l432/src/init.c index e7fa953..d51cf94 100644 --- a/targets/stm32l432/src/init.c +++ b/targets/stm32l432/src/init.c @@ -20,6 +20,7 @@ #include "stm32l4xx_ll_rng.h" #include "stm32l4xx_ll_spi.h" #include "stm32l4xx_ll_usb.h" +#include "stm32l4xx_ll_exti.h" #include "stm32l4xx_hal_pcd.h" #include "stm32l4xx_hal.h" @@ -849,20 +850,17 @@ void init_gpio(void) LL_GPIO_SetPinMode(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_MODE_INPUT); LL_GPIO_SetPinPull(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_PULL_UP); -#ifdef SOLO_AMS_IRQ_PORT -// SAVE POWER - // LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); - // /**/ - // LL_GPIO_InitTypeDef GPIO_InitStruct; - // GPIO_InitStruct.Pin = SOLO_AMS_IRQ_PIN; - // GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT; - // GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - // LL_GPIO_Init(SOLO_AMS_IRQ_PORT, &GPIO_InitStruct); - // - // - // LL_GPIO_SetPinMode(SOLO_AMS_IRQ_PORT,SOLO_AMS_IRQ_PIN,LL_GPIO_MODE_INPUT); - // LL_GPIO_SetPinPull(SOLO_AMS_IRQ_PORT,SOLO_AMS_IRQ_PIN,LL_GPIO_PULL_UP); -#endif + + LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTA, LL_SYSCFG_EXTI_LINE0); + LL_EXTI_InitTypeDef EXTI_InitStruct; + EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_0; // GPIOA_0 + EXTI_InitStruct.Line_32_63 = LL_EXTI_LINE_NONE; + EXTI_InitStruct.LineCommand = ENABLE; + EXTI_InitStruct.Mode = LL_EXTI_MODE_IT; + EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_RISING; + LL_EXTI_Init(&EXTI_InitStruct); + + NVIC_EnableIRQ(EXTI0_IRQn); } From ffd854a303cbcd7e711ed21472bcea35d81b5170 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 18:05:08 +0800 Subject: [PATCH 103/139] only 1 user presence auth per button press --- targets/stm32l432/src/device.c | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index 3618563..d211bdd 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -509,6 +509,7 @@ int ctap_user_presence_test(uint32_t up_delay) // "cache" button presses for 2 seconds. if (millis() - __last_button_press_time < 2000) { + __last_button_press_time = 0; return 1; } #if SKIP_BUTTON_CHECK_WITH_DELAY From b5d3276df61cd62045832d28c83f7d2d31100101 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 18:16:58 +0800 Subject: [PATCH 104/139] not for bootloader --- targets/stm32l432/src/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/init.c b/targets/stm32l432/src/init.c index d51cf94..60f820d 100644 --- a/targets/stm32l432/src/init.c +++ b/targets/stm32l432/src/init.c @@ -850,7 +850,7 @@ void init_gpio(void) LL_GPIO_SetPinMode(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_MODE_INPUT); LL_GPIO_SetPinPull(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_PULL_UP); - +#ifndef IS_BOOTLOADER LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTA, LL_SYSCFG_EXTI_LINE0); LL_EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_0; // GPIOA_0 @@ -861,6 +861,7 @@ void init_gpio(void) LL_EXTI_Init(&EXTI_InitStruct); NVIC_EnableIRQ(EXTI0_IRQn); +#endif } From 36aec9f20b177d4bc684a6076afc4fba7f8c6942 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 12 Aug 2019 16:18:30 +0800 Subject: [PATCH 105/139] separate interface into two and add "IAD" descriptor --- targets/stm32l432/lib/usbd/usbd_composite.c | 87 +++++++++++++++------ 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/targets/stm32l432/lib/usbd/usbd_composite.c b/targets/stm32l432/lib/usbd/usbd_composite.c index a1a868c..182f3cc 100644 --- a/targets/stm32l432/lib/usbd/usbd_composite.c +++ b/targets/stm32l432/lib/usbd/usbd_composite.c @@ -26,16 +26,18 @@ static uint8_t *USBD_Composite_GetOtherSpeedCfgDesc (uint16_t *length); static uint8_t *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length); -#define NUM_INTERFACES 2 +#define NUM_CLASSES 2 +#define NUM_INTERFACES 3 #if NUM_INTERFACES>1 -#define COMPOSITE_CDC_HID_DESCRIPTOR_SIZE (90) +#define COMPOSITE_CDC_HID_DESCRIPTOR_SIZE (90 + 8+9 + 4) #else #define COMPOSITE_CDC_HID_DESCRIPTOR_SIZE (41) #endif -#define HID_INTF_NUM 0 -#define CDC_INTF_NUM 1 +#define HID_INTF_NUM 0 +#define CDC_MASTER_INTF_NUM 1 +#define CDC_SLAVE_INTF_NUM 2 __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_SIZE] __ALIGN_END = { /*Configuration Descriptor*/ @@ -43,7 +45,7 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ COMPOSITE_CDC_HID_DESCRIPTOR_SIZE, /* wTotalLength:no of returned bytes */ 0x00, - NUM_INTERFACES, /* bNumInterfaces: 1 interface */ + NUM_INTERFACES, /* bNumInterfaces */ 0x01, /* bConfigurationValue: Configuration value */ 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ 0x80, /* bmAttributes: self powered */ @@ -99,15 +101,23 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ /* */ /* CDC */ /* */ - + // This "IAD" is needed for Windows since it ignores the standard Union Functional Descriptor + 0x08, // bLength + 0x0B, // IAD type + CDC_MASTER_INTF_NUM, // First interface + CDC_SLAVE_INTF_NUM, // Next interface + 0x02, // bInterfaceClass of the first interface + 0x02, // bInterfaceSubClass of the first interface + 0x00, // bInterfaceProtocol of the first interface + 0x00, // Interface string index /*Interface Descriptor */ 0x09, /* bLength: Interface Descriptor size */ USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ /* Interface descriptor type */ -/*!*/ CDC_INTF_NUM, /* bInterfaceNumber: Number of Interface */ +/*!*/ CDC_MASTER_INTF_NUM, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ - 0x03, /* bNumEndpoints: 3 endpoints used */ + 0x01, /* bNumEndpoints: 1 endpoint used */ 0x02, /* bInterfaceClass: Communication Interface Class */ 0x02, /* bInterfaceSubClass: Abstract Control Model */ 0x00, /* bInterfaceProtocol: Common AT commands */ @@ -125,7 +135,7 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ 0x24, /* bDescriptorType: CS_INTERFACE */ 0x01, /* bDescriptorSubtype: Call Management Func Desc */ 0x00, /* bmCapabilities: D0+D1 */ -/*!*/ CDC_INTF_NUM, /* bDataInterface: 0 */ +/*!*/ CDC_SLAVE_INTF_NUM, /* bDataInterface: 0 */ /*ACM Functional Descriptor*/ 0x04, /* bFunctionLength */ @@ -137,10 +147,10 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ 0x05, /* bFunctionLength */ 0x24, /* bDescriptorType: CS_INTERFACE */ 0x06, /* bDescriptorSubtype: Union func desc */ -/*!*/ CDC_INTF_NUM, /* bMasterInterface: Communication class interface */ -/*!*/ CDC_INTF_NUM, /* bSlaveInterface0: Data Class Interface */ +/*!*/ CDC_MASTER_INTF_NUM, /* bMasterInterface: Communication class interface */ +/*!*/ CDC_SLAVE_INTF_NUM, /* bSlaveInterface0: Data Class Interface */ - /*Endpoint 2 Descriptor*/ + /* Control Endpoint Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ CDC_CMD_EP, /* bEndpointAddress */ @@ -149,6 +159,17 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ HIBYTE(CDC_CMD_PACKET_SIZE), 0x10, /* bInterval: */ +/* Interface descriptor */ + 0x09, /* bLength */ + USB_DESC_TYPE_INTERFACE, /* bDescriptorType */ + CDC_SLAVE_INTF_NUM, /* bInterfaceNumber */ + 0x00, /* bAlternateSetting */ + 0x02, /* bNumEndpoints */ + 0x0A, /* bInterfaceClass: Communication class data */ + 0x00, /* bInterfaceSubClass */ + 0x00, /* bInterfaceProtocol */ + 0x00, + /*Endpoint OUT Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ @@ -167,6 +188,10 @@ __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_ HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), 0x00, /* bInterval: ignore for Bulk transfer */ + 4, /* Descriptor size */ + 3, /* Descriptor type */ + 0x09, + 0x04, #endif }; @@ -195,14 +220,27 @@ int in_endpoint_to_class[MAX_ENDPOINTS]; int out_endpoint_to_class[MAX_ENDPOINTS]; -void USBD_Composite_Set_Classes(USBD_ClassTypeDef *class0, USBD_ClassTypeDef *class1) { - USBD_Classes[0] = class0; - USBD_Classes[1] = class1; +void USBD_Composite_Set_Classes(USBD_ClassTypeDef *hid_class, USBD_ClassTypeDef *cdc_class) { + USBD_Classes[0] = hid_class; + USBD_Classes[1] = cdc_class; +} + +static USBD_ClassTypeDef * getClass(uint8_t index) +{ + switch(index) + { + case HID_INTF_NUM: + return USBD_Classes[0]; + case CDC_MASTER_INTF_NUM: + case CDC_SLAVE_INTF_NUM: + return USBD_Classes[1]; + } + return NULL; } static uint8_t USBD_Composite_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx) { int i; - for(i = 0; i < NUM_INTERFACES; i++) { + for(i = 0; i < NUM_CLASSES; i++) { if (USBD_Classes[i]->Init(pdev, cfgidx) != USBD_OK) { return USBD_FAIL; } @@ -213,7 +251,7 @@ static uint8_t USBD_Composite_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx) { static uint8_t USBD_Composite_DeInit (USBD_HandleTypeDef *pdev, uint8_t cfgidx) { int i; - for(i = 0; i < NUM_INTERFACES; i++) { + for(i = 0; i < NUM_CLASSES; i++) { if (USBD_Classes[i]->DeInit(pdev, cfgidx) != USBD_OK) { return USBD_FAIL; } @@ -224,10 +262,13 @@ static uint8_t USBD_Composite_DeInit (USBD_HandleTypeDef *pdev, uint8_t cfgidx) static uint8_t USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { int i; + USBD_ClassTypeDef * device_class; + device_class = getClass(req->wIndex); + switch (req->bmRequest & USB_REQ_TYPE_MASK) { case USB_REQ_TYPE_CLASS : - if (req->wIndex < NUM_INTERFACES) - return USBD_Classes[req->wIndex]->Setup(pdev, req); + if (device_class != NULL) + return device_class->Setup(pdev, req); else return USBD_FAIL; @@ -236,7 +277,7 @@ static uint8_t USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqType switch (req->bRequest) { case USB_REQ_GET_DESCRIPTOR : - for(i = 0; i < NUM_INTERFACES; i++) { + for(i = 0; i < NUM_CLASSES; i++) { if (USBD_Classes[i]->Setup(pdev, req) != USBD_OK) { return USBD_FAIL; } @@ -246,8 +287,8 @@ static uint8_t USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqType case USB_REQ_GET_INTERFACE : case USB_REQ_SET_INTERFACE : - if (req->wIndex < NUM_INTERFACES) - return USBD_Classes[req->wIndex]->Setup(pdev, req); + if (device_class != NULL) + return device_class->Setup(pdev, req); else return USBD_FAIL; } @@ -274,7 +315,7 @@ static uint8_t USBD_Composite_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum) static uint8_t USBD_Composite_EP0_RxReady (USBD_HandleTypeDef *pdev) { int i; - for(i = 0; i < NUM_INTERFACES; i++) { + for(i = 0; i < NUM_CLASSES; i++) { if (USBD_Classes[i]->EP0_RxReady != NULL) { if (USBD_Classes[i]->EP0_RxReady(pdev) != USBD_OK) { return USBD_FAIL; From 78280e570b2e4fe5cd342ff9946aa5c2f14f55a2 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 12 Aug 2019 16:18:47 +0800 Subject: [PATCH 106/139] adjust whitespace --- targets/stm32l432/lib/usbd/usbd_composite.c | 275 ++++++++++---------- 1 file changed, 136 insertions(+), 139 deletions(-) diff --git a/targets/stm32l432/lib/usbd/usbd_composite.c b/targets/stm32l432/lib/usbd/usbd_composite.c index 182f3cc..50ea08b 100644 --- a/targets/stm32l432/lib/usbd/usbd_composite.c +++ b/targets/stm32l432/lib/usbd/usbd_composite.c @@ -37,165 +37,162 @@ static uint8_t *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length); #define HID_INTF_NUM 0 #define CDC_MASTER_INTF_NUM 1 -#define CDC_SLAVE_INTF_NUM 2 +#define CDC_SLAVE_INTF_NUM 2 __ALIGN_BEGIN uint8_t COMPOSITE_CDC_HID_DESCRIPTOR[COMPOSITE_CDC_HID_DESCRIPTOR_SIZE] __ALIGN_END = - { - /*Configuration Descriptor*/ - 0x09, /* bLength: Configuration Descriptor size */ - USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ - COMPOSITE_CDC_HID_DESCRIPTOR_SIZE, /* wTotalLength:no of returned bytes */ - 0x00, - NUM_INTERFACES, /* bNumInterfaces */ - 0x01, /* bConfigurationValue: Configuration value */ - 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ - 0x80, /* bmAttributes: self powered */ - 0x32, /* MaxPower 100 mA */ + { + /*Configuration Descriptor*/ + 0x09, /* bLength: Configuration Descriptor size */ + USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ + COMPOSITE_CDC_HID_DESCRIPTOR_SIZE, /* wTotalLength:no of returned bytes */ + 0x00, + NUM_INTERFACES, /* bNumInterfaces */ + 0x01, /* bConfigurationValue: Configuration value */ + 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ + 0x80, /* bmAttributes: self powered */ + 0x32, /* MaxPower 100 mA */ - /*---------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------*/ - /* */ - /* HID */ - /* */ + /* */ + /* HID */ + /* */ - /************** Descriptor of Joystick Mouse interface ****************/ - 0x09, /*bLength: Interface Descriptor size*/ - USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ - HID_INTF_NUM, /*bInterfaceNumber: Number of Interface*/ - 0x00, /*bAlternateSetting: Alternate setting*/ - 0x02, /*bNumEndpoints*/ - 0x03, /*bInterfaceClass: HID*/ - 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ - 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ - 2, /*iInterface: Index of string descriptor*/ - /******************** Descriptor of Joystick Mouse HID ********************/ - 0x09, /*bLength: HID Descriptor size*/ - HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ - 0x11, /*bcdHID: HID Class Spec release number*/ - 0x01, - 0x00, /*bCountryCode: Hardware target country*/ - 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ - 0x22, /*bDescriptorType*/ - HID_FIDO_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/ - 0, - /******************** Descriptor of Mouse endpoint ********************/ - 0x07, /*bLength: Endpoint Descriptor size*/ - USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ - HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ - 0x03, /*bmAttributes: Interrupt endpoint*/ - HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */ - 0x00, - HID_BINTERVAL, /*bInterval: Polling Interval */ + /************** Descriptor of Joystick Mouse interface ****************/ + 0x09, /*bLength: Interface Descriptor size*/ + USB_DESC_TYPE_INTERFACE, /*bDescriptorType: Interface descriptor type*/ + HID_INTF_NUM, /*bInterfaceNumber: Number of Interface*/ + 0x00, /*bAlternateSetting: Alternate setting*/ + 0x02, /*bNumEndpoints*/ + 0x03, /*bInterfaceClass: HID*/ + 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ + 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ + 2, /*iInterface: Index of string descriptor*/ + /******************** Descriptor of Joystick Mouse HID ********************/ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bcdHID: HID Class Spec release number*/ + 0x01, + 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + HID_FIDO_REPORT_DESC_SIZE, /*wItemLength: Total length of Report descriptor*/ + 0, + /******************** Descriptor of Mouse endpoint ********************/ + 0x07, /*bLength: Endpoint Descriptor size*/ + USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ + HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ + 0x03, /*bmAttributes: Interrupt endpoint*/ + HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */ + 0x00, + HID_BINTERVAL, /*bInterval: Polling Interval */ - 0x07, /*bLength: Endpoint Descriptor size*/ - USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ - HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ - 0x03, /*bmAttributes: Interrupt endpoint*/ - HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max */ - 0x00, - HID_BINTERVAL, /*bInterval: Polling Interval */ + 0x07, /*bLength: Endpoint Descriptor size*/ + USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ + HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ + 0x03, /*bmAttributes: Interrupt endpoint*/ + HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max */ + 0x00, + HID_BINTERVAL, /*bInterval: Polling Interval */ +#if NUM_INTERFACES > 1 + /* */ + /* CDC */ + /* */ + // This "IAD" is needed for Windows since it ignores the standard Union Functional Descriptor + 0x08, // bLength + 0x0B, // IAD type + CDC_MASTER_INTF_NUM, // First interface + CDC_SLAVE_INTF_NUM, // Next interface + 0x02, // bInterfaceClass of the first interface + 0x02, // bInterfaceSubClass of the first interface + 0x00, // bInterfaceProtocol of the first interface + 0x00, // Interface string index -#if NUM_INTERFACES>1 + /*Interface Descriptor */ + 0x09, /* bLength: Interface Descriptor size */ + USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ + /* Interface descriptor type */ + /*!*/ CDC_MASTER_INTF_NUM, /* bInterfaceNumber: Number of Interface */ + 0x00, /* bAlternateSetting: Alternate setting */ + 0x01, /* bNumEndpoints: 1 endpoint used */ + 0x02, /* bInterfaceClass: Communication Interface Class */ + 0x02, /* bInterfaceSubClass: Abstract Control Model */ + 0x00, /* bInterfaceProtocol: Common AT commands */ + 0x00, /* iInterface: */ - /* */ - /* CDC */ - /* */ - // This "IAD" is needed for Windows since it ignores the standard Union Functional Descriptor - 0x08, // bLength - 0x0B, // IAD type - CDC_MASTER_INTF_NUM, // First interface - CDC_SLAVE_INTF_NUM, // Next interface - 0x02, // bInterfaceClass of the first interface - 0x02, // bInterfaceSubClass of the first interface - 0x00, // bInterfaceProtocol of the first interface - 0x00, // Interface string index + /*Header Functional Descriptor*/ + 0x05, /* bLength: Endpoint Descriptor size */ + 0x24, /* bDescriptorType: CS_INTERFACE */ + 0x00, /* bDescriptorSubtype: Header Func Desc */ + 0x10, /* bcdCDC: spec release number */ + 0x01, - /*Interface Descriptor */ - 0x09, /* bLength: Interface Descriptor size */ - USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ - /* Interface descriptor type */ -/*!*/ CDC_MASTER_INTF_NUM, /* bInterfaceNumber: Number of Interface */ - 0x00, /* bAlternateSetting: Alternate setting */ - 0x01, /* bNumEndpoints: 1 endpoint used */ - 0x02, /* bInterfaceClass: Communication Interface Class */ - 0x02, /* bInterfaceSubClass: Abstract Control Model */ - 0x00, /* bInterfaceProtocol: Common AT commands */ - 0x00, /* iInterface: */ + /*Call Management Functional Descriptor*/ + 0x05, /* bFunctionLength */ + 0x24, /* bDescriptorType: CS_INTERFACE */ + 0x01, /* bDescriptorSubtype: Call Management Func Desc */ + 0x00, /* bmCapabilities: D0+D1 */ + /*!*/ CDC_SLAVE_INTF_NUM, /* bDataInterface: 0 */ - /*Header Functional Descriptor*/ - 0x05, /* bLength: Endpoint Descriptor size */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x00, /* bDescriptorSubtype: Header Func Desc */ - 0x10, /* bcdCDC: spec release number */ - 0x01, + /*ACM Functional Descriptor*/ + 0x04, /* bFunctionLength */ + 0x24, /* bDescriptorType: CS_INTERFACE */ + 0x02, /* bDescriptorSubtype: Abstract Control Management desc */ + 0x02, /* bmCapabilities */ - /*Call Management Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x01, /* bDescriptorSubtype: Call Management Func Desc */ - 0x00, /* bmCapabilities: D0+D1 */ -/*!*/ CDC_SLAVE_INTF_NUM, /* bDataInterface: 0 */ + /*Union Functional Descriptor*/ + 0x05, /* bFunctionLength */ + 0x24, /* bDescriptorType: CS_INTERFACE */ + 0x06, /* bDescriptorSubtype: Union func desc */ + /*!*/ CDC_MASTER_INTF_NUM, /* bMasterInterface: Communication class interface */ + /*!*/ CDC_SLAVE_INTF_NUM, /* bSlaveInterface0: Data Class Interface */ - /*ACM Functional Descriptor*/ - 0x04, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x02, /* bDescriptorSubtype: Abstract Control Management desc */ - 0x02, /* bmCapabilities */ + /* Control Endpoint Descriptor*/ + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ + CDC_CMD_EP, /* bEndpointAddress */ + 0x03, /* bmAttributes: Interrupt */ + LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(CDC_CMD_PACKET_SIZE), + 0x10, /* bInterval: */ - /*Union Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x06, /* bDescriptorSubtype: Union func desc */ -/*!*/ CDC_MASTER_INTF_NUM, /* bMasterInterface: Communication class interface */ -/*!*/ CDC_SLAVE_INTF_NUM, /* bSlaveInterface0: Data Class Interface */ + /* Interface descriptor */ + 0x09, /* bLength */ + USB_DESC_TYPE_INTERFACE, /* bDescriptorType */ + CDC_SLAVE_INTF_NUM, /* bInterfaceNumber */ + 0x00, /* bAlternateSetting */ + 0x02, /* bNumEndpoints */ + 0x0A, /* bInterfaceClass: Communication class data */ + 0x00, /* bInterfaceSubClass */ + 0x00, /* bInterfaceProtocol */ + 0x00, - /* Control Endpoint Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_CMD_EP, /* bEndpointAddress */ - 0x03, /* bmAttributes: Interrupt */ - LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_CMD_PACKET_SIZE), - 0x10, /* bInterval: */ + /*Endpoint OUT Descriptor*/ + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ + CDC_OUT_EP, /* bEndpointAddress */ + 0x02, /* bmAttributes: Bulk */ + LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), + 0x00, /* bInterval: ignore for Bulk transfer */ -/* Interface descriptor */ - 0x09, /* bLength */ - USB_DESC_TYPE_INTERFACE, /* bDescriptorType */ - CDC_SLAVE_INTF_NUM, /* bInterfaceNumber */ - 0x00, /* bAlternateSetting */ - 0x02, /* bNumEndpoints */ - 0x0A, /* bInterfaceClass: Communication class data */ - 0x00, /* bInterfaceSubClass */ - 0x00, /* bInterfaceProtocol */ - 0x00, + /*Endpoint IN Descriptor*/ + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ + CDC_IN_EP, /* bEndpointAddress */ + 0x02, /* bmAttributes: Bulk */ + LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), + 0x00, /* bInterval: ignore for Bulk transfer */ - /*Endpoint OUT Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_OUT_EP, /* bEndpointAddress */ - 0x02, /* bmAttributes: Bulk */ - LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), - 0x00, /* bInterval: ignore for Bulk transfer */ - - /*Endpoint IN Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_IN_EP, /* bEndpointAddress */ - 0x02, /* bmAttributes: Bulk */ - LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), - 0x00, /* bInterval: ignore for Bulk transfer */ - - 4, /* Descriptor size */ - 3, /* Descriptor type */ - 0x09, - 0x04, + 4, /* Descriptor size */ + 3, /* Descriptor type */ + 0x09, + 0x04, #endif }; - USBD_ClassTypeDef USBD_Composite = { USBD_Composite_Init, From 14c94ea8f5b3f07420d2994eb4cff77713280e9a Mon Sep 17 00:00:00 2001 From: Dan Drown Date: Fri, 9 Aug 2019 16:20:04 -0500 Subject: [PATCH 107/139] minor typo --- docs/solo/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/solo/building.md b/docs/solo/building.md index ee7a335..084e2c4 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -32,7 +32,7 @@ Source code can be downloaded from: - from python programs [repository](https://pypi.org/project/solo-python/) `pip install solo-python` - from installing prerequisites `pip3 install -r tools/requirements.txt` - github repository: [repository](https://github.com/solokeys/solo-python) -- installation python enviroment witn command `make venv` from root directory of source code +- installation python enviroment with command `make venv` from root directory of source code ## Compilation From 9105b988e24df5461dd18b482a424816f64c2a88 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 16 Jul 2019 18:59:13 +0300 Subject: [PATCH 108/139] fix some warnings --- targets/stm32l432/src/nfc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index a223378..436cd32 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -196,7 +196,6 @@ bool nfc_write_response(uint8_t req0, uint16_t resp) void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) { uint8_t res[32 + 2]; - int sendlen = 0; uint8_t iBlock = NFC_CMD_IBLOCK | (req0 & 0x0f); uint8_t block_offset = p14443_block_offset(req0); @@ -208,6 +207,7 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) memcpy(&res[block_offset], data, len); nfc_write_frame(res, len + block_offset); } else { + int sendlen = 0; do { // transmit I block int vlen = MIN(32 - block_offset, len - sendlen); @@ -316,7 +316,7 @@ bool WTX_off() void WTX_timer_exec() { // condition: (timer on) or (not expired[300ms]) - if ((WTX_timer <= 0) || WTX_timer + 300 > millis()) + if ((WTX_timer == 0) || WTX_timer + 300 > millis()) return; WTX_process(10); @@ -327,12 +327,12 @@ void WTX_timer_exec() // read timeout must be 10 ms to call from interrupt bool WTX_process(int read_timeout) { - uint8_t wtx[] = {0xf2, 0x01}; if (WTX_fail) return false; if (!WTX_sent) { + uint8_t wtx[] = {0xf2, 0x01}; nfc_write_frame(wtx, sizeof(wtx)); WTX_sent = true; return true; @@ -618,7 +618,7 @@ void nfc_process_iblock(uint8_t * buf, int len) if (!WTX_off()) return; - printf1(TAG_NFC, "CTAP resp: 0x%02� len: %d\r\n", status, ctap_resp.length); + printf1(TAG_NFC, "CTAP resp: 0x%02x len: %d\r\n", status, ctap_resp.length); if (status == CTAP1_ERR_SUCCESS) { From 6e1110ca9bd08b492ee2af7ed5983805ef240b8c Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 16 Jul 2019 19:22:52 +0300 Subject: [PATCH 109/139] fix bug with ams_wait_for_tx --- targets/stm32l432/src/nfc.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 436cd32..dc6164a 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -92,19 +92,27 @@ int nfc_init() return NFC_IS_NA; } +static uint8_t gl_int0 = 0; void process_int0(uint8_t int0) { - + gl_int0 = int0; } bool ams_wait_for_tx(uint32_t timeout_ms) { + if (gl_int0 & AMS_INT_TXE) { + uint8_t int0 = ams_read_reg(AMS_REG_INT0); + process_int0(int0); + + return true; + } + uint32_t tstart = millis(); while (tstart + timeout_ms > millis()) { uint8_t int0 = ams_read_reg(AMS_REG_INT0); - if (int0) process_int0(int0); - if (int0 & AMS_INT_TXE) + process_int0(int0); + if (int0 & AMS_INT_TXE || int0 & AMS_INT_RXE) return true; delay(1); @@ -121,8 +129,13 @@ bool ams_receive_with_timeout(uint32_t timeout_ms, uint8_t * data, int maxlen, i uint32_t tstart = millis(); while (tstart + timeout_ms > millis()) { - uint8_t int0 = ams_read_reg(AMS_REG_INT0); - if (int0) process_int0(int0); + uint8_t int0 = 0; + if (gl_int0 & AMS_INT_RXE) { + int0 = gl_int0; + } else { + int0 = ams_read_reg(AMS_REG_INT0); + process_int0(int0); + } uint8_t buffer_status2 = ams_read_reg(AMS_REG_BUF2); if (buffer_status2 && (int0 & AMS_INT_RXE)) @@ -227,11 +240,11 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) sendlen += vlen; // wait for transmit (32 bytes aprox 2,5ms) - // if (!ams_wait_for_tx(10)) - // { - // printf1(TAG_NFC, "TX timeout. slen: %d \r\n", sendlen); - // break; - // } + if (!ams_wait_for_tx(5)) + { + printf1(TAG_NFC, "TX timeout. slen: %d \r\n", sendlen); + break; + } // if needs to receive R block (not a last block) if (res[0] & 0x10) From 44205141eb01715a6b5c8d9946fd6356558ea80c Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 16 Jul 2019 19:42:18 +0300 Subject: [PATCH 110/139] add one place for int0 --- targets/stm32l432/src/nfc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index dc6164a..2a3d302 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -792,6 +792,7 @@ int nfc_loop() read_reg_block(&ams); + process_int0(ams.regs.int0); uint8_t state = AMS_STATE_MASK & ams.regs.rfid_status; if (state != AMS_STATE_SELECTED && state != AMS_STATE_SELECTEDX) From 301e18c6a2a2d337729a2c4d768efbf4b522fe18 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 16 Jul 2019 19:47:45 +0300 Subject: [PATCH 111/139] add some int0 logic to main cycle --- targets/stm32l432/src/nfc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 2a3d302..cdcaa8f 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -792,6 +792,7 @@ int nfc_loop() read_reg_block(&ams); + uint8_t old_int0 = gl_int0; process_int0(ams.regs.int0); uint8_t state = AMS_STATE_MASK & ams.regs.rfid_status; @@ -806,7 +807,7 @@ int nfc_loop() // if (state != AMS_STATE_SENSE) // printf1(TAG_NFC," %s x%02x\r\n", ams_get_state_string(ams.regs.rfid_status), state); } - if (ams.regs.int0 & AMS_INT_INIT) + if (ams.regs.int0 & AMS_INT_INIT || old_int0 & AMS_INT_INIT) { nfc_state_init(); } @@ -815,7 +816,7 @@ int nfc_loop() // ams_print_int1(ams.regs.int1); } - if ((ams.regs.int0 & AMS_INT_RXE)) + if (ams.regs.int0 & AMS_INT_RXE || old_int0 & AMS_INT_RXE) { if (ams.regs.buffer_status2) { From 3e52d7b42bf931fa6fab36ea48dae1c451432c8a Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 17:59:31 +0800 Subject: [PATCH 112/139] cache button press for 2s --- targets/stm32l432/build/common.mk | 2 +- targets/stm32l432/lib/stm32l4xx_ll_exti.c | 290 +++++ targets/stm32l432/lib/stm32l4xx_ll_exti.h | 1361 +++++++++++++++++++++ targets/stm32l432/src/device.c | 11 + targets/stm32l432/src/init.c | 26 +- 5 files changed, 1675 insertions(+), 15 deletions(-) create mode 100644 targets/stm32l432/lib/stm32l4xx_ll_exti.c create mode 100644 targets/stm32l432/lib/stm32l4xx_ll_exti.h diff --git a/targets/stm32l432/build/common.mk b/targets/stm32l432/build/common.mk index 7c0ae32..9e09ffa 100644 --- a/targets/stm32l432/build/common.mk +++ b/targets/stm32l432/build/common.mk @@ -6,7 +6,7 @@ AR=$(PREFIX)arm-none-eabi-ar DRIVER_LIBS := lib/stm32l4xx_hal_pcd.c lib/stm32l4xx_hal_pcd_ex.c lib/stm32l4xx_ll_gpio.c \ lib/stm32l4xx_ll_rcc.c lib/stm32l4xx_ll_rng.c lib/stm32l4xx_ll_tim.c \ lib/stm32l4xx_ll_usb.c lib/stm32l4xx_ll_utils.c lib/stm32l4xx_ll_pwr.c \ - lib/stm32l4xx_ll_usart.c lib/stm32l4xx_ll_spi.c + lib/stm32l4xx_ll_usart.c lib/stm32l4xx_ll_spi.c lib/stm32l4xx_ll_exti.c USB_LIB := lib/usbd/usbd_cdc.c lib/usbd/usbd_cdc_if.c lib/usbd/usbd_composite.c \ lib/usbd/usbd_conf.c lib/usbd/usbd_core.c lib/usbd/usbd_ioreq.c \ diff --git a/targets/stm32l432/lib/stm32l4xx_ll_exti.c b/targets/stm32l432/lib/stm32l4xx_ll_exti.c new file mode 100644 index 0000000..5c52247 --- /dev/null +++ b/targets/stm32l432/lib/stm32l4xx_ll_exti.c @@ -0,0 +1,290 @@ +/** + ****************************************************************************** + * @file stm32l4xx_ll_exti.c + * @author MCD Application Team + * @brief EXTI LL module driver. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2017 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ +#if defined(USE_FULL_LL_DRIVER) + +/* Includes ------------------------------------------------------------------*/ +#include "stm32l4xx_ll_exti.h" +#ifdef USE_FULL_ASSERT +#include "stm32_assert.h" +#else +#define assert_param(expr) ((void)0U) +#endif + +/** @addtogroup STM32L4xx_LL_Driver + * @{ + */ + +#if defined (EXTI) + +/** @defgroup EXTI_LL EXTI + * @{ + */ + +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/* Private macros ------------------------------------------------------------*/ +/** @addtogroup EXTI_LL_Private_Macros + * @{ + */ + +#define IS_LL_EXTI_LINE_0_31(__VALUE__) (((__VALUE__) & ~LL_EXTI_LINE_ALL_0_31) == 0x00000000U) +#define IS_LL_EXTI_LINE_32_63(__VALUE__) (((__VALUE__) & ~LL_EXTI_LINE_ALL_32_63) == 0x00000000U) + +#define IS_LL_EXTI_MODE(__VALUE__) (((__VALUE__) == LL_EXTI_MODE_IT) \ + || ((__VALUE__) == LL_EXTI_MODE_EVENT) \ + || ((__VALUE__) == LL_EXTI_MODE_IT_EVENT)) + + +#define IS_LL_EXTI_TRIGGER(__VALUE__) (((__VALUE__) == LL_EXTI_TRIGGER_NONE) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_RISING) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_FALLING) \ + || ((__VALUE__) == LL_EXTI_TRIGGER_RISING_FALLING)) + +/** + * @} + */ + +/* Private function prototypes -----------------------------------------------*/ + +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup EXTI_LL_Exported_Functions + * @{ + */ + +/** @addtogroup EXTI_LL_EF_Init + * @{ + */ + +/** + * @brief De-initialize the EXTI registers to their default reset values. + * @retval An ErrorStatus enumeration value: + * - 0x00: EXTI registers are de-initialized + */ +uint32_t LL_EXTI_DeInit(void) +{ + /* Interrupt mask register set to default reset values */ + LL_EXTI_WriteReg(IMR1, 0xFF820000U); + /* Event mask register set to default reset values */ + LL_EXTI_WriteReg(EMR1, 0x00000000U); + /* Rising Trigger selection register set to default reset values */ + LL_EXTI_WriteReg(RTSR1, 0x00000000U); + /* Falling Trigger selection register set to default reset values */ + LL_EXTI_WriteReg(FTSR1, 0x00000000U); + /* Software interrupt event register set to default reset values */ + LL_EXTI_WriteReg(SWIER1, 0x00000000U); + /* Pending register clear */ + LL_EXTI_WriteReg(PR1, 0x007DFFFFU); + + /* Interrupt mask register 2 set to default reset values */ +#if defined(LL_EXTI_LINE_40) + LL_EXTI_WriteReg(IMR2, 0x00000187U); +#else + LL_EXTI_WriteReg(IMR2, 0x00000087U); +#endif + /* Event mask register 2 set to default reset values */ + LL_EXTI_WriteReg(EMR2, 0x00000000U); + /* Rising Trigger selection register 2 set to default reset values */ + LL_EXTI_WriteReg(RTSR2, 0x00000000U); + /* Falling Trigger selection register 2 set to default reset values */ + LL_EXTI_WriteReg(FTSR2, 0x00000000U); + /* Software interrupt event register 2 set to default reset values */ + LL_EXTI_WriteReg(SWIER2, 0x00000000U); + /* Pending register 2 clear */ + LL_EXTI_WriteReg(PR2, 0x00000078U); + + return 0x00u; +} + +/** + * @brief Initialize the EXTI registers according to the specified parameters in EXTI_InitStruct. + * @param EXTI_InitStruct pointer to a @ref LL_EXTI_InitTypeDef structure. + * @retval An ErrorStatus enumeration value: + * - 0x00: EXTI registers are initialized + * - any other calue : wrong configuration + */ +uint32_t LL_EXTI_Init(LL_EXTI_InitTypeDef *EXTI_InitStruct) +{ + uint32_t status = 0x00u; + + /* Check the parameters */ + assert_param(IS_LL_EXTI_LINE_0_31(EXTI_InitStruct->Line_0_31)); + assert_param(IS_LL_EXTI_LINE_32_63(EXTI_InitStruct->Line_32_63)); + assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->LineCommand)); + assert_param(IS_LL_EXTI_MODE(EXTI_InitStruct->Mode)); + + /* ENABLE LineCommand */ + if (EXTI_InitStruct->LineCommand != DISABLE) + { + assert_param(IS_LL_EXTI_TRIGGER(EXTI_InitStruct->Trigger)); + + /* Configure EXTI Lines in range from 0 to 31 */ + if (EXTI_InitStruct->Line_0_31 != LL_EXTI_LINE_NONE) + { + switch (EXTI_InitStruct->Mode) + { + case LL_EXTI_MODE_IT: + /* First Disable Event on provided Lines */ + LL_EXTI_DisableEvent_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableIT_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_MODE_EVENT: + /* First Disable IT on provided Lines */ + LL_EXTI_DisableIT_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Event on provided Lines */ + LL_EXTI_EnableEvent_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_MODE_IT_EVENT: + /* Directly Enable IT & Event on provided Lines */ + LL_EXTI_EnableIT_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_EnableEvent_0_31(EXTI_InitStruct->Line_0_31); + break; + default: + status = 0x01u; + break; + } + if (EXTI_InitStruct->Trigger != LL_EXTI_TRIGGER_NONE) + { + switch (EXTI_InitStruct->Trigger) + { + case LL_EXTI_TRIGGER_RISING: + /* First Disable Falling Trigger on provided Lines */ + LL_EXTI_DisableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Rising Trigger on provided Lines */ + LL_EXTI_EnableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_TRIGGER_FALLING: + /* First Disable Rising Trigger on provided Lines */ + LL_EXTI_DisableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + /* Then Enable Falling Trigger on provided Lines */ + LL_EXTI_EnableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + case LL_EXTI_TRIGGER_RISING_FALLING: + LL_EXTI_EnableRisingTrig_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_EnableFallingTrig_0_31(EXTI_InitStruct->Line_0_31); + break; + default: + status |= 0x02u; + break; + } + } + } + /* Configure EXTI Lines in range from 32 to 63 */ + if (EXTI_InitStruct->Line_32_63 != LL_EXTI_LINE_NONE) + { + switch (EXTI_InitStruct->Mode) + { + case LL_EXTI_MODE_IT: + /* First Disable Event on provided Lines */ + LL_EXTI_DisableEvent_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableIT_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_MODE_EVENT: + /* First Disable IT on provided Lines */ + LL_EXTI_DisableIT_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable Event on provided Lines */ + LL_EXTI_EnableEvent_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_MODE_IT_EVENT: + /* Directly Enable IT & Event on provided Lines */ + LL_EXTI_EnableIT_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_EnableEvent_32_63(EXTI_InitStruct->Line_32_63); + break; + default: + status |= 0x04u; + break; + } + if (EXTI_InitStruct->Trigger != LL_EXTI_TRIGGER_NONE) + { + switch (EXTI_InitStruct->Trigger) + { + case LL_EXTI_TRIGGER_RISING: + /* First Disable Falling Trigger on provided Lines */ + LL_EXTI_DisableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable IT on provided Lines */ + LL_EXTI_EnableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_TRIGGER_FALLING: + /* First Disable Rising Trigger on provided Lines */ + LL_EXTI_DisableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + /* Then Enable Falling Trigger on provided Lines */ + LL_EXTI_EnableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + case LL_EXTI_TRIGGER_RISING_FALLING: + LL_EXTI_EnableRisingTrig_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_EnableFallingTrig_32_63(EXTI_InitStruct->Line_32_63); + break; + default: + status = ERROR; + break; + } + } + } + } + /* DISABLE LineCommand */ + else + { + /* De-configure EXTI Lines in range from 0 to 31 */ + LL_EXTI_DisableIT_0_31(EXTI_InitStruct->Line_0_31); + LL_EXTI_DisableEvent_0_31(EXTI_InitStruct->Line_0_31); + /* De-configure EXTI Lines in range from 32 to 63 */ + LL_EXTI_DisableIT_32_63(EXTI_InitStruct->Line_32_63); + LL_EXTI_DisableEvent_32_63(EXTI_InitStruct->Line_32_63); + } + + return status; +} + +/** + * @brief Set each @ref LL_EXTI_InitTypeDef field to default value. + * @param EXTI_InitStruct Pointer to a @ref LL_EXTI_InitTypeDef structure. + * @retval None + */ +void LL_EXTI_StructInit(LL_EXTI_InitTypeDef *EXTI_InitStruct) +{ + EXTI_InitStruct->Line_0_31 = LL_EXTI_LINE_NONE; + EXTI_InitStruct->Line_32_63 = LL_EXTI_LINE_NONE; + EXTI_InitStruct->LineCommand = DISABLE; + EXTI_InitStruct->Mode = LL_EXTI_MODE_IT; + EXTI_InitStruct->Trigger = LL_EXTI_TRIGGER_FALLING; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +#endif /* defined (EXTI) */ + +/** + * @} + */ + +#endif /* USE_FULL_LL_DRIVER */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/stm32l432/lib/stm32l4xx_ll_exti.h b/targets/stm32l432/lib/stm32l4xx_ll_exti.h new file mode 100644 index 0000000..245f7fd --- /dev/null +++ b/targets/stm32l432/lib/stm32l4xx_ll_exti.h @@ -0,0 +1,1361 @@ +/** + ****************************************************************************** + * @file stm32l4xx_ll_exti.h + * @author MCD Application Team + * @brief Header file of EXTI LL module. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2017 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32L4xx_LL_EXTI_H +#define __STM32L4xx_LL_EXTI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32l4xx.h" + +/** @addtogroup STM32L4xx_LL_Driver + * @{ + */ + +#if defined (EXTI) + +/** @defgroup EXTI_LL EXTI + * @{ + */ + +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/* Private Macros ------------------------------------------------------------*/ +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_Private_Macros EXTI Private Macros + * @{ + */ +/** + * @} + */ +#endif /*USE_FULL_LL_DRIVER*/ +/* Exported types ------------------------------------------------------------*/ +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_ES_INIT EXTI Exported Init structure + * @{ + */ +typedef struct +{ + + uint32_t Line_0_31; /*!< Specifies the EXTI lines to be enabled or disabled for Lines in range 0 to 31 + This parameter can be any combination of @ref EXTI_LL_EC_LINE */ + + uint32_t Line_32_63; /*!< Specifies the EXTI lines to be enabled or disabled for Lines in range 32 to 63 + This parameter can be any combination of @ref EXTI_LL_EC_LINE */ + + FunctionalState LineCommand; /*!< Specifies the new state of the selected EXTI lines. + This parameter can be set either to ENABLE or DISABLE */ + + uint8_t Mode; /*!< Specifies the mode for the EXTI lines. + This parameter can be a value of @ref EXTI_LL_EC_MODE. */ + + uint8_t Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines. + This parameter can be a value of @ref EXTI_LL_EC_TRIGGER. */ +} LL_EXTI_InitTypeDef; + +/** + * @} + */ +#endif /*USE_FULL_LL_DRIVER*/ + +/* Exported constants --------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Constants EXTI Exported Constants + * @{ + */ + +/** @defgroup EXTI_LL_EC_LINE LINE + * @{ + */ +#define LL_EXTI_LINE_0 EXTI_IMR1_IM0 /*!< Extended line 0 */ +#define LL_EXTI_LINE_1 EXTI_IMR1_IM1 /*!< Extended line 1 */ +#define LL_EXTI_LINE_2 EXTI_IMR1_IM2 /*!< Extended line 2 */ +#define LL_EXTI_LINE_3 EXTI_IMR1_IM3 /*!< Extended line 3 */ +#define LL_EXTI_LINE_4 EXTI_IMR1_IM4 /*!< Extended line 4 */ +#define LL_EXTI_LINE_5 EXTI_IMR1_IM5 /*!< Extended line 5 */ +#define LL_EXTI_LINE_6 EXTI_IMR1_IM6 /*!< Extended line 6 */ +#define LL_EXTI_LINE_7 EXTI_IMR1_IM7 /*!< Extended line 7 */ +#define LL_EXTI_LINE_8 EXTI_IMR1_IM8 /*!< Extended line 8 */ +#define LL_EXTI_LINE_9 EXTI_IMR1_IM9 /*!< Extended line 9 */ +#define LL_EXTI_LINE_10 EXTI_IMR1_IM10 /*!< Extended line 10 */ +#define LL_EXTI_LINE_11 EXTI_IMR1_IM11 /*!< Extended line 11 */ +#define LL_EXTI_LINE_12 EXTI_IMR1_IM12 /*!< Extended line 12 */ +#define LL_EXTI_LINE_13 EXTI_IMR1_IM13 /*!< Extended line 13 */ +#define LL_EXTI_LINE_14 EXTI_IMR1_IM14 /*!< Extended line 14 */ +#define LL_EXTI_LINE_15 EXTI_IMR1_IM15 /*!< Extended line 15 */ +#if defined(EXTI_IMR1_IM16) +#define LL_EXTI_LINE_16 EXTI_IMR1_IM16 /*!< Extended line 16 */ +#endif +#define LL_EXTI_LINE_17 EXTI_IMR1_IM17 /*!< Extended line 17 */ +#if defined(EXTI_IMR1_IM18) +#define LL_EXTI_LINE_18 EXTI_IMR1_IM18 /*!< Extended line 18 */ +#endif +#define LL_EXTI_LINE_19 EXTI_IMR1_IM19 /*!< Extended line 19 */ +#if defined(EXTI_IMR1_IM20) +#define LL_EXTI_LINE_20 EXTI_IMR1_IM20 /*!< Extended line 20 */ +#endif +#if defined(EXTI_IMR1_IM21) +#define LL_EXTI_LINE_21 EXTI_IMR1_IM21 /*!< Extended line 21 */ +#endif +#if defined(EXTI_IMR1_IM22) +#define LL_EXTI_LINE_22 EXTI_IMR1_IM22 /*!< Extended line 22 */ +#endif +#define LL_EXTI_LINE_23 EXTI_IMR1_IM23 /*!< Extended line 23 */ +#if defined(EXTI_IMR1_IM24) +#define LL_EXTI_LINE_24 EXTI_IMR1_IM24 /*!< Extended line 24 */ +#endif +#if defined(EXTI_IMR1_IM25) +#define LL_EXTI_LINE_25 EXTI_IMR1_IM25 /*!< Extended line 25 */ +#endif +#if defined(EXTI_IMR1_IM26) +#define LL_EXTI_LINE_26 EXTI_IMR1_IM26 /*!< Extended line 26 */ +#endif +#if defined(EXTI_IMR1_IM27) +#define LL_EXTI_LINE_27 EXTI_IMR1_IM27 /*!< Extended line 27 */ +#endif +#if defined(EXTI_IMR1_IM28) +#define LL_EXTI_LINE_28 EXTI_IMR1_IM28 /*!< Extended line 28 */ +#endif +#if defined(EXTI_IMR1_IM29) +#define LL_EXTI_LINE_29 EXTI_IMR1_IM29 /*!< Extended line 29 */ +#endif +#if defined(EXTI_IMR1_IM30) +#define LL_EXTI_LINE_30 EXTI_IMR1_IM30 /*!< Extended line 30 */ +#endif +#if defined(EXTI_IMR1_IM31) +#define LL_EXTI_LINE_31 EXTI_IMR1_IM31 /*!< Extended line 31 */ +#endif +#define LL_EXTI_LINE_ALL_0_31 EXTI_IMR1_IM /*!< All Extended line not reserved*/ + +#define LL_EXTI_LINE_32 EXTI_IMR2_IM32 /*!< Extended line 32 */ +#if defined(EXTI_IMR2_IM33) +#define LL_EXTI_LINE_33 EXTI_IMR2_IM33 /*!< Extended line 33 */ +#endif +#if defined(EXTI_IMR2_IM34) +#define LL_EXTI_LINE_34 EXTI_IMR2_IM34 /*!< Extended line 34 */ +#endif +#if defined(EXTI_IMR2_IM35) +#define LL_EXTI_LINE_35 EXTI_IMR2_IM35 /*!< Extended line 35 */ +#endif +#if defined(EXTI_IMR2_IM36) +#define LL_EXTI_LINE_36 EXTI_IMR2_IM36 /*!< Extended line 36 */ +#endif +#if defined(EXTI_IMR2_IM37) +#define LL_EXTI_LINE_37 EXTI_IMR2_IM37 /*!< Extended line 37 */ +#endif +#if defined(EXTI_IMR2_IM38) +#define LL_EXTI_LINE_38 EXTI_IMR2_IM38 /*!< Extended line 38 */ +#endif +#if defined(EXTI_IMR2_IM39) +#define LL_EXTI_LINE_39 EXTI_IMR2_IM39 /*!< Extended line 39 */ +#endif +#if defined(EXTI_IMR2_IM40) +#define LL_EXTI_LINE_40 EXTI_IMR2_IM40 /*!< Extended line 40 */ +#endif +#define LL_EXTI_LINE_ALL_32_63 EXTI_IMR2_IM /*!< All Extended line not reserved*/ + + +#define LL_EXTI_LINE_ALL (0xFFFFFFFFU) /*!< All Extended line */ + +#if defined(USE_FULL_LL_DRIVER) +#define LL_EXTI_LINE_NONE (0x00000000U) /*!< None Extended line */ +#endif /*USE_FULL_LL_DRIVER*/ + +/** + * @} + */ + + +#if defined(USE_FULL_LL_DRIVER) + +/** @defgroup EXTI_LL_EC_MODE Mode + * @{ + */ +#define LL_EXTI_MODE_IT ((uint8_t)0x00U) /*!< Interrupt Mode */ +#define LL_EXTI_MODE_EVENT ((uint8_t)0x01U) /*!< Event Mode */ +#define LL_EXTI_MODE_IT_EVENT ((uint8_t)0x02U) /*!< Interrupt & Event Mode */ +/** + * @} + */ + +/** @defgroup EXTI_LL_EC_TRIGGER Edge Trigger + * @{ + */ +#define LL_EXTI_TRIGGER_NONE ((uint8_t)0x00U) /*!< No Trigger Mode */ +#define LL_EXTI_TRIGGER_RISING ((uint8_t)0x01U) /*!< Trigger Rising Mode */ +#define LL_EXTI_TRIGGER_FALLING ((uint8_t)0x02U) /*!< Trigger Falling Mode */ +#define LL_EXTI_TRIGGER_RISING_FALLING ((uint8_t)0x03U) /*!< Trigger Rising & Falling Mode */ + +/** + * @} + */ + + +#endif /*USE_FULL_LL_DRIVER*/ + + +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Macros EXTI Exported Macros + * @{ + */ + +/** @defgroup EXTI_LL_EM_WRITE_READ Common Write and read registers Macros + * @{ + */ + +/** + * @brief Write a value in EXTI register + * @param __REG__ Register to be written + * @param __VALUE__ Value to be written in the register + * @retval None + */ +#define LL_EXTI_WriteReg(__REG__, __VALUE__) WRITE_REG(EXTI->__REG__, (__VALUE__)) + +/** + * @brief Read a value in EXTI register + * @param __REG__ Register to be read + * @retval Register value + */ +#define LL_EXTI_ReadReg(__REG__) READ_REG(EXTI->__REG__) +/** + * @} + */ + + +/** + * @} + */ + + + +/* Exported functions --------------------------------------------------------*/ +/** @defgroup EXTI_LL_Exported_Functions EXTI Exported Functions + * @{ + */ +/** @defgroup EXTI_LL_EF_IT_Management IT_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Interrupt request for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_EnableIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableIT_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->IMR1, ExtiLine); +} +/** + * @brief Enable ExtiLine Interrupt request for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_EnableIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableIT_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->IMR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Interrupt request for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_DisableIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableIT_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->IMR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Interrupt request for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_DisableIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableIT_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->IMR2, ExtiLine); +} + +/** + * @brief Indicate if ExtiLine Interrupt request is enabled for Lines in range 0 to 31 + * @note The reset value for the direct or internal lines (see RM) + * is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR1 IMx LL_EXTI_IsEnabledIT_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledIT_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->IMR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Indicate if ExtiLine Interrupt request is enabled for Lines in range 32 to 63 + * @note The reset value for the direct lines (lines from 32 to 34, line + * 39) is set to 1 in order to enable the interrupt by default. + * Bits are set automatically at Power on. + * @rmtoll IMR2 IMx LL_EXTI_IsEnabledIT_32_63 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledIT_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->IMR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Event_Management Event_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Event request for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_EnableEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableEvent_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->EMR1, ExtiLine); + +} + +/** + * @brief Enable ExtiLine Event request for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_EnableEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableEvent_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->EMR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Event request for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_DisableEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableEvent_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->EMR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Event request for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_DisableEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableEvent_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->EMR2, ExtiLine); +} + +/** + * @brief Indicate if ExtiLine Event request is enabled for Lines in range 0 to 31 + * @rmtoll EMR1 EMx LL_EXTI_IsEnabledEvent_0_31 + * @param ExtiLine This parameter can be one of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_17 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_23 + * @arg @ref LL_EXTI_LINE_24 + * @arg @ref LL_EXTI_LINE_25 + * @arg @ref LL_EXTI_LINE_26 + * @arg @ref LL_EXTI_LINE_27 + * @arg @ref LL_EXTI_LINE_28 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @arg @ref LL_EXTI_LINE_ALL_0_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledEvent_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->EMR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); + +} + +/** + * @brief Indicate if ExtiLine Event request is enabled for Lines in range 32 to 63 + * @rmtoll EMR2 EMx LL_EXTI_IsEnabledEvent_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_32 + * @arg @ref LL_EXTI_LINE_33 + * @arg @ref LL_EXTI_LINE_34(*) + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @arg @ref LL_EXTI_LINE_39(*) + * @arg @ref LL_EXTI_LINE_40(*) + * @arg @ref LL_EXTI_LINE_ALL_32_63 + * @note (*): Available in some devices + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledEvent_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->EMR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Rising_Trigger_Management Rising_Trigger_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Rising Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR1 RTx LL_EXTI_EnableRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableRisingTrig_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->RTSR1, ExtiLine); + +} + +/** + * @brief Enable ExtiLine Rising Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set.Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR2 RTx LL_EXTI_EnableRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableRisingTrig_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->RTSR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Rising Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR1 RTx LL_EXTI_DisableRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableRisingTrig_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->RTSR1, ExtiLine); + +} + +/** + * @brief Disable ExtiLine Rising Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a rising edge on a configurable interrupt + * line occurs during a write operation in the EXTI_RTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll RTSR2 RTx LL_EXTI_DisableRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableRisingTrig_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->RTSR2, ExtiLine); +} + +/** + * @brief Check if rising edge trigger is enabled for Lines in range 0 to 31 + * @rmtoll RTSR1 RTx LL_EXTI_IsEnabledRisingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledRisingTrig_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->RTSR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if rising edge trigger is enabled for Lines in range 32 to 63 + * @rmtoll RTSR2 RTx LL_EXTI_IsEnabledRisingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledRisingTrig_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->RTSR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Falling_Trigger_Management Falling_Trigger_Management + * @{ + */ + +/** + * @brief Enable ExtiLine Falling Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll FTSR1 FTx LL_EXTI_EnableFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableFallingTrig_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->FTSR1, ExtiLine); +} + +/** + * @brief Enable ExtiLine Falling Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for + * the same interrupt line. In this case, both generate a trigger + * condition. + * @rmtoll FTSR2 FTx LL_EXTI_EnableFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_EnableFallingTrig_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->FTSR2, ExtiLine); +} + +/** + * @brief Disable ExtiLine Falling Edge Trigger for Lines in range 0 to 31 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for the same interrupt line. + * In this case, both generate a trigger condition. + * @rmtoll FTSR1 FTx LL_EXTI_DisableFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableFallingTrig_0_31(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->FTSR1, ExtiLine); +} + +/** + * @brief Disable ExtiLine Falling Edge Trigger for Lines in range 32 to 63 + * @note The configurable wakeup lines are edge-triggered. No glitch must be + * generated on these lines. If a Falling edge on a configurable interrupt + * line occurs during a write operation in the EXTI_FTSR register, the + * pending bit is not set. + * Rising and falling edge triggers can be set for the same interrupt line. + * In this case, both generate a trigger condition. + * @rmtoll FTSR2 FTx LL_EXTI_DisableFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_DisableFallingTrig_32_63(uint32_t ExtiLine) +{ + CLEAR_BIT(EXTI->FTSR2, ExtiLine); +} + +/** + * @brief Check if falling edge trigger is enabled for Lines in range 0 to 31 + * @rmtoll FTSR1 FTx LL_EXTI_IsEnabledFallingTrig_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledFallingTrig_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->FTSR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if falling edge trigger is enabled for Lines in range 32 to 63 + * @rmtoll FTSR2 FTx LL_EXTI_IsEnabledFallingTrig_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsEnabledFallingTrig_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->FTSR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Software_Interrupt_Management Software_Interrupt_Management + * @{ + */ + +/** + * @brief Generate a software Interrupt Event for Lines in range 0 to 31 + * @note If the interrupt is enabled on this line in the EXTI_IMR1, writing a 1 to + * this bit when it is at '0' sets the corresponding pending bit in EXTI_PR1 + * resulting in an interrupt request generation. + * This bit is cleared by clearing the corresponding bit in the EXTI_PR1 + * register (by writing a 1 into the bit) + * @rmtoll SWIER1 SWIx LL_EXTI_GenerateSWI_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_GenerateSWI_0_31(uint32_t ExtiLine) +{ + SET_BIT(EXTI->SWIER1, ExtiLine); +} + +/** + * @brief Generate a software Interrupt Event for Lines in range 32 to 63 + * @note If the interrupt is enabled on this line inthe EXTI_IMR2, writing a 1 to + * this bit when it is at '0' sets the corresponding pending bit in EXTI_PR2 + * resulting in an interrupt request generation. + * This bit is cleared by clearing the corresponding bit in the EXTI_PR2 + * register (by writing a 1 into the bit) + * @rmtoll SWIER2 SWIx LL_EXTI_GenerateSWI_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_GenerateSWI_32_63(uint32_t ExtiLine) +{ + SET_BIT(EXTI->SWIER2, ExtiLine); +} + +/** + * @} + */ + +/** @defgroup EXTI_LL_EF_Flag_Management Flag_Management + * @{ + */ + +/** + * @brief Check if the ExtLine Flag is set or not for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_IsActiveFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsActiveFlag_0_31(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->PR1, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Check if the ExtLine Flag is set or not for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_IsActiveFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval State of bit (1 or 0). + */ +__STATIC_INLINE uint32_t LL_EXTI_IsActiveFlag_32_63(uint32_t ExtiLine) +{ + return ((READ_BIT(EXTI->PR2, ExtiLine) == (ExtiLine)) ? 1UL : 0UL); +} + +/** + * @brief Read ExtLine Combination Flag for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_ReadFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval @note This bit is set when the selected edge event arrives on the interrupt + */ +__STATIC_INLINE uint32_t LL_EXTI_ReadFlag_0_31(uint32_t ExtiLine) +{ + return (uint32_t)(READ_BIT(EXTI->PR1, ExtiLine)); +} + +/** + * @brief Read ExtLine Combination Flag for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_ReadFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval @note This bit is set when the selected edge event arrives on the interrupt + */ +__STATIC_INLINE uint32_t LL_EXTI_ReadFlag_32_63(uint32_t ExtiLine) +{ + return (uint32_t)(READ_BIT(EXTI->PR2, ExtiLine)); +} + +/** + * @brief Clear ExtLine Flags for Lines in range 0 to 31 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR1 PIFx LL_EXTI_ClearFlag_0_31 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_0 + * @arg @ref LL_EXTI_LINE_1 + * @arg @ref LL_EXTI_LINE_2 + * @arg @ref LL_EXTI_LINE_3 + * @arg @ref LL_EXTI_LINE_4 + * @arg @ref LL_EXTI_LINE_5 + * @arg @ref LL_EXTI_LINE_6 + * @arg @ref LL_EXTI_LINE_7 + * @arg @ref LL_EXTI_LINE_8 + * @arg @ref LL_EXTI_LINE_9 + * @arg @ref LL_EXTI_LINE_10 + * @arg @ref LL_EXTI_LINE_11 + * @arg @ref LL_EXTI_LINE_12 + * @arg @ref LL_EXTI_LINE_13 + * @arg @ref LL_EXTI_LINE_14 + * @arg @ref LL_EXTI_LINE_15 + * @arg @ref LL_EXTI_LINE_16 + * @arg @ref LL_EXTI_LINE_18 + * @arg @ref LL_EXTI_LINE_19 + * @arg @ref LL_EXTI_LINE_20 + * @arg @ref LL_EXTI_LINE_21 + * @arg @ref LL_EXTI_LINE_22 + * @arg @ref LL_EXTI_LINE_29 + * @arg @ref LL_EXTI_LINE_30 + * @arg @ref LL_EXTI_LINE_31 + * @note Please check each device line mapping for EXTI Line availability + * @retval None + */ +__STATIC_INLINE void LL_EXTI_ClearFlag_0_31(uint32_t ExtiLine) +{ + WRITE_REG(EXTI->PR1, ExtiLine); +} + +/** + * @brief Clear ExtLine Flags for Lines in range 32 to 63 + * @note This bit is set when the selected edge event arrives on the interrupt + * line. This bit is cleared by writing a 1 to the bit. + * @rmtoll PR2 PIFx LL_EXTI_ClearFlag_32_63 + * @param ExtiLine This parameter can be a combination of the following values: + * @arg @ref LL_EXTI_LINE_35 + * @arg @ref LL_EXTI_LINE_36 + * @arg @ref LL_EXTI_LINE_37 + * @arg @ref LL_EXTI_LINE_38 + * @retval None + */ +__STATIC_INLINE void LL_EXTI_ClearFlag_32_63(uint32_t ExtiLine) +{ + WRITE_REG(EXTI->PR2, ExtiLine); +} + + +/** + * @} + */ + +#if defined(USE_FULL_LL_DRIVER) +/** @defgroup EXTI_LL_EF_Init Initialization and de-initialization functions + * @{ + */ + +uint32_t LL_EXTI_Init(LL_EXTI_InitTypeDef *EXTI_InitStruct); +uint32_t LL_EXTI_DeInit(void); +void LL_EXTI_StructInit(LL_EXTI_InitTypeDef *EXTI_InitStruct); + + +/** + * @} + */ +#endif /* USE_FULL_LL_DRIVER */ + +/** + * @} + */ + +/** + * @} + */ + +#endif /* EXTI */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32L4xx_LL_EXTI_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index 6289189..3618563 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -38,6 +38,7 @@ void wait_for_usb_tether(); uint32_t __90_ms = 0; +uint32_t __last_button_press_time = 0; uint32_t __device_status = 0; uint32_t __last_update = 0; extern PCD_HandleTypeDef hpcd; @@ -83,6 +84,11 @@ void TIM6_DAC_IRQHandler() } #endif } +void EXTI0_IRQHandler(void) +{ + EXTI->PR1 = EXTI->PR1; + __last_button_press_time = millis(); +} // Global USB interrupt handler void USB_IRQHandler(void) @@ -500,6 +506,11 @@ int ctap_user_presence_test(uint32_t up_delay) { return 1; } + // "cache" button presses for 2 seconds. + if (millis() - __last_button_press_time < 2000) + { + return 1; + } #if SKIP_BUTTON_CHECK_WITH_DELAY int i=500; while(i--) diff --git a/targets/stm32l432/src/init.c b/targets/stm32l432/src/init.c index e7fa953..d51cf94 100644 --- a/targets/stm32l432/src/init.c +++ b/targets/stm32l432/src/init.c @@ -20,6 +20,7 @@ #include "stm32l4xx_ll_rng.h" #include "stm32l4xx_ll_spi.h" #include "stm32l4xx_ll_usb.h" +#include "stm32l4xx_ll_exti.h" #include "stm32l4xx_hal_pcd.h" #include "stm32l4xx_hal.h" @@ -849,20 +850,17 @@ void init_gpio(void) LL_GPIO_SetPinMode(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_MODE_INPUT); LL_GPIO_SetPinPull(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_PULL_UP); -#ifdef SOLO_AMS_IRQ_PORT -// SAVE POWER - // LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); - // /**/ - // LL_GPIO_InitTypeDef GPIO_InitStruct; - // GPIO_InitStruct.Pin = SOLO_AMS_IRQ_PIN; - // GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT; - // GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - // LL_GPIO_Init(SOLO_AMS_IRQ_PORT, &GPIO_InitStruct); - // - // - // LL_GPIO_SetPinMode(SOLO_AMS_IRQ_PORT,SOLO_AMS_IRQ_PIN,LL_GPIO_MODE_INPUT); - // LL_GPIO_SetPinPull(SOLO_AMS_IRQ_PORT,SOLO_AMS_IRQ_PIN,LL_GPIO_PULL_UP); -#endif + + LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTA, LL_SYSCFG_EXTI_LINE0); + LL_EXTI_InitTypeDef EXTI_InitStruct; + EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_0; // GPIOA_0 + EXTI_InitStruct.Line_32_63 = LL_EXTI_LINE_NONE; + EXTI_InitStruct.LineCommand = ENABLE; + EXTI_InitStruct.Mode = LL_EXTI_MODE_IT; + EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_RISING; + LL_EXTI_Init(&EXTI_InitStruct); + + NVIC_EnableIRQ(EXTI0_IRQn); } From 027fa791a3e921fb534945118652279f170c2b6d Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 18:05:08 +0800 Subject: [PATCH 113/139] only 1 user presence auth per button press --- targets/stm32l432/src/device.c | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index 3618563..d211bdd 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -509,6 +509,7 @@ int ctap_user_presence_test(uint32_t up_delay) // "cache" button presses for 2 seconds. if (millis() - __last_button_press_time < 2000) { + __last_button_press_time = 0; return 1; } #if SKIP_BUTTON_CHECK_WITH_DELAY From 416da63a9a414e2f00963ce4ab2a955b5eeb7e22 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 11 Aug 2019 18:16:58 +0800 Subject: [PATCH 114/139] not for bootloader --- targets/stm32l432/src/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/init.c b/targets/stm32l432/src/init.c index d51cf94..60f820d 100644 --- a/targets/stm32l432/src/init.c +++ b/targets/stm32l432/src/init.c @@ -850,7 +850,7 @@ void init_gpio(void) LL_GPIO_SetPinMode(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_MODE_INPUT); LL_GPIO_SetPinPull(SOLO_BUTTON_PORT,SOLO_BUTTON_PIN,LL_GPIO_PULL_UP); - +#ifndef IS_BOOTLOADER LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTA, LL_SYSCFG_EXTI_LINE0); LL_EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_0; // GPIOA_0 @@ -861,6 +861,7 @@ void init_gpio(void) LL_EXTI_Init(&EXTI_InitStruct); NVIC_EnableIRQ(EXTI0_IRQn); +#endif } From a07a3dee8df839d09141cd9008f34edc07b4b6cb Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Thu, 15 Aug 2019 17:27:31 +0800 Subject: [PATCH 115/139] refactor user_presence handling --- targets/stm32l432/src/device.c | 132 ++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 44 deletions(-) diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index d211bdd..49bbfe7 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -39,6 +39,7 @@ void wait_for_usb_tether(); uint32_t __90_ms = 0; uint32_t __last_button_press_time = 0; +uint32_t __last_button_bounce_time = 0; uint32_t __device_status = 0; uint32_t __last_update = 0; extern PCD_HandleTypeDef hpcd; @@ -76,6 +77,21 @@ void TIM6_DAC_IRQHandler() ctaphid_update_status(__device_status); } } + + + if (is_touch_button_pressed == IS_BUTTON_PRESSED) + { + if (IS_BUTTON_PRESSED()) + { + // Only allow 1 press per 25 ms. + if ((millis() - __last_button_bounce_time) > 25) + { + __last_button_press_time = millis(); + } + __last_button_bounce_time = millis(); + } + } + #ifndef IS_BOOTLOADER // NFC sending WTX if needs if (device_is_nfc() == NFC_IS_ACTIVE) @@ -84,10 +100,20 @@ void TIM6_DAC_IRQHandler() } #endif } + +// Interrupt on rising edge of button (button released) void EXTI0_IRQHandler(void) { EXTI->PR1 = EXTI->PR1; - __last_button_press_time = millis(); + if (is_physical_button_pressed == IS_BUTTON_PRESSED) + { + // Only allow 1 press per 25 ms. + if ((millis() - __last_button_bounce_time) > 25) + { + __last_button_press_time = millis(); + } + __last_button_bounce_time = millis(); + } } // Global USB interrupt handler @@ -499,6 +525,41 @@ static int handle_packets() return 0; } +static int wait_for_button_activate(uint32_t wait) +{ + int ret; + uint32_t start = millis(); + do + { + if ((start + wait) < millis()) + { + return 0; + } + delay(1); + ret = handle_packets(); + if (ret) + return ret; + } while (!IS_BUTTON_PRESSED()); + return 0; +} +static int wait_for_button_release(uint32_t wait) +{ + int ret; + uint32_t start = millis(); + do + { + if ((start + wait) < millis()) + { + return 0; + } + delay(1); + ret = handle_packets(); + if (ret) + return ret; + } while (IS_BUTTON_PRESSED()); + return 0; +} + int ctap_user_presence_test(uint32_t up_delay) { int ret; @@ -506,12 +567,7 @@ int ctap_user_presence_test(uint32_t up_delay) { return 1; } - // "cache" button presses for 2 seconds. - if (millis() - __last_button_press_time < 2000) - { - __last_button_press_time = 0; - return 1; - } + #if SKIP_BUTTON_CHECK_WITH_DELAY int i=500; while(i--) @@ -524,53 +580,41 @@ int ctap_user_presence_test(uint32_t up_delay) #elif SKIP_BUTTON_CHECK_FAST delay(2); ret = handle_packets(); - if (ret) return ret; + if (ret) + return ret; goto done; #endif - uint32_t t1 = millis(); + + // If button was pressed within last [2] seconds, succeed. + if (__last_button_press_time && (millis() - __last_button_press_time < 2000)) + { + goto done; + } + + // Set LED status and wait. led_rgb(0xff3520); -if (IS_BUTTON_PRESSED == is_touch_button_pressed) -{ - // Wait for user to release touch button if it's already pressed - while (IS_BUTTON_PRESSED()) - { - if (t1 + up_delay < millis()) - { - printf1(TAG_GEN,"Button not pressed\n"); - goto fail; - } - ret = handle_packets(); - if (ret) return ret; - } -} - -t1 = millis(); - -do -{ - if (t1 + up_delay < millis()) - { - goto fail; - } - delay(1); - ret = handle_packets(); + // Block and wait for some time. + ret = wait_for_button_activate(up_delay); + if (ret) return ret; + ret = wait_for_button_release(up_delay); if (ret) return ret; -} -while (! IS_BUTTON_PRESSED()); -led_rgb(0x001040); - -delay(50); + // If button was pressed within last [2] seconds, succeed. + if (__last_button_press_time && (millis() - __last_button_press_time < 2000)) + { + goto done; + } + + + return 0; -#if SKIP_BUTTON_CHECK_WITH_DELAY || SKIP_BUTTON_CHECK_FAST done: -#endif -return 1; + ret = wait_for_button_release(up_delay); + __last_button_press_time = 0; + return 1; -fail: -return 0; } int ctap_generate_rng(uint8_t * dst, size_t num) From 3be8611fcfcea91869d23f6f2314fd54cc8fe2c9 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Thu, 15 Aug 2019 17:44:09 +0800 Subject: [PATCH 116/139] remove duplicate from merge --- targets/stm32l432/src/device.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index d1cc41c..49bbfe7 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -100,11 +100,6 @@ void TIM6_DAC_IRQHandler() } #endif } -void EXTI0_IRQHandler(void) -{ - EXTI->PR1 = EXTI->PR1; - __last_button_press_time = millis(); -} // Interrupt on rising edge of button (button released) void EXTI0_IRQHandler(void) From 6f0cf99c92b09b5b779db05c565a87a3f79d8d83 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 12 Aug 2019 19:49:36 +0300 Subject: [PATCH 117/139] PPS implementation --- targets/stm32l432/src/nfc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index cdcaa8f..5bd19e9 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -700,7 +700,14 @@ void nfc_process_block(uint8_t * buf, unsigned int len) if (IS_PPSS_CMD(buf[0])) { - printf1(TAG_NFC, "NFC_CMD_PPSS\r\n"); + printf1(TAG_NFC, "NFC_CMD_PPSS [%d] 0x%02x %02x\r\n", len, (len > 2) ? buf[2] : 0); + + if (buf[1] == 0x11 && (buf[2] & 0x0f) == 0x00) { + nfc_write_frame(buf, 1); // ack with correct start byte + } else { + printf1(TAG_NFC, "NFC_CMD_PPSS ERROR!!!\r\n"); + nfc_write_frame("\x00", 1); // this should not happend. but iso14443-4 dont have NACK here, so just 0x00 + } } else if (IS_IBLOCK(buf[0])) { From cf79b7865d4e48407cf2c1f7909cef4a3b4bd87c Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 12 Aug 2019 20:39:00 +0300 Subject: [PATCH 118/139] small fix --- targets/stm32l432/src/nfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 5bd19e9..29a57a3 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -700,7 +700,7 @@ void nfc_process_block(uint8_t * buf, unsigned int len) if (IS_PPSS_CMD(buf[0])) { - printf1(TAG_NFC, "NFC_CMD_PPSS [%d] 0x%02x %02x\r\n", len, (len > 2) ? buf[2] : 0); + printf1(TAG_NFC, "NFC_CMD_PPSS [%d] 0x%02x\r\n", len, (len > 2) ? buf[2] : 0); if (buf[1] == 0x11 && (buf[2] & 0x0f) == 0x00) { nfc_write_frame(buf, 1); // ack with correct start byte From 2423154feead79abf59b20707f210bca073bdc18 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Thu, 15 Aug 2019 18:07:40 +0800 Subject: [PATCH 119/139] fix warning --- targets/stm32l432/src/nfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 29a57a3..a10a273 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -706,7 +706,7 @@ void nfc_process_block(uint8_t * buf, unsigned int len) nfc_write_frame(buf, 1); // ack with correct start byte } else { printf1(TAG_NFC, "NFC_CMD_PPSS ERROR!!!\r\n"); - nfc_write_frame("\x00", 1); // this should not happend. but iso14443-4 dont have NACK here, so just 0x00 + nfc_write_frame((uint8_t*)"\x00", 1); // this should not happend. but iso14443-4 dont have NACK here, so just 0x00 } } else if (IS_IBLOCK(buf[0])) From ffadab05a3e9324f407694d671c5c4353f86cd75 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Thu, 15 Aug 2019 19:35:54 +0800 Subject: [PATCH 120/139] Update STABLE_VERSION --- STABLE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STABLE_VERSION b/STABLE_VERSION index 8e8299d..35cee72 100644 --- a/STABLE_VERSION +++ b/STABLE_VERSION @@ -1 +1 @@ -2.4.2 +2.4.3 From caac9d0cc1c8f620fb2130338d48e285d52ac159 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 10:29:24 +0800 Subject: [PATCH 121/139] add secure build that uses default attestation key --- targets/stm32l432/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/targets/stm32l432/Makefile b/targets/stm32l432/Makefile index f325b57..9594f2f 100644 --- a/targets/stm32l432/Makefile +++ b/targets/stm32l432/Makefile @@ -21,6 +21,9 @@ firmware-hacker-debug-1: firmware-hacker-debug-2: $(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) DEBUG=2 EXTRA_DEFINES='-DSOLO_HACKER -DFLASH_ROP=0' +firmware-secure-non-solokeys: + $(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) DEBUG=0 EXTRA_DEFINES='-DFLASH_ROP=2' + firmware-secure: $(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) DEBUG=0 EXTRA_DEFINES='-DUSE_SOLOKEYS_CERT -DFLASH_ROP=2' From f5794481ae5cc5aa3d761a8314dffaeb6a2a1d9c Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 11:59:54 +0800 Subject: [PATCH 122/139] initial draft --- docs/solo/bootloader-mode.md | 51 +++++++++++++ docs/solo/building.md | 113 ++-------------------------- docs/solo/customization.md | 140 +++++++++++++++++++++++++++++++++++ docs/solo/programming.md | 113 ++++++++++++++++++++++++++++ in-docker-build.sh | 4 + mkdocs.yml | 3 + 6 files changed, 316 insertions(+), 108 deletions(-) create mode 100644 docs/solo/bootloader-mode.md create mode 100644 docs/solo/customization.md create mode 100644 docs/solo/programming.md diff --git a/docs/solo/bootloader-mode.md b/docs/solo/bootloader-mode.md new file mode 100644 index 0000000..5979c1b --- /dev/null +++ b/docs/solo/bootloader-mode.md @@ -0,0 +1,51 @@ +# Booting into bootloader mode + +You can put Solo into bootloader mode by holding down the button, and plugging in Solo. After 2 seconds, bootloader mode will activate. +You'll see a yellowish flashing light and you can let go of the button. + +Now Solo is ready to accept firmware updates. If the Solo is a secured model, it can only accept signed updates, typically in the `firmware-*.json` format. + +If Solo is running a hacker build, it can be put into bootloader mode on command. This makes it easier for development. + +```bash +solo program aux enter-bootloader +``` + +# The boot stages of Solo + +Solo has 3 boot stages. + +## DFU + +The first stage is the DFU (Device Firmware Update) which is in a ROM on Solo. It is baked into the chip and is not implemented by us. +This is what allows the entire firmware of Solo to be programmed. **It's not recommended to develop for Solo using the DFU because +if you program broken firmware, you could brick your device**. + +On hacker devices, you can boot into the DFU by holding down the button for 5 seconds, when Solo is already in bootloader mode. + +You can also run this command when Solo is in bootloader mode to put it in DFU mode. + +```bash +solo program aux enter-dfu +``` + +Note it will stay in DFU mode until to tell it to boot again. You can boot it again by running the following. + +```bash +solo program aux leave-dfu +``` + +*Warning*: If you change the firmware to something broken, and you tell the DFU to boot it, you could brick your device. + +## Solo Bootloader + +The next boot stage is the "Solo bootloader". So when we say to put your Solo into bootloader mode, it is this stage. +This bootloader is written by us and allows signed firmware updates to be written. On Solo Hackers, there is no signature checking +and will allow any firmware updates. + +It is safe to develop for Solo using our Solo bootloader. If broken firmware is uploaded to the device, then the Solo +bootloader can always be booted again by holding down the button when plugging in. + +## Solo application + +This is what contains all the important functionality of Solo. FIDO2, U2F, etc. This is what Solo will boot to by default. diff --git a/docs/solo/building.md b/docs/solo/building.md index 084e2c4..0cc4817 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -14,12 +14,6 @@ but be warned they might be out of date. Typically it will be called `gcc-arm-n Install `solo-python` usually with `pip3 install solo-python`. The `solo` python application may also be used for [programming](#programming). -To program your build, you'll need one of the following programs. - -- [openocd](http://openocd.org) -- [stlink](https://github.com/texane/stlink) -- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html) - ## Obtain source code and solo tool Source code can be downloaded from: @@ -54,7 +48,7 @@ enabled, like being able to jump to the bootloader on command. It then merges b and solo builds into the same binary. I.e. it combines `bootloader.hex` and `solo.hex` into `all.hex`. -If you're just planning to do development, please don't try to reprogram the bootloader, +If you're just planning to do development, **please don't try to reprogram the bootloader**, as this can be risky if done often. Just use `solo.hex`. ### Building with debug messages @@ -86,6 +80,8 @@ solo monitor ### Building a Solo release +To build Solo + If you want to build a release of Solo, we recommend trying a Hacker build first just to make sure that it's working. Otherwise it may not be as easy or possible to fix any mistakes. @@ -96,105 +92,6 @@ If you're ready to program a full release, run this recipe to build. make build-release-locked ``` -Programming `all.hex` will cause the device to permanently lock itself. +Programming `all.hex` will cause the device to permanently lock itself. This means debuggers cannot be used and signature checking +will be enforced on all future updates. -## Programming - -It's recommended to test a debug/hacker build first to make sure Solo is working as expected. -Then you can switch to a locked down build, which cannot be reprogrammed as easily (or not at all!). - -We recommend using our `solo` tool to manage programming. It is cross platform. First you must -install the prerequisites: - -``` -pip3 install -r tools/requirements.txt -``` - -If you're on Windows, you must also install [libusb](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/). - -### Pre-programmed Solo Hacker - -If your Solo device is already programmed (it flashes green when powered), we recommend -programming it using the Solo bootloader. - -``` -solo program aux enter-bootloader -solo program bootloader solo.hex -``` - -Make sure to program `solo.hex` and not `all.hex`. Nothing bad would happen, but you'd -see errors. - -If something bad happens, you can always boot the Solo bootloader by doing the following. - -1. Unplug device. -2. Hold down button. -3. Plug in device while holding down button. -4. Wait about 2 seconds for flashing yellow light. Release button. - -If you hold the button for an additional 5 seconds, it will boot to the ST DFU (device firmware update). -Don't use the ST DFU unless you know what you're doing. - -### ST USB DFU - -If your Solo has never been programmed, it will boot the ST USB DFU. The LED is turned -off and it enumerates as "STM BOOTLOADER". - -You can program it by running the following. - -``` -solo program aux enter-bootloader -solo program aux enter-dfu -# powercycle key -solo program dfu all.hex -``` - -Make sure to program `all.hex`, as this contains both the bootloader and the Solo application. - -If all goes well, you should see a slow-flashing green light. - -### Solo Hacker vs Solo - -A Solo hacker device doesn't need to be in bootloader mode to be programmed, it will automatically switch. - -Solo (locked) needs the button to be held down when plugged in to boot to the bootloader. - -A locked Solo will only accept signed updates. - -### Signed updates - -If this is not a device with a hacker build, you can only program signed updates. - -``` -solo program bootloader /path/to/firmware.json -``` - -If you've provisioned the Solo bootloader with your own secp256r1 public key, you can sign your -firmware by running the following command. - -``` -solo sign /path/to/signing-key.pem /path/to/solo.hex /output-path/to/firmware.json -``` - -If your Solo isn't locked, you can always reprogram it using a debugger connected directly -to the token. - -## Permanently locking the device - -If you plan to be using your Solo for real, you should lock it permanently. This prevents -someone from connecting a debugger to your token and stealing credentials. - -To do this, build the locked release firmware. -``` -make build-release-locked -``` - -Now when you program `all.hex`, the device will lock itself when it first boots. You can only update it -with signed updates. - -If you'd like to also permanently disable signed updates, plug in your programmed Solo and run the following: - -``` -# WARNING: No more signed updates. -solo program disable-bootloader -``` diff --git a/docs/solo/customization.md b/docs/solo/customization.md new file mode 100644 index 0000000..08b74da --- /dev/null +++ b/docs/solo/customization.md @@ -0,0 +1,140 @@ +# Customization + +If you are interested in customizing parts of your Solo, and you have a Solo Hacker, this page is for you. + +## Custom Attestation key + +The attestation key is used in the FIDO2 *makeCredential* or U2F *register* requests. It signs +newly generated credentials. The certification associated with the attestation key is output as well. +Platforms or services can use the attestation feature to enforce specific authenticators to be used. +This is typically a use case for organizations and isn't seen in the wild for consumer use cases. + +Attestation keys are typically the same for at least 100K units of a particular authenticator model. +This is so they don't contribute a significant fingerprint that platforms could use to identify the user. + +If you don't want to use the default attestation key that Solo builds with, you can create your own +and program it. + +### Creating your attestation key pair + +Since we are generating keys, it's important to use a good entropy source. +You can use your Solo device to generate some good random numbers. + +``` +# Run for 1 second, then hit control-c +solo key rng > seed.bin +``` + +First we will create a self signed key pair that acts as the root of trust. This +won't go on the authenticator, but will sign the keypair that does. + +Please change the root certification information as needed. You may change the ECC curve. + +``` +curve=prime256v1 + +country=US +state=Maine +organization=OpenSourceSecurity +unit=Root CA +CN=example.com +email=example@example.com + +# generate EC private key +openssl ecparam -genkey -name "$curve" -out root_key.pem -rand seed.bin + +# generate a "signing request" +openssl req -new -key root_key.pem -out root_key.pem.csr -subj "/C=$country/ST=$state/O=$organization/OU=$unit/CN=example.com/emailAddress=$email" + +# self sign the request +openssl x509 -trustout -req -days 18250 -in root_key.pem.csr -signkey root_key.pem -out root_cert.pem -sha256 + +# convert to smaller size format DER +openssl x509 -in root_cert.pem -outform der -out root_cert.der + +# print out information and verify +openssl x509 -in root_cert.pem -text -noout +``` + +You need to create a extended certificate for the device certificate to work with FIDO2. You need to create this +file, `v3.ext`, and add these options to it. + +``` +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +``` + +Now to generate & sign the attestation key pair that will go on your device, or maybe 100,000 devices :). +Note you must use a prime256v1 curve for this step, and you must leave the unit/OU as "Authenticator Attestation". + +``` +country=US +state=Maine +organization=OpenSourceSecurity +unit=Authenticator Attestation +CN=example.com +email=example@example.com + +# generate EC private key +openssl ecparam -genkey -name "$curve" -out device_key.pem -rand seed.bin + +# generate a "signing request" +openssl req -new -key device_key.pem -out device_key.pem.csr -subj "/C=$country/ST=$state/O=$organization/OU=$unit/CN=example.com/emailAddress=$email + +# sign the request +openssl x509 -req -days 18250 -in device_key.pem.csr -extfile v3.ext -CA root_cert.pem -CAkey root_key.pem -set_serial 01 -out device_cert.pem -sha256 + +# convert to smaller size format DER +openssl x509 -in device_cert.pem -outform der -out device_cert.der + +# Verify the device certificate details +openssl x509 -in device_cert.pem -text -noout +``` + +Let's verify that the attestation key and certificate are valid, and that they can be verified with the root key pair. + +``` +echo 'challenge $RANDOM' > chal.txt + +# check that they are valid key pairs +openssl dgst -sha256 -sign device_key.pem -out sig.txt chal.txt +openssl dgst -sha256 -verify <(openssl x509 -in device_cert.der -pubkey -noout) -signature sig.txt chal.txt + +openssl dgst -sha256 -sign "root_key.pem" -out sig.txt chal.txt +openssl dgst -sha256 -verify <(openssl x509 -in root_cert.der -pubkey -noout) -signature sig.txt chal.txt + +# Check they are a chain +openssl verify -verbose -CAfile "$rcert" "$icert" +``` + +If the checks succeed, you are ready to program the device attestation key and certificate. + +### Programming an attestation key and certificate + +Convert the DER format of the device attestation certificate to "C" bytes using our utility script. You may first need to +first install prerequisite python modules (pip install -r tools/requirements.txt). + +``` +python tools/gencert/cbytes.py device_cert.der +``` + +Copy the byte string portion into the [`attestation.c` source file of Solo](https://github.com/solokeys/solo/blob/master/targets/stm32l432/src/attestation.c). Overwrite the development or "default" certificate that is already there. + +Now [build the Solo firmware](/solo/building), either a secure or hacker build. You will need to produce a bootloader.hex file and a solo.hex file. + +Print your attestation key in a hex string format. + +``` +python tools/print_x_y.py +``` + +Merge the bootloader.hex, solo.hex, and attestion key into one firmware file. + +``` +solo merge --attestation-key bootloader.hex solo.hex all.hex +``` + +Now you have a newly create `all.hex` file with a custom attestation key. You can [program this all.hex file +with Solo in DFU mode](/solo/programming#procedure). \ No newline at end of file diff --git a/docs/solo/programming.md b/docs/solo/programming.md new file mode 100644 index 0000000..a63d39e --- /dev/null +++ b/docs/solo/programming.md @@ -0,0 +1,113 @@ +# Programming + +This page documents how to update or program your Solo. + +## Prerequisites + +To program Solo, you'll likely only need to use our Solo tool. + +```python +pip3 install solo-python +``` + +## Updating the firmware + +If you just want to update the firmware, you can run one of the following commands. +Make sure your key [is in bootloader mode](/solo/bootloader-mode#solo-bootloader) first. + +```bash +solo key update <--secure | --hacker> +``` + +You can manually install the [latest release](https://github.com/solokeys/solo/releases), or use a build that you made. + +```bash +# If it's a hacker, it will automatically boot into bootloader mode. +solo program bootloader +``` + +Note you won't be able to use `all.hex` or the `bundle-*.hex` builds, as these include the solo bootloader. You shouldn't +risk changing the Solo bootloader unless you want to make it a secure device, or [make other customizations](). + +## Updating a Hacker to a Secure Solo + +Updating a hacker to be a secure build overwrites the [Solo bootloader](/solo/bootloader-mode#solo-bootloader). +So it's important to not mess this up or you may brick your device. + +You can use a firmware build from the [latest release](https://github.com/solokeys/solo/releases) or use +a build that you made yourself. + +You need to use a firmware file that has the combined bootloader and application (or at the very least just the bootloader). +This means using the `bundle-*.hex` file or the `all.hex` from your build. If you overwrite the Solo flash with a missing bootloader, +it will be bricked. + +We provide two types of bundled builds. The `bundle-hacker-*.hex` build is the hacker build. If you update with this, +you will update the bootloader and application, but nothing will be secured. The `bundle-secure-non-solokeys.hex` +is a secured build that will lock your device and it will behave just like a Secure Solo. The main difference is that +it uses a "default" attestation key in the device, rather than the SoloKeys attestation key. There is no security +concern with using our default attestation key, aside from a privacy implication that services can distinguish it from Solo Secure. + +### Procedure + +1. Boot into DFU mode. + + # Enter Solo bootloader + solo program aux enter-bootloader + + # Enter DFU + solo program aux enter-dfu + + The device should be turned off. + +2. Program the device + + solo program dfu + + Double check you programmed it with bootloader + application (or just bootloader). + If you messed it up, simply don't do the next step and repeat this step correctly. + +3. Boot the device + + Once Solo boots a secure build, it will lock the flash permantly from debugger access. Also the bootloader + will only accept signed firmware updates. + + solo program aux leave-dfu + +If you are having problems with solo tool and DFU mode, you could alternatively try booting into DFU +by holding down the button while Solo is in bootloader mode. Then try another programming tool that works +with ST DFU: + +* STM32CubeProg +* openocd +* stlink + +Windows users need to install [libusb](https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/) +for solo-python to work with Solo's DFU. + + +## Programming a Solo that hasn't been programmed + +A Solo that hasn't been programmed will boot into DFU mode. You can program +it by following a bootloader, or combined bootloader + application. + +``` +solo program dfu +``` + +Then boot the device. Make sure it has a bootloader to boot to. + +``` +solo program aux leave-dfu +``` + +## Disable signed firmware updates + +If you'd like to also permanently disable signed updates, plug in your programmed Solo and run the following: + +```bash +# WARNING: No more signed updates. +solo program disable-bootloader +``` + +You won't be able to update to any new releases. + diff --git a/in-docker-build.sh b/in-docker-build.sh index 631d359..ece565c 100755 --- a/in-docker-build.sh +++ b/in-docker-build.sh @@ -38,6 +38,7 @@ build firmware hacker solo build firmware hacker-debug-1 solo build firmware hacker-debug-2 solo build firmware secure solo +build firmware secure-non-solokeys solo pip install -U pip pip install -U solo-python @@ -49,3 +50,6 @@ bundle="bundle-hacker-debug-1-${version}" /opt/conda/bin/solo mergehex bootloader-nonverifying-${version}.hex firmware-hacker-debug-1-${version}.hex ${bundle}.hex bundle="bundle-hacker-debug-2-${version}" /opt/conda/bin/solo mergehex bootloader-nonverifying-${version}.hex firmware-hacker-debug-2-${version}.hex ${bundle}.hex +bundle="bundle-secure-non-solokeys-${version}" +/opt/conda/bin/solo mergehex bootloader-verifying-${version}.hex firmware-secure-non-solokeys-${version}.hex ${bundle}.hex +sha256sum ${bundle}.hex > ${bundle}.sha2 diff --git a/mkdocs.yml b/mkdocs.yml index 2cfc0c9..642adab 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,9 @@ nav: - FIDO2 Implementation: solo/fido2-impl.md - Metadata Statements: solo/metadata-statements.md - Build instructions: solo/building.md + - Programming instructions: solo/programming.md + - Bootloader mode: solo/bootloader-mode.md + - Customization: solo/customization.md - Running on Nucleo32 board: solo/nucleo32-board.md - Signed update process: solo/signed-updates.md - Code documentation: solo/code-overview.md From 3927aec06d612d62512e9b10ca3c3c10cbf90292 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 13:52:17 +0800 Subject: [PATCH 123/139] dont remove solo.hex bootloader.hex --- targets/stm32l432/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/targets/stm32l432/Makefile b/targets/stm32l432/Makefile index 9594f2f..e7db178 100644 --- a/targets/stm32l432/Makefile +++ b/targets/stm32l432/Makefile @@ -62,7 +62,6 @@ boot-no-sig: build-release-locked: cbor clean2 boot-sig-checking clean all-locked $(VENV) $(merge_hex) solo.hex bootloader.hex all.hex - rm -f solo.hex bootloader.hex # don't program solo.hex ... build-release: cbor clean2 boot-sig-checking clean all $(VENV) $(merge_hex) solo.hex bootloader.hex all.hex From b7a4cf001ae4209da393ec3d648b14d12c1edf3b Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 13:53:28 +0800 Subject: [PATCH 124/139] run through fixes --- docs/solo/bootloader-mode.md | 2 +- docs/solo/building.md | 7 +++++++ docs/solo/customization.md | 23 ++++++++++++----------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/solo/bootloader-mode.md b/docs/solo/bootloader-mode.md index 5979c1b..ff4358a 100644 --- a/docs/solo/bootloader-mode.md +++ b/docs/solo/bootloader-mode.md @@ -3,7 +3,7 @@ You can put Solo into bootloader mode by holding down the button, and plugging in Solo. After 2 seconds, bootloader mode will activate. You'll see a yellowish flashing light and you can let go of the button. -Now Solo is ready to accept firmware updates. If the Solo is a secured model, it can only accept signed updates, typically in the `firmware-*.json` format. +Now Solo is ready to [accept firmware updates](/solo/signed-updates). If the Solo is a secured model, it can only accept signed updates, typically in the `firmware-*.json` format. If Solo is running a hacker build, it can be put into bootloader mode on command. This makes it easier for development. diff --git a/docs/solo/building.md b/docs/solo/building.md index 0cc4817..3fbfb9b 100644 --- a/docs/solo/building.md +++ b/docs/solo/building.md @@ -92,6 +92,13 @@ If you're ready to program a full release, run this recipe to build. make build-release-locked ``` +This outputs bootloader.hex, solo.hex, and the combined all.hex. + Programming `all.hex` will cause the device to permanently lock itself. This means debuggers cannot be used and signature checking will be enforced on all future updates. +Note if you program a secured `solo.hex` file onto a Solo Hacker, it will lock the flash, but the bootloader +will still accept unsigned firmware updates. So you can switch it back to being a hacker, but you will +not be able to replace the unlocked bootloader anymore, since the permanently locked flash also disables the DFU. +[Read more on Solo's boot stages](/solo/bootloader-mode). + diff --git a/docs/solo/customization.md b/docs/solo/customization.md index 08b74da..e7d65b2 100644 --- a/docs/solo/customization.md +++ b/docs/solo/customization.md @@ -5,7 +5,8 @@ If you are interested in customizing parts of your Solo, and you have a Solo Hac ## Custom Attestation key The attestation key is used in the FIDO2 *makeCredential* or U2F *register* requests. It signs -newly generated credentials. The certification associated with the attestation key is output as well. +newly generated credentials. The certificate associated with the attestation key is output with newly created credentials. + Platforms or services can use the attestation feature to enforce specific authenticators to be used. This is typically a use case for organizations and isn't seen in the wild for consumer use cases. @@ -18,11 +19,11 @@ and program it. ### Creating your attestation key pair Since we are generating keys, it's important to use a good entropy source. -You can use your Solo device to generate some good random numbers. +You can use the True RNG on your Solo device to generate some good random numbers. ``` # Run for 1 second, then hit control-c -solo key rng > seed.bin +solo key rng raw > seed.bin ``` First we will create a self signed key pair that acts as the root of trust. This @@ -36,7 +37,7 @@ curve=prime256v1 country=US state=Maine organization=OpenSourceSecurity -unit=Root CA +unit="Root CA" CN=example.com email=example@example.com @@ -73,7 +74,7 @@ Note you must use a prime256v1 curve for this step, and you must leave the unit/ country=US state=Maine organization=OpenSourceSecurity -unit=Authenticator Attestation +unit="Authenticator Attestation" CN=example.com email=example@example.com @@ -81,7 +82,7 @@ email=example@example.com openssl ecparam -genkey -name "$curve" -out device_key.pem -rand seed.bin # generate a "signing request" -openssl req -new -key device_key.pem -out device_key.pem.csr -subj "/C=$country/ST=$state/O=$organization/OU=$unit/CN=example.com/emailAddress=$email +openssl req -new -key device_key.pem -out device_key.pem.csr -subj "/C=$country/ST=$state/O=$organization/OU=$unit/CN=example.com/emailAddress=$email" # sign the request openssl x509 -req -days 18250 -in device_key.pem.csr -extfile v3.ext -CA root_cert.pem -CAkey root_key.pem -set_serial 01 -out device_cert.pem -sha256 @@ -100,13 +101,13 @@ echo 'challenge $RANDOM' > chal.txt # check that they are valid key pairs openssl dgst -sha256 -sign device_key.pem -out sig.txt chal.txt -openssl dgst -sha256 -verify <(openssl x509 -in device_cert.der -pubkey -noout) -signature sig.txt chal.txt +openssl dgst -sha256 -verify <(openssl x509 -in device_cert.pem -pubkey -noout) -signature sig.txt chal.txt openssl dgst -sha256 -sign "root_key.pem" -out sig.txt chal.txt -openssl dgst -sha256 -verify <(openssl x509 -in root_cert.der -pubkey -noout) -signature sig.txt chal.txt +openssl dgst -sha256 -verify <(openssl x509 -in root_cert.pem -pubkey -noout) -signature sig.txt chal.txt # Check they are a chain -openssl verify -verbose -CAfile "$rcert" "$icert" +openssl verify -verbose -CAfile "root_cert.pem" "device_cert.pem" ``` If the checks succeed, you are ready to program the device attestation key and certificate. @@ -127,13 +128,13 @@ Now [build the Solo firmware](/solo/building), either a secure or hacker build. Print your attestation key in a hex string format. ``` -python tools/print_x_y.py +python tools/print_x_y.py device_key.pem ``` Merge the bootloader.hex, solo.hex, and attestion key into one firmware file. ``` -solo merge --attestation-key bootloader.hex solo.hex all.hex +solo mergehex --attestation-key bootloader.hex solo.hex all.hex ``` Now you have a newly create `all.hex` file with a custom attestation key. You can [program this all.hex file From 19422d9daa1cf52590daa5f47c4748aa9a3c6cec Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 14:03:07 +0800 Subject: [PATCH 125/139] add info for rng use --- docs/solo/customization.md | 2 +- docs/solo/solo-extras.md | 19 +++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/solo/solo-extras.md diff --git a/docs/solo/customization.md b/docs/solo/customization.md index e7d65b2..601a576 100644 --- a/docs/solo/customization.md +++ b/docs/solo/customization.md @@ -19,7 +19,7 @@ and program it. ### Creating your attestation key pair Since we are generating keys, it's important to use a good entropy source. -You can use the True RNG on your Solo device to generate some good random numbers. +You can use the [True RNG on your Solo](/solo/solo-extras) to generate some good random numbers. ``` # Run for 1 second, then hit control-c diff --git a/docs/solo/solo-extras.md b/docs/solo/solo-extras.md new file mode 100644 index 0000000..1d5dd80 --- /dev/null +++ b/docs/solo/solo-extras.md @@ -0,0 +1,19 @@ +# Solo Extras + +## Random number generation + +Solo contains a True Random Number Generator (TRNG). A TRNG is a hardware based mechanism +that leverages natural phenomenon to generate random numbers, which is often better than a traditional +RNG that has state and updates deterministically using cryptographic methods. + +You can easily access the TRNG stream on Solo using our python tool [solo-python](https://github.com/solokeys/solo-python). + +``` +solo key rng raw > random.bin +``` + +Or you can seed the state of the RNG on your kernel (/dev/random). + +``` +solo key rng feedkernel +``` diff --git a/mkdocs.yml b/mkdocs.yml index 642adab..1b45b2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ nav: - Programming instructions: solo/programming.md - Bootloader mode: solo/bootloader-mode.md - Customization: solo/customization.md + - Solo Extras: solo/solo-extras.md - Running on Nucleo32 board: solo/nucleo32-board.md - Signed update process: solo/signed-updates.md - Code documentation: solo/code-overview.md From 26af0c423ea78db1e1c70a8355b075435866480d Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 16 Aug 2019 14:03:41 +0800 Subject: [PATCH 126/139] Update solo-extras.md --- docs/solo/solo-extras.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/solo/solo-extras.md b/docs/solo/solo-extras.md index 1d5dd80..9a72a3f 100644 --- a/docs/solo/solo-extras.md +++ b/docs/solo/solo-extras.md @@ -3,7 +3,7 @@ ## Random number generation Solo contains a True Random Number Generator (TRNG). A TRNG is a hardware based mechanism -that leverages natural phenomenon to generate random numbers, which is often better than a traditional +that leverages natural phenomenon to generate random numbers, which is can be better than a traditional RNG that has state and updates deterministically using cryptographic methods. You can easily access the TRNG stream on Solo using our python tool [solo-python](https://github.com/solokeys/solo-python). From 4f3d4b09eb46f0f16ebe80387a3c758635aa3d72 Mon Sep 17 00:00:00 2001 From: Emanuele Cesena Date: Fri, 16 Aug 2019 15:50:42 -0400 Subject: [PATCH 127/139] Update README.md --- README.md | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index dae22e1..ba13ea7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,18 @@ -[![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) -[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors) -[![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) -[![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) +**NEW!** We launched a new tiny security key called Somu, it's live on Crowd Supply and you can [pre-order it now](https://solokeys.com/somu)! + +[](https://solokeys.com/somu) + +Somu is the micro version of Solo. We were inspired to make a secure Tomu, so we took its tiny form factor, we added the secure microcontroller and firmware of Solo, et voilà! Here we have Somu. + +[![latest release](https://img.shields.io/github/release/solokeys/solo.svg)](https://update.solokeys.com/) [![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo?ref=badge_shield) - -[![latest release](https://img.shields.io/github/release/solokeys/solo.svg)](https://github.com/solokeys/solo/releases) -[![commits since last release](https://img.shields.io/github/commits-since/solokeys/solo/latest.svg)](https://github.com/solokeys/solo/commits/master) -[![last commit](https://img.shields.io/github/last-commit/solokeys/solo.svg)](https://github.com/solokeys/solo/commits/master) -[![commit activity](https://img.shields.io/github/commit-activity/m/solokeys/solo.svg)](https://github.com/solokeys/solo/commits/master) -[![contributors](https://img.shields.io/github/contributors/solokeys/solo.svg)](https://github.com/solokeys/solo/graphs/contributors) - - -# Solo +[![Build Status](https://travis-ci.com/solokeys/solo.svg?style=flat-square&branch=master)](https://travis-ci.com/solokeys/solo) Solo is an open source security key, and you can get one at [solokeys.com](https://solokeys.com). -Solo supports FIDO2 and U2F standards for strong two-factor authentication and password-less login, and it will protect you against phishing and other online attacks. With colored cases and multilingual guides we want to make secure login more personable and accessible to everyone around the globe. +[](https://solokeys.com) - +Solo supports FIDO2 and U2F standards for strong two-factor authentication and password-less login, and it will protect you against phishing and other online attacks. With colored cases and multilingual guides we want to make secure login more personable and accessible to everyone around the globe. This repo contains the Solo firmware, including implementations of FIDO2 and U2F (CTAP2 and CTAP) over USB and NFC. The main implementation is for STM32L432, but it is easily portable. @@ -42,7 +36,7 @@ Solo for Hacker is a special version of Solo that let you customize its firmware Check out [solokeys.com](https://solokeys.com), for options on where to buy Solo. Solo Hacker can be converted to a secure version, but normal Solo cannot be converted to a Hacker version. -If you have a Solo for Hacker, here's how you can load your own code on it. You can find more details, including how to permanently lock it, in our [documentation](https://docs.solokeys.io/solo/building/). We only support Python3. +If you have a Solo for Hacker, here's how you can load your own code on it. You can find more details, including how to permanently lock it, in our [documentation](https://docs.solokeys.io/solo/building/). We support Python3. ```bash git clone --recurse-submodules https://github.com/solokeys/solo @@ -168,3 +162,19 @@ You may use Solo documentation under the terms of the CC-BY-SA 4.0 license You can buy Solo, Solo Tap, and Solo for Hackers at [solokeys.com](https://solokeys.com). +
+
+
+ +[![License](https://img.shields.io/github/license/solokeys/solo.svg)](https://github.com/solokeys/solo/blob/master/LICENSE) +[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors) +[![Build Status](https://travis-ci.com/solokeys/solo.svg?branch=master)](https://travis-ci.com/solokeys/solo) +[![Discourse Users](https://img.shields.io/discourse/https/discourse.solokeys.com/users.svg)](https://discourse.solokeys.com) +[![Keybase Chat](https://img.shields.io/badge/chat-on%20keybase-brightgreen.svg)](https://keybase.io/team/solokeys.public) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo?ref=badge_shield) + +[![latest release](https://img.shields.io/github/release/solokeys/solo.svg)](https://github.com/solokeys/solo/releases) +[![commits since last release](https://img.shields.io/github/commits-since/solokeys/solo/latest.svg)](https://github.com/solokeys/solo/commits/master) +[![last commit](https://img.shields.io/github/last-commit/solokeys/solo.svg)](https://github.com/solokeys/solo/commits/master) +[![commit activity](https://img.shields.io/github/commit-activity/m/solokeys/solo.svg)](https://github.com/solokeys/solo/commits/master) +[![contributors](https://img.shields.io/github/contributors/solokeys/solo.svg)](https://github.com/solokeys/solo/graphs/contributors) From 89e021003a89c5142c094238cb2b58e46bcf7c7a Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 16 Aug 2019 20:38:53 +0300 Subject: [PATCH 128/139] small fix for HID readers --- targets/stm32l432/src/nfc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index a10a273..d230849 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -852,6 +852,7 @@ int nfc_loop() printf1(TAG_NFC, "NFC_CMD_WUPA\r\n"); break; case NFC_CMD_HLTA: + ams_write_command(AMS_CMD_SLEEP); printf1(TAG_NFC, "HLTA/Halt\r\n"); break; case NFC_CMD_RATS: From 095b08e3d9fd47dbaf57dbef0b16b7ed3394a2dc Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Fri, 16 Aug 2019 21:53:52 +0300 Subject: [PATCH 129/139] add some stability in small responses --- targets/stm32l432/src/nfc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index d230849..f35f42b 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -196,7 +196,14 @@ bool nfc_write_response_ex(uint8_t req0, uint8_t * data, uint8_t len, uint16_t r res[len + block_offset + 0] = resp >> 8; res[len + block_offset + 1] = resp & 0xff; + nfc_write_frame(res, block_offset + len + 2); + + if (!ams_wait_for_tx(1)) + { + printf1(TAG_NFC, "TX resp timeout. len: %d \r\n", len); + return false; + } return true; } From 4b6619b705e04e904c2b500c111f3457f289a40a Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 21 Aug 2019 02:31:20 +0200 Subject: [PATCH 130/139] Update udev docs --- docs/solo/udev.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/solo/udev.md b/docs/solo/udev.md index 6866f99..7dae7e4 100644 --- a/docs/solo/udev.md +++ b/docs/solo/udev.md @@ -1,20 +1,20 @@ # Summary -On Linux, by default USB dongles can't be accessed by users, for security reasons. To allow user access, so-called "udev rules" must be installed. (Under Fedora, your key may work without such a rule.) +On Linux, by default USB dongles can't be accessed by users, for security reasons. To allow user access, so-called "udev rules" must be installed. -Create a file like [`70-solokeys-access.rules`](https://github.com/solokeys/solo/blob/master/udev/70-solokeys-access.rules) in your `/etc/udev/rules.d` directory, for instance the following rule should cover normal access (it has to be on one line): +For some users, things will work automatically: + - Fedora seems to use a ["universal" udev rule for FIDO devices](https://github.com/amluto/u2f-hidraw-policy) + - Our udev rule made it into [libu2f-host](https://github.com/Yubico/libu2f-host/) v1.1.10 + - Arch Linux [has this package](https://www.archlinux.org/packages/community/x86_64/libu2f-host/) + - [Debian sid](https://packages.debian.org/sid/libu2f-udev) and [Ubuntu Eon](https://packages.ubuntu.com/eoan/libu2f-udev) can use the `libu2f-udev` package + - Debian Buster and Ubuntu Disco still distribute v1.1.10, so need the manual rule + - FreeBSD has support in [u2f-devd](https://github.com/solokeys/solo/issues/144#issuecomment-500216020) -``` -SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a2ca", TAG+="uaccess", MODE="0660", GROUP="plugdev" -``` +There is hope that `udev` itself will adopt the Fedora approach (which is to check for HID usage page `F1D0`, and avoids manually whitelisting each U2F/FIDO2 key): . -Additionally, run the following command after you create this file (it is not necessary to do this again in the future): +Further progress is tracked in: . -``` -sudo udevadm control --reload-rules && sudo udevadm trigger -``` - -A simple way to setup both the udev rule and the udevadm reload is: +If you still need to setup a rule, a simple way to do it is: ``` git clone git@github.com:solokeys/solo.git @@ -22,9 +22,11 @@ cd solo/udev make setup ``` -We are working on getting user access to Solo keys enabled automatically in common Linux distributions: . - - +Or, manually, create a file like [`70-solokeys-access.rules`](https://github.com/solokeys/solo/blob/master/udev/70-solokeys-access.rules) in your `/etc/udev/rules.d` directory. +Additionally, run the following command after you create this file (it is not necessary to do this again in the future): +``` +sudo udevadm control --reload-rules && sudo udevadm trigger +``` # How do udev rules work and why are they needed From b706cc30b02e21e45d5d5075a3acf63f444d0d8d Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 20 Aug 2019 20:23:18 +0800 Subject: [PATCH 131/139] for now, always gen key agreement --- fido2/ctap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index dea0f3f..c364005 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -1678,7 +1678,7 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) break; default: status = CTAP1_ERR_INVALID_COMMAND; - printf2(TAG_ERR,"error, invalid cmd\n"); + printf2(TAG_ERR,"error, invalid cmd: %x\n", cmd); } done: @@ -1767,7 +1767,7 @@ void ctap_init() exit(1); } - if (device_is_nfc() != NFC_IS_ACTIVE) + // if (device_is_nfc() != NFC_IS_ACTIVE) { ctap_reset_key_agreement(); } From d931954a1395904e796109f7c30901bdf4a40de7 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 20 Aug 2019 20:28:38 +0800 Subject: [PATCH 132/139] remove WTX, move debug log --- targets/stm32l432/src/nfc.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index f35f42b..3b42d79 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -489,9 +489,6 @@ void nfc_process_iblock(uint8_t * buf, int len) CTAP_RESPONSE ctap_resp; int status; uint16_t reslen; - - printf1(TAG_NFC,"Iblock: "); - dump_hex1(TAG_NFC, buf, len); uint8_t block_offset = p14443_block_offset(buf[0]); @@ -630,13 +627,15 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "FIDO2 CTAP message. %d\r\n", timestamp()); - WTX_on(WTX_TIME_DEFAULT); + // WTX_on(WTX_TIME_DEFAULT); request_from_nfc(true); + // if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); ctap_response_init(&ctap_resp); status = ctap_request(apdu.data, apdu.lc, &ctap_resp); + // if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); request_from_nfc(false); - if (!WTX_off()) - return; + // if (!WTX_off()) + // return; printf1(TAG_NFC, "CTAP resp: 0x%02x len: %d\r\n", status, ctap_resp.length); @@ -688,6 +687,9 @@ void nfc_process_iblock(uint8_t * buf, int len) nfc_write_response(buf[0], SW_INS_INVALID); break; } + + printf1(TAG_NFC,"prev.Iblock: "); + dump_hex1(TAG_NFC, buf, len); } static uint8_t ibuf[1024]; From adcbd3aeb8f6a6f851238ab6d98c7df92e55d3be Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 20 Aug 2019 21:06:37 +0800 Subject: [PATCH 133/139] speed up public key derivation slightly for nfc --- fido2/ctap.c | 4 ++++ targets/stm32l432/src/nfc.c | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index c364005..9302e1d 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -256,7 +256,9 @@ static int ctap_generate_cose_key(CborEncoder * cose_key, uint8_t * hmac_input, switch(algtype) { case COSE_ALG_ES256: + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); crypto_ecc256_derive_public_key(hmac_input, len, x, y); + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); break; default: printf2(TAG_ERR,"Error, COSE alg %d not supported\n", algtype); @@ -1969,7 +1971,9 @@ int8_t ctap_load_key(uint8_t index, uint8_t * key) static void ctap_reset_key_agreement() { + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); crypto_ecc256_make_key_pair(KEY_AGREEMENT_PUB, KEY_AGREEMENT_PRIV); + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); } void ctap_reset() diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 3b42d79..726c47e 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -629,10 +629,8 @@ void nfc_process_iblock(uint8_t * buf, int len) // WTX_on(WTX_TIME_DEFAULT); request_from_nfc(true); - // if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); ctap_response_init(&ctap_resp); status = ctap_request(apdu.data, apdu.lc, &ctap_resp); - // if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); request_from_nfc(false); // if (!WTX_off()) // return; From a72f0ede05ceb04d722156c23bf34dfa5e96869d Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 20 Aug 2019 21:27:52 +0800 Subject: [PATCH 134/139] take a lazy approach to key agreement generation to not hold up boot time for nfc --- fido2/crypto.c | 5 +++++ fido2/crypto.h | 1 + fido2/ctap.c | 14 +++++++------- targets/stm32l432/src/crypto.c | 5 +++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/fido2/crypto.c b/fido2/crypto.c index 63520c1..6aea29f 100644 --- a/fido2/crypto.c +++ b/fido2/crypto.c @@ -262,6 +262,11 @@ void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8 memmove(y,pubkey+32,32); } +void crypto_ecc256_compute_public_key(uint8_t * privkey, uint8_t * pubkey) +{ + uECC_compute_public_key(privkey, pubkey, _es256_curve); +} + void crypto_load_external_key(uint8_t * key, int len) { _signing_key = key; diff --git a/fido2/crypto.h b/fido2/crypto.h index e9e4433..6b67b02 100644 --- a/fido2/crypto.h +++ b/fido2/crypto.h @@ -26,6 +26,7 @@ void crypto_sha512_final(uint8_t * hash); void crypto_ecc256_init(); void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8_t * y); +void crypto_ecc256_compute_public_key(uint8_t * privkey, uint8_t * pubkey); void crypto_ecc256_load_key(uint8_t * data, int len, uint8_t * data2, int len2); void crypto_ecc256_load_attestation_key(); diff --git a/fido2/ctap.c b/fido2/ctap.c index 9302e1d..8edf591 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -1481,6 +1481,11 @@ uint8_t ctap_client_pin(CborEncoder * encoder, uint8_t * request, int length) ret = cbor_encode_int(&map, RESP_keyAgreement); check_ret(ret); + + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); + crypto_ecc256_compute_public_key(KEY_AGREEMENT_PRIV, KEY_AGREEMENT_PUB); + if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); + ret = ctap_add_cose_key(&map, KEY_AGREEMENT_PUB, KEY_AGREEMENT_PUB+32, PUB_KEY_CRED_PUB_KEY, COSE_ALG_ECDH_ES_HKDF_256); check_retr(ret); @@ -1769,10 +1774,7 @@ void ctap_init() exit(1); } - // if (device_is_nfc() != NFC_IS_ACTIVE) - { - ctap_reset_key_agreement(); - } + ctap_reset_key_agreement(); #ifdef BRIDGE_TO_WALLET wallet_init(); @@ -1971,9 +1973,7 @@ int8_t ctap_load_key(uint8_t index, uint8_t * key) static void ctap_reset_key_agreement() { - if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); - crypto_ecc256_make_key_pair(KEY_AGREEMENT_PUB, KEY_AGREEMENT_PRIV); - if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); + ctap_generate_rng(KEY_AGREEMENT_PRIV, sizeof(KEY_AGREEMENT_PRIV)); } void ctap_reset() diff --git a/targets/stm32l432/src/crypto.c b/targets/stm32l432/src/crypto.c index 33fef68..1dde2f3 100644 --- a/targets/stm32l432/src/crypto.c +++ b/targets/stm32l432/src/crypto.c @@ -282,6 +282,11 @@ void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8 memmove(x,pubkey,32); memmove(y,pubkey+32,32); } +void crypto_ecc256_compute_public_key(uint8_t * privkey, uint8_t * pubkey) +{ + uECC_compute_public_key(privkey, pubkey, _es256_curve); +} + void crypto_load_external_key(uint8_t * key, int len) { From dccfb0d1b3c4d02185e9c8725772221541fc4ac7 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 20 Aug 2019 21:31:02 +0800 Subject: [PATCH 135/139] stub pc build --- pc/device.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pc/device.c b/pc/device.c index 2a0e166..0944254 100644 --- a/pc/device.c +++ b/pc/device.c @@ -628,3 +628,8 @@ int device_is_nfc() { return 0; } + +void device_set_clock_rate(DEVICE_CLOCK_RATE param) +{ + +} From b743d5fac56a1c598054890f00d4efc58ccdbda2 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 20 Aug 2019 18:42:20 +0300 Subject: [PATCH 136/139] sketch --- fido2/apdu.h | 2 + targets/stm32l432/src/nfc.c | 87 ++++++++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/fido2/apdu.h b/fido2/apdu.h index 44029b3..7ce8477 100644 --- a/fido2/apdu.h +++ b/fido2/apdu.h @@ -44,12 +44,14 @@ extern int apdu_decode(uint8_t *data, size_t len, APDU_STRUCT *apdu); #define APDU_FIDO_NFCCTAP_MSG 0x10 #define APDU_INS_SELECT 0xA4 #define APDU_INS_READ_BINARY 0xB0 +#define APDU_GET_RESPONSE 0xC0 #define SW_SUCCESS 0x9000 #define SW_GET_RESPONSE 0x6100 // Command successfully executed; 'XX' bytes of data are available and can be requested using GET RESPONSE. #define SW_WRONG_LENGTH 0x6700 #define SW_COND_USE_NOT_SATISFIED 0x6985 #define SW_FILE_NOT_FOUND 0x6a82 +#define SW_INCORRECT_P1P2 0x6a86 #define SW_INS_INVALID 0x6d00 // Instruction code not supported or invalid #define SW_CLA_INVALID 0x6e00 #define SW_INTERNAL_EXCEPTION 0x6f00 diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 726c47e..bf484da 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -14,6 +14,10 @@ #define IS_IRQ_ACTIVE() (1 == (LL_GPIO_ReadInputPort(SOLO_AMS_IRQ_PORT) & SOLO_AMS_IRQ_PIN)) +// chain buffer for 61XX responses +static uint8_t resp_chain_buffer[2048] = {0}; +static size_t resp_chain_buffer_len = 0; + uint8_t p14443_block_offset(uint8_t pcb) { uint8_t offset = 1; // NAD following @@ -213,7 +217,7 @@ bool nfc_write_response(uint8_t req0, uint16_t resp) return nfc_write_response_ex(req0, NULL, 0, resp); } -void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) +void nfc_write_response_chaining_plain(uint8_t req0, uint8_t * data, int len) { uint8_t res[32 + 2]; uint8_t iBlock = NFC_CMD_IBLOCK | (req0 & 0x0f); @@ -284,6 +288,38 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len) } } +void append_get_response(uint8_t *data, size_t rest_len) +{ + data[0] = 0x61; + data[1] = 0x00; + if (rest_len <= 0xff) + data[1] = rest_len & 0xff; +} + +void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len, bool extapdu) +{ + resp_chain_buffer_len = 0; + + // if we dont need to break data to parts that need to exchange via GET RESPONSE command (ISO 7816-4 7.1.3) +printf1(TAG_NFC, "---%d.\r\n", extapdu); + if (len <= 255 || extapdu) + { + nfc_write_response_chaining_plain(req0, data, len); + } else { + size_t pcklen = MIN(253, len); + resp_chain_buffer_len = len - pcklen; + printf1(TAG_NFC, "61XX chaining %d/%d.\r\n", pcklen, resp_chain_buffer_len); + + memmove(resp_chain_buffer, data, pcklen); + append_get_response(&resp_chain_buffer[pcklen], resp_chain_buffer_len); + + nfc_write_response_chaining_plain(req0, data, pcklen + 2); // 2 for 61XX + + // put the rest data into chain buffer + memmove(resp_chain_buffer, &data[pcklen], resp_chain_buffer_len); + } +} + // WTX on/off: // sends/receives WTX frame to reader every `WTX_time` time in ms // works via timer interrupts @@ -511,6 +547,47 @@ void nfc_process_iblock(uint8_t * buf, int len) // TODO this needs to be organized better switch(apdu.ins) { + // ISO 7816. 7.1 GET RESPONSE command + case APDU_GET_RESPONSE: + if (apdu.p1 != 0x00 || apdu.p2 != 0x00) + { + nfc_write_response(buf[0], SW_INCORRECT_P1P2); + return; + } + + // too many bytes needs. 0x00 and 0x100 - any length + if (apdu.le != 0 && apdu.le != 0x100 && apdu.le > resp_chain_buffer_len) + { + uint16_t wlresp = SW_WRONG_LENGTH; // here can be 6700, 6C00, 6FXX. but the most standard way - 67XX or 6700 + if (resp_chain_buffer_len <= 0xff) + wlresp += resp_chain_buffer_len & 0xff; + nfc_write_response(buf[0], wlresp); + return; + } + + // create temporary packet + uint8_t pck[255] = {0}; + int pcklen = MIN(253, apdu.le); + if (pcklen > resp_chain_buffer_len) + pcklen = resp_chain_buffer_len; + + // create packet and add 61XX there if we have another portion(s) of data + memmove(pck, resp_chain_buffer, pcklen); + size_t dlen = 0; + if (resp_chain_buffer_len - pcklen) + { + append_get_response(&pck[pcklen], resp_chain_buffer_len - pcklen); + dlen = 2; + } + + // send + nfc_write_response_chaining(buf[0], pck, pcklen + dlen, apdu.extended_apdu); // dlen for 61XX + + // shift the buffer + resp_chain_buffer_len -= pcklen; + memmove(resp_chain_buffer, &resp_chain_buffer[pcklen], resp_chain_buffer_len); + break; + case APDU_INS_SELECT: // if (apdu->p1 == 0 && apdu->p2 == 0x0c) // { @@ -554,7 +631,7 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F GetVersion command.\r\n"); u2f_request_nfc(&buf[block_offset], apdu.data, apdu.lc, &ctap_resp); - nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); + nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length, apdu.extended_apdu); break; case APDU_FIDO_U2F_REGISTER: @@ -586,7 +663,7 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F resp len: %d\r\n", ctap_resp.length); printf1(TAG_NFC,"U2F Register P2 took %d\r\n", timestamp()); - nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); + nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length, apdu.extended_apdu); printf1(TAG_NFC,"U2F Register answered %d (took %d)\r\n", millis(), timestamp()); break; @@ -615,7 +692,7 @@ void nfc_process_iblock(uint8_t * buf, int len) printf1(TAG_NFC, "U2F resp len: %d\r\n", ctap_resp.length); printf1(TAG_NFC,"U2F Authenticate processing %d (took %d)\r\n", millis(), timestamp()); - nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); + nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length, apdu.extended_apdu); printf1(TAG_NFC,"U2F Authenticate answered %d (took %d)\r\n", millis(), timestamp); break; @@ -649,7 +726,7 @@ void nfc_process_iblock(uint8_t * buf, int len) ctap_resp.data[ctap_resp.length - 1] = SW_SUCCESS & 0xff; printf1(TAG_NFC,"CTAP processing %d (took %d)\r\n", millis(), timestamp()); - nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length); + nfc_write_response_chaining(buf[0], ctap_resp.data, ctap_resp.length, apdu.extended_apdu); printf1(TAG_NFC,"CTAP answered %d (took %d)\r\n", millis(), timestamp()); break; From 8059a9765f7aa2d4a1517024ec3a17f14618026e Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 20 Aug 2019 18:53:46 +0300 Subject: [PATCH 137/139] was wrong buffer --- targets/stm32l432/src/nfc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index bf484da..85dace4 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -301,7 +301,6 @@ void nfc_write_response_chaining(uint8_t req0, uint8_t * data, int len, bool ext resp_chain_buffer_len = 0; // if we dont need to break data to parts that need to exchange via GET RESPONSE command (ISO 7816-4 7.1.3) -printf1(TAG_NFC, "---%d.\r\n", extapdu); if (len <= 255 || extapdu) { nfc_write_response_chaining_plain(req0, data, len); @@ -313,7 +312,7 @@ printf1(TAG_NFC, "---%d.\r\n", extapdu); memmove(resp_chain_buffer, data, pcklen); append_get_response(&resp_chain_buffer[pcklen], resp_chain_buffer_len); - nfc_write_response_chaining_plain(req0, data, pcklen + 2); // 2 for 61XX + nfc_write_response_chaining_plain(req0, resp_chain_buffer, pcklen + 2); // 2 for 61XX // put the rest data into chain buffer memmove(resp_chain_buffer, &data[pcklen], resp_chain_buffer_len); From 62b4418dac714b79d984270d856e0bf05ad5dd03 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 20 Aug 2019 19:07:33 +0300 Subject: [PATCH 138/139] fix pck length math --- targets/stm32l432/src/nfc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 85dace4..9b75efa 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -551,6 +551,7 @@ void nfc_process_iblock(uint8_t * buf, int len) if (apdu.p1 != 0x00 || apdu.p2 != 0x00) { nfc_write_response(buf[0], SW_INCORRECT_P1P2); + printf1(TAG_NFC, "P1 or P2 error\r\n"); return; } @@ -561,15 +562,20 @@ void nfc_process_iblock(uint8_t * buf, int len) if (resp_chain_buffer_len <= 0xff) wlresp += resp_chain_buffer_len & 0xff; nfc_write_response(buf[0], wlresp); + printf1(TAG_NFC, "buffer length less than requesteds\r\n"); return; } // create temporary packet uint8_t pck[255] = {0}; - int pcklen = MIN(253, apdu.le); + size_t pcklen = 253; + if (apdu.le) + pcklen = apdu.le; if (pcklen > resp_chain_buffer_len) pcklen = resp_chain_buffer_len; + printf1(TAG_NFC, "--- %d/%d\r\n", pcklen, resp_chain_buffer_len); + // create packet and add 61XX there if we have another portion(s) of data memmove(pck, resp_chain_buffer, pcklen); size_t dlen = 0; From 728acc1671292f655022f5511af3a00058337511 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Tue, 20 Aug 2019 19:29:25 +0300 Subject: [PATCH 139/139] chaining not needs to go to the start --- targets/stm32l432/src/nfc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/targets/stm32l432/src/nfc.c b/targets/stm32l432/src/nfc.c index 9b75efa..1d935b0 100644 --- a/targets/stm32l432/src/nfc.c +++ b/targets/stm32l432/src/nfc.c @@ -574,8 +574,8 @@ void nfc_process_iblock(uint8_t * buf, int len) if (pcklen > resp_chain_buffer_len) pcklen = resp_chain_buffer_len; - printf1(TAG_NFC, "--- %d/%d\r\n", pcklen, resp_chain_buffer_len); - + printf1(TAG_NFC, "GET RESPONSE. pck len: %d buffer len: %d\r\n", pcklen, resp_chain_buffer_len); + // create packet and add 61XX there if we have another portion(s) of data memmove(pck, resp_chain_buffer, pcklen); size_t dlen = 0; @@ -586,7 +586,7 @@ void nfc_process_iblock(uint8_t * buf, int len) } // send - nfc_write_response_chaining(buf[0], pck, pcklen + dlen, apdu.extended_apdu); // dlen for 61XX + nfc_write_response_chaining_plain(buf[0], pck, pcklen + dlen); // dlen for 61XX // shift the buffer resp_chain_buffer_len -= pcklen;
Szczepan Zalega
Szczepan Zalega

💻
Szczepan Zalega
Szczepan Zalega

💻 📖 🤔
Wessel dR
Wessel dR

📖
Adam Langley
Adam Langley

🐛 💻
Oleg Moiseenko
Oleg Moiseenko

💻