Skip to content

Commit

Permalink
Merge pull request #79 from canokeys/feature/static_password
Browse files Browse the repository at this point in the history
Feature/static password
  • Loading branch information
z4yx authored Feb 4, 2024
2 parents 8a47c66 + 32dd1a6 commit 2ccd945
Show file tree
Hide file tree
Showing 14 changed files with 565 additions and 124 deletions.
29 changes: 16 additions & 13 deletions applets/admin/admin.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <ndef.h>
#include <oath.h>
#include <openpgp.h>
#include <pass.h>
#include <pin.h>
#include <piv.h>

Expand All @@ -16,7 +17,7 @@

static pin_t pin = {.min_length = 6, .max_length = PIN_MAX_LENGTH, .is_validated = 0, .path = "admin-pin"};

static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1, .kbd_with_return_en = 1};
static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1};

static admin_device_config_t current_config;

Expand Down Expand Up @@ -46,14 +47,10 @@ __attribute__((weak)) int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu) {

uint8_t cfg_is_led_normally_on(void) { return current_config.led_normally_on; }

uint8_t cfg_is_kbd_interface_enable(void) { return current_config.kbd_interface_en; }

uint8_t cfg_is_ndef_enable(void) { return current_config.ndef_en; }

uint8_t cfg_is_webusb_landing_enable(void) { return current_config.webusb_landing_en; }

uint8_t cfg_is_kbd_with_return_enable(void) { return current_config.kbd_with_return_en; }

void admin_poweroff(void) { pin.is_validated = 0; }

int admin_install(const uint8_t reset) {
Expand Down Expand Up @@ -117,18 +114,12 @@ static int admin_config(const CAPDU *capdu, RAPDU *rapdu) {
case ADMIN_P1_CFG_LED_ON:
current_config.led_normally_on = P2 & 1;
break;
case ADMIN_P1_CFG_KBDIFACE:
current_config.kbd_interface_en = P2 & 1;
break;
case ADMIN_P1_CFG_NDEF:
current_config.ndef_en = P2 & 1;
break;
case ADMIN_P1_CFG_WEBUSB_LANDING:
current_config.webusb_landing_en = P2 & 1;
break;
case ADMIN_P1_CFG_KBD_WITH_RETURN:
current_config.kbd_with_return_en = P2 & 1;
break;
default:
EXCEPT(SW_WRONG_P1P2);
}
Expand All @@ -142,11 +133,11 @@ static int admin_read_config(const CAPDU *capdu, RAPDU *rapdu) {
if (LE < 5) EXCEPT(SW_WRONG_LENGTH);

RDATA[0] = current_config.led_normally_on;
RDATA[1] = current_config.kbd_interface_en;
RDATA[1] = 0; // reserved
RDATA[2] = ndef_get_read_only();
RDATA[3] = current_config.ndef_en;
RDATA[4] = current_config.webusb_landing_en;
RDATA[5] = current_config.kbd_with_return_en;
RDATA[5] = 0; // reserved
LL = 6;

return 0;
Expand Down Expand Up @@ -187,8 +178,11 @@ static int admin_factory_reset(const CAPDU *capdu, RAPDU *rapdu) {
if (ret < 0) return ret;
ret = ndef_install(1);
if (ret < 0) return ret;
ret = pass_install(1);
if (ret < 0) return ret;
ret = admin_install(1);
if (ret < 0) return ret;

return 0;
}

Expand Down Expand Up @@ -261,6 +255,9 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) {
case ADMIN_INS_TOGGLE_NDEF_READ_ONLY:
ret = ndef_toggle_read_only(capdu, rapdu);
break;
case ADMIN_INS_RESET_PASS:
ret = pass_install(1);
break;
case ADMIN_INS_RESET_CTAP:
ret = ctap_install(1);
break;
Expand All @@ -285,6 +282,12 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) {
case ADMIN_INS_READ_CONFIG:
ret = admin_read_config(capdu, rapdu);
break;
case ADMIN_INS_READ_PASS_CONFIG:
ret = pass_read_config(capdu, rapdu);
break;
case ADMIN_INS_WRITE_PASS_CONFIG:
ret = pass_write_config(capdu, rapdu);
break;
case ADMIN_INS_VENDOR_SPECIFIC:
ret = admin_vendor_specific(capdu, rapdu);
break;
Expand Down
38 changes: 8 additions & 30 deletions applets/oath/oath.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <hmac.h>
#include <memzero.h>
#include <oath.h>
#include <pass.h>
#include <rand.h>
#include <string.h>

Expand All @@ -30,8 +31,6 @@ int oath_install(const uint8_t reset) {
oath_poweroff();
if (!reset && get_file_size(OATH_FILE) >= 0) return 0;
if (write_file(OATH_FILE, NULL, 0, 0, 1) < 0) return -1;
const uint32_t default_item = 0xffffffff;
if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1;
if (write_attr(OATH_FILE, ATTR_KEY, NULL, 0) < 0) return -1;
uint8_t handle[HANDLE_LEN];
random_buffer(handle, sizeof(handle));
Expand All @@ -42,7 +41,7 @@ int oath_install(const uint8_t reset) {
static int oath_select(const CAPDU *capdu, RAPDU *rapdu) {
if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2);

memcpy(RDATA, (uint8_t[]){OATH_TAG_VERSION, 3, 0x05, 0x05, 0x05, OATH_TAG_NAME, HANDLE_LEN}, 7);
memcpy(RDATA, (uint8_t[]){OATH_TAG_VERSION, 3, 0x06, 0x00, 0x00, OATH_TAG_NAME, HANDLE_LEN}, 7);
if (read_attr(OATH_FILE, ATTR_HANDLE, RDATA + 7, HANDLE_LEN) < 0) return -1;
LL = 7 + HANDLE_LEN;

Expand Down Expand Up @@ -166,13 +165,7 @@ static int oath_delete(const CAPDU *capdu, RAPDU *rapdu) {
for (size_t i = 0; i != n_records; ++i) {
if (read_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1;
if (record.name_len == name_len && memcmp(record.name, name_ptr, name_len) == 0) {
uint32_t default_item;
if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1;
if (default_item == i) { // clear the default set if it is to be deleted
default_item = 0xffffffff;
if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1;
}

if (pass_delete_oath(i * sizeof(OATH_RECORD)) < 0) return -1;
record.name_len = 0;
return write_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD), 0);
}
Expand Down Expand Up @@ -384,7 +377,7 @@ static uint8_t *oath_digest(const OATH_RECORD *record, uint8_t buffer[SHA512_DIG
return buffer + offset;
}

static int oath_calculate_by_offset(const size_t file_offset, uint8_t result[4]) {
int oath_calculate_by_offset(size_t file_offset, uint8_t result[4]) {
if (file_offset % sizeof(OATH_RECORD) != 0) return -2;
const int size = get_file_size(OATH_FILE);
if (size < 0 || file_offset >= (size_t)size) return -2;
Expand Down Expand Up @@ -417,7 +410,8 @@ static int oath_calculate_by_offset(const size_t file_offset, uint8_t result[4])
}

static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) {
if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
if (P1 != 0x01 && P1 != 0x02) EXCEPT(SW_WRONG_P1P2);
if (P2 != 0x00 && P2 != 0x01) EXCEPT(SW_WRONG_P1P2);

uint16_t offset = 0;
if (offset + 1 >= LC) EXCEPT(SW_WRONG_LENGTH);
Expand All @@ -433,7 +427,7 @@ static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) {
if (size < 0) return -1;
const uint32_t n_records = size / sizeof(OATH_RECORD);
uint32_t i;
uint32_t file_offset;
uint32_t file_offset = 0;
OATH_RECORD record;
for (i = 0; i != n_records; ++i) {
file_offset = i * sizeof(OATH_RECORD);
Expand All @@ -443,8 +437,7 @@ static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) {
if (i == n_records) EXCEPT(SW_DATA_INVALID);
if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_TOTP) EXCEPT(SW_CONDITIONS_NOT_SATISFIED);

if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &file_offset, sizeof(file_offset)) < 0) return -1;
return 0;
return pass_update_oath(P1 -1, file_offset, record.name_len, record.name, P2);
}

static int oath_calculate(const CAPDU *capdu, RAPDU *rapdu) {
Expand Down Expand Up @@ -618,21 +611,6 @@ static int oath_send_remaining(const CAPDU *capdu, RAPDU *rapdu) {
EXCEPT(SW_CONDITIONS_NOT_SATISFIED);
}

int oath_process_one_touch(char *output, const size_t maxlen) {
uint32_t offset = 0xffffffff, otp_code;
if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &offset, sizeof(offset)) < 0) return -2;
int ret = oath_calculate_by_offset(offset, (uint8_t *)&otp_code);
if (ret < 0) return ret;
if ((size_t)(ret + 1) > maxlen) return -1;
output[ret] = '\0';
otp_code = htobe32(otp_code);
while (ret--) {
output[ret] = otp_code % 10 + '0';
otp_code /= 10;
}
return 0;
}

// ReSharper disable once CppDFAConstantFunctionResult
int oath_process_apdu(const CAPDU *capdu, RAPDU *rapdu) {
LL = 0;
Expand Down
192 changes: 192 additions & 0 deletions applets/pass/pass.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: Apache-2.0
#include <common.h>
#include <device.h>
#include <memzero.h>
#include <oath.h>
#include <pass.h>

#define PASS_FILE "pass"
#define SLOT_SHORT 0
#define SLOT_LONG 1

typedef struct {
slot_type_t type;
union {
struct {
uint8_t password_len;
uint8_t password[PASS_MAX_PASSWORD_LENGTH];
} __packed;
struct {
uint32_t oath_offset;
uint8_t name_len;
uint8_t name[MAX_NAME_LEN];
} __packed;
};
uint8_t with_enter;
} __packed pass_slot_t;

static pass_slot_t slots[2];

int pass_install(const uint8_t reset) {
if (reset || get_file_size(PASS_FILE) != sizeof(slots)) {
memzero(slots, sizeof(slots));
if (write_file(PASS_FILE, slots, 0, sizeof(slots), 1) < 0) return -1;
} else {
if (read_file(PASS_FILE, slots, 0, sizeof(slots)) < 0) return -1;
}

return 0;
}

// Dump slots to buffer, return the length of the buffer
// For each slot, the first byte is the type.
// For PASS_SLOT_OFF, there is no more data
// For PASS_SLOT_STATIC, the second byte is with_enter
// For PASS_SLOT_OATH, the next byte is the length of the name, followed by the name, and the next byte is with_enter
static int dump_slot(const pass_slot_t *slot, uint8_t *buffer) {
int length = 0;

// First byte is always the type
buffer[0] = (uint8_t)slot->type;
length++;

switch (slot->type) {
case PASS_SLOT_OFF:
break;

case PASS_SLOT_STATIC:
// For STATIC, the second byte is with_enter
buffer[length++] = slot->with_enter;
break;

case PASS_SLOT_OATH:
// For OATH, the second byte is the length of the name
buffer[length++] = slot->name_len;
// The next bytes are the name
memcpy(buffer + length, slot->name, slot->name_len);
length += slot->name_len;
// The next byte is with_enter
buffer[length++] = slot->with_enter;
break;

default:
// ERR_MSG("Invalid type %p %d\n", slot, slot->type);
return 0;
}

return length;
}

int pass_read_config(const CAPDU *capdu, RAPDU *rapdu) {
UNUSED(capdu);

int length = dump_slot(&slots[SLOT_SHORT], RDATA);
length += dump_slot(&slots[SLOT_LONG], RDATA + length);
LL = length;

return 0;
}

// P1 for the slot index, 1 for short slot, 2 for long slot
// DATA contains the slot data:
// The first byte is the slot type
// For OFF, there is no more data
// For STATIC, the second byte is the length of the password, followed by the password, and the next byte is with_enter
// OATH is not allowed to be written here
int pass_write_config(const CAPDU *capdu, RAPDU *rapdu) {
if (P1 != 1 && P1 != 2) EXCEPT(SW_WRONG_P1P2);
if (LC < 1) EXCEPT(SW_WRONG_LENGTH);

pass_slot_t *slot = &slots[P1 - 1];

switch (DATA[0]) {
case PASS_SLOT_OFF:
if (LC != 1) EXCEPT(SW_WRONG_LENGTH);
break;

case PASS_SLOT_STATIC:
if (LC < 3) EXCEPT(SW_WRONG_LENGTH);
if (DATA[1] > PASS_MAX_PASSWORD_LENGTH) EXCEPT(SW_WRONG_LENGTH);
if (LC != 3 + DATA[1]) EXCEPT(SW_WRONG_LENGTH);
slot->password_len = DATA[1];
memcpy(slot->password, DATA + 2, slot->password_len);
slot->with_enter = DATA[2 + slot->password_len];
break;

default:
EXCEPT(SW_WRONG_DATA);
}
slot->type = (slot_type_t)DATA[0];
DBG_MSG("Set type %p %d\n", slot, slot->type);

return write_file(PASS_FILE, slots, 0, sizeof(slots), 1);
}

int pass_update_oath(uint8_t slot_index, uint32_t file_offset, uint8_t name_len, const uint8_t *name, uint8_t with_enter) {
pass_slot_t *slot = &slots[slot_index];
slot->type = PASS_SLOT_OATH;
slot->oath_offset = file_offset;
slot->name_len = name_len;
memcpy(slot->name, name, name_len);
slot->with_enter = with_enter;

return write_file(PASS_FILE, slots, 0, sizeof(slots), 1);
}

int pass_delete_oath(uint32_t file_offset) {
if (slots[0].type == PASS_SLOT_OATH && slots[0].oath_offset == file_offset) {
slots[0].type = PASS_SLOT_OFF;
return write_file(PASS_FILE, slots, 0, sizeof(slots), 1);
}
if (slots[1].type == PASS_SLOT_OATH && slots[1].oath_offset == file_offset) {
slots[1].type = PASS_SLOT_OFF;
return write_file(PASS_FILE, slots, 0, sizeof(slots), 1);
}
return 0;
}

static int oath_process_offset(uint32_t file_offset, char *output) {
uint32_t otp_code;
int ret = oath_calculate_by_offset(file_offset, (uint8_t *)&otp_code);
if (ret < 0) return ret;
const int len = ret;

otp_code = htobe32(otp_code);
while (ret--) {
output[ret] = otp_code % 10 + '0';
otp_code /= 10;
}
output[len] = '\0';

return len;
}

int pass_handle_touch(uint8_t touch_type, char *output) {
pass_slot_t *slot;
if (touch_type == TOUCH_SHORT)
slot = &slots[SLOT_SHORT];
else if (touch_type == TOUCH_LONG)
slot = &slots[SLOT_LONG];
else
return -1;

int length;
switch (slot->type) {
case PASS_SLOT_OFF:
return 0;
case PASS_SLOT_OATH:
length = oath_process_offset(slot->oath_offset, output);
if (length < 0) return -1;
break;
case PASS_SLOT_STATIC:
memcpy(output, slot->password, slot->password_len);
length = slot->password_len;
break;
default:
return -1;
}

if (slot->with_enter) output[length++] = '\r';

return length;
}
Loading

0 comments on commit 2ccd945

Please sign in to comment.