This required implementing multi-feature-report reads in ykcore.c.
It is kind of wrong to have these functions in libykpers, but that's
where they are at the moment. We might split some part of libykpers
out into a new library before releasing anything official. This is
to be considered work in progress.
# The command line tools.
-bin_PROGRAMS = ykpersonalize
+bin_PROGRAMS = ykpersonalize ykchalresp
ykpersonalize_SOURCES = ykpersonalize.c
ykpersonalize_LDADD = ./libykpers-1.la ./ykcore/libykcore.la
+ykchalresp_SOURCES = ykchalresp.c
+ykchalresp_LDADD = ./libykpers-1.la ./ykcore/libykcore.la
+
dist_man1_MANS = ykpersonalize.1
# Dist docs from wiki.
** When generating keys from passphrase, generate 160 bit keys for modes
that support it (OATH-HOTP and HMAC challenge response).
+** An additional program to use challenge-response was added.
* Version 1.4.1 (released 2011-01-19)
** Restore OATH-HOTP symbols for LIBYKPERS_1.0.
Accidentally moved to LIBYKPERS_1.4 in 1.4.0.
# Interfaces changed/added/removed: CURRENT++ REVISION=0
# Interfaces added: AGE++
# Interfaces removed: AGE=0
-AC_SUBST(LT_CURRENT, 4)
-AC_SUBST(LT_REVISION, 1)
-AC_SUBST(LT_AGE, 3)
+AC_SUBST(LT_CURRENT, 5)
+AC_SUBST(LT_REVISION, 0)
+AC_SUBST(LT_AGE, 4)
AM_INIT_AUTOMAKE([-Wall -Werror])
AC_PROG_CC
ykp_set_extflag_SERIAL_API_VISIBLE;
# Variables:
} LIBYKPERS_1.0;
+
+LIBYKPERS_1.5 {
+ global:
+# Functions:
+ yk_wait_for_key_status;
+ yk_read_response_from_key;
+# Variables:
+} LIBYKPERS_1.4;
--- /dev/null
+/* -*- mode:C; c-file-style: "bsd" -*- */
+/*
+ * Copyright (c) 2011 Yubico AB.
+ * All rights reserved.
+ *
+ * Author : Fredrik Thulin <fredrik@yubico.com>
+ *
+ * Some basic code copied from ykpersonalize.c.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <ykpers.h>
+#include <yubikey.h>
+#include <ykdef.h>
+
+const char *usage =
+ "Usage: ykchalresp [options] challenge\n"
+ "\n"
+ "Options :\n"
+ "\n"
+ "\t-1 Send challenge to slot 1. This is the default.\n"
+ "\t-2 Send challenge to slot 2.\n"
+ "\t-H Send a 64 byte HMAC challenge. This is the default.\n"
+ "\t-Y Send a 6 byte Yubico challenge.\n"
+ "\t-N Abort if Yubikey requires button press.\n"
+ "\t-x Challenge is hex encoded.\n"
+ "\n"
+ "\t-v verbose\n"
+ "\t-h help (this text)\n"
+ "\n"
+ "\n"
+ ;
+const char *optstring = "12xvhHYN";
+
+static void report_yk_error()
+{
+ if (ykp_errno)
+ fprintf(stderr, "Yubikey personalization error: %s\n",
+ ykp_strerror(ykp_errno));
+ if (yk_errno) {
+ if (yk_errno == YK_EUSBERR) {
+ fprintf(stderr, "USB error: %s\n",
+ yk_usb_strerror());
+ } else {
+ fprintf(stderr, "Yubikey core error: %s\n",
+ yk_strerror(yk_errno));
+ }
+ }
+}
+
+int parse_args(int argc, char **argv,
+ int *slot, bool *verbose,
+ unsigned char **challenge, unsigned int *challenge_len,
+ bool *hmac, bool *may_block,
+ int *exit_code)
+{
+ char c;
+ bool hex_encoded = false;
+
+ while((c = getopt(argc, argv, optstring)) != -1) {
+ switch (c) {
+ case '1':
+ *slot = 1;
+ break;
+ case '2':
+ *slot = 2;
+ break;
+ case 'H':
+ *hmac = true;
+ break;
+ case 'N':
+ *may_block = false;
+ break;
+ case 'Y':
+ *hmac = false;
+ break;
+ case 'x':
+ hex_encoded = true;
+ break;
+ case 'v':
+ *verbose = true;
+ break;
+ case 'h':
+ default:
+ fputs(usage, stderr);
+ *exit_code = 0;
+ return 0;
+ }
+ }
+
+ if (optind >= argc) {
+ /* No challenge */
+ fputs(usage, stderr);
+ return 0;
+ }
+
+ if (hex_encoded) {
+ static unsigned char decoded[64];
+ int decoded_len;
+
+ int strl = strlen(argv[optind]);
+
+ if (strl >= sizeof(decoded) * 2) {
+ fprintf(stderr, "Hex-encoded challenge too long (max %i chars)\n",
+ sizeof(decoded) * 2);
+ return 0;
+ }
+
+ if (strl % 2 != 0) {
+ fprintf(stderr, "Odd number of characters in hex-encoded challenge\n");
+ return 0;
+ }
+
+ memset(decoded, 0, sizeof(decoded));
+
+ if (yubikey_hex_p(argv[optind])) {
+ yubikey_hex_decode((char *)decoded, argv[optind], strl);
+ } else {
+ fprintf(stderr, "Bad hex-encoded string '%s'\n", argv[optind]);
+ return 0;
+ }
+ *challenge = (unsigned char *) &decoded;
+ *challenge_len = strl / 2;
+ } else {
+ *challenge = argv[optind];
+ *challenge_len = strlen(*challenge);
+ }
+
+ return 1;
+}
+
+int check_firmware(YK_KEY *yk, bool verbose)
+{
+ YK_STATUS *st = ykds_alloc();
+
+ if (!yk_get_status(yk, st)) {
+ free(st);
+ return 0;
+ }
+
+ if (verbose) {
+ printf("Firmware version %d.%d.%d\n",
+ ykds_version_major(st),
+ ykds_version_minor(st),
+ ykds_version_build(st));
+ fflush(stdout);
+ }
+
+ if (ykds_version_major(st) < 2 ||
+ ykds_version_minor(st) < 2) {
+ fprintf(stderr, "Challenge-response not supported before YubiKey 2.2.\n");
+ free(st);
+ return 0;
+ }
+
+ free(st);
+ return 1;
+}
+
+int challenge_response(YK_KEY *yk, int slot,
+ unsigned char *challenge, unsigned int len,
+ bool hmac, bool may_block, bool verbose)
+{
+ unsigned char response[64];
+ unsigned char output_buf[sizeof(response) * 2];
+ int yk_cmd;
+ unsigned int flags = 0;
+ unsigned int response_len = 0;
+ unsigned int expect_bytes = 0;
+
+ memset(response, 0, sizeof(response));
+
+ if (may_block)
+ flags |= YK_FLAG_MAYBLOCK;
+
+ if (verbose) {
+ fprintf(stderr, "Sending %i bytes %s challenge to slot %i\n", len, (hmac == true)?"HMAC":"Yubico", slot);
+ //_yk_hexdump(challenge, len);
+ }
+
+ switch(slot) {
+ case 1:
+ yk_cmd = (hmac == true) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_OTP1;
+ break;
+ case 2:
+ yk_cmd = (hmac == true) ? SLOT_CHAL_HMAC2 : SLOT_CHAL_OTP2;
+ break;
+ }
+
+ if (!yk_write_to_key(yk, yk_cmd, challenge, len))
+ return 0;
+
+ if (verbose) {
+ fprintf(stderr, "Reading response...\n");
+ }
+
+ /* HMAC responses are 160 bits, Yubico 128 */
+ expect_bytes = (hmac == true) ? 20 : 16;
+
+ if (! yk_read_response_from_key(yk, slot, flags,
+ &response, sizeof(response),
+ expect_bytes,
+ &response_len))
+ return 0;
+
+ if (hmac && response_len > 20)
+ response_len = 20;
+ if (! hmac && response_len > 16)
+ response_len = 16;
+
+ memset(output_buf, 0, sizeof(output_buf));
+ if (hmac) {
+ yubikey_hex_encode(output_buf, (char *)response, response_len);
+ } else {
+ yubikey_modhex_encode(output_buf, (char *)response, response_len);
+ }
+ printf("%s\n", output_buf);
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ YK_KEY *yk = 0;
+ bool error = true;
+ int exit_code = 0;
+
+ /* Options */
+ bool verbose = false;
+ bool hex_encoded = false;
+ bool hmac = true;
+ bool may_block = true;
+ unsigned char *challenge;
+ unsigned int challenge_len;
+ int slot = 1;
+
+ ykp_errno = 0;
+ yk_errno = 0;
+
+ if (! parse_args(argc, argv,
+ &slot, &verbose,
+ &challenge, &challenge_len,
+ &hmac, &may_block,
+ &exit_code))
+ goto err;
+
+ if (!yk_init()) {
+ exit_code = 1;
+ goto err;
+ }
+
+ if (!(yk = yk_open_first_key())) {
+ exit_code = 1;
+ goto err;
+ }
+
+ if (! check_firmware(yk, verbose)) {
+ exit_code = 1;
+ goto err;
+ }
+
+ if (! challenge_response(yk, slot,
+ challenge, challenge_len,
+ hmac, may_block, verbose)) {
+ exit_code = 1;
+ goto err;
+ }
+
+ exit_code = 0;
+ error = false;
+
+err:
+ if (error || exit_code != 0) {
+ report_yk_error();
+ }
+
+ if (yk && !yk_close_key(yk)) {
+ report_yk_error();
+ exit_code = 2;
+ }
+
+ if (!yk_release()) {
+ report_yk_error();
+ exit_code = 2;
+ }
+
+ exit(exit_code);
+}
/* To get modhex and crc16 */
#include <yubikey.h>
+#include <stdio.h>
#ifndef _WIN32
#include <unistd.h>
#define Sleep(x) usleep((x)*1000)
"unsupported firmware version",
"out of memory",
"no status structure given",
- "not yet implemented"
+ "not yet implemented",
+ "checksum mismatch",
+ "operation would block"
};
const char *yk_strerror(int errnum)
{
return _ykusb_strerror();
}
-/* Note: we currently have no idea whatsoever how to read things larger
- than FEATURE_RPT_SIZE - 1. We also have no idea what to do with the
- slot parameter, it currently is there for future purposes only. */
+/* This function would've been better named 'yk_read_status_from_key'. Because
+ * it disregards the first byte in each feature report, it can't be used to read
+ * generic feature reports from the Yubikey, and this behaviour can't be changed
+ * without breaking compatibility with existing programs.
+ *
+ * See yk_read_response_from_key() for a generic purpose data reading function.
+ *
+ * The slot parameter is here for future purposes only.
+ */
int yk_read_from_key(YK_KEY *yk, uint8_t slot,
void *buf, unsigned int bufsize, unsigned int *bufcount)
{
/* This makes it apparent that there's some mysterious value in
the first byte... I wonder what... /Richard Levitte */
- memcpy(buf, data + 1, bufsize);
+ memcpy(buf, data + 1, bufsize);
*bufcount = bufsize;
return 1;
}
+/* Wait for the Yubikey to either set or clear (controlled by the boolean logic_and)
+ * the bits in mask.
+ *
+ * The slot parameter is here for future purposes only.
+ */
+int yk_wait_for_key_status(YK_KEY *yk, uint8_t slot, unsigned int flags,
+ unsigned int max_time_ms,
+ bool logic_and, unsigned char mask,
+ unsigned char *last_data)
+{
+ unsigned char data[FEATURE_RPT_SIZE];
+ unsigned int bytes_read;
+
+ int sleepval = 10;
+ int slept_time = 0;
+ int blocking = 0;
+
+ while (slept_time < max_time_ms) {
+ /* Read a status report from the key */
+ memset(data, 0, sizeof(data));
+ if (!_ykusb_read(yk, REPORT_TYPE_FEATURE, slot, (char *) &data, FEATURE_RPT_SIZE))
+ return 0;
+
+ if (last_data != NULL)
+ memcpy(last_data, data, sizeof(data));
+
+ /* The status byte from the key is now in last byte of data */
+ if (logic_and) {
+ /* Check if Yubikey has SET the bit(s) in mask */
+ if ((data[FEATURE_RPT_SIZE - 1] & mask) == mask) {
+ return 1;
+ }
+ } else {
+ /* Check if Yubikey has CLEARED the bit(s) in mask */
+ if (! (data[FEATURE_RPT_SIZE - 1] & mask)) {
+ return 1;
+ }
+ }
+
+ /* Check if Yubikey says it will wait for user interaction */
+ if ((data[FEATURE_RPT_SIZE - 1] & RESP_TIMEOUT_WAIT_FLAG) == RESP_TIMEOUT_WAIT_FLAG) {
+ if ((flags & YK_FLAG_MAYBLOCK) == YK_FLAG_MAYBLOCK) {
+ if (! blocking) {
+ /* Extend timeout first time we see RESP_TIMEOUT_WAIT_FLAG. */
+ blocking = 1;
+ max_time_ms += 15000;
+ }
+ } else {
+ /* Reset read mode of Yubikey before aborting. */
+ yk_force_key_update(yk);
+ yk_errno = YK_EWOULDBLOCK;
+ return 0;
+ }
+ } else {
+ if (blocking) {
+ /* YubiKey timed out waiting for user interaction */
+ break;
+ }
+ }
+
+ Sleep(sleepval);
+ slept_time += sleepval;
+ /* exponential backoff, up to 500 ms */
+ sleepval *= 2;
+ if (sleepval > 500)
+ sleepval = 500;
+ }
+
+ yk_errno = YK_ETIMEOUT;
+ return 0;
+}
+
+/* Read one or more feature reports from a Yubikey and put them together.
+ *
+ * Bufsize must be able to hold at least 2 more bytes than you are expecting
+ * (the CRC), but since all read requests return 7 bytes of data bufsize needs
+ * to be up to 7 bytes more than you expect.
+ *
+ * If the key returns more data than bufsize, we fail and set yk_errno to
+ * YK_EWRONGSIZ. If that happens there will be partial data in buf.
+ *
+ * If we read a response from a Yubikey that is configured to block and wait for
+ * a button press (in challenge response), this function will abort unless
+ * flags contain YK_FLAG_MAYBLOCK, in which case it might take up to 15 seconds
+ * for this function to return.
+ *
+ * The slot parameter is here for future purposes only.
+ */
+int yk_read_response_from_key(YK_KEY *yk, uint8_t slot, unsigned int flags,
+ void *buf, unsigned int bufsize, unsigned int expect_bytes,
+ unsigned int *bytes_read)
+{
+ unsigned char data[FEATURE_RPT_SIZE];
+ memset(data, 0, sizeof(data));
+
+ memset(buf, 0, bufsize);
+ *bytes_read = 0;
+
+ /* Wait for the key to turn on RESP_PENDING_FLAG */
+ if (! yk_wait_for_key_status(yk, slot, flags, 1000, true, RESP_PENDING_FLAG, (char *) &data))
+ return 0;
+
+ /* The first part of the response was read by yk_wait_for_key_status(). We need
+ * to copy it to buf.
+ */
+ memcpy(buf + *bytes_read, data, sizeof(data) - 1);
+ *bytes_read += sizeof(data) - 1;
+
+ while (*bytes_read + FEATURE_RPT_SIZE <= bufsize) {
+ memset(data, 0, sizeof(data));
+
+ if (!_ykusb_read(yk, REPORT_TYPE_FEATURE, 0, (char *)data, FEATURE_RPT_SIZE))
+ return 0;
+
+ if (data[FEATURE_RPT_SIZE - 1] & RESP_PENDING_FLAG) {
+ /* The lower five bits of the status byte has the response sequence
+ * number. If that gets reset to zero we are done.
+ */
+ if ((data[FEATURE_RPT_SIZE - 1] & 31) == 0) {
+ if (expect_bytes > 0) {
+ /* Size of response is known. Verify CRC. */
+ int crc = yubikey_crc16(buf, expect_bytes + 2);
+ if (crc != YK_CRC_OK_RESIDUAL) {
+ yk_errno = YK_ECHECKSUM;
+ return 0;
+ }
+ }
+
+ /* Reset read mode of Yubikey before returning. */
+ yk_force_key_update(yk);
+
+ return 1;
+ }
+
+ memcpy(buf + *bytes_read, data, sizeof(data) - 1);
+ *bytes_read += sizeof(data) - 1;
+ } else {
+ /* Reset read mode of Yubikey before returning. */
+ yk_force_key_update(yk);
+
+ return 0;
+ }
+ }
+
+ /* We're out of buffer space, abort reading */
+ yk_force_key_update(yk);
+
+ yk_errno = YK_EWRONGSIZ;
+ return 0;
+}
+
int yk_write_to_key(YK_KEY *yk, uint8_t slot, const void *buf, int bufcount)
{
YK_FRAME frame;
ptr = (unsigned char *) &frame;
end = (unsigned char *) &frame + sizeof(frame);
+ /* Initial check that the YubiKey is in a state where it will accept
+ * a write.
+ *
+ if (! yk_wait_for_key_status(yk, slot, 0, 1000, false, SLOT_WRITE_FLAG, NULL))
+ return 0;
+ */
for (seq = 0; ptr < end; seq++) {
int all_zeros = 1;
/* Ignore parts that are all zeroes except first and last
/* sequence number goes into lower bits of last byte */
repbuf[i] = seq | SLOT_WRITE_FLAG;
- if (!_ykusb_write(yk, REPORT_TYPE_FEATURE, 0,
- (char *)repbuf, FEATURE_RPT_SIZE))
- return 0;
-
/* When the Yubikey clears the SLOT_WRITE_FLAG, the
- next part can be sent */
-
- for (i = 0; i < 50; i++) {
- memset(repbuf, 0, sizeof(repbuf));
- if (!_ykusb_read(yk, REPORT_TYPE_FEATURE, 0,
- (char *)repbuf, FEATURE_RPT_SIZE))
- return 0;
- if (! (repbuf[FEATURE_RPT_SIZE - 1] & SLOT_WRITE_FLAG))
- break;
- Sleep(10);
- }
-
- /* If timeout, something has gone wrong */
+ * next part can be sent. Yubikey low-level interface
+ * section 2.4 (Report arbitration polling) specifies
+ * a 600 ms timeout for this operation.
+ */
+ if (! yk_wait_for_key_status(yk, slot, 0, 600, false, SLOT_WRITE_FLAG, NULL))
+ return 0;
- if (i >= 50) {
- yk_errno = YK_ETIMEOUT;
+ if (!_ykusb_write(yk, REPORT_TYPE_FEATURE, 0,
+ (char *)repbuf, FEATURE_RPT_SIZE))
return 0;
- }
}
return 1;
return x;
}
+/* Private little hexdump function for debugging */
+void _yk_hexdump(void *buffer, int size)
+{
+ unsigned char *p = buffer;
+ int i;
+ for (i = 0; i < size; i++) {
+ fprintf(stderr, "\\x%02x", *p);
+ p++;
+ }
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
#define YK_ENOKEY 0x05
#define YK_EFIRMWARE 0x06
#define YK_ENOMEM 0x07
-#define YK_ENOSTATUS 0x07
-#define YK_ENOTYETIMPL 0x08
+#define YK_ENOSTATUS 0x08
+#define YK_ENOTYETIMPL 0x09
+#define YK_ECHECKSUM 0x0a /* checksum validation failed */
+#define YK_EWOULDBLOCK 0x0b /* operation would block */
+
+/* Flags for response reading. Use high numbers to not exclude the possibility
+ * to combine these with for example SLOT commands from ykdef.h in the future.
+ */
+#define YK_FLAG_MAYBLOCK 0x01 << 16
+
+#define YK_CRC_OK_RESIDUAL 0xf0b8
#endif /* __YKCORE_H_INCLUDED__ */