diff --git a/Makefile.am b/Makefile.am index 16302c7..97d02f5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,7 @@ libmodule_la_SOURCES += drop_privs.h libmodule_la_SOURCES += expand.c libmodule_la_SOURCES += explicit_bzero.c libmodule_la_SOURCES += util.c util.h +libmodule_la_SOURCES += authtok.c authtok.h libmodule_la_LIBADD = -lpam $(LIBFIDO2_LIBS) $(LIBCRYPTO_LIBS) pampluginexecdir = $(PAMDIR) diff --git a/authtok.c b/authtok.c new file mode 100644 index 0000000..9c44ef3 --- /dev/null +++ b/authtok.c @@ -0,0 +1,257 @@ +#include +#include +#include + +#include "authtok.h" +#include "b64.h" + +static int +encrypt_authtok(const unsigned char *plaintext, size_t plaintext_len, + const unsigned char *key, /* 32 bytes */ + unsigned char *tag, /* 16 bytes */ + unsigned char *ciphertext /* equal to plaintext_len */ +) { + EVP_CIPHER_CTX *ctx = NULL; + int len; + int retval = 0; + unsigned char iv[12] = {0}; + + if (!(ctx = EVP_CIPHER_CTX_new())) { + goto err; + } + if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) { + goto err; + } + if (plaintext_len > INT_MAX) { + goto err; + } + if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, + (int) plaintext_len)) { + goto err; + } + if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { + goto err; + } + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, (void *) tag)) { + goto err; + } + retval = 1; + +err: + EVP_CIPHER_CTX_free(ctx); + return retval; +} + +static int +decrypt_authtok(const unsigned char *ciphertext, size_t ciphertext_len, + const unsigned char *key, /* 32 bytes */ + const unsigned char *tag, /* 16 bytes */ + unsigned char *plaintext /* equal to ciphertext_len */ +) { + EVP_CIPHER_CTX *ctx = NULL; + int len; + int retval = 0; + unsigned char iv[12] = {0}; + + if (!(ctx = EVP_CIPHER_CTX_new())) { + goto err; + } + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv)) { + goto err; + } + if (ciphertext_len > INT_MAX) { + goto err; + } + if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, + (int) ciphertext_len)) { + goto err; + } + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (void *) tag)) { + goto err; + } + if (!EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) { + goto err; + } + retval = 1; + +err: + EVP_CIPHER_CTX_free(ctx); + return retval; +} + +int get_authtok(const fido_assert_t *assert, const char *enc_authtok, + char **authtok, size_t *authtok_len) { + unsigned char *buf = NULL; + size_t buf_len; + const unsigned char *key; + int ok = 0; + + *authtok = NULL; + + if (!b64_decode(enc_authtok, (void **) &buf, &buf_len) || + buf_len <= HMAC_SALT_SIZE + AEAD_TAG_SIZE) { + goto err; + } + + if (fido_assert_count(assert) != 1) { + goto err; + } + + if ((key = fido_assert_hmac_secret_ptr(assert, 0)) == NULL) { + goto err; + } + if (fido_assert_hmac_secret_len(assert, 0) != HMAC_SECRET_SIZE) { + goto err; + } + + *authtok_len = buf_len - HMAC_SALT_SIZE - AEAD_TAG_SIZE; + if (!(*authtok = malloc(*authtok_len + 1))) { + goto err; + } + if (!decrypt_authtok(buf + HMAC_SALT_SIZE, + buf_len - HMAC_SALT_SIZE - AEAD_TAG_SIZE, key, + buf + buf_len - AEAD_TAG_SIZE, + (unsigned char *) *authtok)) { + goto err; + } + (*authtok)[*authtok_len] = '\0'; + + ok = 1; + +err: + free(buf); + if (!ok) { + if (*authtok) { + explicit_bzero(*authtok, *authtok_len); + free(*authtok); + *authtok = NULL; + *authtok_len = 0; + } + } + return ok; +} + +static int get_hmac_secret(fido_dev_t *dev, fido_cred_t *cred, fido_opt_t uv, + const char *pin, const unsigned char *salt, + unsigned char *secret) { + fido_assert_t *assert = NULL; + unsigned char cdh[32]; + const unsigned char *kh = NULL; + const unsigned char *hmac_secret; + size_t kh_len; + size_t hmac_secret_len; + const char *id; + int retval = 0; + + if (!random_bytes(cdh, sizeof(cdh))) { + goto err; + } + + if ((assert = fido_assert_new()) == NULL) { + goto err; + } + + if (!(id = fido_cred_rp_id(cred))) { + goto err; + } + + if (fido_assert_set_rp(assert, id) != FIDO_OK) { + goto err; + } + + if ((kh = fido_cred_id_ptr(cred)) == NULL) { + goto err; + } + + if ((kh_len = fido_cred_id_len(cred)) == 0) { + goto err; + } + + if (fido_assert_allow_cred(assert, kh, kh_len) != FIDO_OK) { + goto err; + } + + if (fido_assert_set_clientdata_hash(assert, cdh, 32) != FIDO_OK) { + goto err; + } + + if (fido_assert_set_uv(assert, uv) != FIDO_OK) { + goto err; + } + + if (fido_assert_set_extensions(assert, FIDO_EXT_HMAC_SECRET) != FIDO_OK) { + goto err; + } + + if (fido_assert_set_hmac_salt(assert, salt, HMAC_SALT_SIZE) != FIDO_OK) { + goto err; + } + + if (fido_dev_get_assert(dev, assert, pin) != FIDO_OK) { + goto err; + } + + if (fido_assert_count(assert) != 1) { + goto err; + } + + if (fido_assert_verify(assert, 0, fido_cred_type(cred), + fido_cred_pubkey_ptr(cred)) != FIDO_OK) { + goto err; + } + + if ((hmac_secret = fido_assert_hmac_secret_ptr(assert, 0)) == NULL) { + goto err; + } + + if ((hmac_secret_len = fido_assert_hmac_secret_len(assert, 0)) != + HMAC_SECRET_SIZE) { + goto err; + } + + memcpy(secret, hmac_secret, hmac_secret_len); + retval = 1; + +err: + fido_assert_free(&assert); + return retval; +} + +int generate_encrypted_authtok(fido_dev_t *dev, fido_cred_t *cred, + const char *authtok, fido_opt_t uv, + const char *pin, unsigned char **enc_authtok, + size_t *enc_authtok_len) { + size_t authtok_len; + unsigned char key[32]; + int ok = 0; + + authtok_len = strlen(authtok); + *enc_authtok_len = HMAC_SALT_SIZE + authtok_len + AEAD_TAG_SIZE; + if ((*enc_authtok = malloc(*enc_authtok_len)) == NULL) { + goto err; + } + if (!random_bytes(*enc_authtok, HMAC_SALT_SIZE)) { + goto err; + } + if (!get_hmac_secret(dev, cred, uv, pin, *enc_authtok, key)) { + goto err; + } + if (!encrypt_authtok((unsigned char *) authtok, authtok_len, key, + *enc_authtok + HMAC_SALT_SIZE + authtok_len, + *enc_authtok + HMAC_SALT_SIZE)) { + goto err; + } + ok = 1; + +err: + explicit_bzero(key, sizeof(key)); + if (!ok) { + if (*enc_authtok) { + explicit_bzero(*enc_authtok, *enc_authtok_len); + free(*enc_authtok); + *enc_authtok = NULL; + *enc_authtok_len = 0; + } + } + return ok; +} diff --git a/authtok.h b/authtok.h new file mode 100644 index 0000000..801541e --- /dev/null +++ b/authtok.h @@ -0,0 +1,20 @@ +#ifndef AUTHTOK_H +#define AUTHTOK_H + +#include + +#include "util.h" + +#define HMAC_SALT_SIZE 32 +#define HMAC_SECRET_SIZE 32 +#define AEAD_TAG_SIZE 16 + +int get_authtok(const fido_assert_t *assert, const char *enc_authtok, + char **authtok, size_t *authtok_len); + +int generate_encrypted_authtok(fido_dev_t *dev, fido_cred_t *cred, + const char *authtok, fido_opt_t uv, + const char *pin, unsigned char **enc_authtok, + size_t *enc_authtok_len); + +#endif /* UTIL_H */ diff --git a/fuzz/fuzz_format_parsers.c b/fuzz/fuzz_format_parsers.c index 81e11b3..c45bbe8 100644 --- a/fuzz/fuzz_format_parsers.c +++ b/fuzz/fuzz_format_parsers.c @@ -19,10 +19,12 @@ static void cleanup(device_t *devs, unsigned int n_devs) { free(devs[i].publicKey); free(devs[i].coseType); free(devs[i].attributes); + free(devs[i].enc_authtok); devs[i].keyHandle = NULL; devs[i].publicKey = NULL; devs[i].coseType = NULL; devs[i].attributes = NULL; + devs[i].enc_authtok = NULL; } } diff --git a/pam-u2f.c b/pam-u2f.c index 28c76d4..2bbfad5 100644 --- a/pam-u2f.c +++ b/pam-u2f.c @@ -65,6 +65,8 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) { cfg->nodetect = 1; } else if (strcmp(argv[i], "expand") == 0) { cfg->expand = 1; + } else if (strcmp(argv[i], "allowauthtok") == 0) { + cfg->allowauthtok = 1; } else if (strncmp(argv[i], "userpresence=", 13) == 0) { sscanf(argv[i], "userpresence=%d", &cfg->userpresence); } else if (strncmp(argv[i], "userverification=", 17) == 0) { @@ -111,6 +113,7 @@ static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) { debug_dbg(cfg, "alwaysok=%d", cfg->alwaysok); debug_dbg(cfg, "sshformat=%d", cfg->sshformat); debug_dbg(cfg, "expand=%d", cfg->expand); + debug_dbg(cfg, "allowauthtok=%d", cfg->allowauthtok); debug_dbg(cfg, "authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)"); debug_dbg(cfg, "authpending_file=%s", cfg->authpending_file ? cfg->authpending_file : "(null)"); diff --git a/pamu2fcfg/Makefile.am b/pamu2fcfg/Makefile.am index 7cb1b9f..6fb8be1 100644 --- a/pamu2fcfg/Makefile.am +++ b/pamu2fcfg/Makefile.am @@ -9,4 +9,5 @@ pamu2fcfg_SOURCES = pamu2fcfg.c pamu2fcfg_SOURCES += readpassphrase.c _readpassphrase.h pamu2fcfg_SOURCES += strlcpy.c openbsd-compat.h pamu2fcfg_SOURCES += ../util.c ../b64.c ../explicit_bzero.c +pamu2fcfg_SOURCES += ../authtok.c ../authtok.h pamu2fcfg_LDADD = $(LIBFIDO2_LIBS) $(LIBCRYPTO_LIBS) diff --git a/pamu2fcfg/pamu2fcfg.c b/pamu2fcfg/pamu2fcfg.c index 74d6ab4..04b613d 100644 --- a/pamu2fcfg/pamu2fcfg.c +++ b/pamu2fcfg/pamu2fcfg.c @@ -27,6 +27,7 @@ #include "b64.h" #include "util.h" +#include "authtok.h" #include "openbsd-compat.h" @@ -46,6 +47,10 @@ struct args { int debug; int verbose; int nouser; + int authtok; + int pam_userpresence; + int pam_userverification; + int pam_pinverification; }; static fido_cred_t *prepare_cred(const struct args *const args) { @@ -160,6 +165,14 @@ static fido_cred_t *prepare_cred(const struct args *const args) { goto err; } + if (args->authtok) { + if ((r = fido_cred_set_extensions(cred, FIDO_EXT_HMAC_SECRET)) != FIDO_OK) { + fprintf(stderr, "error: fido_cred_set_extensions (%d) %s\n", r, + fido_strerr(r)); + goto err; + } + } + ok = 0; err: @@ -171,56 +184,105 @@ static fido_cred_t *prepare_cred(const struct args *const args) { } static int make_cred(const struct args *args, const char *path, fido_dev_t *dev, - fido_cred_t *cred, int devopts) { + fido_cred_t *cred, int devopts, + unsigned char **enc_authtok, size_t *enc_authtok_len) { char prompt[BUFSIZE]; - char pin[BUFSIZE]; + char authtok[BUFSIZE]; + char *pin = NULL; + fido_opt_t uv; + int use_pin; int n; int r; + int retval = -1; if (path == NULL || dev == NULL || cred == NULL) { fprintf(stderr, "%s: args\n", __func__); - return -1; + goto err; + } + + /* Calculate the form of verification we need */ + if (args->authtok) { + if (args->pam_userverification == 1 || args->user_verification) { + uv = FIDO_OPT_TRUE; + } else if (args->pam_userverification == 0) + uv = FIDO_OPT_FALSE; + else { + uv = FIDO_OPT_OMIT; + } + use_pin = args->pam_pinverification == 1 || args->pin_verification; + } else { + uv = args->user_verification ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + use_pin = args->pin_verification; } /* Some form of UV required; built-in UV is available. */ - if (args->user_verification || (devopts & (UV_SET | UV_NOT_REQD)) == UV_SET) { + if (uv == FIDO_OPT_TRUE || (devopts & (UV_SET | UV_NOT_REQD)) == UV_SET) { if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) { fprintf(stderr, "error: fido_cred_set_uv: %s (%d)\n", fido_strerr(r), r); - return -1; + goto err; } } /* Let built-in UV have precedence over PIN. No UV also handled here. */ - if (args->user_verification || !args->pin_verification) { + if (uv == FIDO_OPT_TRUE || !use_pin) { r = fido_dev_make_cred(dev, cred, NULL); } else { r = FIDO_ERR_PIN_REQUIRED; } /* Some form of UV required; built-in UV failed or is not available. */ - if ((devopts & PIN_SET) && - (r == FIDO_ERR_PIN_REQUIRED || r == FIDO_ERR_UV_BLOCKED || - r == FIDO_ERR_PIN_BLOCKED)) { - n = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", path); - if (n < 0 || (size_t) n >= sizeof(prompt)) { - fprintf(stderr, "error: snprintf prompt"); - return -1; - } - if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) { - fprintf(stderr, "error: failed to read pin"); - explicit_bzero(pin, sizeof(pin)); - return -1; + if (r == FIDO_ERR_PIN_REQUIRED || r == FIDO_ERR_UV_BLOCKED || + r == FIDO_ERR_PIN_BLOCKED) { + if (devopts & PIN_SET) { + n = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", path); + if (n < 0 || (size_t) n >= sizeof(prompt)) { + fprintf(stderr, "error: snprintf prompt\n"); + goto err; + } + if ((pin = malloc(BUFSIZE)) == NULL) { + fprintf(stderr, "error: malloc\n"); + goto err; + } + if (!readpassphrase(prompt, pin, BUFSIZE, RPP_ECHO_OFF)) { + fprintf(stderr, "error: failed to read pin\n"); + goto err; + } + r = fido_dev_make_cred(dev, cred, pin); + } else { + fprintf(stderr, "error: pin verification is required but no pin is set " + "on the authenticator"); + goto err; } - r = fido_dev_make_cred(dev, cred, pin); } - explicit_bzero(pin, sizeof(pin)); if (r != FIDO_OK) { fprintf(stderr, "error: fido_dev_make_cred (%d) %s\n", r, fido_strerr(r)); - return -1; + goto err; } - return 0; + if (args->authtok) { + if (!readpassphrase("Enter auth token: ", authtok, sizeof(authtok), + RPP_ECHO_OFF)) { + fprintf(stderr, "error: failed to read auth token\n"); + goto err; + } + + if (!generate_encrypted_authtok(dev, cred, authtok, uv, pin, enc_authtok, + enc_authtok_len)) { + fprintf(stderr, "error: failed to generate encrypted auth token\n"); + goto err; + } + } + + retval = 0; + +err: + explicit_bzero(authtok, BUFSIZE); + if (pin) { + explicit_bzero(pin, BUFSIZE); + free(pin); + } + return retval; } static int verify_cred(const fido_cred_t *const cred) { @@ -248,12 +310,15 @@ static int verify_cred(const fido_cred_t *const cred) { } static int print_authfile_line(const struct args *const args, - const fido_cred_t *const cred) { + const fido_cred_t *const cred, + const unsigned char *enc_authtok, + size_t enc_authtok_len) { const unsigned char *kh = NULL; const unsigned char *pk = NULL; const char *user = NULL; char *b64_kh = NULL; char *b64_pk = NULL; + char *b64_enc_authtok = NULL; size_t kh_len; size_t pk_len; int ok = -1; @@ -296,17 +361,26 @@ static int print_authfile_line(const struct args *const args, printf("%s", user); } - printf(":%s,%s,%s,%s%s%s", args->resident ? "*" : b64_kh, b64_pk, + if (args->authtok) { + if (!b64_encode(enc_authtok, enc_authtok_len, &b64_enc_authtok)) { + fprintf(stderr, "error: failed to encode PAM auth token\n"); + goto err; + } + } + + printf(":%s,%s,%s,%s%s%s,%s", args->resident ? "*" : b64_kh, b64_pk, cose_string(fido_cred_type(cred)), !args->no_user_presence ? "+presence" : "", args->user_verification ? "+verification" : "", - args->pin_verification ? "+pin" : ""); + args->pin_verification ? "+pin" : "", + args->authtok ? b64_enc_authtok : "*"); ok = 0; err: free(b64_kh); free(b64_pk); + free(b64_enc_authtok); return ok; } @@ -369,6 +443,8 @@ static void parse_args(int argc, char *argv[], struct args *args) { { "verbose", no_argument, NULL, 'v' }, { "username", required_argument, NULL, 'u' }, { "nouser", no_argument, NULL, 'n' }, + { "authtok", no_argument, NULL, 'a' }, + { "pam-arguments", required_argument, NULL, 'p' }, { 0, 0, 0, 0 } }; const char *usage = @@ -395,11 +471,21 @@ static void parse_args(int argc, char *argv[], struct args *args) { " defaults to the current user name\n" " -n, --nouser Print only registration information (key handle,\n" " public key, and options), useful for appending\n" +" -a, --authtok Read a PAM auth token (usually a password) that\n" +" should be decrypted during authentication\n" +" -p, --pam-arguments The arguments passed to the pam module, used for\n" +" encryption key generation when --authtok is set\n" "\n" "Report bugs at <" PACKAGE_BUGREPORT ">.\n"; /* clang-format on */ + char *saveptr = NULL; + char *arg; + + args->pam_userpresence = -1; + args->pam_userverification = -1; + args->pam_pinverification = -1; - while ((c = getopt_long(argc, argv, "ho:i:t:rPNVdvu:n", options, NULL)) != + while ((c = getopt_long(argc, argv, "ho:i:t:rPNVdvu:nap:", options, NULL)) != -1) { switch (c) { case 'h': @@ -438,6 +524,22 @@ static void parse_args(int argc, char *argv[], struct args *args) { case 'n': args->nouser = 1; break; + case 'a': + args->authtok = 1; + break; + case 'p': + arg = strtok_r(optarg, " ", &saveptr); + while (arg != NULL) { + if (strncmp(arg, "userpresence=", 13) == 0) { + sscanf(arg, "userpresence=%d", &args->pam_userpresence); + } else if (strncmp(arg, "userverification=", 17) == 0) { + sscanf(arg, "userverification=%d", &args->pam_userverification); + } else if (strncmp(arg, "pinverification=", 16) == 0) { + sscanf(arg, "pinverification=%d", &args->pam_pinverification); + } + arg = strtok_r(NULL, " ", &saveptr); + } + break; case OPT_VERSION: printf("pamu2fcfg " PACKAGE_VERSION "\n"); exit(EXIT_SUCCESS); @@ -463,8 +565,15 @@ int main(int argc, char *argv[]) { size_t ndevs = 0; int devopts = 0; int r; + unsigned char *enc_authtok = NULL; + size_t enc_authtok_len = 0; parse_args(argc, argv, &args); + if (args.authtok && args.no_user_presence) { + fprintf(stderr, "error: user presence is required for auth token\n"); + goto err; + } + fido_init(args.debug ? FIDO_DEBUG : 0); devlist = fido_dev_info_new(64); @@ -555,8 +664,10 @@ int main(int argc, char *argv[]) { if ((cred = prepare_cred(&args)) == NULL) goto err; - if (make_cred(&args, path, dev, cred, devopts) != 0 || - verify_cred(cred) != 0 || print_authfile_line(&args, cred) != 0) + if (make_cred(&args, path, dev, cred, devopts, &enc_authtok, + &enc_authtok_len) != 0 || + verify_cred(cred) != 0 || + print_authfile_line(&args, cred, enc_authtok, enc_authtok_len) != 0) goto err; exit_code = EXIT_SUCCESS; @@ -568,5 +679,9 @@ int main(int argc, char *argv[]) { fido_cred_free(&cred); fido_dev_free(&dev); + if (enc_authtok) { + free(enc_authtok); + } + exit(exit_code); } diff --git a/util.c b/util.c index f3ea20f..955c632 100644 --- a/util.c +++ b/util.c @@ -24,6 +24,7 @@ #include "b64.h" #include "debug.h" #include "util.h" +#include "authtok.h" #define SSH_MAX_SIZE 8192 #define SSH_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\n" @@ -163,40 +164,47 @@ static void reset_device(device_t *device) { free(device->publicKey); free(device->coseType); free(device->attributes); + free(device->enc_authtok); memset(device, 0, sizeof(*device)); } static int parse_native_credential(const cfg_t *cfg, char *s, device_t *cred) { const char *delim = ","; - const char *kh, *pk, *type, *attr; - char *saveptr = NULL; + const char *kh, *pk, *type, *attr, *enc_authtok; + char *saveptr = s; memset(cred, 0, sizeof(*cred)); - if ((kh = strtok_r(s, delim, &saveptr)) == NULL) { + if ((kh = strsep(&saveptr, delim)) == NULL) { debug_dbg(cfg, "Missing key handle"); goto fail; } - if ((pk = strtok_r(NULL, delim, &saveptr)) == NULL) { + if ((pk = strsep(&saveptr, delim)) == NULL) { debug_dbg(cfg, "Missing public key"); goto fail; } - if ((type = strtok_r(NULL, delim, &saveptr)) == NULL) { + if ((type = strsep(&saveptr, delim)) == NULL) { debug_dbg(cfg, "Old format, assume es256 and +presence"); cred->old_format = 1; type = "es256"; attr = "+presence"; - } else if ((attr = strtok_r(NULL, delim, &saveptr)) == NULL) { + enc_authtok = "*"; + } else if ((attr = strsep(&saveptr, delim)) == NULL) { debug_dbg(cfg, "Empty attributes"); attr = ""; + enc_authtok = "*"; + } else if ((enc_authtok = strsep(&saveptr, delim)) == NULL) { + debug_dbg(cfg, "Missing encrypted auth token"); + enc_authtok = "*"; } cred->keyHandle = cred->old_format ? normal_b64(kh) : strdup(kh); if (cred->keyHandle == NULL || (cred->publicKey = strdup(pk)) == NULL || (cred->coseType = strdup(type)) == NULL || - (cred->attributes = strdup(attr)) == NULL) { + (cred->attributes = strdup(attr)) == NULL || + (cred->enc_authtok = strdup(enc_authtok)) == NULL) { debug_dbg(cfg, "Unable to allocate memory for credential components"); goto fail; } @@ -220,13 +228,13 @@ static int parse_native_format(const cfg_t *cfg, const char *username, int r = 0; while ((len = getline(&buf, &bufsiz, opwfile)) != -1) { - char *saveptr = NULL; + char *saveptr = buf; if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0'; debug_dbg(cfg, "Read %zu bytes", len); - s_user = strtok_r(buf, ":", &saveptr); + s_user = strsep(&saveptr, ":"); if (s_user && strcmp(username, s_user) == 0) { debug_dbg(cfg, "Matched user: %s", s_user); @@ -237,7 +245,7 @@ static int parse_native_format(const cfg_t *cfg, const char *username, *n_devs = 0; i = 0; - while ((s_credential = strtok_r(NULL, ":", &saveptr))) { + while ((s_credential = strsep(&saveptr, ":"))) { if ((*n_devs)++ > cfg->max_devs - 1) { *n_devs = cfg->max_devs; debug_dbg(cfg, @@ -259,6 +267,8 @@ static int parse_native_format(const cfg_t *cfg, const char *username, devices[i].coseType); debug_dbg(cfg, "Attributes for device number %u: %s", i + 1, devices[i].attributes); + debug_dbg(cfg, "Encrypted auth token for device number %u: %s", i + 1, + devices[i].enc_authtok); i++; } } @@ -962,6 +972,36 @@ static int set_cdh(const cfg_t *cfg, fido_assert_t *assert) { return 1; } +static int set_hmac_secret(const cfg_t *cfg, fido_assert_t *assert, + const char *b64_enc_authtok) { + unsigned char *enc_authtok = NULL; + size_t enc_authtok_len; + int ok = 0; + + if (!b64_decode(b64_enc_authtok, (void **) &enc_authtok, &enc_authtok_len) || + enc_authtok_len < HMAC_SALT_SIZE + AEAD_TAG_SIZE) { + debug_dbg(cfg, "Failed to decode encrypted auth token"); + goto err; + } + + if (fido_assert_set_extensions(assert, FIDO_EXT_HMAC_SECRET) != FIDO_OK) { + debug_dbg(cfg, "Failed to set extensions"); + goto err; + } + + if (fido_assert_set_hmac_salt(assert, enc_authtok, HMAC_SALT_SIZE) != + FIDO_OK) { + debug_dbg(cfg, "Failed to set hmac salt"); + goto err; + } + + ok = 1; + +err: + free(enc_authtok); + return ok; +} + static fido_assert_t *prepare_assert(const cfg_t *cfg, const device_t *device, const struct opts *opts) { fido_assert_t *assert = NULL; @@ -1146,6 +1186,9 @@ int do_authentication(const cfg_t *cfg, const device_t *devices, struct opts opts; struct pk pk; char *pin = NULL; + char *authtok; + size_t authtok_len; + int enable_authtok; init_opts(&opts); #ifndef WITH_FUZZING @@ -1224,6 +1267,24 @@ int do_authentication(const cfg_t *cfg, const device_t *devices, goto out; } + enable_authtok = 0; + if (cfg->allowauthtok && strcmp(devices[i].enc_authtok, "*") != 0) { + if (opts.pin == FIDO_OPT_TRUE || opts.uv == FIDO_OPT_TRUE) { + enable_authtok = 1; + } else { + debug_dbg(cfg, "Skipping auth token decryption because neither PIN " + "nor UV is set"); + } + } + + debug_dbg(cfg, "enable_authtok=%d", enable_authtok); + if (enable_authtok) { + if (!set_hmac_secret(cfg, assert, devices[i].enc_authtok)) { + debug_dbg(cfg, "Failed to set hmac secret"); + goto out; + } + } + if (opts.pin == FIDO_OPT_TRUE) { pin = converse(pamh, PAM_PROMPT_ECHO_OFF, "Please enter the PIN: "); if (pin == NULL) { @@ -1254,6 +1315,17 @@ int do_authentication(const cfg_t *cfg, const device_t *devices, } r = fido_assert_verify(assert, 0, pk.type, pk.ptr); if (r == FIDO_OK) { + if (enable_authtok) { + if (get_authtok(assert, devices[i].enc_authtok, &authtok, + &authtok_len)) { + pam_set_item(pamh, PAM_AUTHTOK, authtok); + explicit_bzero(authtok, authtok_len); + free(authtok); + debug_dbg(cfg, "Successfully decrypted auth token"); + } else { + debug_dbg(cfg, "Failed to decrypt auth token"); + } + } retval = 1; goto out; } diff --git a/util.h b/util.h index cb8572b..f8b5e91 100644 --- a/util.h +++ b/util.h @@ -36,6 +36,7 @@ typedef struct { int pinverification; int sshformat; int expand; + int allowauthtok; const char *auth_file; const char *authpending_file; const char *origin; @@ -50,6 +51,7 @@ typedef struct { char *keyHandle; char *coseType; char *attributes; + char *enc_authtok; int old_format; } device_t;