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
40 #include "configuration.h"
43 /** @brief Hash function used in signing HMAC */
44 #define ALGO GCRY_MD_SHA1
46 /** @brief Size of key to use */
49 /** @brief Signing key */
50 static uint8_t signing_key
[HASHSIZE
];
52 /** @brief Previous signing key */
53 static uint8_t old_signing_key
[HASHSIZE
];
55 /** @brief Signing key validity limit or 0 if none */
56 static time_t signing_key_validity_limit
;
58 /** @brief Hash of revoked cookies */
61 /** @brief Callback to expire revocation list */
62 static int revoked_cleanup_callback(const char *key
, void *value
,
64 if(*(time_t *)value
< *(time_t *)u
)
65 hash_remove(revoked
, key
);
69 /** @brief Generate a new key */
70 static void newkey(void) {
74 memcpy(old_signing_key
, signing_key
, HASHSIZE
);
75 gcry_randomize(signing_key
, HASHSIZE
, GCRY_STRONG_RANDOM
);
76 signing_key_validity_limit
= now
+ config
->cookie_key_lifetime
;
77 /* Now is a good time to clean up the revocation list... */
79 hash_foreach(revoked
, revoked_cleanup_callback
, &now
);
82 /** @brief Sign @p subject with @p key and return the base64 of the result
83 * @param key Key to sign with (@ref HASHSIZE bytes)
84 * @param subject Subject string
85 * @return Base64-encoded signature or NULL
87 static char *sign(const uint8_t *key
,
88 const char *subject
) {
94 if((e
= gcry_md_open(&h
, ALGO
, GCRY_MD_FLAG_HMAC
))) {
95 error(0, "gcry_md_open: %s", gcry_strerror(e
));
98 if((e
= gcry_md_setkey(h
, key
, HASHSIZE
))) {
99 error(0, "gcry_md_setkey: %s", gcry_strerror(e
));
103 gcry_md_write(h
, subject
, strlen(subject
));
104 sig
= gcry_md_read(h
, ALGO
);
105 sig64
= mime_to_base64(sig
, HASHSIZE
);
110 /** @brief Create a login cookie
111 * @param user Username
112 * @return Cookie or NULL
114 char *make_cookie(const char *user
) {
117 char *b
, *bp
, *c
, *g
;
120 /* semicolons aren't allowed in usernames */
121 if(strchr(user
, ';')) {
122 error(0, "make_cookie for username with semicolon");
125 /* look up the password */
126 for(n
= 0; n
< config
->allow
.n
127 && strcmp(config
->allow
.s
[n
].s
[0], user
); ++n
)
129 if(n
>= config
->allow
.n
) {
130 error(0, "make_cookie for nonexistent user");
133 password
= config
->allow
.s
[n
].s
[1];
134 /* make sure we have a valid signing key */
136 if(now
>= signing_key_validity_limit
)
138 /* construct the subject */
139 byte_xasprintf(&b
, "%jx;%s;", (intmax_t)now
+ config
->cookie_login_lifetime
,
140 urlencodestring(user
));
141 byte_xasprintf(&bp
, "%s%s", b
, password
);
143 if(!(g
= sign(signing_key
, bp
)))
145 /* put together the final cookie */
146 byte_xasprintf(&c
, "%s%s", b
, g
);
150 /** @brief Verify a cookie
151 * @param cookie Cookie to verify
152 * @return Verified user or NULL
154 char *verify_cookie(const char *cookie
) {
158 char *user
, *bp
, *password
, *sig
;
161 /* check the revocation list */
162 if(revoked
&& hash_find(revoked
, cookie
)) {
163 error(0, "attempt to log in with revoked cookie");
166 /* parse the cookie */
168 t
= strtoimax(cookie
, &c1
, 16);
170 error(errno
, "error parsing cookie timestamp");
174 error(0, "invalid cookie timestamp");
177 /* There'd better be two semicolons */
178 c2
= strchr(c1
+ 1, ';');
180 error(0, "invalid cookie syntax");
183 /* Extract the username */
184 user
= xstrndup(c1
+ 1, c2
- (c1
+ 1));
188 error(0, "cookie has expired");
191 /* look up the password */
192 for(n
= 0; n
< config
->allow
.n
193 && strcmp(config
->allow
.s
[n
].s
[0], user
); ++n
)
195 if(n
>= config
->allow
.n
) {
196 error(0, "verify_cookie for nonexistent user");
199 password
= config
->allow
.s
[n
].s
[1];
200 /* construct the expected subject. We re-encode the timestamp and the
202 byte_xasprintf(&bp
, "%jx;%s;%s", t
, urlencodestring(user
), password
);
203 /* Compute the expected signature. NB we base64 the expected signature and
204 * compare that rather than exposing our base64 parser to the cookie. */
205 if(!(sig
= sign(signing_key
, bp
)))
207 if(!strcmp(sig
, c2
+ 1))
209 /* that didn't match, try the old key */
210 if(!(sig
= sign(old_signing_key
, bp
)))
212 if(!strcmp(sig
, c2
+ 1))
214 /* that didn't match either */
215 error(0, "cookie signature does not match");
219 /** @brief Revoke a cookie
220 * @param cookie Cookie to revoke
222 * Further attempts to log in with @p cookie will fail.
224 void revoke_cookie(const char *cookie
) {
228 /* find the cookie's expiry time */
230 when
= (time_t)strtoimax(cookie
, &ptr
, 16);
231 /* reject bogus cookies */
236 /* make sure the revocation list exists */
238 revoked
= hash_new(sizeof(time_t));
239 /* add the cookie to it; its value is the expiry time */
240 hash_add(revoked
, cookie
, &when
, HASH_INSERT
);