Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for nitrokey 3 distinction between the secrets app firmware and the device firmware versions #43

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading