From a7682d2639e04d7b8ce9cf6f71fd63bf0ae96b57 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 7 Nov 2022 21:37:11 +0100 Subject: [PATCH] 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 --- src/hsm/cmd_cipher_sym.c | 46 +++++++++++++++++++-- src/hsm/oid.h | 3 ++ src/hsm/sc_hsm.h | 2 + tools/pico-hsm-tool.py | 86 +++++++++++++++++++++++++++++++++------- 4 files changed, 120 insertions(+), 17 deletions(-) diff --git a/src/hsm/cmd_cipher_sym.c b/src/hsm/cmd_cipher_sym.c index 42d7c3c..e65553f 100644 --- a/src/hsm/cmd_cipher_sym.c +++ b/src/hsm/cmd_cipher_sym.c @@ -19,9 +19,12 @@ #include "mbedtls/aes.h" #include "mbedtls/cmac.h" #include "mbedtls/hkdf.h" +#include "mbedtls/chachapoly.h" #include "crypto_utils.h" #include "sc_hsm.h" #include "kek.h" +#include "asn1.h" +#include "oid.h" int cmd_cipher_sym() { int key_id = P1(apdu); @@ -33,9 +36,6 @@ int cmd_cipher_sym() { return SW_FILE_NOT_FOUND(); if (key_has_purpose(ef, algo) == false) return SW_CONDITIONS_NOT_SATISFIED(); - if ((apdu.nc % 16) != 0) { - return SW_WRONG_LENGTH(); - } if (wait_button_pressed() == true) // timeout return SW_SECURE_MESSAGE_EXEC_ERROR(); int key_size = file_get_size(ef); @@ -45,6 +45,9 @@ int cmd_cipher_sym() { return SW_EXEC_ERROR(); } 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_init(&aes); uint8_t tmp_iv[IV_SIZE]; @@ -105,6 +108,43 @@ int cmd_cipher_sym() { return SW_EXEC_ERROR(); 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 { mbedtls_platform_zeroize(kdata, sizeof(kdata)); return SW_WRONG_P1P2(); diff --git a/src/hsm/oid.h b/src/hsm/oid.h index 880b0de..fd97f57 100644 --- a/src/hsm/oid.h +++ b/src/hsm/oid.h @@ -103,4 +103,7 @@ #define OID_CC_FF_PKA OID_CC_FORMAT "\x03" #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 diff --git a/src/hsm/sc_hsm.h b/src/hsm/sc_hsm.h index 984473e..129ad02 100644 --- a/src/hsm/sc_hsm.h +++ b/src/hsm/sc_hsm.h @@ -66,6 +66,8 @@ extern const uint8_t sc_hsm_aid[]; #define ALGO_AES_CBC_ENCRYPT 0x10 #define ALGO_AES_CBC_DECRYPT 0x11 #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 HSM_OPT_RRC 0x0001 diff --git a/tools/pico-hsm-tool.py b/tools/pico-hsm-tool.py index 6f6a2c9..c577a9c 100644 --- a/tools/pico-hsm-tool.py +++ b/tools/pico-hsm-tool.py @@ -51,7 +51,7 @@ except ModuleNotFoundError: import json import urllib.request import base64 -from binascii import hexlify +from binascii import hexlify, unhexlify import sys import argparse import os @@ -66,6 +66,8 @@ class APDUResponse(Exception): self.sw2 = sw2 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): lc = [] @@ -89,7 +91,7 @@ def parse_args(): parser = argparse.ArgumentParser() 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.add_argument('--pin', help='PIN number') + parser.add_argument('--pin', help='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.') @@ -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('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_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() return args @@ -153,6 +166,12 @@ def pki(card, args): else: 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): print('********************************') print('* PLEASE READ IT CAREFULLY *') @@ -164,14 +183,9 @@ def initialize(card, args): _ = input('[Press enter to confirm]') send_apdu(card, 0xA4, 0x04, 0x00, [0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xC3, 0x1F, 0x02, 0x01]) - if (args.pin): - pin = args.pin.encode() - try: - response = send_apdu(card, 0x20, 0x00, 0x81, list(pin)) - except APDUResponse: - pass - else: - pin = b'648219' + if (not args.pin): + pin = b'648219' + if (args.so_pin): so_pin = args.so_pin.encode() try: @@ -179,7 +193,7 @@ def initialize(card, args): except APDUResponse: pass else: - so_pin = b'57621880' + so_pin = b'57621880' pin_data = [0x81, len(pin)] + list(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): print('ERROR: Key not found') sys.exit(1) - from binascii import hexlify + print(hexlify(bytearray(cert))) print(f'Details of key {kid}:\n') print(f' CAR: {(CVC().decode(cert).car()).decode()}') @@ -359,8 +373,46 @@ def secure(card, args): elif (args.subcommand == 'disable'): 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): - print('Pico HSM Tool v1.6') + print('Pico HSM Tool v1.8') print('Author: Pol Henarejos') print('Report bugs to https://github.com/polhenarejos/pico-hsm/issues') print('') @@ -377,6 +429,9 @@ def main(args): except CardRequestTimeoutException: print('time-out: no card inserted during last 10s') + if (args.pin): + login(card, args) + # Following commands may raise APDU exception on error if (args.command == 'initialize'): initialize(card, args) @@ -390,6 +445,9 @@ def main(args): opts(card, args) elif (args.command == 'secure'): secure(card, args) + elif (args.command == 'cipher'): + cipher(card, args) + def run(): args = parse_args()