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"
40 /** @brief Hash function used in signing HMAC */
41 #define ALGO GCRY_MD_SHA1
43 /** @brief Size of key to use */
46 /** @brief Signing key */
47 static uint8_t signing_key
[HASHSIZE
];
49 /** @brief Previous signing key */
50 static uint8_t old_signing_key
[HASHSIZE
];
52 /** @brief Signing key validity limit or 0 if none */
53 static time_t signing_key_validity_limit
;
55 /** @brief Hash of revoked cookies */
58 /** @brief Callback to expire revocation list */
59 static int revoked_cleanup_callback(const char *key
, void *value
,
61 if(*(time_t *)value
< *(time_t *)u
)
62 hash_remove(revoked
, key
);
66 /** @brief Generate a new key */
67 static void newkey(void) {
71 memcpy(old_signing_key
, signing_key
, HASHSIZE
);
72 gcry_randomize(signing_key
, HASHSIZE
, GCRY_STRONG_RANDOM
);
73 signing_key_validity_limit
= now
+ config
->cookie_key_lifetime
;
74 /* Now is a good time to clean up the revocation list... */
76 hash_foreach(revoked
, revoked_cleanup_callback
, &now
);
79 /** @brief Base64 mapping table for cookies
81 * Stupid Safari cannot cope with quoted cookies, so cookies had better not
82 * need quoting. We use $ to separate the parts of the cookie and +%# to where
83 * MIME uses +/=; see @ref base64.c. See http_separator() for the characters
86 static const char cookie_base64_table
[] =
87 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+%#";
89 /** @brief Sign @p subject with @p key and return the base64 of the result
90 * @param key Key to sign with (@ref HASHSIZE bytes)
91 * @param subject Subject string
92 * @return Base64-encoded signature or NULL
94 static char *sign(const uint8_t *key
,
95 const char *subject
) {
101 if((e
= gcry_md_open(&h
, ALGO
, GCRY_MD_FLAG_HMAC
))) {
102 disorder_error(0, "gcry_md_open: %s", gcry_strerror(e
));
105 if((e
= gcry_md_setkey(h
, key
, HASHSIZE
))) {
106 disorder_error(0, "gcry_md_setkey: %s", gcry_strerror(e
));
110 gcry_md_write(h
, subject
, strlen(subject
));
111 sig
= gcry_md_read(h
, ALGO
);
112 sig64
= generic_to_base64(sig
, HASHSIZE
, cookie_base64_table
);
117 /** @brief Create a login cookie
118 * @param user Username
119 * @return Cookie or NULL
121 char *make_cookie(const char *user
) {
122 const char *password
;
124 char *b
, *bp
, *c
, *g
;
126 /* dollar signs aren't allowed in usernames */
127 if(strchr(user
, '$')) {
128 disorder_error(0, "make_cookie for username with dollar sign");
131 /* look up the password */
132 password
= trackdb_get_password(user
);
134 disorder_error(0, "make_cookie for nonexistent user");
137 /* make sure we have a valid signing key */
139 if(now
>= signing_key_validity_limit
)
141 /* construct the subject */
142 byte_xasprintf(&b
, "%jx$%s$", (intmax_t)now
+ config
->cookie_login_lifetime
,
143 urlencodestring(user
));
144 byte_xasprintf(&bp
, "%s%s", b
, password
);
146 if(!(g
= sign(signing_key
, bp
)))
148 /* put together the final cookie */
149 byte_xasprintf(&c
, "%s%s", b
, g
);
153 /** @brief Verify a cookie
154 * @param cookie Cookie to verify
155 * @param rights Where to store rights value
156 * @return Verified user or NULL
158 char *verify_cookie(const char *cookie
, rights_type
*rights
) {
162 char *user
, *bp
, *sig
;
163 const char *password
;
166 /* check the revocation list */
167 if(revoked
&& hash_find(revoked
, cookie
)) {
168 disorder_error(0, "attempt to log in with revoked cookie");
171 /* parse the cookie */
173 t
= strtoimax(cookie
, &c1
, 16);
175 disorder_error(errno
, "error parsing cookie timestamp");
179 disorder_error(0, "invalid cookie timestamp");
182 /* There'd better be two dollar signs */
183 c2
= strchr(c1
+ 1, '$');
185 disorder_error(0, "invalid cookie syntax");
188 /* Extract the username */
189 user
= xstrndup(c1
+ 1, c2
- (c1
+ 1));
193 disorder_error(0, "cookie has expired");
196 /* look up the password */
197 k
= trackdb_getuserinfo(user
);
199 disorder_error(0, "verify_cookie for nonexistent user");
202 password
= kvp_get(k
, "password");
203 if(!password
) password
= "";
204 if(parse_rights(kvp_get(k
, "rights"), rights
, 1))
206 /* construct the expected subject. We re-encode the timestamp and the
208 byte_xasprintf(&bp
, "%jx$%s$%s", t
, urlencodestring(user
), password
);
209 /* Compute the expected signature. NB we base64 the expected signature and
210 * compare that rather than exposing our base64 parser to the cookie. */
211 if(!(sig
= sign(signing_key
, bp
)))
213 if(!strcmp(sig
, c2
+ 1))
215 /* that didn't match, try the old key */
216 if(!(sig
= sign(old_signing_key
, bp
)))
218 if(!strcmp(sig
, c2
+ 1))
220 /* that didn't match either */
221 disorder_error(0, "cookie signature does not match");
225 /** @brief Revoke a cookie
226 * @param cookie Cookie to revoke
228 * Further attempts to log in with @p cookie will fail.
230 void revoke_cookie(const char *cookie
) {
234 /* find the cookie's expiry time */
236 when
= (time_t)strtoimax(cookie
, &ptr
, 16);
237 /* reject bogus cookies */
242 /* make sure the revocation list exists */
244 revoked
= hash_new(sizeof(time_t));
245 /* add the cookie to it; its value is the expiry time */
246 hash_add(revoked
, cookie
, &when
, HASH_INSERT
);