Adding Extended Cipher feature.

With this new subcommand, Pico HSM will support newer cipher algorithms.
ChaCha20-Poly1305 is the first. It will be based on a custom P2 subcommand to support an arbitrary structure with multiple parameters (AAD, IV, etc.)

pico-hsm-tool.py shall be used.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2022-11-07 21:37:11 +01:00
parent 30301c68f1
commit a7682d2639
4 changed files with 120 additions and 17 deletions

View File

@@ -19,9 +19,12 @@
#include "mbedtls/aes.h" #include "mbedtls/aes.h"
#include "mbedtls/cmac.h" #include "mbedtls/cmac.h"
#include "mbedtls/hkdf.h" #include "mbedtls/hkdf.h"
#include "mbedtls/chachapoly.h"
#include "crypto_utils.h" #include "crypto_utils.h"
#include "sc_hsm.h" #include "sc_hsm.h"
#include "kek.h" #include "kek.h"
#include "asn1.h"
#include "oid.h"
int cmd_cipher_sym() { int cmd_cipher_sym() {
int key_id = P1(apdu); int key_id = P1(apdu);
@@ -33,9 +36,6 @@ int cmd_cipher_sym() {
return SW_FILE_NOT_FOUND(); return SW_FILE_NOT_FOUND();
if (key_has_purpose(ef, algo) == false) if (key_has_purpose(ef, algo) == false)
return SW_CONDITIONS_NOT_SATISFIED(); return SW_CONDITIONS_NOT_SATISFIED();
if ((apdu.nc % 16) != 0) {
return SW_WRONG_LENGTH();
}
if (wait_button_pressed() == true) // timeout if (wait_button_pressed() == true) // timeout
return SW_SECURE_MESSAGE_EXEC_ERROR(); return SW_SECURE_MESSAGE_EXEC_ERROR();
int key_size = file_get_size(ef); int key_size = file_get_size(ef);
@@ -45,6 +45,9 @@ int cmd_cipher_sym() {
return SW_EXEC_ERROR(); return SW_EXEC_ERROR();
} }
if (algo == ALGO_AES_CBC_ENCRYPT || algo == ALGO_AES_CBC_DECRYPT) { if (algo == ALGO_AES_CBC_ENCRYPT || algo == ALGO_AES_CBC_DECRYPT) {
if ((apdu.nc % 16) != 0) {
return SW_WRONG_LENGTH();
}
mbedtls_aes_context aes; mbedtls_aes_context aes;
mbedtls_aes_init(&aes); mbedtls_aes_init(&aes);
uint8_t tmp_iv[IV_SIZE]; uint8_t tmp_iv[IV_SIZE];
@@ -105,6 +108,43 @@ int cmd_cipher_sym() {
return SW_EXEC_ERROR(); return SW_EXEC_ERROR();
res_APDU_size = apdu.nc; res_APDU_size = apdu.nc;
} }
else if (algo == ALGO_EXT_CIPHER_ENCRYPT || algo == ALGO_EXT_CIPHER_DECRYPT) {
size_t oid_len = 0, aad_len = 0, iv_len = 0, enc_len = 0;
uint8_t *oid = NULL, *aad = NULL, *iv = NULL, *enc = NULL;
if (!asn1_find_tag(apdu.data, apdu.nc, 0x6, &oid_len, &oid) || oid_len == 0 || oid == NULL) {
mbedtls_platform_zeroize(kdata, sizeof(kdata));
return SW_WRONG_DATA();
}
asn1_find_tag(apdu.data, apdu.nc, 0x81, &enc_len, &enc);
asn1_find_tag(apdu.data, apdu.nc, 0x82, &iv_len, &iv);
asn1_find_tag(apdu.data, apdu.nc, 0x83, &aad_len, &aad);
uint8_t tmp_iv[16];
memset(tmp_iv, 0, sizeof(tmp_iv));
if (memcmp(oid, OID_CHACHA20_POLY1305, oid_len) == 0)
{
if (algo == ALGO_EXT_CIPHER_DECRYPT && enc_len < 16) {
mbedtls_platform_zeroize(kdata, sizeof(kdata));
return SW_WRONG_DATA();
}
int r = 0;
mbedtls_chachapoly_context ctx;
mbedtls_chachapoly_init(&ctx);
if (algo == ALGO_EXT_CIPHER_ENCRYPT) {
r = mbedtls_chachapoly_encrypt_and_tag(&ctx, enc_len, iv ? iv : tmp_iv, aad, aad_len, enc, res_APDU, res_APDU + enc_len);
}
else if (algo == ALGO_EXT_CIPHER_DECRYPT) {
r = mbedtls_chachapoly_auth_decrypt(&ctx, enc_len - 16, iv ? iv : tmp_iv, aad, aad_len, enc + enc_len - 16, enc, res_APDU);
}
mbedtls_platform_zeroize(kdata, sizeof(kdata));
mbedtls_chachapoly_free(&ctx);
if (r != 0)
return SW_EXEC_ERROR();
if (algo == ALGO_EXT_CIPHER_ENCRYPT)
res_APDU_size = enc_len + 16;
else if (algo == ALGO_EXT_CIPHER_DECRYPT)
res_APDU_size = enc_len - 16;
}
}
else { else {
mbedtls_platform_zeroize(kdata, sizeof(kdata)); mbedtls_platform_zeroize(kdata, sizeof(kdata));
return SW_WRONG_P1P2(); return SW_WRONG_P1P2();

View File

@@ -103,4 +103,7 @@
#define OID_CC_FF_PKA OID_CC_FORMAT "\x03" #define OID_CC_FF_PKA OID_CC_FORMAT "\x03"
#define OID_CC_FF_KDA OID_CC_FORMAT "\x04" #define OID_CC_FF_KDA OID_CC_FORMAT "\x04"
#define OID_CHACHA20_POLY1305 "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x10\x03\x12"
#endif #endif

View File

@@ -66,6 +66,8 @@ extern const uint8_t sc_hsm_aid[];
#define ALGO_AES_CBC_ENCRYPT 0x10 #define ALGO_AES_CBC_ENCRYPT 0x10
#define ALGO_AES_CBC_DECRYPT 0x11 #define ALGO_AES_CBC_DECRYPT 0x11
#define ALGO_AES_CMAC 0x18 #define ALGO_AES_CMAC 0x18
#define ALGO_EXT_CIPHER_ENCRYPT 0x51 /* Extended ciphering Encrypt */
#define ALGO_EXT_CIPHER_DECRYPT 0x52 /* Extended ciphering Decrypt */
#define ALGO_AES_DERIVE 0x99 #define ALGO_AES_DERIVE 0x99
#define HSM_OPT_RRC 0x0001 #define HSM_OPT_RRC 0x0001

View File

@@ -51,7 +51,7 @@ except ModuleNotFoundError:
import json import json
import urllib.request import urllib.request
import base64 import base64
from binascii import hexlify from binascii import hexlify, unhexlify
import sys import sys
import argparse import argparse
import os import os
@@ -66,6 +66,8 @@ class APDUResponse(Exception):
self.sw2 = sw2 self.sw2 = sw2
super().__init__(f'SW:{sw1:02X}{sw2:02X}') super().__init__(f'SW:{sw1:02X}{sw2:02X}')
def hexy(a):
return [hex(i) for i in a]
def send_apdu(card, command, p1, p2, data=None): def send_apdu(card, command, p1, p2, data=None):
lc = [] lc = []
@@ -89,7 +91,7 @@ def parse_args():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command") subparser = parser.add_subparsers(title="commands", dest="command")
parser_init = subparser.add_parser('initialize', help='Performs the first initialization of the Pico HSM.') parser_init = subparser.add_parser('initialize', help='Performs the first initialization of the Pico HSM.')
parser_init.add_argument('--pin', help='PIN number') parser.add_argument('--pin', help='PIN number')
parser_init.add_argument('--so-pin', help='SO-PIN number') parser_init.add_argument('--so-pin', help='SO-PIN number')
parser_attestate = subparser.add_parser('attestate', help='Generates an attestation report for a private key and verifies the private key was generated in the devices or outside.') parser_attestate = subparser.add_parser('attestate', help='Generates an attestation report for a private key and verifies the private key was generated in the devices or outside.')
@@ -111,9 +113,20 @@ 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('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_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 = subparser.add_parser('secure', help='Manages security of Pico HSM.')
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.') parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
parser_cipher = subparser.add_parser('cipher', help='Implements extended symmetric ciphering with new algorithms and options.\n\tIf no file input/output is specified, stdin/stoud will be used.')
parser_cipher.add_argument('subcommand', choices=['encrypt','decrypt','e','d','keygen'], help='Encrypts, decrypts or generates a new key.')
parser_cipher.add_argument('--alg', choices=['CHACHAPOLY'], help='Selects the algorithm.', required='keygen' not in sys.argv)
parser_cipher.add_argument('--iv', help='Sets the IV/nonce (hex string).')
parser_cipher.add_argument('--file-in', help='File to encrypt or decrypt.')
parser_cipher.add_argument('--file-out', help='File to write the result.')
parser_cipher.add_argument('--aad', help='Specifies the authentication data (it can be a string or hex string. Combine with --hex if necesary).')
parser_cipher.add_argument('--hex', help='Parses the AAD parameter as a hex string (for binary data).', action='store_true')
parser_cipher.add_argument('-k', '--key', help='The private key index', metavar='KEY_ID', required=True)
parser_cipher.add_argument('-s', '--key-size', default=32, help='Size of the key in bytes.')
args = parser.parse_args() args = parser.parse_args()
return args return args
@@ -153,6 +166,12 @@ def pki(card, args):
else: else:
print('Error: no PKI is passed. Use --default to retrieve default PKI.') print('Error: no PKI is passed. Use --default to retrieve default PKI.')
def login(card, args):
try:
response = send_apdu(card, 0x20, 0x00, 0x81, list(args.pin.encode()))
except APDUResponse:
pass
def initialize(card, args): def initialize(card, args):
print('********************************') print('********************************')
print('* PLEASE READ IT CAREFULLY *') print('* PLEASE READ IT CAREFULLY *')
@@ -164,14 +183,9 @@ def initialize(card, args):
_ = input('[Press enter to confirm]') _ = input('[Press enter to confirm]')
send_apdu(card, 0xA4, 0x04, 0x00, [0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xC3, 0x1F, 0x02, 0x01]) send_apdu(card, 0xA4, 0x04, 0x00, [0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xC3, 0x1F, 0x02, 0x01])
if (args.pin): if (not args.pin):
pin = args.pin.encode() pin = b'648219'
try:
response = send_apdu(card, 0x20, 0x00, 0x81, list(pin))
except APDUResponse:
pass
else:
pin = b'648219'
if (args.so_pin): if (args.so_pin):
so_pin = args.so_pin.encode() so_pin = args.so_pin.encode()
try: try:
@@ -179,7 +193,7 @@ def initialize(card, args):
except APDUResponse: except APDUResponse:
pass pass
else: else:
so_pin = b'57621880' so_pin = b'57621880'
pin_data = [0x81, len(pin)] + list(pin) pin_data = [0x81, len(pin)] + list(pin)
so_pin_data = [0x82, len(so_pin)] + list(so_pin) so_pin_data = [0x82, len(so_pin)] + list(so_pin)
@@ -229,7 +243,7 @@ def attestate(card, args):
if (a.sw1 == 0x6a and a.sw2 == 0x82): if (a.sw1 == 0x6a and a.sw2 == 0x82):
print('ERROR: Key not found') print('ERROR: Key not found')
sys.exit(1) sys.exit(1)
from binascii import hexlify
print(hexlify(bytearray(cert))) print(hexlify(bytearray(cert)))
print(f'Details of key {kid}:\n') print(f'Details of key {kid}:\n')
print(f' CAR: {(CVC().decode(cert).car()).decode()}') print(f' CAR: {(CVC().decode(cert).car()).decode()}')
@@ -359,8 +373,46 @@ def secure(card, args):
elif (args.subcommand == 'disable'): elif (args.subcommand == 'disable'):
slck.disable_device_aut() slck.disable_device_aut()
def cipher(card, args):
if (args.subcommand == 'keygen'):
ksize = 0xB2
if (args.key_size == 24):
ksize = 0xB1
elif (args.key_size == 16):
ksize = 0xB0
ret = send_apdu(card, 0x48, int(args.key), ksize)
else:
if (args.alg == 'CHACHAPOLY'):
oid = b'\x2A\x86\x48\x86\xF7\x0D\x01\x09\x10\x03\x12'
if (args.subcommand[0] == 'e'):
alg = 0x51
elif (args.subcommand[0] == 'd'):
alg = 0x52
if (args.file_in):
fin = open(args.file_in, 'rb')
else:
fin = sys.stdin.buffer
enc = fin.read()
fin.close()
data = [0x06, len(oid)] + list(oid) + [0x81, len(enc)] + list(enc)
if (args.iv):
data += [0x82, len(args.iv)/2] + list(unhexlify(args.iv))
if (args.aad):
if (args.hex):
data += [0x83, len(args.aad)/2] + list(unhexlify(args.aad))
else:
data += [0x83, len(args.aad)] + list(args.aad)
ret = send_apdu(card, [0x80, 0x78], int(args.key), alg, data)
sys.stdout.buffer.write(bytes(ret))
def main(args): def main(args):
print('Pico HSM Tool v1.6') print('Pico HSM Tool v1.8')
print('Author: Pol Henarejos') print('Author: Pol Henarejos')
print('Report bugs to https://github.com/polhenarejos/pico-hsm/issues') print('Report bugs to https://github.com/polhenarejos/pico-hsm/issues')
print('') print('')
@@ -377,6 +429,9 @@ def main(args):
except CardRequestTimeoutException: except CardRequestTimeoutException:
print('time-out: no card inserted during last 10s') print('time-out: no card inserted during last 10s')
if (args.pin):
login(card, args)
# Following commands may raise APDU exception on error # Following commands may raise APDU exception on error
if (args.command == 'initialize'): if (args.command == 'initialize'):
initialize(card, args) initialize(card, args)
@@ -390,6 +445,9 @@ def main(args):
opts(card, args) opts(card, args)
elif (args.command == 'secure'): elif (args.command == 'secure'):
secure(card, args) secure(card, args)
elif (args.command == 'cipher'):
cipher(card, args)
def run(): def run():
args = parse_args() args = parse_args()