Update SCP03 tests with PlatformSCP03 integration tests and documentation
- Add PlatformSCP03 integration test cases (test_scp03_platform_integration, test_scp03_platform_key_file) - Update test helpers with mock session creation - Update README with PlatformSCP03 configuration guide - Add references to NXP AN12413 and AN12436 - Fix test assertions to work with opaque session type
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* @file se050_i2c_hal.c
|
||||
* @brief SE050 I2C HAL Implementation
|
||||
*
|
||||
* Clean-room I2C hardware abstraction layer for SE050.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "se050_wireguard.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __linux__
|
||||
/* Linux-specific I2C IOCTL definitions */
|
||||
#ifndef I2C_SLAVE
|
||||
#define I2C_SLAVE 0x0703
|
||||
#endif
|
||||
#ifndef I2C_SMBUS
|
||||
#define I2C_SMBUS 0x0720
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* SE050 default I2C address */
|
||||
#define SE050_I2C_ADDR_DEFAULT 0x90
|
||||
|
||||
/* I2C timeout in milliseconds */
|
||||
#define SE050_I2C_TIMEOUT_MS 1000
|
||||
|
||||
/* Maximum retry count for I2C operations */
|
||||
#define SE050_I2C_MAX_RETRY 3
|
||||
|
||||
/* SE050 wakeup delay in milliseconds */
|
||||
#define SE050_WAKEUP_DELAY_MS 5
|
||||
|
||||
/**
|
||||
* @brief Sleep for specified milliseconds
|
||||
*/
|
||||
static void se050_sleep_ms(unsigned int ms)
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
#else
|
||||
/* Fallback for non-Linux platforms */
|
||||
volatile int i;
|
||||
for (i = 0; i < ms * 1000; i++);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open I2C device
|
||||
*/
|
||||
static void *i2c_open(const char *dev_path)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (!dev_path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fd = open(dev_path, O_RDWR);
|
||||
if (fd < 0) {
|
||||
perror("I2C open failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (void *)(intptr_t)fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close I2C device
|
||||
*/
|
||||
static void i2c_close(void *handle)
|
||||
{
|
||||
int fd = (int)(intptr_t)handle;
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C slave address
|
||||
*/
|
||||
static se050_status_t i2c_set_slave_addr(void *handle, uint8_t addr)
|
||||
{
|
||||
int fd = (int)(intptr_t)handle;
|
||||
int ret;
|
||||
|
||||
ret = ioctl(fd, I2C_SLAVE, addr >> 1);
|
||||
if (ret < 0) {
|
||||
perror("I2C set slave address failed");
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Public API Implementation
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_i2c_init(se050_i2c_hal_t *hal, const char *dev_path, uint8_t slave_addr)
|
||||
{
|
||||
void *handle;
|
||||
|
||||
if (!hal || !dev_path) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memset(hal, 0, sizeof(*hal));
|
||||
|
||||
/* Open I2C device */
|
||||
handle = i2c_open(dev_path);
|
||||
if (!handle) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Set slave address */
|
||||
if (i2c_set_slave_addr(handle, slave_addr) != SE050_OK) {
|
||||
i2c_close(handle);
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
hal->handle = handle;
|
||||
hal->slave_addr = slave_addr;
|
||||
hal->dev_path = strdup(dev_path);
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
void se050_i2c_close(se050_i2c_hal_t *hal)
|
||||
{
|
||||
if (!hal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hal->handle) {
|
||||
i2c_close(hal->handle);
|
||||
hal->handle = NULL;
|
||||
}
|
||||
|
||||
if (hal->dev_path) {
|
||||
free((void *)hal->dev_path);
|
||||
hal->dev_path = NULL;
|
||||
}
|
||||
|
||||
hal->slave_addr = 0;
|
||||
}
|
||||
|
||||
int se050_i2c_read(se050_i2c_hal_t *hal, uint8_t *buffer, int length)
|
||||
{
|
||||
int fd;
|
||||
int retry;
|
||||
int bytes_read;
|
||||
|
||||
if (!hal || !buffer || length <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = (int)(intptr_t)hal->handle;
|
||||
|
||||
for (retry = 0; retry < SE050_I2C_MAX_RETRY; retry++) {
|
||||
bytes_read = read(fd, buffer, length);
|
||||
if (bytes_read > 0) {
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN && errno != ETIMEDOUT) {
|
||||
/* Permanent error */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Retry with delay */
|
||||
se050_sleep_ms(SE050_WAKEUP_DELAY_MS);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int se050_i2c_write(se050_i2c_hal_t *hal, const uint8_t *buffer, int length)
|
||||
{
|
||||
int fd;
|
||||
int retry;
|
||||
int bytes_written;
|
||||
|
||||
if (!hal || !buffer || length <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = (int)(intptr_t)hal->handle;
|
||||
|
||||
for (retry = 0; retry < SE050_I2C_MAX_RETRY; retry++) {
|
||||
bytes_written = write(fd, buffer, length);
|
||||
if (bytes_written > 0) {
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN && errno != ETIMEDOUT) {
|
||||
/* Permanent error */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Retry with delay */
|
||||
se050_sleep_ms(SE050_WAKEUP_DELAY_MS);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
se050_status_t se050_i2c_wakeup(se050_i2c_hal_t *hal)
|
||||
{
|
||||
uint8_t wakeup_cmd = 0x00;
|
||||
int ret;
|
||||
|
||||
if (!hal) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Send wakeup command (dummy write) */
|
||||
ret = se050_i2c_write(hal, &wakeup_cmd, 1);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Wait for SE050 to respond */
|
||||
se050_sleep_ms(SE050_WAKEUP_DELAY_MS);
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* @file se050_keystore.c
|
||||
* @brief SE050 Key Store Management
|
||||
*
|
||||
* Clean-room implementation of SE050 key store handling.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#include "se050_wireguard.h"
|
||||
#include "se050_crypto_utils.h"
|
||||
#include "se050_keystore_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ============================================================================
|
||||
* Key Store Management
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_keystore_init(se050_keystore_ctx_t **ctx, se050_session_ctx_t *session)
|
||||
{
|
||||
se050_keystore_ctx_t *keystore;
|
||||
|
||||
if (!ctx || !session) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Allocate key store context */
|
||||
keystore = (se050_keystore_ctx_t *)calloc(1, sizeof(*keystore));
|
||||
if (!keystore) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
keystore->session = session;
|
||||
keystore->max_objects = 16; /* Maximum 16 keys */
|
||||
keystore->objects = (key_object_t *)calloc(keystore->max_objects, sizeof(key_object_t));
|
||||
|
||||
if (!keystore->objects) {
|
||||
free(keystore);
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
keystore->num_objects = 0;
|
||||
|
||||
*ctx = keystore;
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
void se050_keystore_free(se050_keystore_ctx_t *ctx)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Securely zeroize all key objects */
|
||||
for (i = 0; i < ctx->num_objects; i++) {
|
||||
key_object_t *obj = &ctx->objects[i];
|
||||
|
||||
if (obj->flags & KEY_FLAG_GENERATED) {
|
||||
memzero_explicit(obj->private_key, sizeof(obj->private_key));
|
||||
memzero_explicit(obj->public_key, sizeof(obj->public_key));
|
||||
}
|
||||
}
|
||||
|
||||
/* Free key objects array */
|
||||
if (ctx->objects) {
|
||||
free(ctx->objects);
|
||||
ctx->objects = NULL;
|
||||
}
|
||||
|
||||
ctx->num_objects = 0;
|
||||
ctx->max_objects = 0;
|
||||
|
||||
/* Free key store context */
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Key Object Management
|
||||
* ============================================================================ */
|
||||
|
||||
/* Internal functions - defined in se050_keystore_internal.h */
|
||||
|
||||
key_object_t *find_key_object(se050_keystore_ctx_t *keystore, uint32_t key_id)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < keystore->num_objects; i++) {
|
||||
if (keystore->objects[i].key_id == key_id) {
|
||||
return &keystore->objects[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key_object_t *allocate_key_object(se050_keystore_ctx_t *keystore)
|
||||
{
|
||||
key_object_t *obj;
|
||||
|
||||
if (keystore->num_objects >= keystore->max_objects) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
obj = &keystore->objects[keystore->num_objects];
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
|
||||
keystore->num_objects++;
|
||||
return obj;
|
||||
}
|
||||
|
||||
se050_status_t se050_keystore_generate_key(se050_keystore_ctx_t *keystore,
|
||||
uint32_t key_id,
|
||||
cipher_type_t cipher_type,
|
||||
size_t key_size,
|
||||
uint8_t *private_key,
|
||||
uint8_t *public_key)
|
||||
{
|
||||
key_object_t *obj;
|
||||
|
||||
if (!keystore || !private_key || !public_key) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Check if key already exists */
|
||||
if (find_key_object(keystore, key_id) != NULL) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Allocate new key object */
|
||||
obj = allocate_key_object(keystore);
|
||||
if (!obj) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize key object */
|
||||
obj->key_id = key_id;
|
||||
obj->key_part = KEY_PART_PAIR;
|
||||
obj->cipher_type = cipher_type;
|
||||
obj->key_size = key_size;
|
||||
obj->flags = KEY_FLAG_GENERATED | KEY_FLAG_PERSISTENT;
|
||||
|
||||
/* Copy key data securely */
|
||||
secure_memcpy(obj->private_key, private_key, key_size);
|
||||
secure_memcpy(obj->public_key, public_key, key_size);
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
se050_status_t se050_keystore_get_public_key(se050_keystore_ctx_t *keystore,
|
||||
uint32_t key_id,
|
||||
uint8_t *public_key,
|
||||
size_t *key_size)
|
||||
{
|
||||
key_object_t *obj;
|
||||
|
||||
if (!keystore || !public_key || !key_size) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Find key object */
|
||||
obj = find_key_object(keystore, key_id);
|
||||
if (!obj) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Check if public key is available */
|
||||
if (!(obj->flags & KEY_FLAG_GENERATED)) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Copy public key securely */
|
||||
*key_size = obj->key_size;
|
||||
secure_memcpy(public_key, obj->public_key, obj->key_size);
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
se050_status_t se050_keystore_get_private_key(se050_keystore_ctx_t *keystore,
|
||||
uint32_t key_id,
|
||||
uint8_t *private_key,
|
||||
size_t *key_size)
|
||||
{
|
||||
key_object_t *obj;
|
||||
|
||||
if (!keystore || !private_key || !key_size) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Find key object */
|
||||
obj = find_key_object(keystore, key_id);
|
||||
if (!obj) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Check if private key is available */
|
||||
if (!(obj->flags & KEY_FLAG_GENERATED)) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Copy private key securely */
|
||||
*key_size = obj->key_size;
|
||||
secure_memcpy(private_key, obj->private_key, obj->key_size);
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* @file se050_rng.c
|
||||
* @brief SE050 True Random Number Generator
|
||||
*
|
||||
* Clean-room implementation of SE050 TRNG interface.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "se050_wireguard.h"
|
||||
#include "se050_crypto_utils.h"
|
||||
#include "se050_session_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* @brief RNG context structure
|
||||
*/
|
||||
struct se050_rng_ctx {
|
||||
se050_session_ctx_t *session; /**< Associated session */
|
||||
uint32_t rng_counter; /**< RNG generation counter */
|
||||
uint8_t entropy_pool[64]; /**< Entropy pool for mixing */
|
||||
size_t entropy_len; /**< Current entropy pool size */
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* RNG Management
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_rng_init(se050_rng_ctx_t **ctx, se050_session_ctx_t *session)
|
||||
{
|
||||
se050_rng_ctx_t *rng;
|
||||
|
||||
if (!ctx || !session) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Allocate RNG context */
|
||||
rng = (se050_rng_ctx_t *)calloc(1, sizeof(*rng));
|
||||
if (!rng) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
rng->session = session;
|
||||
rng->rng_counter = 0;
|
||||
rng->entropy_len = 0;
|
||||
|
||||
/* Zeroize entropy pool */
|
||||
memzero_explicit(rng->entropy_pool, sizeof(rng->entropy_pool));
|
||||
|
||||
*ctx = rng;
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
void se050_rng_free(se050_rng_ctx_t *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Securely zeroize entropy pool */
|
||||
if (ctx->entropy_len > 0) {
|
||||
memzero_explicit(ctx->entropy_pool, ctx->entropy_len);
|
||||
ctx->entropy_len = 0;
|
||||
}
|
||||
|
||||
/* Free RNG context */
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Random Number Generation
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_rng_generate(se050_rng_ctx_t *ctx, uint8_t *output, size_t length)
|
||||
{
|
||||
uint8_t cmd[64];
|
||||
uint8_t rsp[256];
|
||||
int ret;
|
||||
size_t cmd_len, rsp_len;
|
||||
size_t offset = 0;
|
||||
|
||||
if (!ctx || !output) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (length == 0 || length > sizeof(rsp) - 2) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
while (offset < length) {
|
||||
size_t chunk_len = length - offset;
|
||||
if (chunk_len > 240) {
|
||||
chunk_len = 240; /* Maximum chunk size */
|
||||
}
|
||||
|
||||
/* Build GET_RANDOM APDU */
|
||||
cmd[0] = 0x80; /* CLA */
|
||||
cmd[1] = 0xC0; /* INS - GET_RANDOM */
|
||||
cmd[2] = 0x00; /* P1 */
|
||||
cmd[3] = 0x00; /* P2 */
|
||||
cmd[4] = 0x00; /* LC */
|
||||
cmd[5] = (uint8_t)chunk_len; /* LE - requested length */
|
||||
|
||||
cmd_len = 6;
|
||||
|
||||
/* Send command */
|
||||
ret = se050_i2c_write(ctx->session->hal, cmd, (int)cmd_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Receive response */
|
||||
rsp_len = sizeof(rsp);
|
||||
ret = se050_i2c_read(ctx->session->hal, rsp, (int)rsp_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
rsp_len = (size_t)ret;
|
||||
|
||||
/* Check response status */
|
||||
if (rsp_len < 2) {
|
||||
return SE050_ERR_RNG;
|
||||
}
|
||||
|
||||
uint16_t sw = (uint16_t)((rsp[rsp_len - 2] << 8) | rsp[rsp_len - 1]);
|
||||
if (sw != 0x9000) {
|
||||
return SE050_ERR_RNG;
|
||||
}
|
||||
|
||||
/* Copy random data (excluding status word) */
|
||||
size_t data_len = rsp_len - 2;
|
||||
if (data_len > chunk_len) {
|
||||
data_len = chunk_len;
|
||||
}
|
||||
|
||||
memcpy(output + offset, rsp, data_len);
|
||||
offset += data_len;
|
||||
|
||||
/* Increment counter */
|
||||
ctx->rng_counter++;
|
||||
}
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate random bytes using host crypto fallback
|
||||
* This is a fallback implementation if SE050 TRNG is not available.
|
||||
*/
|
||||
se050_status_t se050_rng_generate_fallback(uint8_t *output, size_t length)
|
||||
{
|
||||
#ifdef __linux__
|
||||
int fd;
|
||||
ssize_t ret;
|
||||
|
||||
/* Use /dev/urandom as fallback */
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return SE050_ERR_RNG;
|
||||
}
|
||||
|
||||
ret = read(fd, output, length);
|
||||
close(fd);
|
||||
|
||||
if (ret != (ssize_t)length) {
|
||||
return SE050_ERR_RNG;
|
||||
}
|
||||
|
||||
return SE050_OK;
|
||||
#else
|
||||
/* Simple LCG-based fallback (NOT SECURE - for testing only) */
|
||||
static uint32_t lcg_state = 0x12345678;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
lcg_state = lcg_state * 1103515245 + 12345;
|
||||
output[i] = (uint8_t)((lcg_state >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
return SE050_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a random 32-byte key
|
||||
*/
|
||||
se050_status_t se050_rng_generate_key(se050_rng_ctx_t *ctx, uint8_t *key)
|
||||
{
|
||||
return se050_rng_generate(ctx, key, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a random nonce
|
||||
*/
|
||||
se050_status_t se050_rng_generate_nonce(se050_rng_ctx_t *ctx, uint8_t *nonce, size_t length)
|
||||
{
|
||||
return se050_rng_generate(ctx, nonce, length);
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/**
|
||||
* @file se050_scp03.c
|
||||
* @brief SE050 Platform SCP03 Secure Channel
|
||||
*
|
||||
* Clean-room implementation of SCP03 secure channel protocol.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#include "se050_wireguard.h"
|
||||
#include "se050_crypto_utils.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* SCP03 constants */
|
||||
#define SCP03_KEY_SIZE 16
|
||||
#define SCP03_IV_SIZE 16
|
||||
#define SCP03_CMAC_SIZE 8
|
||||
#define SCP03_MAX_CMD_LEN 255
|
||||
#define SCP03_MAX_RSP_LEN 255
|
||||
|
||||
/* SCP03 APDU status codes */
|
||||
#define SCP03_SW_SUCCESS 0x9000
|
||||
#define SCP03_SW_FAIL 0x6F00
|
||||
|
||||
/**
|
||||
* @brief SCP03 session context structure
|
||||
*/
|
||||
struct se050_scp03_ctx {
|
||||
se050_session_ctx_t *session; /**< Associated session */
|
||||
uint8_t enc_key[SCP03_KEY_SIZE]; /**< Encryption key */
|
||||
uint8_t mac_key[SCP03_KEY_SIZE]; /**< MAC key */
|
||||
uint8_t dek_key[SCP03_KEY_SIZE]; /**< DEK key (for key derivation) */
|
||||
uint8_t cmd_icv[SCP03_CMAC_SIZE]; /**< Command ICV */
|
||||
uint8_t rsp_icv[SCP03_CMAC_SIZE]; /**< Response ICV */
|
||||
uint64_t cmd_counter; /**< Command counter */
|
||||
uint64_t rsp_counter; /**< Response counter */
|
||||
uint8_t initialized; /**< Initialization flag */
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Helper Functions
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* @brief Calculate CMAC for SCP03
|
||||
*
|
||||
* This is a placeholder implementation. In production, use a proper
|
||||
* AES-CMAC implementation (e.g., from OpenSSL or libgcrypt).
|
||||
*/
|
||||
static int scp03_calc_cmac(const uint8_t *key, size_t key_len,
|
||||
const uint8_t *data, size_t data_len,
|
||||
uint8_t *mac, size_t *mac_len)
|
||||
{
|
||||
#if defined(__linux__) && defined(OPENSSL_AVAILABLE)
|
||||
/* Use OpenSSL for AES-CMAC */
|
||||
#include <openssl/cmac.h>
|
||||
CMAC_CTX *ctx;
|
||||
size_t len;
|
||||
|
||||
ctx = CMAC_CTX_new();
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (CMAC_Init(ctx, key, key_len, EVP_aes_128(), NULL) != 1) {
|
||||
CMAC_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (CMAC_Update(ctx, data, data_len) != 1) {
|
||||
CMAC_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (CMAC_Final(ctx, mac, &len) != 1) {
|
||||
CMAC_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*mac_len = len;
|
||||
CMAC_CTX_free(ctx);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
/* Fallback: Simple XOR-based MAC (NOT SECURE - for testing only) */
|
||||
size_t i, j;
|
||||
|
||||
memset(mac, 0, *mac_len);
|
||||
|
||||
for (i = 0; i < data_len; i++) {
|
||||
for (j = 0; j < *mac_len && j < SCP03_KEY_SIZE; j++) {
|
||||
mac[j % *mac_len] ^= data[i] ^ key[j];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AES-CBC encryption for SCP03
|
||||
*/
|
||||
static int scp03_aes_cbc_encrypt(const uint8_t *key, size_t key_len,
|
||||
const uint8_t *iv, size_t iv_len,
|
||||
const uint8_t *plaintext, size_t plaintext_len,
|
||||
uint8_t *ciphertext, size_t *ciphertext_len)
|
||||
{
|
||||
#if defined(__linux__) && defined(OPENSSL_AVAILABLE)
|
||||
/* Use OpenSSL for AES-CBC */
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
EVP_CIPHER_CTX *ctx;
|
||||
int len;
|
||||
int ciphertext_len_tmp;
|
||||
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, (int)plaintext_len) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ciphertext_len_tmp = len;
|
||||
|
||||
if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ciphertext_len_tmp += len;
|
||||
*ciphertext_len = (size_t)ciphertext_len_tmp;
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
/* Fallback: Simple XOR-based encryption (NOT SECURE - for testing only) */
|
||||
size_t i, j;
|
||||
size_t block_size = 16;
|
||||
|
||||
/* Pad to block size */
|
||||
size_t padded_len = ((plaintext_len + block_size - 1) / block_size) * block_size;
|
||||
memset(ciphertext, 0, padded_len);
|
||||
memcpy(ciphertext, plaintext, plaintext_len);
|
||||
|
||||
/* XOR with key and IV */
|
||||
for (i = 0; i < padded_len; i++) {
|
||||
ciphertext[i] ^= key[i % key_len] ^ iv[i % iv_len];
|
||||
}
|
||||
|
||||
*ciphertext_len = padded_len;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AES-CBC decryption for SCP03
|
||||
*/
|
||||
static int scp03_aes_cbc_decrypt(const uint8_t *key, size_t key_len,
|
||||
const uint8_t *iv, size_t iv_len,
|
||||
const uint8_t *ciphertext, size_t ciphertext_len,
|
||||
uint8_t *plaintext, size_t *plaintext_len)
|
||||
{
|
||||
#if defined(__linux__) && defined(OPENSSL_AVAILABLE)
|
||||
/* Use OpenSSL for AES-CBC */
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
EVP_CIPHER_CTX *ctx;
|
||||
int len;
|
||||
int plaintext_len_tmp;
|
||||
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, (int)ciphertext_len) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext_len_tmp = len;
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext_len_tmp += len;
|
||||
*plaintext_len = (size_t)plaintext_len_tmp;
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
/* Fallback: Simple XOR-based decryption (NOT SECURE - for testing only) */
|
||||
size_t i, j;
|
||||
|
||||
/* XOR with key and IV */
|
||||
for (i = 0; i < ciphertext_len; i++) {
|
||||
plaintext[i] = ciphertext[i] ^ key[i % key_len] ^ iv[i % iv_len];
|
||||
}
|
||||
|
||||
*plaintext_len = ciphertext_len;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* SCP03 Management
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* @brief Derive session keys from DEK using SCP03 key derivation
|
||||
*
|
||||
* SCP03 uses the DEK key to derive session-specific encryption and MAC keys.
|
||||
* This is done through a key derivation function using AES.
|
||||
*/
|
||||
static se050_status_t scp03_derive_session_keys(se050_scp03_ctx_t *ctx)
|
||||
{
|
||||
uint8_t derive_data[16];
|
||||
uint8_t derived_key[16];
|
||||
size_t derived_len;
|
||||
|
||||
if (!ctx) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/*
|
||||
* SCP03 Key Derivation:
|
||||
* - Use DEK to encrypt a derivation constant
|
||||
* - Result is used as session key material
|
||||
*
|
||||
* Note: This is a simplified derivation. Real SCP03 uses more complex
|
||||
* derivation with counters and nonces.
|
||||
*/
|
||||
|
||||
/* Derivation constant for ENC key */
|
||||
memset(derive_data, 0, sizeof(derive_data));
|
||||
derive_data[0] = 0x01; /* ENC key derivation */
|
||||
|
||||
if (scp03_aes_cbc_encrypt(ctx->dek_key, SCP03_KEY_SIZE,
|
||||
derive_data, SCP03_IV_SIZE,
|
||||
derive_data, sizeof(derive_data),
|
||||
derived_key, &derived_len) != 0) {
|
||||
return SE050_ERR_SCP03;
|
||||
}
|
||||
|
||||
/* Update ENC key with derived key */
|
||||
for (size_t i = 0; i < SCP03_KEY_SIZE; i++) {
|
||||
ctx->enc_key[i] ^= derived_key[i];
|
||||
}
|
||||
|
||||
/* Derivation constant for MAC key */
|
||||
memset(derive_data, 0, sizeof(derive_data));
|
||||
derive_data[0] = 0x02; /* MAC key derivation */
|
||||
|
||||
if (scp03_aes_cbc_encrypt(ctx->dek_key, SCP03_KEY_SIZE,
|
||||
derive_data, SCP03_IV_SIZE,
|
||||
derive_data, sizeof(derive_data),
|
||||
derived_key, &derived_len) != 0) {
|
||||
return SE050_ERR_SCP03;
|
||||
}
|
||||
|
||||
/* Update MAC key with derived key */
|
||||
for (size_t i = 0; i < SCP03_KEY_SIZE; i++) {
|
||||
ctx->mac_key[i] ^= derived_key[i];
|
||||
}
|
||||
|
||||
/* Securely zeroize */
|
||||
memzero_explicit(derive_data, sizeof(derive_data));
|
||||
memzero_explicit(derived_key, sizeof(derived_key));
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
se050_status_t se050_scp03_init(se050_scp03_ctx_t **ctx, se050_session_ctx_t *session)
|
||||
{
|
||||
se050_scp03_ctx_t *scp03;
|
||||
|
||||
if (!ctx || !session) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Allocate SCP03 context */
|
||||
scp03 = (se050_scp03_ctx_t *)calloc(1, sizeof(*scp03));
|
||||
if (!scp03) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
scp03->session = session;
|
||||
scp03->cmd_counter = 0;
|
||||
scp03->rsp_counter = 0;
|
||||
scp03->initialized = 0;
|
||||
|
||||
/* Zeroize keys */
|
||||
memzero_explicit(scp03->enc_key, sizeof(scp03->enc_key));
|
||||
memzero_explicit(scp03->mac_key, sizeof(scp03->mac_key));
|
||||
memzero_explicit(scp03->dek_key, sizeof(scp03->dek_key));
|
||||
memzero_explicit(scp03->cmd_icv, sizeof(scp03->cmd_icv));
|
||||
memzero_explicit(scp03->rsp_icv, sizeof(scp03->rsp_icv));
|
||||
|
||||
*ctx = scp03;
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
void se050_scp03_free(se050_scp03_ctx_t *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Securely zeroize all sensitive data */
|
||||
if (ctx->initialized) {
|
||||
memzero_explicit(ctx->enc_key, sizeof(ctx->enc_key));
|
||||
memzero_explicit(ctx->mac_key, sizeof(ctx->mac_key));
|
||||
memzero_explicit(ctx->dek_key, sizeof(ctx->dek_key));
|
||||
memzero_explicit(ctx->cmd_icv, sizeof(ctx->cmd_icv));
|
||||
memzero_explicit(ctx->rsp_icv, sizeof(ctx->rsp_icv));
|
||||
}
|
||||
|
||||
/* Free SCP03 context */
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
se050_status_t se050_scp03_set_keys(se050_scp03_ctx_t *ctx,
|
||||
const uint8_t *enc_key,
|
||||
const uint8_t *mac_key,
|
||||
const uint8_t *dek_key)
|
||||
{
|
||||
se050_status_t status;
|
||||
|
||||
if (!ctx || !enc_key || !mac_key || !dek_key) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Copy keys securely */
|
||||
secure_memcpy(ctx->enc_key, enc_key, SCP03_KEY_SIZE);
|
||||
secure_memcpy(ctx->mac_key, mac_key, SCP03_KEY_SIZE);
|
||||
secure_memcpy(ctx->dek_key, dek_key, SCP03_KEY_SIZE);
|
||||
|
||||
/* Derive session keys from DEK */
|
||||
status = scp03_derive_session_keys(ctx);
|
||||
if (status != SE050_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Initialize ICVs */
|
||||
memset(ctx->cmd_icv, 0, sizeof(ctx->cmd_icv));
|
||||
memset(ctx->rsp_icv, 0, sizeof(ctx->rsp_icv));
|
||||
|
||||
/* Reset counters */
|
||||
ctx->cmd_counter = 0;
|
||||
ctx->rsp_counter = 0;
|
||||
|
||||
ctx->initialized = 1;
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
se050_status_t se050_scp03_load_keys_from_file(se050_scp03_ctx_t *ctx, const char *file_path)
|
||||
{
|
||||
FILE *fp;
|
||||
uint8_t keys[48]; /* ENC (16) + MAC (16) + DEK (16) */
|
||||
size_t bytes_read;
|
||||
|
||||
if (!ctx || !file_path) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Open key file */
|
||||
fp = fopen(file_path, "rb");
|
||||
if (!fp) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Read all 48 bytes (ENC + MAC + DEK) */
|
||||
bytes_read = fread(keys, 1, sizeof(keys), fp);
|
||||
fclose(fp);
|
||||
|
||||
if (bytes_read < 48) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Set all three keys */
|
||||
se050_scp03_set_keys(ctx, keys, &keys[16], &keys[32]);
|
||||
|
||||
/* Securely zeroize */
|
||||
memzero_explicit(keys, sizeof(keys));
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* SCP03 Encryption/Decryption
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_scp03_encrypt_command(se050_scp03_ctx_t *ctx,
|
||||
uint8_t *cmd,
|
||||
size_t *cmd_len)
|
||||
{
|
||||
uint8_t iv[SCP03_IV_SIZE];
|
||||
uint8_t mac[SCP03_CMAC_SIZE];
|
||||
size_t mac_len = SCP03_CMAC_SIZE;
|
||||
uint8_t padded_cmd[SCP03_MAX_CMD_LEN + 16];
|
||||
size_t padded_len;
|
||||
size_t encrypted_len;
|
||||
|
||||
if (!ctx || !cmd || !cmd_len) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ctx->initialized) {
|
||||
return SE050_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
if (*cmd_len > SCP03_MAX_CMD_LEN) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Calculate IV from counter */
|
||||
memset(iv, 0, sizeof(iv));
|
||||
iv[0] = (uint8_t)((ctx->cmd_counter >> 56) & 0xFF);
|
||||
iv[1] = (uint8_t)((ctx->cmd_counter >> 48) & 0xFF);
|
||||
iv[2] = (uint8_t)((ctx->cmd_counter >> 40) & 0xFF);
|
||||
iv[3] = (uint8_t)((ctx->cmd_counter >> 32) & 0xFF);
|
||||
iv[4] = (uint8_t)((ctx->cmd_counter >> 24) & 0xFF);
|
||||
iv[5] = (uint8_t)((ctx->cmd_counter >> 16) & 0xFF);
|
||||
iv[6] = (uint8_t)((ctx->cmd_counter >> 8) & 0xFF);
|
||||
iv[7] = (uint8_t)(ctx->cmd_counter & 0xFF);
|
||||
|
||||
/* Pad command (PKCS#7) */
|
||||
padded_len = *cmd_len + 1;
|
||||
memcpy(padded_cmd, cmd, *cmd_len);
|
||||
padded_cmd[*cmd_len] = 0x80;
|
||||
memset(padded_cmd + padded_len, 0, 15 - ((*cmd_len + 1) % 16));
|
||||
padded_len = ((padded_len + 15) / 16) * 16;
|
||||
|
||||
/* Encrypt */
|
||||
if (scp03_aes_cbc_encrypt(ctx->enc_key, SCP03_KEY_SIZE,
|
||||
iv, SCP03_IV_SIZE,
|
||||
padded_cmd, padded_len,
|
||||
cmd, &encrypted_len) != 0) {
|
||||
return SE050_ERR_SCP03;
|
||||
}
|
||||
|
||||
/* Calculate ICV (CMAC) */
|
||||
if (scp03_calc_cmac(ctx->mac_key, SCP03_KEY_SIZE,
|
||||
cmd, encrypted_len,
|
||||
mac, &mac_len) != 0) {
|
||||
return SE050_ERR_SCP03;
|
||||
}
|
||||
|
||||
/* Update ICV */
|
||||
memcpy(ctx->cmd_icv, mac, SCP03_CMAC_SIZE);
|
||||
|
||||
/* Increment counter */
|
||||
ctx->cmd_counter++;
|
||||
|
||||
*cmd_len = encrypted_len;
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
uint16_t se050_scp03_decrypt_response(se050_scp03_ctx_t *ctx,
|
||||
size_t cmd_len,
|
||||
uint8_t *rsp,
|
||||
size_t *rsp_len)
|
||||
{
|
||||
uint8_t iv[SCP03_IV_SIZE];
|
||||
uint8_t mac[SCP03_CMAC_SIZE];
|
||||
size_t mac_len = SCP03_CMAC_SIZE;
|
||||
uint8_t decrypted[SCP03_MAX_RSP_LEN];
|
||||
size_t decrypted_len;
|
||||
uint16_t sw;
|
||||
int ret;
|
||||
|
||||
if (!ctx || !rsp || !rsp_len) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
if (!ctx->initialized) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
if (*rsp_len < 2) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
/* Calculate IV from counter */
|
||||
memset(iv, 0, sizeof(iv));
|
||||
iv[0] = (uint8_t)((ctx->rsp_counter >> 56) & 0xFF);
|
||||
iv[1] = (uint8_t)((ctx->rsp_counter >> 48) & 0xFF);
|
||||
iv[2] = (uint8_t)((ctx->rsp_counter >> 40) & 0xFF);
|
||||
iv[3] = (uint8_t)((ctx->rsp_counter >> 32) & 0xFF);
|
||||
iv[4] = (uint8_t)((ctx->rsp_counter >> 24) & 0xFF);
|
||||
iv[5] = (uint8_t)((ctx->rsp_counter >> 16) & 0xFF);
|
||||
iv[6] = (uint8_t)((ctx->rsp_counter >> 8) & 0xFF);
|
||||
iv[7] = (uint8_t)(ctx->rsp_counter & 0xFF);
|
||||
|
||||
/* Calculate expected ICV */
|
||||
ret = scp03_calc_cmac(ctx->mac_key, SCP03_KEY_SIZE,
|
||||
rsp, *rsp_len,
|
||||
mac, &mac_len);
|
||||
if (ret != 0) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
/* Verify ICV (constant-time comparison) */
|
||||
if (crypto_memneq(mac, ctx->rsp_icv, SCP03_CMAC_SIZE) != 0) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
/* Decrypt */
|
||||
ret = scp03_aes_cbc_decrypt(ctx->enc_key, SCP03_KEY_SIZE,
|
||||
iv, SCP03_IV_SIZE,
|
||||
rsp, *rsp_len,
|
||||
decrypted, &decrypted_len);
|
||||
if (ret != 0) {
|
||||
return SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
/* Remove padding */
|
||||
if (decrypted_len > 0 && decrypted[decrypted_len - 1] == 0x80) {
|
||||
decrypted_len--;
|
||||
}
|
||||
|
||||
/* Copy decrypted data */
|
||||
memcpy(rsp, decrypted, decrypted_len);
|
||||
*rsp_len = decrypted_len;
|
||||
|
||||
/* Extract status word */
|
||||
if (decrypted_len >= 2) {
|
||||
sw = (uint16_t)((decrypted[decrypted_len - 2] << 8) | decrypted[decrypted_len - 1]);
|
||||
} else {
|
||||
sw = SCP03_SW_FAIL;
|
||||
}
|
||||
|
||||
/* Update ICV */
|
||||
memcpy(ctx->rsp_icv, mac, SCP03_CMAC_SIZE);
|
||||
|
||||
/* Increment counter */
|
||||
ctx->rsp_counter++;
|
||||
|
||||
return sw;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @file se050_session.c
|
||||
* @brief SE050 Session Management
|
||||
*
|
||||
* Clean-room implementation of SE050 session handling.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#include "se050_wireguard.h"
|
||||
#include "se050_crypto_utils.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Session states */
|
||||
typedef enum {
|
||||
SESSION_STATE_CREATED = 0,
|
||||
SESSION_STATE_OPENED,
|
||||
SESSION_STATE_CLOSED,
|
||||
} session_state_t;
|
||||
|
||||
/**
|
||||
* @brief Session context structure
|
||||
*/
|
||||
struct se050_session_ctx {
|
||||
se050_i2c_hal_t *hal; /**< I2C HAL interface */
|
||||
session_state_t state; /**< Current session state */
|
||||
uint32_t session_id; /**< Unique session identifier */
|
||||
uint8_t session_key[32]; /**< Session encryption key */
|
||||
size_t session_key_len; /**< Session key length */
|
||||
uint32_t cmd_counter; /**< Command counter for SCP03 */
|
||||
uint32_t resp_counter; /**< Response counter for SCP03 */
|
||||
se050_rng_ctx_t *rng; /**< RNG context */
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Session Management
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_session_create(se050_session_ctx_t **ctx, se050_i2c_hal_t *hal)
|
||||
{
|
||||
se050_session_ctx_t *session;
|
||||
static uint32_t session_counter = 0;
|
||||
|
||||
if (!ctx || !hal) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Allocate session context */
|
||||
session = (se050_session_ctx_t *)calloc(1, sizeof(*session));
|
||||
if (!session) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Initialize session */
|
||||
session->hal = hal;
|
||||
session->state = SESSION_STATE_CREATED;
|
||||
session->session_id = ++session_counter;
|
||||
session->session_key_len = 0;
|
||||
session->cmd_counter = 0;
|
||||
session->resp_counter = 0;
|
||||
|
||||
/* Zeroize session key on allocation */
|
||||
memzero_explicit(session->session_key, sizeof(session->session_key));
|
||||
|
||||
*ctx = session;
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
se050_status_t se050_session_open(se050_session_ctx_t *ctx)
|
||||
{
|
||||
uint8_t cmd[64];
|
||||
uint8_t rsp[64];
|
||||
int ret;
|
||||
size_t cmd_len, rsp_len;
|
||||
|
||||
if (!ctx) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (ctx->state != SESSION_STATE_CREATED) {
|
||||
return SE050_ERR_SESSION;
|
||||
}
|
||||
|
||||
/* Build OPEN_SESSION APDU */
|
||||
/* CLA INS P1 P2 LC DATA LE */
|
||||
cmd[0] = 0x80; /* CLA */
|
||||
cmd[1] = 0x70; /* INS - OPEN_SESSION */
|
||||
cmd[2] = 0x00; /* P1 */
|
||||
cmd[3] = 0x00; /* P2 */
|
||||
cmd[4] = 0x00; /* LC */
|
||||
cmd[5] = 0x00; /* LE */
|
||||
|
||||
cmd_len = 6;
|
||||
|
||||
/* Send command */
|
||||
ret = se050_i2c_write(ctx->hal, cmd, (int)cmd_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Receive response */
|
||||
rsp_len = sizeof(rsp);
|
||||
ret = se050_i2c_read(ctx->hal, rsp, (int)rsp_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
rsp_len = (size_t)ret;
|
||||
|
||||
/* Check response status */
|
||||
if (rsp_len < 2) {
|
||||
return SE050_ERR_SESSION;
|
||||
}
|
||||
|
||||
uint16_t sw = (uint16_t)((rsp[rsp_len - 2] << 8) | rsp[rsp_len - 1]);
|
||||
if (sw != 0x9000) {
|
||||
return SE050_ERR_SESSION;
|
||||
}
|
||||
|
||||
ctx->state = SESSION_STATE_OPENED;
|
||||
return SE050_OK;
|
||||
}
|
||||
|
||||
void se050_session_close(se050_session_ctx_t *ctx)
|
||||
{
|
||||
uint8_t cmd[64];
|
||||
uint8_t rsp[64];
|
||||
int ret;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->state != SESSION_STATE_OPENED) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build CLOSE_SESSION APDU */
|
||||
cmd[0] = 0x80; /* CLA */
|
||||
cmd[1] = 0x71; /* INS - CLOSE_SESSION */
|
||||
cmd[2] = 0x00; /* P1 */
|
||||
cmd[3] = 0x00; /* P2 */
|
||||
cmd[4] = 0x00; /* LC */
|
||||
cmd[5] = 0x00; /* LE */
|
||||
|
||||
ret = se050_i2c_write(ctx->hal, cmd, 6);
|
||||
if (ret >= 0) {
|
||||
se050_i2c_read(ctx->hal, rsp, sizeof(rsp));
|
||||
}
|
||||
|
||||
ctx->state = SESSION_STATE_CLOSED;
|
||||
}
|
||||
|
||||
void se050_session_delete(se050_session_ctx_t *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Securely zeroize session key */
|
||||
if (ctx->session_key_len > 0) {
|
||||
memzero_explicit(ctx->session_key, ctx->session_key_len);
|
||||
ctx->session_key_len = 0;
|
||||
}
|
||||
|
||||
/* Free session context */
|
||||
free(ctx);
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* @file se050_x25519.c
|
||||
* @brief SE050 X25519 ECDH Implementation
|
||||
*
|
||||
* Clean-room implementation of X25519 ECDH using SE050.
|
||||
*
|
||||
* License: MIT (Clean-room implementation)
|
||||
*/
|
||||
|
||||
#include "se050_wireguard.h"
|
||||
#include "se050_crypto_utils.h"
|
||||
#include "se050_keystore_internal.h"
|
||||
#include "se050_session_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* X25519 scalar size */
|
||||
#define X25519_SCALAR_SIZE 32
|
||||
#define X25519_POINT_SIZE 32
|
||||
|
||||
/* ============================================================================
|
||||
* X25519 Key Generation
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* @brief Clamp a private key for X25519
|
||||
*
|
||||
* X25519 requires specific bit manipulation of the private key:
|
||||
* - Clear bits 0, 1, 2 of first byte
|
||||
* - Clear bit 254 of last byte
|
||||
* - Set bit 255 of last byte
|
||||
*/
|
||||
static void x25519_clamp(uint8_t *scalar)
|
||||
{
|
||||
scalar[0] &= 248; /* Clear bits 0, 1, 2 */
|
||||
scalar[31] &= 127; /* Clear bit 254 */
|
||||
scalar[31] |= 64; /* Set bit 255 */
|
||||
}
|
||||
|
||||
se050_status_t se050_x25519_generate_keypair(se050_keystore_ctx_t *keystore,
|
||||
se050_x25519_keypair_t *keypair,
|
||||
uint32_t key_id)
|
||||
{
|
||||
se050_rng_ctx_t *rng;
|
||||
se050_i2c_hal_t *hal;
|
||||
uint8_t private_key[X25519_SCALAR_SIZE];
|
||||
uint8_t public_key[X25519_POINT_SIZE];
|
||||
uint8_t cmd[128];
|
||||
uint8_t rsp[128];
|
||||
int ret;
|
||||
size_t cmd_len, rsp_len;
|
||||
se050_status_t status;
|
||||
|
||||
if (!keystore || !keystore->session || !keypair) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
rng = keystore->session->rng;
|
||||
hal = keystore->session->hal;
|
||||
if (!rng || !hal) {
|
||||
return SE050_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/* Generate random private key */
|
||||
status = se050_rng_generate(rng, private_key, X25519_SCALAR_SIZE);
|
||||
if (status != SE050_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Clamp private key */
|
||||
x25519_clamp(private_key);
|
||||
|
||||
/* Build GENERATE_ECC_KEY_APDU for X25519 */
|
||||
/* CLA INS P1 P2 LC DATA LE */
|
||||
cmd[0] = 0x80; /* CLA */
|
||||
cmd[1] = 0x46; /* INS - GENERATE_ECC_KEY */
|
||||
cmd[2] = 0x00; /* P1 */
|
||||
cmd[3] = 0x00; /* P2 */
|
||||
cmd[4] = 0x24; /* LC - data length */
|
||||
|
||||
/* ECC Key Generation Data */
|
||||
cmd[5] = 0x81; /* Tag: Key ID */
|
||||
cmd[6] = 0x04; /* Length */
|
||||
cmd[7] = (uint8_t)(key_id >> 24);
|
||||
cmd[8] = (uint8_t)(key_id >> 16);
|
||||
cmd[9] = (uint8_t)(key_id >> 8);
|
||||
cmd[10] = (uint8_t)key_id;
|
||||
|
||||
cmd[11] = 0x82; /* Tag: Cipher Type */
|
||||
cmd[12] = 0x01; /* Length */
|
||||
cmd[13] = 0x32; /* Value: EC_MONTGOMERY (X25519) */
|
||||
|
||||
cmd[14] = 0x83; /* Tag: Key Size */
|
||||
cmd[15] = 0x02; /* Length */
|
||||
cmd[16] = 0x00; /* High byte */
|
||||
cmd[17] = 0x20; /* Low byte: 32 bytes */
|
||||
|
||||
cmd[18] = 0x84; /* Tag: Private Key */
|
||||
cmd[19] = 0x20; /* Length: 32 bytes */
|
||||
memcpy(&cmd[20], private_key, X25519_SCALAR_SIZE);
|
||||
|
||||
cmd_len = 20 + X25519_SCALAR_SIZE;
|
||||
cmd[4] = (uint8_t)(cmd_len - 5); /* Update LC */
|
||||
|
||||
/* Send command */
|
||||
ret = se050_i2c_write(hal, cmd, (int)cmd_len);
|
||||
if (ret < 0) {
|
||||
memzero_explicit(private_key, sizeof(private_key));
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Receive response */
|
||||
rsp_len = sizeof(rsp);
|
||||
ret = se050_i2c_read(hal, rsp, (int)rsp_len);
|
||||
if (ret < 0) {
|
||||
memzero_explicit(private_key, sizeof(private_key));
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
rsp_len = (size_t)ret;
|
||||
|
||||
/* Check response status */
|
||||
if (rsp_len < 2) {
|
||||
memzero_explicit(private_key, sizeof(private_key));
|
||||
return SE050_ERR_ECDH;
|
||||
}
|
||||
|
||||
uint16_t sw = (uint16_t)((rsp[rsp_len - 2] << 8) | rsp[rsp_len - 1]);
|
||||
if (sw != 0x9000) {
|
||||
memzero_explicit(private_key, sizeof(private_key));
|
||||
return SE050_ERR_ECDH;
|
||||
}
|
||||
|
||||
/* Extract public key from response */
|
||||
/* Response format: TLV with public key */
|
||||
size_t data_offset = 0;
|
||||
while (data_offset < rsp_len - 2) {
|
||||
uint8_t tag = rsp[data_offset];
|
||||
uint8_t len = rsp[data_offset + 1];
|
||||
|
||||
if (tag == 0x85) { /* Public Key tag */
|
||||
if (len == X25519_POINT_SIZE) {
|
||||
memcpy(public_key, &rsp[data_offset + 2], X25519_POINT_SIZE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data_offset += 2 + len;
|
||||
}
|
||||
|
||||
/* Store key pair */
|
||||
secure_memcpy(keypair->private_key, private_key, X25519_SCALAR_SIZE);
|
||||
secure_memcpy(keypair->public_key, public_key, X25519_POINT_SIZE);
|
||||
|
||||
/* Store in keystore */
|
||||
status = se050_keystore_generate_key(keystore, key_id,
|
||||
CIPHER_TYPE_EC_MONTGOMERY,
|
||||
X25519_SCALAR_SIZE,
|
||||
private_key, public_key);
|
||||
|
||||
/* Securely zeroize private key */
|
||||
memzero_explicit(private_key, sizeof(private_key));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* X25519 ECDH Shared Secret Computation
|
||||
* ============================================================================ */
|
||||
|
||||
se050_status_t se050_x25519_compute_shared_secret(se050_keystore_ctx_t *keystore,
|
||||
uint32_t private_key_id,
|
||||
const uint8_t *peer_public,
|
||||
uint8_t *shared_secret)
|
||||
{
|
||||
uint8_t cmd[128];
|
||||
uint8_t rsp[128];
|
||||
int ret;
|
||||
size_t cmd_len, rsp_len;
|
||||
key_object_t *priv_key;
|
||||
se050_i2c_hal_t *hal;
|
||||
|
||||
if (!keystore || !keystore->session || !peer_public || !shared_secret) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
hal = keystore->session->hal;
|
||||
if (!hal) {
|
||||
return SE050_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/* Find private key object */
|
||||
priv_key = find_key_object(keystore, private_key_id);
|
||||
if (!priv_key) {
|
||||
return SE050_ERR_FAIL;
|
||||
}
|
||||
|
||||
/* Build ECDH COMPUTE SHARED SECRET APDU */
|
||||
cmd[0] = 0x80; /* CLA */
|
||||
cmd[1] = 0x48; /* INS - ECDH COMPUTE SHARED SECRET */
|
||||
cmd[2] = 0x00; /* P1 */
|
||||
cmd[3] = 0x00; /* P2 */
|
||||
cmd[4] = 0x26; /* LC - data length */
|
||||
|
||||
/* ECDH Data */
|
||||
cmd[5] = 0x81; /* Tag: Key ID */
|
||||
cmd[6] = 0x04; /* Length */
|
||||
cmd[7] = (uint8_t)(private_key_id >> 24);
|
||||
cmd[8] = (uint8_t)(private_key_id >> 16);
|
||||
cmd[9] = (uint8_t)(private_key_id >> 8);
|
||||
cmd[10] = (uint8_t)private_key_id;
|
||||
|
||||
cmd[11] = 0x82; /* Tag: Peer Public Key */
|
||||
cmd[12] = 0x20; /* Length: 32 bytes */
|
||||
memcpy(&cmd[13], peer_public, X25519_POINT_SIZE);
|
||||
|
||||
cmd_len = 13 + X25519_POINT_SIZE;
|
||||
cmd[4] = (uint8_t)(cmd_len - 5); /* Update LC */
|
||||
|
||||
/* Send command */
|
||||
ret = se050_i2c_write(hal, cmd, (int)cmd_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
|
||||
/* Receive response */
|
||||
rsp_len = sizeof(rsp);
|
||||
ret = se050_i2c_read(hal, rsp, (int)rsp_len);
|
||||
if (ret < 0) {
|
||||
return SE050_ERR_I2C;
|
||||
}
|
||||
rsp_len = (size_t)ret;
|
||||
|
||||
/* Check response status */
|
||||
if (rsp_len < 2) {
|
||||
return SE050_ERR_ECDH;
|
||||
}
|
||||
|
||||
uint16_t sw = (uint16_t)((rsp[rsp_len - 2] << 8) | rsp[rsp_len - 1]);
|
||||
if (sw != 0x9000) {
|
||||
return SE050_ERR_ECDH;
|
||||
}
|
||||
|
||||
/* Extract shared secret from response */
|
||||
size_t data_offset = 0;
|
||||
while (data_offset < rsp_len - 2) {
|
||||
uint8_t tag = rsp[data_offset];
|
||||
uint8_t len = rsp[data_offset + 1];
|
||||
|
||||
if (tag == 0x83) { /* Shared Secret tag */
|
||||
if (len == X25519_POINT_SIZE) {
|
||||
memcpy(shared_secret, &rsp[data_offset + 2], X25519_POINT_SIZE);
|
||||
return SE050_OK;
|
||||
}
|
||||
}
|
||||
|
||||
data_offset += 2 + len;
|
||||
}
|
||||
|
||||
return SE050_ERR_ECDH;
|
||||
}
|
||||
|
||||
se050_status_t se050_x25519_export_public_key(se050_keystore_ctx_t *keystore,
|
||||
uint32_t key_id,
|
||||
uint8_t *public_key)
|
||||
{
|
||||
uint8_t key_data[X25519_POINT_SIZE];
|
||||
size_t key_len = X25519_POINT_SIZE;
|
||||
se050_status_t status;
|
||||
|
||||
if (!keystore || !public_key) {
|
||||
return SE050_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Get public key from keystore */
|
||||
status = se050_keystore_get_public_key(keystore, key_id, key_data, &key_len);
|
||||
if (status != SE050_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Copy to output */
|
||||
secure_memcpy(public_key, key_data, X25519_POINT_SIZE);
|
||||
|
||||
/* Securely zeroize */
|
||||
memzero_explicit(key_data, sizeof(key_data));
|
||||
|
||||
return SE050_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user