/* 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"
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);
+}
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
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;
*/
/* 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));
}