From 44018606bc761c2c10fb8311e358c637a3dc628b Mon Sep 17 00:00:00 2001 From: Tollef Fog Heen Date: Mon, 12 Oct 2009 08:04:49 +0200 Subject: [PATCH] Various cleanups --- configure.ac | 2 +- schema.sql | 4 +- src/Makefile.am | 2 +- src/main.c | 356 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 250 insertions(+), 114 deletions(-) diff --git a/configure.ac b/configure.ac index e7fd913..068003e 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ AC_INIT([yubikey-server-c], [0.1], ) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([-Wall -Werror foreign]) +AM_INIT_AUTOMAKE([-Wall -Wextra -Werror foreign]) AC_PROG_CC AC_MSG_CHECKING([for PostgreSQL libraries]) diff --git a/schema.sql b/schema.sql index b42fdc2..de1ef8d 100644 --- a/schema.sql +++ b/schema.sql @@ -6,7 +6,7 @@ CREATE TABLE yubikey ( yubikey_id serial NOT NULL, active boolean NOT NULL DEFAULT 'f', - public_id bytea, -- fixed public bit of the key's output + public_id varchar, -- fixed public bit of the key's output, modhex encoded secret_uid bytea NOT NULL, -- secret uid bit of the key secret_key bytea NOT NULL, session_counter int, @@ -15,7 +15,7 @@ CREATE TABLE yubikey ( INSERT INTO yubikey (active, public_id, secret_uid, secret_key, session_counter, session_use) VALUES - ('t', decode('d4633b', 'hex'), + ('t', 'tfheen', E'\\000\\000\\000\\000\\000\\000', decode('baef43c254e9d2217912e80ed71a7b4a', 'hex'), 0, 0); diff --git a/src/Makefile.am b/src/Makefile.am index f698982..b979782 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ sbin_PROGRAMS = yubikeyd -yubikeyd_SOURCES = main.c +yubikeyd_SOURCES = main.c util.c yubikeyd_CFLAGS = @glib_CFLAGS@ @libmicrohttpd_CFLAGS@ @POSTGRESQL_CFLAGS@ yubikeyd_LDADD = @glib_LIBS@ @libmicrohttpd_LIBS@ @POSTGRESQL_LDFLAGS@ \ No newline at end of file diff --git a/src/main.c b/src/main.c index a4811cd..f875146 100644 --- a/src/main.c +++ b/src/main.c @@ -18,6 +18,7 @@ #define PORT 8000 +#define _GNU_SOURCE #include #include #include @@ -31,6 +32,17 @@ #include #include #include +#include "util.h" +#include + +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif PGconn *db_conn; @@ -50,23 +62,26 @@ struct error { const char *info; }; +struct ykc_stats { + int active; + char *public_id; + char *secret_uid; + char *secret_key; + int session_counter; + int session_use; +}; + char *get_timestamp(void) { - struct tm tm; size_t len = strlen("YYYY-mm-ddTHH:MM:SSZMSMS"); char *ts = malloc(len + 1); - char ms[5]; - struct timeval tv; - gettimeofday(&tv, NULL); - gmtime_r(&tv.tv_sec, &tm); - strftime(ts, len, "%FT%TZ", &tm); - sprintf(ms, "%.4d", tv.tv_usec / 1000); - strncat(ts, ms, 4); + + ysc_strftime(ts, len + 1, "%FT%TZ%v", NULL); return ts; } gchar *sign_request(char *key, size_t key_len, char *info, char *status, char *timestamp) { - char *line, *sig; + char *line; gchar *ret; gcry_md_hd_t hd; if (info != NULL) { @@ -85,43 +100,54 @@ gchar *sign_request(char *key, size_t key_len, char *info, char *status, return ret; } -static int handle_request(void *data, - struct MHD_Connection *conn, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) +static int send_response(struct MHD_Connection *conn, + const char *signature, + const char *status, + const char *info, + const char *timestamp) { - static char *ret = "This is a test"; + char *resp_text, *t; int r; + size_t r_l; struct MHD_Response *response; - PGresult *res; - const char *paramValues[3]; - int paramLengths[3]; - int paramFormats[3]; - int i; - const unsigned char *id = NULL, *otp_mh = NULL, *h = NULL, *secret_id; - unsigned char *aes_key, *otp, *uid, *timestamp, *signature; - unsigned char *shared_secret, *resp_text; - size_t shared_secret_len, otp_len, uid_len; - struct error *e; - yubikey_token_st token; - memset(&token, '\0', sizeof(token)); - /* Parse query string, grab id, otp_mh and h (optional) */ + r_l = strlen("h=\nstatus=\ntimestamp=\ninfo="); /* This is a maximum + * of static strings */ + r_l += (signature != NULL ? strlen(signature) : 0); + r_l += (status != NULL ? strlen(status) : 0); + r_l += (info != NULL ? strlen(info) : 0); + r_l += (timestamp != NULL ? strlen(timestamp) : 0); - id = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "id"); - otp_mh = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "otp"); - h = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "h"); - fprintf(stderr, "got params: url=%s id=%s otp=%s, h=%s\n", url, id, - otp_mh, h); - /* XXX Handle missing params here */ + resp_text = malloc(r_l + 1); + t = resp_text; + if (signature) { + t += sprintf(t, "h=%s\n", signature); + } + if (status) { + t += sprintf(t, "status=%s\n", status); + } + if (info) { + t += sprintf(t, "info=%s\n", info); + } + if (timestamp) { + t += sprintf(t, "timestamp=%s\n", timestamp); + } + /* XXX error checking above */ - otp_len = strlen(otp_mh)/2 + 1; - otp = malloc(otp_len); - yubikey_modhex_decode(otp, otp_mh, otp_len); + response = MHD_create_response_from_data(strlen(resp_text), resp_text, + MHD_YES, MHD_YES); + r = MHD_queue_response(conn, MHD_HTTP_OK, response); + MHD_destroy_response(response); + if (r == MHD_YES) + return 0; + return -1; +} + +static int get_shared_secret(const char *id, char **shared_secret, + size_t *shared_secret_len) +{ + const char *paramValues[1]; + PGresult *res; /* Do query to grab shared secret, we need this later anyway */ paramValues[0] = id; @@ -137,113 +163,223 @@ static int handle_request(void *data, if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT failed: %s", PQerrorMessage(db_conn)); PQclear(res); - return MHD_NO; - /* XXX Better error handling.*/ - /* XXX Handle zero rows too */ + return -1; + /* XXX Return error object */ } if (PQntuples(res) == 0) { /* XXX Better handling */ fprintf(stderr, "No such secrets: %s\n", id); PQclear(res); - return MHD_NO; + return -1; } - - /* If h exists, verify. FIXME */ - shared_secret_len = PQgetlength(res, 0, 0); - shared_secret = malloc(shared_secret_len); - memcpy(shared_secret, PQgetvalue(res, 0, 0), shared_secret_len); + *shared_secret_len = PQgetlength(res, 0, 0); + *shared_secret = malloc(*shared_secret_len); + memcpy(*shared_secret, PQgetvalue(res, 0, 0), *shared_secret_len); PQclear(res); + return 0; +} - /* Validate OTP */ - /* Find public uid, if possible */ - if (otp_len > YUBIKEY_BLOCK_SIZE) { - uid_len = otp_len - YUBIKEY_BLOCK_SIZE - 1; - uid = malloc(uid_len); - memcpy(uid, otp, uid_len); - /* XXX error checking */ +static int split_otp(const char *otp, char **user, char **s_otp) +{ + size_t otp_len, i, j; + /* Modhex doubles the length of the string, so grab the last + * YUBIKEY_BLOCK_SIZE * 2 octets to get the actual OTP */ + otp_len = strlen(otp); + + assert(otp_len > YUBIKEY_BLOCK_SIZE * 2); + assert(yubikey_modhex_p(otp)); + + /* XXX error checking */ + i = otp_len - YUBIKEY_BLOCK_SIZE * 2; + *user = malloc(i+1); + memcpy(*user, otp, i); + (*user)[i] = '\0'; + + j = YUBIKEY_BLOCK_SIZE * 2; + *s_otp = malloc(j+1); + memcpy(*s_otp, otp + i, j); + (*s_otp)[j] = '\0'; + return 0; +} + +static int set_data_for_uid(char *uid, struct ykc_stats *stats) +{ + PGresult *res; + const char *paramValues[3]; + char ctr[10], use[10]; + + paramValues[0] = uid; + + snprintf(ctr, sizeof (ctr), "%d", stats->session_counter); + snprintf(use, sizeof (use), "%d", stats->session_use); + + paramValues[1] = ctr; + paramValues[2] = use; + res = PQexecParams(db_conn, + "UPDATE yubikey SET session_counter = $2, " + "session_use = $3 WHERE public_id = $1", + 3, /* number of params */ + NULL, /* let the backend deduce param type */ + paramValues, + NULL, + NULL, + 1); /* ask for binary results */ + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "UPDATE failed: %s\n", PQerrorMessage(db_conn)); + PQclear(res); + return -1; + /* XXX Better error handling.*/ } + assert(PQntuples(res) == 0); + return 0; +} + +static int get_data_for_uid(char *uid, struct ykc_stats *stats) +{ + PGresult *res; + const char *paramValues[1]; + char *tmp; paramValues[0] = uid; - paramLengths[0] = uid_len; - paramFormats[0] = 1; /* Binary */ res = PQexecParams(db_conn, - "SELECT secret_uid, secret_key, session_counter, " - "session_use FROM yubikey WHERE public_id = $1::bytea", + "SELECT active, secret_uid, secret_key, " + "session_counter, session_use FROM yubikey " + "WHERE public_id = $1", 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, - paramLengths, - paramFormats, /* default to all text params */ + NULL, + NULL, /* default to all text params */ 1); /* ask for binary results */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT failed: %s", PQerrorMessage(db_conn)); PQclear(res); - return MHD_NO; + return -1; /* XXX Better error handling*/ } assert(PQgetlength(res, 0, PQfnumber(res, "secret_key")) == YUBIKEY_KEY_SIZE); - aes_key = PQgetvalue(res, 0, PQfnumber(res, "secret_key")); + stats->secret_key = ysc_memdup( + PQgetvalue(res, 0, PQfnumber(res, "secret_key")), + PQgetlength(res, 0, PQfnumber(res, "secret_key"))); + + stats->secret_uid = ysc_memdup( + PQgetvalue(res, 0, PQfnumber(res, "secret_uid")), + PQgetlength(res, 0, PQfnumber(res, "secret_uid"))); + + tmp = PQgetvalue(res, 0, PQfnumber(res, "session_counter")); + stats->session_counter = ntohl(*((uint32_t *) tmp)); + tmp = PQgetvalue(res, 0, PQfnumber(res, "session_use")); + stats->session_use = ntohl(*((uint32_t *) tmp)); + stats->public_id = NULL; + return 0; +} + +static int handle_request(void * UNUSED(data), + struct MHD_Connection *conn, + const char * url, + const char *UNUSED(method), + const char *UNUSED(version), + const char *UNUSED(upload_data), + size_t *UNUSED(upload_data_size), + void **UNUSED(con_cls)) +{ + const char *id = NULL, *otp = NULL, *h = NULL; + char *uid, *otp_token; + char *signature = NULL, *status = NULL, *info = NULL, *timestamp = NULL; + char *shared_secret; + size_t shared_secret_len; + yubikey_token_st token; + struct ykc_stats stats; + memset(&token, '\0', sizeof(token)); + + timestamp = get_timestamp(); + assert(timestamp != NULL); + /* Parse query string, grab id, otp and h (optional) */ + + id = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "id"); + otp = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "otp"); + h = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "h"); + fprintf(stderr, "got params: url=%s id=%s otp=%s, h=%s\n", url, id, + otp, h); + + /* Do query to grab shared secret, we need this later anyway */ + if (get_shared_secret(id, &shared_secret, &shared_secret_len) < 0) { + /* XXX: Something blew up, assume no such ID */ + status = "NO_SUCH_CLIENT"; + send_response(conn, NULL, status, NULL, timestamp); + /* XXX free memory */ + return MHD_YES; + } + + if (otp == NULL) { + info = "otp"; + signature = sign_request(shared_secret, shared_secret_len, + info, "MISSING_PARAMETER", timestamp); + send_response(conn, signature, status, NULL, timestamp); + /* XXX free memory */ + return MHD_YES; + } + + if (! yubikey_modhex_p(otp)) { + signature = sign_request(shared_secret, shared_secret_len, + NULL, "BAD OTP", timestamp); + send_response(conn, signature, status, NULL, timestamp); + /* XXX free memory */ + return MHD_YES; + } + + /* XXX: If h exists, verify. FIXME */ + + /* Validate OTP */ + /* Find public uid, if possible */ + split_otp(otp, &uid, &otp_token); + get_data_for_uid(uid, &stats); /* Argh, yubikey_parse takes in one modhex-ed token (but * requires us to strip the public id first, and an unencoded aes key*/ - yubikey_parse(otp_mh+uid_len*2, aes_key, &token); + yubikey_parse((uint8_t*)(otp_token), (const uint8_t *)stats.secret_key, &token); if (!yubikey_crc_ok_p((void*)&token) || - memcmp(token.uid, - PQgetvalue(res, 0, PQfnumber(res, "secret_uid")), - YUBIKEY_UID_SIZE) != 0) { + memcmp(token.uid, stats.secret_uid, YUBIKEY_UID_SIZE) != 0) { + signature = sign_request(shared_secret, shared_secret_len, + NULL, "BAD OTP", timestamp); + send_response(conn, signature, status, NULL, timestamp); + /* XXX: FIXME: check counters too */ - /* XXX FIXME - bad token => reject */ + /* XXX FIXME free memory */ fprintf(stderr, "BAD TOKEN\n"); - return MHD_NO; + return MHD_YES; } - PQclear(res); - - /* Update status, if appropriate */ - - asprintf(&(paramValues[0]), "%d", yubikey_counter(token.ctr)); - paramLengths[0] = 0; - paramFormats[0] = 0; /* Text */ - asprintf(&(paramValues[1]), "%d", yubikey_counter(token.use)); - paramLengths[1] = 0; - paramFormats[1] = 0; /* Text */ - paramValues[2] = uid; - paramLengths[2] = uid_len; - paramFormats[2] = 1; /* Binary */ - res = PQexecParams(db_conn, - "UPDATE yubikey SET session_counter = $1, " - "session_use = $2 WHERE public_id = $3", - 3, /* number of params */ - NULL, /* let the backend deduce param type */ - paramValues, - paramLengths, - paramFormats, - 1); /* ask for binary results */ - - if (PQresultStatus(res) != PGRES_COMMAND_OK) { - fprintf(stderr, "UPDATE failed: %s\n", PQerrorMessage(db_conn)); - PQclear(res); - return MHD_NO; - /* XXX Better error handling.*/ + if (yubikey_counter(token.ctr) < stats.session_counter || + (yubikey_counter(token.ctr) == stats.session_counter && + token.use <= stats.session_use)) { + /* Replay */ + status = "REPLAYED_OTP"; + signature = sign_request(shared_secret, shared_secret_len, + NULL, status, timestamp); + send_response(conn, signature, status, NULL, timestamp); + /* XXX FIXME free memory */ + fprintf(stderr, "replay\n"); + return MHD_YES; } - assert(PQntuples(res) == 0); + /* Update status, if appropriate */ + memset(&stats, 0, sizeof(struct ykc_stats)); + stats.session_counter = yubikey_counter(token.ctr); + stats.session_use = token.use; + set_data_for_uid(uid, &stats); /* Generate response, sign it */ - timestamp = get_timestamp(); + fprintf(stderr, "ok request\n"); + status = "OK"; signature = sign_request(shared_secret, shared_secret_len, - NULL, "OK", timestamp); - asprintf(&resp_text, "h=%s\nstatus=%s\nt=%s\n", signature, "OK", - timestamp); - response = MHD_create_response_from_data(strlen(resp_text), resp_text, - MHD_YES, MHD_YES); - r = MHD_queue_response(conn, MHD_HTTP_OK, response); - MHD_destroy_response(response); - fprintf(stderr, "%s %s\n", method, url); - return r; + NULL, status, timestamp); + send_response(conn, signature, status, NULL, timestamp); + return MHD_YES; } -int main(int argc, char **argv) +int main(int UNUSED(argc), char ** UNUSED(argv)) { struct MHD_Daemon *d; -- 2.39.5