summaryrefslogtreecommitdiff
path: root/auth/auth_digest.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth/auth_digest.c')
-rw-r--r--auth/auth_digest.c475
1 files changed, 475 insertions, 0 deletions
diff --git a/auth/auth_digest.c b/auth/auth_digest.c
new file mode 100644
index 0000000..2e29180
--- /dev/null
+++ b/auth/auth_digest.c
@@ -0,0 +1,475 @@
+/* Copyright 2009 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*** Digest authentication ***/
+
+#include <serf.h>
+#include <serf_private.h>
+#include <auth/auth.h>
+
+#include <apr.h>
+#include <apr_base64.h>
+#include <apr_strings.h>
+#include <apr_uuid.h>
+#include <apr_md5.h>
+
+/** Digest authentication, implements RFC 2617. **/
+
+/* Stores the context information related to Digest authentication.
+ The context is per connection. */
+typedef struct digest_authn_info_t {
+ /* nonce-count for digest authentication */
+ unsigned int digest_nc;
+
+ const char *header;
+
+ const char *ha1;
+
+ const char *realm;
+ const char *cnonce;
+ const char *nonce;
+ const char *opaque;
+ const char *algorithm;
+ const char *qop;
+ const char *username;
+
+ apr_pool_t *pool;
+} digest_authn_info_t;
+
+static char
+int_to_hex(int v)
+{
+ return (v < 10) ? '0' + v : 'a' + (v - 10);
+}
+
+/**
+ * Convert a string if ASCII characters HASHVAL to its hexadecimal
+ * representation.
+ *
+ * The returned string will be allocated in the POOL and be null-terminated.
+ */
+static const char *
+hex_encode(const unsigned char *hashval,
+ apr_pool_t *pool)
+{
+ int i;
+ char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1);
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++) {
+ hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
+ hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
+ }
+ hexval[APR_MD5_DIGESTSIZE * 2] = '\0';
+ return hexval;
+}
+
+/**
+ * Returns a 36-byte long string of random characters.
+ * UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF.
+ *
+ * The returned string will be allocated in the POOL and be null-terminated.
+ */
+static const char *
+random_cnonce(apr_pool_t *pool)
+{
+ apr_uuid_t uuid;
+ char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1);
+
+ apr_uuid_get(&uuid);
+ apr_uuid_format(buf, &uuid);
+
+ return hex_encode((unsigned char*)buf, pool);
+}
+
+static const char *
+build_digest_ha1(const char *username,
+ const char *password,
+ const char *realm_name,
+ apr_pool_t *pool)
+{
+ const char *tmp;
+ unsigned char ha1[APR_MD5_DIGESTSIZE];
+ apr_status_t status;
+
+ /* calculate ha1:
+ MD5 hash of the combined user name, authentication realm and password */
+ tmp = apr_psprintf(pool, "%s:%s:%s",
+ username,
+ realm_name,
+ password);
+ status = apr_md5(ha1, tmp, strlen(tmp));
+
+ return hex_encode(ha1, pool);
+}
+
+static const char *
+build_digest_ha2(const char *uri,
+ const char *method,
+ const char *qop,
+ apr_pool_t *pool)
+{
+ if (!qop || strcmp(qop, "auth") == 0) {
+ const char *tmp;
+ unsigned char ha2[APR_MD5_DIGESTSIZE];
+ apr_status_t status;
+
+ /* calculate ha2:
+ MD5 hash of the combined method and URI */
+ tmp = apr_psprintf(pool, "%s:%s",
+ method,
+ uri);
+ status = apr_md5(ha2, tmp, strlen(tmp));
+
+ return hex_encode(ha2, pool);
+ } else {
+ /* TODO: auth-int isn't supported! */
+ }
+
+ return NULL;
+}
+
+static const char *
+build_auth_header(digest_authn_info_t *digest_info,
+ const char *path,
+ const char *method,
+ apr_pool_t *pool)
+{
+ char *hdr;
+ const char *ha2;
+ const char *response;
+ unsigned char response_hdr[APR_MD5_DIGESTSIZE];
+ const char *response_hdr_hex;
+ apr_status_t status;
+
+ ha2 = build_digest_ha2(path, method, digest_info->qop, pool);
+
+ hdr = apr_psprintf(pool,
+ "Digest realm=\"%s\","
+ " username=\"%s\","
+ " nonce=\"%s\","
+ " uri=\"%s\"",
+ digest_info->realm, digest_info->username,
+ digest_info->nonce,
+ path);
+
+ if (digest_info->qop) {
+ if (! digest_info->cnonce)
+ digest_info->cnonce = random_cnonce(digest_info->pool);
+
+ hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"",
+ hdr,
+ digest_info->digest_nc,
+ digest_info->cnonce,
+ digest_info->qop);
+
+ /* Build the response header:
+ MD5 hash of the combined HA1 result, server nonce (nonce),
+ request counter (nc), client nonce (cnonce),
+ quality of protection code (qop) and HA2 result. */
+ response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s",
+ digest_info->ha1, digest_info->nonce,
+ digest_info->digest_nc,
+ digest_info->cnonce, digest_info->qop, ha2);
+ } else {
+ /* Build the response header:
+ MD5 hash of the combined HA1 result, server nonce (nonce)
+ and HA2 result. */
+ response = apr_psprintf(pool, "%s:%s:%s",
+ digest_info->ha1, digest_info->nonce, ha2);
+ }
+
+ status = apr_md5(response_hdr, response, strlen(response));
+ response_hdr_hex = hex_encode(response_hdr, pool);
+
+ hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex);
+
+ if (digest_info->opaque) {
+ hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr,
+ digest_info->opaque);
+ }
+ if (digest_info->algorithm) {
+ hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr,
+ digest_info->algorithm);
+ }
+
+ return hdr;
+}
+
+apr_status_t
+serf__handle_digest_auth(int code,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ const char *auth_hdr,
+ const char *auth_attr,
+ void *baton,
+ apr_pool_t *pool)
+{
+ char *attrs;
+ char *nextkv;
+ const char *realm_name = NULL;
+ const char *nonce = NULL;
+ const char *algorithm = NULL;
+ const char *qop = NULL;
+ const char *opaque = NULL;
+ const char *key;
+ serf_connection_t *conn = request->conn;
+ serf_context_t *ctx = conn->ctx;
+ serf__authn_info_t *authn_info = (code == 401) ? &ctx->authn_info :
+ &ctx->proxy_authn_info;
+ digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+ apr_status_t status;
+ apr_pool_t *cred_pool;
+ char *username, *password;
+
+ /* Can't do Digest authentication if there's no callback to get
+ username & password. */
+ if (!ctx->cred_cb) {
+ return SERF_ERROR_AUTHN_FAILED;
+ }
+
+ /* Need a copy cuz we're going to write NUL characters into the string. */
+ attrs = apr_pstrdup(pool, auth_attr);
+
+ /* We're expecting a list of key=value pairs, separated by a comma.
+ Ex. realm="SVN Digest",
+ nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5",
+ algorithm=MD5, qop="auth" */
+ for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) {
+ char *val;
+
+ val = strchr(key, '=');
+ if (val == NULL)
+ continue;
+ *val++ = '\0';
+
+ /* skip leading spaces */
+ while (*key && *key == ' ')
+ key++;
+
+ /* If the value is quoted, then remove the quotes. */
+ if (*val == '"') {
+ apr_size_t last = strlen(val) - 1;
+
+ if (val[last] == '"') {
+ val[last] = '\0';
+ val++;
+ }
+ }
+
+ if (strcmp(key, "realm") == 0)
+ realm_name = val;
+ else if (strcmp(key, "nonce") == 0)
+ nonce = val;
+ else if (strcmp(key, "algorithm") == 0)
+ algorithm = val;
+ else if (strcmp(key, "qop") == 0)
+ qop = val;
+ else if (strcmp(key, "opaque") == 0)
+ opaque = val;
+
+ /* Ignore all unsupported attributes. */
+ }
+
+ if (!realm_name) {
+ return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE;
+ }
+
+ authn_info->realm = apr_psprintf(conn->pool, "<%s://%s:%d> %s",
+ conn->host_info.scheme,
+ conn->host_info.hostname,
+ conn->host_info.port,
+ realm_name);
+
+ /* Ask the application for credentials */
+ apr_pool_create(&cred_pool, pool);
+ status = (*ctx->cred_cb)(&username, &password, request, baton,
+ code, authn_info->scheme->name,
+ authn_info->realm, cred_pool);
+ if (status) {
+ apr_pool_destroy(cred_pool);
+ return status;
+ }
+
+ digest_info->header = (code == 401) ? "Authorization" :
+ "Proxy-Authorization";
+
+ /* Store the digest authentication parameters in the context relative
+ to this connection, so we can use it to create the Authorization header
+ when setting up requests. */
+ digest_info->pool = conn->pool;
+ digest_info->qop = apr_pstrdup(digest_info->pool, qop);
+ digest_info->nonce = apr_pstrdup(digest_info->pool, nonce);
+ digest_info->cnonce = NULL;
+ digest_info->opaque = apr_pstrdup(digest_info->pool, opaque);
+ digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm);
+ digest_info->realm = apr_pstrdup(digest_info->pool, realm_name);
+ digest_info->username = apr_pstrdup(digest_info->pool, username);
+ digest_info->digest_nc++;
+
+ digest_info->ha1 = build_digest_ha1(username, password, digest_info->realm,
+ digest_info->pool);
+
+ apr_pool_destroy(cred_pool);
+
+ /* If the handshake is finished tell serf it can send as much requests as it
+ likes. */
+ serf_connection_set_max_outstanding_requests(conn, 0);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+serf__init_digest(int code,
+ serf_context_t *ctx,
+ apr_pool_t *pool)
+{
+ return APR_SUCCESS;
+}
+
+apr_status_t
+serf__init_digest_connection(int code,
+ serf_connection_t *conn,
+ apr_pool_t *pool)
+{
+ /* Digest authentication is done per connection, so keep all progress
+ information per connection. */
+ if (code == 401) {
+ conn->authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
+ } else {
+ conn->proxy_authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
+ }
+
+ /* Make serf send the initial requests one by one */
+ serf_connection_set_max_outstanding_requests(conn, 1);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+serf__setup_request_digest_auth(int code,
+ serf_connection_t *conn,
+ const char *method,
+ const char *uri,
+ serf_bucket_t *hdrs_bkt)
+{
+ digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+ apr_status_t status = APR_SUCCESS;
+
+ if (digest_info && digest_info->realm) {
+ const char *value;
+ apr_uri_t parsed_uri;
+
+ /* extract path from uri */
+ status = apr_uri_parse(conn->pool, uri, &parsed_uri);
+
+ /* Build a new Authorization header. */
+ digest_info->header = (code == 401) ? "Authorization" :
+ "Proxy-Authorization";
+ value = build_auth_header(digest_info, parsed_uri.path, method,
+ conn->pool);
+
+ serf_bucket_headers_setn(hdrs_bkt, digest_info->header,
+ value);
+ digest_info->digest_nc++;
+ }
+
+ return status;
+}
+
+apr_status_t
+serf__validate_response_digest_auth(int code,
+ serf_connection_t *conn,
+ serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *pool)
+{
+ const char *key;
+ char *auth_attr;
+ char *nextkv;
+ const char *rspauth = NULL;
+ const char *qop = NULL;
+ const char *nc_str = NULL;
+ serf_bucket_t *hdrs;
+ digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton :
+ conn->proxy_authn_baton;
+
+ hdrs = serf_bucket_response_get_headers(response);
+
+ /* Need a copy cuz we're going to write NUL characters into the string. */
+ if (code == 401)
+ auth_attr = apr_pstrdup(pool,
+ serf_bucket_headers_get(hdrs, "Authentication-Info"));
+ else
+ auth_attr = apr_pstrdup(pool,
+ serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info"));
+
+ /* If there's no Authentication-Info header there's nothing to validate. */
+ if (! auth_attr)
+ return APR_SUCCESS;
+
+ /* We're expecting a list of key=value pairs, separated by a comma.
+ Ex. rspauth="8a4b8451084b082be6b105e2b7975087",
+ cnonce="346531653132652d303033392d3435", nc=00000007,
+ qop=auth */
+ for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) {
+ char *val;
+
+ val = strchr(key, '=');
+ if (val == NULL)
+ continue;
+ *val++ = '\0';
+
+ /* skip leading spaces */
+ while (*key && *key == ' ')
+ key++;
+
+ /* If the value is quoted, then remove the quotes. */
+ if (*val == '"') {
+ apr_size_t last = strlen(val) - 1;
+
+ if (val[last] == '"') {
+ val[last] = '\0';
+ val++;
+ }
+ }
+
+ if (strcmp(key, "rspauth") == 0)
+ rspauth = val;
+ else if (strcmp(key, "qop") == 0)
+ qop = val;
+ else if (strcmp(key, "nc") == 0)
+ nc_str = val;
+ }
+
+ if (rspauth) {
+ const char *ha2, *tmp, *resp_hdr_hex;
+ unsigned char resp_hdr[APR_MD5_DIGESTSIZE];
+
+ ha2 = build_digest_ha2(conn->host_info.path, "", qop, pool);
+ tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s",
+ digest_info->ha1, digest_info->nonce, nc_str,
+ digest_info->cnonce, digest_info->qop, ha2);
+ apr_md5(resp_hdr, tmp, strlen(tmp));
+ resp_hdr_hex = hex_encode(resp_hdr, pool);
+
+ /* Incorrect response-digest in Authentication-Info header. */
+ if (strcmp(rspauth, resp_hdr_hex) != 0) {
+ return SERF_ERROR_AUTHN_FAILED;
+ }
+ }
+
+ return APR_SUCCESS;
+}