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