11 Commits
v1.2 ... v1.4

Author SHA1 Message Date
Pol Henarejos
78d71a6d9c Upgrading to version 1.4.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-21 00:55:50 +01:00
Pol Henarejos
0a2740fbab Added AES derive support based on HKDF.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-21 00:54:59 +01:00
Pol Henarejos
3192e928ff Fixed a bug with deleting intermediate EF on flash. A new field on EF flash structure is added. Thus, the old structure must be erased.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-21 00:16:00 +01:00
Pol Henarejos
ae1e2ac111 Fix storing public key description when generating a new keypair.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-19 19:11:09 +01:00
Pol Henarejos
d87073f4cc Auth status should not be removed when apple is reselected. Auth status is removed when the reader disconnects the card (unloads it).
With this fix, it is possible to login first and send immediate low level APDU command that requires authentification (such as login+CMAC).

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-17 23:37:02 +01:00
Pol Henarejos
36a8f78313 Added support for AES-CMAC.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-17 23:35:07 +01:00
Pol Henarejos
0628d5015c Update asymmetric-ciphering.md 2022-03-17 00:44:38 +01:00
Pol Henarejos
daf0f98660 Update asymmetric-ciphering.md
Adding examples for ECDH key derivation.
2022-03-17 00:43:44 +01:00
Pol Henarejos
1f06c44a89 Adding ecdh support with MBEDTLS.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-17 00:28:40 +01:00
Pol Henarejos
ab1490a50b Added ECDH key derivation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-17 00:28:16 +01:00
Pol Henarejos
23f53a6095 Added some free on bad return.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-03-16 23:40:09 +01:00
6 changed files with 179 additions and 65 deletions

View File

@@ -53,6 +53,20 @@ target_sources(pico_hsm PUBLIC
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/md5.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/ripemd160.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/sha1.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/ecdh.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/cmac.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/cipher.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/cipher_wrap.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/chachapoly.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/camellia.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/chacha20.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/aria.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/poly1305.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/gcm.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/ccm.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/des.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/nist_kw.c
${CMAKE_CURRENT_LIST_DIR}/mbedtls/library/hkdf.c
${CMAKE_CURRENT_LIST_DIR}/OpenSC/src/libopensc/pkcs15.c
${CMAKE_CURRENT_LIST_DIR}/OpenSC/src/libopensc/pkcs15-prkey.c

View File

@@ -4,6 +4,7 @@ Pico HSM supports in place decryption with the following algorithms:
* RSA-PKCS
* RSA-X-509
* RSA-PKCS-OAEP
* ECDH-DERIVE
First, we generate the data:
```
@@ -76,3 +77,35 @@ OAEP parameters: hashAlg=SHA256, mgf=MGF1-SHA256, source_type=0, source_ptr=0x0,
This is a test string. Be safe, be secure.
```
## ECDH-DERIVE
ECC keys do not allow ciphering operations. Instead, the ECDH scheme provides a mechanism to exchange a shared symmetric key without transmitting it to the remote part. The shared key is composed by multiplying the local private key and the remote public key.
First, we create the remote part, Bob, by generating an ECC keypair and getting the public key:
```
$ openssl ecparam -genkey -name prime192v1 > bob.pem
$ openssl ec -in bob.pem -pubout -outform DER > bob.der
```
We derive the shared key by giving the Bob's public key to the Pico HSM:
```
$ pkcs11-tool --pin 648219 --id 11 --derive -i bob.der -o mine-bob.der
```
We compute the other shared key, with Bob's private key and our public key:
```
$ openssl pkeyutl -derive -out bob-mine.der -inkey bob.pem -peerkey 11.pub
```
Finally, we compare both shared keys:
```
$ cmp bob-mine.der mine-bob.der
```
No output is displayed if both are equal.
You can also view the contents of both keys:
```
$ xxd -p bob-mine.der
9874558aefa9d92cc051e5da6d1753987e5314925d6d78bf
$ xxd -p mine-bob.der
9874558aefa9d92cc051e5da6d1753987e5314925d6d78bf
```

View File

@@ -279,8 +279,8 @@ void scan_flash() {
if (base == 0x0) //all is empty
break;
uint16_t fid = flash_read_uint16(base+sizeof(uintptr_t));
printf("scan fid %x\r\n",fid);
uint16_t fid = flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t));
printf("[%x] scan fid %x, len %d\r\n",base,fid,flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t)));
file_t *file = (file_t *)search_by_fid(fid, NULL, SPECIFY_EF);
if (!file) {
file = file_new(fid);
@@ -301,7 +301,7 @@ void scan_flash() {
continue;
}
}
file->data = (uint8_t *)(base+sizeof(uintptr_t)+sizeof(uint16_t));
file->data = (uint8_t *)(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t));
if (flash_read_uintptr(base) == 0x0) {
break;
}

