1193323Sed/* ==================================================================== 2193323Sed * Licensed to the Apache Software Foundation (ASF) under one 3193323Sed * or more contributor license agreements. See the NOTICE file 4193323Sed * distributed with this work for additional information 5193323Sed * regarding copyright ownership. The ASF licenses this file 6193323Sed * to you under the Apache License, Version 2.0 (the 7193323Sed * "License"); you may not use this file except in compliance 8193323Sed * with the License. You may obtain a copy of the License at 9193323Sed * 10193323Sed * http://www.apache.org/licenses/LICENSE-2.0 11193323Sed * 12193323Sed * Unless required by applicable law or agreed to in writing, 13193323Sed * software distributed under the License is distributed on an 14193323Sed * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15193323Sed * KIND, either express or implied. See the License for the 16193323Sed * specific language governing permissions and limitations 17193323Sed * under the License. 18193323Sed * ==================================================================== 19210006Srdivacky */ 20218885Sdim 21193323Sed/*** Digest authentication ***/ 22193323Sed 23193323Sed#include <serf.h> 24193323Sed#include <serf_private.h> 25193323Sed#include <auth/auth.h> 26193323Sed 27193323Sed#include <apr.h> 28193323Sed#include <apr_base64.h> 29193323Sed#include <apr_strings.h> 30193323Sed#include <apr_uuid.h> 31193323Sed#include <apr_md5.h> 32193323Sed 33193323Sed/** Digest authentication, implements RFC 2617. **/ 34195340Sed 35193323Sed/* TODO: add support for the domain attribute. This defines the protection 36193323Sed space, so that serf can decide per URI if it should reuse the cached 37193323Sed credentials for the server, or not. */ 38193323Sed 39193323Sed/* Stores the context information related to Digest authentication. 40193323Sed This information is stored in the per server cache in the serf context. */ 41193323Sedtypedef struct digest_authn_info_t { 42193323Sed /* nonce-count for digest authentication */ 43193323Sed unsigned int digest_nc; 44193323Sed 45193323Sed const char *header; 46193323Sed 47195340Sed const char *ha1; 48198090Srdivacky 49193323Sed const char *realm; 50193323Sed const char *cnonce; 51212793Sdim const char *nonce; 52193323Sed const char *opaque; 53193323Sed const char *algorithm; 54193323Sed const char *qop; 55193323Sed const char *username; 56193323Sed 57193323Sed apr_pool_t *pool; 58205407Srdivacky} digest_authn_info_t; 59193323Sed 60193323Sedstatic char 61193323Sedint_to_hex(int v) 62193323Sed{ 63193323Sed return (v < 10) ? '0' + v : 'a' + (v - 10); 64193323Sed} 65212793Sdim 66205407Srdivacky/** 67205407Srdivacky * Convert a string if ASCII characters HASHVAL to its hexadecimal 68206083Srdivacky * representation. 69193323Sed * 70198090Srdivacky * The returned string will be allocated in the POOL and be null-terminated. 71193323Sed */ 72212793Sdimstatic const char * 73195340Sedhex_encode(const unsigned char *hashval, 74193323Sed apr_pool_t *pool) 75193323Sed{ 76193323Sed int i; 77193323Sed char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1); 78212793Sdim for (i = 0; i < APR_MD5_DIGESTSIZE; i++) { 79212793Sdim hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf); 80193323Sed hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf); 81193323Sed } 82212793Sdim hexval[APR_MD5_DIGESTSIZE * 2] = '\0'; 83193323Sed return hexval; 84193323Sed} 85193323Sed 86193323Sed/** 87193323Sed * Returns a 36-byte long string of random characters. 88193323Sed * UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF. 89193323Sed * 90207618Srdivacky * The returned string will be allocated in the POOL and be null-terminated. 91193323Sed */ 92193323Sedstatic const char * 93193323Sedrandom_cnonce(apr_pool_t *pool) 94193323Sed{ 95193323Sed apr_uuid_t uuid; 96193323Sed char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1); 97193323Sed 98193323Sed apr_uuid_get(&uuid); 99193323Sed apr_uuid_format(buf, &uuid); 100193323Sed 101207618Srdivacky return hex_encode((unsigned char*)buf, pool); 102193323Sed} 103193323Sed 104193323Sedstatic apr_status_t 105193323Sedbuild_digest_ha1(const char **out_ha1, 106207618Srdivacky const char *username, 107193323Sed const char *password, 108193323Sed const char *realm_name, 109193323Sed apr_pool_t *pool) 110193323Sed{ 111193323Sed const char *tmp; 112193323Sed unsigned char ha1[APR_MD5_DIGESTSIZE]; 113193323Sed apr_status_t status; 114193323Sed 115193323Sed /* calculate ha1: 116193323Sed MD5 hash of the combined user name, authentication realm and password */ 117193323Sed tmp = apr_psprintf(pool, "%s:%s:%s", 118193323Sed username, 119193323Sed realm_name, 120207618Srdivacky password); 121207618Srdivacky status = apr_md5(ha1, tmp, strlen(tmp)); 122193323Sed if (status) 123193323Sed return status; 124193323Sed 125193323Sed *out_ha1 = hex_encode(ha1, pool); 126207618Srdivacky 127193323Sed return APR_SUCCESS; 128193323Sed} 129193323Sed 130193323Sedstatic apr_status_t 131193323Sedbuild_digest_ha2(const char **out_ha2, 132193323Sed const char *uri, 133193323Sed const char *method, 134212793Sdim const char *qop, 135212793Sdim apr_pool_t *pool) 136193323Sed{ 137193323Sed if (!qop || strcmp(qop, "auth") == 0) { 138193323Sed const char *tmp; 139193323Sed unsigned char ha2[APR_MD5_DIGESTSIZE]; 140193323Sed apr_status_t status; 141193323Sed 142193323Sed /* calculate ha2: 143193323Sed MD5 hash of the combined method and URI */ 144193323Sed tmp = apr_psprintf(pool, "%s:%s", 145193323Sed method, 146193323Sed uri); 147193323Sed status = apr_md5(ha2, tmp, strlen(tmp)); 148193323Sed if (status) 149193323Sed return status; 150193323Sed 151193323Sed *out_ha2 = hex_encode(ha2, pool); 152193323Sed 153193323Sed return APR_SUCCESS; 154193323Sed } else { 155193323Sed /* TODO: auth-int isn't supported! */ 156193323Sed return APR_ENOTIMPL; 157193323Sed } 158193323Sed} 159193323Sed 160193323Sedstatic apr_status_t 161193323Sedbuild_auth_header(const char **out_header, 162193323Sed digest_authn_info_t *digest_info, 163207618Srdivacky const char *path, 164207618Srdivacky const char *method, 165207618Srdivacky apr_pool_t *pool) 166193323Sed{ 167212793Sdim char *hdr; 168193323Sed const char *ha2; 169193323Sed const char *response; 170207618Srdivacky unsigned char response_hdr[APR_MD5_DIGESTSIZE]; 171193323Sed const char *response_hdr_hex; 172212793Sdim apr_status_t status; 173212793Sdim 174207618Srdivacky status = build_digest_ha2(&ha2, path, method, digest_info->qop, pool); 175207618Srdivacky if (status) 176207618Srdivacky return status; 177212793Sdim 178193323Sed hdr = apr_psprintf(pool, 179193323Sed "Digest realm=\"%s\"," 180193323Sed " username=\"%s\"," 181207618Srdivacky " nonce=\"%s\"," 182207618Srdivacky " uri=\"%s\"", 183193323Sed digest_info->realm, digest_info->username, 184212793Sdim digest_info->nonce, 185212793Sdim path); 186212793Sdim 187193323Sed if (digest_info->qop) { 188193323Sed if (! digest_info->cnonce) 189193323Sed digest_info->cnonce = random_cnonce(digest_info->pool); 190193323Sed 191193323Sed hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"", 192193323Sed hdr, 193193323Sed digest_info->digest_nc, 194193323Sed digest_info->cnonce, 195193323Sed digest_info->qop); 196193323Sed 197193323Sed /* Build the response header: 198207618Srdivacky MD5 hash of the combined HA1 result, server nonce (nonce), 199207618Srdivacky request counter (nc), client nonce (cnonce), 200193323Sed quality of protection code (qop) and HA2 result. */ 201212793Sdim response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s", 202212793Sdim digest_info->ha1, digest_info->nonce, 203193323Sed digest_info->digest_nc, 204207618Srdivacky digest_info->cnonce, digest_info->qop, ha2); 205212793Sdim } else { 206207618Srdivacky /* Build the response header: 207212793Sdim MD5 hash of the combined HA1 result, server nonce (nonce) 208212793Sdim and HA2 result. */ 209193323Sed response = apr_psprintf(pool, "%s:%s:%s", 210212793Sdim digest_info->ha1, digest_info->nonce, ha2); 211212793Sdim } 212193323Sed 213193323Sed status = apr_md5(response_hdr, response, strlen(response)); 214193323Sed if (status) 215193323Sed return status; 216193323Sed 217193323Sed response_hdr_hex = hex_encode(response_hdr, pool); 218212793Sdim 219193323Sed hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex); 220193323Sed 221193323Sed if (digest_info->opaque) { 222193323Sed hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr, 223193323Sed digest_info->opaque); 224193323Sed } 225193323Sed if (digest_info->algorithm) { 226193323Sed hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr, 227193323Sed digest_info->algorithm); 228193323Sed } 229193323Sed 230193323Sed *out_header = hdr; 231193323Sed 232193323Sed return APR_SUCCESS; 233193323Sed} 234193323Sed 235193323Sedapr_status_t 236193323Sedserf__handle_digest_auth(int code, 237193323Sed serf_request_t *request, 238193323Sed serf_bucket_t *response, 239193323Sed const char *auth_hdr, 240193323Sed const char *auth_attr, 241193323Sed void *baton, 242193323Sed apr_pool_t *pool) 243193323Sed{ 244193323Sed char *attrs; 245212793Sdim char *nextkv; 246193323Sed const char *realm, *realm_name = NULL; 247193323Sed const char *nonce = NULL; 248193323Sed const char *algorithm = NULL; 249193323Sed const char *qop = NULL; 250193323Sed const char *opaque = NULL; 251193323Sed const char *key; 252193323Sed serf_connection_t *conn = request->conn; 253193323Sed serf_context_t *ctx = conn->ctx; 254198090Srdivacky serf__authn_info_t *authn_info; 255193323Sed digest_authn_info_t *digest_info; 256193323Sed apr_status_t status; 257193323Sed apr_pool_t *cred_pool; 258212793Sdim char *username, *password; 259212793Sdim 260193323Sed /* Can't do Digest authentication if there's no callback to get 261193323Sed username & password. */ 262193323Sed if (!ctx->cred_cb) { 263193323Sed return SERF_ERROR_AUTHN_FAILED; 264193323Sed } 265193323Sed 266193323Sed if (code == 401) { 267193323Sed authn_info = serf__get_authn_info_for_server(conn); 268193323Sed } else { 269193323Sed authn_info = &ctx->proxy_authn_info; 270193323Sed } 271212793Sdim digest_info = authn_info->baton; 272210006Srdivacky 273193323Sed /* Need a copy cuz we're going to write NUL characters into the string. */ 274193323Sed attrs = apr_pstrdup(pool, auth_attr); 275193323Sed 276193323Sed /* We're expecting a list of key=value pairs, separated by a comma. 277212793Sdim Ex. realm="SVN Digest", 278193323Sed nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5", 279193323Sed algorithm=MD5, qop="auth" */ 280193323Sed for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) { 281193323Sed char *val; 282193323Sed 283193323Sed val = strchr(key, '='); 284212793Sdim if (val == NULL) 285212793Sdim continue; 286193323Sed *val++ = '\0'; 287193323Sed 288212793Sdim /* skip leading spaces */ 289193323Sed while (*key && *key == ' ') 290193323Sed key++; 291193323Sed 292193323Sed /* If the value is quoted, then remove the quotes. */ 293193323Sed if (*val == '"') { 294193323Sed apr_size_t last = strlen(val) - 1; 295193323Sed 296193323Sed if (val[last] == '"') { 297193323Sed val[last] = '\0'; 298193323Sed val++; 299193323Sed } 300195340Sed } 301195340Sed 302193323Sed if (strcmp(key, "realm") == 0) 303193323Sed realm_name = val; 304193323Sed else if (strcmp(key, "nonce") == 0) 305193323Sed nonce = val; 306193323Sed else if (strcmp(key, "algorithm") == 0) 307212793Sdim algorithm = val; 308193323Sed else if (strcmp(key, "qop") == 0) 309193323Sed qop = val; 310193323Sed else if (strcmp(key, "opaque") == 0) 311193323Sed opaque = val; 312193323Sed 313193323Sed /* Ignore all unsupported attributes. */ 314193323Sed } 315193323Sed 316193323Sed if (!realm_name) { 317193323Sed return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE; 318193323Sed } 319193323Sed 320193323Sed realm = serf__construct_realm(code == 401 ? HOST : PROXY, 321193323Sed conn, realm_name, 322193323Sed pool); 323193323Sed 324193323Sed /* Ask the application for credentials */ 325193323Sed apr_pool_create(&cred_pool, pool); 326218885Sdim status = serf__provide_credentials(ctx, 327193323Sed &username, &password, 328193323Sed request, baton, 329193323Sed code, authn_info->scheme->name, 330193323Sed 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 367apr_status_t 368serf__init_digest(int code, 369 serf_context_t *ctx, 370 apr_pool_t *pool) 371{ 372 return APR_SUCCESS; 373} 374 375apr_status_t 376serf__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 400apr_status_t 401serf__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 464apr_status_t 465serf__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} 563