Bugzilla – Attachment 2512 Details for
Bug 2319
[PATCH REVIEW] U2F authentication
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
patch 2/2
0002-add-u2f-auth-module-not-done-yet.patch (text/plain), 41.45 KB, created by
Michael Stapelberg
on 2014-11-19 06:54:11 AEDT
(
hide
)
Description:
patch 2/2
Filename:
MIME Type:
Creator:
Michael Stapelberg
Created:
2014-11-19 06:54:11 AEDT
Size:
41.45 KB
patch
obsolete
>From c63e98108ca5fbe2e4ec9de87b93a1a91dad24d7 Mon Sep 17 00:00:00 2001 >From: Michael Stapelberg <michael@stapelberg.de> >Date: Mon, 27 Oct 2014 09:51:43 +0100 >Subject: [PATCH 2/2] add u2f auth module (not done yet) > >--- > Makefile.in | 1 + > audit-linux.c | 1 + > audit.c | 3 + > audit.h | 1 + > auth-u2f.c | 561 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > auth.h | 11 ++ > auth2.c | 6 + > config.h.in | 11 +- > monitor.c | 63 +++++++ > monitor.h | 2 + > monitor_wrap.c | 59 ++++++ > monitor_wrap.h | 4 + > readconf.h | 1 + > servconf.c | 17 ++ > servconf.h | 1 + > ssh.c | 10 + > sshconnect2.c | 160 ++++++++++++++++ > sshd.c | 1 + > sshkey.c | 85 +++++++++ > sshkey.h | 6 + > 20 files changed, 1001 insertions(+), 3 deletions(-) > create mode 100644 auth-u2f.c > >diff --git a/Makefile.in b/Makefile.in >index 06be3d5..4ae423c 100644 >--- a/Makefile.in >+++ b/Makefile.in >@@ -101,6 +101,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ > auth2-none.o auth2-passwd.o auth2-pubkey.o \ > monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o kexecdhs.o \ > kexc25519s.o auth-krb5.o \ >+ auth-u2f.o \ > auth2-gss.o gss-serv.o gss-serv-krb5.o \ > loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ > sftp-server.o sftp-common.o \ >diff --git a/audit-linux.c b/audit-linux.c >index b3ee2f4..c07cb7c 100644 >--- a/audit-linux.c >+++ b/audit-linux.c >@@ -113,6 +113,7 @@ audit_event(ssh_audit_event_t event) > case SSH_AUTH_FAIL_PUBKEY: > case SSH_AUTH_FAIL_HOSTBASED: > case SSH_AUTH_FAIL_GSSAPI: >+ case SSH_AUTH_FAIL_U2F: > case SSH_INVALID_USER: > linux_audit_record_event(-1, audit_username(), NULL, > get_remote_ipaddr(), "sshd", 0); >diff --git a/audit.c b/audit.c >index ced57fa..ddb949b 100644 >--- a/audit.c >+++ b/audit.c >@@ -63,6 +63,8 @@ audit_classify_auth(const char *method) > return SSH_AUTH_FAIL_HOSTBASED; > else if (strcmp(method, "gssapi-with-mic") == 0) > return SSH_AUTH_FAIL_GSSAPI; >+ else if (strcmp(method, "u2f") == 0) >+ return SSH_AUTH_FAIL_U2F; > else > return SSH_AUDIT_UNKNOWN; > } >@@ -98,6 +100,7 @@ audit_event_lookup(ssh_audit_event_t ev) > {SSH_AUTH_FAIL_PUBKEY, "AUTH_FAIL_PUBKEY"}, > {SSH_AUTH_FAIL_HOSTBASED, "AUTH_FAIL_HOSTBASED"}, > {SSH_AUTH_FAIL_GSSAPI, "AUTH_FAIL_GSSAPI"}, >+ {SSH_AUTH_FAIL_U2F, "AUTH_FAIL_U2F"}, > {SSH_INVALID_USER, "INVALID_USER"}, > {SSH_NOLOGIN, "NOLOGIN"}, > {SSH_CONNECTION_CLOSE, "CONNECTION_CLOSE"}, >diff --git a/audit.h b/audit.h >index 92ede5b..f99191a 100644 >--- a/audit.h >+++ b/audit.h >@@ -39,6 +39,7 @@ enum ssh_audit_event_type { > SSH_AUTH_FAIL_PUBKEY, /* ssh2 pubkey or ssh1 rsa */ > SSH_AUTH_FAIL_HOSTBASED, /* ssh2 hostbased or ssh1 rhostsrsa */ > SSH_AUTH_FAIL_GSSAPI, >+ SSH_AUTH_FAIL_U2F, > SSH_INVALID_USER, > SSH_NOLOGIN, /* denied by /etc/nologin, not implemented */ > SSH_CONNECTION_CLOSE, /* closed after attempting auth or session */ >diff --git a/auth-u2f.c b/auth-u2f.c >new file mode 100644 >index 0000000..07b8523 >--- /dev/null >+++ b/auth-u2f.c >@@ -0,0 +1,561 @@ >+#include "includes.h" >+ >+#ifdef U2F >+ >+#include <ctype.h> >+#include <openssl/x509.h> >+#include <u2f-host.h> >+#include <fcntl.h> >+ >+#include "key.h" >+#include "hostfile.h" >+#include "auth.h" >+#include "ssh.h" >+#include "ssh2.h" >+#include "log.h" >+#include "dispatch.h" >+#include "misc.h" >+#include "servconf.h" >+#include "packet.h" >+#include "digest.h" >+#include "xmalloc.h" >+#include "monitor_wrap.h" >+ >+// Evaluates to the maximum size that base64-encoding 'size' bytes can have, >+// including one byte for a trailing NULL byte. >+#define BASE64_ENCODED_SIZE(size) (((size)+2)/3)*4 + 1 >+ >+// Evaluates to the maximum size that base64-decoding 'size' bytes can have. >+#define BASE64_DECODED_SIZE(size) (size * 3/4) >+ >+extern ServerOptions options; >+ >+static void input_userauth_u2f_auth_response(int, u_int32_t, void *); >+static void input_userauth_u2f_register_response(int type, u_int32_t seq, void *ctxt); >+ >+static const int u2f_challenge_len = 32; >+// TODO: to what should we set the appid? >+static const char *appid = "ssh://localhost"; >+ >+void u2f_sha256(u_char *dest, u_char *src, size_t srclen) { >+ struct ssh_digest_ctx *ctx = ssh_digest_start(SSH_DIGEST_SHA256); >+ ssh_digest_update(ctx, src, srclen); >+ ssh_digest_final(ctx, dest, ssh_digest_bytes(SSH_DIGEST_SHA256)); >+} >+ >+/* We can get away without a JSON parser because all values in the JSON >+ * messages used in U2F are (websafe) base64 encoded, therefore we donât need >+ * to care about escaping at all. We can just look for the starting double >+ * quote and take everything until the next double quote. >+ */ >+static char * >+extract_json_string(const char *json, const char *key) >+{ >+ char *quotedkey; >+ char *keypos; >+ char *value; >+ char *end; >+ int quotedkeylen; >+ >+ quotedkeylen = xasprintf("edkey, "\"%s\"", key); >+ if ((keypos = strstr(json, quotedkey)) == NULL) >+ return NULL; >+ >+ keypos += quotedkeylen; >+ if (*keypos == ':') >+ keypos++; >+ while (*keypos != '\0' && isspace(*keypos)) >+ keypos++; >+ if (*keypos != '"') >+ return NULL; >+ keypos++; >+ value = xstrdup(keypos); >+ if ((end = strchr(value, '"')) == NULL) { >+ free(value); >+ return NULL; >+ } >+ *end = '\0'; >+ return value; >+} >+ >+static int >+urlsafe_base64_decode(const char *base64, u_char *buffer, size_t bufferlen) >+{ >+ // U2F uses urlsafe base64, which replaces + with - and / with _, so we >+ // need to revert that before base64 decoding. >+ char *replaced; >+ char *pos; >+ >+ replaced = xstrdup(base64); >+ while ((pos = strchr(replaced, '-')) != NULL) >+ *pos = '+'; >+ while ((pos = strchr(replaced, '_')) != NULL) >+ *pos = '/'; >+ >+ return b64_pton(replaced, buffer, bufferlen); >+} >+ >+static int >+urlsafe_base64_encode(u_char const *src, size_t srclength, char *target, size_t targsize) >+{ >+ char *pos; >+ int len; >+ >+ if ((len = b64_ntop(src, srclength, target, targsize)) == -1) >+ return -1; >+ >+ while ((pos = strchr(target, '+')) != NULL) >+ *pos = '-'; >+ >+ while ((pos = strchr(target, '/')) != NULL) >+ *pos = '_'; >+ >+ return len; >+} >+ >+static Key* >+read_keyfile(FILE *fp, char *filename, struct passwd *pw, u_long *linenum) >+{ >+ // TODO: do we need to use a different constant here? >+ char line[SSH_MAX_PUBKEY_BYTES]; >+ Key *found = NULL; >+ >+ while (read_keyfile_line(fp, filename, line, sizeof(line), linenum) != -1) { >+ char *cp, *key_options; >+ if (found != NULL) >+ key_free(found); >+ found = key_new(KEY_U2F); >+ // TODO: auth_clear_options();? >+ >+ /* Skip leading whitespace, empty and comment lines. */ >+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++) >+ ; >+ if (!*cp || *cp == '\n' || *cp == '#') >+ continue; >+ >+ debug("reading key from line %lu", *linenum); >+ if (key_read(found, &cp) != 1) { >+ debug("key_read failed, skipping line %lu", *linenum); >+ continue; >+ } >+ debug("key type: %d (u2f = %d)", found->type, KEY_U2F); >+ if (found->type == KEY_U2F) { >+ //if (key_equal(found, key)) { >+ //if (auth_parse_options(pw, key_options, filename, *linenum) != 1) >+ // continue; >+ // TODO: calculate and display a fingerprint of the key handle and pubkey? >+ debug("matching key found: file %s, line %lu", filename, *linenum); >+ // TODO: store multiple matches in authctx->methoddata, or rather authctxt->keys? (see sshconnect2.c) >+ return found; >+ } >+ } >+ return NULL; >+} >+ >+/* >+ * Read a key from the key files. >+ */ >+Key* >+read_user_u2f_key(struct passwd *pw, u_int key_idx) >+{ >+ size_t i; >+ // TODO: It might not be safe to pass the key back to the unprivileged >+ // process. It probably is, but we should review this. >+ >+ // In the first step, we need to go through all u2f keys that we have and >+ // collect their key handles. >+ for (i = 0; i < options.num_authkeys_files; i++) { >+ FILE *fp; >+ char *file; >+ Key *key = NULL; >+ u_long linenum = 0; >+ if (strcasecmp(options.authorized_keys_files[i], "none") == 0) >+ continue; >+ file = expand_authorized_keys(options.authorized_keys_files[i], pw); >+ debug("need to check %s", file); >+ fp = fopen(file, "r"); >+ do >+ { >+ // TODO: Hackish way to allow getting more than one key >+ key_free(key); >+ key = read_keyfile(fp, file, pw, &linenum); >+ } >+ while(key_idx-- > 0); >+ fclose(fp); >+ free(file); >+ if (key != NULL) >+ return key; >+ } >+ return NULL; >+} >+ >+static int >+userauth_u2f_register(Authctxt *authctxt) >+{ >+ u_char random[u2f_challenge_len]; >+ char challenge[BASE64_ENCODED_SIZE(sizeof(random))]; >+ char *json; >+ >+ arc4random_buf(random, sizeof(random)); >+ if (urlsafe_base64_encode(random, sizeof(random), challenge, sizeof(challenge)) == -1) >+ fatal("urlsafe_base64_encode(arc4random_buf()) failed"); >+ >+ xasprintf(&json, "{\"challenge\": \"%s\", \"version\": \"U2F_V2\", \"appId\": \"%s\"}", >+ challenge, appid); >+ >+ packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST); >+ packet_put_cstring(json); >+ packet_send(); >+ free(json); >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, >+ &input_userauth_u2f_register_response); >+ authctxt->postponed = 1; >+ return 0; >+} >+ >+static int >+userauth_u2f_authenticate(Authctxt *authctxt) >+{ >+ char challenge[BASE64_ENCODED_SIZE(u2f_challenge_len)]; >+ char pubkey[BASE64_ENCODED_SIZE(U2F_PUBKEY_LEN)]; >+ char *keyhandle; >+ char *json; >+ Key *key; >+ u_int idx = 0; >+ >+ // Get multiple keys by increasing idx until key == NULL >+ // TODO: send multiple challenges for all keys (or something) >+ if ((key = PRIVSEP(read_user_u2f_key(authctxt->pw, idx))) == NULL) { >+ error("U2F authentication impossible: no ssh-u2f keys found in the authorized keys file(s)."); >+ return (0); >+ } >+ >+ packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST); >+ authctxt->u2f_challenge = xmalloc(u2f_challenge_len); >+ arc4random_buf(authctxt->u2f_challenge, u2f_challenge_len); >+ authctxt->u2f_key = key; >+ >+ if (urlsafe_base64_encode(authctxt->u2f_challenge, u2f_challenge_len, >+ challenge, sizeof(challenge)) == -1) >+ fatal("urlsafe_base64_encode(arc4random_buf()) failed"); >+ >+ if (urlsafe_base64_encode(key->u2f_pubkey, U2F_PUBKEY_LEN, pubkey, sizeof(pubkey)) == -1) >+ fatal("urlsafe_base64_encode(key->u2f_pubkey) failed"); >+ >+ keyhandle = xmalloc(BASE64_ENCODED_SIZE(key->u2f_key_handle_len)); >+ if (urlsafe_base64_encode(key->u2f_key_handle, key->u2f_key_handle_len, >+ keyhandle, BASE64_ENCODED_SIZE(key->u2f_key_handle_len)) == -1) >+ fatal("urlsafe_base64_encode(key->u2f_key_handle) failed"); >+ >+ xasprintf(&json, "{\"challenge\": \"%s\", \"keyHandle\": \"%s\", \"appId\": \"%s\"}", >+ challenge, keyhandle, appid); >+ packet_put_cstring(json); >+ free(json); >+ free(keyhandle); >+ packet_send(); >+ >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, >+ &input_userauth_u2f_auth_response); >+ authctxt->postponed = 1; >+ return (0); >+} >+ >+static int >+userauth_u2f(Authctxt *authctxt) >+{ >+ int mode = packet_get_int(); >+ packet_check_eom(); >+ // TODO: shared constants >+ if (mode == 0) { >+ debug("Starting U2F registration"); >+ return userauth_u2f_register(authctxt); >+ } else if (mode == 1) { >+ debug("Starting U2F authentication"); >+ return userauth_u2f_authenticate(authctxt); >+ } else { >+ error("Unknown U2F mode %d requested by the client.", mode); >+ return 0; >+ } >+} >+ >+static void >+input_userauth_u2f_register_response(int type, u_int32_t seq, void *ctxt) >+{ >+#define u2f_bounds_check(necessary_bytes) do { \ >+ if (restlen < necessary_bytes) { \ >+ error("U2F response too short: need %d bytes, but only %d remaining", \ >+ necessary_bytes, restlen); \ >+ goto out; \ >+ } \ >+} while (0) >+ >+#define u2f_advance(parsed_bytes) do { \ >+ int advance = parsed_bytes; \ >+ walk += advance; \ >+ restlen -= advance; \ >+} while (0) >+ >+ Authctxt *authctxt = ctxt; >+ char *response, *regdata = NULL, *clientdata = NULL; >+ u_char *decoded = NULL; >+ u_char *walk = NULL; >+ u_char *keyhandle = NULL; >+ u_char *pubkey = NULL; >+ u_char *signature = NULL; >+ u_char *dummy = NULL; >+ u_char *cdecoded = NULL; >+ X509 *x509 = NULL; >+ EVP_PKEY *pkey = NULL; >+ EVP_MD_CTX mdctx; >+ int restlen; >+ int khlen; >+ int cdecodedlen; >+ int err; >+ char errorbuf[4096]; >+ u_char digest[ssh_digest_bytes(SSH_DIGEST_SHA256)]; >+ >+ authctxt->postponed = 0; >+ >+ response = packet_get_string(NULL); >+ packet_check_eom(); >+ if ((regdata = extract_json_string(response, "registrationData")) == NULL) { >+ error("U2F Response not JSON, or does not contain \"registrationData\""); >+ goto out; >+ } >+ >+ decoded = xmalloc(BASE64_DECODED_SIZE(strlen(regdata))); >+ restlen = urlsafe_base64_decode(regdata, decoded, BASE64_DECODED_SIZE(strlen(regdata))); >+ walk = decoded; >+ >+ // Header (magic byte) >+ u2f_bounds_check(1); >+ if (walk[0] != 0x05) { >+ error("U2F response does not start with magic byte 0x05"); >+ goto out; >+ } >+ u2f_advance(1); >+ >+ // Length of the public key >+ u2f_bounds_check(U2F_PUBKEY_LEN); >+ pubkey = walk; >+ u2f_advance(U2F_PUBKEY_LEN); >+ >+ // Length of the key handle >+ u2f_bounds_check(1); >+ khlen = walk[0]; >+ u2f_advance(1); >+ >+ // Key handle >+ u2f_bounds_check(khlen); >+ keyhandle = walk; >+ u2f_advance(khlen); >+ >+ // Attestation certificate >+ u2f_bounds_check(1); >+ signature = walk; >+ if ((x509 = d2i_X509(NULL, &signature, restlen)) == NULL) { >+ error("U2F response contains an invalid attestation certificate."); >+ goto out; >+ } >+ >+ // U2F dictates that the length of the certificate should be determined by >+ // encoding the certificate using DER. >+ u2f_advance(i2d_X509(x509, &dummy)); >+ free(dummy); >+ >+ // Ensure we have at least one byte of signature. >+ u2f_bounds_check(1); >+ >+ if ((clientdata = extract_json_string(response, "clientData")) == NULL) { >+ error("U2F response JSON lacks the \"clientData\" key."); >+ goto out; >+ } >+ >+ cdecoded = xmalloc(BASE64_DECODED_SIZE(strlen(clientdata))); >+ cdecodedlen = urlsafe_base64_decode(clientdata, cdecoded, BASE64_DECODED_SIZE(strlen(clientdata))); >+ pkey = X509_get_pubkey(x509); >+ >+ if ((err = EVP_VerifyInit(&mdctx, EVP_ecdsa())) != 1) { >+ ERR_error_string(ERR_get_error(), errorbuf); >+ fatal("EVP_VerifyInit() failed: %s (reason: %s)", >+ errorbuf, ERR_reason_error_string(err)); >+ } >+ EVP_VerifyUpdate(&mdctx, "\0", 1); >+ u2f_sha256(digest, appid, strlen(appid)); >+ EVP_VerifyUpdate(&mdctx, digest, sizeof(digest)); >+ u2f_sha256(digest, cdecoded, cdecodedlen); >+ EVP_VerifyUpdate(&mdctx, digest, sizeof(digest)); >+ EVP_VerifyUpdate(&mdctx, keyhandle, khlen); >+ EVP_VerifyUpdate(&mdctx, pubkey, U2F_PUBKEY_LEN); >+ >+ if ((err = EVP_VerifyFinal(&mdctx, walk, restlen, pkey)) == -1) { >+ ERR_error_string(ERR_get_error(), errorbuf); >+ error("Verifying the U2F registration signature failed: %s (reason: %s)", >+ errorbuf, ERR_reason_error_string(err)); >+ goto out; >+ } >+ >+ { >+ /* Send the client a ssh-u2f line to append to the authorized_keys file >+ * (in order to register the security key that was just used). */ >+ char *authorizedkey; >+ char key[U2F_PUBKEY_LEN + khlen]; >+ char key64[BASE64_ENCODED_SIZE(sizeof(key))]; >+ >+ memcpy(key, pubkey, U2F_PUBKEY_LEN); >+ memcpy(key+U2F_PUBKEY_LEN, keyhandle, khlen); >+ >+ if (b64_ntop(key, sizeof(key), key64, sizeof(key64)) == -1) >+ fatal("b64_ntop(key)"); >+ >+ xasprintf(&authorizedkey, "ssh-u2f %s my security key", key64); >+ packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST); >+ packet_put_cstring(authorizedkey); >+ packet_send(); >+ free(authorizedkey); >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL); >+ } >+ >+out: >+ free(regdata); >+ free(clientdata); >+ free(decoded); >+ free(cdecoded); >+ if (x509 != NULL) >+ X509_free(x509); >+ if (pkey != NULL) >+ EVP_PKEY_free(pkey); >+ userauth_finish(authctxt, 0, "u2f", NULL); >+#undef u2f_bounds_check >+#undef u2f_advance >+} >+ >+int >+verify_u2f_user(Key *key, u_char *dgst, size_t dgstlen, u_char *sig, size_t siglen) >+{ >+ char errorbuf[4096]; >+ int ret = 0; >+ EC_KEY *ec; >+ u_char *p; >+ /* To save bytes, the (common) public key prefix is not included in U2F >+ * messages itself. */ >+ const int prefix_len = 26; >+ const int total_len = U2F_PUBKEY_LEN + prefix_len; >+ u_char user_pubkey[total_len] = >+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a" >+ "\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00"; >+ >+ memcpy(user_pubkey+prefix_len, key->u2f_pubkey, U2F_PUBKEY_LEN); >+ >+ p = user_pubkey; >+ if ((ec = d2i_EC_PUBKEY(NULL, &p, total_len)) == NULL) { >+ ERR_error_string(ERR_get_error(), errorbuf); >+ error("Verifying U2F authentication signature failed: " >+ "d2i_EC_PUBKEY() failed: %s (reason: %s)", >+ errorbuf, ERR_reason_error_string(ERR_get_error())); >+ return 0; >+ } >+ >+ if ((ret = ECDSA_verify(0, dgst, dgstlen, sig, siglen, ec)) == -1) { >+ ERR_error_string(ERR_get_error(), errorbuf); >+ error("Verifying U2F authentication signature failed: " >+ "ECDSA_verify() failed: %s (reason: %s)", >+ errorbuf, ERR_reason_error_string(ERR_get_error())); >+ goto out; >+ } >+ >+ debug("U2F authentication signature verified."); >+ >+out: >+ EC_KEY_free(ec); >+ return (ret == 1); >+} >+ >+// TODO: can we send multiple authrequests at the same time, so that we donât >+// need multiple round-trips but still support multiple security keys >+// TODO: use auth_info() so that in log messages about accepted auths we will see a message that identifies the key. perhaps we can just use the human readable suffix that you can specify in the authorized_keys file(s)? >+static void >+input_userauth_u2f_auth_response(int type, u_int32_t seq, void *ctxt) >+{ >+ int authenticated = 0; >+ Authctxt *authctxt = ctxt; >+ u_char digest[ssh_digest_bytes(SSH_DIGEST_SHA256)]; >+ char *sig = NULL; >+ char *clientdata = NULL; >+ u_char *decoded = NULL; >+ int decodedlen; >+ u_char *cdecoded = NULL; >+ int cdecodedlen; >+ char *resp = packet_get_string(NULL); >+ packet_check_eom(); >+ >+ if ((sig = extract_json_string(resp, "signatureData")) == NULL) { >+ error("U2F Response not JSON, or does not contain \"signatureData\""); >+ goto out; >+ } >+ >+ if (*sig == '\0') { >+ error("U2F authentication failed: empty signature. " >+ "Probably the key is not registered (i.e. the configured " >+ "key handle/pubkey do not exist on the security key you are using)"); >+ goto out; >+ } >+ >+ decoded = xmalloc(BASE64_DECODED_SIZE(strlen(sig))); >+ decodedlen = urlsafe_base64_decode(sig, decoded, BASE64_DECODED_SIZE(strlen(sig))); >+ // Ensure that the user presence byte, the counter and at least one byte of >+ // signature are present. >+ if (decodedlen <= (sizeof(u_char) + sizeof(u_int32_t))) { >+ error("Decoded U2F signature too short (%d bytes, expected more than %d bytes)", >+ decodedlen, sizeof(u_char) + sizeof(u_int32_t)); >+ goto out; >+ } >+ if ((decoded[0] & 0x01) != 0x01) { >+ error("No user presence detected. Please touch your security key upon " >+ "being prompted when retrying."); >+ goto out; >+ } >+ u_int32_t counter = ntohl(*((u_int32_t*)(decoded + sizeof(u_char)))); >+ // TODO: Ideally, we would verify that this counter never decreases to >+ // detect cloned security keys. >+ debug("usage counter = %d\n", counter); >+ >+ struct sha_digest_ctx *sha256ctx = ssh_digest_start(SSH_DIGEST_SHA256); >+ u2f_sha256(digest, appid, strlen(appid)); >+ ssh_digest_update(sha256ctx, digest, sizeof(digest)); >+ ssh_digest_update(sha256ctx, decoded, sizeof(u_char)); >+ ssh_digest_update(sha256ctx, decoded+1, 4 * sizeof(u_char)); >+ >+ if ((clientdata = extract_json_string(resp, "clientData")) == NULL) { >+ error("U2F response JSON lacks the \"clientData\" key."); >+ goto out; >+ } >+ >+ // TODO: verify that the challenge and appid is identical to what we sent out, to verify that there was no man in the middle. >+ >+ cdecoded = xmalloc(BASE64_DECODED_SIZE(strlen(clientdata))); >+ cdecodedlen = urlsafe_base64_decode(clientdata, cdecoded, BASE64_DECODED_SIZE(strlen(clientdata))); >+ u2f_sha256(digest, cdecoded, cdecodedlen); >+ ssh_digest_update(sha256ctx, digest, sizeof(digest)); >+ ssh_digest_final(sha256ctx, digest, sizeof(digest)); >+ >+ authenticated = PRIVSEP(verify_u2f_user( >+ authctxt->u2f_key, digest, sizeof(digest), decoded+5, decodedlen-5)); >+ >+ authctxt->postponed = 0; >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL); >+out: >+ free(sig); >+ free(clientdata); >+ free(decoded); >+ free(cdecoded); >+ userauth_finish(authctxt, authenticated, "u2f", NULL); >+} >+ >+Authmethod method_u2f = { >+ "u2f", >+ userauth_u2f, >+ &options.u2f_authentication >+}; >+ >+#endif /* U2F */ >diff --git a/auth.h b/auth.h >index d081c94..2bba47d 100644 >--- a/auth.h >+++ b/auth.h >@@ -73,6 +73,10 @@ struct Authctxt { > char *krb5_ticket_file; > char *krb5_ccname; > #endif >+#ifdef U2F >+ Key *u2f_key; >+ u_char *u2f_challenge; >+#endif > Buffer *loginmsg; > void *methoddata; > }; >@@ -124,6 +128,13 @@ int user_key_allowed(struct passwd *, Key *); > void pubkey_auth_info(Authctxt *, const Key *, const char *, ...) > __attribute__((__format__ (printf, 3, 4))); > >+#ifdef U2F >+#define U2F_PUBKEY_LEN 65 >+ >+Key *read_user_u2f_key(struct passwd *, u_int); >+int verify_u2f_user(Key *, u_char *, size_t, u_char *, size_t); >+#endif >+ > struct stat; > int auth_secure_path(const char *, struct stat *, const char *, uid_t, > char *, size_t); >diff --git a/auth2.c b/auth2.c >index d9b440a..8cf5991 100644 >--- a/auth2.c >+++ b/auth2.c >@@ -72,6 +72,9 @@ extern Authmethod method_hostbased; > #ifdef GSSAPI > extern Authmethod method_gssapi; > #endif >+#ifdef U2F >+extern Authmethod method_u2f; >+#endif > > Authmethod *authmethods[] = { > &method_none, >@@ -79,6 +82,9 @@ Authmethod *authmethods[] = { > #ifdef GSSAPI > &method_gssapi, > #endif >+#ifdef U2F >+ &method_u2f, >+#endif > &method_passwd, > &method_kbdint, > &method_hostbased, >diff --git a/config.h.in b/config.h.in >index eb72af8..6cdb6b6 100644 >--- a/config.h.in >+++ b/config.h.in >@@ -1555,9 +1555,6 @@ > /* Define if you want S/Key support */ > #undef SKEY > >-/* Define if you want U2F support */ >-#undef U2F >- > /* Define if your skeychallenge() function takes 4 arguments (NetBSD) */ > #undef SKEYCHALLENGE_4ARG > >@@ -1610,6 +1607,9 @@ > /* syslog_r function is safe to use in in a signal handler */ > #undef SYSLOG_R_SAFE_IN_SIGHAND > >+/* Enable U2F support (using libu2f-host) */ >+#undef U2F >+ > /* Support passwords > 8 chars */ > #undef UNIXWARE_LONG_PASSWORDS > >@@ -1689,6 +1689,11 @@ > /* Define if xauth is found in your path */ > #undef XAUTH_PATH > >+/* Enable large inode numbers on Mac OS X 10.5. */ >+#ifndef _DARWIN_USE_64_BIT_INODE >+# define _DARWIN_USE_64_BIT_INODE 1 >+#endif >+ > /* Number of bits in a file offset, on hosts where this is settable. */ > #undef _FILE_OFFSET_BITS > >diff --git a/monitor.c b/monitor.c >index dbe29f1..37adbfd 100644 >--- a/monitor.c >+++ b/monitor.c >@@ -185,6 +185,11 @@ int mm_answer_audit_event(int, Buffer *); > int mm_answer_audit_command(int, Buffer *); > #endif > >+#ifdef U2F >+int mm_answer_read_user_u2f_key(int, Buffer *); >+int mm_answer_verify_u2f_user(int, Buffer *); >+#endif >+ > static int monitor_read_log(struct monitor *); > > static Authctxt *authctxt; >@@ -256,6 +261,10 @@ struct mon_table mon_dispatch_proto20[] = { > {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, > {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, > #endif >+#ifdef U2F >+ {MONITOR_REQ_READUSERU2FKEY, MON_ISAUTH, mm_answer_read_user_u2f_key}, >+ {MONITOR_REQ_VERIFYU2FUSER, MON_AUTH, mm_answer_verify_u2f_user}, >+#endif > {0, 0, NULL} > }; > >@@ -1752,6 +1761,7 @@ mm_answer_audit_event(int socket, Buffer *m) > case SSH_AUTH_FAIL_PUBKEY: > case SSH_AUTH_FAIL_HOSTBASED: > case SSH_AUTH_FAIL_GSSAPI: >+ case SSH_AUTH_FAIL_U2F: > case SSH_LOGIN_EXCEED_MAXTRIES: > case SSH_LOGIN_ROOT_DENIED: > case SSH_CONNECTION_CLOSE: >@@ -2164,3 +2174,56 @@ mm_answer_gss_userok(int sock, Buffer *m) > } > #endif /* GSSAPI */ > >+#ifdef U2F >+int >+mm_answer_read_user_u2f_key(int sock, Buffer *m) >+{ >+ Key *key; >+ u_int key_idx; >+ u_char *blob = NULL; >+ u_int blen = 0; >+ >+ key_idx = buffer_get_int(m); >+ buffer_clear(m); >+ >+ key = read_user_u2f_key(authctxt->pw, key_idx); >+ buffer_put_int(m, key == NULL ? 1 : 0); >+ if (key != NULL) >+ { >+ if (key_to_blob(key, &blob, &blen) == 0) >+ fatal("%s: key_to_blob failed", __func__); >+ buffer_put_string(m, blob, blen); >+ debug3("%s: sending key", __func__); >+ } else { >+ debug3("%s: no key to send", __func__); >+ } >+ >+ mm_request_send(sock, MONITOR_ANS_READUSERU2FKEY, m); >+ return (0); >+} >+ >+int >+mm_answer_verify_u2f_user(int sock, Buffer *m) >+{ >+ int authenticated = 0; >+ Key *key; >+ u_char *blob, *dgst, *sig; >+ size_t bloblen, dgstlen, siglen; >+ >+ blob = buffer_get_string(m, &bloblen); >+ key = key_from_blob(blob, bloblen); >+ dgst = buffer_get_string(m, &dgstlen); >+ sig = buffer_get_string(m, &siglen); >+ >+ buffer_clear(m); >+ >+ authenticated = verify_u2f_user(key, dgst, dgstlen, sig, siglen); >+ buffer_put_int(m, authenticated); >+ >+ auth_method = "u2f"; >+ mm_request_send(sock, MONITOR_ANS_VERIFYU2FUSER, m); >+ >+ key_free(key); >+ return authenticated; >+} >+#endif /* U2F */ >diff --git a/monitor.h b/monitor.h >index 5bc41b5..7305c13 100644 >--- a/monitor.h >+++ b/monitor.h >@@ -56,6 +56,8 @@ enum monitor_reqtype { > MONITOR_REQ_GSSUSEROK = 46, MONITOR_ANS_GSSUSEROK = 47, > MONITOR_REQ_GSSCHECKMIC = 48, MONITOR_ANS_GSSCHECKMIC = 49, > MONITOR_REQ_TERM = 50, >+ MONITOR_REQ_READUSERU2FKEY = 52, MONITOR_ANS_READUSERU2FKEY = 53, >+ MONITOR_REQ_VERIFYU2FUSER = 54, MONITOR_ANS_VERIFYU2FUSER = 55, > > MONITOR_REQ_PAM_START = 100, > MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103, >diff --git a/monitor_wrap.c b/monitor_wrap.c >index 45dc169..13b81d5 100644 >--- a/monitor_wrap.c >+++ b/monitor_wrap.c >@@ -1300,3 +1300,62 @@ mm_ssh_gssapi_userok(char *user) > } > #endif /* GSSAPI */ > >+#ifdef U2F >+Key * >+mm_read_user_u2f_key(struct passwd *pw, u_int key_idx) >+{ >+ Buffer m; >+ Key *key = NULL; >+ u_char *blob; >+ u_int blen; >+ u_int is_null; >+ >+ debug3("%s entering", __func__); >+ >+ buffer_init(&m); >+ buffer_put_int(&m, key_idx); >+ >+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_READUSERU2FKEY, &m); >+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_READUSERU2FKEY, &m); >+ >+ is_null = buffer_get_int(&m); >+ if (is_null == 0) { >+ blob = buffer_get_string(&m, &blen); >+ if ((key = key_from_blob(blob, blen)) == NULL) >+ fatal("%s: key_from_blob failed", __func__); >+ >+ free(blob); >+ } >+ >+ buffer_free(&m); >+ return key; >+} >+ >+int >+mm_verify_u2f_user(Key *key, u_char * dgst, size_t dgstlen, u_char * sig, size_t siglen) >+{ >+ int authenticated = 0; >+ Buffer m; >+ u_char *blob; >+ u_int blen; >+ >+ debug3("%s entering", __func__); >+ >+ if (key_to_blob(key, &blob, &blen) == 0) >+ fatal("%s: key_to_blob failed", __func__); >+ buffer_init(&m); >+ buffer_put_string(&m, blob, blen); >+ free(blob); >+ >+ buffer_put_string(&m, dgst, dgstlen); >+ buffer_put_string(&m, sig, siglen); >+ >+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_VERIFYU2FUSER, &m); >+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_VERIFYU2FUSER, &m); >+ >+ authenticated = buffer_get_int(&m); >+ buffer_free(&m); >+ >+ return authenticated; >+} >+#endif /* U2F */ >diff --git a/monitor_wrap.h b/monitor_wrap.h >index 18c2501..aecb148 100644 >--- a/monitor_wrap.h >+++ b/monitor_wrap.h >@@ -53,6 +53,10 @@ int mm_key_verify(Key *, u_char *, u_int, u_char *, u_int); > int mm_auth_rsa_key_allowed(struct passwd *, BIGNUM *, Key **); > int mm_auth_rsa_verify_response(Key *, BIGNUM *, u_char *); > BIGNUM *mm_auth_rsa_generate_challenge(Key *); >+#ifdef U2F >+Key *mm_read_user_u2f_key(struct passwd *, u_int); >+int mm_verify_u2f_user(Key *, u_char *, size_t, u_char *, size_t); >+#endif > > #ifdef GSSAPI > OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); >diff --git a/readconf.h b/readconf.h >index 0b9cb77..b33d8cf 100644 >--- a/readconf.h >+++ b/readconf.h >@@ -46,6 +46,7 @@ typedef struct { > /* Try S/Key or TIS, authentication. */ > int gss_authentication; /* Try GSS authentication */ > int gss_deleg_creds; /* Delegate GSS credentials */ >+ int u2f_authentication; > int password_authentication; /* Try password > * authentication. */ > int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ >diff --git a/servconf.c b/servconf.c >index b7f3294..2684564 100644 >--- a/servconf.c >+++ b/servconf.c >@@ -109,6 +109,7 @@ initialize_server_options(ServerOptions *options) > options->kerberos_ticket_cleanup = -1; > options->kerberos_get_afs_token = -1; > options->gss_authentication=-1; >+ options->u2f_authentication = -1; > options->gss_cleanup_creds = -1; > options->password_authentication = -1; > options->kbd_interactive_authentication = -1; >@@ -250,6 +251,8 @@ fill_default_server_options(ServerOptions *options) > options->kerberos_get_afs_token = 0; > if (options->gss_authentication == -1) > options->gss_authentication = 0; >+ if (options->u2f_authentication == -1) >+ options->u2f_authentication = 1; > if (options->gss_cleanup_creds == -1) > options->gss_cleanup_creds = 1; > if (options->password_authentication == -1) >@@ -353,6 +356,7 @@ typedef enum { > sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, > sClientAliveCountMax, sAuthorizedKeysFile, > sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, >+ sU2FAuthentication, > sMatch, sPermitOpen, sForceCommand, sChrootDirectory, > sUsePrivilegeSeparation, sAllowAgentForwarding, > sHostCertificate, >@@ -425,6 +429,11 @@ static struct { > { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, > { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, > #endif >+#ifdef U2F >+ { "u2fauthentication", sU2FAuthentication, SSHCFG_ALL }, >+#else >+ { "u2fauthentication", sUnsupported, SSHCFG_ALL }, >+#endif > { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, > { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, > { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, >@@ -1108,6 +1117,10 @@ process_server_config_line(ServerOptions *options, char *line, > intptr = &options->gss_cleanup_creds; > goto parse_flag; > >+ case sU2FAuthentication: >+ intptr = &options->u2f_authentication; >+ goto parse_flag; >+ > case sPasswordAuthentication: > intptr = &options->password_authentication; > goto parse_flag; >@@ -1792,6 +1805,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) > > M_CP_INTOPT(password_authentication); > M_CP_INTOPT(gss_authentication); >+ M_CP_INTOPT(u2f_authentication); > M_CP_INTOPT(rsa_authentication); > M_CP_INTOPT(pubkey_authentication); > M_CP_INTOPT(kerberos_authentication); >@@ -2044,6 +2058,9 @@ dump_config(ServerOptions *o) > dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); > dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); > #endif >+#ifdef U2F >+ dump_cfg_fmtint(sU2FAuthentication, o->u2f_authentication); >+#endif > dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); > dump_cfg_fmtint(sKbdInteractiveAuthentication, > o->kbd_interactive_authentication); >diff --git a/servconf.h b/servconf.h >index 766db3a..e008332 100644 >--- a/servconf.h >+++ b/servconf.h >@@ -118,6 +118,7 @@ typedef struct { > * authentication. */ > int kbd_interactive_authentication; /* If true, permit */ > int challenge_response_authentication; >+ int u2f_authentication; > int permit_empty_passwd; /* If false, do not permit empty > * passwords. */ > int permit_user_env; /* If true, read ~/.ssh/environment */ >diff --git a/ssh.c b/ssh.c >index 26e9681..fe8fc06 100644 >--- a/ssh.c >+++ b/ssh.c >@@ -78,6 +78,10 @@ > #include "openbsd-compat/openssl-compat.h" > #include "openbsd-compat/sys-queue.h" > >+#ifdef U2F >+#include <u2f-host.h> >+#endif >+ > #include "xmalloc.h" > #include "ssh.h" > #include "ssh1.h" >@@ -843,6 +847,12 @@ main(int ac, char **av) > #ifdef WITH_OPENSSL > OpenSSL_add_all_algorithms(); > ERR_load_crypto_strings(); >+// TODO: SSL_load_error_strings(), requires -lssl i think? >+#endif >+ >+#ifdef U2F >+ if (u2fh_global_init(0) != U2FH_OK) >+ fatal("u2fh_global_init() failed"); > #endif > > /* Initialize the command to execute on remote host. */ >diff --git a/sshconnect2.c b/sshconnect2.c >index 68f7f4f..2cdc252 100644 >--- a/sshconnect2.c >+++ b/sshconnect2.c >@@ -30,6 +30,7 @@ > #include <sys/socket.h> > #include <sys/wait.h> > #include <sys/stat.h> >+#include <time.h> > > #include <errno.h> > #include <fcntl.h> >@@ -44,6 +45,10 @@ > #include <vis.h> > #endif > >+#ifdef U2F >+#include <u2f-host.h> >+#endif >+ > #include "openbsd-compat/sys-queue.h" > > #include "xmalloc.h" >@@ -308,6 +313,13 @@ void input_gssapi_error(int, u_int32_t, void *); > void input_gssapi_errtok(int, u_int32_t, void *); > #endif > >+#ifdef U2F >+int userauth_u2f(Authctxt *authctxt); >+void input_userauth_u2f_req(int type, u_int32_t seq, void *ctxt); >+void input_userauth_u2f_register(int type, u_int32_t seq, void *ctxt); >+void input_userauth_u2f_register_response(int type, u_int32_t seq, void *ctxt); >+#endif >+ > void userauth(Authctxt *, char *); > > static int sign_and_send_pubkey(Authctxt *, Identity *); >@@ -327,6 +339,13 @@ Authmethod authmethods[] = { > &options.gss_authentication, > NULL}, > #endif >+#ifdef U2F >+ {"u2f", >+ userauth_u2f, >+ NULL, >+ &options.u2f_authentication, >+ NULL}, >+#endif > {"hostbased", > userauth_hostbased, > NULL, >@@ -838,6 +857,147 @@ input_gssapi_error(int type, u_int32_t plen, void *ctxt) > } > #endif /* GSSAPI */ > >+#ifdef U2F >+int >+userauth_u2f(Authctxt *authctxt) >+{ >+ // first step: we dont send anything, but install a custom dispatcher. >+ debug("sshconnect2:userauth_u2f"); >+ >+ // TODO: is this the right way to disable the method after registration? >+ if (authctxt->info_req_seen) { >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, NULL); >+ return 0; >+ } >+ >+ packet_start(SSH2_MSG_USERAUTH_REQUEST); >+ packet_put_cstring(authctxt->server_user); >+ packet_put_cstring(authctxt->service); >+ packet_put_cstring(authctxt->method->name); >+ // TODO: shared constants >+ // TODO: how can we make the user chose between registration and authentication? >+ packet_put_int(1); >+ packet_send(); >+ >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_u2f_req); >+ //dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_u2f_register); >+ return 1; >+} >+ >+static void >+wait_for_u2f_devices(u2fh_devs *devs) >+{ >+ time_t looking; >+ int attempts = 0; >+ u2fh_rc rc; >+ >+ // The U2F implementation considerations recommend 3 seconds as the time a >+ // client implementation should grant for security keys to respond. We wait >+ // 3 times that for the user to insert a security key (and it being >+ // detected). >+ looking = time(NULL); >+ do { >+ if ((rc = u2fh_devs_discover(devs, NULL)) != U2FH_OK && attempts++ == 0) >+ error("Please insert and touch your security key"); >+ if (rc != U2FH_OK) >+ usleep(50); >+ } while (rc != U2FH_OK && (time(NULL) - looking) <= 9); >+ if (rc != U2FH_OK) >+ fatal("No U2F devices found (%s). Did you plug in your security key?", >+ u2fh_strerror(rc)); >+ >+ if (attempts == 0) >+ error("Please touch your security key"); >+} >+ >+void >+input_userauth_u2f_register(int type, u_int32_t seq, void *ctxt) >+{ >+ Authctxt *authctxt = ctxt; >+ char *challenge, *response; >+ u2fh_devs *devs = NULL; >+ u2fh_rc rc; >+ // TODO: is it okay to use this as origin? or rather the host fingerprint? >+ const char *origin = options.host_key_alias ? options.host_key_alias : >+ authctxt->host; >+ >+ if (authctxt == NULL) >+ fatal("input_userauth_u2f_register: no authentication context"); >+ >+ authctxt->info_req_seen = 1; >+ >+ challenge = packet_get_string(NULL); >+ packet_check_eom(); >+ >+ if ((rc = u2fh_devs_init(&devs)) != U2FH_OK) >+ fatal("u2fh_devs_init() failed: %s", u2fh_strerror(rc)); >+ >+ wait_for_u2f_devices(devs); >+ >+ if ((rc = u2fh_register(devs, challenge, origin, &response, U2FH_REQUEST_USER_PRESENCE)) != U2FH_OK) >+ fatal("u2fh_register() failed: %s", u2fh_strerror(rc)); >+ >+ u2fh_devs_done(devs); >+ >+ packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE); >+ packet_put_cstring(response); >+ packet_send(); >+ >+ free(response); >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, NULL); >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, &input_userauth_u2f_register_response); >+} >+ >+void >+input_userauth_u2f_register_response(int type, u_int32_t seq, void *ctxt) >+{ >+ char *response = packet_get_string(NULL); >+ // TODO: print >+ error("r = %s", response); >+} >+ >+void >+input_userauth_u2f_req(int type, u_int32_t seq, void *ctxt) >+{ >+ Authctxt *authctxt = ctxt; >+ char *challenge, *response; >+ u_int num_prompts, i; >+ int echo = 0; >+ u2fh_devs *devs = NULL; >+ u2fh_rc rc; >+ time_t looking; >+ const char *origin = options.host_key_alias ? options.host_key_alias : >+ authctxt->host; >+ >+ if (authctxt == NULL) >+ fatal("input_userauth_u2f_req: no authentication context"); >+ >+ authctxt->info_req_seen = 1; >+ >+ challenge = packet_get_string(NULL); >+ packet_check_eom(); >+ >+ if ((rc = u2fh_devs_init(&devs)) != U2FH_OK) >+ fatal("u2fh_devs_init() failed: %s", u2fh_strerror(rc)); >+ >+ wait_for_u2f_devices(devs); >+ >+ // TODO: refactor with input_userauth_u2f_register(), the following line is the only one that is different :) >+ if ((rc = u2fh_authenticate(devs, challenge, origin, &response, U2FH_REQUEST_USER_PRESENCE)) != U2FH_OK) >+ fatal("u2fh_authenticate() failed: %s", u2fh_strerror(rc)); >+ >+ u2fh_devs_done(devs); >+ >+ packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE); >+ packet_put_cstring(response); >+ packet_send(); >+ >+ free(response); >+ dispatch_set(SSH2_MSG_USERAUTH_INFO_REQUEST, NULL); >+} >+ >+#endif /* U2F */ >+ > int > userauth_none(Authctxt *authctxt) > { >diff --git a/sshd.c b/sshd.c >index 481d001..082141b 100644 >--- a/sshd.c >+++ b/sshd.c >@@ -1562,6 +1562,7 @@ main(int ac, char **av) > > #ifdef WITH_OPENSSL > OpenSSL_add_all_algorithms(); >+// TODO: SSL_load_error_strings(), requires -lssl i think? > #endif > > /* If requested, redirect the logs to the specified logfile. */ >diff --git a/sshkey.c b/sshkey.c >index fdd0c8a..ba2c535 100644 >--- a/sshkey.c >+++ b/sshkey.c >@@ -52,6 +52,9 @@ > #include "digest.h" > #define SSHKEY_INTERNAL > #include "sshkey.h" >+#include "key.h" >+#include "hostfile.h" >+#include "auth.h" > > /* openssh private key file format */ > #define MARK_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" >@@ -110,6 +113,7 @@ static const struct keytype keytypes[] = { > { "ssh-dss-cert-v00@openssh.com", "DSA-CERT-V00", > KEY_DSA_CERT_V00, 0, 1 }, > #endif /* WITH_OPENSSL */ >+ { "ssh-u2f", "U2F", KEY_U2F, 0, 0 }, > { NULL, NULL, -1, -1, 0 } > }; > >@@ -508,6 +512,9 @@ sshkey_new(int type) > break; > case KEY_UNSPEC: > break; >+ case KEY_U2F: >+ debug("key_new"); >+ break; > default: > free(k); > return NULL; >@@ -790,6 +797,17 @@ to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain) > key->ed25519_pk, ED25519_PK_SZ)) != 0) > return ret; > break; >+#ifdef U2F >+ case KEY_U2F: >+ if (key->u2f_pubkey == NULL) >+ return SSH_ERR_INVALID_ARGUMENT; >+ if ((ret = sshbuf_put_cstring(b, typename)) != 0 || >+ (ret = sshbuf_put_string(b, key->u2f_pubkey, U2F_PUBKEY_LEN)) != 0 || >+ (ret = sshbuf_put_string(b, >+ key->u2f_key_handle, key->u2f_key_handle_len)) != 0) >+ return ret; >+ break; >+#endif > default: > return SSH_ERR_KEY_TYPE_UNKNOWN; > } >@@ -1188,6 +1206,10 @@ sshkey_read(struct sshkey *ret, char **cpp) > > cp = *cpp; > >+ debug("sshkey_read"); >+ debug("ret = %p", ret); >+ debug("sshkey_read, ret->type = %d", ret->type); >+ > switch (ret->type) { > case KEY_RSA1: > #ifdef WITH_SSH1 >@@ -1208,6 +1230,40 @@ sshkey_read(struct sshkey *ret, char **cpp) > retval = 0; > #endif /* WITH_SSH1 */ > break; >+ case KEY_U2F: >+ space = strchr(cp, ' '); >+ if (space == NULL) >+ return SSH_ERR_INVALID_FORMAT; >+ *space = '\0'; >+ type = sshkey_type_from_name(cp); >+ if (type == KEY_UNSPEC) >+ return SSH_ERR_INVALID_FORMAT; >+ cp = space+1; >+ if (*cp == '\0') >+ return SSH_ERR_INVALID_FORMAT; >+ if (ret->type == KEY_UNSPEC) { >+ ret->type = type; >+ } else if (ret->type != type) >+ return SSH_ERR_KEY_TYPE_MISMATCH; >+ cp = space+1; >+ /* trim comment */ >+ space = strchr(cp, ' '); >+ if (space) >+ *space = '\0'; >+ blob = sshbuf_new(); >+ if ((r = sshbuf_b64tod(blob, cp)) != 0) { >+ sshbuf_free(blob); >+ return r; >+ } >+ // TODO: why do we _need_ to use malloc here? xmalloc gives memory that crashes! >+ ret->u2f_pubkey = malloc(U2F_PUBKEY_LEN); >+ memcpy(ret->u2f_pubkey, sshbuf_ptr(blob), U2F_PUBKEY_LEN); >+ ret->u2f_key_handle_len = sshbuf_len(blob) - U2F_PUBKEY_LEN; >+ ret->u2f_key_handle = malloc(ret->u2f_key_handle_len); >+ memcpy(ret->u2f_key_handle, sshbuf_ptr(blob) + U2F_PUBKEY_LEN, ret->u2f_key_handle_len); >+ sshbuf_free(blob); >+ retval = (r >= 0) ? 0 : 1; >+ break; > case KEY_UNSPEC: > case KEY_RSA: > case KEY_DSA: >@@ -1329,6 +1385,7 @@ sshkey_read(struct sshkey *ret, char **cpp) > default: > return SSH_ERR_INVALID_ARGUMENT; > } >+ debug("retval = %d", retval); > return retval; > } > >@@ -1909,6 +1966,9 @@ sshkey_from_blob_internal(const u_char *blob, size_t blen, > #if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) > EC_POINT *q = NULL; > #endif /* WITH_OPENSSL && OPENSSL_HAS_ECC */ >+#ifdef U2F >+ u_char *khandle = NULL; >+#endif > > #ifdef DEBUG_PK /* XXX */ > dump_base64(stderr, blob, blen); >@@ -2046,6 +2106,28 @@ sshkey_from_blob_internal(const u_char *blob, size_t blen, > key->ed25519_pk = pk; > pk = NULL; > break; >+#ifdef U2F >+ case KEY_U2F: >+ if ((ret = sshbuf_get_string(b, &pk, &len)) != 0) >+ goto out; >+ if (len != U2F_PUBKEY_LEN) { >+ ret = SSH_ERR_INVALID_FORMAT; >+ goto out; >+ } >+ if ((ret = sshbuf_get_string(b, &khandle, &len)) != 0) >+ goto out; >+ if ((key = sshkey_new(type)) == NULL) { >+ ret = SSH_ERR_ALLOC_FAIL; >+ goto out; >+ } >+ key->u2f_pubkey = pk; >+ key->u2f_key_handle_len = len; >+ key->u2f_key_handle = khandle; >+ pk = NULL; >+ khandle = NULL; >+ ret = SSH_ERR_ALLOC_FAIL; >+ break; >+#endif > case KEY_UNSPEC: > if ((key = sshkey_new(type)) == NULL) { > ret = SSH_ERR_ALLOC_FAIL; >@@ -2079,6 +2161,9 @@ sshkey_from_blob_internal(const u_char *blob, size_t blen, > if (q != NULL) > EC_POINT_free(q); > #endif /* WITH_OPENSSL && OPENSSL_HAS_ECC */ >+#ifdef U2F >+ free(khandle); >+#endif > return ret; > } > >diff --git a/sshkey.h b/sshkey.h >index 450b30c..8f1d6a2 100644 >--- a/sshkey.h >+++ b/sshkey.h >@@ -64,6 +64,7 @@ enum sshkey_types { > KEY_ED25519_CERT, > KEY_RSA_CERT_V00, > KEY_DSA_CERT_V00, >+ KEY_U2F, > KEY_UNSPEC > }; > >@@ -110,6 +111,11 @@ struct sshkey { > u_char *ed25519_sk; > u_char *ed25519_pk; > struct sshkey_cert *cert; >+#ifdef U2F >+ u_char *u2f_pubkey; >+ u_int u2f_key_handle_len; >+ u_char *u2f_key_handle; >+#endif > }; > > #define ED25519_SK_SZ crypto_sign_ed25519_SECRETKEYBYTES >-- >2.1.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 2319
:
2511
|
2512
|
2521
|
2710
|
2782