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 99262339Speterstatic apr_status_t 100262339Speterbuild_digest_ha1(const char **out_ha1, 101262339Speter const char *username, 102251877Speter const char *password, 103251877Speter const char *realm_name, 104251877Speter apr_pool_t *pool) 105251877Speter{ 106251877Speter const char *tmp; 107251877Speter unsigned char ha1[APR_MD5_DIGESTSIZE]; 108251877Speter apr_status_t status; 109251877Speter 110251877Speter /* calculate ha1: 111251877Speter MD5 hash of the combined user name, authentication realm and password */ 112251877Speter tmp = apr_psprintf(pool, "%s:%s:%s", 113251877Speter username, 114251877Speter realm_name, 115251877Speter password); 116251877Speter status = apr_md5(ha1, tmp, strlen(tmp)); 117262339Speter if (status) 118262339Speter return status; 119251877Speter 120262339Speter *out_ha1 = hex_encode(ha1, pool); 121262339Speter 122262339Speter return APR_SUCCESS; 123251877Speter} 124251877Speter 125262339Speterstatic apr_status_t 126262339Speterbuild_digest_ha2(const char **out_ha2, 127262339Speter const char *uri, 128251877Speter const char *method, 129251877Speter const char *qop, 130251877Speter apr_pool_t *pool) 131251877Speter{ 132251877Speter if (!qop || strcmp(qop, "auth") == 0) { 133251877Speter const char *tmp; 134251877Speter unsigned char ha2[APR_MD5_DIGESTSIZE]; 135251877Speter apr_status_t status; 136251877Speter 137251877Speter /* calculate ha2: 138251877Speter MD5 hash of the combined method and URI */ 139251877Speter tmp = apr_psprintf(pool, "%s:%s", 140251877Speter method, 141251877Speter uri); 142251877Speter status = apr_md5(ha2, tmp, strlen(tmp)); 143262339Speter if (status) 144262339Speter return status; 145251877Speter 146262339Speter *out_ha2 = hex_encode(ha2, pool); 147262339Speter 148262339Speter return APR_SUCCESS; 149251877Speter } else { 150251877Speter /* TODO: auth-int isn't supported! */ 151262339Speter return APR_ENOTIMPL; 152251877Speter } 153251877Speter} 154251877Speter 155262339Speterstatic apr_status_t 156262339Speterbuild_auth_header(const char **out_header, 157262339Speter digest_authn_info_t *digest_info, 158251877Speter const char *path, 159251877Speter const char *method, 160251877Speter apr_pool_t *pool) 161251877Speter{ 162251877Speter char *hdr; 163251877Speter const char *ha2; 164251877Speter const char *response; 165251877Speter unsigned char response_hdr[APR_MD5_DIGESTSIZE]; 166251877Speter const char *response_hdr_hex; 167251877Speter apr_status_t status; 168251877Speter 169262339Speter status = build_digest_ha2(&ha2, path, method, digest_info->qop, pool); 170262339Speter if (status) 171262339Speter return status; 172251877Speter 173251877Speter hdr = apr_psprintf(pool, 174251877Speter "Digest realm=\"%s\"," 175251877Speter " username=\"%s\"," 176251877Speter " nonce=\"%s\"," 177251877Speter " uri=\"%s\"", 178251877Speter digest_info->realm, digest_info->username, 179251877Speter digest_info->nonce, 180251877Speter path); 181251877Speter 182251877Speter if (digest_info->qop) { 183251877Speter if (! digest_info->cnonce) 184251877Speter digest_info->cnonce = random_cnonce(digest_info->pool); 185251877Speter 186251877Speter hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"", 187251877Speter hdr, 188251877Speter digest_info->digest_nc, 189251877Speter digest_info->cnonce, 190251877Speter digest_info->qop); 191251877Speter 192251877Speter /* Build the response header: 193251877Speter MD5 hash of the combined HA1 result, server nonce (nonce), 194251877Speter request counter (nc), client nonce (cnonce), 195251877Speter quality of protection code (qop) and HA2 result. */ 196251877Speter response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s", 197251877Speter digest_info->ha1, digest_info->nonce, 198251877Speter digest_info->digest_nc, 199251877Speter digest_info->cnonce, digest_info->qop, ha2); 200251877Speter } else { 201251877Speter /* Build the response header: 202251877Speter MD5 hash of the combined HA1 result, server nonce (nonce) 203251877Speter and HA2 result. */ 204251877Speter response = apr_psprintf(pool, "%s:%s:%s", 205251877Speter digest_info->ha1, digest_info->nonce, ha2); 206251877Speter } 207251877Speter 208251877Speter status = apr_md5(response_hdr, response, strlen(response)); 209262339Speter if (status) 210262339Speter return status; 211262339Speter 212251877Speter response_hdr_hex = hex_encode(response_hdr, pool); 213251877Speter 214251877Speter hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex); 215251877Speter 216251877Speter if (digest_info->opaque) { 217251877Speter hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr, 218251877Speter digest_info->opaque); 219251877Speter } 220251877Speter if (digest_info->algorithm) { 221251877Speter hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr, 222251877Speter digest_info->algorithm); 223251877Speter } 224251877Speter 225262339Speter *out_header = hdr; 226262339Speter 227262339Speter return APR_SUCCESS; 228251877Speter} 229251877Speter 230251877Speterapr_status_t 231251877Speterserf__handle_digest_auth(int code, 232251877Speter serf_request_t *request, 233251877Speter serf_bucket_t *response, 234251877Speter const char *auth_hdr, 235251877Speter const char *auth_attr, 236251877Speter void *baton, 237251877Speter apr_pool_t *pool) 238251877Speter{ 239251877Speter char *attrs; 240251877Speter char *nextkv; 241253895Speter const char *realm, *realm_name = NULL; 242251877Speter const char *nonce = NULL; 243251877Speter const char *algorithm = NULL; 244251877Speter const char *qop = NULL; 245251877Speter const char *opaque = NULL; 246251877Speter const char *key; 247251877Speter serf_connection_t *conn = request->conn; 248251877Speter serf_context_t *ctx = conn->ctx; 249253895Speter serf__authn_info_t *authn_info; 250253895Speter digest_authn_info_t *digest_info; 251251877Speter apr_status_t status; 252251877Speter apr_pool_t *cred_pool; 253251877Speter char *username, *password; 254251877Speter 255251877Speter /* Can't do Digest authentication if there's no callback to get 256251877Speter username & password. */ 257251877Speter if (!ctx->cred_cb) { 258251877Speter return SERF_ERROR_AUTHN_FAILED; 259251877Speter } 260251877Speter 261253895Speter if (code == 401) { 262253895Speter authn_info = serf__get_authn_info_for_server(conn); 263253895Speter } else { 264253895Speter authn_info = &ctx->proxy_authn_info; 265253895Speter } 266253895Speter digest_info = authn_info->baton; 267253895Speter 268251877Speter /* Need a copy cuz we're going to write NUL characters into the string. */ 269251877Speter attrs = apr_pstrdup(pool, auth_attr); 270251877Speter 271251877Speter /* We're expecting a list of key=value pairs, separated by a comma. 272251877Speter Ex. realm="SVN Digest", 273251877Speter nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5", 274251877Speter algorithm=MD5, qop="auth" */ 275251877Speter for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) { 276251877Speter char *val; 277251877Speter 278251877Speter val = strchr(key, '='); 279251877Speter if (val == NULL) 280251877Speter continue; 281251877Speter *val++ = '\0'; 282251877Speter 283251877Speter /* skip leading spaces */ 284251877Speter while (*key && *key == ' ') 285251877Speter key++; 286251877Speter 287251877Speter /* If the value is quoted, then remove the quotes. */ 288251877Speter if (*val == '"') { 289251877Speter apr_size_t last = strlen(val) - 1; 290251877Speter 291251877Speter if (val[last] == '"') { 292251877Speter val[last] = '\0'; 293251877Speter val++; 294251877Speter } 295251877Speter } 296251877Speter 297251877Speter if (strcmp(key, "realm") == 0) 298251877Speter realm_name = val; 299251877Speter else if (strcmp(key, "nonce") == 0) 300251877Speter nonce = val; 301251877Speter else if (strcmp(key, "algorithm") == 0) 302251877Speter algorithm = val; 303251877Speter else if (strcmp(key, "qop") == 0) 304251877Speter qop = val; 305251877Speter else if (strcmp(key, "opaque") == 0) 306251877Speter opaque = val; 307251877Speter 308251877Speter /* Ignore all unsupported attributes. */ 309251877Speter } 310251877Speter 311251877Speter if (!realm_name) { 312251877Speter return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; 313251877Speter } 314251877Speter 315253895Speter realm = serf__construct_realm(code == 401 ? HOST : PROXY, 316253895Speter conn, realm_name, 317253895Speter pool); 318251877Speter 319251877Speter /* Ask the application for credentials */ 320251877Speter apr_pool_create(&cred_pool, pool); 321253895Speter status = serf__provide_credentials(ctx, 322253895Speter &username, &password, 323253895Speter request, baton, 324253895Speter code, authn_info->scheme->name, 325253895Speter realm, cred_pool); 326251877Speter if (status) { 327251877Speter apr_pool_destroy(cred_pool); 328251877Speter return status; 329251877Speter } 330251877Speter 331251877Speter digest_info->header = (code == 401) ? "Authorization" : 332251877Speter "Proxy-Authorization"; 333251877Speter 334253895Speter /* Store the digest authentication parameters in the context cached for 335253895Speter this server in the serf context, so we can use it to create the 336253895Speter Authorization header when setting up requests on the same or different 337253895Speter connections (e.g. in case of KeepAlive off on the server). 338253895Speter TODO: we currently don't cache this info per realm, so each time a request 339253895Speter 'switches realms', we have to ask the application for new credentials. */ 340251877Speter digest_info->pool = conn->pool; 341251877Speter digest_info->qop = apr_pstrdup(digest_info->pool, qop); 342251877Speter digest_info->nonce = apr_pstrdup(digest_info->pool, nonce); 343251877Speter digest_info->cnonce = NULL; 344251877Speter digest_info->opaque = apr_pstrdup(digest_info->pool, opaque); 345251877Speter digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm); 346251877Speter digest_info->realm = apr_pstrdup(digest_info->pool, realm_name); 347251877Speter digest_info->username = apr_pstrdup(digest_info->pool, username); 348251877Speter digest_info->digest_nc++; 349251877Speter 350262339Speter status = build_digest_ha1(&digest_info->ha1, username, password, 351262339Speter digest_info->realm, digest_info->pool); 352251877Speter 353251877Speter apr_pool_destroy(cred_pool); 354251877Speter 355251877Speter /* If the handshake is finished tell serf it can send as much requests as it 356251877Speter likes. */ 357251877Speter serf_connection_set_max_outstanding_requests(conn, 0); 358251877Speter 359262339Speter return status; 360251877Speter} 361251877Speter 362251877Speterapr_status_t 363251877Speterserf__init_digest(int code, 364251877Speter serf_context_t *ctx, 365251877Speter apr_pool_t *pool) 366251877Speter{ 367251877Speter return APR_SUCCESS; 368251877Speter} 369251877Speter 370251877Speterapr_status_t 371253895Speterserf__init_digest_connection(const serf__authn_scheme_t *scheme, 372253895Speter int code, 373251877Speter serf_connection_t *conn, 374251877Speter apr_pool_t *pool) 375251877Speter{ 376253895Speter serf_context_t *ctx = conn->ctx; 377253895Speter serf__authn_info_t *authn_info; 378253895Speter 379251877Speter if (code == 401) { 380253895Speter authn_info = serf__get_authn_info_for_server(conn); 381251877Speter } else { 382253895Speter authn_info = &ctx->proxy_authn_info; 383251877Speter } 384251877Speter 385253895Speter if (!authn_info->baton) { 386253895Speter authn_info->baton = apr_pcalloc(pool, sizeof(digest_authn_info_t)); 387253895Speter } 388253895Speter 389251877Speter /* Make serf send the initial requests one by one */ 390251877Speter serf_connection_set_max_outstanding_requests(conn, 1); 391251877Speter 392251877Speter return APR_SUCCESS; 393251877Speter} 394251877Speter 395251877Speterapr_status_t 396251877Speterserf__setup_request_digest_auth(peer_t peer, 397251877Speter int code, 398251877Speter serf_connection_t *conn, 399251877Speter serf_request_t *request, 400251877Speter const char *method, 401251877Speter const char *uri, 402251877Speter serf_bucket_t *hdrs_bkt) 403251877Speter{ 404253895Speter serf_context_t *ctx = conn->ctx; 405253895Speter serf__authn_info_t *authn_info; 406253895Speter digest_authn_info_t *digest_info; 407262339Speter apr_status_t status; 408251877Speter 409253895Speter if (peer == HOST) { 410253895Speter authn_info = serf__get_authn_info_for_server(conn); 411253895Speter } else { 412253895Speter authn_info = &ctx->proxy_authn_info; 413253895Speter } 414253895Speter digest_info = authn_info->baton; 415253895Speter 416251877Speter if (digest_info && digest_info->realm) { 417251877Speter const char *value; 418253895Speter const char *path; 419251877Speter 420251877Speter /* TODO: per request pool? */ 421251877Speter 422253895Speter /* for request 'CONNECT serf.googlecode.com:443', the uri also should be 423253895Speter serf.googlecode.com:443. apr_uri_parse can't handle this, so special 424253895Speter case. */ 425253895Speter if (strcmp(method, "CONNECT") == 0) 426253895Speter path = uri; 427253895Speter else { 428253895Speter apr_uri_t parsed_uri; 429251877Speter 430253895Speter /* Extract path from uri. */ 431253895Speter status = apr_uri_parse(conn->pool, uri, &parsed_uri); 432253895Speter if (status) 433253895Speter return status; 434253895Speter 435253895Speter path = parsed_uri.path; 436253895Speter } 437253895Speter 438251877Speter /* Build a new Authorization header. */ 439251877Speter digest_info->header = (peer == HOST) ? "Authorization" : 440251877Speter "Proxy-Authorization"; 441262339Speter status = build_auth_header(&value, digest_info, path, method, 442262339Speter conn->pool); 443262339Speter if (status) 444262339Speter return status; 445251877Speter 446251877Speter serf_bucket_headers_setn(hdrs_bkt, digest_info->header, 447251877Speter value); 448251877Speter digest_info->digest_nc++; 449251877Speter 450251877Speter /* Store the uri of this request on the serf_request_t object, to make 451251877Speter it available when validating the Authentication-Info header of the 452251877Speter matching response. */ 453262339Speter request->auth_baton = (void *)path; 454251877Speter } 455251877Speter 456262339Speter return APR_SUCCESS; 457251877Speter} 458251877Speter 459251877Speterapr_status_t 460262339Speterserf__validate_response_digest_auth(const serf__authn_scheme_t *scheme, 461262339Speter peer_t peer, 462251877Speter int code, 463251877Speter serf_connection_t *conn, 464251877Speter serf_request_t *request, 465251877Speter serf_bucket_t *response, 466251877Speter apr_pool_t *pool) 467251877Speter{ 468251877Speter const char *key; 469251877Speter char *auth_attr; 470251877Speter char *nextkv; 471251877Speter const char *rspauth = NULL; 472251877Speter const char *qop = NULL; 473251877Speter const char *nc_str = NULL; 474251877Speter serf_bucket_t *hdrs; 475253895Speter serf_context_t *ctx = conn->ctx; 476262339Speter apr_status_t status; 477251877Speter 478251877Speter hdrs = serf_bucket_response_get_headers(response); 479251877Speter 480251877Speter /* Need a copy cuz we're going to write NUL characters into the string. */ 481251877Speter if (peer == HOST) 482251877Speter auth_attr = apr_pstrdup(pool, 483251877Speter serf_bucket_headers_get(hdrs, "Authentication-Info")); 484251877Speter else 485251877Speter auth_attr = apr_pstrdup(pool, 486251877Speter serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info")); 487251877Speter 488251877Speter /* If there's no Authentication-Info header there's nothing to validate. */ 489251877Speter if (! auth_attr) 490251877Speter return APR_SUCCESS; 491251877Speter 492251877Speter /* We're expecting a list of key=value pairs, separated by a comma. 493251877Speter Ex. rspauth="8a4b8451084b082be6b105e2b7975087", 494251877Speter cnonce="346531653132652d303033392d3435", nc=00000007, 495251877Speter qop=auth */ 496251877Speter for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) { 497251877Speter char *val; 498251877Speter 499251877Speter val = strchr(key, '='); 500251877Speter if (val == NULL) 501251877Speter continue; 502251877Speter *val++ = '\0'; 503251877Speter 504251877Speter /* skip leading spaces */ 505251877Speter while (*key && *key == ' ') 506251877Speter key++; 507251877Speter 508251877Speter /* If the value is quoted, then remove the quotes. */ 509251877Speter if (*val == '"') { 510251877Speter apr_size_t last = strlen(val) - 1; 511251877Speter 512251877Speter if (val[last] == '"') { 513251877Speter val[last] = '\0'; 514251877Speter val++; 515251877Speter } 516251877Speter } 517251877Speter 518251877Speter if (strcmp(key, "rspauth") == 0) 519251877Speter rspauth = val; 520251877Speter else if (strcmp(key, "qop") == 0) 521251877Speter qop = val; 522251877Speter else if (strcmp(key, "nc") == 0) 523251877Speter nc_str = val; 524251877Speter } 525251877Speter 526251877Speter if (rspauth) { 527251877Speter const char *ha2, *tmp, *resp_hdr_hex; 528251877Speter unsigned char resp_hdr[APR_MD5_DIGESTSIZE]; 529251877Speter const char *req_uri = request->auth_baton; 530253895Speter serf__authn_info_t *authn_info; 531253895Speter digest_authn_info_t *digest_info; 532251877Speter 533253895Speter if (peer == HOST) { 534253895Speter authn_info = serf__get_authn_info_for_server(conn); 535253895Speter } else { 536253895Speter authn_info = &ctx->proxy_authn_info; 537253895Speter } 538253895Speter digest_info = authn_info->baton; 539253895Speter 540262339Speter status = build_digest_ha2(&ha2, req_uri, "", qop, pool); 541262339Speter if (status) 542262339Speter return status; 543262339Speter 544251877Speter tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s", 545251877Speter digest_info->ha1, digest_info->nonce, nc_str, 546251877Speter digest_info->cnonce, digest_info->qop, ha2); 547251877Speter apr_md5(resp_hdr, tmp, strlen(tmp)); 548251877Speter resp_hdr_hex = hex_encode(resp_hdr, pool); 549251877Speter 550251877Speter /* Incorrect response-digest in Authentication-Info header. */ 551251877Speter if (strcmp(rspauth, resp_hdr_hex) != 0) { 552251877Speter return SERF_ERROR_AUTHN_FAILED; 553251877Speter } 554251877Speter } 555251877Speter 556251877Speter return APR_SUCCESS; 557251877Speter} 558