From ab47c06c209d79261175ed3e5eb53ce807c04b9c Mon Sep 17 00:00:00 2001 From: Fredrik Thulin Date: Thu, 17 Mar 2011 16:10:42 +0100 Subject: [PATCH] Further cleanups to challenge response code, and move more code to util.c. --- pam_yubico.c | 91 ++++++++++++---------------------------------------- util.c | 80 +++++++++++++++++++++++++++++++++++++++++++++ util.h | 34 ++++++++++++++++++++ 3 files changed, 135 insertions(+), 70 deletions(-) diff --git a/pam_yubico.c b/pam_yubico.c index d4bad92..e837479 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -51,19 +51,6 @@ #include #endif -#if defined(DEBUG_PAM) -# if defined(HAVE_SECURITY__PAM_MACROS_H) -# define DEBUG -# include -# else -# define D(x) do { \ - printf ("debug: %s:%d (%s): ", __FILE__, __LINE__, __FUNCTION__); \ - printf x; \ - printf ("\n"); \ - } while (0) -# endif -#endif - #ifdef HAVE_LIBLDAP /* Some functions like ldap_init, ldap_simple_bind_s, ldap_unbind are deprecated but still available. We will drop support for 'ldapserver' @@ -88,12 +75,6 @@ #define MAX_TOKEN_ID_LEN 16 #define DEFAULT_TOKEN_ID_LEN 12 -/* Challenges can be 0..63 or 64 bytes long, depending on YubiKey configuration. - * We settle for 63 bytes to have something that works with all configurations. - */ -#define CR_CHALLENGE_SIZE 63 -#define CR_RESPONSE_SIZE 20 - /* * This function will look for users name with valid user token id. It * will returns 0 for failure and 1 for success. @@ -396,17 +377,15 @@ do_challenge_response(struct cfg *cfg, const char *username) { char *userfile = NULL; FILE *f = NULL; - unsigned char challenge[CR_CHALLENGE_SIZE + 1]; - unsigned char challenge_hex[sizeof(challenge) * 2 + 1], expected_response[CR_RESPONSE_SIZE * 2 + 1]; - int r, slot, ret, fd; + unsigned char buf[CR_RESPONSE_SIZE + 16], response_hex[CR_RESPONSE_SIZE * 2 + 1]; + int ret; - unsigned char response[CR_RESPONSE_SIZE + 16]; /* Need some extra bytes in this read buffer */ - unsigned char response_hex[CR_RESPONSE_SIZE * 2 + 1]; - int yk_cmd; unsigned int flags = 0; unsigned int response_len = 0; unsigned int expect_bytes = 0; YK_KEY *yk = NULL; + CR_STATE state; + int len; ret = PAM_AUTH_ERR; @@ -421,31 +400,9 @@ do_challenge_response(struct cfg *cfg, const char *username) /* XXX should drop root privileges before opening file in user's home directory */ f = fopen(userfile, "r+"); - if (! f) - goto out; - /* XXX not ideal with hard coded lengths in this scan string. - * 126 corresponds to twice the size of CR_CHALLENGE_SIZE, - * 40 is twice the size of CR_RESPONSE_SIZE - * (twice because we hex encode the challenge and response) - */ - r = fscanf(f, "%126[0-9a-z]:%40[0-9a-z]:%d", &challenge_hex, &expected_response, &slot); - D(("Challenge: %s, response: %s, slot: %d", challenge_hex, expected_response, slot)); - if (r != 3) - goto out; - if (! yubikey_hex_p(challenge_hex)) { - D(("Invalid challenge hex input : %s", challenge_hex)); + if (! load_chalresp_state(f, &state)) goto out; - } - - if (! yubikey_hex_p(expected_response)) { - D(("Invalid expected response hex input : %s", expected_response)); - goto out; - } - - if (slot != 1 && slot != 2) { - D(("Invalid slot input : %i", slot)); - } if (! init_yubikey(&yk)) { D(("Failed initializing YubiKey")); @@ -457,11 +414,9 @@ do_challenge_response(struct cfg *cfg, const char *username) goto out; } - yubikey_hex_decode(challenge, challenge_hex, sizeof(challenge)); - len = strlen(challenge_hex) / 2; - - if (! challenge_response(yk, slot, challenge, len, true, flags, false, - response, sizeof(response), &response_len)) { + if (! challenge_response(yk, state.slot, state.challenge, state.challenge_len, + true, flags, false, + buf, sizeof(buf), &response_len)) { D(("Challenge-response FAILED")); goto out; } @@ -470,9 +425,9 @@ do_challenge_response(struct cfg *cfg, const char *username) * Check YubiKey response against the expected response */ - yubikey_hex_encode(response_hex, (char *)response, response_len); + yubikey_hex_encode(response_hex, (char *)buf, response_len); - if (strcmp(response_hex, expected_response) == 0) { + if (memcmp(buf, state.response, response_len) == 0) { ret = PAM_SUCCESS; } else { D(("Unexpected C/R response : %s", response_hex)); @@ -481,13 +436,14 @@ do_challenge_response(struct cfg *cfg, const char *username) D(("Got the expected response, generating new challenge (%i bytes).", CR_CHALLENGE_SIZE)); - if (generate_random(challenge, CR_CHALLENGE_SIZE)) { + if (generate_random(state.challenge, sizeof(state.challenge))) { D(("Failed generating new challenge!")); goto out; } - if (! challenge_response(yk, slot, challenge, CR_CHALLENGE_SIZE, true, flags, false, - response, sizeof(response), &response_len)) { + if (! challenge_response(yk, state.slot, state.challenge, CR_CHALLENGE_SIZE, + true, flags, false, + buf, sizeof(buf), &response_len)) { D(("Second challenge-response FAILED")); goto out; } @@ -498,19 +454,14 @@ do_challenge_response(struct cfg *cfg, const char *username) /* * Write the challenge and response we will expect the next time to the state file. */ + if (response_len > sizeof(state.response)) { + D(("Got too long response ??? (%i/%i)", response_len, sizeof(state.response))); + goto out; + } + memcpy (state.response, buf, response_len); + state.response_len = response_len; - memset(challenge_hex, 0, sizeof(challenge_hex)); - memset(response_hex, 0, sizeof(response_hex)); - yubikey_hex_encode(challenge_hex, (char *)challenge, CR_CHALLENGE_SIZE); - yubikey_hex_encode(response_hex, (char *)response, response_len); - rewind(f); - fd = fileno(f); - if (fd == -1) - goto out; - if (ftruncate(fd, 0)) - goto out; - fprintf(f, "%s:%s:%d\n", challenge_hex, response_hex, slot); - if (fsync(fd) < 0) + if (! write_chalresp_state (f, &state)) goto out; D(("Challenge-response success!")); diff --git a/util.c b/util.c index 51799cf..4a3520e 100644 --- a/util.c +++ b/util.c @@ -184,3 +184,83 @@ int challenge_response(YK_KEY *yk, int slot, return 1; } + +int +load_chalresp_state(FILE *f, CR_STATE *state) +{ + unsigned char challenge_hex[CR_CHALLENGE_SIZE * 2 + 1], response_hex[CR_RESPONSE_SIZE * 2 + 1]; + int slot; + int r; + + if (! f) + goto out; + + /* XXX not ideal with hard coded lengths in this scan string. + * 126 corresponds to twice the size of CR_CHALLENGE_SIZE, + * 40 is twice the size of CR_RESPONSE_SIZE + * (twice because we hex encode the challenge and response) + */ + r = fscanf(f, "%126[0-9a-z]:%40[0-9a-z]:%d", &challenge_hex, &response_hex, &slot); + D(("Challenge: %s, response: %s, slot: %d", challenge_hex, response_hex, slot)); + if (r != 3) + goto out; + + if (! yubikey_hex_p(challenge_hex)) { + D(("Invalid challenge hex input : %s", challenge_hex)); + goto out; + } + + if (! yubikey_hex_p(response_hex)) { + D(("Invalid expected response hex input : %s", response_hex)); + goto out; + } + + if (slot != 1 && slot != 2) { + D(("Invalid slot input : %i", slot)); + goto out; + } + + yubikey_hex_decode(state->challenge, challenge_hex, sizeof(state->challenge)); + state->challenge_len = strlen(challenge_hex) / 2; + + yubikey_hex_decode(state->response, response_hex, sizeof(state->response)); + state->response_len = strlen(response_hex) / 2; + + state->slot = slot; + + return 1; + + out: + return 0; +} + +int +write_chalresp_state(FILE *f, CR_STATE *state) +{ + unsigned char challenge_hex[CR_CHALLENGE_SIZE * 2 + 1], response_hex[CR_RESPONSE_SIZE * 2 + 1]; + int fd; + + memset(challenge_hex, 0, sizeof(challenge_hex)); + memset(response_hex, 0, sizeof(response_hex)); + + yubikey_hex_encode(challenge_hex, (char *)state->challenge, state->challenge_len); + yubikey_hex_encode(response_hex, (char *)state->response, state->response_len); + + rewind(f); + + fd = fileno(f); + if (fd == -1) + goto out; + + if (ftruncate(fd, 0)) + goto out; + + fprintf(f, "%s:%s:%d\n", challenge_hex, response_hex, state->slot); + + if (fsync(fd) < 0) + goto out; + + return 1; + out: + return 0; +} diff --git a/util.h b/util.h index c404bef..b3b1caf 100644 --- a/util.h +++ b/util.h @@ -36,9 +36,43 @@ #include #include +#ifndef D +#if defined(DEBUG_PAM) +# if defined(HAVE_SECURITY__PAM_MACROS_H) +# define DEBUG +# include +# else +# define D(x) do { \ + printf ("debug: %s:%d (%s): ", __FILE__, __LINE__, __FUNCTION__); \ + printf x; \ + printf ("\n"); \ + } while (0) +# endif /* HAVE_SECURITY__PAM_MACROS_H */ +#endif /* DEBUG_PAM */ +#endif /* D */ + +/* Challenges can be 0..63 or 64 bytes long, depending on YubiKey configuration. + * We settle for 63 bytes to have something that works with all configurations. + */ +#define CR_CHALLENGE_SIZE 63 +#define CR_RESPONSE_SIZE 20 + +struct chalresp_state { + unsigned char challenge[CR_CHALLENGE_SIZE]; + uint8_t challenge_len; + unsigned char response[CR_RESPONSE_SIZE]; + uint8_t response_len; + uint8_t slot; +}; + +typedef struct chalresp_state CR_STATE; + int generate_random(char *buf, int len); int get_user_cfgfile_path(const char *common_path, const char *filename, const char *username, char **fn); +int load_chalresp_state(FILE *f, CR_STATE *state); +int write_chalresp_state(FILE *f, CR_STATE *state); + int init_yubikey(YK_KEY **yk); int check_firmware_version(YK_KEY *yk, bool verbose, bool quiet); int challenge_response(YK_KEY *yk, int slot,