2 * This file is part of DisOrder
3 * Copyright (C) 2007 Richard Kettlewell
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 /** @file lib/cookies.c
21 * @brief Cookie support
41 #include "configuration.h"
46 /** @brief Hash function used in signing HMAC */
47 #define ALGO GCRY_MD_SHA1
49 /** @brief Size of key to use */
52 /** @brief Signing key */
53 static uint8_t signing_key[HASHSIZE];
55 /** @brief Previous signing key */
56 static uint8_t old_signing_key[HASHSIZE];
58 /** @brief Signing key validity limit or 0 if none */
59 static time_t signing_key_validity_limit;
61 /** @brief Hash of revoked cookies */
64 /** @brief Callback to expire revocation list */
65 static int revoked_cleanup_callback(const char *key, void *value,
67 if(*(time_t *)value < *(time_t *)u)
68 hash_remove(revoked, key);
72 /** @brief Generate a new key */
73 static void newkey(void) {
77 memcpy(old_signing_key, signing_key, HASHSIZE);
78 gcry_randomize(signing_key, HASHSIZE, GCRY_STRONG_RANDOM);
79 signing_key_validity_limit = now + config->cookie_key_lifetime;
80 /* Now is a good time to clean up the revocation list... */
82 hash_foreach(revoked, revoked_cleanup_callback, &now);
85 /** @brief Sign @p subject with @p key and return the base64 of the result
86 * @param key Key to sign with (@ref HASHSIZE bytes)
87 * @param subject Subject string
88 * @return Base64-encoded signature or NULL
90 static char *sign(const uint8_t *key,
91 const char *subject) {
97 if((e = gcry_md_open(&h, ALGO, GCRY_MD_FLAG_HMAC))) {
98 error(0, "gcry_md_open: %s", gcry_strerror(e));
101 if((e = gcry_md_setkey(h, key, HASHSIZE))) {
102 error(0, "gcry_md_setkey: %s", gcry_strerror(e));
106 gcry_md_write(h, subject, strlen(subject));
107 sig = gcry_md_read(h, ALGO);
108 sig64 = mime_to_base64(sig, HASHSIZE);
113 /** @brief Create a login cookie
114 * @param user Username
115 * @return Cookie or NULL
117 char *make_cookie(const char *user) {
118 const char *password;
120 char *b, *bp, *c, *g;
122 /* semicolons aren't allowed in usernames */
123 if(strchr(user, ';')) {
124 error(0, "make_cookie for username with semicolon");
127 /* look up the password */
128 password = trackdb_get_password(user);
130 error(0, "make_cookie for nonexistent user");
133 /* make sure we have a valid signing key */
135 if(now >= signing_key_validity_limit)
137 /* construct the subject */
138 byte_xasprintf(&b, "%jx;%s;", (intmax_t)now + config->cookie_login_lifetime,
139 urlencodestring(user));
140 byte_xasprintf(&bp, "%s%s", b, password);
142 if(!(g = sign(signing_key, bp)))
144 /* put together the final cookie */
145 byte_xasprintf(&c, "%s%s", b, g);
149 /** @brief Verify a cookie
150 * @param cookie Cookie to verify
151 * @return Verified user or NULL
153 char *verify_cookie(const char *cookie) {
157 char *user, *bp, *sig;
158 const char *password;
160 /* check the revocation list */
161 if(revoked && hash_find(revoked, cookie)) {
162 error(0, "attempt to log in with revoked cookie");
165 /* parse the cookie */
167 t = strtoimax(cookie, &c1, 16);
169 error(errno, "error parsing cookie timestamp");
173 error(0, "invalid cookie timestamp");
176 /* There'd better be two semicolons */
177 c2 = strchr(c1 + 1, ';');
179 error(0, "invalid cookie syntax");
182 /* Extract the username */
183 user = xstrndup(c1 + 1, c2 - (c1 + 1));
187 error(0, "cookie has expired");
190 /* look up the password */
191 password = trackdb_get_password(user);
193 error(0, "verify_cookie for nonexistent user");
196 /* construct the expected subject. We re-encode the timestamp and the
198 byte_xasprintf(&bp, "%jx;%s;%s", t, urlencodestring(user), password);
199 /* Compute the expected signature. NB we base64 the expected signature and
200 * compare that rather than exposing our base64 parser to the cookie. */
201 if(!(sig = sign(signing_key, bp)))
203 if(!strcmp(sig, c2 + 1))
205 /* that didn't match, try the old key */
206 if(!(sig = sign(old_signing_key, bp)))
208 if(!strcmp(sig, c2 + 1))
210 /* that didn't match either */
211 error(0, "cookie signature does not match");
215 /** @brief Revoke a cookie
216 * @param cookie Cookie to revoke
218 * Further attempts to log in with @p cookie will fail.
220 void revoke_cookie(const char *cookie) {
224 /* find the cookie's expiry time */
226 when = (time_t)strtoimax(cookie, &ptr, 16);
227 /* reject bogus cookies */
232 /* make sure the revocation list exists */
234 revoked = hash_new(sizeof(time_t));
235 /* add the cookie to it; its value is the expiry time */
236 hash_add(revoked, cookie, &when, HASH_INSERT);