From 3aeb120e10106fafc587d9c50e66909eaba38da1 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 28 Aug 2018 22:47:39 -0400 Subject: [PATCH] Fix issue with HTTP Digest authentication, add unit tests (rdar://41709086) --- CHANGES.md | 3 +- cups/auth.c | 79 +++------------------------------ cups/http-private.h | 1 + cups/http-support.c | 105 ++++++++++++++++++++++++++++++++++++++++++++ cups/ipp.c | 2 +- cups/testhttp.c | 32 ++++++++++++++ 6 files changed, 147 insertions(+), 75 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e1e30efc1..2f74e1705f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -CHANGES - 2.3b6 - 2018-08-27 +CHANGES - 2.3b6 - 2018-08-28 ============================ Changes in CUPS v2.3b6 @@ -30,6 +30,7 @@ Changes in CUPS v2.3b6 - The scheduler did not validate that required initial request attributes were in the operation group (rdar://41098178) - Authentication in the web interface did not work on macOS (rdar://41444473) +- Fixed an issue with HTTP Digest authentication (rdar://41709086) - The scheduler could crash when job history was purged (rdar://42198057) - Fixed a memory leak for some IPP (extension) syntaxes. diff --git a/cups/auth.c b/cups/auth.c index 291a1a55fc..aeebcdea42 100644 --- a/cups/auth.c +++ b/cups/auth.c @@ -266,14 +266,7 @@ cupsDoAuthentication( /* Opaque data from server */ cnonce[65], /* cnonce value */ kd[65], /* Final MD5/SHA-256 digest */ - ha1[65], /* Hash of username:realm:password */ - ha2[65], /* Hash of method:request-uri */ - hdata[65], /* Hash of auth data */ - temp[1024], /* Temporary string */ digest[1024]; /* Digest auth data */ - unsigned char hash[32]; /* Hash buffer */ - const char *hashalg; /* Hashing algorithm */ - size_t hashsize; /* Size of hash */ if (strcmp(nonce, http->nonce)) { @@ -294,59 +287,12 @@ cupsDoAuthentication( if (cups_auth_param(schemedata, "algorithm", algorithm, sizeof(algorithm))) { /* - * Follow RFC 2617/7616... + * Calculate and pass the RFC 2617/7616 WWW-Authenticate header... */ - if (!_cups_strcasecmp(algorithm, "MD5")) - { - /* - * RFC 2617 Digest with MD5 - */ + if (!_httpDigest(kd, sizeof(kd), algorithm, cupsUser(), realm, strchr(http->userpass, ':') + 1, nonce, http->nonce_count, cnonce, "auth", method, resource)) + continue; - hashalg = "md5"; - } - else if (!_cups_strcasecmp(algorithm, "SHA-256")) - { - /* - * RFC 7616 Digest with SHA-256 - */ - - hashalg = "sha2-256"; - } - else - { - /* - * Some other algorithm we don't support, skip this one... - */ - - continue; - } - - /* - * Calculate digest value... - */ - - /* H(A1) = H(username:realm:password) */ - snprintf(temp, sizeof(temp), "%s:%s:%s", cupsUser(), realm, strchr(http->userpass, ':') + 1); - hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, ha1, sizeof(ha1)); - - /* H(A2) = H(method:uri) */ - snprintf(temp, sizeof(temp), "%s:%s", method, resource); - hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, ha2, sizeof(ha2)); - - /* H(data) = H(nonce:nc:cnonce:qop:H(A2)) */ - snprintf(temp, sizeof(temp), "%s:%08x:%s:auth:%s", nonce, http->nonce_count, cnonce, ha2); - hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, hdata, sizeof(hdata)); - - /* KD = H(H(A1):H(data)) */ - snprintf(temp, sizeof(temp), "%s:%s", ha1, hdata); - hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, kd, sizeof(kd)); - - /* Pass the RFC 2617/7616 WWW-Authenticate header */ if (opaque[0]) snprintf(digest, sizeof(digest), "username=\"%s\", realm=\"%s\", nonce=\"%s\", algorithm=%s, qop=auth, opaque=\"%s\", cnonce=\"%s\", nc=%08x, uri=\"%s\", response=\"%s\"", cupsUser(), realm, nonce, algorithm, opaque, cnonce, http->nonce_count, resource, kd); else @@ -355,25 +301,12 @@ cupsDoAuthentication( else { /* - * Use old RFC 2069 Digest method... + * Calculate and pass the old RFC 2069 WWW-Authenticate header... */ - /* H(A1) = H(username:realm:password) */ - snprintf(temp, sizeof(temp), "%s:%s:%s", cupsUser(), realm, strchr(http->userpass, ':') + 1); - hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, ha1, sizeof(ha1)); - - /* H(A2) = H(method:uri) */ - snprintf(temp, sizeof(temp), "%s:%s", method, resource); - hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, ha2, sizeof(ha2)); - - /* KD = H(H(A1):nonce:H(A2)) */ - snprintf(temp, sizeof(temp), "%s:%s:%s", ha1, nonce, ha2); - hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); - cupsHashString(hash, hashsize, kd, sizeof(kd)); + if (!_httpDigest(kd, sizeof(kd), NULL, cupsUser(), realm, strchr(http->userpass, ':') + 1, nonce, http->nonce_count, NULL, NULL, method, resource)) + continue; - /* Pass the RFC 2069 WWW-Authenticate header */ snprintf(digest, sizeof(digest), "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", cupsUser(), realm, nonce, resource, kd); } diff --git a/cups/http-private.h b/cups/http-private.h index 6d550a6593..047a692530 100644 --- a/cups/http-private.h +++ b/cups/http-private.h @@ -423,6 +423,7 @@ extern http_tls_credentials_t _httpCreateCredentials(cups_array_t *credentials); extern char *_httpDecodeURI(char *dst, const char *src, size_t dstsize); +extern char *_httpDigest(char *buffer, size_t bufsize, const char *algorithm, const char *username, const char *realm, const char *password, const char *nonce, unsigned nc, const char *cnonce, const char *qop, const char *method, const char *resource); extern void _httpDisconnect(http_t *http); extern char *_httpEncodeURI(char *dst, const char *src, size_t dstsize); diff --git a/cups/http-support.c b/cups/http-support.c index 2535908431..9a167cac36 100644 --- a/cups/http-support.c +++ b/cups/http-support.c @@ -661,6 +661,111 @@ httpDecode64_2(char *out, /* I - String to write to */ } +/* + * '_httpDigest()' - Calculate a Digest authentication response using the + * appropriate RFC 2068/2617/7616 algorithm. + */ + +char * /* O - Response string */ +_httpDigest(char *buffer, /* I - Response buffer */ + size_t bufsize, /* I - Size of response buffer */ + const char *algorithm, /* I - algorithm value or `NULL` */ + const char *username, /* I - username value */ + const char *realm, /* I - realm value */ + const char *password, /* I - password value */ + const char *nonce, /* I - nonce value */ + unsigned nc, /* I - nc value */ + const char *cnonce, /* I - cnonce value or `NULL` */ + const char *qop, /* I - qop value */ + const char *method, /* I - HTTP method */ + const char *resource) /* I - HTTP resource path */ +{ + char ha1[65], /* Hash of username:realm:password */ + ha2[65], /* Hash of method:request-uri */ + temp[1024]; /* Temporary string */ + unsigned char hash[32]; /* Hash buffer */ + const char *hashalg; /* Hashing algorithm */ + size_t hashsize; /* Size of hash */ + + + if (algorithm) + { + /* + * Follow RFC 2617/7616... + */ + + if (!_cups_strcasecmp(algorithm, "MD5")) + { + /* + * RFC 2617 Digest with MD5 + */ + + hashalg = "md5"; + } + else if (!_cups_strcasecmp(algorithm, "SHA-256")) + { + /* + * RFC 7616 Digest with SHA-256 + */ + + hashalg = "sha2-256"; + } + else + { + /* + * Some other algorithm we don't support, skip this one... + */ + + *buffer = '\0'; + + return (NULL); + } + + /* + * Calculate digest value... + */ + + /* H(A1) = H(username:realm:password) */ + snprintf(temp, sizeof(temp), "%s:%s:%s", username, realm, password); + hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, ha1, sizeof(ha1)); + + /* H(A2) = H(method:uri) */ + snprintf(temp, sizeof(temp), "%s:%s", method, resource); + hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, ha2, sizeof(ha2)); + + /* KD = H(H(A1):nonce:nc:cnonce:qop:H(A2)) */ + snprintf(temp, sizeof(temp), "%s:%s:%08x:%s:%s:%s", ha1, nonce, nc, cnonce, qop, ha2); + hashsize = (size_t)cupsHashData(hashalg, (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, buffer, bufsize); + } + else + { + /* + * Use old RFC 2069 Digest method... + */ + + /* H(A1) = H(username:realm:password) */ + snprintf(temp, sizeof(temp), "%s:%s:%s", username, realm, password); + hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, ha1, sizeof(ha1)); + + /* H(A2) = H(method:uri) */ + snprintf(temp, sizeof(temp), "%s:%s", method, resource); + hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, ha2, sizeof(ha2)); + + /* KD = H(H(A1):nonce:H(A2)) */ + snprintf(temp, sizeof(temp), "%s:%s:%s", ha1, nonce, ha2); + hashsize = (size_t)cupsHashData("md5", (unsigned char *)temp, strlen(temp), hash, sizeof(hash)); + cupsHashString(hash, hashsize, buffer, bufsize); + } + + return (buffer); +} + + /* * 'httpEncode64()' - Base64-encode a string. * diff --git a/cups/ipp.c b/cups/ipp.c index 9d65291f15..ac58cd9e97 100644 --- a/cups/ipp.c +++ b/cups/ipp.c @@ -4324,7 +4324,7 @@ ippSetString(ipp_t *ipp, /* I - IPP message */ if (!ipp || !attr || !*attr || (value_tag < IPP_TAG_TEXT && value_tag != IPP_TAG_TEXTLANG && value_tag != IPP_TAG_NAMELANG) || value_tag > IPP_TAG_MIMETYPE || - !strvalue) + element < 0 || element > (*attr)->num_values || !strvalue) return (0); /* diff --git a/cups/testhttp.c b/cups/testhttp.c index bc77b02369..3ad737f473 100644 --- a/cups/testhttp.c +++ b/cups/testhttp.c @@ -336,6 +336,38 @@ main(int argc, /* I - Number of command-line arguments */ if (!j) puts("PASS"); + /* + * _httpDigest() + */ + + fputs("_httpDigest(MD5): ", stdout); + if (!_httpDigest(buffer, sizeof(buffer), "MD5", "Mufasa", "http-auth@example.org", "Circle of Life", "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", 1, "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", "auth", "GET", "/dir/index.html")) + { + failures ++; + puts("FAIL (unable to calculate hash)"); + } + else if (strcmp(buffer, "8ca523f5e9506fed4657c9700eebdbec")) + { + failures ++; + printf("FAIL (got \"%s\", expected \"8ca523f5e9506fed4657c9700eebdbec\")\n", buffer); + } + else + puts("PASS"); + + fputs("_httpDigest(SHA-256): ", stdout); + if (!_httpDigest(buffer, sizeof(buffer), "SHA-256", "Mufasa", "http-auth@example.org", "Circle of Life", "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", 1, "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", "auth", "GET", "/dir/index.html")) + { + failures ++; + puts("FAIL (unable to calculate hash)"); + } + else if (strcmp(buffer, "753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1")) + { + failures ++; + printf("FAIL (got \"%s\", expected \"753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1\")\n", buffer); + } + else + puts("PASS"); + /* * httpGetHostname() */ -- 2.47.3