From 9e75a7b27be820e3f7a4cd3e67b781bb2139b560 Mon Sep 17 00:00:00 2001 From: Fredrik Thulin Date: Fri, 14 Jan 2011 17:22:46 +0100 Subject: [PATCH] Add support for 40 bytes (160 bits) -a. This is for OATH and HMAC challenge-response, introduced in Yubikey 2.1 and 2.2. --- libykpers-1.map | 1 + tests/test_args_to_config.c | 47 +++++++++++++++++++++++++++++--- ykpers.c | 54 ++++++++++++++++++++++++++++++------- ykpersonalize.1 | 17 +++++++----- ykpersonalize.c | 30 ++++++++++++++++++--- 5 files changed, 127 insertions(+), 22 deletions(-) diff --git a/libykpers-1.map b/libykpers-1.map index edd4d16..a3f17eb 100644 --- a/libykpers-1.map +++ b/libykpers-1.map @@ -52,6 +52,7 @@ LIBYKPERS_1.0 { ykds_version_major; ykds_version_minor; ykp_AES_key_from_hex; + ykp_AES160_key_from_hex; ykp_AES_key_from_passphrase; ykp_config_num; ykp_configure_for; diff --git a/tests/test_args_to_config.c b/tests/test_args_to_config.c index 43d85fe..88a8c08 100644 --- a/tests/test_args_to_config.c +++ b/tests/test_args_to_config.c @@ -139,7 +139,7 @@ int _test_config_slot1() assert(cfg->yk_minor_version == 3); /* verify some specific flags */ - ycfg = (struct config_st *) &cfg->ykcore_config; + ycfg = (struct config_st *) ykp_core_config(cfg); assert(ycfg->tktFlags == TKTFLAG_APPEND_CR); /* then check CRC against a known value to bulk check the rest */ @@ -147,7 +147,7 @@ int _test_config_slot1() offsetof(struct config_st, crc)); if (ycfg->crc != 0xc046) - _yktest_hexdump ("NO-MATCH :\n", ycfg, 64, 8); + _yktest_hexdump ("NO-MATCH :\n", ycfg, sizeof(*ycfg), 8); assert(ycfg->crc == 0xc046); @@ -176,7 +176,7 @@ int _test_config_static_slot2() assert(cfg->yk_minor_version == 0); /* verify some specific flags */ - ycfg = (struct config_st *) &cfg->ykcore_config; + ycfg = (struct config_st *) ykp_core_config(cfg); assert(ycfg->tktFlags == TKTFLAG_APPEND_CR); assert(ycfg->cfgFlags == CFGFLAG_STATIC_TICKET | CFGFLAG_STRONG_PW1 | CFGFLAG_STRONG_PW2 | CFGFLAG_MAN_UPDATE); @@ -185,7 +185,7 @@ int _test_config_static_slot2() offsetof(struct config_st, crc)); if (ycfg->crc != 0xf5e9) - _yktest_hexdump ("NO-MATCH :\n", ycfg, 64, 8); + _yktest_hexdump ("NO-MATCH :\n", ycfg, sizeof(*ycfg), 8); assert(ycfg->crc == 0xf5e9); @@ -284,6 +284,44 @@ int _test_non_config_args() free(st); } +int _test_oath_hotp_nist_160_bits() +{ + YKP_CONFIG *cfg = ykp_create_config(); + YK_STATUS *st = _test_init_st(2, 1, 0); + int rc = 0; + struct config_st *ycfg; + + char *argv[] = { + "unittest", "-1", "-a303132333435363738393a3b3c3d3e3f40414243", "-ooath-hotp", "-o-append-cr", + NULL + }; + int argc = sizeof argv/sizeof argv[0] - 1; + + rc = _test_config(cfg, st, argc, argv); + assert(rc == 1); + + /* verify required version for this config */ + assert(cfg->yk_major_version == 2); + assert(cfg->yk_minor_version == 1); + + /* verify some specific flags */ + ycfg = (struct config_st *) ykp_core_config(cfg); + assert(ycfg->tktFlags == TKTFLAG_OATH_HOTP); + assert(ycfg->cfgFlags == 0); + + /* then check CRC against a known value to bulk check the rest */ + ycfg->crc = ~yubikey_crc16 ((unsigned char *) ycfg, + offsetof(struct config_st, crc)); + + if (ycfg->crc != 0xb96a) + _yktest_hexdump ("NO-MATCH :\n", ycfg, sizeof(*ycfg), 8); + + assert(ycfg->crc == 0xb96a); + + ykp_free_config(cfg); + free(st); +} + int main (int argc, char **argv) { _test_config_slot1(); @@ -291,6 +329,7 @@ int main (int argc, char **argv) _test_too_old_key(); _test_too_new_key(); _test_non_config_args(); + _test_oath_hotp_nist_160_bits(); return 0; } diff --git a/ykpers.c b/ykpers.c index 132bda1..89e7f5c 100644 --- a/ykpers.c +++ b/ykpers.c @@ -126,26 +126,62 @@ int ykp_configure_for(YKP_CONFIG *cfg, int confnum, YK_STATUS *st) return 0; } +/* local helper function to check that a string contains only 0-9a-f */ +static bool is_valid_hexstr(const char *buf) +{ + int i; + for (i=0; i < strlen(buf); i++) { + char c = tolower(*(buf + i)); + /* In ASCII, 0-9 == 48-57 and a-f == 97-102 */ + if ( c<48 || (c>57 && c<97) || c>102 ) { + return false; + } + } + + return true; +} + +/* Decode 128 bit AES key into cfg->ykcore_config.key */ int ykp_AES_key_from_hex(YKP_CONFIG *cfg, const char *hexkey) { char aesbin[256]; -/* Make sure that the hexkey is exactly 32 characters */ + /* Make sure that the hexkey is exactly 32 characters */ if (strlen(hexkey) != 32) { return 1; /* Bad AES key */ } -/* Make sure that the hexkey is made up of only [0-9a-f] */ + /* Make sure that the hexkey is made up of only [0-9a-f] */ + if (! is_valid_hexstr(hexkey)) + return 1; + + yubikey_hex_decode(aesbin, hexkey, sizeof(aesbin)); + memcpy(cfg->ykcore_config.key, aesbin, sizeof(cfg->ykcore_config.key)); + + return 0; +} + +/* Decode 160 bits AES key, used with OATH and HMAC challenge-response. + * + * The first 128 bits of the AES go key into cfg->ykcore_config.key, + * and 32 bits into the first four bytes of cfg->ykcore_config.uid. +*/ +int ykp_AES160_key_from_hex(YKP_CONFIG *cfg, const char *hexkey) { + char aesbin[256]; int i; - for (i=0; i < strlen(hexkey); i++) { - char c = tolower(hexkey[i]); -/* In ASCII, 0-9 == 48-57 and a-f == 97-102 */ - if ( c<48 || (c>57 && c<97) || c>102 ) { - return 1; - } + + /* Make sure that the hexkey is exactly 40 characters */ + if (strlen(hexkey) != 40) { + return 1; /* Bad AES key */ } + /* Make sure that the hexkey is made up of only [0-9a-f] */ + if (! is_valid_hexstr(hexkey)) + return 1; + yubikey_hex_decode(aesbin, hexkey, sizeof(aesbin)); - memcpy(cfg->ykcore_config.key, aesbin, sizeof(cfg->ykcore_config.key)); + i = sizeof(cfg->ykcore_config.key); + memcpy(cfg->ykcore_config.key, aesbin, i); + memcpy(cfg->ykcore_config.uid, aesbin + i, 20 - i); return 0; } diff --git a/ykpersonalize.1 b/ykpersonalize.1 index d310d49..2db6dfa 100644 --- a/ykpersonalize.1 +++ b/ykpersonalize.1 @@ -65,7 +65,7 @@ read configuration from file. (if file is -, read from stdin) .TP \fB\-a\fIxxx\fR -A 32 char hex value (not modhex) of a fixed AES key to use. +A 32 char (40 for OATH-HOTP and HMAC challenge-response) hex value (not modhex) of a fixed AES key to use. .TP \fB\-c\fIxxx\fR A 12 char hex value (not modhex) to use as access code for @@ -202,16 +202,21 @@ When set, the first two bytes of the fixed part is sent as modhex. [\-]\fBoath-fixed-modhex\fR When set, the fixed part is sent as modhex. .SH OATH-HOTP Mode -When using OATH-HOTP mode, the key that is shared with the server -consists of the AES key plus the first four bytes (eight hex -characters) of the UID. The token identifier is defined by the fixed -prefix. +When using OATH-HOTP mode, an AES key of 160 bits (20 bytes, 40 chars of hex) +can be supplied with -a. +.PP +The token identifier can be set with the -ofixed= option. +See section "5.3.4 - OATH-HOTP Token Identifier" of the +.URL "http://static.yubico.com/var/uploads/pdfs/YubiKey_Manual_2010-09-16.pdf" "Yubikey manual" +for details, but in short the token identifier is 2 bytes manufacturer prefix, +2 character token type and then 8 bytes manufacturer unique ID. + .SH BUGS Report ykpersonalize bugs in .URL "http://code.google.com/p/yubikey-personalization/issues/list" "the issue tracker" .SH "SEE ALSO" The .URL "http://code.google.com/p/yubikey-personalization/" "ykpersonalize home page" -.br +.PP Yubikeys can be obtained from .URL "http://www.yubico.com/products/yubikey/" "Yubico" "." diff --git a/ykpersonalize.c b/ykpersonalize.c index 2cdd447..489edef 100644 --- a/ykpersonalize.c +++ b/ykpersonalize.c @@ -38,6 +38,7 @@ #include #include /* To get yubikey_modhex_encode and yubikey_hex_encode */ +#include const char *usage = "Usage: ykpersonalize [options]\n" @@ -53,8 +54,9 @@ const char *usage = " (if FILE is -, send to stdout)\n" "-iFILE read configuration from FILE.\n" " (if FILE is -, read from stdin)\n" -"-aXXX.. A 32 char hex value (not modhex) of a fixed AES key to use\n" -"-cXXX.. A 12 char hex value to use as access code for programming\n" +"-aXXX.. The AES secret key as a 32 (or 40 for OATH-HOTP/HMAC CHAL-RESP)\n" +" char hex value (not modhex)\n" +"-cXXX.. A 12 char hex value (not modhex) to use as access code for programming\n" " (this does NOT SET the access code, that's done with -oaccess=)\n" "-oOPTION change configuration option. Possible OPTION arguments are:\n" " salt=ssssssss Salt to be used when deriving key from a\n" @@ -359,7 +361,29 @@ int args_to_config(int argc, char **argv, YKP_CONFIG *cfg, } if (*aesviahash) { - if (ykp_AES_key_from_hex(cfg, aeshash)) { + int long_key_valid = false; + struct config_st *ycfg; + int res = 0; + + ycfg = (struct config_st *) ykp_core_config(cfg); + + /* for OATH-HOTP, 160 bits key is also valid */ + if ((ycfg->tktFlags & TKTFLAG_OATH_HOTP) == TKTFLAG_OATH_HOTP) + long_key_valid = true; + + /* for HMAC (not Yubico) challenge-response, 160 bits key is also valid */ + if ((ycfg->tktFlags & TKTFLAG_CHAL_RESP) == TKTFLAG_CHAL_RESP && + (ycfg->cfgFlags & CFGFLAG_CHAL_HMAC) == CFGFLAG_CHAL_HMAC) { + long_key_valid = true; + } + + if (long_key_valid && strlen(aeshash) == 40) { + res = ykp_AES160_key_from_hex(cfg, aeshash); + } else { + res = ykp_AES_key_from_hex(cfg, aeshash); + } + + if (res) { fprintf(stderr, "Bad AES key: %s\n", aeshash); fflush(stderr); return 0; -- 2.39.5