]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/serf/auth/auth_digest.c
bhnd(9): Fix a few mandoc related issues
[FreeBSD/FreeBSD.git] / contrib / serf / auth / auth_digest.c
1 /* ====================================================================
2  *    Licensed to the Apache Software Foundation (ASF) under one
3  *    or more contributor license agreements.  See the NOTICE file
4  *    distributed with this work for additional information
5  *    regarding copyright ownership.  The ASF licenses this file
6  *    to you under the Apache License, Version 2.0 (the
7  *    "License"); you may not use this file except in compliance
8  *    with the License.  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *    Unless required by applicable law or agreed to in writing,
13  *    software distributed under the License is distributed on an
14  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  *    KIND, either express or implied.  See the License for the
16  *    specific language governing permissions and limitations
17  *    under the License.
18  * ====================================================================
19  */
20
21 /*** Digest authentication ***/
22
23 #include <serf.h>
24 #include <serf_private.h>
25 #include <auth/auth.h>
26
27 #include <apr.h>
28 #include <apr_base64.h>
29 #include <apr_strings.h>
30 #include <apr_uuid.h>
31 #include <apr_md5.h>
32
33 /** Digest authentication, implements RFC 2617. **/
34
35 /* TODO: add support for the domain attribute. This defines the protection
36    space, so that serf can decide per URI if it should reuse the cached
37    credentials for the server, or not. */
38
39 /* Stores the context information related to Digest authentication.
40    This information is stored in the per server cache in the serf context. */
41 typedef struct digest_authn_info_t {
42     /* nonce-count for digest authentication */
43     unsigned int digest_nc;
44
45     const char *header;
46
47     const char *ha1;
48
49     const char *realm;
50     const char *cnonce;
51     const char *nonce;
52     const char *opaque;
53     const char *algorithm;
54     const char *qop;
55     const char *username;
56
57     apr_pool_t *pool;
58 } digest_authn_info_t;
59
60 static char
61 int_to_hex(int v)
62 {
63     return (v < 10) ? '0' + v : 'a' + (v - 10);
64 }
65
66 /**
67  * Convert a string if ASCII characters HASHVAL to its hexadecimal
68  * representation.
69  *
70  * The returned string will be allocated in the POOL and be null-terminated.
71  */
72 static const char *
73 hex_encode(const unsigned char *hashval,
74            apr_pool_t *pool)
75 {
76     int i;
77     char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1);
78     for (i = 0; i < APR_MD5_DIGESTSIZE; i++) {
79         hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
80         hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
81     }
82     hexval[APR_MD5_DIGESTSIZE * 2] = '\0';
83     return hexval;
84 }
85
86 /**
87  * Returns a 36-byte long string of random characters.
88  * UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF.
89  *
90  * The returned string will be allocated in the POOL and be null-terminated.
91  */
92 static const char *
93 random_cnonce(apr_pool_t *pool)
94 {
95     apr_uuid_t uuid;
96     char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1);
97
98     apr_uuid_get(&uuid);
99     apr_uuid_format(buf, &uuid);
100
101     return hex_encode((unsigned char*)buf, pool);
102 }
103
104 static apr_status_t
105 build_digest_ha1(const char **out_ha1,
106                  const char *username,
107                  const char *password,
108                  const char *realm_name,
109                  apr_pool_t *pool)
110 {
111     const char *tmp;
112     unsigned char ha1[APR_MD5_DIGESTSIZE];
113     apr_status_t status;
114
115     /* calculate ha1:
116        MD5 hash of the combined user name, authentication realm and password */
117     tmp = apr_psprintf(pool, "%s:%s:%s",
118                        username,
119                        realm_name,
120                        password);
121     status = apr_md5(ha1, tmp, strlen(tmp));
122     if (status)
123         return status;
124
125     *out_ha1 = hex_encode(ha1, pool);
126
127     return APR_SUCCESS;
128 }
129
130 static apr_status_t
131 build_digest_ha2(const char **out_ha2,
132                  const char *uri,
133                  const char *method,
134                  const char *qop,
135                  apr_pool_t *pool)
136 {
137     if (!qop || strcmp(qop, "auth") == 0) {
138         const char *tmp;
139         unsigned char ha2[APR_MD5_DIGESTSIZE];
140         apr_status_t status;
141
142         /* calculate ha2:
143            MD5 hash of the combined method and URI */
144         tmp = apr_psprintf(pool, "%s:%s",
145                            method,
146                            uri);
147         status = apr_md5(ha2, tmp, strlen(tmp));
148         if (status)
149             return status;
150
151         *out_ha2 = hex_encode(ha2, pool);
152
153         return APR_SUCCESS;
154     } else {
155         /* TODO: auth-int isn't supported! */
156         return APR_ENOTIMPL;
157     }
158 }
159
160 static apr_status_t
161 build_auth_header(const char **out_header,
162                   digest_authn_info_t *digest_info,
163                   const char *path,
164                   const char *method,
165                   apr_pool_t *pool)
166 {
167     char *hdr;
168     const char *ha2;
169     const char *response;
170     unsigned char response_hdr[APR_MD5_DIGESTSIZE];
171     const char *response_hdr_hex;
172     apr_status_t status;
173
174     status = build_digest_ha2(&ha2, path, method, digest_info->qop, pool);
175     if (status)
176         return status;
177
178     hdr = apr_psprintf(pool,
179                        "Digest realm=\"%s\","
180                        " username=\"%s\","
181                        " nonce=\"%s\","
182                        " uri=\"%s\"",
183                        digest_info->realm, digest_info->username,
184                        digest_info->nonce,
185                        path);
186
187     if (digest_info->qop) {
188         if (! digest_info->cnonce)
189             digest_info->cnonce = random_cnonce(digest_info->pool);
190
191         hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"",
192                            hdr,
193                            digest_info->digest_nc,
194                            digest_info->cnonce,
195                            digest_info->qop);
196
197         /* Build the response header:
198            MD5 hash of the combined HA1 result, server nonce (nonce),
199            request counter (nc), client nonce (cnonce),
200            quality of protection code (qop) and HA2 result. */
201         response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s",
202                                 digest_info->ha1, digest_info->nonce,
203                                 digest_info->digest_nc,
204                                 digest_info->cnonce, digest_info->qop, ha2);
205     } else {
206         /* Build the response header:
207            MD5 hash of the combined HA1 result, server nonce (nonce)
208            and HA2 result. */
209         response = apr_psprintf(pool, "%s:%s:%s",
210                                 digest_info->ha1, digest_info->nonce, ha2);
211     }
212
213     status = apr_md5(response_hdr, response, strlen(response));
214     if (status)
215         return status;
216
217     response_hdr_hex = hex_encode(response_hdr, pool);
218
219     hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex);
220
221     if (digest_info->opaque) {
222         hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr,
223                            digest_info->opaque);
224     }
225     if (digest_info->algorithm) {
226         hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr,
227                            digest_info->algorithm);
228     }
229
230     *out_header = hdr;
231
232     return APR_SUCCESS;
233 }
234
235 apr_status_t
236 serf__handle_digest_auth(int code,
237                          serf_request_t *request,
238                          serf_bucket_t *response,
239                          const char *auth_hdr,
240                          const char *auth_attr,
241                          void *baton,
242                          apr_pool_t *pool)
243 {
244     char *attrs;
245     char *nextkv;
246     const char *realm, *realm_name = NULL;
247     const char *nonce = NULL;
248     const char *algorithm = NULL;
249     const char *qop = NULL;
250     const char *opaque = NULL;
251     const char *key;
252     serf_connection_t *conn = request->conn;
253     serf_context_t *ctx = conn->ctx;
254     serf__authn_info_t *authn_info;
255     digest_authn_info_t *digest_info;
256     apr_status_t status;
257     apr_pool_t *cred_pool;
258     char *username, *password;
259
260     /* Can't do Digest authentication if there's no callback to get
261        username & password. */
262     if (!ctx->cred_cb) {
263         return SERF_ERROR_AUTHN_FAILED;
264     }
265
266     if (code == 401) {
267         authn_info = serf__get_authn_info_for_server(conn);
268     } else {
269         authn_info = &ctx->proxy_authn_info;
270     }
271     digest_info = authn_info->baton;
272
273     /* Need a copy cuz we're going to write NUL characters into the string.  */
274     attrs = apr_pstrdup(pool, auth_attr);
275
276     /* We're expecting a list of key=value pairs, separated by a comma.
277        Ex. realm="SVN Digest",
278        nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5",
279        algorithm=MD5, qop="auth" */
280     for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) {
281         char *val;
282
283         val = strchr(key, '=');
284         if (val == NULL)
285             continue;
286         *val++ = '\0';
287
288         /* skip leading spaces */
289         while (*key && *key == ' ')
290             key++;
291
292         /* If the value is quoted, then remove the quotes.  */
293         if (*val == '"') {
294             apr_size_t last = strlen(val) - 1;
295
296             if (val[last] == '"') {
297                 val[last] = '\0';
298                 val++;
299             }
300         }
301
302         if (strcmp(key, "realm") == 0)
303             realm_name = val;
304         else if (strcmp(key, "nonce") == 0)
305             nonce = val;
306         else if (strcmp(key, "algorithm") == 0)
307             algorithm = val;
308         else if (strcmp(key, "qop") == 0)
309             qop = val;
310         else if (strcmp(key, "opaque") == 0)
311             opaque = val;
312
313         /* Ignore all unsupported attributes. */
314     }
315
316     if (!realm_name) {
317         return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE;
318     }
319
320     realm = serf__construct_realm(code == 401 ? HOST : PROXY,
321                                   conn, realm_name,
322                                   pool);
323
324     /* Ask the application for credentials */
325     apr_pool_create(&cred_pool, pool);
326     status = serf__provide_credentials(ctx,
327                                        &username, &password,
328                                        request, baton,
329                                        code, authn_info->scheme->name,
330                                        realm, cred_pool);
331     if (status) {
332         apr_pool_destroy(cred_pool);
333         return status;
334     }
335
336     digest_info->header = (code == 401) ? "Authorization" :
337                                           "Proxy-Authorization";
338
339     /* Store the digest authentication parameters in the context cached for
340        this server in the serf context, so we can use it to create the
341        Authorization header when setting up requests on the same or different
342        connections (e.g. in case of KeepAlive off on the server).
343        TODO: we currently don't cache this info per realm, so each time a request
344        'switches realms', we have to ask the application for new credentials. */
345     digest_info->pool = conn->pool;
346     digest_info->qop = apr_pstrdup(digest_info->pool, qop);
347     digest_info->nonce = apr_pstrdup(digest_info->pool, nonce);
348     digest_info->cnonce = NULL;
349     digest_info->opaque = apr_pstrdup(digest_info->pool, opaque);
350     digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm);
351     digest_info->realm = apr_pstrdup(digest_info->pool, realm_name);
352     digest_info->username = apr_pstrdup(digest_info->pool, username);
353     digest_info->digest_nc++;
354
355     status = build_digest_ha1(&digest_info->ha1, username, password,
356                               digest_info->realm, digest_info->pool);
357
358     apr_pool_destroy(cred_pool);
359
360     /* If the handshake is finished tell serf it can send as much requests as it
361        likes. */
362     serf_connection_set_max_outstanding_requests(conn, 0);
363
364     return status;
365 }
366
367 apr_status_t
368 serf__init_digest(int code,
369                   serf_context_t *ctx,
370                   apr_pool_t *pool)
371 {
372     return APR_SUCCESS;
373 }
374
375 apr_status_t
376 serf__init_digest_connection(const serf__authn_scheme_t *scheme,
377                              int code,
378                              serf_connection_t *conn,
379                              apr_pool_t *pool)
380 {
381     serf_context_t *ctx = conn->ctx;
382     serf__authn_info_t *authn_info;
383
384     if (code == 401) {
385         authn_info = serf__get_authn_info_for_server(conn);
386     } else {
387         authn_info = &ctx->proxy_authn_info;
388     }
389
390     if (!authn_info->baton) {
391         authn_info->baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
392     }
393
394     /* Make serf send the initial requests one by one */
395     serf_connection_set_max_outstanding_requests(conn, 1);
396
397     return APR_SUCCESS;
398 }
399
400 apr_status_t
401 serf__setup_request_digest_auth(peer_t peer,
402                                 int code,
403                                 serf_connection_t *conn,
404                                 serf_request_t *request,
405                                 const char *method,
406                                 const char *uri,
407                                 serf_bucket_t *hdrs_bkt)
408 {
409     serf_context_t *ctx = conn->ctx;
410     serf__authn_info_t *authn_info;
411     digest_authn_info_t *digest_info;
412     apr_status_t status;
413
414     if (peer == HOST) {
415         authn_info = serf__get_authn_info_for_server(conn);
416     } else {
417         authn_info = &ctx->proxy_authn_info;
418     }
419     digest_info = authn_info->baton;
420
421     if (digest_info && digest_info->realm) {
422         const char *value;
423         const char *path;
424
425         /* TODO: per request pool? */
426
427         /* for request 'CONNECT serf.googlecode.com:443', the uri also should be
428            serf.googlecode.com:443. apr_uri_parse can't handle this, so special
429            case. */
430         if (strcmp(method, "CONNECT") == 0)
431             path = uri;
432         else {
433             apr_uri_t parsed_uri;
434
435             /* Extract path from uri. */
436             status = apr_uri_parse(conn->pool, uri, &parsed_uri);
437             if (status)
438                 return status;
439
440             path = parsed_uri.path;
441         }
442
443         /* Build a new Authorization header. */
444         digest_info->header = (peer == HOST) ? "Authorization" :
445             "Proxy-Authorization";
446         status = build_auth_header(&value, digest_info, path, method,
447                                    conn->pool);
448         if (status)
449             return status;
450
451         serf_bucket_headers_setn(hdrs_bkt, digest_info->header,
452                                  value);
453         digest_info->digest_nc++;
454
455         /* Store the uri of this request on the serf_request_t object, to make
456            it available when validating the Authentication-Info header of the
457            matching response. */
458         request->auth_baton = (void *)path;
459     }
460
461     return APR_SUCCESS;
462 }
463
464 apr_status_t
465 serf__validate_response_digest_auth(const serf__authn_scheme_t *scheme,
466                                     peer_t peer,
467                                     int code,
468                                     serf_connection_t *conn,
469                                     serf_request_t *request,
470                                     serf_bucket_t *response,
471                                     apr_pool_t *pool)
472 {
473     const char *key;
474     char *auth_attr;
475     char *nextkv;
476     const char *rspauth = NULL;
477     const char *qop = NULL;
478     const char *nc_str = NULL;
479     serf_bucket_t *hdrs;
480     serf_context_t *ctx = conn->ctx;
481     apr_status_t status;
482
483     hdrs = serf_bucket_response_get_headers(response);
484
485     /* Need a copy cuz we're going to write NUL characters into the string.  */
486     if (peer == HOST)
487         auth_attr = apr_pstrdup(pool,
488             serf_bucket_headers_get(hdrs, "Authentication-Info"));
489     else
490         auth_attr = apr_pstrdup(pool,
491             serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info"));
492
493     /* If there's no Authentication-Info header there's nothing to validate. */
494     if (! auth_attr)
495         return APR_SUCCESS;
496
497     /* We're expecting a list of key=value pairs, separated by a comma.
498        Ex. rspauth="8a4b8451084b082be6b105e2b7975087",
499        cnonce="346531653132652d303033392d3435", nc=00000007,
500        qop=auth */
501     for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) {
502         char *val;
503
504         val = strchr(key, '=');
505         if (val == NULL)
506             continue;
507         *val++ = '\0';
508
509         /* skip leading spaces */
510         while (*key && *key == ' ')
511             key++;
512
513         /* If the value is quoted, then remove the quotes.  */
514         if (*val == '"') {
515             apr_size_t last = strlen(val) - 1;
516
517             if (val[last] == '"') {
518                 val[last] = '\0';
519                 val++;
520             }
521         }
522
523         if (strcmp(key, "rspauth") == 0)
524             rspauth = val;
525         else if (strcmp(key, "qop") == 0)
526             qop = val;
527         else if (strcmp(key, "nc") == 0)
528             nc_str = val;
529     }
530
531     if (rspauth) {
532         const char *ha2, *tmp, *resp_hdr_hex;
533         unsigned char resp_hdr[APR_MD5_DIGESTSIZE];
534         const char *req_uri = request->auth_baton;
535         serf__authn_info_t *authn_info;
536         digest_authn_info_t *digest_info;
537
538         if (peer == HOST) {
539             authn_info = serf__get_authn_info_for_server(conn);
540         } else {
541             authn_info = &ctx->proxy_authn_info;
542         }
543         digest_info = authn_info->baton;
544
545         status = build_digest_ha2(&ha2, req_uri, "", qop, pool);
546         if (status)
547             return status;
548
549         tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s",
550                            digest_info->ha1, digest_info->nonce, nc_str,
551                            digest_info->cnonce, digest_info->qop, ha2);
552         apr_md5(resp_hdr, tmp, strlen(tmp));
553         resp_hdr_hex =  hex_encode(resp_hdr, pool);
554
555         /* Incorrect response-digest in Authentication-Info header. */
556         if (strcmp(rspauth, resp_hdr_hex) != 0) {
557             return SERF_ERROR_AUTHN_FAILED;
558         }
559     }
560
561     return APR_SUCCESS;
562 }