diff --git a/pam_yubico.c b/pam_yubico.c index d6754fe..823fcd2 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -64,10 +64,6 @@ # endif #endif -#include -#include -#include - #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' @@ -400,8 +396,8 @@ do_challenge_response(struct cfg *cfg, const char *username) { char *userfile = NULL; FILE *f = NULL; - char challenge[CR_CHALLENGE_SIZE + 1]; - char challenge_hex[sizeof(challenge) * 2 + 1], expected_response[CR_RESPONSE_SIZE * 2 + 1]; + 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 response[CR_RESPONSE_SIZE + 16]; /* Need some extra bytes in this read buffer */ @@ -447,36 +443,37 @@ do_challenge_response(struct cfg *cfg, const char *username) goto out; } - yubikey_hex_decode(challenge, challenge_hex, strlen(challenge_hex)); - len = strlen(challenge_hex) / 2; - if (slot == 1) { - yk_cmd = SLOT_CHAL_HMAC1; - } else if (slot == 2) { - yk_cmd = SLOT_CHAL_HMAC2; - } else { + if (slot != 1 && slot != 2) { D(("Invalid slot input : %i", slot)); } - if (!yk_init()) + if (! init_yubikey(&yk)) { + D(("Failed initializing YubiKey")); goto out; + } - if (!(yk = yk_open_first_key())) + if (! check_firmware_version(yk, false, true)) { + D(("YubiKey does not support Challenge-Response (version 2.2 required)")); goto out; + } - if (!yk_write_to_key(yk, yk_cmd, challenge, len)) - goto out; + yubikey_hex_decode(challenge, challenge_hex, sizeof(challenge)); + len = strlen(challenge_hex) / 2; - if (! yk_read_response_from_key(yk, slot, flags, - &response, sizeof(response), - CR_RESPONSE_SIZE, - &response_len)) + if (! challenge_response(yk, slot, challenge, len, true, flags, false, + response, sizeof(response), &response_len)) { + D(("Challenge-response FAILED")); goto out; - /* response read includes some extra bytes (CRC etc.) */ - if (response_len > CR_RESPONSE_SIZE) - response_len = CR_RESPONSE_SIZE; + } + + /* + * Check YubiKey response against the expected response + */ + yubikey_hex_encode(response_hex, (char *)response, response_len); + if (strcmp(response_hex, expected_response) != 0) { - D(("Unexpected C/R response : %s", response_hex)); + D(("Unexpected C/R response : %s != %s", response_hex, expected_response)); ret = PAM_AUTH_ERR; goto out; } @@ -488,22 +485,19 @@ do_challenge_response(struct cfg *cfg, const char *username) goto out; } - if (!yk_write_to_key(yk, yk_cmd, challenge, CR_CHALLENGE_SIZE)) + if (! challenge_response(yk, slot, challenge, CR_CHALLENGE_SIZE, true, flags, false, + response, sizeof(response), &response_len)) { + D(("Second challenge-response FAILED")); goto out; - - if (! yk_read_response_from_key(yk, slot, flags, - &response, sizeof(response), - CR_RESPONSE_SIZE, - &response_len)) - goto out; - - /* response read includes some extra bytes (CRC etc.) */ - if (response_len > CR_RESPONSE_SIZE) - response_len = CR_RESPONSE_SIZE; + } /* the yk_* functions leave 'junk' in errno */ errno = 0; + /* + * Write the challenge and response we will expect the next time to the state file. + */ + memset(challenge_hex, 0, sizeof(challenge_hex)); memset(response_hex, 0, sizeof(response_hex)); yubikey_hex_encode(challenge_hex, (char *)challenge, CR_CHALLENGE_SIZE); diff --git a/util.c b/util.c index 59edb9a..51799cf 100644 --- a/util.c +++ b/util.c @@ -39,8 +39,13 @@ #include "util.h" +#include +#include +#include +#include + /* Fill buf with len bytes of random data */ -static int generate_random(char *buf, int len) +int generate_random(char *buf, int len) { FILE *u; int i, res; @@ -59,7 +64,7 @@ static int generate_random(char *buf, int len) int get_user_cfgfile_path(const char *common_path, const char *filename, const char *username, char **fn) { - /* Getting file from user home directory, i.e. ~/.yubico/challenge, or + /* Getting file from user home directory, e.g. ~/.yubico/challenge, or * from a system wide directory. * * Format is hex(challenge):hex(response):slot num @@ -83,3 +88,99 @@ get_user_cfgfile_path(const char *common_path, const char *filename, const char *fn = userfile; return (userfile >= 0); } + +int +check_firmware_version(YK_KEY *yk, bool verbose, bool quiet) +{ + 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) { + if (! quiet) + fprintf(stderr, "Challenge-response not supported before YubiKey 2.2.\n"); + free(st); + return 0; + } + + free(st); + return 1; +} + +int +init_yubikey(YK_KEY **yk) +{ + if (!yk_init()) + return 0; + + if (!(*yk = yk_open_first_key())) + return 0; + + return 1; +} + +int challenge_response(YK_KEY *yk, int slot, + unsigned char *challenge, unsigned int len, + bool hmac, unsigned int flags, bool verbose, + unsigned char *response, int res_size, int *res_len) +{ + int yk_cmd; + unsigned int response_len = 0; + unsigned int expect_bytes = 0; + + if (res_size < sizeof(64 + 16)) + return 0; + + memset(response, 0, sizeof(response)); + + 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, res_size, + expect_bytes, + &response_len)) + return 0; + + if (hmac && response_len > 20) + response_len = 20; + if (! hmac && response_len > 16) + response_len = 16; + + *res_len = response_len; + + return 1; +} diff --git a/util.h b/util.h index 259cebc..c404bef 100644 --- a/util.h +++ b/util.h @@ -31,5 +31,17 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include +#include + 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 init_yubikey(YK_KEY **yk); +int check_firmware_version(YK_KEY *yk, bool verbose, bool quiet); +int challenge_response(YK_KEY *yk, int slot, + unsigned char *challenge, unsigned int len, + bool hmac, unsigned int flags, bool verbose, + unsigned char *response, int res_size, int *res_len);