Skip to content

Commit

Permalink
Merge pull request #43 from Nitrokey/more-info
Browse files Browse the repository at this point in the history
Add support for nitrokey 3 distinction between the secrets app firmware and the device firmware versions
  • Loading branch information
sosthene-nitrokey authored Dec 9, 2024
2 parents e9050e0 + 707c654 commit 08e654b
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 41 deletions.
71 changes: 70 additions & 1 deletion src/ccid.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ IccResult parse_icc_result(uint8_t *buf, size_t buf_len) {
// .buffer_len = buf_len
};
// Make sure the response do not contain overread attempts
rassert(i.data_len < buf_len - 10);
rassert(i.data_len <= buf_len - 10);
return i;
}

Expand Down Expand Up @@ -307,6 +307,75 @@ int send_select_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_siz
return RET_NO_ERROR;
}

int send_select_nk3_admin_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_size, IccResult *iccResult) {
unsigned char cmd_select[] = {
0x6f,
0x0E,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xa4,
0x04,
0x00,
0x09,
0xa0,
0x00,
0x00,
0x08,
0x47,
0x00,
0x00,
0x00,
0x01,
};

check_ret(
ccid_process_single(handle, buf, buf_size, cmd_select, sizeof cmd_select, iccResult),
RET_COMM_ERROR);


return RET_NO_ERROR;
}

int send_select_nk3_pgp_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_size, IccResult *iccResult) {
unsigned char cmd_select[] = {
0x6f,
0x0C,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xA4,
0x04,
0x00,
0x06,
0xD2,
0x76,
0x00,
0x01,
0x24,
0x01,
0x00,
};

check_ret(
ccid_process_single(handle, buf, buf_size, cmd_select, sizeof cmd_select, iccResult),
RET_COMM_ERROR);


return RET_NO_ERROR;
}

int ccid_init(libusb_device_handle *handle) {

Expand Down
2 changes: 2 additions & 0 deletions src/ccid.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ uint32_t icc_pack_tlvs_for_sending(uint8_t *buf, size_t buflen, TLV tlvs[], int
libusb_device_handle *get_device(libusb_context *ctx, const struct VidPid pPid[], int devices_count);
int ccid_init(libusb_device_handle *handle);
int send_select_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_size, IccResult *iccResult);
int send_select_nk3_admin_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_size, IccResult *iccResult);
int send_select_nk3_pgp_ccid(libusb_device_handle *handle, uint8_t buf[], size_t buf_size, IccResult *iccResult);


