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