#include <string.h>
#include <microhttpd.h>
#include <libpq-fe.h>
+#include <yubikey.h>
+#include <time.h>
+#include <glib.h>
+#include <gcrypt.h>
PGconn *db_conn;
const char *info;
};
+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);
+ return ts;
+}
+
+gchar *sign_request(char *key, size_t key_len, char *info, char *status,
+ char *timestamp) {
+ char *line, *sig;
+ gchar *ret;
+ gcry_md_hd_t hd;
+ if (info != NULL) {
+ asprintf(&line, "info=%s&status=%s×tamp=%s", info,
+ status, timestamp);
+ } else {
+ asprintf(&line, "status=%s×tamp=%s", status, timestamp);
+ }
+ gcry_md_open(&hd, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+ gcry_md_setkey(hd, key, key_len);
+ gcry_md_write(hd, line, strlen(line));
+ gcry_md_final(hd);
+ ret = g_base64_encode(gcry_md_read(hd, 0), gcry_md_get_algo_dlen(GCRY_MD_SHA1));
+ gcry_md_close(hd);
+ free(line);
+ return ret;
+}
+
static int handle_request(void *data,
struct MHD_Connection *conn,
const char *url,
int r;
struct MHD_Response *response;
PGresult *res;
- const char *paramValues[1];
+ const char *paramValues[3];
+ int paramLengths[3];
+ int paramFormats[3];
int i;
- const char *id = NULL, *otp = NULL, *h = NULL, *shared_secret;
+ 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 and h (optional) */
+ /* Parse query string, grab id, otp_mh 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");
+ 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, h);
+ otp_mh, h);
/* XXX Handle missing params here */
+ otp_len = strlen(otp_mh)/2 + 1;
+ otp = malloc(otp_len);
+ yubikey_modhex_decode(otp, otp_mh, otp_len);
+
/* Do query to grab shared secret, we need this later anyway */
paramValues[0] = id;
res = PQexecParams(db_conn,
- "SELECT secret_base64 FROM shared_secret WHERE secret_id = $1",
+ "SELECT secret FROM shared_secret WHERE secret_id = $1",
1, /* one param */
NULL, /* let the backend deduce param type */
paramValues,
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;
+ /* XXX Better error handling.*/
+ /* XXX Handle zero rows too */
+ }
+ if (PQntuples(res) == 0) {
+ /* XXX Better handling */
+ fprintf(stderr, "No such secrets: %s\n", id);
+ PQclear(res);
+ return MHD_NO;
+ }
+
+ /* 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);
+ PQclear(res);
+
+ /* 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 */
+ }
+
+ 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",
+ 1, /* one param */
+ NULL, /* let the backend deduce param type */
+ paramValues,
+ paramLengths,
+ paramFormats, /* default to all text params */
+ 1); /* ask for binary results */
+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "SELECT failed: %s", PQerrorMessage(db_conn));
return MHD_NO;
/* XXX Better error handling*/
}
- /* If h exists, verify FIXME */
- /* Validate OTP */
- /* Update status, if appropriate */
- /* Generate response, sign it */
- for (i = 0; i < PQntuples(res); i++) {
- const char *fullname;
-
- fullname = PQgetvalue(res, i, 0);
- response = MHD_create_response_from_data(strlen(fullname), (void*)fullname,
- MHD_YES, MHD_YES);
- r = MHD_queue_response(conn, MHD_HTTP_OK, response);
- MHD_destroy_response(response);
- fprintf(stderr, "%s %s\n", method, url);
+ assert(PQgetlength(res, 0, PQfnumber(res, "secret_key")) == YUBIKEY_KEY_SIZE);
+ aes_key = PQgetvalue(res, 0, PQfnumber(res, "secret_key"));
+
+ /* 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);
+ if (!yubikey_crc_ok_p((void*)&token) ||
+ memcmp(token.uid,
+ PQgetvalue(res, 0, PQfnumber(res, "secret_uid")),
+ YUBIKEY_UID_SIZE) != 0) {
+ /* XXX: FIXME: check counters too */
+ /* XXX FIXME - bad token => reject */
+ fprintf(stderr, "BAD TOKEN\n");
+ return MHD_NO;
}
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.*/
+ }
+ assert(PQntuples(res) == 0);
+
+ /* Generate response, sign it */
+ timestamp = get_timestamp();
+ 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;
}