Module Name: src
Committed By: christos
Date: Fri Sep 24 13:08:16 UTC 2021
Modified Files:
src/external/bsd/pam-u2f/dist: util.c
Log Message:
Merge conflicts, avoid ssp issues
To generate a diff of this commit:
cvs rdiff -u -r1.3 -r1.4 src/external/bsd/pam-u2f/dist/util.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/external/bsd/pam-u2f/dist/util.c
diff -u src/external/bsd/pam-u2f/dist/util.c:1.3 src/external/bsd/pam-u2f/dist/util.c:1.4
--- src/external/bsd/pam-u2f/dist/util.c:1.3 Mon Mar 2 19:21:20 2020
+++ src/external/bsd/pam-u2f/dist/util.c Fri Sep 24 09:08:16 2021
@@ -5,11 +5,13 @@
#include <fido.h>
#include <fido/es256.h>
#include <fido/rs256.h>
+#include <fido/eddsa.h>
#include <openssl/ec.h>
#include <openssl/obj_mac.h>
-#include <stdbool.h>
+#include <inttypes.h>
+#include <limits.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
@@ -19,10 +21,40 @@
#include <errno.h>
#include <unistd.h>
#include <string.h>
+#include <arpa/inet.h>
#include "b64.h"
#include "util.h"
+#define SSH_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+#define SSH_HEADER_LEN (sizeof(SSH_HEADER) - 1)
+#define SSH_TRAILER "-----END OPENSSH PRIVATE KEY-----\n"
+#define SSH_TRAILER_LEN (sizeof(SSH_TRAILER) - 1)
+#define SSH_AUTH_MAGIC "openssh-key-v1"
+#define SSH_AUTH_MAGIC_LEN (sizeof(SSH_AUTH_MAGIC)) // AUTH_MAGIC includes \0
+#define SSH_ES256 "[email protected]"
+#define SSH_ES256_LEN (sizeof(SSH_ES256) - 1)
+#define SSH_ES256_POINT_LEN 65
+#define SSH_P256_NAME "nistp256"
+#define SSH_P256_NAME_LEN (sizeof(SSH_P256_NAME) - 1)
+#define SSH_EDDSA "[email protected]"
+#define SSH_EDDSA_LEN (sizeof(SSH_EDDSA) - 1)
+#define SSH_EDDSA_POINT_LEN 32
+#define SSH_SK_USER_PRESENCE_REQD 0x01
+#define SSH_SK_USER_VERIFICATION_REQD 0x04
+#define SSH_SK_RESIDENT_KEY 0x20
+
+struct opts {
+ fido_opt_t up;
+ fido_opt_t uv;
+ fido_opt_t pin;
+};
+
+struct pk {
+ void *ptr;
+ int type;
+};
+
static int hex_decode(const char *ascii_hex, unsigned char **blob,
size_t *blob_len) {
*blob = NULL;
@@ -94,13 +126,15 @@ static char *normal_b64(const char *webs
return (b64);
}
-static es256_pk_t *translate_old_format_pubkey(const unsigned char *pk,
- size_t pk_len) {
- es256_pk_t *es256_pk = NULL;
+static int translate_old_format_pubkey(es256_pk_t *es256_pk,
+ const unsigned char *pk, size_t pk_len) {
EC_KEY *ec = NULL;
EC_POINT *q = NULL;
const EC_GROUP *g = NULL;
- int ok = 0;
+ int r = FIDO_ERR_INTERNAL;
+
+ if (es256_pk == NULL)
+ goto fail;
if ((ec = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL ||
(g = EC_KEY_get0_group(ec)) == NULL)
@@ -111,226 +145,175 @@ static es256_pk_t *translate_old_format_
!EC_KEY_set_public_key(ec, q))
goto fail;
- es256_pk = es256_pk_new();
- if (es256_pk == NULL || es256_pk_from_EC_KEY(es256_pk, ec) < 0)
- goto fail;
+ r = es256_pk_from_EC_KEY(es256_pk, ec);
- ok = 1;
fail:
if (ec != NULL)
EC_KEY_free(ec);
if (q != NULL)
EC_POINT_free(q);
- if (!ok)
- es256_pk_free(&es256_pk);
- return (es256_pk);
+ return r;
}
-int get_devices_from_authfile(const char *authfile, const char *username,
- unsigned max_devs, int verbose, FILE *debug_file,
- device_t *devices, unsigned *n_devs) {
-
- char *buf = NULL;
- char *s_user, *s_token;
- int retval = 0;
- int fd = -1;
- struct stat st;
- struct passwd *pw = NULL, pw_s;
- char buffer[BUFSIZE];
- int gpu_ret;
- FILE *opwfile = NULL;
- unsigned i;
-
- /* Ensure we never return uninitialized count. */
- *n_devs = 0;
-
- fd = open(authfile, O_RDONLY | O_CLOEXEC | O_NOCTTY);
- if (fd < 0) {
- if (verbose)
- D(debug_file, "Cannot open file: %s (%s)", authfile, strerror(errno));
- goto err;
- }
-
- if (fstat(fd, &st) < 0) {
- if (verbose)
- D(debug_file, "Cannot stat file: %s (%s)", authfile, strerror(errno));
- goto err;
- }
-
- if (!S_ISREG(st.st_mode)) {
- if (verbose)
- D(debug_file, "%s is not a regular file", authfile);
- goto err;
- }
-
- if (st.st_size == 0) {
- if (verbose)
- D(debug_file, "File %s is empty", authfile);
- goto err;
- }
-
- gpu_ret = getpwuid_r(st.st_uid, &pw_s, buffer, sizeof(buffer), &pw);
- if (gpu_ret != 0 || pw == NULL) {
- D(debug_file, "Unable to retrieve credentials for uid %u, (%s)", st.st_uid,
- strerror(errno));
- goto err;
- }
+static int is_resident(const char *kh) { return strcmp(kh, "*") == 0; }
- if (strcmp(pw->pw_name, username) != 0 && strcmp(pw->pw_name, "root") != 0) {
- if (strcmp(username, "root") != 0) {
- D(debug_file,
- "The owner of the authentication file is neither %s nor root",
- username);
- } else {
- D(debug_file, "The owner of the authentication file is not root");
- }
- goto err;
- }
+static void reset_device(device_t *device) {
+ free(device->keyHandle);
+ free(device->publicKey);
+ free(device->coseType);
+ free(device->attributes);
+ memset(device, 0, sizeof(*device));
+}
- opwfile = fdopen(fd, "r");
- if (opwfile == NULL) {
- if (verbose)
- D(debug_file, "fdopen: %s", strerror(errno));
- goto err;
- } else {
- fd = -1; /* fd belongs to opwfile */
- }
+static int parse_native_format(const cfg_t *cfg, const char *username,
+ char *buf, FILE *opwfile, device_t *devices,
+ unsigned *n_devs) {
- buf = malloc(sizeof(char) * (DEVSIZE * max_devs));
- if (!buf) {
- if (verbose)
- D(debug_file, "Unable to allocate memory");
- goto err;
- }
+ char *s_user, *s_credential;
+ const char *s_token;
+ unsigned i;
- retval = -2;
- while (fgets(buf, (int) (DEVSIZE * (max_devs - 1)), opwfile)) {
+ while (fgets(buf, (int) (DEVSIZE * (cfg->max_devs - 1)), opwfile)) {
char *saveptr = NULL;
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = '\0';
- if (verbose)
- D(debug_file, "Authorization line: %s", buf);
+ if (cfg->debug)
+ D(cfg->debug_file, "Authorization line: %s", buf);
s_user = strtok_r(buf, ":", &saveptr);
if (s_user && strcmp(username, s_user) == 0) {
- if (verbose)
- D(debug_file, "Matched user: %s", s_user);
-
- retval = -1; // We found at least one line for the user
+ if (cfg->debug)
+ D(cfg->debug_file, "Matched user: %s", s_user);
// only keep last line for this user
for (i = 0; i < *n_devs; i++) {
- free(devices[i].keyHandle);
- free(devices[i].publicKey);
- free(devices[i].coseType);
- free(devices[i].attributes);
- devices[i].keyHandle = NULL;
- devices[i].publicKey = NULL;
- devices[i].coseType = NULL;
- devices[i].attributes = NULL;
- devices[i].old_format = 0;
+ reset_device(&devices[i]);
}
*n_devs = 0;
i = 0;
- while ((s_token = strtok_r(NULL, ",", &saveptr)) != NULL) {
- if ((*n_devs)++ > max_devs - 1) {
- *n_devs = max_devs;
- if (verbose)
- D(debug_file,
+ while ((s_credential = strtok_r(NULL, ":", &saveptr)) != NULL) {
+ // s_credential is the whole line now
+ char *credsaveptr = NULL;
+
+ if ((*n_devs)++ > cfg->max_devs - 1) {
+ *n_devs = cfg->max_devs;
+ if (cfg->debug) {
+ D(cfg->debug_file,
"Found more than %d devices, ignoring the remaining ones",
- max_devs);
+ cfg->max_devs);
+ }
break;
}
- devices[i].keyHandle = NULL;
- devices[i].publicKey = NULL;
- devices[i].coseType = NULL;
- devices[i].attributes = NULL;
- devices[i].old_format = 0;
+ reset_device(&devices[i]);
+
+ s_token = strtok_r(s_credential, ",", &credsaveptr);
+
+ if (!s_token) {
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to retrieve keyHandle for device %d", i + 1);
+ }
+ return -1;
+ }
- if (verbose)
- D(debug_file, "KeyHandle for device number %d: %s", i + 1, s_token);
+ if (cfg->debug) {
+ D(cfg->debug_file, "KeyHandle for device number %d: %s", i + 1,
+ s_token);
+ }
devices[i].keyHandle = strdup(s_token);
if (!devices[i].keyHandle) {
- if (verbose)
- D(debug_file, "Unable to allocate memory for keyHandle number %d",
- i);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to allocate memory for keyHandle number %d", i);
+ }
+ return -1;
}
- if (!strcmp(devices[i].keyHandle, "*") && verbose)
- D(debug_file, "Credential is resident");
+ if (is_resident(devices[i].keyHandle) && cfg->debug) {
+ D(cfg->debug_file, "Credential is resident");
+ }
- s_token = strtok_r(NULL, ",", &saveptr);
+ s_token = strtok_r(NULL, ",", &credsaveptr);
if (!s_token) {
- if (verbose)
- D(debug_file, "Unable to retrieve publicKey number %d", i + 1);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to retrieve publicKey number %d", i + 1);
+ }
+ return -1;
}
- if (verbose)
- D(debug_file, "publicKey for device number %d: %s", i + 1, s_token);
+ if (cfg->debug) {
+ D(cfg->debug_file, "publicKey for device number %d: %s", i + 1,
+ s_token);
+ }
devices[i].publicKey = strdup(s_token);
if (!devices[i].publicKey) {
- if (verbose)
- D(debug_file, "Unable to allocate memory for publicKey number %d",
- i);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to allocate memory for publicKey number %d", i);
+ }
+ return -1;
}
- s_token = strtok_r(NULL, ",", &saveptr);
-
- devices[i].old_format = 0;
+ s_token = strtok_r(NULL, ",", &credsaveptr);
if (!s_token) {
- if (verbose) {
- D(debug_file, "Unable to retrieve COSE type %d", i + 1);
- D(debug_file, "Assuming ES256 (backwards compatibility)");
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to retrieve COSE type %d", i + 1);
+ D(cfg->debug_file, "Assuming ES256 (backwards compatibility)");
}
devices[i].old_format = 1;
devices[i].coseType = strdup("es256");
} else {
- if (verbose)
- D(debug_file, "COSE type for device number %d: %s", i + 1, s_token);
+ if (cfg->debug) {
+ D(cfg->debug_file, "COSE type for device number %d: %s", i + 1,
+ s_token);
+ }
devices[i].coseType = strdup(s_token);
}
if (!devices[i].coseType) {
- if (verbose)
- D(debug_file, "Unable to allocate memory for COSE type number %d",
- i);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to allocate memory for COSE type number %d", i);
+ }
+ return -1;
}
- s_token = strtok_r(NULL, ":", &saveptr);
+ s_token = strtok_r(NULL, ",", &credsaveptr);
- if (!s_token) {
- if (verbose) {
- D(debug_file, "Unable to retrieve attributes %d", i + 1);
- D(debug_file, "Assuming 'p' (backwards compatibility)");
+ if (devices[i].old_format == 1) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Old format for device %d, no attributes",
+ i + 1);
+ D(cfg->debug_file, "Assuming 'presence' (backwards compatibility)");
}
- devices[i].attributes = strdup("p");
- } else {
- if (verbose)
- D(debug_file, "Attributes for device number %d: %s", i + 1,
- s_token);
- devices[i].attributes = strdup(s_token);
+ s_token = "+presence";
+ } else if (!s_token) {
+ s_token = "";
+ }
+
+ if (cfg->debug) {
+ D(cfg->debug_file, "Attributes for device number %d: %s", i + 1,
+ s_token);
}
+ devices[i].attributes = strdup(s_token);
if (!devices[i].attributes) {
- if (verbose)
- D(debug_file, "Unable to allocate memory for attributes number %d",
- i);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to allocate memory for attributes number %d", i);
+ }
+ return -1;
}
if (devices[i].old_format) {
@@ -338,10 +321,11 @@ int get_devices_from_authfile(const char
devices[i].keyHandle = normal_b64(websafe_b64);
free(websafe_b64);
if (!devices[i].keyHandle) {
- if (verbose)
- D(debug_file, "Unable to allocate memory for keyHandle number %d",
- i);
- goto err;
+ if (cfg->debug) {
+ D(cfg->debug_file,
+ "Unable to allocate memory for keyHandle number %d", i);
+ }
+ return -1;
}
}
@@ -350,161 +334,1000 @@ int get_devices_from_authfile(const char
}
}
- if (verbose)
- D(debug_file, "Found %d device(s) for user %s", *n_devs, username);
+ return 1;
+}
- retval = 1;
- goto out;
+static int load_ssh_key(const cfg_t *cfg, char *buf, size_t buf_size,
+ FILE *opwfile, size_t opwfile_size) {
+ char *cp = buf;
+ int ch;
-err:
- for (i = 0; i < *n_devs; i++) {
- free(devices[i].keyHandle);
- free(devices[i].publicKey);
- free(devices[i].coseType);
- free(devices[i].attributes);
- devices[i].keyHandle = NULL;
- devices[i].publicKey = NULL;
- devices[i].coseType = NULL;
- devices[i].attributes = NULL;
+ if (opwfile_size > buf_size ||
+ opwfile_size < SSH_HEADER_LEN + SSH_TRAILER_LEN) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (length)");
+ }
+ return 0;
}
- *n_devs = 0;
+ // NOTE(adma): +1 for \0
+ if (fgets(buf, (int)(SSH_HEADER_LEN + 1), opwfile) == NULL ||
+ strlen(buf) != SSH_HEADER_LEN ||
+ strncmp(buf, SSH_HEADER, SSH_HEADER_LEN) != 0) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (header)");
+ }
+ return 0;
+ }
-out:
- if (buf) {
- free(buf);
- buf = NULL;
+ while (opwfile_size > 0 && buf_size > 1) {
+ ch = fgetc(opwfile);
+ if (ch == EOF) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unexpected authfile termination");
+ }
+ return 0;
+ }
+
+ opwfile_size--;
+ buf_size--;
+
+ if (ch != '\n' && ch != '\r') {
+ *cp = (char) ch;
+ if (ch == '-') {
+ // NOTE(adma): no +1 here since we already read one '-'
+ if (buf_size < SSH_TRAILER_LEN ||
+ fgets(cp + 1, (int)SSH_TRAILER_LEN, opwfile) == NULL ||
+ strlen(cp) != SSH_TRAILER_LEN ||
+ strncmp(cp, SSH_TRAILER, SSH_TRAILER_LEN) != 0) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (trailer)");
+ }
+ return 0;
+ }
+
+ *(cp) = '\0';
+ break;
+ } else {
+ cp++;
+ }
+ }
}
- if (opwfile)
- fclose(opwfile);
+ if (cfg->debug) { // TODO(adma): too verbose? Delete?
+ D(cfg->debug_file, "Credential is \"%s\"", buf);
+ }
- if (fd != -1)
- close(fd);
+ return 1;
+}
- return retval;
+static int ssh_get(const unsigned char **buf, size_t *size, unsigned char *dst,
+ size_t len) {
+ if (*size < len)
+ return 0;
+ if (dst != NULL)
+ memcpy(dst, *buf, len);
+ *buf += len;
+ *size -= len;
+ return 1;
}
-void free_devices(device_t *devices, const unsigned n_devs) {
- unsigned i;
+static int ssh_get_u8(const unsigned char **buf, size_t *size, uint8_t *val) {
+ return ssh_get(buf, size, val, sizeof(*val));
+}
- if (!devices)
- return;
+static int ssh_get_u32(const unsigned char **buf, size_t *size, uint32_t *val) {
+ if (!ssh_get(buf, size, (unsigned char *) val, sizeof(*val)))
+ return 0;
+ if (val != NULL)
+ *val = ntohl(*val);
+ return 1;
+}
- for (i = 0; i < n_devs; i++) {
- free(devices[i].keyHandle);
- devices[i].keyHandle = NULL;
+static int ssh_get_string_ref(const unsigned char **buf, size_t *size,
+ const unsigned char **ref, size_t *lenp) {
+ uint32_t len;
+
+ if (!ssh_get_u32(buf, size, &len))
+ return 0;
+ if (!ssh_get(buf, size, NULL, len))
+ return 0;
+ if (ref != NULL)
+ *ref = *buf - len;
+ if (lenp != NULL)
+ *lenp = len;
+ return 1;
+}
+
+static int ssh_get_cstring(const unsigned char **buf, size_t *size, char **str,
+ size_t *lenp) {
+ const unsigned char *ref;
+ size_t len;
+
+ if (!ssh_get_string_ref(buf, size, &ref, &len))
+ return 0;
+ if (str != NULL) {
+ if (len > SIZE_MAX - 1 || (*str = calloc(1, len + 1)) == NULL)
+ return 0;
+ memcpy(*str, ref, len);
+ }
+ if (lenp != NULL)
+ *lenp = len;
+ return 1;
+}
- free(devices[i].publicKey);
- devices[i].publicKey = NULL;
+static int ssh_log_cstring(const cfg_t *cfg, const unsigned char **buf,
+ size_t *size, const char *name) {
+ char *str = NULL;
+ size_t len;
- free(devices[i].coseType);
- devices[i].coseType = NULL;
+ (void) name; // silence compiler warnings if PAM_DEBUG disabled
- free(devices[i].attributes);
- devices[i].attributes = NULL;
+ if (!ssh_get_cstring(buf, size, &str, &len)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Malformed SSH key (%s)", name);
+ return 0;
}
+ if (cfg->debug)
+ D(cfg->debug_file, "%s (%zu) \"%s\"", name, len, str);
- free(devices);
- devices = NULL;
+ free(str);
+ return 1;
}
-static int get_authenticators(const cfg_t *cfg, const fido_dev_info_t *devlist,
- size_t devlist_len, fido_assert_t *assert,
- const void *kh, fido_dev_t **authlist) {
- const fido_dev_info_t *di = NULL;
- fido_dev_t *dev = NULL;
+static int ssh_get_attrs(const cfg_t *cfg, const unsigned char **buf,
+ size_t *size, char **attrs) {
+ char tmp[32] = {0};
+ uint8_t flags;
int r;
- size_t i;
- size_t j;
- if (cfg->debug)
- D(cfg->debug_file, "Working with %zu authenticator(s)", devlist_len);
+ // flags
+ if (!ssh_get_u8(buf, size, &flags)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (flags)");
+ }
+ return 0;
+ }
+ if (cfg->debug) {
+ D(cfg->debug_file, "flags: %02x", flags);
+ }
- for (i = 0, j = 0; i < devlist_len; i++) {
+ r = snprintf(tmp, sizeof(tmp), "%s%s",
+ flags & SSH_SK_USER_PRESENCE_REQD ? "+presence" : "",
+ flags & SSH_SK_USER_VERIFICATION_REQD ? "+verification" : "");
+ if (r < 0 || (size_t) r >= sizeof(tmp)) {
if (cfg->debug)
- D(cfg->debug_file, "Checking whether key exists in authenticator %zu", i);
+ D(cfg->debug_file, "Unable to prepare flags");
+ return 0;
+ }
- di = fido_dev_info_ptr(devlist, i);
- if (!di) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to get device pointer");
- continue;
+ if ((*attrs = strdup(tmp)) == NULL) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to allocate attributes");
}
+ return 0;
+ }
- if (cfg->debug)
- D(cfg->debug_file, "Authenticator path: %s", fido_dev_info_path(di));
+ return 1;
+}
- dev = fido_dev_new();
- if (!dev) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate device type");
- continue;
+static int ssh_get_pubkey(const cfg_t *cfg, const unsigned char **buf,
+ size_t *size, char **type_p, char **pubkey_p) {
+ char *ssh_type = NULL;
+ char *ssh_curve = NULL;
+ const unsigned char *blob;
+ size_t len;
+ int type;
+ size_t point_len;
+ int ok = 0;
+
+ *type_p = NULL;
+ *pubkey_p = NULL;
+
+ // key type
+ if (!ssh_get_cstring(buf, size, &ssh_type, &len)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (keytype)");
+ }
+ goto err;
+ }
+
+ if (len == SSH_ES256_LEN && memcmp(ssh_type, SSH_ES256, SSH_ES256_LEN) == 0) {
+ type = COSE_ES256;
+ point_len = SSH_ES256_POINT_LEN;
+ } else if (len == SSH_EDDSA_LEN &&
+ memcmp(ssh_type, SSH_EDDSA, SSH_EDDSA_LEN) == 0) {
+ type = COSE_EDDSA;
+ point_len = SSH_EDDSA_POINT_LEN;
+ } else {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unknown key type %s", ssh_type);
}
+ goto err;
+ }
- r = fido_dev_open(dev, fido_dev_info_path(di));
+ if (cfg->debug) {
+ D(cfg->debug_file, "keytype (%zu) \"%s\"", len, ssh_type);
+ }
+
+ if (type == COSE_ES256) {
+ // curve name
+ if (!ssh_get_cstring(buf, size, &ssh_curve, &len)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (curvename)");
+ }
+ goto err;
+ }
+
+ if (len == SSH_P256_NAME_LEN &&
+ memcmp(ssh_curve, SSH_P256_NAME, SSH_P256_NAME_LEN) == 0) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "curvename (%zu) \"%s\"", len, ssh_curve);
+ }
+ } else {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unknown curve %s", ssh_curve);
+ }
+ goto err;
+ }
+ }
+
+ // point
+ if (!ssh_get_string_ref(buf, size, &blob, &len)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (point)");
+ }
+ goto err;
+ }
+
+ if (len != point_len) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Invalid point length, should be %zu, found %zu",
+ point_len, len);
+ }
+ goto err;
+ }
+
+ if (type == COSE_ES256) {
+ // Skip the initial '04'
+ if (len < 1) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Failed to skip initial '04'");
+ }
+ goto err;
+ }
+ blob++;
+ len--;
+ }
+
+ if (!b64_encode(blob, len, pubkey_p)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to allocate public key");
+ }
+ goto err;
+ }
+
+ if ((*type_p = strdup(cose_string(type))) == NULL) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to allocate COSE type");
+ }
+ goto err;
+ }
+
+ ok = 1;
+err:
+ if (!ok) {
+ free(*type_p);
+ free(*pubkey_p);
+ *type_p = NULL;
+ *pubkey_p = NULL;
+ }
+ free(ssh_type);
+ free(ssh_curve);
+
+ return ok;
+}
+
+static int parse_ssh_format(const cfg_t *cfg, char *buf, size_t buf_size,
+ FILE *opwfile, size_t opwfile_size,
+ device_t *devices, unsigned *n_devs) {
+
+ const unsigned char *decoded;
+ unsigned char *decoded_initial = NULL;
+ size_t decoded_len;
+ const unsigned char *blob;
+ uint32_t check1, check2, tmp;
+ size_t len;
+
+ // The logic below is inspired by
+ // how ssh parses its own keys. See sshkey.c
+ reset_device(&devices[0]);
+
+ if (!load_ssh_key(cfg, buf, buf_size, opwfile, opwfile_size) ||
+ !b64_decode(buf, (void **) &decoded_initial, &decoded_len)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Unable to decode credential");
+ }
+ goto out;
+ }
+
+ decoded = decoded_initial;
+
+ // magic
+ if (decoded_len < SSH_AUTH_MAGIC_LEN ||
+ memcmp(decoded, SSH_AUTH_MAGIC, SSH_AUTH_MAGIC_LEN) != 0) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (magic)");
+ }
+ goto out;
+ }
+
+ decoded += SSH_AUTH_MAGIC_LEN;
+ decoded_len -= SSH_AUTH_MAGIC_LEN;
+
+ if (!ssh_log_cstring(cfg, &decoded, &decoded_len, "ciphername") ||
+ !ssh_log_cstring(cfg, &decoded, &decoded_len, "kdfname") ||
+ !ssh_log_cstring(cfg, &decoded, &decoded_len, "kdfoptions"))
+ goto out;
+
+ if (!ssh_get_u32(&decoded, &decoded_len, &tmp)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (nkeys)");
+ }
+ goto out;
+ }
+ if (cfg->debug) {
+ D(cfg->debug_file, "nkeys: %" PRIu32, tmp);
+ }
+ if (tmp != 1) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Multiple keys not supported");
+ }
+ goto out;
+ }
+
+ // public_key (skip)
+ if (!ssh_get_string_ref(&decoded, &decoded_len, NULL, NULL)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (pubkey)");
+ }
+ goto out;
+ }
+
+ // private key (consume length)
+ if (!ssh_get_u32(&decoded, &decoded_len, &tmp) || decoded_len < tmp) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (pvtkey length)");
+ }
+ goto out;
+ }
+
+ // check1, check2
+ if (!ssh_get_u32(&decoded, &decoded_len, &check1) ||
+ !ssh_get_u32(&decoded, &decoded_len, &check2)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (check1, check2)");
+ }
+ goto out;
+ }
+ if (cfg->debug) {
+ D(cfg->debug_file, "check1: %" PRIu32, check1);
+ D(cfg->debug_file, "check2: %" PRIu32, check2);
+ }
+ if (check1 != check2) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Mismatched check values");
+ goto out;
+ }
+ }
+
+ if (!ssh_get_pubkey(cfg, &decoded, &decoded_len, &devices[0].coseType,
+ &devices[0].publicKey) ||
+ !ssh_log_cstring(cfg, &decoded, &decoded_len, "application") ||
+ !ssh_get_attrs(cfg, &decoded, &decoded_len, &devices[0].attributes))
+ goto out;
+
+ // keyhandle
+ if (!ssh_get_string_ref(&decoded, &decoded_len, &blob, &len) ||
+ !b64_encode(blob, len, &devices[0].keyHandle)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (keyhandle)");
+ }
+ goto out;
+ }
+
+ if (cfg->debug) {
+ D(cfg->debug_file, "KeyHandle for device number 1: %s",
+ devices[0].keyHandle);
+ D(cfg->debug_file, "publicKey for device number 1: %s",
+ devices[0].publicKey);
+ D(cfg->debug_file, "COSE type for device number 1: %s",
+ devices[0].coseType);
+ D(cfg->debug_file, "Attributes for device number 1: %s",
+ devices[0].attributes);
+ }
+
+ // reserved (skip)
+ if (!ssh_get_string_ref(&decoded, &decoded_len, NULL, NULL)) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (reserved)");
+ }
+ goto out;
+ }
+
+ // comment
+ if (!ssh_log_cstring(cfg, &decoded, &decoded_len, "comment"))
+ goto out;
+
+ // padding
+ if (decoded_len >= 255) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (padding length)");
+ }
+ goto out;
+ }
+
+ for (int i = 1; (unsigned) i <= decoded_len; i++) {
+ if (decoded[i - 1] != i) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "Malformed SSH key (padding)");
+ }
+ goto out;
+ }
+ }
+
+ free(decoded_initial);
+ decoded_initial = NULL;
+
+ *n_devs = 1;
+
+ return 1;
+
+out:
+ reset_device(&devices[0]);
+
+ if (decoded_initial) {
+ free(decoded_initial);
+ decoded_initial = NULL;
+ }
+
+ return -1;
+}
+
+int get_devices_from_authfile(const cfg_t *cfg, const char *username,
+ device_t *devices, unsigned *n_devs) {
+
+ char *buf = NULL;
+ int retval = 0;
+ int fd = -1;
+ struct stat st;
+ struct passwd *pw = NULL, pw_s;
+ char buffer[BUFSIZE];
+ int gpu_ret;
+ FILE *opwfile = NULL;
+ size_t opwfile_size;
+ unsigned i;
+
+ /* Ensure we never return uninitialized count. */
+ *n_devs = 0;
+
+ fd = open(cfg->auth_file, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ if (fd < 0) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Cannot open file: %s (%s)", cfg->auth_file,
+ strerror(errno));
+ goto err;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Cannot stat file: %s (%s)", cfg->auth_file,
+ strerror(errno));
+ goto err;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "%s is not a regular file", cfg->auth_file);
+ goto err;
+ }
+
+ if (st.st_size == 0) {
+ if (cfg->debug)
+ D(cfg->debug_file, "File %s is empty", cfg->auth_file);
+ goto err;
+ }
+ opwfile_size = (size_t)st.st_size;
+
+ gpu_ret = getpwuid_r(st.st_uid, &pw_s, buffer, sizeof(buffer), &pw);
+ if (gpu_ret != 0 || pw == NULL) {
+ D(cfg->debug_file, "Unable to retrieve credentials for uid %u, (%s)",
+ st.st_uid, strerror(errno));
+ goto err;
+ }
+
+ if (strcmp(pw->pw_name, username) != 0 && strcmp(pw->pw_name, "root") != 0) {
+ if (strcmp(username, "root") != 0) {
+ D(cfg->debug_file,
+ "The owner of the authentication file is neither %s nor root",
+ username);
+ } else {
+ D(cfg->debug_file, "The owner of the authentication file is not root");
+ }
+ goto err;
+ }
+
+ opwfile = fdopen(fd, "r");
+ if (opwfile == NULL) {
+ if (cfg->debug)
+ D(cfg->debug_file, "fdopen: %s", strerror(errno));
+ goto err;
+ } else {
+ fd = -1; /* fd belongs to opwfile */
+ }
+
+ buf = calloc(1, (DEVSIZE * cfg->max_devs));
+ if (!buf) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to allocate memory");
+ goto err;
+ }
+
+ if (cfg->sshformat == 0) {
+ retval = parse_native_format(cfg, username, buf, opwfile, devices, n_devs);
+ } else {
+ retval = parse_ssh_format(cfg, buf, DEVSIZE * cfg->max_devs, opwfile,
+ opwfile_size, devices, n_devs);
+ }
+
+ if (retval != 1) {
+ // NOTE(adma): error message is logged by the previous function
+ goto err;
+ }
+
+ if (cfg->debug)
+ D(cfg->debug_file, "Found %d device(s) for user %s", *n_devs, username);
+
+ retval = 1;
+ goto out;
+
+err:
+ for (i = 0; i < *n_devs; i++) {
+ reset_device(&devices[i]);
+ }
+
+ *n_devs = 0;
+
+out:
+ if (buf) {
+ free(buf);
+ buf = NULL;
+ }
+
+ if (opwfile)
+ fclose(opwfile);
+
+ if (fd != -1)
+ close(fd);
+
+ return retval;
+}
+
+void free_devices(device_t *devices, const unsigned n_devs) {
+ unsigned i;
+
+ if (!devices)
+ return;
+
+ for (i = 0; i < n_devs; i++) {
+ reset_device(&devices[i]);
+ }
+
+ free(devices);
+ devices = NULL;
+}
+
+static int get_authenticators(const cfg_t *cfg, const fido_dev_info_t *devlist,
+ size_t devlist_len, fido_assert_t *assert,
+ const int rk, fido_dev_t **authlist) {
+ const fido_dev_info_t *di = NULL;
+ fido_dev_t *dev = NULL;
+ int r;
+ size_t i;
+ size_t j;
+
+ if (cfg->debug)
+ D(cfg->debug_file, "Working with %zu authenticator(s)", devlist_len);
+
+ for (i = 0, j = 0; i < devlist_len; i++) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Checking whether key exists in authenticator %zu", i);
+
+ di = fido_dev_info_ptr(devlist, i);
+ if (!di) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to get device pointer");
+ continue;
+ }
+
+ if (cfg->debug)
+ D(cfg->debug_file, "Authenticator path: %s", fido_dev_info_path(di));
+
+ dev = fido_dev_new();
+ if (!dev) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to allocate device type");
+ continue;
+ }
+
+ r = fido_dev_open(dev, fido_dev_info_path(di));
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to open authenticator: %s (%d)",
+ fido_strerr(r), r);
+ fido_dev_free(&dev);
+ continue;
+ }
+
+ if (rk || cfg->nodetect) {
+ /* resident credential or nodetect: try all authenticators */
+ authlist[j++] = dev;
+ } else {
+ r = fido_dev_get_assert(dev, assert, NULL);
+ if ((!fido_dev_is_fido2(dev) && r == FIDO_ERR_USER_PRESENCE_REQUIRED) ||
+ (fido_dev_is_fido2(dev) && r == FIDO_OK)) {
+ authlist[j++] = dev;
+ if (cfg->debug)
+ D(cfg->debug_file, "Found key in authenticator %zu", i);
+ return (1);
+ }
+ if (cfg->debug)
+ D(cfg->debug_file, "Key not found in authenticator %zu", i);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ }
+ }
+
+ if (j != 0)
+ return (1);
+ else {
+ if (cfg->debug)
+ D(cfg->debug_file, "Key not found");
+ return (0);
+ }
+}
+
+static void init_opts(struct opts *opts) {
+ opts->up = FIDO_OPT_FALSE;
+ opts->uv = FIDO_OPT_OMIT;
+ opts->pin = FIDO_OPT_FALSE;
+}
+
+static void parse_opts(const cfg_t *cfg, const char *attr, struct opts *opts) {
+ if (cfg->userpresence == 1 || strstr(attr, "+presence")) {
+ opts->up = FIDO_OPT_TRUE;
+ } else if (cfg->userpresence == 0) {
+ opts->up = FIDO_OPT_FALSE;
+ } else {
+ opts->up = FIDO_OPT_OMIT;
+ }
+
+ if (cfg->userverification == 1 || strstr(attr, "+verification")) {
+ opts->uv = FIDO_OPT_TRUE;
+ } else if (cfg->userverification == 0)
+ opts->uv = FIDO_OPT_FALSE;
+ else {
+ opts->uv = FIDO_OPT_OMIT;
+ }
+
+ if (cfg->pinverification == 1 || strstr(attr, "+pin")) {
+ opts->pin = FIDO_OPT_TRUE;
+ } else if (cfg->pinverification == 0) {
+ opts->pin = FIDO_OPT_FALSE;
+ } else {
+ opts->pin = FIDO_OPT_OMIT;
+ }
+}
+
+static int get_device_opts(fido_dev_t *dev, int *pin, int *uv) {
+ fido_cbor_info_t *info = NULL;
+ char *const *ptr;
+ const bool *val;
+ size_t len;
+
+ *pin = *uv = -1; /* unsupported */
+
+ if (fido_dev_is_fido2(dev)) {
+ if ((info = fido_cbor_info_new()) == NULL ||
+ fido_dev_get_cbor_info(dev, info) != FIDO_OK) {
+ fido_cbor_info_free(&info);
+ return 0;
+ }
+
+ ptr = fido_cbor_info_options_name_ptr(info);
+ val = fido_cbor_info_options_value_ptr(info);
+ len = fido_cbor_info_options_len(info);
+ for (size_t i = 0; i < len; i++) {
+ if (strcmp(ptr[i], "clientPin") == 0) {
+ *pin = val[i];
+ } else if (strcmp(ptr[i], "uv") == 0) {
+ *uv = val[i];
+ }
+ }
+ }
+
+ fido_cbor_info_free(&info);
+ return 1;
+}
+
+static int match_device_opts(fido_dev_t *dev, struct opts *opts) {
+ int pin, uv;
+
+ /* FIXME: fido_dev_{supports,has}_{pin,uv} (1.7.0) */
+ if (!get_device_opts(dev, &pin, &uv)) {
+ return -1;
+ }
+
+ if (opts->uv == FIDO_OPT_FALSE && uv < 0) {
+ opts->uv = FIDO_OPT_OMIT;
+ }
+
+ if ((opts->pin == FIDO_OPT_TRUE && pin != 1) ||
+ (opts->uv == FIDO_OPT_TRUE && uv != 1)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int set_opts(const cfg_t *cfg, const struct opts *opts,
+ fido_assert_t *assert) {
+ if (fido_assert_set_up(assert, opts->up) != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set UP");
+ return 0;
+ }
+ if (fido_assert_set_uv(assert, opts->uv) != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set UV");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int set_cdh(const cfg_t *cfg, fido_assert_t *assert) {
+ unsigned char cdh[32];
+ int r;
+
+ if (!random_bytes(cdh, sizeof(cdh))) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to generate challenge");
+ return 0;
+ }
+
+ r = fido_assert_set_clientdata_hash(assert, cdh, sizeof(cdh));
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to set challenge: %s (%d)", fido_strerr(r), r);
+ return 0;
+ }
+
+ return 1;
+}
+
+static fido_assert_t *prepare_assert(const cfg_t *cfg, const device_t *device,
+ const struct opts *opts) {
+ fido_assert_t *assert = NULL;
+ unsigned char *buf = NULL;
+ size_t buf_len;
+ int ok = 0;
+ int r;
+
+ if ((assert = fido_assert_new()) == NULL) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to allocate assertion");
+ goto err;
+ }
+
+ if (device->old_format && strcmp(cfg->origin, cfg->appid) != 0)
+ r = fido_assert_set_rp(assert, cfg->appid);
+ else
+ r = fido_assert_set_rp(assert, cfg->origin);
+
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to set origin: %s (%d)", fido_strerr(r), r);
+ goto err;
+ }
+
+ if (is_resident(device->keyHandle)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Credential is resident");
+ } else {
+ if (cfg->debug)
+ D(cfg->debug_file, "Key handle: %s", device->keyHandle);
+ if (!b64_decode(device->keyHandle, (void **) &buf, &buf_len)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to decode key handle");
+ goto err;
+ }
+
+ r = fido_assert_allow_cred(assert, buf, buf_len);
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unable to set keyHandle: %s (%d)", fido_strerr(r),
+ r);
+ goto err;
+ }
+ }
+
+ if (!set_opts(cfg, opts, assert)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set assert options");
+ goto err;
+ }
+
+ if (!set_cdh(cfg, assert)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set client data hash");
+ goto err;
+ }
+
+ ok = 1;
+
+err:
+ if (!ok)
+ fido_assert_free(&assert);
+
+ free(buf);
+
+ return assert;
+}
+
+static void reset_pk(struct pk *pk) {
+ if (pk->type == COSE_ES256) {
+ es256_pk_free((es256_pk_t **) &pk->ptr);
+ } else if (pk->type == COSE_RS256) {
+ rs256_pk_free((rs256_pk_t **) &pk->ptr);
+ } else if (pk->type == COSE_EDDSA) {
+ eddsa_pk_free((eddsa_pk_t **) &pk->ptr);
+ }
+ memset(pk, 0, sizeof(*pk));
+}
+
+int cose_type(const char *str, int *type) {
+ if (strcasecmp(str, "es256") == 0) {
+ *type = COSE_ES256;
+ } else if (strcasecmp(str, "rs256") == 0) {
+ *type = COSE_RS256;
+ } else if (strcasecmp(str, "eddsa") == 0) {
+ *type = COSE_EDDSA;
+ } else {
+ *type = 0;
+ return 0;
+ }
+
+ return 1;
+}
+
+const char *cose_string(int type) {
+ switch (type) {
+ case COSE_ES256:
+ return "es256";
+ case COSE_RS256:
+ return "rs256";
+ case COSE_EDDSA:
+ return "eddsa";
+ default:
+ return "unknown";
+ }
+}
+
+static int parse_pk(const cfg_t *cfg, int old, const char *type, const char *pk,
+ struct pk *out) {
+ unsigned char *buf = NULL;
+ size_t buf_len;
+ int ok = 0;
+ int r;
+
+ reset_pk(out);
+
+ if (old) {
+ if (!hex_decode(pk, &buf, &buf_len)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to decode public key");
+ goto err;
+ }
+ } else {
+ if (!b64_decode(pk, (void **) &buf, &buf_len)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to decode public key");
+ goto err;
+ }
+ }
+
+ if (!cose_type(type, &out->type)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Unknown COSE type '%s'", type);
+ goto err;
+ }
+
+ // For backwards compatibility, failure to pack the public key is not
+ // returned as an error. Instead, it is handled by fido_verify_assert().
+ if (out->type == COSE_ES256) {
+ if ((out->ptr = es256_pk_new()) == NULL) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to allocate ES256 public key");
+ goto err;
+ }
+ if (old) {
+ r = translate_old_format_pubkey(out->ptr, buf, buf_len);
+ } else {
+ r = es256_pk_from_ptr(out->ptr, buf, buf_len);
+ }
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to convert ES256 public key");
+ }
+ } else if (out->type == COSE_RS256) {
+ if ((out->ptr = rs256_pk_new()) == NULL) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to allocate RS256 public key");
+ goto err;
+ }
+ r = rs256_pk_from_ptr(out->ptr, buf, buf_len);
if (r != FIDO_OK) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to open authenticator: %s (%d)",
- fido_strerr(r), r);
- fido_dev_free(&dev);
- continue;
+ D(cfg->debug_file, "Failed to convert RS256 public key");
}
-
- if (kh == NULL || cfg->nodetect) {
- /* resident credential or nodetect: try all authenticators */
- authlist[j++] = dev;
- } else {
- r = fido_dev_get_assert(dev, assert, NULL);
- if ((!fido_dev_is_fido2(dev) && r == FIDO_ERR_USER_PRESENCE_REQUIRED) ||
- (fido_dev_is_fido2(dev) && r == FIDO_OK)) {
- authlist[j++] = dev;
- if (cfg->debug)
- D(cfg->debug_file, "Found key in authenticator %zu", i);
- return (1);
- }
+ } else if (out->type == COSE_EDDSA) {
+ if ((out->ptr = eddsa_pk_new()) == NULL) {
if (cfg->debug)
- D(cfg->debug_file, "Key not found in authenticator %zu", i);
-
- fido_dev_close(dev);
- fido_dev_free(&dev);
+ D(cfg->debug_file, "Failed to allocate EDDSA public key");
+ goto err;
}
- }
-
- if (kh == NULL && j != 0)
- return (1);
- else {
+ r = eddsa_pk_from_ptr(out->ptr, buf, buf_len);
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to convert EDDSA public key");
+ }
+ } else {
if (cfg->debug)
- D(cfg->debug_file, "Key not found");
- return (0);
+ D(cfg->debug_file, "COSE type '%s' not handled", type);
+ goto err;
}
+
+ ok = 1;
+err:
+ free(buf);
+
+ return ok;
}
int do_authentication(const cfg_t *cfg, const device_t *devices,
const unsigned n_devs, pam_handle_t *pamh) {
- es256_pk_t *es256_pk = NULL;
- rs256_pk_t *rs256_pk = NULL;
fido_assert_t *assert = NULL;
fido_dev_info_t *devlist = NULL;
fido_dev_t **authlist = NULL;
int cued = 0;
int r;
int retval = -2;
- int cose_type;
- size_t kh_len;
size_t ndevs = 0;
size_t ndevs_prev = 0;
- size_t pk_len;
- unsigned char challenge[32];
- unsigned char *kh = NULL;
- unsigned char *pk = NULL;
unsigned i = 0;
- fido_opt_t user_presence = FIDO_OPT_OMIT;
- fido_opt_t user_verification = FIDO_OPT_OMIT;
- fido_opt_t pin_verification = FIDO_OPT_OMIT;
+ struct opts opts;
+ struct pk pk;
char *pin = NULL;
+ init_opts(&opts);
+#ifndef WITH_FUZZING
fido_init(cfg->debug ? FIDO_DEBUG : 0);
+#else
+ fido_init(0);
+#endif
+ memset(&pk, 0, sizeof(pk));
devlist = fido_dev_info_new(64);
if (!devlist) {
@@ -524,21 +1347,7 @@ int do_authentication(const cfg_t *cfg,
ndevs_prev = ndevs;
if (cfg->debug)
- D(cfg->debug_file, "Device max index is %u", ndevs);
-
- es256_pk = es256_pk_new();
- if (!es256_pk) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate ES256 public key");
- goto out;
- }
-
- rs256_pk = rs256_pk_new();
- if (!rs256_pk) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate RS256 public key");
- goto out;
- }
+ D(cfg->debug_file, "Device max index is %zu", ndevs);
authlist = calloc(64 + 1, sizeof(fido_dev_t *));
if (!authlist) {
@@ -559,179 +1368,57 @@ int do_authentication(const cfg_t *cfg,
D(cfg->debug_file, "Attempting authentication with device number %d",
i + 1);
- assert = fido_assert_new();
- if (!assert) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate assertion");
- goto out;
- }
-
- r = fido_assert_set_rp(assert, cfg->origin);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set origin: %s (%d)", fido_strerr(r), r);
- goto out;
- }
-
- if (!strcmp(devices[i].keyHandle, "*")) {
- if (cfg->debug)
- D(cfg->debug_file, "Credential is resident");
- } else {
- if (cfg->debug)
- D(cfg->debug_file, "Key handle: %s", devices[i].keyHandle);
- if (!b64_decode(devices[i].keyHandle, (void **) &kh, &kh_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode key handle");
- goto out;
- }
-
- r = fido_assert_allow_cred(assert, kh, kh_len);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set keyHandle: %s (%d)", fido_strerr(r),
- r);
- goto out;
- }
- }
-
- if (devices[i].old_format) {
- if (!hex_decode(devices[i].publicKey, &pk, &pk_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode public key");
- goto out;
- }
- } else {
- if (!b64_decode(devices[i].publicKey, (void **) &pk, &pk_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode public key");
- goto out;
- }
- }
-
- if (!strcmp(devices[i].coseType, "es256")) {
- if (devices[i].old_format) {
- es256_pk = translate_old_format_pubkey(pk, pk_len);
- if (es256_pk == NULL) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to convert ES256 public key");
- }
- } else {
- r = es256_pk_from_ptr(es256_pk, pk, pk_len);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to convert ES256 public key");
- }
- }
- cose_type = COSE_ES256;
- } else if (!strcmp(devices[i].coseType, "rs256")) {
- r = rs256_pk_from_ptr(rs256_pk, pk, pk_len);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to convert RS256 public key");
- }
- cose_type = COSE_RS256;
- } else {
- if (cfg->debug)
- D(cfg->debug_file, "Unknown COSE type '%s'", devices[i].coseType);
- goto out;
- }
-
- if (cfg->userpresence == 1 || strstr(devices[i].attributes, "presence"))
- user_presence = FIDO_OPT_TRUE;
- else if (cfg->userpresence == 0)
- user_presence = FIDO_OPT_FALSE;
- else
- user_presence = FIDO_OPT_OMIT;
-
- if (cfg->userverification == 1 ||
- strstr(devices[i].attributes, "verification"))
- user_verification = FIDO_OPT_TRUE;
- else if (cfg->userverification == 0)
- user_verification = FIDO_OPT_FALSE;
- else
- user_verification = FIDO_OPT_OMIT;
-
- if (cfg->pinverification == 1 || strstr(devices[i].attributes, "pin")) {
- pin_verification = FIDO_OPT_TRUE;
- user_verification = FIDO_OPT_TRUE;
- } else if (cfg->pinverification == 0)
- pin_verification = FIDO_OPT_FALSE;
- else
- pin_verification = FIDO_OPT_OMIT;
-
- r = fido_assert_set_up(assert, FIDO_OPT_FALSE);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to set UP");
- goto out;
- }
-
- r = fido_assert_set_uv(assert, FIDO_OPT_OMIT);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to set UV");
- goto out;
- }
-
- if (!random_bytes(challenge, sizeof(challenge))) {
+ init_opts(&opts); /* used during authenticator discovery */
+ assert = prepare_assert(cfg, &devices[i], &opts);
+ if (assert == NULL) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to generate challenge");
+ D(cfg->debug_file, "Failed to prepare assert");
goto out;
}
- if (cfg->debug) {
- char *b64_challenge;
- if (!b64_encode(challenge, sizeof(challenge), &b64_challenge)) {
- D(cfg->debug_file, "Failed to encode challenge");
- } else {
- D(cfg->debug_file, "Challenge: %s", b64_challenge);
- free(b64_challenge);
- }
- }
-
- r = fido_assert_set_clientdata_hash(assert, challenge, sizeof(challenge));
- if (r != FIDO_OK) {
+ if (!parse_pk(cfg, devices[i].old_format, devices[i].coseType,
+ devices[i].publicKey, &pk)) {
if (cfg->debug)
- D(cfg->debug_file, "Unable to set challenge: %s( %d)", fido_strerr(r),
- r);
+ D(cfg->debug_file, "Failed to parse public key");
goto out;
}
- if (get_authenticators(cfg, devlist, ndevs, assert, kh, authlist)) {
+ if (get_authenticators(cfg, devlist, ndevs, assert,
+ is_resident(devices[i].keyHandle), authlist)) {
for (size_t j = 0; authlist[j] != NULL; j++) {
- r = fido_assert_set_up(assert, user_presence);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to reset UP");
- goto out;
- }
+ /* options used during authentication */
+ parse_opts(cfg, devices[i].attributes, &opts);
- r = fido_assert_set_uv(assert, user_verification);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to reset UV");
- goto out;
+ r = match_device_opts(authlist[j], &opts);
+ if (r != 1) {
+ if (cfg->debug) {
+ D(cfg->debug_file, "%s, skipping authenticator",
+ r < 0 ? "Failed to query supported options"
+ : "Unsupported options");
+ }
+ continue;
}
- if (!random_bytes(challenge, sizeof(challenge))) {
+ if (!set_opts(cfg, &opts, assert)) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to regenerate challenge");
+ D(cfg->debug_file, "Failed to set assert options");
goto out;
}
- r =
- fido_assert_set_clientdata_hash(assert, challenge, sizeof(challenge));
- if (r != FIDO_OK) {
+ if (!set_cdh(cfg, assert)) {
if (cfg->debug)
- D(cfg->debug_file, "Unable to reset challenge: %s( %d)",
- fido_strerr(r), r);
+ D(cfg->debug_file, "Failed to reset client data hash");
goto out;
}
- if (pin_verification == FIDO_OPT_TRUE)
+ if (opts.pin == FIDO_OPT_TRUE) {
pin = converse(pamh, PAM_PROMPT_ECHO_OFF, "Please enter the PIN: ");
- if (user_presence == FIDO_OPT_TRUE ||
- user_verification == FIDO_OPT_TRUE) {
+ if (pin == NULL) {
+ D(cfg->debug_file, "converse() returned NULL");
+ goto out;
+ }
+ }
+ if (opts.up == FIDO_OPT_TRUE || opts.uv == FIDO_OPT_TRUE) {
if (cfg->manual == 0 && cfg->cue && !cued) {
cued = 1;
converse(pamh, PAM_TEXT_INFO,
@@ -745,10 +1432,14 @@ int do_authentication(const cfg_t *cfg,
pin = NULL;
}
if (r == FIDO_OK) {
- r = fido_assert_verify(assert, 0, cose_type,
- cose_type == COSE_ES256
- ? (const void *) es256_pk
- : (const void *) rs256_pk);
+ if (opts.pin == FIDO_OPT_TRUE || opts.uv == FIDO_OPT_TRUE) {
+ r = fido_assert_set_uv(assert, FIDO_OPT_TRUE);
+ if (r != FIDO_OK) {
+ D(cfg->debug_file, "Failed to set UV");
+ goto out;
+ }
+ }
+ r = fido_assert_verify(assert, 0, pk.type, pk.ptr);
if (r == FIDO_OK) {
retval = 1;
goto out;
@@ -757,7 +1448,7 @@ int do_authentication(const cfg_t *cfg,
}
} else {
if (cfg->debug)
- D(cfg->debug_file, "Device for this keyhandle is not present.");
+ D(cfg->debug_file, "Device for this keyhandle is not present");
}
i++;
@@ -788,12 +1479,6 @@ int do_authentication(const cfg_t *cfg,
i = 0;
}
- free(kh);
- free(pk);
-
- kh = NULL;
- pk = NULL;
-
for (size_t j = 0; authlist[j] != NULL; j++) {
fido_dev_close(authlist[j]);
fido_dev_free(&authlist[j]);
@@ -803,8 +1488,7 @@ int do_authentication(const cfg_t *cfg,
}
out:
- es256_pk_free(&es256_pk);
- rs256_pk_free(&rs256_pk);
+ reset_pk(&pk);
fido_assert_free(&assert);
fido_dev_info_free(&devlist, ndevs);
@@ -816,174 +1500,125 @@ out:
free(authlist);
}
- free(kh);
- free(pk);
-
return retval;
}
#define MAX_PROMPT_LEN (1024)
-int do_manual_authentication(const cfg_t *cfg, const device_t *devices,
- const unsigned n_devs, pam_handle_t *pamh) {
- fido_assert_t *assert[n_devs];
- es256_pk_t *es256_pk[n_devs];
- rs256_pk_t *rs256_pk[n_devs];
- unsigned char challenge[32];
- unsigned char *kh = NULL;
- unsigned char *pk = NULL;
- unsigned char *authdata = NULL;
- unsigned char *sig = NULL;
- char *b64_challenge = NULL;
+static int manual_get_assert(const cfg_t *cfg, const char *prompt,
+ pam_handle_t *pamh, fido_assert_t *assert) {
char *b64_cdh = NULL;
char *b64_rpid = NULL;
char *b64_authdata = NULL;
char *b64_sig = NULL;
- char prompt[MAX_PROMPT_LEN];
- char buf[MAX_PROMPT_LEN];
- size_t kh_len;
- size_t pk_len;
+ unsigned char *authdata = NULL;
+ unsigned char *sig = NULL;
size_t authdata_len;
size_t sig_len;
- int cose_type[n_devs];
- int retval = -2;
- int n;
int r;
- unsigned i = 0;
- bool user_presence = false;
- bool user_verification = false;
-
- memset(assert, 0, sizeof(assert));
- memset(es256_pk, 0, sizeof(es256_pk));
- memset(rs256_pk, 0, sizeof(rs256_pk));
-
- fido_init(cfg->debug ? FIDO_DEBUG : 0);
-
- for (i = 0; i < n_devs; ++i) {
-
- assert[i] = fido_assert_new();
- if (!assert[i]) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate assertion %u", i);
- goto out;
- }
-
- r = fido_assert_set_rp(assert[i], cfg->origin);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set origin: %s (%d)", fido_strerr(r), r);
- goto out;
- }
-
- if (strstr(devices[i].attributes, "presence"))
- user_presence = true;
- if (strstr(devices[i].attributes, "verification"))
- user_verification = true;
-
- r = fido_assert_set_up(assert[i], user_presence);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set UP: %s (%d)", fido_strerr(r), r);
- goto out;
- }
+ int ok = 0;
- r = fido_assert_set_uv(assert[i], user_verification);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set UV: %s (%d)", fido_strerr(r), r);
- goto out;
- }
+ b64_cdh = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
+ b64_rpid = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
+ b64_authdata = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
+ b64_sig = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
+ if (!b64_decode(b64_authdata, (void **) &authdata, &authdata_len)) {
if (cfg->debug)
- D(cfg->debug_file, "Attempting authentication with device number %d",
- i + 1);
-
- if (!strcmp(devices[i].keyHandle, "*")) {
- if (cfg->debug)
- D(cfg->debug_file, "Credential is resident");
- } else {
- if (!b64_decode(devices[i].keyHandle, (void **) &kh, &kh_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode key handle");
- goto out;
- }
+ D(cfg->debug_file, "Failed to decode authenticator data");
+ goto err;
+ }
- r = fido_assert_allow_cred(assert[i], kh, kh_len);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to set keyHandle: %s (%d)", fido_strerr(r),
- r);
- goto out;
- }
+ if (!b64_decode(b64_sig, (void **) &sig, &sig_len)) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to decode signature");
+ goto err;
+ }
- free(kh);
- kh = NULL;
- }
+ r = fido_assert_set_count(assert, 1);
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set signature count of assertion");
+ goto err;
+ }
- if (devices[i].old_format) {
- if (!hex_decode(devices[i].publicKey, &pk, &pk_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode public key");
- goto out;
- }
- } else {
- if (!b64_decode(devices[i].publicKey, (void **) &pk, &pk_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode public key");
- goto out;
- }
- }
+ r = fido_assert_set_authdata(assert, 0, authdata, authdata_len);
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set authdata of assertion");
+ goto err;
+ }
- if (!strcmp(devices[i].coseType, "es256")) {
- es256_pk[i] = es256_pk_new();
- if (!es256_pk[i]) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate key %u", i);
- goto out;
- }
+ r = fido_assert_set_sig(assert, 0, sig, sig_len);
+ if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Failed to set signature of assertion");
+ goto err;
+ }
- if (es256_pk_from_ptr(es256_pk[i], pk, pk_len) != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to convert public key");
- goto out;
- }
+ ok = 1;
+err:
+ free(b64_cdh);
+ free(b64_rpid);
+ free(b64_authdata);
+ free(b64_sig);
+ free(authdata);
+ free(sig);
- cose_type[i] = COSE_ES256;
- } else {
- rs256_pk[i] = rs256_pk_new();
- if (!rs256_pk[i]) {
- if (cfg->debug)
- D(cfg->debug_file, "Unable to allocate key %u", i);
- goto out;
- }
+ return ok;
+}
- if (rs256_pk_from_ptr(rs256_pk[i], pk, pk_len) != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to convert public key");
- goto out;
- }
+int do_manual_authentication(const cfg_t *cfg, const device_t *devices,
+ const unsigned n_devs, pam_handle_t *pamh) {
+ fido_assert_t **assert = NULL;
+ struct pk *pk = NULL;
+ char *b64_challenge = NULL;
+ char prompt[MAX_PROMPT_LEN];
+ char buf[MAX_PROMPT_LEN];
+ int retval = -2;
+ int n;
+ int r;
+ unsigned i = 0;
+ struct opts opts;
- cose_type[i] = COSE_RS256;
- }
+ init_opts(&opts);
+ assert = calloc(n_devs, sizeof(*assert));
+ if (assert == NULL)
+ goto out;
+ pk = calloc(n_devs, sizeof(*pk));
+ if (pk == NULL)
+ goto out;
- free(pk);
- pk = NULL;
+#ifndef WITH_FUZZING
+ fido_init(cfg->debug ? FIDO_DEBUG : 0);
+#else
+ fido_init(0);
+#endif
- if (!random_bytes(challenge, sizeof(challenge))) {
+ for (i = 0; i < n_devs; ++i) {
+ /* options used during authentication */
+ parse_opts(cfg, devices[i].attributes, &opts);
+ assert[i] = prepare_assert(cfg, &devices[i], &opts);
+ if (assert[i] == NULL) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to generate challenge");
+ D(cfg->debug_file, "Failed to prepare assert");
goto out;
}
- r =
- fido_assert_set_clientdata_hash(assert[i], challenge, sizeof(challenge));
- if (r != FIDO_OK) {
+ if (cfg->debug)
+ D(cfg->debug_file, "Attempting authentication with device number %d",
+ i + 1);
+
+ if (!parse_pk(cfg, devices[i].old_format, devices[i].coseType,
+ devices[i].publicKey, &pk[i])) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to set challenge");
+ D(cfg->debug_file, "Unable to parse public key %u", i);
goto out;
}
- if (!b64_encode(challenge, sizeof(challenge), &b64_challenge)) {
+ if (!b64_encode(fido_assert_clientdata_hash_ptr(assert[i]),
+ fido_assert_clientdata_hash_len(assert[i]),
+ &b64_challenge)) {
if (cfg->debug)
D(cfg->debug_file, "Failed to encode challenge");
goto out;
@@ -1029,65 +1664,13 @@ int do_manual_authentication(const cfg_t
goto out;
}
- b64_cdh = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
- b64_rpid = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
- b64_authdata = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
- b64_sig = converse(pamh, PAM_PROMPT_ECHO_ON, prompt);
-
- if (!b64_decode(b64_authdata, (void **) &authdata, &authdata_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode authenticator data");
- goto out;
- }
-
- if (!b64_decode(b64_sig, (void **) &sig, &sig_len)) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to decode signature");
- goto out;
- }
-
- free(b64_cdh);
- free(b64_rpid);
- free(b64_authdata);
- free(b64_sig);
-
- b64_cdh = NULL;
- b64_rpid = NULL;
- b64_authdata = NULL;
- b64_sig = NULL;
-
- r = fido_assert_set_count(assert[i], 1);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to set signature count of assertion %u", i);
- goto out;
- }
-
- r = fido_assert_set_authdata(assert[i], 0, authdata, authdata_len);
- if (r != FIDO_OK) {
+ if (!manual_get_assert(cfg, prompt, pamh, assert[i])) {
if (cfg->debug)
- D(cfg->debug_file, "Failed to set authdata of assertion %u", i);
+ D(cfg->debug_file, "Failed to get assert %u", i);
goto out;
}
- r = fido_assert_set_sig(assert[i], 0, sig, sig_len);
- if (r != FIDO_OK) {
- if (cfg->debug)
- D(cfg->debug_file, "Failed to set signature of assertion %u", i);
- goto out;
- }
-
- free(authdata);
- free(sig);
-
- authdata = NULL;
- sig = NULL;
-
- if (cose_type[i] == COSE_ES256)
- r = fido_assert_verify(assert[i], 0, COSE_ES256, es256_pk[i]);
- else
- r = fido_assert_verify(assert[i], 0, COSE_RS256, rs256_pk[i]);
-
+ r = fido_assert_verify(assert[i], 0, pk[i].type, pk[i].ptr);
if (r == FIDO_OK) {
retval = 1;
break;
@@ -1097,19 +1680,11 @@ int do_manual_authentication(const cfg_t
out:
for (i = 0; i < n_devs; i++) {
fido_assert_free(&assert[i]);
- es256_pk_free(&es256_pk[i]);
- rs256_pk_free(&rs256_pk[i]);
+ reset_pk(&pk[i]);
}
-
- free(kh);
+ free(assert);
free(pk);
free(b64_challenge);
- free(b64_cdh);
- free(b64_rpid);
- free(b64_authdata);
- free(b64_sig);
- free(authdata);
- free(sig);
return retval;
}
@@ -1131,7 +1706,7 @@ static int _converse(pam_handle_t *pamh,
char *converse(pam_handle_t *pamh, int echocode, const char *prompt) {
const struct pam_message msg = {.msg_style = echocode,
- .msg = (char *)(uintptr_t)prompt};
+ .msg = (char *) (uintptr_t) prompt};
const struct pam_message *msgs = &msg;
struct pam_response *resp = NULL;
int retval = _converse(pamh, 1, &msgs, &resp);
@@ -1163,7 +1738,12 @@ void _debug(FILE *debug_file, const char
const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
-#ifdef LOG_DEBUG
+
+#if defined(WITH_FUZZING)
+ (void) debug_file;
+ snprintf(NULL, 0, DEBUG_STR, file, line, func);
+ vsnprintf(NULL, 0, fmt, ap);
+#elif defined(LOG_DEBUG)
if (debug_file == (FILE *) -1) {
syslog(LOG_AUTHPRIV | LOG_DEBUG, DEBUG_STR, file, line, func);
vsyslog(LOG_AUTHPRIV | LOG_DEBUG, fmt, ap);