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;
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 */
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);
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);
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);
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();
_test_too_old_key();
_test_too_new_key();
_test_non_config_args();
+ _test_oath_hotp_nist_160_bits();
return 0;
}
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;
}
(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
[\-]\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" "."
#include <ykpers.h>
#include <yubikey.h> /* To get yubikey_modhex_encode and yubikey_hex_encode */
+#include <ykdef.h>
const char *usage =
"Usage: ykpersonalize [options]\n"
" (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"
}
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;