Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
2af747ddaa | |||
9ead11de8d | |||
f17faca689 | |||
ca66b6e43b | |||
1cd1b3c295 | |||
df2cff2350 | |||
f5d50e001d | |||
235785b225 | |||
303c42901a | |||
df2f950e69 | |||
10bf4242e1 | |||
9e95b0075c | |||
ddbe31776c | |||
645ca6a5a0 | |||
15fc39faed | |||
63ee003535 | |||
3f225f362f |
@ -1 +1 @@
|
|||||||
2.3.0
|
2.4.0
|
||||||
|
@ -1,22 +1,40 @@
|
|||||||
|
# Building solo
|
||||||
|
|
||||||
To build, develop and debug the firmware for the STM32L432. This will work
|
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.
|
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)).
|
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.
|
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.
|
||||||
|
|
||||||
You can also install the ARM toolchain using a package manager like `apt-get` or `pacman`,
|
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`.
|
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.
|
To program your build, you'll need one of the following programs.
|
||||||
|
|
||||||
- [openocd](http://openocd.org)
|
- [openocd](http://openocd.org)
|
||||||
- [stlink](https://github.com/texane/stlink)
|
- [stlink](https://github.com/texane/stlink)
|
||||||
- [STM32CubeProg](https://www.st.com/en/development-tools/stm32cubeprog.html)
|
- [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.
|
Enter the `stm32l4xx` target directory.
|
||||||
|
|
||||||
@ -80,8 +98,7 @@ 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.
|
||||||
|
|
||||||
|
## Programming
|
||||||
# Programming
|
|
||||||
|
|
||||||
It's recommended to test a debug/hacker build first to make sure Solo is working as expected.
|
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!).
|
Then you can switch to a locked down build, which cannot be reprogrammed as easily (or not at all!).
|
||||||
@ -95,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/).
|
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
|
If your Solo device is already programmed (it flashes green when powered), we recommend
|
||||||
programming it using the Solo bootloader.
|
programming it using the Solo bootloader.
|
||||||
@ -118,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).
|
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.
|
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
|
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".
|
off and it enumerates as "STM BOOTLOADER".
|
||||||
@ -136,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.
|
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.
|
A Solo hacker device doesn't need to be in bootloader mode to be programmed, it will automatically switch.
|
||||||
|
|
||||||
@ -144,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.
|
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.
|
If this is not a device with a hacker build, you can only program signed updates.
|
||||||
|
|
||||||
@ -162,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
|
If your Solo isn't locked, you can always reprogram it using a debugger connected directly
|
||||||
to the token.
|
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
|
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.
|
someone from connecting a debugger to your token and stealing credentials.
|
||||||
|
@ -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_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);
|
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_TRANSPORT_KEY ((uint8_t*)1)
|
||||||
#define CRYPTO_MASTER_KEY ((uint8_t*)0)
|
#define CRYPTO_MASTER_KEY ((uint8_t*)0)
|
||||||
|
|
||||||
|
@ -355,9 +355,9 @@ static int ctap_make_extensions(CTAP_extensions * ext, uint8_t * ext_encoder_buf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate credRandom
|
// 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_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
|
// Decrypt saltEnc
|
||||||
crypto_aes256_init(shared_secret, NULL);
|
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_sha256_final(hashbuf);
|
||||||
|
|
||||||
crypto_ecc256_sign(hashbuf, 32, sigbuf);
|
crypto_ecc256_sign(hashbuf, 32, sigbuf);
|
||||||
|
|
||||||
return ctap_encode_der_sig(sigbuf,sigder);
|
return ctap_encode_der_sig(sigbuf,sigder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1056,7 +1055,7 @@ uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cr
|
|||||||
else
|
else
|
||||||
#endif
|
#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);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -342,6 +342,7 @@ static uint8_t USBD_HID_Setup (USBD_HandleTypeDef *pdev,
|
|||||||
uint8_t *pbuf = NULL;
|
uint8_t *pbuf = NULL;
|
||||||
uint16_t status_info = 0U;
|
uint16_t status_info = 0U;
|
||||||
USBD_StatusTypeDef ret = USBD_OK;
|
USBD_StatusTypeDef ret = USBD_OK;
|
||||||
|
req->wLength = req->wLength & 0x7f;
|
||||||
|
|
||||||
switch (req->bmRequest & USB_REQ_TYPE_MASK)
|
switch (req->bmRequest & USB_REQ_TYPE_MASK)
|
||||||
{
|
{
|
||||||
@ -386,6 +387,7 @@ static uint8_t USBD_HID_Setup (USBD_HandleTypeDef *pdev,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case USB_REQ_GET_DESCRIPTOR:
|
case USB_REQ_GET_DESCRIPTOR:
|
||||||
|
req->wLength = req->wLength & 0x7f;
|
||||||
if(req->wValue >> 8 == HID_REPORT_DESC)
|
if(req->wValue >> 8 == HID_REPORT_DESC)
|
||||||
{
|
{
|
||||||
len = MIN(HID_FIDO_REPORT_DESC_SIZE , req->wLength);
|
len = MIN(HID_FIDO_REPORT_DESC_SIZE , req->wLength);
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
// #define DISABLE_CTAPHID_WINK
|
// #define DISABLE_CTAPHID_WINK
|
||||||
// #define DISABLE_CTAPHID_CBOR
|
// #define DISABLE_CTAPHID_CBOR
|
||||||
|
|
||||||
#define ENABLE_SERIAL_PRINTING
|
// #define ENABLE_SERIAL_PRINTING
|
||||||
|
|
||||||
#if defined(SOLO_HACKER)
|
#if defined(SOLO_HACKER)
|
||||||
#define SOLO_PRODUCT_NAME "Solo Hacker " SOLO_VERSION
|
#define SOLO_PRODUCT_NAME "Solo Hacker " SOLO_VERSION
|
||||||
|
@ -157,6 +157,11 @@ void crypto_sha256_hmac_final(uint8_t * key, uint32_t klen, uint8_t * hmac)
|
|||||||
key = master_secret;
|
key = master_secret;
|
||||||
klen = sizeof(master_secret)/2;
|
klen = sizeof(master_secret)/2;
|
||||||
}
|
}
|
||||||
|
else if (key == CRYPTO_TRANSPORT_KEY2)
|
||||||
|
{
|
||||||
|
key = transport_secret;
|
||||||
|
klen = 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(klen > 64)
|
if(klen > 64)
|
||||||
|
@ -211,7 +211,7 @@ class FIDO2Tests(Tester):
|
|||||||
assert "hmac-secret" in reg.auth_data.extensions
|
assert "hmac-secret" in reg.auth_data.extensions
|
||||||
assert reg.auth_data.extensions["hmac-secret"] == True
|
assert reg.auth_data.extensions["hmac-secret"] == True
|
||||||
|
|
||||||
reg = self.testMC(
|
self.testMC(
|
||||||
"Send MC with fake extension set to true, expect SUCCESS",
|
"Send MC with fake extension set to true, expect SUCCESS",
|
||||||
cdh,
|
cdh,
|
||||||
rp,
|
rp,
|
||||||
@ -278,6 +278,10 @@ class FIDO2Tests(Tester):
|
|||||||
assert shannon_entropy(ext["hmac-secret"]) > 5.4
|
assert shannon_entropy(ext["hmac-secret"]) > 5.4
|
||||||
assert shannon_entropy(key) > 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,))
|
salt_enc, salt_auth = get_salt_params((salt3,))
|
||||||
|
|
||||||
auth = self.testGA(
|
auth = self.testGA(
|
||||||
@ -743,6 +747,40 @@ class FIDO2Tests(Tester):
|
|||||||
expectedError=CtapError.ERR.SUCCESS,
|
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"):
|
with Test("Test auth_data is 37 bytes"):
|
||||||
assert len(prev_auth.auth_data) == 37
|
assert len(prev_auth.auth_data) == 37
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import time, struct
|
|||||||
|
|
||||||
from fido2.hid import CtapHidDevice
|
from fido2.hid import CtapHidDevice
|
||||||
from fido2.client import Fido2Client
|
from fido2.client import Fido2Client
|
||||||
|
from fido2.attestation import Attestation
|
||||||
from fido2.ctap1 import CTAP1
|
from fido2.ctap1 import CTAP1
|
||||||
from fido2.utils import Timeout
|
from fido2.utils import Timeout
|
||||||
|
|
||||||
@ -73,14 +74,12 @@ class Tester:
|
|||||||
dev = next(CtapHidDevice.list_devices(), None)
|
dev = next(CtapHidDevice.list_devices(), None)
|
||||||
|
|
||||||
if not dev:
|
if not dev:
|
||||||
try:
|
|
||||||
from fido2.pcsc import CtapPcscDevice
|
from fido2.pcsc import CtapPcscDevice
|
||||||
|
|
||||||
print("--- NFC ---")
|
print("--- NFC ---")
|
||||||
print(list(CtapPcscDevice.list_devices()))
|
print(list(CtapPcscDevice.list_devices()))
|
||||||
dev = next(CtapPcscDevice.list_devices(), None)
|
dev = next(CtapPcscDevice.list_devices(), None)
|
||||||
except (ModuleNotFoundError, ImportError):
|
|
||||||
print("One of NFC library is not installed properly.")
|
|
||||||
if not dev:
|
if not dev:
|
||||||
raise RuntimeError("No FIDO device found")
|
raise RuntimeError("No FIDO device found")
|
||||||
self.dev = dev
|
self.dev = dev
|
||||||
@ -203,7 +202,18 @@ class Tester:
|
|||||||
self.ctap.reset()
|
self.ctap.reset()
|
||||||
|
|
||||||
def testMC(self, test, *args, **kwargs):
|
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:
|
||||||
|
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):
|
def testGA(self, test, *args, **kwargs):
|
||||||
return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs)
|
return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs)
|
||||||
|
@ -78,6 +78,16 @@ class U2FTests(Tester):
|
|||||||
auth = self.authenticate(chal, appid, regs[i].key_handle)
|
auth = self.authenticate(chal, appid, regs[i].key_handle)
|
||||||
auth.verify(appid, chal, regs[i].public_key)
|
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...")
|
print("Check that all previous credentials are registered...")
|
||||||
for i in range(0, self.user_count):
|
for i in range(0, self.user_count):
|
||||||
with Test("Check that previous credential %d is registered" % i):
|
with Test("Check that previous credential %d is registered" % i):
|
||||||
|
Reference in New Issue
Block a user