View File

@@ -8,10 +8,17 @@
#include "file.h"
#include "sc_hsm.h"
/*
* ------------------------------------------------------
* | |
* | next_addr | prev_addr | fid | data (len + payload) |
* | |
* ------------------------------------------------------
*/
#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES >> 1) // DATA starts at the mid of flash
#define FLASH_DATA_HEADER_SIZE (sizeof(uintptr_t)+sizeof(uint32_t))
//To avoid possible future allocations, data region starts at the begining of flash and goes upwards to the center region
//To avoid possible future allocations, data region starts at the end of flash and goes upwards to the center region
const uintptr_t start_data_pool = (XIP_BASE + FLASH_TARGET_OFFSET);
const uintptr_t end_data_pool = (XIP_BASE + PICO_FLASH_SIZE_BYTES)-FLASH_DATA_HEADER_SIZE; //This is a fixed value. DO NOT CHANGE
@@ -25,33 +32,10 @@ extern uint16_t flash_read_uint16(uintptr_t addr);
extern void low_flash_available();
/*
* Flash data pool managenent
*
* Flash data pool consists of two parts:
* 2-byte header
* contents
*
* Flash data pool objects:
* Data Object (DO) (of smart card)
* Internal objects:
* NONE (0x0000)
* 123-counter
* 14-bit counter
* bool object
* small enum
*
* Format of a Data Object:
* NR: 8-bit tag_number
* LEN: 8-bit length
* DATA: data * LEN
* PAD: optional byte for 16-bit alignment
*/
uintptr_t allocate_free_addr(uint16_t size) {
if (size > FLASH_SECTOR_SIZE)
return 0x0; //ERROR
size_t real_size = size+sizeof(uint16_t)+sizeof(uintptr_t)+sizeof(uint16_t); //len+len size+next address+fid
size_t real_size = size+sizeof(uint16_t)+sizeof(uintptr_t)+sizeof(uint16_t)+sizeof(uintptr_t); //len+len size+next address+fid+prev_addr size
uintptr_t next_base = 0x0;
for (uintptr_t base = end_data_pool; base >= start_data_pool; base = next_base) {
uintptr_t addr_alg = base & -FLASH_SECTOR_SIZE; //start address of sector
@@ -64,20 +48,23 @@ uintptr_t allocate_free_addr(uint16_t size) {
if (addr_alg <= potential_addr) //it fits in the current sector
{
flash_program_uintptr(potential_addr, 0x0);
flash_program_uintptr(potential_addr+sizeof(uintptr_t), base);
flash_program_uintptr(base, potential_addr);
return potential_addr;
}
else if (addr_alg-FLASH_SECTOR_SIZE >= start_data_pool) { //check whether it fits in the next sector, so we take addr_aligned as the base
potential_addr = addr_alg-real_size;
flash_program_uintptr(potential_addr, 0x0);
flash_program_uintptr(potential_addr+sizeof(uintptr_t), base);
flash_program_uintptr(base, potential_addr);
return potential_addr;
}
return 0x0;
}
//we check if |base-(next_addr+size_next_addr)| > |base-potential_addr| only if fid != 1xxx (not size blocked)
else if (addr_alg <= potential_addr && base-(next_base+flash_read_uint16(next_base+sizeof(uintptr_t)+sizeof(uint16_t))+2*sizeof(uint16_t)) > base-potential_addr && flash_read_uint16(next_base+sizeof(uintptr_t)) & 0x1000 != 0x1000) {
else if (addr_alg <= potential_addr && base-(next_base+flash_read_uint16(next_base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t))+2*sizeof(uint16_t)+2*sizeof(uintptr_t)) > base-potential_addr && flash_read_uint16(next_base+sizeof(uintptr_t)) & 0x1000 != 0x1000) {
flash_program_uintptr(potential_addr, next_base);
flash_program_uintptr(potential_addr+sizeof(uintptr_t), base);
flash_program_uintptr(base, potential_addr);
return potential_addr;
}
@@ -86,13 +73,16 @@ uintptr_t allocate_free_addr(uint16_t size) {
}
int flash_clear_file(file_t *file) {
uintptr_t prev_addr = (uintptr_t)(file->data+flash_read_uint16((uintptr_t)file->data)+sizeof(uint16_t));
uintptr_t base_addr = (uintptr_t)(file->data-sizeof(uintptr_t)-sizeof(uint16_t));
uintptr_t base_addr = (uintptr_t)(file->data-sizeof(uintptr_t)-sizeof(uint16_t)-sizeof(uintptr_t));
uintptr_t prev_addr = flash_read_uintptr(base_addr+sizeof(uintptr_t));
uintptr_t next_addr = flash_read_uintptr(base_addr);
//printf("nc %x %x %x\r\n",prev_addr,base_addr,next_addr);
//printf("nc %x->%x %x->%x\r\n",prev_addr,flash_read_uintptr(prev_addr),base_addr,next_addr);
flash_program_uintptr(prev_addr, next_addr);
flash_program_halfword((uintptr_t)file->data, 0);
return 0;
if (next_addr > 0)
flash_program_uintptr(next_addr+sizeof(uintptr_t), prev_addr);
//printf("na %x->%x\r\n",prev_addr,flash_read_uintptr(prev_addr));
return HSM_OK;
}
int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len) {
@@ -116,8 +106,8 @@ int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len) {
//printf("na %x\r\n",new_addr);
if (new_addr == 0x0)
return HSM_ERR_NO_MEMORY;
file->data = (uint8_t *)new_addr+sizeof(uintptr_t)+sizeof(uint16_t); //next addr+fid
flash_program_halfword(new_addr+sizeof(uintptr_t), file->fid);
file->data = (uint8_t *)new_addr+sizeof(uintptr_t)+sizeof(uint16_t)+sizeof(uintptr_t); //next addr+fid+prev addr
flash_program_halfword(new_addr+sizeof(uintptr_t)+sizeof(uintptr_t), file->fid);
flash_program_halfword((uintptr_t)file->data, len);
if (data)
flash_program_block((uintptr_t)file->data+sizeof(uint16_t), data, len);

View File

@@ -8,6 +8,9 @@
#include "mbedtls/rsa.h"
#include "mbedtls/ecp.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/cmac.h"
#include "mbedtls/hkdf.h"
#include "version.h"
const uint8_t sc_hsm_aid[] = {
@@ -110,7 +113,7 @@ void select_file(file_t *pe) {
}
if (currentEF == file_openpgp || currentEF == file_sc_hsm) {
selected_applet = currentEF;
sc_hsm_unload(); //reset auth status
//sc_hsm_unload(); //reset auth status
}
}
static int cmd_select() {
@@ -483,8 +486,8 @@ static int cmd_initialize() {
encrypt(session_pin, tmp_dkek, tmp_dkek+IV_SIZE, 32);
file_t *tf = search_by_fid(EF_DKEK, NULL, SPECIFY_EF);
flash_write_data_to_file(tf, tmp_dkek, sizeof(tmp_dkek));
low_flash_available();
}
low_flash_available();
return SW_OK();
}
@@ -679,7 +682,7 @@ int store_keys(void *key_ctx, int type, uint8_t key_id, sc_context_t *ctx) {
free(pukd);
free(p15o);
//sc_asn1_print_tags(asn1bin, asn1len);
fpk = file_new((CD_PREFIX << 8) | key_id);
fpk = file_new((EE_CERTIFICATE_PREFIX << 8) | key_id);
r = flash_write_data_to_file(fpk, asn1bin, asn1len);
free(asn1bin);
if (r != HSM_OK)
@@ -1218,21 +1221,23 @@ static int cmd_key_gen() {
key_size = 24;
else if (p2 == 0xB0)
key_size = 16;
if (!isUserAuthenticated)
return SW_SECURITY_STATUS_NOT_SATISFIED();
//at this moment, we do not use the template, as only CBC is supported by the driver (encrypt, decrypt and CMAC)
uint8_t aes_key[32]; //maximum AES key size
memcpy(aes_key, random_bytes_get(key_size), key_size);
if ((r = load_dkek()) != HSM_OK)
return r;
return SW_EXEC_ERROR() ;
if ((r = encrypt(tmp_dkek+IV_SIZE, tmp_dkek, aes_key, key_size)) != 0)
return r;
return SW_EXEC_ERROR() ;
release_dkek();
file_t *fpk = file_new((KEY_PREFIX << 8) | key_id);
if (!fpk)
return SW_MEMORY_FAILURE();
r = flash_write_data_to_file(fpk, aes_key, key_size);
if (r != HSM_OK)
return SW_MEMORY_FAILURE();
fpk = file_new((PRKD_PREFIX << 8) | key_id);
if (!fpk)
return SW_MEMORY_FAILURE();
r = flash_write_data_to_file(fpk, NULL, 0);
if (r != HSM_OK)
return SW_MEMORY_FAILURE();
@@ -1246,15 +1251,19 @@ int load_private_key_rsa(mbedtls_rsa_context *ctx, file_t *fkey) {
return SW_EXEC_ERROR();
uint8_t *kdata = (uint8_t *)calloc(1,key_size);
memcpy(kdata, file_read(fkey->data+2), key_size);
if (decrypt(tmp_dkek+IV_SIZE, tmp_dkek, kdata, key_size) != 0)
if (decrypt(tmp_dkek+IV_SIZE, tmp_dkek, kdata, key_size) != 0) {
free(kdata);
return SW_EXEC_ERROR();
}
release_dkek();
if (mbedtls_mpi_read_binary(&ctx->P, kdata, key_size/2) != 0) {
mbedtls_rsa_free(ctx);
free(kdata);
return SW_DATA_INVALID();
}
if (mbedtls_mpi_read_binary(&ctx->Q, kdata+key_size/2, key_size/2) != 0) {
mbedtls_rsa_free(ctx);
free(kdata);
return SW_DATA_INVALID();
}
free(kdata);
@@ -1283,15 +1292,19 @@ int load_private_key_ecdsa(mbedtls_ecdsa_context *ctx, file_t *fkey) {
return SW_EXEC_ERROR();
uint8_t *kdata = (uint8_t *)calloc(1,key_size);
memcpy(kdata, file_read(fkey->data+2), key_size);
if (decrypt(tmp_dkek+IV_SIZE, tmp_dkek, kdata, key_size) != 0)
if (decrypt(tmp_dkek+IV_SIZE, tmp_dkek, kdata, key_size) != 0) {
free(kdata);
return SW_EXEC_ERROR();
}
release_dkek();
mbedtls_ecp_group_id gid = kdata[0];
if (mbedtls_ecp_group_load(&ctx->grp, gid) != 0) {
free(kdata);
mbedtls_ecdsa_free(ctx);
return SW_DATA_INVALID();
}
if (mbedtls_mpi_read_binary(&ctx->d, kdata+1, key_size-1) != 0) {
free(kdata);
mbedtls_ecdsa_free(ctx);
return SW_DATA_INVALID();
}
@@ -1385,10 +1398,8 @@ static int cmd_signature() {
}
else {
uint8_t *signature = (uint8_t *)calloc(key_size, sizeof(uint8_t));
printf("md %d\r\n",md);
DEBUG_PAYLOAD(hash,hash_len);
r = mbedtls_rsa_pkcs1_sign(&ctx, random_gen, NULL, md, hash_len, hash, signature);
printf("r %d\r\n",r);
memcpy(res_APDU, signature, key_size);
free(signature);
}
@@ -1471,28 +1482,73 @@ static int cmd_key_unwrap() {
static int cmd_decrypt_asym() {
int key_id = P1(apdu);
if (P2(apdu) != ALGO_RSA_DECRYPT)
return SW_WRONG_P1P2();
if (!isUserAuthenticated)
return SW_SECURITY_STATUS_NOT_SATISFIED();
file_t *ef = search_dynamic_file((KEY_PREFIX << 8) | key_id);
if (!ef)
return SW_FILE_NOT_FOUND();
mbedtls_rsa_context ctx;
mbedtls_rsa_init(&ctx);
int r = load_private_key_rsa(&ctx, ef);
if (r != HSM_OK)
return r;
int key_size = file_read_uint16(ef->data);
if (apdu.cmd_apdu_data_len < key_size) //needs padding
memset(apdu.cmd_apdu_data+apdu.cmd_apdu_data_len, 0, key_size-apdu.cmd_apdu_data_len);
r = mbedtls_rsa_private(&ctx, random_gen, NULL, apdu.cmd_apdu_data, res_APDU);
if (r != 0) {
if (P2(apdu) == ALGO_RSA_DECRYPT) {
mbedtls_rsa_context ctx;
mbedtls_rsa_init(&ctx);
int r = load_private_key_rsa(&ctx, ef);
if (r != HSM_OK)
return r;
int key_size = file_read_uint16(ef->data);
if (apdu.cmd_apdu_data_len < key_size) //needs padding
memset(apdu.cmd_apdu_data+apdu.cmd_apdu_data_len, 0, key_size-apdu.cmd_apdu_data_len);
r = mbedtls_rsa_private(&ctx, random_gen, NULL, apdu.cmd_apdu_data, res_APDU);
if (r != 0) {
mbedtls_rsa_free(&ctx);
return SW_EXEC_ERROR();
}
res_APDU_size = key_size;
mbedtls_rsa_free(&ctx);
return SW_EXEC_ERROR();
}
res_APDU_size = key_size;
mbedtls_rsa_free(&ctx);
else if (P2(apdu) == ALGO_EC_DH) {
mbedtls_ecdh_context ctx;
int key_size = file_read_uint16(ef->data);
if (load_dkek() != HSM_OK)
return SW_EXEC_ERROR();
uint8_t *kdata = (uint8_t *)calloc(1,key_size);
memcpy(kdata, file_read(ef->data+2), key_size);
if (decrypt(tmp_dkek+IV_SIZE, tmp_dkek, kdata, key_size) != 0) {
free(kdata);
return SW_EXEC_ERROR();
}
release_dkek();
mbedtls_ecdh_init(&ctx);
mbedtls_ecp_group_id gid = kdata[0];
int r = 0;
r = mbedtls_ecdh_setup(&ctx, gid);
if (r != 0) {
mbedtls_ecdh_free(&ctx);
free(kdata);
return SW_DATA_INVALID();
}
r = mbedtls_mpi_read_binary(&ctx.ctx.mbed_ecdh.d, kdata+1, key_size-1);
if (r != 0) {
mbedtls_ecdh_free(&ctx);
free(kdata);
return SW_DATA_INVALID();
}
free(kdata);
r = mbedtls_ecdh_read_public(&ctx, apdu.cmd_apdu_data-1, apdu.cmd_apdu_data_len+1);
if (r != 0) {
mbedtls_ecdh_free(&ctx);
return SW_DATA_INVALID();
}
size_t olen = 0;
res_APDU[0] = 0x04;
r = mbedtls_ecdh_calc_secret(&ctx, &olen, res_APDU+1, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
if (r != 0) {
mbedtls_ecdh_free(&ctx);
return SW_EXEC_ERROR();
}
res_APDU_size = olen+1;
mbedtls_ecdh_free(&ctx);
}
else
return SW_WRONG_P1P2();
return SW_OK();
}
@@ -1504,6 +1560,9 @@ static int cmd_cipher_sym() {
file_t *ef = search_dynamic_file((KEY_PREFIX << 8) | key_id);
if (!ef)
return SW_FILE_NOT_FOUND();
if ((apdu.cmd_apdu_data_len % 16) != 0) {
return SW_WRONG_LENGTH();
}
int key_size = file_read_uint16(ef->data);
if (load_dkek() != HSM_OK)
return SW_EXEC_ERROR();
@@ -1514,9 +1573,6 @@ static int cmd_cipher_sym() {
}
release_dkek();
if (algo == ALGO_AES_CBC_ENCRYPT || algo == ALGO_AES_CBC_DECRYPT) {
if ((apdu.cmd_apdu_data_len % 16) != 0) {
return SW_WRONG_LENGTH();
}
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
uint8_t tmp_iv[IV_SIZE];
@@ -1548,6 +1604,27 @@ static int cmd_cipher_sym() {
res_APDU_size = apdu.cmd_apdu_data_len;
mbedtls_aes_free(&aes);
}
else if (algo == ALGO_AES_CMAC) {
const mbedtls_cipher_info_t *cipher_info;
if (key_size == 16)
cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB);
else if (key_size == 24)
cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_192_ECB);
else if (key_size == 32)
cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_256_ECB);
else
return SW_WRONG_DATA();
int r = mbedtls_cipher_cmac(cipher_info, kdata, key_size*8, apdu.cmd_apdu_data, apdu.cmd_apdu_data_len, res_APDU);
if (r != 0)
return SW_EXEC_ERROR();
res_APDU_size = apdu.cmd_apdu_data_len;
}
else if (algo == ALGO_AES_DERIVE) {
int r = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, file_read(ef->data+2), key_size, apdu.cmd_apdu_data, apdu.cmd_apdu_data_len, res_APDU, apdu.cmd_apdu_data_len);
if (r != 0)
return SW_EXEC_ERROR();
res_APDU_size = apdu.cmd_apdu_data_len;
}
else {
return SW_WRONG_P1P2();
}

View File

@@ -1,7 +1,7 @@
#ifndef __VERSION_H_
#define __VERSION_H_
#define HSM_VERSION 0x0102
#define HSM_VERSION 0x0104
#define HSM_VERSION_MAJOR ((HSM_VERSION >> 8) & 0xff)
#define HSM_VERSION_MINOR (HSM_VERSION & 0xff)