From 00279da8d5d88591713fa4f1043f70847d3ee20f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 31 Oct 2022 15:09:54 +0100 Subject: [PATCH] Adding Secure Lock to lock the device with a random 256 bit key. This is an extra layer of security to avoid brute force attacks if PIN is too weak. At every hard reset (on device plug), the device must be unlocked prior any other command. Once unlocked, the device can be used as usual. Signed-off-by: Pol Henarejos --- src/hsm/cmd_extras.c | 92 +++++++++++++++++++++++++++++++++++++++++- src/hsm/kek.c | 20 +++++++++ src/hsm/kek.h | 14 +++++++ src/hsm/sc_hsm.h | 1 + tools/pico-hsm-tool.py | 92 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 215 insertions(+), 4 deletions(-) diff --git a/src/hsm/cmd_extras.c b/src/hsm/cmd_extras.c index 440ed9b..c215274 100644 --- a/src/hsm/cmd_extras.c +++ b/src/hsm/cmd_extras.c @@ -15,14 +15,20 @@ * along with this program. If not, see . */ +#include "common.h" +#include "mbedtls/ecdh.h" #include "sc_hsm.h" #include "hardware/rtc.h" #include "files.h" +#include "random.h" +#include "kek.h" +#include "mbedtls/hkdf.h" +#include "mbedtls/chachapoly.h" int cmd_extras() { - if (P2(apdu) != 0x0) - return SW_INCORRECT_P1P2(); if (P1(apdu) == 0xA) { //datetime operations + if (P2(apdu) != 0x0) + return SW_INCORRECT_P1P2(); if (apdu.nc == 0) { datetime_t dt; if (!rtc_get_datetime(&dt)) @@ -52,6 +58,8 @@ int cmd_extras() { } } else if (P1(apdu) == 0x6) { //dynamic options + if (P2(apdu) != 0x0) + return SW_INCORRECT_P1P2(); if (apdu.nc > sizeof(uint8_t)) return SW_WRONG_LENGTH(); uint16_t opts = get_device_options(); @@ -66,6 +74,86 @@ int cmd_extras() { low_flash_available(); } } + else if (P1(apdu) == 0x3A) { // secure lock + if (apdu.nc == 0) { + return SW_WRONG_LENGTH(); + } + if (P2(apdu) == 0x01) { // Key Agreement + mbedtls_ecdh_context hkey; + mbedtls_ecdh_init(&hkey); + mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1); + int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL); + mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1); + ret = mbedtls_ecp_point_read_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Qp, apdu.data, apdu.nc); + if (ret != 0) { + mbedtls_ecdh_free(&hkey); + return SW_WRONG_DATA(); + } + memcpy(mse.Qpt, apdu.data, sizeof(mse.Qpt)); + + uint8_t buf[MBEDTLS_ECP_MAX_BYTES]; + size_t olen = 0; + ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL); + if (ret != 0) { + mbedtls_ecdh_free(&hkey); + mbedtls_platform_zeroize(buf, sizeof(buf)); + return SW_WRONG_DATA(); + } + ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, olen, mse.Qpt, sizeof(mse.Qpt), mse.key_enc, sizeof(mse.key_enc)); + mbedtls_platform_zeroize(buf, sizeof(buf)); + if (ret != 0) { + mbedtls_ecdh_free(&hkey); + return SW_EXEC_ERROR(); + } + + ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, res_APDU, 4096); + mbedtls_ecdh_free(&hkey); + if (ret != 0) { + return SW_EXEC_ERROR(); + } + mse.init = true; + res_APDU_size = olen; + } + else if (P2(apdu) == 0x02 || P2(apdu) == 0x03 || P2(apdu) == 0x04) { + if (mse.init == false) + return SW_COMMAND_NOT_ALLOWED(); + + int ret = mse_decrypt_ct(apdu.data, apdu.nc); + if (ret != 0) { + return SW_WRONG_DATA(); + } + if (P2(apdu) == 0x02 || P2(apdu) == 0x04) { // Enable + uint16_t opts = get_device_options(); + uint8_t newopts[] = { opts >> 8, (opts & 0xff) }; + if ((P2(apdu) == 0x02 && !(opts & HSM_OPT_SECURE_LOCK)) || (P2(apdu) == 0x04 && (opts & HSM_OPT_SECURE_LOCK))) { + uint16_t tfids[] = { EF_MKEK, EF_MKEK_SO }; + for (int t = 0; t < sizeof(tfids)/sizeof(uint16_t); t++) { + file_t *tf = search_by_fid(tfids[t], NULL, SPECIFY_EF); + if (tf) { + uint8_t *tmp = (uint8_t *)calloc(1, file_get_size(tf)); + memcpy(tmp, file_get_data(tf), file_get_size(tf)); + for (int i = 0; i < MKEK_KEY_SIZE; i++) { + MKEK_KEY(tmp)[i] ^= apdu.data[i]; + } + flash_write_data_to_file(tf, tmp, file_get_size(tf)); + free(tmp); + } + } + } + if (P2(apdu) == 0x02) + newopts[0] |= HSM_OPT_SECURE_LOCK >> 8; + else if (P2(apdu) == 0x04) + newopts[0] &= ~HSM_OPT_SECURE_LOCK >> 8; + file_t *tf = search_by_fid(EF_DEVOPS, NULL, SPECIFY_EF); + flash_write_data_to_file(tf, newopts, sizeof(newopts)); + low_flash_available(); + } + else if (P2(apdu) == 0x03) { + memcpy(mkek_mask, apdu.data, apdu.nc); + has_mkek_mask = true; + } + } + } else return SW_INCORRECT_P1P2(); return SW_OK(); diff --git a/src/hsm/kek.c b/src/hsm/kek.c index 93b53a2..f1f1317 100644 --- a/src/hsm/kek.c +++ b/src/hsm/kek.c @@ -27,10 +27,13 @@ #include "mbedtls/cmac.h" #include "mbedtls/rsa.h" #include "mbedtls/ecdsa.h" +#include "mbedtls/chachapoly.h" #include "files.h" extern bool has_session_pin, has_session_sopin; extern uint8_t session_pin[32], session_sopin[32]; +uint8_t mkek_mask[MKEK_KEY_SIZE]; +bool has_mkek_mask = false; #define POLY 0xedb88320 @@ -65,6 +68,12 @@ int load_mkek(uint8_t *mkek) { } if (pin == NULL) //Should never happen return CCID_EXEC_ERROR; + + if (has_mkek_mask) { + for (int i = 0; i < MKEK_KEY_SIZE; i++) { + MKEK_KEY(mkek)[i] ^= mkek_mask[i]; + } + } int ret = aes_decrypt_cfb_256(pin, MKEK_IV(mkek), MKEK_KEY(mkek), MKEK_KEY_SIZE+MKEK_KEY_CS_SIZE); if (ret != 0) return CCID_EXEC_ERROR; @@ -73,6 +82,17 @@ int load_mkek(uint8_t *mkek) { return CCID_OK; } +mse_t mse = {.init = false}; + +int mse_decrypt_ct(uint8_t *data, size_t len) { + mbedtls_chachapoly_context chatx; + mbedtls_chachapoly_init(&chatx); + mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12); + int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data); + mbedtls_chachapoly_free(&chatx); + return ret; +} + int load_dkek(uint8_t id, uint8_t *dkek) { file_t *tf = search_dynamic_file(EF_DKEK+id); if (!tf) diff --git a/src/hsm/kek.h b/src/hsm/kek.h index 8a0e5d1..479aeb4 100644 --- a/src/hsm/kek.h +++ b/src/hsm/kek.h @@ -18,6 +18,8 @@ #ifndef _DKEK_H_ #define _DKEK_H_ +#include "crypto_utils.h" + extern int load_mkek(uint8_t *); extern int store_mkek(const uint8_t *); extern int save_dkek_key(uint8_t, const uint8_t *key); @@ -45,4 +47,16 @@ extern int dkek_decode_key(uint8_t, void *key_ctx, const uint8_t *in, size_t in_ #define MKEK_CHECKSUM(p) (MKEK_KEY(p)+MKEK_KEY_SIZE) #define DKEK_KEY_SIZE (32) +extern uint8_t mkek_mask[MKEK_KEY_SIZE]; +extern bool has_mkek_mask; + +typedef struct mse { + uint8_t Qpt[65]; + uint8_t key_enc[12 + 32]; + bool init; +} mse_t; +extern mse_t mse; + +extern int mse_decrypt_ct(uint8_t *, size_t); + #endif diff --git a/src/hsm/sc_hsm.h b/src/hsm/sc_hsm.h index 45d590a..984473e 100644 --- a/src/hsm/sc_hsm.h +++ b/src/hsm/sc_hsm.h @@ -77,6 +77,7 @@ extern const uint8_t sc_hsm_aid[]; #define HSM_OPT_RRC_RESET_ONLY 0x0020 #define HSM_OPT_BOOTSEL_BUTTON 0x0100 #define HSM_OPT_KEY_COUNTER_ALL 0x0200 +#define HSM_OPT_SECURE_LOCK 0x0400 #define PRKD_PREFIX 0xC4 /* Hi byte in file identifier for PKCS#15 PRKD objects */ #define CD_PREFIX 0xC8 /* Hi byte in file identifier for PKCS#15 CD objects */ diff --git a/tools/pico-hsm-tool.py b/tools/pico-hsm-tool.py index b811190..41df2b7 100755 --- a/tools/pico-hsm-tool.py +++ b/tools/pico-hsm-tool.py @@ -33,10 +33,22 @@ try: from cvc.oid import oid2scheme from cvc.utils import scheme_rsa from cvc.certificates import CVC - from cryptography.hazmat.primitives.asymmetric import ec + except: print('ERROR: cvc module not found! Install pycvc package.\nTry with `pip install pycvc`') sys.exit(-1) + +try: + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.kdf.hkdf import HKDF + from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + from cryptography.hazmat.primitives import hashes +except: + print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`') + sys.exit(-1) + + import json import urllib.request import base64 @@ -44,9 +56,20 @@ from binascii import hexlify import sys import argparse import os +import platform from datetime import datetime from argparse import RawTextHelpFormatter +if (platform.system() == 'Windows'): + from secure_key import windows as skey +elif (platform.system() == 'Linux'): + from secure_key import linux as skey +elif (platform.system() == 'Darwin'): + from secure_key import macos as skey +else: + print('ERROR: platform not supported') + sys.exit(-1) + class APDUResponse(Exception): def __init__(self, sw1, sw2): self.sw1 = sw1 @@ -98,6 +121,9 @@ def parse_args(): parser_opts.add_argument('opt', choices=['button', 'counter'], help='Button: press-to-confirm button.\nCounter: every generated key has an internal counter.') parser_opts.add_argument('onoff', choices=['on', 'off'], help='Toggles state ON or OFF', metavar='ON/OFF', nargs='?') + parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.') + parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.') + args = parser.parse_args() return args @@ -276,8 +302,68 @@ def opts(card, args): elif (args.subcommand == 'get'): print(f'Option {args.opt.upper()} is {"ON" if current & opt else "OFF"}') +class SecureLock: + def __init__(self, card): + self.card = card + + def mse(self): + sk = ec.generate_private_key(ec.SECP256R1()) + pn = sk.public_key().public_numbers() + self.__pb = sk.public_key().public_bytes(Encoding.X962, PublicFormat.UncompressedPoint) + + + ret = send_apdu(self.card, [0x80, 0x64], 0x3A, 0x01, list(self.__pb)) + + pk = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), bytes(ret)) + shared_key = sk.exchange(ec.ECDH(), pk) + + xkdf = HKDF( + algorithm=hashes.SHA256(), + length=12+32, + salt=None, + info=self.__pb + ) + kdf_out = xkdf.derive(shared_key) + self.__key_enc = kdf_out[12:] + self.__iv = kdf_out[:12] + + def encrypt_chacha(self, data): + chacha = ChaCha20Poly1305(self.__key_enc) + ct = chacha.encrypt(self.__iv, data, self.__pb) + return ct + + def unlock_device(self): + ct = self.get_skey() + send_apdu(self.card, [0x80, 0x64], 0x3A, 0x03, list(ct)) + + def _get_key_device(self): + return skey.get_secure_key() + + def get_skey(self): + self.mse() + ct = self.encrypt_chacha(self._get_key_device()) + return ct + + def enable_device_aut(self): + ct = self.get_skey() + send_apdu(self.card, [0x80, 0x64], 0x3A, 0x02, list(ct)) + + def disable_device_aut(self): + ct = self.get_skey() + send_apdu(self.card, [0x80, 0x64], 0x3A, 0x04, list(ct)) + + +def secure(card, args): + slck = SecureLock(card) + if (args.subcommand == 'enable'): + slck.enable_device_aut() + elif (args.subcommand == 'unlock'): + slck.unlock_device() + elif (args.subcommand == 'disable'): + slck.disable_device_aut() + def main(args): - print('Pico HSM Tool v1.4') + print('Pico HSM Tool v1.6') print('Author: Pol Henarejos') print('Report bugs to https://github.com/polhenarejos/pico-hsm/issues') print('') @@ -305,6 +391,8 @@ def main(args): rtc(card, args) elif (args.command == 'options'): opts(card, args) + elif (args.command == 'secure'): + secure(card, args) def run(): args = parse_args()