enum {
Expand Down
29 changes: 13 additions & 16 deletions src/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "structs.h"
#include "utils.h"
#include <assert.h>
#include <hidapi/hidapi.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
Expand Down Expand Up @@ -259,23 +260,19 @@ int device_receive_buf(struct Device *dev) {

#include "operations_ccid.h"

int device_get_status(struct Device *dev, struct ResponseStatus *out_status) {
assert(out_status != NULL);
int device_get_status(struct Device *dev, struct FullResponseStatus *out_response) {
assert(out_response != NULL);
assert(dev != NULL);
memset(out_status, 0, sizeof(struct ResponseStatus));
memset(out_response, 0, sizeof(struct FullResponseStatus));

struct ResponseStatus *out_status = &out_response->response_status;

if (dev->connection_type == CONNECTION_CCID) {
int counter = 0;
uint32_t serial = 0;
uint16_t version = 0;
int res = status_ccid(dev->mp_devhandle_ccid,
&counter,
&version,
&serial);
out_status->retry_admin = counter;
out_status->retry_user = counter;
out_status->card_serial_u32 = serial;
out_status->firmware_version = version;
int res = status_ccid(dev->mp_devhandle_ccid, out_response);
// out_status->retry_admin = counter;
// out_status->retry_user = counter;
// out_status->card_serial_u32 = serial;
// out_status->firmware_version = version;
return res;
}

Expand All @@ -290,7 +287,7 @@ int device_get_status(struct Device *dev, struct ResponseStatus *out_status) {

device_send_buf(dev, GET_STATUS);
device_receive_buf(dev);
*out_status = *(struct ResponseStatus *) dev->packet_response.response_st.payload;
out_response->response_status = *(struct ResponseStatus *) dev->packet_response.response_st.payload;

if (out_status->firmware_version_st.minor == 1) {
for (int i = 0; i < 100; ++i) {
Expand Down Expand Up @@ -343,4 +340,4 @@ const char *command_status_to_string(uint8_t status_code) {
void clean_buffers(struct Device *dev) {
memset(dev->ccid_buffer_in, 0, sizeof dev->ccid_buffer_in);
memset(dev->ccid_buffer_out, 0, sizeof dev->ccid_buffer_out);
}
}
2 changes: 1 addition & 1 deletion src/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct Device {

int device_connect(struct Device *dev);
int device_disconnect(struct Device *dev);
int device_get_status(struct Device *dev, struct ResponseStatus *out_status);
int device_get_status(struct Device *dev, struct FullResponseStatus *out_status);
int device_send(struct Device *dev, uint8_t *in_data, size_t data_size, uint8_t command_ID);
int device_receive(struct Device *dev, uint8_t *out_data, size_t out_buffer_size);
int device_send_buf(struct Device *dev, uint8_t command_ID);
Expand Down
41 changes: 31 additions & 10 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,46 @@ int parse_cmd_and_run(int argc, char *const *argv) {
res = RET_NO_ERROR;
break;
case 'i': {// id | info
struct ResponseStatus status;
struct FullResponseStatus status;
memset(&status, 0, sizeof (struct FullResponseStatus));

res = device_get_status(&dev, &status);
check_ret((res != RET_NO_ERROR) && (res != RET_NO_PIN_ATTEMPTS), res);
if (strnlen(argv[1], 10) == 2 && argv[1][1] == 'd') {
// id command - print ID only
print_card_serial(&status);
print_card_serial(&status.response_status);
} else {
// info command - print status
printf("Connected device status:\n");
printf("\tCard serial: ");
print_card_serial(&status);
printf("\tFirmware: v%d.%d\n",
status.firmware_version_st.major,
status.firmware_version_st.minor);
if (res != RET_NO_PIN_ATTEMPTS) {
printf("\tCard counters: Admin %d, User %d\n",
status.retry_admin, status.retry_user);
print_card_serial(&status.response_status);
if (status.device_type == Nk3) {
printf("\tFirmware Nitrokey 3: v%d.%d.%d\n",
(status.nk3_extra_info.firmware_version >> 22) & 0b1111111111,
(status.nk3_extra_info.firmware_version >> 6) & 0xFFFF,
status.nk3_extra_info.firmware_version & 0b111111);
printf("\tFirmware Secrets App: v%d.%d\n",
status.response_status.firmware_version_st.major,
status.response_status.firmware_version_st.minor);
if (res != RET_NO_PIN_ATTEMPTS) {
printf("\tSecrets app PIN counter: %d\n",
status.response_status.retry_user);
} else {
printf("\tSecrets app PIN counter: PIN is not set - set PIN before the first use\n");
}
printf("\tGPG Card counters: Admin %d, User %d\n",
status.nk3_extra_info.pgp_admin_pin_retries,
status.nk3_extra_info.pgp_user_pin_retries);
} else {
printf("\tCard counters: PIN is not set - set PIN before the first use\n");
printf("\tFirmware: v%d.%d\n",
status.response_status.firmware_version_st.major,
status.response_status.firmware_version_st.minor);
if (res != RET_NO_PIN_ATTEMPTS) {
printf("\tCard counters: Admin %d, User %d\n",
status.response_status.retry_admin, status.response_status.retry_user);
} else {
printf("\tCard counters: PIN is not set - set PIN before the first use\n");
}
}
}
if (res == RET_NO_PIN_ATTEMPTS) {
Expand Down
113 changes: 101 additions & 12 deletions src/operations_ccid.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,102 @@ int verify_code_ccid(struct Device *dev, const uint32_t code_to_verify) {
return RET_VALIDATION_PASSED;
}

int status_ccid(libusb_device_handle *handle, int *attempt_counter, uint16_t *firmware_version, uint32_t *serial_number) {
int status_ccid(libusb_device_handle *handle, struct FullResponseStatus *full_response) {
rassert(full_response != NULL);
struct ResponseStatus *response = &full_response->response_status;
rassert(handle != NULL);
rassert(attempt_counter != NULL);
rassert(firmware_version != NULL);
rassert(serial_number != NULL);
uint8_t buf[1024] = {};
IccResult iccResult = {};
int r = send_select_ccid(handle, buf, sizeof buf, &iccResult);
bool pin_counter_is_error = false;
int r;
libusb_device *usb_dev;
struct libusb_device_descriptor usb_desc;

usb_dev = libusb_get_device(handle);

r = libusb_get_device_descriptor(usb_dev, &usb_desc);

if (r < 0) {
return r;
}


if (usb_desc.idVendor == NITROKEY_USB_VID || usb_desc.idProduct == NITROKEY_3_USB_PID) {
full_response->device_type = Nk3;
} else if (usb_desc.idVendor == NITROKEY_USB_VID || usb_desc.idProduct == NITROKEY_PRO_USB_PID) {
full_response->device_type = NkPro2;
} else if (usb_desc.idVendor == NITROKEY_USB_VID || usb_desc.idProduct == NITROKEY_STORAGE_USB_PID) {
full_response->device_type = NkStorage;
} else if (usb_desc.idVendor == LIBREM_KEY_USB_VID || usb_desc.idProduct == LIBREM_KEY_USB_PID) {
full_response->device_type = LibremKey;
}

if (full_response->device_type == Nk3) {
r = send_select_nk3_admin_ccid(handle, buf, sizeof buf, &iccResult);
if (r != RET_NO_ERROR) {
return r;
}

uint8_t data_iso[MAX_CCID_BUFFER_SIZE] = {};
uint32_t iso_actual_length = iso7816_compose(
data_iso, sizeof data_iso,
0x61, 0, 0, 0, 4, NULL, 0);

// encode ccid wrapper
uint32_t icc_actual_length = icc_compose(buf, sizeof buf,
0x6F, iso_actual_length,
0, 0, 0, data_iso);
int transferred;
r = ccid_send(handle, &transferred, buf, icc_actual_length);
if (r != 0) {
return r;
}

r = ccid_receive(handle, &transferred, buf, sizeof buf);
if (r != 0) {
return r;
}

IccResult iccResult = parse_icc_result(buf, transferred);
rassert(iccResult.data_status_code == 0x9000);
rassert(iccResult.data_len == 6);
full_response->nk3_extra_info.firmware_version = be32toh(*(uint32_t *) iccResult.data);
}

if (full_response->device_type == Nk3) {
r = send_select_nk3_pgp_ccid(handle, buf, sizeof buf, &iccResult);
if (r != RET_NO_ERROR) {
return r;
}

uint8_t data_iso[MAX_CCID_BUFFER_SIZE] = {};
uint32_t iso_actual_length = iso7816_compose(
data_iso, sizeof data_iso,
0xCA, 0, 0xC4, 0, 0xFF, NULL, 0);

// encode ccid wrapper
uint32_t icc_actual_length = icc_compose(buf, sizeof buf,
0x6F, iso_actual_length,
0, 0, 0, data_iso);
int transferred;
r = ccid_send(handle, &transferred, buf, icc_actual_length);
if (r != 0) {
return r;
}

r = ccid_receive(handle, &transferred, buf, sizeof buf);
if (r != 0) {
return r;
}

IccResult iccResult = parse_icc_result(buf, transferred);
rassert(iccResult.data_status_code == 0x9000);
rassert(iccResult.data_len == 9);
full_response->nk3_extra_info.pgp_user_pin_retries = iccResult.data[4];
full_response->nk3_extra_info.pgp_admin_pin_retries = iccResult.data[6];
}

r = send_select_ccid(handle, buf, sizeof buf, &iccResult);
if (r != RET_NO_ERROR) {
return r;
}
Expand All @@ -292,29 +380,30 @@ int status_ccid(libusb_device_handle *handle, int *attempt_counter, uint16_t *fi
r = get_tlv(iccResult.data, iccResult.data_len, Tag_PINCounter, &counter_tlv);
if (!(r == RET_NO_ERROR && counter_tlv.tag == Tag_PINCounter)) {
// PIN counter not found - comm error (ignore) or PIN not set
*attempt_counter = -1;
pin_counter_is_error = true;
} else {
*attempt_counter = counter_tlv.v_data[0];
response->retry_admin = counter_tlv.v_data[0];
response->retry_user = counter_tlv.v_data[0];
}

TLV serial_tlv = {};
r = get_tlv(iccResult.data, iccResult.data_len, Tag_SerialNumber, &serial_tlv);
if (r == RET_NO_ERROR && serial_tlv.tag == Tag_SerialNumber) {
*serial_number = be32toh(*(uint32_t *) serial_tlv.v_data);
response->card_serial_u32 = be32toh(*(uint32_t *) serial_tlv.v_data);
} else {
// ignore errors - unsupported or hidden serial_tlv number
*serial_number = 0;
response->card_serial_u32 = 0;
}

TLV version_tlv = {};
r = get_tlv(iccResult.data, iccResult.data_len, Tag_Version, &version_tlv);
if (!(r == RET_NO_ERROR && version_tlv.tag == Tag_Version)) {
*firmware_version = 0;
response->firmware_version = 0;
return RET_COMM_ERROR;
}
*firmware_version = be16toh(*(uint16_t *) version_tlv.v_data);
response->firmware_version = be16toh(*(uint16_t *) version_tlv.v_data);

if (*attempt_counter == -1) {
if (pin_counter_is_error == true) {
return RET_NO_PIN_ATTEMPTS;
}
return RET_NO_ERROR;
Expand Down
2 changes: 1 addition & 1 deletion src/operations_ccid.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ int authenticate_ccid(struct Device *dev, const char *admin_PIN);
int authenticate_or_set_ccid(struct Device *dev, const char *admin_PIN);
int set_secret_on_device_ccid(struct Device *dev, const char *admin_PIN, const char *OTP_secret_base32, const uint64_t hotp_counter);
int verify_code_ccid(struct Device *dev, const uint32_t code_to_verify);
int status_ccid(libusb_device_handle *handle, int *attempt_counter, uint16_t *firmware_version, uint32_t *serial_number);
int status_ccid(libusb_device_handle *handle, struct FullResponseStatus *full_response);


#endif//NITROKEY_HOTP_VERIFICATION_OPERATIONS_CCID_H
19 changes: 19 additions & 0 deletions src/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,25 @@ struct ResponseStatus {
uint8_t retry_user; /*not present in the firmware response for the Status command in v0.8 firmware*/
};

enum DeviceType {
Unknown = 0,
Nk3,
NkPro2,
NkStorage,
LibremKey,
};

struct FullResponseStatus {
enum DeviceType device_type;
struct ResponseStatus response_status;
struct {
// Only valid if device_type is NK3
uint8_t pgp_admin_pin_retries;
uint8_t pgp_user_pin_retries;
uint32_t firmware_version;
} nk3_extra_info;
};


struct WriteToOTPSlot {
//admin auth
Expand Down

0 comments on commit 08e654b

Please sign in to comment.