From 8297c38e1dd925a1233a67ae74e09b6a30f0186d Mon Sep 17 00:00:00 2001 From: Richard Levitte Date: Thu, 18 Sep 2008 14:29:52 +0000 Subject: [PATCH] Added common routines (shouldn't they be in a separate package, say yubico-c?) --- common/ykdef.h | 114 ++++++++++++++++++ common/ykutil.c | 141 +++++++++++++++++++++++ common/ykutil.h | 19 +++ common/yubikey.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++ common/yubikey.h | 40 +++++++ 5 files changed, 606 insertions(+) create mode 100644 common/ykdef.h create mode 100644 common/ykutil.c create mode 100644 common/ykutil.h create mode 100644 common/yubikey.c create mode 100644 common/yubikey.h diff --git a/common/ykdef.h b/common/ykdef.h new file mode 100644 index 0000000..50b7fbe --- /dev/null +++ b/common/ykdef.h @@ -0,0 +1,114 @@ +/***************************************************************************************** +** ** +** Y K D E F - Common Yubikey project header ** +** ** +** Date / Rev / Sign / Remark ** +** 06-06-03 / 0.9.0 / J E / Main ** +** 06-08-25 / 1.0.0 / J E / Rewritten for final spec ** +** 08-06-03 / 1.3.0 / J E / Added static OTP feature ** +** ** +*****************************************************************************************/ + +#ifndef __YKDEF_H_INCLUDED__ +#define __YKDEF_H_INCLUDED__ + +// Slot entries + +#define SLOT_CONFIG 1 +#define SLOT_NAV 2 + +#define SLOT_DATA_SIZE 64 + +// Ticket structure + +#define UID_SIZE 6 // Size of secret ID field + +typedef struct { + unsigned char uid[UID_SIZE]; // Unique (secret) ID + unsigned short useCtr; // Use counter (incremented by 1 at first use after power up) + usage flag in msb + unsigned short tstpl; // Timestamp incremented by approx 8Hz (low part) + unsigned char tstph; // Timestamp (high part) + unsigned char sessionCtr; // Number of times used within session. 0 for first use. After it wraps from 0xff to 1 + unsigned short rnd; // Pseudo-random value + unsigned short crc; // CRC16 value of all fields +} TICKET; + +// Activation modifier of sessionUse field (bitfields not uses as they are not portable) + +#define TICKET_ACT_HIDRPT 0x8000 // Ticket generated at activation by keyboard (scroll/num/caps) +#define TICKET_CTR_MASK 0x7fff // Mask for useCtr value (except HID flag) + +// Configuration structure + +#define FIXED_SIZE 16 // Max size of fixed field +#define KEY_SIZE 16 // Size of AES key +#define ACC_CODE_SIZE 6 // Size of access code to re-program device + +typedef struct { + unsigned char fixed[FIXED_SIZE];// Fixed data in binary format + unsigned char uid[UID_SIZE]; // Fixed UID part of ticket + unsigned char key[KEY_SIZE]; // AES key + unsigned char accCode[ACC_CODE_SIZE]; // Access code to re-program device + unsigned char fixedSize; // Number of bytes in fixed field (0 if not used) + unsigned char pgmSeq; // Program sequence number (ignored at programming - updated by firmware) + unsigned char tktFlags; // Ticket configuration flags + unsigned char cfgFlags; // General configuration flags + unsigned short ctrOffs; // Counter offset value (ignored at programming - updated by firmware) + unsigned short crc; // CRC16 value of all fields +} CONFIG; + +// Ticket flags + +#define TKTFLAG_TAB_FIRST 0x01 // Send TAB before first part +#define TKTFLAG_APPEND_TAB1 0x02 // Send TAB after first part +#define TKTFLAG_APPEND_TAB2 0x04 // Send TAB after second part +#define TKTFLAG_APPEND_DELAY1 0x08 // Add 0.5s delay after first part +#define TKTFLAG_APPEND_DELAY2 0x10 // Add 0.5s delay after second part +#define TKTFLAG_APPEND_CR 0x20 // Append CR as final character + +// Configuration flags + +#define CFGFLAG_SEND_REF 0x01 // Send reference string (0..F) before data +#define CFGFLAG_TICKET_FIRST 0x02 // Send ticket first (default is fixed part) +#define CFGFLAG_PACING_10MS 0x04 // Add 10ms intra-key pacing +#define CFGFLAG_PACING_20MS 0x08 // Add 20ms intra-key pacing +#define CFGFLAG_ALLOW_HIDTRIG 0x10 // Allow trigger through HID/keyboard +#define CFGFLAG_STATIC_TICKET 0x20 // Static ticket generation + +// Navigation + +#define MAX_URL 48 + +typedef struct { + unsigned char scancode[MAX_URL];// Scancode (lower 7 bits) + unsigned char scanmod[MAX_URL >> 2]; // Modifier fields (packed 2 bits each) + unsigned char flags; // NAVFLAG_xxx flags + unsigned char filler; // Filler byte + unsigned short crc; // CRC16 value of all fields +} NAV; + +#define SCANMOD_SHIFT 0x80 // Highest bit in scancode +#define SCANMOD_ALT_GR 0x01 // Lowest bit in mod +#define SCANMOD_WIN 0x02 // WIN key + +// Navigation flags + +#define NAVFLAG_INSERT_TRIG 0x01 // Automatic trigger when device is inserted +#define NAVFLAG_APPEND_TKT 0x02 // Append ticket to URL +#define NAVFLAG_DUAL_KEY_USAGE 0x04 // Dual usage of key: Short = ticket Long = Navigate + +// Status block + +typedef struct { + unsigned char versionMajor; // Firmware version information + unsigned char versionMinor; + unsigned char versionBuild; + unsigned char pgmSeq; // Programming sequence number. 0 if no valid configuration + unsigned short touchLevel; // Level from touch detector +} STATUS; + +// Modified hex string mapping + +#define MODHEX_MAP "cbdefghijklnrtuv" + +#endif // __YKDEF_H_INCLUDED__ diff --git a/common/ykutil.c b/common/ykutil.c new file mode 100644 index 0000000..efb39b2 --- /dev/null +++ b/common/ykutil.c @@ -0,0 +1,141 @@ +/************************************************************************* +** ** +** Y K U T I L - Yubikey utilities ** +** ** +** Copyright 2008 Yubico AB ** +** ** +** Date / Sig / Rev / History ** +** 2008-06-05 / J E / 0.00 / Main ** +** ** +*************************************************************************/ + +#include "ykutil.h" +#include "yubikey.h" +#include +#include +#include + +/************************************************************************* +** function getCRC ** +** Calculate ISO13239 checksum of buffer ** +** ** +** unsigned short getCRC(const unsigned char *buf, int bcnt) ** +** ** +** Where: ** +** "buf" is pointer to buffer ** +** "bcnt" is size of the buffer ** +** ** +** Returns: ISO13239 checksum ** +** ** +*************************************************************************/ + +unsigned short getCRC(const unsigned char *buf, int bcnt) +{ + unsigned short crc = 0xffff; + int i; + + while (bcnt--) { + crc ^= *buf++; + for (i = 0; i < 8; i++) crc = (crc & 1) ? ((crc >> 1) ^ 0x8408) : (crc >> 1); + } + + return crc; +} + +/************************************************************************* +** function modhexDecode ** +** Decodes modhex string into binary ** +** ** +** int modhexDecode(unsigned char *dst, const unsigned char *src, ** +** int dstSize) ** +** ** +** Where: ** +** "dst" is pointer to decoded binary data ** +** "src" is pointer to modhex string ** +** "dstSize" is size of the destination buffer ** +** ** +** Returns: Number of bytes decoded ** +** ** +*************************************************************************/ + +int modhexDecode(unsigned char *dst, const unsigned char *src, int dstSize) +{ + static const char trans[] = MODHEX_MAP; + unsigned char b, flag = 0; + int bcnt; + char *p1; + + for (bcnt = 0; *src && (bcnt < dstSize); src++) { + if (p1 = strchr(trans, tolower(*src))) + b = (unsigned char) (p1 - trans); + else + b = 0; + + if (flag = !flag) + *dst = b; + else { + *dst = (*dst << 4) | b; + dst++; + bcnt++; + } + } + + return bcnt; +} + +/************************************************************************* +** function parseOTP ** +** Parses OTP string and inserts result in TICKET structure ** +** ** +** int parseOTP(TICKET *tkt, unsigned char *fixed, ** +** int *fixedSize, const char *str, ** +** const char *key) ** +** ** +** Where: ** +** "tkt" is pointer to receiving TICKET structure ** +** "fixed" is pointer to receiving fixed part buffer ** +** "fixedSize" is pointer to receiving size of fixed part ** +** "str" is pointer to ascii OTP string ** +** "key" is pointer to AES key ** +** ** +** Returns: Nonzero if successful, zero otherwise ** +** ** +*************************************************************************/ + +int parseOTP(TICKET *tkt, unsigned char *fixed, int *fixedSize, const char *str, const unsigned char *key) +{ + int i, j; + unsigned char bin[FIXED_SIZE + sizeof(TICKET)]; + + // Convert from modhex to binary. Must be at least sizeof(TICKET) bytes + + if ((i = modhexDecode(bin, str, sizeof(bin))) < sizeof(TICKET)) return 0; + + // The ticket is located in the last 16 bytes + + memcpy(tkt, bin + i - sizeof(TICKET), sizeof(TICKET)); + + // Decrypt the stuff + + aesDecrypt((unsigned char *) tkt, key); + + // Is the checksum okay ? + + j = getCRC((unsigned char *) tkt, sizeof(TICKET)); + ENDIAN_SWAP(j); + if (j != CRC_OK_RESIDUE) return 0; + + // Shape up little-endian fields (if applicable) + + ENDIAN_SWAP(tkt->rnd); + ENDIAN_SWAP(tkt->tstpl); + ENDIAN_SWAP(tkt->useCtr); + + // Insert fixed id (if present) + + *fixedSize = i - sizeof(TICKET); + + if (*fixedSize) memcpy(fixed, bin, *fixedSize); + + return 1; +} diff --git a/common/ykutil.h b/common/ykutil.h new file mode 100644 index 0000000..af75ce0 --- /dev/null +++ b/common/ykutil.h @@ -0,0 +1,19 @@ +/************************************************************************* +** ** +** Y K U T I L - Yubikey utilities ** +** ** +** Copyright 2008 Yubico AB ** +** ** +** Date / Sig / Rev / History ** +** 2008-06-05 / J E / 0.00 / Main ** +** ** +*************************************************************************/ + +#include + +#define CRC_OK_RESIDUE 0xf0b8 + +extern unsigned short getCRC(const unsigned char *, int); +extern int modhexDecode(unsigned char *, const unsigned char *, int); +extern int parseOTP(TICKET *tkt, unsigned char *fixed, int *fixedSize, const char *str, const unsigned char *key); + diff --git a/common/yubikey.c b/common/yubikey.c new file mode 100644 index 0000000..8977038 --- /dev/null +++ b/common/yubikey.c @@ -0,0 +1,292 @@ +/************************************************************************* +** ** +** Y U B I K E Y - Basic LibUSB programming API for the Yubikey ** +** ** +** Copyright 2008 Yubico AB ** +** ** +** Date / Sig / Rev / History ** +** 2008-06-05 / J E / 0.00 / Main ** +** ** +*************************************************************************/ + +#include // Rename to avoid clash with windows USBxxx headers +#include "yubikey.h" +#include + +#define YUBICO_VID 0x1050 +#define YUBIKEY_PID 0x0010 + +#define HID_GET_REPORT 0x01 +#define HID_SET_REPORT 0x09 + +#define FEATURE_RPT_SIZE 8 + +#define REPORT_TYPE_FEATURE 0x03 + +/************************************************************************* +** function hidSetReport ** +** Set HID report ** +** ** +** int hidSetReport(YUBIKEY yk, int reportType, int reportNumber, ** +** char *buffer, int size) ** +** ** +** Where: ** +** "yk" is handle to open Yubikey ** +** "reportType" is HID report type (in, out or feature) ** +** "reportNumber" is report identifier ** +** "buffer" is pointer to in buffer ** +** "size" is size of the buffer ** +** ** +** Returns: Nonzero if successful, zero otherwise ** +** ** +*************************************************************************/ + +static int hidSetReport(YUBIKEY yk, int reportType, int reportNumber, char *buffer, int size) +{ + return usb_control_msg(yk, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_ENDPOINT_OUT, HID_SET_REPORT, + reportType << 8 | reportNumber, 0, buffer, size, 1000) > 0; +} + +/************************************************************************* +** function hidGetReport ** +** Get HID report ** +** ** +** int hidGetReport(YUBIKEY yk, int reportType, int reportNumber, ** +** char *buffer, int size) ** +** ** +** Where: ** +** "yk" is handle to open Yubikey ** +** "reportType" is HID report type (in, out or feature) ** +** "reportNumber" is report identifier ** +** "buffer" is pointer to in buffer ** +** "size" is size of the buffer ** +** ** +** Returns: Number of bytes read. Zero if failure ** +** ** +*************************************************************************/ + +static int hidGetReport(YUBIKEY yk, int reportType, int reportNumber, char *buffer, int size) +{ + int m = usb_claim_interface(yk, 0); + printf ("m %d: %s\n", m, usb_strerror ()); + + return usb_control_msg(yk, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_ENDPOINT_IN, HID_GET_REPORT, + reportType << 8 | reportNumber, 0, buffer, size, 1000) > 0; +} + +/************************************************************************* +** function ykInit ** +** Initiates libUsb and other stuff. Call this one first ** +** ** +** void ykInit(void) ** +** ** +*************************************************************************/ + +int ykInit(void) +{ + usb_init(); + + if (usb_find_busses()) return usb_find_devices(); + + return 0; +} + +/************************************************************************* +** function ykOpen ** +** Opens first Yubikey found for subsequent operations ** +** ** +** YUBIKEY ykOpen(void) ** +** ** +** Returns: Handle to opened Yubikey ** +** ** +*************************************************************************/ + +YUBIKEY ykOpen(void) +{ + struct usb_bus *bus; + struct usb_device *dev; + + // Find first instance of the Yubikey + + for (bus = usb_get_busses(); bus; bus = bus->next) + for (dev = bus->devices; dev; dev = dev->next) + if (dev->descriptor.idVendor == YUBICO_VID && dev->descriptor.idProduct == YUBIKEY_PID) return usb_open(dev); + + return (YUBIKEY) 0; +} + +/************************************************************************* +** function ykClose ** +** Closes open Yubikey handle ** +** ** +** void ykClose(void) ** +** ** +*************************************************************************/ + +void ykClose(YUBIKEY *yk) +{ + usb_close((usb_dev_handle *) yk); +} + +/************************************************************************* +** function ykGetStatus ** +** Read the Yubikey status structure ** +** ** +** int ykGetStatus(YUBIKEY *yk, STATUS *status, int forceUpdate) ** +** ** +** Where: ** +** "yk" is handle to open Yubikey ** +** "status" is pointer to returned status structure ** +** "forceUpdate" is set to nonzero to force update of dynamic fields ** +** ** +** Returns: Nonzero if successful, zero otherwise ** +** ** +*************************************************************************/ + +int ykGetStatus(YUBIKEY yk, STATUS *status, int forceUpdate) +{ + unsigned char buf[FEATURE_RPT_SIZE]; + + // Read status structure + + memset(buf, 0, sizeof(buf)); + + if (!hidGetReport(yk, REPORT_TYPE_FEATURE, 0, buf, FEATURE_RPT_SIZE)) return 0; + + memcpy(status, buf + 1, sizeof(STATUS)); + ENDIAN_SWAP(status->touchLevel); + + // If force update, force Yubikey to update its dynamic + // status value(s) + + if (forceUpdate) { + memset(buf, 0, sizeof(buf)); + buf[FEATURE_RPT_SIZE - 1] = 0x8a; // Invalid partition = update only + hidSetReport(yk, REPORT_TYPE_FEATURE, 0, buf, FEATURE_RPT_SIZE); + } + + return 1; +} + +/************************************************************************* +** function ykWriteSlot ** +** Writes data to Yubikey slot ** +** ** +** static int ykWriteSlot(YUBIKEY *yk, unsigned char slot, ** +** const void *buf, int bcnt) ** +** ** +** Where: ** +** "yk" is handle to open Yubikey ** +** "slot" is slot number to write to ** +** "buf" is pointer to write data buffer ** +** "bcnt" is number of bytes to write ** +** ** +** Returns: Nonzero if successful, zero otherwise ** +** ** +*************************************************************************/ + +static int ykWriteSlot(YUBIKEY *yk, unsigned char slot, const void *dt, int bcnt) +{ + unsigned char buf[FEATURE_RPT_SIZE], data[SLOT_DATA_SIZE + FEATURE_RPT_SIZE]; + int i, j, pos, part; + + // Insert data and set slot # + + memset(data, 0, sizeof(data)); + memcpy(data, dt, bcnt); + data[SLOT_DATA_SIZE] = slot; + + // Append slot checksum + + i = getCRC(data, SLOT_DATA_SIZE); + data[SLOT_DATA_SIZE + 1] = (unsigned char) (i & 0xff); + data[SLOT_DATA_SIZE + 2] = (unsigned char) (i >> 8); + + // Chop up the data into parts that fits into the payload of a + // feature report. Set the part number | 0x80 in the end + // of the feature report. When the Yubikey has processed it, + // it will clear this byte, signaling that the next part can be sent + + for (pos = 0, part = 0x80; pos < (SLOT_DATA_SIZE + 4); part++) { + + // Ignore parts that are all zeroes except first and last + // to speed up the transfer + + for (i = j = 0; i < (FEATURE_RPT_SIZE - 1); i++) if (buf[i] = data[pos++]) j = 1; + if (!j && (part > 0x80) && (pos < SLOT_DATA_SIZE)) continue; + + buf[i] = part; + + if (!hidSetReport(yk, REPORT_TYPE_FEATURE, 0, buf, FEATURE_RPT_SIZE)) return 0; + + // When the last byte in the feature report is cleared by + // the Yubikey, the next part can be sent + + for (i = 0; i < 50; i++) { + memset(buf, 0, sizeof(buf)); + if (!hidGetReport(yk, REPORT_TYPE_FEATURE, 0, buf, FEATURE_RPT_SIZE)) return 0; + if (!buf[FEATURE_RPT_SIZE - 1]) break; + sleep(10); + } + + // If timeout, something has gone wrong + + if (i >= 50) return 0; + } + + return 1; +} + +/************************************************************************* +** function ykWriteConfig ** +** Writes key config structure ** +** ** +** int ykGetStatus(YUBIKEY *yk, STATUS *status, unsigned char accCode) ** +** ** +** Where: ** +** "yk" is handle to open Yubikey ** +** "cfg" is pointer to configuration structure. NULL to zap ** +** "accCode" is current program access code. NULL if none ** +** ** +** Returns: Nonzero if successful, zero otherwise ** +** ** +*************************************************************************/ + +int ykWriteConfig(YUBIKEY *yk, CONFIG *cfg, unsigned char *accCode) +{ + unsigned char buf[sizeof(CONFIG) + ACC_CODE_SIZE]; + STATUS stat; + int seq; + + // Get current seqence # from status block + + if (!ykGetStatus(yk, &stat, 0)) return 0; + + seq = stat.pgmSeq; + + // Update checksum and insert config block in buffer if present + + memset(buf, 0, sizeof(buf)); + + if (cfg) { + cfg->crc = ~getCRC((unsigned char *) cfg, sizeof(CONFIG) - sizeof(cfg->crc)); + ENDIAN_SWAP(cfg->crc); + memcpy(buf, cfg, sizeof(CONFIG)); + } + + // Append current access code if present + + if (accCode) memcpy(buf + sizeof(CONFIG), accCode, ACC_CODE_SIZE); + + // Write to Yubikey + + if (!ykWriteSlot(yk, SLOT_CONFIG, buf, sizeof(buf))) return 0; + + // Verify update + + if (!ykGetStatus(yk, &stat, 0)) return 0; + + if (cfg) return stat.pgmSeq != seq; + + return stat.pgmSeq == 0; +} diff --git a/common/yubikey.h b/common/yubikey.h new file mode 100644 index 0000000..2631885 --- /dev/null +++ b/common/yubikey.h @@ -0,0 +1,40 @@ +/************************************************************************* +** ** +** Y U B I K E Y - Basic LibUSB programming API for the Yubikey ** +** ** +** Copyright 2008 Yubico AB ** +** ** +** Date / Sig / Rev / History ** +** 2008-06-05 / J E / 0.00 / Main ** +** ** +************************************************************************** +** +** For binary compatibility, ykdef structures must be byte-aligned +** Furthermore - define ENDIAN_SWAP appropriately +*/ + +#ifdef _WIN32 +#pragma pack(push, 1) +#endif + +#include + +#ifdef _WIN32 +#pragma pack(pop) +#endif + +#ifdef _WIN32 +// Little endian +#define ENDIAN_SWAP(x) +#else +// Big endian +#define ENDIAN_SWAP(x) x = ((x) >> 8) | ((x) << 8) +#endif + +typedef void YUBIKEY; + +extern int ykInit(void); +extern YUBIKEY * ykOpen(void); +extern void ykClose(YUBIKEY *); +extern int ykGetStatus(YUBIKEY *, STATUS *, int); +extern int ykWriteConfig(YUBIKEY *, CONFIG *, unsigned char *); -- 2.39.5