From: Stephan Bosch Date: Tue, 18 Feb 2025 01:40:10 +0000 (+0100) Subject: lib-sasl: sasl-server-mech-digest-md5 - Move core processing to lib-auth/auth-digest X-Git-Tag: 2.4.2~145 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6763da9db3e4bf17c72d1486a192bab975835336;p=thirdparty%2Fdovecot%2Fcore.git lib-sasl: sasl-server-mech-digest-md5 - Move core processing to lib-auth/auth-digest --- diff --git a/src/lib-auth/auth-digest.c b/src/lib-auth/auth-digest.c index 06df4da42b..575d02cfff 100644 --- a/src/lib-auth/auth-digest.c +++ b/src/lib-auth/auth-digest.c @@ -1,6 +1,7 @@ /* Copyright (c) 2025 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "hex-binary.h" #include "hash-method.h" #include "auth-digest.h" @@ -89,3 +90,140 @@ void auth_digest_get_hash_a1_secret(const struct hash_method *hmethod, hash_method_loop(&ctx, password, strlen(password)); hash_method_result(&ctx, digest_r); } + +const char * +auth_digest_get_hash_a1(const struct hash_method *hmethod, + const unsigned char *hash_a1_secret, + const char *nonce, const char *cnonce, + const char *authzid) +{ + struct hash_method_context ctx; + + if (nonce == NULL) + return binary_to_hex(hash_a1_secret, hmethod->digest_size); + i_assert(cnonce != NULL); + + unsigned char digest[hmethod->digest_size]; + + /* A1 = H( unq(username) ":" unq(realm) ":" passwd ) + ":" unq(nonce-prime) ":" unq(cnonce-prime) + + If authzid is not NULL it is added in an additional ":" authzid as + per RFC 2831. + */ + + hash_method_init(&ctx, hmethod); + hash_method_loop(&ctx, hash_a1_secret, hmethod->digest_size); + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, nonce, strlen(nonce)); + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, cnonce, strlen(cnonce)); + if (authzid != NULL) { + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, authzid, strlen(authzid)); + } + hash_method_result(&ctx, digest); + + return binary_to_hex(digest, sizeof(digest)); +} + +const char * +auth_digest_get_hash_a2(const struct hash_method *hmethod, + const char *req_method, const char *req_uri, + const char *entity_body_hash) +{ + struct hash_method_context ctx; + unsigned char digest[hmethod->digest_size]; + + /* If the qop parameter's value is "auth" or is unspecified, then A2 is: + A2 = Method ":" request-uri + + If the qop value is "auth-int", then A2 is: + + A2 = Method ":" request-uri ":" H(entity-body) + */ + + hash_method_init(&ctx, hmethod); + if (req_method != NULL) + hash_method_loop(&ctx, req_method, strlen(req_method)); + hash_method_loop(&ctx, ":", 1); + if (req_uri != NULL) + hash_method_loop(&ctx, req_uri, strlen(req_uri)); + if (entity_body_hash != NULL) { + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, entity_body_hash, + strlen(entity_body_hash)); + } + hash_method_result(&ctx, digest); + + return binary_to_hex(digest, sizeof(digest)); +} + +static const char * +auth_digest_get_response(const struct hash_method *hmethod, + const char *hash_a1, const char *hash_a2, + const char *qop, const char *nonce, const char *nc, + const char *cnonce) +{ + /* response = <"> < KD ( H(A1), unq(nonce) + ":" nc + ":" unq(cnonce) + ":" unq(qop) + ":" H(A2) + ) <"> + */ + + struct hash_method_context ctx; + unsigned char digest[hmethod->digest_size]; + + hash_method_init(&ctx, hmethod); + hash_method_loop(&ctx, hash_a1, strlen(hash_a1)); + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, nonce, strlen(nonce)); + hash_method_loop(&ctx, ":", 1); + if (qop != NULL) { + hash_method_loop(&ctx, nc, strlen(nc)); + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, cnonce, strlen(cnonce)); + hash_method_loop(&ctx, ":", 1); + hash_method_loop(&ctx, qop, strlen(qop)); + hash_method_loop(&ctx, ":", 1); + } + hash_method_loop(&ctx, hash_a2, strlen(hash_a2)); + hash_method_result(&ctx, digest); + + return binary_to_hex(digest, sizeof(digest)); +} + +const char * +auth_digest_get_client_response(const struct hash_method *hmethod, + const char *hash_a1, const char *req_method, + const char *req_uri, const char *qop, + const char *nonce, const char *nc, + const char *cnonce, + const char *entity_body_hash) +{ + const char *hash_a2; + + hash_a2 = auth_digest_get_hash_a2(hmethod, req_method, req_uri, + entity_body_hash); + + return auth_digest_get_response(hmethod, hash_a1, hash_a2, + qop, nonce, nc, cnonce); +} + +const char * +auth_digest_get_server_response(const struct hash_method *hmethod, + const char *hash_a1, const char *req_uri, + const char *qop, const char *nonce, + const char *nc, const char *cnonce, + const char *entity_body_hash) +{ + const char *hash_a2; + + hash_a2 = auth_digest_get_hash_a2(hmethod, NULL, req_uri, + entity_body_hash); + + return auth_digest_get_response(hmethod, hash_a1, hash_a2, + qop, nonce, nc, cnonce); +} diff --git a/src/lib-auth/auth-digest.h b/src/lib-auth/auth-digest.h index 83c4302182..2ab4ab3640 100644 --- a/src/lib-auth/auth-digest.h +++ b/src/lib-auth/auth-digest.h @@ -15,5 +15,27 @@ void auth_digest_get_hash_a1_secret(const struct hash_method *hmethod, const char *username, const char *realm, const char *password, unsigned char *digest_r); +const char * +auth_digest_get_hash_a1(const struct hash_method *hmethod, + const unsigned char *hash_a1_secret, + const char *nonce, const char *cnonce, + const char *authzid); +const char * +auth_digest_get_hash_a2(const struct hash_method *hmethod, + const char *req_method, const char *req_uri, + const char *entity_body_hash); +const char * +auth_digest_get_client_response(const struct hash_method *hmethod, + const char *hash_a1, const char *req_method, + const char *req_uri, const char *qop, + const char *nonce, const char *nc, + const char *cnonce, + const char *entity_body_hash); +const char * +auth_digest_get_server_response(const struct hash_method *hmethod, + const char *hash_a1, const char *req_uri, + const char *qop, const char *nonce, + const char *nc, const char *cnonce, + const char *entity_body_hash); #endif diff --git a/src/lib-sasl/sasl-server-mech-digest-md5.c b/src/lib-sasl/sasl-server-mech-digest-md5.c index 26353fff06..07a1bbfc28 100644 --- a/src/lib-sasl/sasl-server-mech-digest-md5.c +++ b/src/lib-sasl/sasl-server-mech-digest-md5.c @@ -112,16 +112,14 @@ static void verify_credentials(struct sasl_server_mech_request *auth_request, const unsigned char *credentials, size_t size) { + static const struct hash_method *const hmethod = &hash_method_md5; struct digest_auth_request *request = container_of(auth_request, struct digest_auth_request, auth_request); - struct md5_context ctx; - unsigned char digest[MD5_RESULTLEN]; - const char *a1_hex, *a2_hex, *response_hex; - int i; + const char *a1_hex, *response_hex; /* get the MD5 password */ - if (size != MD5_RESULTLEN) { + if (size != hmethod->digest_size) { e_error(auth_request->event, "invalid credentials length"); sasl_server_request_failure(auth_request); return; @@ -154,74 +152,37 @@ verify_credentials(struct sasl_server_mech_request *auth_request, */ /* A1 */ - md5_init(&ctx); - md5_update(&ctx, credentials, size); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->nonce, strlen(request->nonce)); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->cnonce, strlen(request->cnonce)); - if (request->authzid != NULL) { - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->authzid, strlen(request->authzid)); - } - md5_final(&ctx, digest); - a1_hex = binary_to_hex(digest, 16); - - /* do it twice, first verify the user's response, the second is - sent for client as a reply */ - for (i = 0; i < 2; i++) { - /* A2 */ - md5_init(&ctx); - if (i == 0) - md5_update(&ctx, "AUTHENTICATE:", 13); - else - md5_update(&ctx, ":", 1); - - if (request->digest_uri != NULL) { - md5_update(&ctx, request->digest_uri, - strlen(request->digest_uri)); - } - if (request->qop == QOP_AUTH_INT || - request->qop == QOP_AUTH_CONF) { - md5_update(&ctx, ":00000000000000000000000000000000", - 33); - } - md5_final(&ctx, digest); - a2_hex = binary_to_hex(digest, 16); - - /* response */ - md5_init(&ctx); - md5_update(&ctx, a1_hex, 32); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->nonce, strlen(request->nonce)); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->nonce_count, - strlen(request->nonce_count)); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->cnonce, strlen(request->cnonce)); - md5_update(&ctx, ":", 1); - md5_update(&ctx, request->qop_value, - strlen(request->qop_value)); - md5_update(&ctx, ":", 1); - md5_update(&ctx, a2_hex, 32); - md5_final(&ctx, digest); - response_hex = binary_to_hex(digest, 16); - - if (i == 0) { - /* verify response */ - if (!mem_equals_timing_safe(response_hex, - request->response, 32)) { - sasl_server_request_password_mismatch( - auth_request); - return; - } - } else { - request->rspauth = - p_strconcat(auth_request->pool, "rspauth=", - response_hex, NULL); - } + a1_hex = auth_digest_get_hash_a1(hmethod, credentials, + request->nonce, request->cnonce, + request->authzid); + + const char *entity_body_hash = NULL; + + if (request->qop == QOP_AUTH_INT || + request->qop == QOP_AUTH_CONF) + entity_body_hash = "00000000000000000000000000000000"; + + /* client response */ + response_hex = auth_digest_get_client_response( + hmethod, a1_hex, "AUTHENTICATE", request->digest_uri, + request->qop_value, request->nonce, request->nonce_count, + request->cnonce, entity_body_hash); + + /* verify response */ + if (!mem_equals_timing_safe(response_hex, request->response, + hmethod->digest_size * 2)) { + sasl_server_request_password_mismatch(auth_request); + return; } + /* server response */ + response_hex = auth_digest_get_server_response( + hmethod, a1_hex, request->digest_uri, request->qop_value, + request->nonce, request->nonce_count, request->cnonce, + entity_body_hash); + + request->rspauth = p_strconcat(auth_request->pool, "rspauth=", + response_hex, NULL); sasl_server_request_success(auth_request, request->rspauth, strlen(request->rspauth)); }