#define PORT 8000
+#define _GNU_SOURCE
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <glib.h>
#include <gcrypt.h>
+#include "util.h"
+#include <arpa/inet.h>
+
+#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;
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) {
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;
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;