]> err.no Git - yubikey-server-c/commitdiff
Initial write-up
authorTollef Fog Heen <tfheen@err.no>
Fri, 9 Oct 2009 08:02:16 +0000 (10:02 +0200)
committerTollef Fog Heen <tfheen@err.no>
Fri, 9 Oct 2009 08:02:16 +0000 (10:02 +0200)
configure.ac
schema.sql
src/Makefile.am
src/main.c

index 09e9d8ed9935bf1e83b6089a2107fc46c92ce47c..e7fd913d3768cf8849c86f3aee2181f1ca8f2b84 100644 (file)
@@ -20,8 +20,10 @@ else
    AC_MSG_ERROR([$PG_CONFIG is not installed - install it and postgresql headers?])
 fi
 
-
 PKG_CHECK_MODULES([libmicrohttpd], [libmicrohttpd])
+PKG_CHECK_MODULES([glib], [glib-2.0])
+
+AC_CHECK_LIB([yubikey], [yubikey_modhex_decode],[], AC_MSG_ERROR([libyubikey is not installed or not new enough]))
 
 AC_CONFIG_FILES([
   Makefile
index eacc6f8400c73698695943ee8ebd75bbc96c91c2..b42fdc262b3c241dba684f488299ba2e4e63c9b3 100644 (file)
@@ -6,17 +6,24 @@
 CREATE TABLE yubikey (
        yubikey_id serial NOT NULL,
        active boolean NOT NULL DEFAULT 'f',
-       secret_id varchar NOT NULL,
+       public_id bytea, -- fixed public bit of the key's output
+       secret_uid bytea NOT NULL, -- secret uid bit of the key
+       secret_key bytea NOT NULL, 
        session_counter int,
        session_use int
 );
 
-INSERT INTO yubikey (yubikey_id, active, secret_id, session_counter, session_use) VALUES (0, 't', 'secret', 0,0);
+INSERT INTO yubikey (active, public_id,
+       secret_uid, secret_key, session_counter, session_use) VALUES 
+       ('t', decode('d4633b', 'hex'), 
+       E'\\000\\000\\000\\000\\000\\000',
+       decode('baef43c254e9d2217912e80ed71a7b4a', 'hex'),
+       0, 0);
 
 CREATE TABLE shared_secret (
        secret_id serial NOT NULL,
-       secret_base64 varchar NOT NULL,
+       secret bytea NOT NULL,
        active boolean NOT NULL DEFAULT 'f'
 );
 
-INSERT INTO shared_secrets (secret_base64, active) VALUES ('MQ6fOy1t/add/wisbu2O+LpPiMs=', 't');
+INSERT INTO shared_secret (secret, active) VALUES (decode('MQ6fOy1t/add/wisbu2O+LpPiMs=', 'base64'), 't');
index 3d3f75db9c73b85d0c7886b99d5b70a8802c2ca6..f698982cff0b4e896841e4561b27ca9f1e524127 100644 (file)
@@ -3,5 +3,5 @@ sbin_PROGRAMS = yubikeyd
 
 yubikeyd_SOURCES = main.c
 
-yubikeyd_CFLAGS = @libmicrohttpd_CFLAGS@ @POSTGRESQL_CFLAGS@
-yubikeyd_LDADD = @libmicrohttpd_LIBS@ @POSTGRESQL_LDFLAGS@
\ No newline at end of file
+yubikeyd_CFLAGS = @glib_CFLAGS@ @libmicrohttpd_CFLAGS@ @POSTGRESQL_CFLAGS@
+yubikeyd_LDADD = @glib_LIBS@ @libmicrohttpd_LIBS@ @POSTGRESQL_LDFLAGS@
\ No newline at end of file
index 6ff5afb6515a12d9aa8d36deb5c61239e6f00e6f..a4811cd1c4178cb504c4d7ddf679d6b8dd6f5aa5 100644 (file)
 #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;
 
@@ -46,6 +50,41 @@ struct error {
        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&timestamp=%s", info,
+                        status, timestamp);
+       } else {
+               asprintf(&line, "status=%s&timestamp=%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,
@@ -59,24 +98,35 @@ static int handle_request(void *data,
        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,
@@ -84,6 +134,48 @@ static int handle_request(void *data,
                           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));
@@ -91,21 +183,63 @@ static int handle_request(void *data,
                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;
 }