2 * This file is part of DisOrder
3 * Copyright (C) 2007, 2008 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 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU 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, see <http://www.gnu.org/licenses/>.
18 /** @file lib/cookies.c
19 * @brief Cookie support
35 #include "configuration.h"
39 /** @brief Hash function used in signing HMAC */
40 #define ALGO GCRY_MD_SHA1
42 /** @brief Size of key to use */
45 /** @brief Signing key */
46 static uint8_t signing_key[HASHSIZE];
48 /** @brief Previous signing key */
49 static uint8_t old_signing_key[HASHSIZE];
51 /** @brief Signing key validity limit or 0 if none */
52 static time_t signing_key_validity_limit;
54 /** @brief Hash of revoked cookies */
57 /** @brief Callback to expire revocation list */
58 static int revoked_cleanup_callback(const char *key, void *value,
60 if(*(time_t *)value < *(time_t *)u)
61 hash_remove(revoked, key);
65 /** @brief Generate a new key */
66 static void newkey(void) {
70 memcpy(old_signing_key, signing_key, HASHSIZE);
71 gcry_randomize(signing_key, HASHSIZE, GCRY_STRONG_RANDOM);
72 signing_key_validity_limit = now + config->cookie_key_lifetime;
73 /* Now is a good time to clean up the revocation list... */
75 hash_foreach(revoked, revoked_cleanup_callback, &now);
78 /** @brief Base64 mapping table for cookies
80 * Stupid Safari cannot cope with quoted cookies, so cookies had better not
81 * need quoting. We use $ to separate the parts of the cookie and +%# to where
82 * MIME uses +/=; see @ref base64.c. See http_separator() for the characters
85 static const char cookie_base64_table[] =
86 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+%#";
88 /** @brief Sign @p subject with @p key and return the base64 of the result
89 * @param key Key to sign with (@ref HASHSIZE bytes)
90 * @param subject Subject string
91 * @return Base64-encoded signature or NULL
93 static char *sign(const uint8_t *key,
94 const char *subject) {
100 if((e = gcry_md_open(&h, ALGO, GCRY_MD_FLAG_HMAC))) {
101 error(0, "gcry_md_open: %s", gcry_strerror(e));
104 if((e = gcry_md_setkey(h, key, HASHSIZE))) {
105 error(0, "gcry_md_setkey: %s", gcry_strerror(e));
109 gcry_md_write(h, subject, strlen(subject));
110 sig = gcry_md_read(h, ALGO);
111 sig64 = generic_to_base64(sig, HASHSIZE, cookie_base64_table);
116 /** @brief Create a login cookie
117 * @param user Username
118 * @return Cookie or NULL
120 char *make_cookie(const char *user) {
121 const char *password;
123 char *b, *bp, *c, *g;
125 /* dollar signs aren't allowed in usernames */
126 if(strchr(user, '$')) {
127 error(0, "make_cookie for username with dollar sign");
130 /* look up the password */
131 password = trackdb_get_password(user);
133 error(0, "make_cookie for nonexistent user");
136 /* make sure we have a valid signing key */
138 if(now >= signing_key_validity_limit)
140 /* construct the subject */
141 byte_xasprintf(&b, "%jx$%s$", (intmax_t)now + config->cookie_login_lifetime,
142 urlencodestring(user));
143 byte_xasprintf(&bp, "%s%s", b, password);
145 if(!(g = sign(signing_key, bp)))
147 /* put together the final cookie */
148 byte_xasprintf(&c, "%s%s", b, g);
152 /** @brief Verify a cookie
153 * @param cookie Cookie to verify
154 * @param rights Where to store rights value
155 * @return Verified user or NULL
157 char *verify_cookie(const char *cookie, rights_type *rights) {
161 char *user, *bp, *sig;
162 const char *password;
165 /* check the revocation list */
166 if(revoked && hash_find(revoked, cookie)) {
167 error(0, "attempt to log in with revoked cookie");
170 /* parse the cookie */
172 t = strtoimax(cookie, &c1, 16);
174 error(errno, "error parsing cookie timestamp");
178 error(0, "invalid cookie timestamp");
181 /* There'd better be two dollar signs */
182 c2 = strchr(c1 + 1, '$');
184 error(0, "invalid cookie syntax");
187 /* Extract the username */
188 user = xstrndup(c1 + 1, c2 - (c1 + 1));
192 error(0, "cookie has expired");
195 /* look up the password */
196 k = trackdb_getuserinfo(user);
198 error(0, "verify_cookie for nonexistent user");
201 password = kvp_get(k, "password");
202 if(!password) password = "";
203 if(parse_rights(kvp_get(k, "rights"), rights, 1))
205 /* construct the expected subject. We re-encode the timestamp and the
207 byte_xasprintf(&bp, "%jx$%s$%s", t, urlencodestring(user), password);
208 /* Compute the expected signature. NB we base64 the expected signature and
209 * compare that rather than exposing our base64 parser to the cookie. */
210 if(!(sig = sign(signing_key, bp)))
212 if(!strcmp(sig, c2 + 1))
214 /* that didn't match, try the old key */
215 if(!(sig = sign(old_signing_key, bp)))
217 if(!strcmp(sig, c2 + 1))
219 /* that didn't match either */
220 error(0, "cookie signature does not match");
224 /** @brief Revoke a cookie
225 * @param cookie Cookie to revoke
227 * Further attempts to log in with @p cookie will fail.
229 void revoke_cookie(const char *cookie) {
233 /* find the cookie's expiry time */
235 when = (time_t)strtoimax(cookie, &ptr, 16);
236 /* reject bogus cookies */
241 /* make sure the revocation list exists */
243 revoked = hash_new(sizeof(time_t));
244 /* add the cookie to it; its value is the expiry time */
245 hash_add(revoked, cookie, &when, HASH_INSERT);