1251877Speter/* Copyright 2009 Justin Erenkrantz and Greg Stein
2251877Speter *
3251877Speter * Licensed under the Apache License, Version 2.0 (the "License");
4251877Speter * you may not use this file except in compliance with the License.
5251877Speter * You may obtain a copy of the License at
6251877Speter *
7251877Speter *     http://www.apache.org/licenses/LICENSE-2.0
8251877Speter *
9251877Speter * Unless required by applicable law or agreed to in writing, software
10251877Speter * distributed under the License is distributed on an "AS IS" BASIS,
11251877Speter * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12251877Speter * See the License for the specific language governing permissions and
13251877Speter * limitations under the License.
14251877Speter */
15251877Speter
16251877Speter/*** Digest authentication ***/
17251877Speter
18251877Speter#include <serf.h>
19251877Speter#include <serf_private.h>
20251877Speter#include <auth/auth.h>
21251877Speter
22251877Speter#include <apr.h>
23251877Speter#include <apr_base64.h>
24251877Speter#include <apr_strings.h>
25251877Speter#include <apr_uuid.h>
26251877Speter#include <apr_md5.h>
27251877Speter
28251877Speter/** Digest authentication, implements RFC 2617. **/
29251877Speter
30253895Speter/* TODO: add support for the domain attribute. This defines the protection
31253895Speter   space, so that serf can decide per URI if it should reuse the cached
32253895Speter   credentials for the server, or not. */
33253895Speter
34251877Speter/* Stores the context information related to Digest authentication.
35253895Speter   This information is stored in the per server cache in the serf context. */
36251877Spetertypedef struct digest_authn_info_t {
37251877Speter    /* nonce-count for digest authentication */
38251877Speter    unsigned int digest_nc;
39251877Speter
40251877Speter    const char *header;
41251877Speter
42251877Speter    const char *ha1;
43251877Speter
44251877Speter    const char *realm;
45251877Speter    const char *cnonce;
46251877Speter    const char *nonce;
47251877Speter    const char *opaque;
48251877Speter    const char *algorithm;
49251877Speter    const char *qop;
50251877Speter    const char *username;
51251877Speter
52251877Speter    apr_pool_t *pool;
53251877Speter} digest_authn_info_t;
54251877Speter
55251877Speterstatic char
56251877Speterint_to_hex(int v)
57251877Speter{
58251877Speter    return (v < 10) ? '0' + v : 'a' + (v - 10);
59251877Speter}
60251877Speter
61251877Speter/**
62251877Speter * Convert a string if ASCII characters HASHVAL to its hexadecimal
63251877Speter * representation.
64251877Speter *
65251877Speter * The returned string will be allocated in the POOL and be null-terminated.
66251877Speter */
67251877Speterstatic const char *
68251877Speterhex_encode(const unsigned char *hashval,
69251877Speter           apr_pool_t *pool)
70251877Speter{
71251877Speter    int i;
72251877Speter    char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1);
73251877Speter    for (i = 0; i < APR_MD5_DIGESTSIZE; i++) {
74251877Speter        hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
75251877Speter        hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
76251877Speter    }
77251877Speter    hexval[APR_MD5_DIGESTSIZE * 2] = '\0';
78251877Speter    return hexval;
79251877Speter}
80251877Speter
81251877Speter/**
82251877Speter * Returns a 36-byte long string of random characters.
83251877Speter * UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF.
84251877Speter *
85251877Speter * The returned string will be allocated in the POOL and be null-terminated.
86251877Speter */
87251877Speterstatic const char *
88251877Speterrandom_cnonce(apr_pool_t *pool)
89251877Speter{
90251877Speter    apr_uuid_t uuid;
91251877Speter    char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1);
92251877Speter
93251877Speter    apr_uuid_get(&uuid);
94251877Speter    apr_uuid_format(buf, &uuid);
95251877Speter
96251877Speter    return hex_encode((unsigned char*)buf, pool);
97251877Speter}
98251877Speter
99251877Speterstatic const char *
100251877Speterbuild_digest_ha1(const char *username,
101251877Speter                 const char *password,
102251877Speter                 const char *realm_name,
103251877Speter                 apr_pool_t *pool)
104251877Speter{
105251877Speter    const char *tmp;
106251877Speter    unsigned char ha1[APR_MD5_DIGESTSIZE];
107251877Speter    apr_status_t status;
108251877Speter
109251877Speter    /* calculate ha1:
110251877Speter       MD5 hash of the combined user name, authentication realm and password */
111251877Speter    tmp = apr_psprintf(pool, "%s:%s:%s",
112251877Speter                       username,
113251877Speter                       realm_name,
114251877Speter                       password);
115251877Speter    status = apr_md5(ha1, tmp, strlen(tmp));
116251877Speter
117251877Speter    return hex_encode(ha1, pool);
118251877Speter}
119251877Speter
120251877Speterstatic const char *
121251877Speterbuild_digest_ha2(const char *uri,
122251877Speter                 const char *method,
123251877Speter                 const char *qop,
124251877Speter                 apr_pool_t *pool)
125251877Speter{
126251877Speter    if (!qop || strcmp(qop, "auth") == 0) {
127251877Speter        const char *tmp;
128251877Speter        unsigned char ha2[APR_MD5_DIGESTSIZE];
129251877Speter        apr_status_t status;
130251877Speter
131251877Speter        /* calculate ha2:
132251877Speter           MD5 hash of the combined method and URI */
133251877Speter        tmp = apr_psprintf(pool, "%s:%s",
134251877Speter                           method,
135251877Speter                           uri);
136251877Speter        status = apr_md5(ha2, tmp, strlen(tmp));
137251877Speter
138251877Speter        return hex_encode(ha2, pool);
139251877Speter    } else {
140251877Speter        /* TODO: auth-int isn't supported! */
141251877Speter    }
142251877Speter
143251877Speter    return NULL;
144251877Speter}
145251877Speter
146251877Speterstatic const char *
147251877Speterbuild_auth_header(digest_authn_info_t *digest_info,
148251877Speter                  const char *path,
149251877Speter                  const char *method,
150251877Speter                  apr_pool_t *pool)
151251877Speter{
152251877Speter    char *hdr;
153251877Speter    const char *ha2;
154251877Speter    const char *response;
155251877Speter    unsigned char response_hdr[APR_MD5_DIGESTSIZE];
156251877Speter    const char *response_hdr_hex;
157251877Speter    apr_status_t status;
158251877Speter
159251877Speter    ha2 = build_digest_ha2(path, method, digest_info->qop, pool);
160251877Speter
161251877Speter    hdr = apr_psprintf(pool,
162251877Speter                       "Digest realm=\"%s\","
163251877Speter                       " username=\"%s\","
164251877Speter                       " nonce=\"%s\","
165251877Speter                       " uri=\"%s\"",
166251877Speter                       digest_info->realm, digest_info->username,
167251877Speter                       digest_info->nonce,
168251877Speter                       path);
169251877Speter
170251877Speter    if (digest_info->qop) {
171251877Speter        if (! digest_info->cnonce)
172251877Speter            digest_info->cnonce = random_cnonce(digest_info->pool);
173251877Speter
174251877Speter        hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"",
175251877Speter                           hdr,
176251877Speter                           digest_info->digest_nc,
177251877Speter                           digest_info->cnonce,
178251877Speter                           digest_info->qop);
179251877Speter
180251877Speter        /* Build the response header:
181251877Speter           MD5 hash of the combined HA1 result, server nonce (nonce),
182251877Speter           request counter (nc), client nonce (cnonce),
183251877Speter           quality of protection code (qop) and HA2 result. */
184251877Speter        response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s",
185251877Speter                                digest_info->ha1, digest_info->nonce,
186251877Speter                                digest_info->digest_nc,
187251877Speter                                digest_info->cnonce, digest_info->qop, ha2);
188251877Speter    } else {
189251877Speter        /* Build the response header:
190251877Speter           MD5 hash of the combined HA1 result, server nonce (nonce)
191251877Speter           and HA2 result. */
192251877Speter        response = apr_psprintf(pool, "%s:%s:%s",
193251877Speter                                digest_info->ha1, digest_info->nonce, ha2);
194251877Speter    }
195251877Speter
196251877Speter    status = apr_md5(response_hdr, response, strlen(response));
197251877Speter    response_hdr_hex = hex_encode(response_hdr, pool);
198251877Speter
199251877Speter    hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex);
200251877Speter
201251877Speter    if (digest_info->opaque) {
202251877Speter        hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr,
203251877Speter                           digest_info->opaque);
204251877Speter    }
205251877Speter    if (digest_info->algorithm) {
206251877Speter        hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr,
207251877Speter                           digest_info->algorithm);
208251877Speter    }
209251877Speter
210251877Speter    return hdr;
211251877Speter}
212251877Speter
213251877Speterapr_status_t
214251877Speterserf__handle_digest_auth(int code,
215251877Speter                         serf_request_t *request,
216251877Speter                         serf_bucket_t *response,
217251877Speter                         const char *auth_hdr,
218251877Speter                         const char *auth_attr,
219251877Speter                         void *baton,
220251877Speter                         apr_pool_t *pool)
221251877Speter{
222251877Speter    char *attrs;
223251877Speter    char *nextkv;
224253895Speter    const char *realm, *realm_name = NULL;
225251877Speter    const char *nonce = NULL;
226251877Speter    const char *algorithm = NULL;
227251877Speter    const char *qop = NULL;
228251877Speter    const char *opaque = NULL;
229251877Speter    const char *key;
230251877Speter    serf_connection_t *conn = request->conn;
231251877Speter    serf_context_t *ctx = conn->ctx;
232253895Speter    serf__authn_info_t *authn_info;
233253895Speter    digest_authn_info_t *digest_info;
234251877Speter    apr_status_t status;
235251877Speter    apr_pool_t *cred_pool;
236251877Speter    char *username, *password;
237251877Speter
238251877Speter    /* Can't do Digest authentication if there's no callback to get
239251877Speter       username & password. */
240251877Speter    if (!ctx->cred_cb) {
241251877Speter        return SERF_ERROR_AUTHN_FAILED;
242251877Speter    }
243251877Speter
244253895Speter    if (code == 401) {
245253895Speter        authn_info = serf__get_authn_info_for_server(conn);
246253895Speter    } else {
247253895Speter        authn_info = &ctx->proxy_authn_info;
248253895Speter    }
249253895Speter    digest_info = authn_info->baton;
250253895Speter
251251877Speter    /* Need a copy cuz we're going to write NUL characters into the string.  */
252251877Speter    attrs = apr_pstrdup(pool, auth_attr);
253251877Speter
254251877Speter    /* We're expecting a list of key=value pairs, separated by a comma.
255251877Speter       Ex. realm="SVN Digest",
256251877Speter       nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5",
257251877Speter       algorithm=MD5, qop="auth" */
258251877Speter    for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) {
259251877Speter        char *val;
260251877Speter
261251877Speter        val = strchr(key, '=');
262251877Speter        if (val == NULL)
263251877Speter            continue;
264251877Speter        *val++ = '\0';
265251877Speter
266251877Speter        /* skip leading spaces */
267251877Speter        while (*key && *key == ' ')
268251877Speter            key++;
269251877Speter
270251877Speter        /* If the value is quoted, then remove the quotes.  */
271251877Speter        if (*val == '"') {
272251877Speter            apr_size_t last = strlen(val) - 1;
273251877Speter
274251877Speter            if (val[last] == '"') {
275251877Speter                val[last] = '\0';
276251877Speter                val++;
277251877Speter            }
278251877Speter        }
279251877Speter
280251877Speter        if (strcmp(key, "realm") == 0)
281251877Speter            realm_name = val;
282251877Speter        else if (strcmp(key, "nonce") == 0)
283251877Speter            nonce = val;
284251877Speter        else if (strcmp(key, "algorithm") == 0)
285251877Speter            algorithm = val;
286251877Speter        else if (strcmp(key, "qop") == 0)
287251877Speter            qop = val;
288251877Speter        else if (strcmp(key, "opaque") == 0)
289251877Speter            opaque = val;
290251877Speter
291251877Speter        /* Ignore all unsupported attributes. */
292251877Speter    }
293251877Speter
294251877Speter    if (!realm_name) {
295251877Speter        return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE;
296251877Speter    }
297251877Speter
298253895Speter    realm = serf__construct_realm(code == 401 ? HOST : PROXY,
299253895Speter                                  conn, realm_name,
300253895Speter                                  pool);
301251877Speter
302251877Speter    /* Ask the application for credentials */
303251877Speter    apr_pool_create(&cred_pool, pool);
304253895Speter    status = serf__provide_credentials(ctx,
305253895Speter                                       &username, &password,
306253895Speter                                       request, baton,
307253895Speter                                       code, authn_info->scheme->name,
308253895Speter                                       realm, cred_pool);
309251877Speter    if (status) {
310251877Speter        apr_pool_destroy(cred_pool);
311251877Speter        return status;
312251877Speter    }
313251877Speter
314251877Speter    digest_info->header = (code == 401) ? "Authorization" :
315251877Speter                                          "Proxy-Authorization";
316251877Speter
317253895Speter    /* Store the digest authentication parameters in the context cached for
318253895Speter       this server in the serf context, so we can use it to create the
319253895Speter       Authorization header when setting up requests on the same or different
320253895Speter       connections (e.g. in case of KeepAlive off on the server).
321253895Speter       TODO: we currently don't cache this info per realm, so each time a request
322253895Speter       'switches realms', we have to ask the application for new credentials. */
323251877Speter    digest_info->pool = conn->pool;
324251877Speter    digest_info->qop = apr_pstrdup(digest_info->pool, qop);
325251877Speter    digest_info->nonce = apr_pstrdup(digest_info->pool, nonce);
326251877Speter    digest_info->cnonce = NULL;
327251877Speter    digest_info->opaque = apr_pstrdup(digest_info->pool, opaque);
328251877Speter    digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm);
329251877Speter    digest_info->realm = apr_pstrdup(digest_info->pool, realm_name);
330251877Speter    digest_info->username = apr_pstrdup(digest_info->pool, username);
331251877Speter    digest_info->digest_nc++;
332251877Speter
333251877Speter    digest_info->ha1 = build_digest_ha1(username, password, digest_info->realm,
334251877Speter                                        digest_info->pool);
335251877Speter
336251877Speter    apr_pool_destroy(cred_pool);
337251877Speter
338251877Speter    /* If the handshake is finished tell serf it can send as much requests as it
339251877Speter       likes. */
340251877Speter    serf_connection_set_max_outstanding_requests(conn, 0);
341251877Speter
342251877Speter    return APR_SUCCESS;
343251877Speter}
344251877Speter
345251877Speterapr_status_t
346251877Speterserf__init_digest(int code,
347251877Speter                  serf_context_t *ctx,
348251877Speter                  apr_pool_t *pool)
349251877Speter{
350251877Speter    return APR_SUCCESS;
351251877Speter}
352251877Speter
353251877Speterapr_status_t
354253895Speterserf__init_digest_connection(const serf__authn_scheme_t *scheme,
355253895Speter                             int code,
356251877Speter                             serf_connection_t *conn,
357251877Speter                             apr_pool_t *pool)
358251877Speter{
359253895Speter    serf_context_t *ctx = conn->ctx;
360253895Speter    serf__authn_info_t *authn_info;
361253895Speter
362251877Speter    if (code == 401) {
363253895Speter        authn_info = serf__get_authn_info_for_server(conn);
364251877Speter    } else {
365253895Speter        authn_info = &ctx->proxy_authn_info;
366251877Speter    }
367251877Speter
368253895Speter    if (!authn_info->baton) {
369253895Speter        authn_info->baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
370253895Speter    }
371253895Speter
372251877Speter    /* Make serf send the initial requests one by one */
373251877Speter    serf_connection_set_max_outstanding_requests(conn, 1);
374251877Speter
375251877Speter    return APR_SUCCESS;
376251877Speter}
377251877Speter
378251877Speterapr_status_t
379251877Speterserf__setup_request_digest_auth(peer_t peer,
380251877Speter                                int code,
381251877Speter                                serf_connection_t *conn,
382251877Speter                                serf_request_t *request,
383251877Speter                                const char *method,
384251877Speter                                const char *uri,
385251877Speter                                serf_bucket_t *hdrs_bkt)
386251877Speter{
387253895Speter    serf_context_t *ctx = conn->ctx;
388253895Speter    serf__authn_info_t *authn_info;
389253895Speter    digest_authn_info_t *digest_info;
390251877Speter    apr_status_t status = APR_SUCCESS;
391251877Speter
392253895Speter    if (peer == HOST) {
393253895Speter        authn_info = serf__get_authn_info_for_server(conn);
394253895Speter    } else {
395253895Speter        authn_info = &ctx->proxy_authn_info;
396253895Speter    }
397253895Speter    digest_info = authn_info->baton;
398253895Speter
399251877Speter    if (digest_info && digest_info->realm) {
400251877Speter        const char *value;
401253895Speter        const char *path;
402251877Speter
403251877Speter        /* TODO: per request pool? */
404251877Speter
405253895Speter        /* for request 'CONNECT serf.googlecode.com:443', the uri also should be
406253895Speter           serf.googlecode.com:443. apr_uri_parse can't handle this, so special
407253895Speter           case. */
408253895Speter        if (strcmp(method, "CONNECT") == 0)
409253895Speter            path = uri;
410253895Speter        else {
411253895Speter            apr_uri_t parsed_uri;
412251877Speter
413253895Speter            /* Extract path from uri. */
414253895Speter            status = apr_uri_parse(conn->pool, uri, &parsed_uri);
415253895Speter            if (status)
416253895Speter                return status;
417253895Speter
418253895Speter            path = parsed_uri.path;
419253895Speter        }
420253895Speter
421251877Speter        /* Build a new Authorization header. */
422251877Speter        digest_info->header = (peer == HOST) ? "Authorization" :
423251877Speter            "Proxy-Authorization";
424253895Speter        value = build_auth_header(digest_info, path, method,
425251877Speter                                  conn->pool);
426251877Speter
427251877Speter        serf_bucket_headers_setn(hdrs_bkt, digest_info->header,
428251877Speter                                 value);
429251877Speter        digest_info->digest_nc++;
430251877Speter
431251877Speter        /* Store the uri of this request on the serf_request_t object, to make
432251877Speter           it available when validating the Authentication-Info header of the
433251877Speter           matching response. */
434253895Speter        request->auth_baton = path;
435251877Speter    }
436251877Speter
437251877Speter    return status;
438251877Speter}
439251877Speter
440251877Speterapr_status_t
441251877Speterserf__validate_response_digest_auth(peer_t peer,
442251877Speter                                    int code,
443251877Speter                                    serf_connection_t *conn,
444251877Speter                                    serf_request_t *request,
445251877Speter                                    serf_bucket_t *response,
446251877Speter                                    apr_pool_t *pool)
447251877Speter{
448251877Speter    const char *key;
449251877Speter    char *auth_attr;
450251877Speter    char *nextkv;
451251877Speter    const char *rspauth = NULL;
452251877Speter    const char *qop = NULL;
453251877Speter    const char *nc_str = NULL;
454251877Speter    serf_bucket_t *hdrs;
455253895Speter    serf_context_t *ctx = conn->ctx;
456251877Speter
457251877Speter    hdrs = serf_bucket_response_get_headers(response);
458251877Speter
459251877Speter    /* Need a copy cuz we're going to write NUL characters into the string.  */
460251877Speter    if (peer == HOST)
461251877Speter        auth_attr = apr_pstrdup(pool,
462251877Speter            serf_bucket_headers_get(hdrs, "Authentication-Info"));
463251877Speter    else
464251877Speter        auth_attr = apr_pstrdup(pool,
465251877Speter            serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info"));
466251877Speter
467251877Speter    /* If there's no Authentication-Info header there's nothing to validate. */
468251877Speter    if (! auth_attr)
469251877Speter        return APR_SUCCESS;
470251877Speter
471251877Speter    /* We're expecting a list of key=value pairs, separated by a comma.
472251877Speter       Ex. rspauth="8a4b8451084b082be6b105e2b7975087",
473251877Speter       cnonce="346531653132652d303033392d3435", nc=00000007,
474251877Speter       qop=auth */
475251877Speter    for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) {
476251877Speter        char *val;
477251877Speter
478251877Speter        val = strchr(key, '=');
479251877Speter        if (val == NULL)
480251877Speter            continue;
481251877Speter        *val++ = '\0';
482251877Speter
483251877Speter        /* skip leading spaces */
484251877Speter        while (*key && *key == ' ')
485251877Speter            key++;
486251877Speter
487251877Speter        /* If the value is quoted, then remove the quotes.  */
488251877Speter        if (*val == '"') {
489251877Speter            apr_size_t last = strlen(val) - 1;
490251877Speter
491251877Speter            if (val[last] == '"') {
492251877Speter                val[last] = '\0';
493251877Speter                val++;
494251877Speter            }
495251877Speter        }
496251877Speter
497251877Speter        if (strcmp(key, "rspauth") == 0)
498251877Speter            rspauth = val;
499251877Speter        else if (strcmp(key, "qop") == 0)
500251877Speter            qop = val;
501251877Speter        else if (strcmp(key, "nc") == 0)
502251877Speter            nc_str = val;
503251877Speter    }
504251877Speter
505251877Speter    if (rspauth) {
506251877Speter        const char *ha2, *tmp, *resp_hdr_hex;
507251877Speter        unsigned char resp_hdr[APR_MD5_DIGESTSIZE];
508251877Speter        const char *req_uri = request->auth_baton;
509253895Speter        serf__authn_info_t *authn_info;
510253895Speter        digest_authn_info_t *digest_info;
511251877Speter
512253895Speter        if (peer == HOST) {
513253895Speter            authn_info = serf__get_authn_info_for_server(conn);
514253895Speter        } else {
515253895Speter            authn_info = &ctx->proxy_authn_info;
516253895Speter        }
517253895Speter        digest_info = authn_info->baton;
518253895Speter
519251877Speter        ha2 = build_digest_ha2(req_uri, "", qop, pool);
520251877Speter        tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s",
521251877Speter                           digest_info->ha1, digest_info->nonce, nc_str,
522251877Speter                           digest_info->cnonce, digest_info->qop, ha2);
523251877Speter        apr_md5(resp_hdr, tmp, strlen(tmp));
524251877Speter        resp_hdr_hex =  hex_encode(resp_hdr, pool);
525251877Speter
526251877Speter        /* Incorrect response-digest in Authentication-Info header. */
527251877Speter        if (strcmp(rspauth, resp_hdr_hex) != 0) {
528251877Speter            return SERF_ERROR_AUTHN_FAILED;
529251877Speter        }
530251877Speter    }
531251877Speter
532251877Speter    return APR_SUCCESS;
533251877Speter}
534