mirror of
https://github.com/polhenarejos/pico-hsm.git
synced 2026-01-17 09:28:05 +00:00
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 <pol.henarejos@cttc.es>
This commit is contained in:
parent
eda8b53949
commit
00279da8d5
5 changed files with 215 additions and 4 deletions
|
|
@ -15,14 +15,20 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue