From fb6b0911fdf9ad3de0279f80f03adcefdc2d81d4 Mon Sep 17 00:00:00 2001 From: Klas Lindfors Date: Wed, 18 Sep 2013 09:58:47 +0200 Subject: [PATCH] use pbkdf2 to process the exepected response this bumps the version on the state file to 2 old files can still be read but new files will use the new format --- pam_yubico.c | 10 ++++++++-- util.c | 54 +++++++++++++++++++++++++++++++++++++++++++++------- util.h | 6 ++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index 2a80fda..d6f55cb 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -47,6 +47,7 @@ #if HAVE_CR /* for yubikey_hex_decode and yubikey_hex_p */ #include +#include #endif /* HAVE_CR */ /* Libtool defines PIC for shared objects */ @@ -553,8 +554,13 @@ do_challenge_response(pam_handle_t *pamh, struct cfg *cfg, const char *username) */ yubikey_hex_encode(response_hex, buf, response_len); + if(state.salt_len > 0) { // the expected response has gone through pbkdf2 + YK_PRF_METHOD prf_method = {20, yk_hmac_sha1}; + yk_pbkdf2(response_hex, state.salt, state.salt_len, state.iterations, + buf, response_len, &prf_method); + } - if (memcmp(buf, state.response, response_len) == 0) { + if (memcmp(buf, state.response, state.response_len) == 0) { ret = PAM_SUCCESS; } else { DBG(("Unexpected C/R response : %s", response_hex)); @@ -579,7 +585,7 @@ do_challenge_response(pam_handle_t *pamh, struct cfg *cfg, const char *username) /* There is a bug that makes the YubiKey 2.2 send the same response for all challenges unless HMAC_LT64 is set, check for that here */ - if (memcmp(buf, state.response, response_len) == 0) { + if (memcmp(buf, state.response, state.response_len) == 0) { errstr = "Same response for second challenge, YubiKey should be reconfigured with the option HMAC_LT64"; goto out; } diff --git a/util.c b/util.c index 2d25452..3066706 100644 --- a/util.c +++ b/util.c @@ -43,6 +43,7 @@ #if HAVE_CR /* for yubikey_hex_decode and yubikey_hex_p */ #include +#include #endif /* HAVE_CR */ int @@ -237,6 +238,8 @@ load_chalresp_state(FILE *f, CR_STATE *state, bool verbose) * Format is hex(challenge):hex(response):slot num */ char challenge_hex[CR_CHALLENGE_SIZE * 2 + 1], response_hex[CR_RESPONSE_SIZE * 2 + 1]; + char salt_hex[CR_SALT_SIZE * 2 + 1]; + unsigned int iterations; int slot; int r; @@ -248,14 +251,34 @@ load_chalresp_state(FILE *f, CR_STATE *state, bool verbose) * 40 is twice the size of CR_RESPONSE_SIZE * (twice because we hex encode the challenge and response) */ - r = fscanf(f, "v1:%126[0-9a-z]:%40[0-9a-z]:%d", &challenge_hex[0], &response_hex[0], &slot); - if (r != 3) { - D(("Could not parse contents of chalresp_state file (%i)", r)); - goto out; + r = fscanf(f, "v2:%126[0-9a-z]:%40[0-9a-z]:%64[0-9a-z]:%d:%d", challenge_hex, response_hex, salt_hex, &iterations, &slot); + if(r == 5) { + if (! yubikey_hex_p(salt_hex)) { + D(("Invalid salt hex input : %s", salt_hex)); + goto out; + } + + if(verbose) { + D(("Challenge: %s, hashed response: %s, salt: %s, iterations: %d, slot: %d", + challenge_hex, response_hex, salt_hex, iterations, slot)); + } + + yubikey_hex_decode(state->salt, salt_hex, sizeof(state->challenge)); + state->salt_len = strlen(salt_hex) / 2; + + state->iterations = iterations; + } else { + r = fscanf(f, "v1:%126[0-9a-z]:%40[0-9a-z]:%d", challenge_hex, response_hex, &slot); + if (r != 3) { + D(("Could not parse contents of chalresp_state file (%i)", r)); + goto out; + } + + if (verbose) { + D(("Challenge: %s, expected response: %s, slot: %d", challenge_hex, response_hex, slot)); + } } - if (verbose) - D(("Challenge: %s, expected response: %s, slot: %d", challenge_hex, response_hex, slot)); if (! yubikey_hex_p(challenge_hex)) { D(("Invalid challenge hex input : %s", challenge_hex)); @@ -290,14 +313,31 @@ int write_chalresp_state(FILE *f, CR_STATE *state) { char challenge_hex[CR_CHALLENGE_SIZE * 2 + 1], response_hex[CR_RESPONSE_SIZE * 2 + 1]; + char salt_hex[CR_SALT_SIZE * 2 + 1], hashed_hex[CR_RESPONSE_SIZE * 2 + 1]; + unsigned char salt[CR_SALT_SIZE], hash[CR_RESPONSE_SIZE]; + YK_PRF_METHOD prf_method = {20, yk_hmac_sha1}; + unsigned int iterations = CR_DEFAULT_ITERATIONS; int fd; memset(challenge_hex, 0, sizeof(challenge_hex)); memset(response_hex, 0, sizeof(response_hex)); + memset(salt_hex, 0, sizeof(salt_hex)); + memset(hashed_hex, 0, sizeof(hashed_hex)); yubikey_hex_encode(challenge_hex, (char *)state->challenge, state->challenge_len); yubikey_hex_encode(response_hex, (char *)state->response, state->response_len); + if(state->iterations > 0) { + iterations = state->iterations; + } + + generate_random(salt, CR_SALT_SIZE); + yk_pbkdf2(response_hex, salt, CR_SALT_SIZE, iterations, + hash, CR_RESPONSE_SIZE, &prf_method); + + yubikey_hex_encode(hashed_hex, (char *)hash, CR_RESPONSE_SIZE); + yubikey_hex_encode(salt_hex, (char *)salt, CR_SALT_SIZE); + rewind(f); fd = fileno(f); @@ -307,7 +347,7 @@ write_chalresp_state(FILE *f, CR_STATE *state) if (ftruncate(fd, 0)) goto out; - fprintf(f, "v1:%s:%s:%d\n", challenge_hex, response_hex, state->slot); + fprintf(f, "v2:%s:%s:%s:%d:%d\n", challenge_hex, hashed_hex, salt_hex, iterations, state->slot); if (fflush(f) < 0) goto out; diff --git a/util.h b/util.h index 4ea013f..d88e525 100644 --- a/util.h +++ b/util.h @@ -67,13 +67,19 @@ int get_user_cfgfile_path(const char *common_path, const char *filename, const c */ #define CR_CHALLENGE_SIZE 63 #define CR_RESPONSE_SIZE 20 +#define CR_SALT_SIZE 32 + +#define CR_DEFAULT_ITERATIONS 10000 struct chalresp_state { char challenge[CR_CHALLENGE_SIZE]; uint8_t challenge_len; char response[CR_RESPONSE_SIZE]; uint8_t response_len; + char salt[CR_SALT_SIZE]; + uint8_t salt_len; uint8_t slot; + uint32_t iterations; }; typedef struct chalresp_state CR_STATE;