Merge branch 'master' into license-change
This commit is contained in:
commit
894f6f7ee1
@ -4,10 +4,12 @@
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
#include <stdint.h>
|
||||
#include "extensions.h"
|
||||
#include "u2f.h"
|
||||
#include "wallet.h"
|
||||
#include "solo.h"
|
||||
#include "device.h"
|
||||
|
||||
#include "log.h"
|
||||
@ -54,6 +56,8 @@ int16_t bridge_u2f_to_extensions(uint8_t * _chal, uint8_t * _appid, uint8_t klen
|
||||
ret = bootloader_bridge(klen, keyh);
|
||||
#elif defined(WALLET_EXTENSION)
|
||||
ret = bridge_u2f_to_wallet(_chal, _appid, klen, keyh);
|
||||
#else
|
||||
ret = bridge_u2f_to_solo(_chal, _appid, klen, keyh);
|
||||
#endif
|
||||
|
||||
if (ret != 0)
|
||||
|
77
fido2/extensions/solo.c
Normal file
77
fido2/extensions/solo.c
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2018 SoloKeys, Inc. <https://solokeys.com/>
|
||||
*
|
||||
* This file is part of Solo.
|
||||
*
|
||||
* Solo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Solo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Solo. If not, see <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This code is available under licenses for commercial use.
|
||||
* Please contact SoloKeys for more information.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "extensions.h"
|
||||
#include "u2f.h"
|
||||
#include "wallet.h"
|
||||
#include "device.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap_errors.h"
|
||||
|
||||
#include "log.h"
|
||||
#include APP_CONFIG
|
||||
|
||||
int16_t bridge_u2f_to_solo(uint8_t * _chal, uint8_t * _appid, uint8_t klen, uint8_t * keyh)
|
||||
{
|
||||
static uint8_t msg_buf[72];
|
||||
int reqlen = klen;
|
||||
int i;
|
||||
int8_t ret = 0;
|
||||
|
||||
wallet_request * req = (wallet_request *) keyh;
|
||||
|
||||
printf1(TAG_WALLET, "u2f-solo [%d]: ", reqlen); dump_hex1(TAG_WALLET, keyh, reqlen);
|
||||
|
||||
switch(req->operation)
|
||||
{
|
||||
case WalletVersion:
|
||||
msg_buf[0] = SOLO_VERSION_MAJ;
|
||||
msg_buf[1] = SOLO_VERSION_MIN;
|
||||
msg_buf[2] = SOLO_VERSION_PATCH;
|
||||
u2f_response_writeback(msg_buf, 3);
|
||||
break;
|
||||
case WalletRng:
|
||||
printf1(TAG_WALLET,"SoloRng\n");
|
||||
|
||||
ret = ctap_generate_rng(msg_buf, 72);
|
||||
if (ret != 1)
|
||||
{
|
||||
printf1(TAG_WALLET,"Rng failed\n");
|
||||
ret = CTAP2_ERR_PROCESSING;
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
u2f_response_writeback((uint8_t *)msg_buf,72);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf2(TAG_ERR,"Invalid wallet command: %x\n",req->operation);
|
||||
ret = CTAP1_ERR_INVALID_COMMAND;
|
||||
break;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
return ret;
|
||||
}
|
27
fido2/extensions/solo.h
Normal file
27
fido2/extensions/solo.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2018 SoloKeys, Inc. <https://solokeys.com/>
|
||||
*
|
||||
* This file is part of Solo.
|
||||
*
|
||||
* Solo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Solo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Solo. If not, see <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This code is available under licenses for commercial use.
|
||||
* Please contact SoloKeys for more information.
|
||||
*/
|
||||
#ifndef SOLO_H_
|
||||
#define SOLO_H_
|
||||
|
||||
int16_t bridge_u2f_to_solo(uint8_t * _chal, uint8_t * _appid, uint8_t klen, uint8_t * keyh);
|
||||
|
||||
#endif
|
@ -2,30 +2,33 @@ ifndef DEBUG
|
||||
DEBUG=0
|
||||
endif
|
||||
|
||||
APPMAKE=build/application.mk
|
||||
BOOTMAKE=build/application.mk
|
||||
|
||||
merge_hex=python ../../tools/solotool.py mergehex
|
||||
|
||||
all:
|
||||
$(MAKE) -f application.mk -j8 solo.hex PREFIX=$(PREFIX) DEBUG=$(DEBUG) EXTRA_DEFINES='-DFLASH_ROP=1'
|
||||
$(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) DEBUG=$(DEBUG) EXTRA_DEFINES='-DFLASH_ROP=1'
|
||||
|
||||
all-hacker:
|
||||
$(MAKE) -f application.mk -j8 solo.hex PREFIX=$(PREFIX) DEBUG=$(DEBUG) EXTRA_DEFINES='-DSOLO_HACKER -DFLASH_ROP=0'
|
||||
$(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) DEBUG=$(DEBUG) EXTRA_DEFINES='-DSOLO_HACKER -DFLASH_ROP=0'
|
||||
|
||||
all-locked:
|
||||
$(MAKE) -f application.mk -j8 solo.hex PREFIX=$(PREFIX) EXTRA_DEFINES='-DFLASH_ROP=2'
|
||||
$(MAKE) -f $(APPMAKE) -j8 solo.hex PREFIX=$(PREFIX) EXTRA_DEFINES='-DFLASH_ROP=2'
|
||||
|
||||
debugboot-app:
|
||||
$(MAKE) -f application.mk -j8 solo.hex DEBUG=2 PREFIX=$(PREFIX)\
|
||||
$(MAKE) -f $(APPMAKE) -j8 solo.hex DEBUG=2 PREFIX=$(PREFIX)\
|
||||
LDSCRIPT=linker/stm32l4xx_extra.ld EXTRA_DEFINES='-DAPPLICATION_START_PAGE=16 -DSOLO_HACKER'
|
||||
|
||||
debugboot-boot:
|
||||
$(MAKE) -f bootloader.mk -j8 bootloader.hex PREFIX=$(PREFIX) DEBUG=1 \
|
||||
$(MAKE) -f $(BOOTMAKE) -j8 bootloader.hex PREFIX=$(PREFIX) DEBUG=1 \
|
||||
LDSCRIPT=linker/bootloader_stm32l4xx_extra.ld EXTRA_DEFINES='-DAPPLICATION_START_PAGE=16 -DSOLO_HACKER'
|
||||
|
||||
boot-sig-checking:
|
||||
$(MAKE) -f bootloader.mk -j8 bootloader.hex PREFIX=$(PREFIX) DEBUG=0
|
||||
$(MAKE) -f $(BOOTMAKE) -j8 bootloader.hex PREFIX=$(PREFIX) DEBUG=0
|
||||
|
||||
boot-no-sig:
|
||||
$(MAKE) -f bootloader.mk -j8 bootloader.hex PREFIX=$(PREFIX) EXTRA_DEFINES='-DSOLO_HACKER' DEBUG=0
|
||||
$(MAKE) -f $(BOOTMAKE) -j8 bootloader.hex PREFIX=$(PREFIX) EXTRA_DEFINES='-DSOLO_HACKER' DEBUG=0
|
||||
|
||||
build-release-locked: clean2 boot-sig-checking clean all-locked
|
||||
$(merge_hex) solo.hex bootloader.hex all.hex
|
||||
@ -38,12 +41,12 @@ build-hacker: clean2 boot-no-sig clean all-hacker
|
||||
$(merge_hex) solo.hex bootloader.hex all.hex
|
||||
|
||||
clean:
|
||||
$(MAKE) -f application.mk clean
|
||||
$(MAKE) -f bootloader.mk clean
|
||||
$(MAKE) -f $(APPMAKE) clean
|
||||
$(MAKE) -f $(BOOTMAKE) clean
|
||||
clean2:
|
||||
rm -f solo.hex bootloader.hex all.hex
|
||||
$(MAKE) -f application.mk clean
|
||||
$(MAKE) -f bootloader.mk clean
|
||||
$(MAKE) -f $(APPMAKE) clean
|
||||
$(MAKE) -f $(BOOTMAKE) clean
|
||||
|
||||
|
||||
flash: solo.hex bootloader.hex
|
||||
|
@ -4,6 +4,7 @@
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -176,8 +177,12 @@ int bootloader_bridge(int klen, uint8_t * keyh)
|
||||
case BootVersion:
|
||||
has_erased = 0;
|
||||
printf1(TAG_BOOT, "BootVersion.\r\n");
|
||||
version = BOOT_VERSION_MAJ;
|
||||
u2f_response_writeback(&version,1);
|
||||
version = BOOT_VERSION_MIN;
|
||||
u2f_response_writeback(&version,1);
|
||||
version = BOOT_VERSION_PATCH;
|
||||
u2f_response_writeback(&version,1);
|
||||
return 0;
|
||||
break;
|
||||
case BootReboot:
|
||||
printf1(TAG_BOOT, "BootReboot.\r\n");
|
||||
|
@ -4,9 +4,11 @@
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
#ifndef _APP_H_
|
||||
#define _APP_H_
|
||||
#include <stdint.h>
|
||||
#include "version.h"
|
||||
|
||||
#define DEBUG_UART USART1
|
||||
|
||||
@ -50,6 +52,8 @@
|
||||
#define SKIP_BUTTON_CHECK_WITH_DELAY 0
|
||||
#define SKIP_BUTTON_CHECK_FAST 1
|
||||
|
||||
#define SOLO_PRODUCT_NAME "Solo Bootloader " VERSION
|
||||
|
||||
void printing_init();
|
||||
void hw_init(void);
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
CC=$(PREFIX)arm-none-eabi-gcc
|
||||
CP=$(PREFIX)arm-none-eabi-objcopy
|
||||
SZ=$(PREFIX)arm-none-eabi-size
|
||||
AR=$(PREFIX)arm-none-eabi-ar
|
||||
include build/common.mk
|
||||
|
||||
# ST related
|
||||
SRC = src/main.c src/init.c src/redirect.c src/flash.c src/rng.c src/led.c src/device.c
|
||||
@ -13,6 +10,7 @@ SRC += $(wildcard lib/*.c) $(wildcard lib/usbd/*.c)
|
||||
SRC += ../../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
|
||||
|
||||
# Crypto libs
|
||||
SRC += ../../crypto/sha256/sha256.c ../../crypto/micro-ecc/uECC.c ../../crypto/tiny-AES-c/aes.c
|
||||
@ -45,7 +43,7 @@ endif
|
||||
DEFINES = -DDEBUG_LEVEL=$(DEBUG) -D$(CHIP) -DAES256=1 -DUSE_FULL_LL_DRIVER -DAPP_CONFIG=\"app.h\" $(EXTRA_DEFINES)
|
||||
# DEFINES += -DTEST_SOLO_STM32 -DTEST -DTEST_FIFO=1
|
||||
|
||||
CFLAGS=$(INC) -c $(DEFINES) -Wall -fdata-sections -ffunction-sections $(HW) -g
|
||||
CFLAGS=$(INC) -c $(DEFINES) -Wall -fdata-sections -ffunction-sections $(HW) -g $(VERSION_FLAGS)
|
||||
LDFLAGS_LIB=$(HW) $(SEARCH) -specs=nano.specs -specs=nosys.specs -Wl,--gc-sections -u _printf_float -lnosys
|
||||
LDFLAGS=$(HW) $(LDFLAGS_LIB) -T$(LDSCRIPT) -Wl,-Map=$(TARGET).map,--cref -Wl,-Bstatic -ltinycbor
|
||||
|
@ -1,7 +1,4 @@
|
||||
CC=$(PREFIX)arm-none-eabi-gcc
|
||||
CP=$(PREFIX)arm-none-eabi-objcopy
|
||||
SZ=$(PREFIX)arm-none-eabi-size
|
||||
AR=$(PREFIX)arm-none-eabi-ar
|
||||
include build/common.mk
|
||||
|
||||
# ST related
|
||||
SRC = bootloader/main.c bootloader/bootloader.c
|
||||
@ -44,7 +41,7 @@ endif
|
||||
DEFINES = -DDEBUG_LEVEL=$(DEBUG) -D$(CHIP) -DAES256=1 -DUSE_FULL_LL_DRIVER -DAPP_CONFIG=\"bootloader.h\" $(EXTRA_DEFINES)
|
||||
# DEFINES += -DTEST_SOLO_STM32 -DTEST -DTEST_FIFO=1
|
||||
|
||||
CFLAGS=$(INC) -c $(DEFINES) -Wall -fdata-sections -ffunction-sections $(HW) -g
|
||||
CFLAGS=$(INC) -c $(DEFINES) -Wall -fdata-sections -ffunction-sections $(HW) -g $(VERSION_FLAGS)
|
||||
LDFLAGS_LIB=$(HW) $(SEARCH) -specs=nano.specs -specs=nosys.specs -Wl,--gc-sections -lnosys
|
||||
LDFLAGS=$(HW) $(LDFLAGS_LIB) -T$(LDSCRIPT) -Wl,-Map=$(TARGET).map,--cref -Wl,-Bstatic
|
||||
|
19
targets/stm32l432/build/common.mk
Normal file
19
targets/stm32l432/build/common.mk
Normal file
@ -0,0 +1,19 @@
|
||||
CC=$(PREFIX)arm-none-eabi-gcc
|
||||
CP=$(PREFIX)arm-none-eabi-objcopy
|
||||
SZ=$(PREFIX)arm-none-eabi-size
|
||||
AR=$(PREFIX)arm-none-eabi-ar
|
||||
|
||||
VERSION=$(shell git describe --abbrev=0 )
|
||||
VERSION_FULL=$(shell git describe)
|
||||
VERSION_MAJ=$(shell python -c 'print("$(VERSION)".split(".")[0])')
|
||||
VERSION_MIN=$(shell python -c 'print("$(VERSION)".split(".")[1])')
|
||||
VERSION_PAT=$(shell python -c 'print("$(VERSION)".split(".")[2])')
|
||||
|
||||
VERSION_FLAGS= -DSOLO_VERSION_MAJ=$(VERSION_MAJ) -DSOLO_VERSION_MIN=$(VERSION_MIN) \
|
||||
-DSOLO_VERSION_PATCH=$(VERSION_PAT) -DVERSION=\"$(VERSION_FULL)\"
|
||||
|
||||
_all:
|
||||
echo $(VERSION_FULL)
|
||||
echo $(VERSION_MAJ)
|
||||
echo $(VERSION_MIN)
|
||||
echo $(VERSION_PAT)
|
@ -53,19 +53,10 @@
|
||||
#define USBD_VID 0x0483
|
||||
#define USBD_PID 0xA2CA
|
||||
#define USBD_LANGID_STRING 0x409
|
||||
#ifndef SOLO_HACKER
|
||||
#define USBD_MANUFACTURER_STRING "Solo Keys"
|
||||
#define USBD_PRODUCT_FS_STRING "Solo"
|
||||
#ifndef USBD_SERIAL_NUM
|
||||
#define USBD_MANUFACTURER_STRING "SoloKeys"
|
||||
#define USBD_PRODUCT_FS_STRING SOLO_PRODUCT_NAME
|
||||
#define USBD_SERIAL_NUM "0123456789ABCDEF"
|
||||
#endif
|
||||
#else
|
||||
#define USBD_MANUFACTURER_STRING "Solo Keys"
|
||||
#define USBD_PRODUCT_FS_STRING "Solo HACKER (Unlocked)"
|
||||
#ifndef USBD_SERIAL_NUM
|
||||
#define USBD_SERIAL_NUM "0123456789ABCDEF"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
uint8_t *USBD_HID_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
|
||||
uint8_t *USBD_HID_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#ifndef _APP_H_
|
||||
#define _APP_H_
|
||||
#include <stdint.h>
|
||||
#include "version.h"
|
||||
|
||||
#define DEBUG_UART USART1
|
||||
|
||||
@ -21,7 +22,7 @@
|
||||
|
||||
//#define USING_DEV_BOARD
|
||||
|
||||
//#define ENABLE_U2F_EXTENSIONS
|
||||
#define ENABLE_U2F_EXTENSIONS
|
||||
|
||||
#define ENABLE_U2F
|
||||
|
||||
@ -29,6 +30,13 @@
|
||||
// #define DISABLE_CTAPHID_WINK
|
||||
// #define DISABLE_CTAPHID_CBOR
|
||||
|
||||
|
||||
#if defined(SOLO_HACKER)
|
||||
#define SOLO_PRODUCT_NAME "Solo Hacker " VERSION
|
||||
#else
|
||||
#define SOLO_PRODUCT_NAME "Solo " VERSION
|
||||
#endif
|
||||
|
||||
void printing_init();
|
||||
void hw_init(void);
|
||||
|
||||
|
21
targets/stm32l432/src/version.h
Normal file
21
targets/stm32l432/src/version.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef _VERSION_H_
|
||||
#define _VERSION_H_
|
||||
|
||||
|
||||
#ifndef SOLO_VERSION_MAJ
|
||||
|
||||
#define SOLO_VERSION_MAJ 0
|
||||
#define SOLO_VERSION_MIN 0
|
||||
#define SOLO_VERSION_PATCH 0
|
||||
|
||||
#endif
|
||||
|
||||
#define __STR_HELPER(x) #x
|
||||
#define __STR(x) __STR_HELPER(x)
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION __STR(SOLO_VERSION_MAJ) "." __STR(SOLO_VERSION_MIN) "." __STR(SOLO_VERSION_PATCH)
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
@ -90,6 +90,9 @@ def get_firmware_object(sk_name, hex_file):
|
||||
msg = {'firmware': fw, 'signature': sig}
|
||||
return msg
|
||||
|
||||
class SoloExtension:
|
||||
version= 0x14
|
||||
rng = 0x15
|
||||
|
||||
class SoloBootloader:
|
||||
write = 0x40
|
||||
@ -196,10 +199,16 @@ class SoloClient:
|
||||
|
||||
return res.signature[1:]
|
||||
|
||||
def version(self,):
|
||||
def bootloader_version(self,):
|
||||
data = self.exchange(SoloBootloader.version)
|
||||
if len(data) > 2:
|
||||
return (data[0],data[1],data[2])
|
||||
return data[0]
|
||||
|
||||
def solo_version(self,):
|
||||
data = self.exchange_u2f(SoloExtension.version)
|
||||
return (data[0],data[1],data[2])
|
||||
|
||||
def write_flash(self, addr, data):
|
||||
self.exchange(SoloBootloader.write, addr, data)
|
||||
|
||||
@ -256,7 +265,7 @@ class SoloClient:
|
||||
|
||||
def is_solo_bootloader(self,):
|
||||
try:
|
||||
self.version()
|
||||
self.bootloader_version()
|
||||
return True
|
||||
except CtapError as e:
|
||||
if e.code == CtapError.ERR.INVALID_COMMAND:
|
||||
@ -583,7 +592,7 @@ def solo_main():
|
||||
)
|
||||
parser.add_argument("--wink", action="store_true", help='HID Wink command.')
|
||||
parser.add_argument("--reset", action="store_true", help='Issue a FIDO2 reset command. Warning: your credentials will be lost.')
|
||||
parser.add_argument("--verify-solo", action="store_true", help='Verify that the Solo firmware is from SoloKeys.')
|
||||
parser.add_argument("--verify-solo", action="store_true", help='Verify that the Solo firmware is from SoloKeys. Check firmware version.')
|
||||
args = parser.parse_args()
|
||||
|
||||
p = SoloClient()
|
||||
@ -604,12 +613,22 @@ def solo_main():
|
||||
|
||||
if args.verify_solo:
|
||||
cert = p.make_credential()
|
||||
|
||||
solo_fingerprint = b'r\xd5\x831&\xac\xfc\xe9\xa8\xe8&`\x18\xe6AI4\xc8\xbeJ\xb8h_\x91\xb0\x99!\x13\xbb\xd42\x95'
|
||||
hacker_fingerprint = b"\xd0ml\xcb\xda}\xe5j\x16'\xc2\xa7\x89\x9c5\xa2\xa3\x16\xc8Q\xb3j\xd8\xed~\xd7\x84y\xbbx~\xf7"
|
||||
|
||||
if (cert.fingerprint(hashes.SHA256()) == solo_fingerprint):
|
||||
print('Valid firmware from SoloKeys')
|
||||
print('Valid SOLO firmware from SoloKeys')
|
||||
elif (cert.fingerprint(hashes.SHA256()) == hacker_fingerprint):
|
||||
print('Valid HACKER firmware')
|
||||
else:
|
||||
print('This is either a Solo Hacker or a invalid Solo.')
|
||||
print('Unknown fingerprint! ', cert.fingerprint(hashes.SHA256()))
|
||||
|
||||
try:
|
||||
v = p.solo_version()
|
||||
print('Version: ', v)
|
||||
except ApduError:
|
||||
print('Firmware is out of date.')
|
||||
|
||||
|
||||
def asked_for_help():
|
||||
@ -909,7 +928,7 @@ def programmer_main():
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
p.version()
|
||||
p.bootloader_version()
|
||||
except CtapError as e:
|
||||
if e.code == CtapError.ERR.INVALID_COMMAND:
|
||||
print('Bootloader not active. Attempting to boot into bootloader mode...')
|
||||
|
Loading…
x
Reference in New Issue
Block a user