smartcard: implement more file system and APDU things
This commit is contained in:
parent
d0bc7fc18f
commit
ce8a29ef35
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
|||
CFLAGS = -std=c99
|
||||
CFLAGS = -std=c11 -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-empty-translation-unit
|
||||
SRCS =
|
||||
OBJS =
|
||||
BIN = nonsenselock
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
#include <string.h>
|
||||
#include "apdu.h"
|
||||
#include "error.h"
|
||||
|
||||
|
||||
static inline void c_apdu_init(struct nsl_smartcard_c_apdu *apdu)
|
||||
{
|
||||
apdu->len = apdu->outlen = 0;
|
||||
apdu->data = NULL;
|
||||
}
|
||||
|
||||
static inline int c_apdu_read(struct nsl_smartcard_c_apdu *apdu, const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t orig_len = len;
|
||||
if (len < 4) {
|
||||
return 4;
|
||||
}
|
||||
len -= 4;
|
||||
apdu->class = *data++;
|
||||
apdu->instruction = *data++;
|
||||
apdu->params[0] = *data++;
|
||||
apdu->params[1] = *data++;
|
||||
|
||||
/* figure out the amount of length bytes */
|
||||
size_t len_len = 0;
|
||||
if (!len) {
|
||||
/* no length */
|
||||
len_len = 0;
|
||||
} else if (!*data && len >= 3) {
|
||||
/* extended length */
|
||||
data++;
|
||||
len--;
|
||||
len_len = 2;
|
||||
} else {
|
||||
/* short length */
|
||||
len_len = 1;
|
||||
}
|
||||
|
||||
if (len > len_len) {
|
||||
/* we have Lc */
|
||||
apdu->len = 0;
|
||||
for (size_t i = 0; i < len_len; i++) {
|
||||
apdu->len <<= 8;
|
||||
apdu->len |= *data++;
|
||||
}
|
||||
len -= len_len;
|
||||
|
||||
if (len < apdu->len) {
|
||||
return (orig_len - len) + apdu->len;
|
||||
}
|
||||
|
||||
apdu->data = data;
|
||||
data += apdu->len;
|
||||
len -= apdu->len;
|
||||
} else {
|
||||
/* no Lc for us */
|
||||
apdu->data = NULL;
|
||||
apdu->len = 0;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
/* we have Le */
|
||||
if (len < len_len) {
|
||||
return (orig_len - len) + len_len;
|
||||
}
|
||||
apdu->outlen = 0;
|
||||
for (size_t i = 0; i < len_len; i++) {
|
||||
apdu->outlen <<= 8;
|
||||
apdu->outlen |= *data++;
|
||||
}
|
||||
if (!apdu->outlen) {
|
||||
apdu->outlen = 1 << len_len;
|
||||
}
|
||||
len -= len_len;
|
||||
} else {
|
||||
/* no Le for us */
|
||||
apdu->outlen = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void r_apdu_init(struct nsl_smartcard_r_apdu *apdu)
|
||||
{
|
||||
apdu->status[0] = NSL_SMARTCARD_APDU_STATUS_ERROR_MISC;
|
||||
apdu->status[1] = 0;
|
||||
apdu->len = 0;
|
||||
apdu->data = NULL;
|
||||
}
|
||||
|
||||
static inline int r_apdu_write(struct nsl_smartcard_r_apdu *apdu, uint8_t *data, size_t *len)
|
||||
{
|
||||
if (*len < apdu->len + 2) {
|
||||
return apdu->len + 2;
|
||||
}
|
||||
if (apdu->data) {
|
||||
memcpy(data, apdu->data, apdu->len);
|
||||
} else {
|
||||
memset(data, 0, apdu->len);
|
||||
}
|
||||
data += apdu->len;
|
||||
*data++ = apdu->status[0];
|
||||
*data++ = apdu->status[1];
|
||||
*len = apdu->len + 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void nsl_smartcard_apdu_init(struct nsl_smartcard_apdu *apdu)
|
||||
{
|
||||
c_apdu_init(&apdu->command);
|
||||
r_apdu_init(&apdu->response);
|
||||
}
|
||||
|
||||
int nsl_smartcard_apdu_read_command(struct nsl_smartcard_apdu *apdu, const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t needed = c_apdu_read(&apdu->command, data, len);
|
||||
if (needed) {
|
||||
apdu->response.status[0] = NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_IN_LENGTH;
|
||||
apdu->response.status[1] = needed;
|
||||
apdu->response.len = 0;
|
||||
return NSL_SMARTCARD_ERROR_WRONG_LENGTH;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_apdu_write_response(struct nsl_smartcard_apdu *apdu, uint8_t *data, size_t *len)
|
||||
{
|
||||
if (apdu->response.len > apdu->command.outlen) {
|
||||
apdu->response.status[0] = NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_OUT_LENGTH;
|
||||
apdu->response.status[1] = apdu->response.len;
|
||||
apdu->response.len = 0;
|
||||
}
|
||||
size_t needed = r_apdu_write(&apdu->response, data, len);
|
||||
if (needed) {
|
||||
apdu->response.status[0] = NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_OUT_LENGTH;
|
||||
apdu->response.status[1] = needed;
|
||||
apdu->response.len = 0;
|
||||
r_apdu_write(&apdu->response, data, len);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1,21 +1,91 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct nsl_smartcard_apdu {
|
||||
uint8_t class;
|
||||
uint8_t instruction;
|
||||
uint8_t params[2];
|
||||
} __attribute__((packed));
|
||||
struct nsl_smartcard_c_apdu
|
||||
{
|
||||
uint8_t class;
|
||||
uint8_t instruction;
|
||||
uint8_t params[2];
|
||||
size_t len;
|
||||
size_t outlen;
|
||||
const uint8_t *data;
|
||||
};
|
||||
|
||||
enum nsl_smartcard_apdu_instruction{
|
||||
NSL_SMARTCARD_APDU_INS_VERIFY = 0x20,
|
||||
NSL_SMARTCARD_APDU_INS_SELECT_DIR = 0xA4,
|
||||
NSL_SMARTCARD_APDU_INS_DELETE_DIR = 0x0E,
|
||||
NSL_SMARTCARD_APDU_INS_GET_DATA = 0xCA,
|
||||
NSL_SMARTCARD_APDU_INS_CREATE_FILE = 0xE0,
|
||||
NSL_SMARTCARD_APDU_INS_UPDATE_BIN = 0xD6,
|
||||
NSL_SMARTCARD_APDU_INS_EXECUTE = 0x32,
|
||||
NSL_SMARTCARD_APDU_INS_NET_LICENSE = 0x3A,
|
||||
NSL_SMARTCARD_APDU_INS_CONTROL_PIN = 0xD4,
|
||||
struct nsl_smartcard_r_apdu
|
||||
{
|
||||
uint8_t status[2];
|
||||
size_t len;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
struct nsl_smartcard_apdu
|
||||
{
|
||||
struct nsl_smartcard_c_apdu command;
|
||||
struct nsl_smartcard_r_apdu response;
|
||||
};
|
||||
|
||||
enum nsl_smartcard_apdu_instruction {
|
||||
NSL_SMARTCARD_APDU_INS_DEACTIVATE = 0x04,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_ERASE = 0x0C,
|
||||
NSL_SMARTCARD_APDU_INS_BINARY_ERASE = 0x0E,
|
||||
NSL_SMARTCARD_APDU_INS_OP_SCOL = 0x10,
|
||||
NSL_SMARTCARD_APDU_INS_OP_TRANSACTION = 0x12,
|
||||
NSL_SMARTCARD_APDU_INS_OP_USER = 0x14,
|
||||
NSL_SMARTCARD_APDU_INS_VERIFY = 0x20,
|
||||
NSL_SMARTCARD_APDU_INS_ENV_MANAGE = 0x22,
|
||||
NSL_SMARTCARD_APDU_INS_REFDATA_CHANGE = 0x24,
|
||||
NSL_SMARTCARD_APDU_INS_VERIFY_DISABLE = 0x26,
|
||||
NSL_SMARTCARD_APDU_INS_VERIFY_ENABLE = 0x28,
|
||||
NSL_SMARTCARD_APDU_INS_OP_SECURITY = 0x2A,
|
||||
NSL_SMARTCARD_APDU_INS_RETRY_RESET = 0x2C,
|
||||
NSL_SMARTCARD_APDU_INS_ACTIVATE = 0x44,
|
||||
NSL_SMARTCARD_APDU_INS_KEYPAIR_GEN = 0x46,
|
||||
NSL_SMARTCARD_APDU_INS_SECRET_IMPORT = 0x48,
|
||||
NSL_SMARTCARD_APDU_INS_CHANNEL_MANAGE = 0x70,
|
||||
NSL_SMARTCARD_APDU_INS_AUTH_EXTERNAL = 0x82,
|
||||
NSL_SMARTCARD_APDU_INS_CHALLENGE_GET = 0x84,
|
||||
NSL_SMARTCARD_APDU_INS_AUTH_GENERAL = 0x86,
|
||||
NSL_SMARTCARD_APDU_INS_AUTH_INTERNAL = 0x88,
|
||||
NSL_SMARTCARD_APDU_INS_BINARY_SEARCH = 0xA0,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_SEARCH = 0xA2,
|
||||
NSL_SMARTCARD_APDU_INS_SELECT = 0xA4,
|
||||
NSL_SMARTCARD_APDU_INS_BINARY_READ = 0xB0,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_READ = 0xB2,
|
||||
NSL_SMARTCARD_APDU_INS_RESPONSE_GET = 0xC0,
|
||||
NSL_SMARTCARD_APDU_INS_ENVELOPE = 0xC2,
|
||||
NSL_SMARTCARD_APDU_INS_DATA_GET = 0xCA,
|
||||
NSL_SMARTCARD_APDU_INS_DATA_MANAGE = 0xCF,
|
||||
NSL_SMARTCARD_APDU_INS_BINARY_WRITE = 0xD0,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_WRITE = 0xD2,
|
||||
NSL_SMARTCARD_APDU_INS_BINARY_UPDATE = 0xD6,
|
||||
NSL_SMARTCARD_APDU_INS_DATA_PUT = 0xDA,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_UPDATE = 0xDC,
|
||||
NSL_SMARTCARD_APDU_INS_CREATE = 0xE0,
|
||||
NSL_SMARTCARD_APDU_INS_RECORD_APPEND = 0xE2,
|
||||
NSL_SMARTCARD_APDU_INS_DELETE = 0xE4,
|
||||
NSL_SMARTCARD_APDU_INS_TERMINATE = 0xE6,
|
||||
NSL_SMARTCARD_APDU_INS_FILE_TERMINATE = 0xE8,
|
||||
NSL_SMARTCARD_APDU_INS_TERMINATE_CARD = 0xEF,
|
||||
};
|
||||
|
||||
enum nsl_smartcard_apdu_status {
|
||||
NSL_SMARTCARD_APDU_STATUS_SUCCESS = 0x90,
|
||||
NSL_SMARTCARD_APDU_STATUS_MORE_DATA = 0x61,
|
||||
NSL_SMARTCARD_APDU_STATUS_WARNING = 0x62,
|
||||
NSL_SMARTCARD_APDU_STATUS_WARNING_EFFECT = 0x63,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR = 0x64,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_EFFECT = 0x65,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_SECURITY = 0x66,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_IN_LENGTH = 0x67,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_CLASS = 0x68,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_COMMAND = 0x69,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_PARAMS = 0x6A,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_PARAMS2 = 0x6B,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_BAD_OUT_LENGTH = 0x6C,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_UNK_COMMAND = 0x6D,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_UNK_CLASS = 0x6E,
|
||||
NSL_SMARTCARD_APDU_STATUS_ERROR_MISC = 0x6F,
|
||||
};
|
||||
|
||||
enum nsl_smartcard_apdu_data_tag {
|
||||
|
@ -29,3 +99,7 @@ enum nsl_smartcard_apdu_data_tag {
|
|||
NSL_SMARTCARD_APDU_DATA_METADATA = 0xA,
|
||||
NSL_SMARTCARD_APDU_DATA_CURRENT_TIME = 0xB,
|
||||
};
|
||||
|
||||
void nsl_smartcard_apdu_init(struct nsl_smartcard_apdu *);
|
||||
int nsl_smartcard_apdu_read_command(struct nsl_smartcard_apdu *, const uint8_t *data, size_t len);
|
||||
int nsl_smartcard_apdu_write_response(struct nsl_smartcard_apdu *, uint8_t *data, size_t *len);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
SRCS += \
|
||||
smartcard/apdu.c \
|
||||
smartcard/fs.c \
|
||||
smartcard/emu.c \
|
||||
smartcard/fs.o \
|
||||
smartcard/smartcard.o
|
||||
smartcard/commands.c \
|
||||
smartcard/smartcard.c
|
||||
|
||||
OBJS += \
|
||||
ext/emu8051/libem8051.a
|
||||
|
@ -9,5 +11,5 @@ OBJS += \
|
|||
ext/emu8051/libem8051.a:
|
||||
cd $(dir $@) && make CPPFLAGS=-DEM8051_MINIMAL=1 $(notdir $@)
|
||||
|
||||
CPPFLAGS += -I ext/emu8051
|
||||
CPPFLAGS += -I ext/emu8051 -DEM8051_MINIMAL=1
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ enum nsl_smartcard_error
|
|||
NSL_SMARTCARD_ERROR_WRONG_TYPE,
|
||||
NSL_SMARTCARD_ERROR_WRONG_ID,
|
||||
NSL_SMARTCARD_ERROR_WRONG_VALUE,
|
||||
NSL_SMARTCARD_ERROR_WRONG_LENGTH,
|
||||
NSL_SMARTCARD_ERROR_NOT_FOUND,
|
||||
NSL_SMARTCARD_ERROR_IN_USE,
|
||||
NSL_SMARTCARD_ERROR_UNAUTHORIZED,
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
#define NSL_SMARTCARD_DEV_PIN_DEFAULT "123456781234567812345678"
|
||||
#define NSL_SMARTCARD_PIN_LEN_REPORT_CUTOFF 16
|
||||
|
||||
static inline void init_common(struct nsl_smartcard_file *file, uint16_t id, enum nsl_smartcard_file_type type)
|
||||
static inline void init_common(struct nsl_smartcard_file *file, nsl_smartcart_file_id_t id, enum nsl_smartcard_file_type type)
|
||||
{
|
||||
file->parent = NULL;
|
||||
file->next = NULL;
|
||||
file->prev = file->next = NULL;
|
||||
file->id = id;
|
||||
file->type = type;
|
||||
file->flags = NSL_SMARTCARD_FILE_FLAG_USED;
|
||||
}
|
||||
|
||||
static inline int is_authorized(struct nsl_smartcard_file *file, enum nsl_smartcard_auth_level level)
|
||||
|
@ -22,11 +23,11 @@ static inline int is_authorized(struct nsl_smartcard_file *file, enum nsl_smartc
|
|||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return 0;
|
||||
}
|
||||
return file->contents.dir.auth >= level;
|
||||
return (file->flags & NSL_SMARTCARD_FILE_FLAG_AUTH_OVERRIDDEN) || (file->contents.dir.auth >= level);
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_file_init(struct nsl_smartcard_file *file, uint16_t id, enum nsl_smartcard_file_type type)
|
||||
int nsl_smartcard_file_init(struct nsl_smartcard_file *file, nsl_smartcart_file_id_t id, enum nsl_smartcard_file_type type)
|
||||
{
|
||||
init_common(file, id, type);
|
||||
if (!nsl_smartcard_is_file(file)) {
|
||||
|
@ -38,20 +39,21 @@ int nsl_smartcard_file_init(struct nsl_smartcard_file *file, uint16_t id, enum
|
|||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_file_read(struct nsl_smartcard_file *file, const uint8_t **data, size_t *len, enum nsl_smartcard_file_type type)
|
||||
int nsl_smartcard_file_open(struct nsl_smartcard_file *file, struct nsl_smartcard_handle *handle)
|
||||
{
|
||||
if (!nsl_smartcard_is_file(file) || file->type != type) {
|
||||
if (!nsl_smartcard_is_file(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
|
||||
switch (file->type) {
|
||||
case NSL_SMARTCARD_FILE_EXECUTABLE:
|
||||
if (!is_authorized(file, NSL_SMARTCARD_AUTH_DEV)) {
|
||||
if (!is_authorized(file->parent, NSL_SMARTCARD_AUTH_DEV)) {
|
||||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
break;
|
||||
case NSL_SMARTCARD_FILE_DATA:
|
||||
case NSL_SMARTCARD_FILE_KEY:
|
||||
if (!is_authorized(file, NSL_SMARTCARD_AUTH_INTERNAL)) {
|
||||
if (!is_authorized(file->parent, NSL_SMARTCARD_AUTH_INTERNAL)) {
|
||||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
break;
|
||||
|
@ -59,14 +61,96 @@ int nsl_smartcard_file_read(struct nsl_smartcard_file *file, const uint8_t **dat
|
|||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
*data = file->contents.file.data;
|
||||
*len = file->contents.file.len;
|
||||
handle->valid = 1;
|
||||
handle->file = file;
|
||||
handle->offset = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_file_delete(struct nsl_smartcard_file *file)
|
||||
{
|
||||
if (!nsl_smartcard_is_file(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
return nsl_smartcard_dir_delete(file->parent, file->id);
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_handle_read(struct nsl_smartcard_handle *handle, uint8_t *data, size_t *len)
|
||||
{
|
||||
if (!nsl_smartcard_handle_is_valid(handle)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
if (!len) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_VALUE;
|
||||
}
|
||||
|
||||
size_t rlen = nsl_util_min(handle->file->contents.file.len - handle->offset, *len);
|
||||
if (data) {
|
||||
memcpy(data, handle->file->contents.file.data + handle->offset, rlen);
|
||||
}
|
||||
*len = rlen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_handle_write(struct nsl_smartcard_handle *handle, const uint8_t *data, size_t *len)
|
||||
{
|
||||
if (!nsl_smartcard_handle_is_valid(handle)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
size_t rlen = nsl_util_min(handle->file->contents.file.len - handle->offset, *len);
|
||||
memcpy(handle->file->contents.file.data, data, rlen);
|
||||
*len = rlen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_handle_seek(struct nsl_smartcard_handle *handle, size_t pos, enum nsl_smartcard_origin origin)
|
||||
{
|
||||
if (!nsl_smartcard_handle_is_valid(handle)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
size_t offset;
|
||||
switch (origin) {
|
||||
case NSL_SMARTCARD_ORIGIN_START:
|
||||
offset = pos;
|
||||
break;
|
||||
case NSL_SMARTCARD_ORIGIN_CURRENT:
|
||||
offset = handle->offset + pos;
|
||||
break;
|
||||
case NSL_SMARTCARD_ORIGIN_END:
|
||||
offset = handle->file->contents.file.len + pos;
|
||||
break;
|
||||
default:
|
||||
return NSL_SMARTCARD_ERROR_WRONG_VALUE;
|
||||
}
|
||||
|
||||
handle->offset = nsl_util_min(offset, handle->file->contents.file.len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_handle_tell(struct nsl_smartcard_handle *handle, size_t *pos)
|
||||
{
|
||||
if (!nsl_smartcard_handle_is_valid(handle)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
*pos = handle->offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_handle_close(struct nsl_smartcard_handle *handle)
|
||||
{
|
||||
if (!nsl_smartcard_handle_is_valid(handle)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
handle->valid = 0;
|
||||
handle->file = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int nsl_smartcard_dir_init(struct nsl_smartcard_file *file, uint16_t id)
|
||||
int nsl_smartcard_dir_init(struct nsl_smartcard_file *file, nsl_smartcart_file_id_t id)
|
||||
{
|
||||
init_common(file, id, NSL_SMARTCARD_FILE_DIR);
|
||||
|
||||
|
@ -151,33 +235,54 @@ int nsl_smartcard_dir_change_pin(struct nsl_smartcard_file *file, const uint8_t
|
|||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_dir_internal_begin(struct nsl_smartcard_file *file)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
file->flags |= NSL_SMARTCARD_FILE_FLAG_AUTH_OVERRIDDEN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_dir_internal_end(struct nsl_smartcard_file *file)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
file->flags |= NSL_SMARTCARD_FILE_FLAG_AUTH_OVERRIDDEN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_dir_add(struct nsl_smartcard_file *file, struct nsl_smartcard_file *child)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
if (file->id == NSL_SMARTCARD_ROOT_DIR_ID) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_ID;
|
||||
}
|
||||
if (!is_authorized(file, NSL_SMARTCARD_AUTH_DEV)) {
|
||||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
if (child->parent || child->next) {
|
||||
if (child->id == NSL_SMARTCARD_ROOT_DIR_ID) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_ID;
|
||||
}
|
||||
if (child->parent || child->prev || child->next) {
|
||||
return NSL_SMARTCARD_ERROR_IN_USE;
|
||||
}
|
||||
if (file->id == child->id || (file->parent && file->parent->id == child->id)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_ID;
|
||||
}
|
||||
if (nsl_smartcard_dir_find(file, child->id, NULL)) {
|
||||
if (!nsl_smartcard_dir_find(file, child->id, NULL)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_ID;
|
||||
}
|
||||
child->parent = file;
|
||||
child->prev = NULL;
|
||||
child->next = file->contents.dir.children;
|
||||
file->contents.dir.children->prev = child;
|
||||
file->contents.dir.children = child;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_dir_find(struct nsl_smartcard_file *file, uint16_t id, struct nsl_smartcard_file **child)
|
||||
int nsl_smartcard_dir_find(struct nsl_smartcard_file *file, nsl_smartcart_file_id_t id, struct nsl_smartcard_file **child)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
|
@ -193,6 +298,38 @@ int nsl_smartcard_dir_find(struct nsl_smartcard_file *file, uint16_t id, struct
|
|||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int nsl_smartcard_dir_delete(struct nsl_smartcard_file *file, nsl_smartcart_file_id_t id)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
}
|
||||
if (!is_authorized(file, NSL_SMARTCARD_AUTH_DEV)) {
|
||||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
struct nsl_smartcard_file *child;
|
||||
if (!nsl_smartcard_dir_find(file, id, &child)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_ID;
|
||||
}
|
||||
|
||||
if (child->prev) {
|
||||
child->prev->next = child->next;
|
||||
} else {
|
||||
file->contents.dir.children = child->next;
|
||||
}
|
||||
if (child->next) {
|
||||
child->next->prev = child->prev;
|
||||
}
|
||||
|
||||
child->parent = NULL;
|
||||
child->prev = child->next = NULL;
|
||||
child->flags &= ~NSL_SMARTCARD_FILE_FLAG_USED;
|
||||
if (child->flags & NSL_SMARTCARD_FILE_FLAG_ALLOCATED) {
|
||||
free(child);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_dir_clear(struct nsl_smartcard_file *file)
|
||||
{
|
||||
if (!nsl_smartcard_is_dir(file)) {
|
||||
|
@ -202,18 +339,15 @@ int nsl_smartcard_dir_clear(struct nsl_smartcard_file *file)
|
|||
return NSL_SMARTCARD_ERROR_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
struct nsl_smartcard_file *prev = NULL, *f = file->contents.dir.children;
|
||||
struct nsl_smartcard_file *f = file->contents.dir.children, *next;
|
||||
while (f) {
|
||||
next = f->next;
|
||||
if (nsl_smartcard_is_dir(f)) {
|
||||
nsl_smartcard_dir_clear(f);
|
||||
prev = f;
|
||||
f = f->next;
|
||||
} else {
|
||||
struct nsl_smartcard_file *next = f->next;
|
||||
free(f);
|
||||
prev->next = f;
|
||||
f = next;
|
||||
nsl_smartcard_file_delete(f);
|
||||
}
|
||||
f = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
#define NSL_SMARTCARD_USER_PIN_LEN 8
|
||||
#define NSL_SMARTCARD_DEV_PIN_LEN 24
|
||||
#define NSL_SMARTCARD_ROOT_DIR_ID 0x3F00
|
||||
#define NSL_SMARTCARD_CUR_DIR_ID 0x3FFF
|
||||
|
||||
|
||||
typedef uint16_t nsl_smartcart_file_id_t;
|
||||
|
||||
enum nsl_smartcard_auth_level
|
||||
{
|
||||
NSL_SMARTCARD_AUTH_NONE,
|
||||
|
@ -24,6 +27,15 @@ enum nsl_smartcard_file_type
|
|||
NSL_SMARTCARD_FILE_MODULE,
|
||||
};
|
||||
|
||||
enum nsl_smartcard_file_flags
|
||||
{
|
||||
NSL_SMARTCARD_FILE_FLAG_ENABLED,
|
||||
NSL_SMARTCARD_FILE_FLAG_INTERNAL,
|
||||
NSL_SMARTCARD_FILE_FLAG_USED,
|
||||
NSL_SMARTCARD_FILE_FLAG_ALLOCATED,
|
||||
NSL_SMARTCARD_FILE_FLAG_AUTH_OVERRIDDEN,
|
||||
};
|
||||
|
||||
struct nsl_smartcard_file_contents
|
||||
{
|
||||
uint8_t *data;
|
||||
|
@ -35,6 +47,7 @@ struct nsl_smartcard_dir_contents
|
|||
struct nsl_smartcard_file *children;
|
||||
uint8_t user_pin[NSL_SMARTCARD_USER_PIN_LEN];
|
||||
uint8_t dev_pin[NSL_SMARTCARD_DEV_PIN_LEN];
|
||||
|
||||
enum nsl_smartcard_auth_level auth;
|
||||
uint8_t auth_attempts;
|
||||
};
|
||||
|
@ -42,11 +55,12 @@ struct nsl_smartcard_dir_contents
|
|||
struct nsl_smartcard_file
|
||||
{
|
||||
struct nsl_smartcard_file *parent;
|
||||
struct nsl_smartcard_file *next;
|
||||
struct nsl_smartcard_file *prev, *next;
|
||||
|
||||
enum nsl_smartcard_file_type type;
|
||||
int flags;
|
||||
|
||||
uint16_t id;
|
||||
nsl_smartcart_file_id_t id;
|
||||
union {
|
||||
struct nsl_smartcard_file_contents file;
|
||||
struct nsl_smartcard_dir_contents dir;
|
||||
|
@ -70,12 +84,43 @@ static inline int nsl_smartcard_is_file(struct nsl_smartcard_file *file)
|
|||
}
|
||||
}
|
||||
|
||||
int nsl_smartcard_file_init(struct nsl_smartcard_file *, uint16_t id, enum nsl_smartcard_file_type type);
|
||||
int nsl_smartcard_file_read(struct nsl_smartcard_file *, const uint8_t **data, size_t *len, enum nsl_smartcard_file_type type);
|
||||
enum nsl_smartcard_origin
|
||||
{
|
||||
NSL_SMARTCARD_ORIGIN_START,
|
||||
NSL_SMARTCARD_ORIGIN_CURRENT,
|
||||
NSL_SMARTCARD_ORIGIN_END,
|
||||
};
|
||||
|
||||
int nsl_smartcard_dir_init(struct nsl_smartcard_file *, uint16_t id);
|
||||
struct nsl_smartcard_handle
|
||||
{
|
||||
struct nsl_smartcard_file *file;
|
||||
size_t offset;
|
||||
int valid;
|
||||
};
|
||||
|
||||
|
||||
int nsl_smartcard_file_init(struct nsl_smartcard_file *, nsl_smartcart_file_id_t id, enum nsl_smartcard_file_type type);
|
||||
int nsl_smartcard_file_open(struct nsl_smartcard_file *, struct nsl_smartcard_handle *handle);
|
||||
int nsl_smartcard_file_delete(struct nsl_smartcard_file *);
|
||||
|
||||
static inline int nsl_smartcard_handle_is_valid(struct nsl_smartcard_handle *handle)
|
||||
{
|
||||
return handle->valid;
|
||||
}
|
||||
|
||||
int nsl_smartcard_handle_read(struct nsl_smartcard_handle *, uint8_t *data, size_t *len);
|
||||
int nsl_smartcard_handle_write(struct nsl_smartcard_handle *, const uint8_t *data, size_t *len);
|
||||
int nsl_smartcard_handle_seek(struct nsl_smartcard_handle *, size_t pos, enum nsl_smartcard_origin origin);
|
||||
int nsl_smartcard_handle_tell(struct nsl_smartcard_handle *, size_t *pos);
|
||||
int nsl_smartcard_handle_close(struct nsl_smartcard_handle *);
|
||||
|
||||
int nsl_smartcard_dir_init(struct nsl_smartcard_file *, nsl_smartcart_file_id_t id);
|
||||
int nsl_smartcard_dir_authorize(struct nsl_smartcard_file *, const uint8_t *pin, size_t len, enum nsl_smartcard_auth_level level);
|
||||
int nsl_smartcard_dir_change_pin(struct nsl_smartcard_file *, const uint8_t *pin, size_t len, enum nsl_smartcard_auth_level level);
|
||||
int nsl_smartcard_dir_internal_begin(struct nsl_smartcard_file *);
|
||||
int nsl_smartcard_dir_internal_end(struct nsl_smartcard_file *);
|
||||
|
||||
int nsl_smartcard_dir_add(struct nsl_smartcard_file *, struct nsl_smartcard_file *child);
|
||||
int nsl_smartcard_dir_find(struct nsl_smartcard_file *, uint16_t id, struct nsl_smartcard_file **child);
|
||||
int nsl_smartcard_dir_find(struct nsl_smartcard_file *, nsl_smartcart_file_id_t id, struct nsl_smartcard_file **child);
|
||||
int nsl_smartcard_dir_delete(struct nsl_smartcard_file *, nsl_smartcart_file_id_t id);
|
||||
int nsl_smartcard_dir_clear(struct nsl_smartcard_file *);
|
||||
|
|
|
@ -4,23 +4,24 @@
|
|||
#include "error.h"
|
||||
#include "../util.h"
|
||||
|
||||
#define NSL_SMARTCARD_DIR_SEP "\\"
|
||||
|
||||
|
||||
static int parse_file_name(const char *name, uint16_t *id)
|
||||
static void set_error(struct nsl_smartcard *smartcard, uint8_t sw1, uint8_t sw2)
|
||||
{
|
||||
uint8_t pname[2];
|
||||
if (nsl_util_fromhex(name, pname, sizeof(pname))) {
|
||||
return 0;
|
||||
}
|
||||
*id = (pname[0] << 8) | pname[1];
|
||||
return 1;
|
||||
smartcard->apdu.response.status[0] = sw1;
|
||||
smartcard->apdu.response.status[1] = sw2;
|
||||
smartcard->apdu.response.len = 0;
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_init(struct nsl_smartcard *smartcard)
|
||||
{
|
||||
smartcard->rootdir = smartcard->curdir = NULL;
|
||||
memset(smartcard->handles, 0, sizeof(smartcard->handles));
|
||||
smartcard->last_handle = 0;
|
||||
|
||||
memset(smartcard->standard_handlers, 0, sizeof(smartcard->standard_handlers));
|
||||
memset(smartcard->proprietary_handlers, 0, sizeof(smartcard->proprietary_handlers));
|
||||
|
||||
nsl_smartcard_emu_init(&smartcard->emu, 0xFFFF, 0xFFFF);
|
||||
return nsl_smartcard_reset(smartcard);
|
||||
}
|
||||
|
@ -49,12 +50,8 @@ int nsl_smartcard_change_pin(struct nsl_smartcard *smartcard, const uint8_t *pin
|
|||
}
|
||||
|
||||
|
||||
int nsl_smartcard_mkdir(struct nsl_smartcard *smartcard, const char *name)
|
||||
int nsl_smartcard_mkdir(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id)
|
||||
{
|
||||
uint16_t id;
|
||||
if (!parse_file_name(name, &id)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_VALUE;
|
||||
}
|
||||
if (!smartcard->curdir) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
@ -83,58 +80,92 @@ err:
|
|||
return err;
|
||||
}
|
||||
|
||||
int nsl_smartcard_chdir(struct nsl_smartcard *smartcard, char *name)
|
||||
int nsl_smartcard_chdir(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id)
|
||||
{
|
||||
struct nsl_smartcard_file *dir;
|
||||
if (!strcmp(name, NSL_SMARTCARD_DIR_SEP)) {
|
||||
dir = smartcard->rootdir;
|
||||
name += sizeof(NSL_SMARTCARD_DIR_SEP) - 1;
|
||||
} else {
|
||||
if (id == NSL_SMARTCARD_CUR_DIR_ID) {
|
||||
dir = smartcard->curdir;
|
||||
}
|
||||
if (!dir) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
do {
|
||||
char *seg = strsep(&name, NSL_SMARTCARD_DIR_SEP);
|
||||
uint16_t id;
|
||||
if (!parse_file_name(seg, &id)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_VALUE;
|
||||
}
|
||||
if (nsl_smartcard_dir_find(dir, id, &dir)) {
|
||||
} else if (id == NSL_SMARTCARD_ROOT_DIR_ID) {
|
||||
dir = smartcard->rootdir;
|
||||
} else {
|
||||
if (!smartcard->curdir) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
if (!nsl_smartcard_is_dir(dir)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_TYPE;
|
||||
int err = nsl_smartcard_dir_find(smartcard->curdir, id, &dir);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} while (name);
|
||||
}
|
||||
|
||||
smartcard->curdir = dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nsl_smartcard_exec(struct nsl_smartcard *smartcard, const char *name, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen)
|
||||
int nsl_smartcard_exec(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen)
|
||||
{
|
||||
uint16_t id;
|
||||
if (!parse_file_name(name, &id)) {
|
||||
return NSL_SMARTCARD_ERROR_WRONG_VALUE;
|
||||
}
|
||||
if (!smartcard->curdir) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
struct nsl_smartcard_file *file;
|
||||
if (nsl_smartcard_dir_find(smartcard->curdir, id, &file)) {
|
||||
return NSL_SMARTCARD_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
int err = nsl_smartcard_file_read(file, &data, &len, NSL_SMARTCARD_FILE_EXECUTABLE);
|
||||
struct nsl_smartcard_handle *handle;
|
||||
int err = nsl_smartcard_open(smartcard, id, &handle);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* TODO: setup VM and run. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_open(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id, struct nsl_smartcard_handle **handle)
|
||||
{
|
||||
size_t nhandles = sizeof(smartcard->handles) / sizeof(*smartcard->handles);
|
||||
size_t cur = smartcard->last_handle + 1;
|
||||
for (size_t i = 0; i < nhandles; i++, cur++) {
|
||||
cur %= nhandles;
|
||||
if (!nsl_smartcard_handle_is_valid(&smartcard->handles[cur])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct nsl_smartcard_handle *h = &smartcard->handles[cur];
|
||||
if (nsl_smartcard_handle_is_valid(h)) {
|
||||
return NSL_SMARTCARD_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
struct nsl_smartcard_file *file;
|
||||
int err = nsl_smartcard_dir_find(smartcard->curdir, id, &file);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
err = nsl_smartcard_file_open(file, h);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
smartcard->last_handle = cur;
|
||||
*handle = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int nsl_smartcard_process(struct nsl_smartcard *smartcard, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen)
|
||||
{
|
||||
int err = nsl_smartcard_apdu_read_command(&smartcard->apdu, inbuf, inlen);
|
||||
if (!err) {
|
||||
nsl_smartcard_handler_t handler;
|
||||
if (smartcard->apdu.command.class & 0x80) {
|
||||
handler = smartcard->proprietary_handlers[smartcard->apdu.command.instruction];
|
||||
} else {
|
||||
handler = smartcard->standard_handlers[smartcard->apdu.command.instruction];
|
||||
}
|
||||
if (handler) {
|
||||
err = handler(smartcard, &smartcard->apdu);
|
||||
} else {
|
||||
set_error(smartcard, NSL_SMARTCARD_APDU_STATUS_ERROR_UNK_COMMAND, 0);
|
||||
}
|
||||
}
|
||||
nsl_smartcard_apdu_write_response(&smartcard->apdu, outbuf, &outlen);
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#include "apdu.h"
|
||||
#include "fs.h"
|
||||
#include "emu.h"
|
||||
|
||||
|
||||
#define NSL_SMARTCARD_MAX_HANDLES 16
|
||||
|
||||
|
||||
struct nsl_smartcard;
|
||||
|
||||
typedef int (*nsl_smartcard_handler_t)(struct nsl_smartcard *smartcard, struct nsl_smartcard_apdu *apdu);
|
||||
|
||||
struct nsl_smartcard {
|
||||
struct nsl_smartcard_apdu apdu;
|
||||
struct nsl_smartcard_file *rootdir;
|
||||
struct nsl_smartcard_file *curdir;
|
||||
struct nsl_smartcard_emu emu;
|
||||
|
||||
nsl_smartcard_handler_t standard_handlers[0x100];
|
||||
nsl_smartcard_handler_t proprietary_handlers[0x100];
|
||||
|
||||
struct nsl_smartcard_handle handles[NSL_SMARTCARD_MAX_HANDLES];
|
||||
uint8_t last_handle;
|
||||
};
|
||||
|
||||
int nsl_smartcard_init(struct nsl_smartcard *smartcard);
|
||||
|
@ -15,8 +31,9 @@ int nsl_smartcard_reset(struct nsl_smartcard *smartcard);
|
|||
int nsl_smartcard_authorize(struct nsl_smartcard *smartcard, const uint8_t *pin, size_t len, enum nsl_smartcard_auth_level level);
|
||||
int nsl_smartcard_change_pin(struct nsl_smartcard *smartcard, const uint8_t *pin, size_t len, enum nsl_smartcard_auth_level level);
|
||||
|
||||
int nsl_smartcard_mkdir(struct nsl_smartcard *smartcard, const char *dir);
|
||||
int nsl_smartcard_chdir(struct nsl_smartcard *smartcard, char *name);
|
||||
int nsl_smartcard_exec(struct nsl_smartcard *smarcard, const char *name, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen);
|
||||
int nsl_smartcard_mkdir(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id);
|
||||
int nsl_smartcard_chdir(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id);
|
||||
int nsl_smartcard_exec(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen);
|
||||
int nsl_smartcard_open(struct nsl_smartcard *smartcard, nsl_smartcart_file_id_t id, struct nsl_smartcard_handle **handle);
|
||||
|
||||
int nsl_smartcard_process(struct nsl_smartcard *smartcard, const uint8_t *inbuf, size_t inlen, uint8_t *outbuf, size_t outlen);
|
||||
|
|
47
src/util.h
47
src/util.h
|
@ -5,28 +5,37 @@ int nsl_util_memcmp(const uint8_t *left, const uint8_t *right, size_t len);
|
|||
|
||||
static inline int nsl_util_fromhexdigit(int c)
|
||||
{
|
||||
c = tolower(c);
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return 10 + (c - 'a');
|
||||
}
|
||||
return -1;
|
||||
c = tolower(c);
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return 10 + (c - 'a');
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int nsl_util_fromhex(const char *src, unsigned char *dest, size_t len)
|
||||
{
|
||||
while (*src && len--) {
|
||||
unsigned char a = nsl_util_fromhexdigit(*src++);
|
||||
if (a < 0) {
|
||||
return 0;
|
||||
while (*src && len--) {
|
||||
unsigned char a = nsl_util_fromhexdigit(*src++);
|
||||
if (a < 0) {
|
||||
return 0;
|
||||
}
|
||||
unsigned char b = nsl_util_fromhexdigit(*src++);
|
||||
if (b < 0) {
|
||||
return 0;
|
||||
}
|
||||
*dest++ = (a << 4) | b;
|
||||
}
|
||||
return !*src && !len;
|
||||
}
|
||||
|
||||
static inline int nsl_util_min(size_t a, size_t b)
|
||||
{
|
||||
if (a > b) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
unsigned char b = nsl_util_fromhexdigit(*src++);
|
||||
if (b < 0) {
|
||||
return 0;
|
||||
}
|
||||
*dest++ = (a << 4) | b;
|
||||
}
|
||||
return !*src && !len;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue