mech-digest-md5.c revision 853533bfe9e0e2fa96f6559d3fcf5945ea3300e5
76b43e4417bab52e913da39b5f5bc2a130d3f149Timo Sirainen/* Copyright (C) 2002 Timo Sirainen */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen/* Digest-MD5 SASL authentication, see RFC-2831 */
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen/* Linear whitespace */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen QOP_AUTH_INT = 0x02, /* + integrity protection, not supported yet */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen QOP_AUTH_CONF = 0x04, /* + encryption, not supported yet */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstatic const char *qop_names[] = { "auth", "auth-int", "auth-conf" };
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen /* requested: */
150e64c376365becf1ec5c9d45912ecb840eea96Timo Sirainen /* received: */
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen unsigned long maxbuf;
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen /* final reply: */
5afa8e2edf4f313cd56e5909f92f39c3b5b7b4d3Timo Sirainenstatic string_t *get_digest_challenge(struct digest_auth_request *auth)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen const char *const *tmp;
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen realm="hostname" (multiple allowed)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen nonce="randomized data, at least 64bit"
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen qop="auth,auth-int,auth-conf"
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen maxbuf=number (with auth-int, auth-conf, defaults to 64k)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen charset="utf-8" (iso-8859-1 if it doesn't exist)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen algorithm="md5-sess"
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen cipher="3des,des,rc4-40,rc4,rc4-56" (with auth-conf)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen /* get 128bit of random data as nonce */
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen buf = buffer_create_static(pool_datastack_create(),
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen auth->nonce = p_strdup(auth->pool, buffer_get_data(buf, NULL));
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen for (tmp = auth_realms; *tmp != NULL; tmp++) {
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen str_printfa(str, "nonce=\"%s\",", auth->nonce);
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen for (i = 0; i < QOP_COUNT; i++) {
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen "algorithm=\"md5-sess\"");
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainenstatic int verify_credentials(struct digest_auth_request *auth,
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen /* get the MD5 password */
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen if (credentials == NULL || strlen(credentials) != sizeof(digest)*2)
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen digest_buf = buffer_create_data(pool_datastack_create(),
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen if (hex_to_binary(credentials, digest_buf) <= 0)
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen HEX( KD ( HEX(H(A1)),
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen { nonce-value, ":" nc-value, ":",
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen and since we don't support authzid yet:
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen ":", nonce-value, ":", cnonce-value }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen If the "qop" directive's value is "auth", then A2 is:
fdc557286bc9f92c5f3bb49096ff6e2bcec0ea79Timo Sirainen A2 = { "AUTHENTICATE:", digest-uri-value }
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen If the "qop" value is "auth-int" or "auth-conf" then A2 is:
bc3698b8892df8003b410daea6f5bbcd20433808Timo Sirainen A2 = { "AUTHENTICATE:", digest-uri-value,
06e72c658de3ce1252594b151313df90acf73271Timo Sirainen ":00000000000000000000000000000000" }
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen md5_update(&ctx, auth->nonce, strlen(auth->nonce));
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen /* do it twice, first verify the user's response, the second is
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen sent for client as a reply */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen for (i = 0; i < 2; i++) {
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen if (auth->qop == QOP_AUTH_INT || auth->qop == QOP_AUTH_CONF) {
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen md5_update(&ctx, ":00000000000000000000000000000000",
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen /* response */
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen md5_update(&ctx, auth->nonce, strlen(auth->nonce));
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen md5_update(&ctx, auth->nonce_count, strlen(auth->nonce_count));
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen md5_update(&ctx, auth->qop_value, strlen(auth->qop_value));
bd74402ca1a39ec303075fefb1212d7e18a71531Timo Sirainen if (i == 0) {
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen /* verify response */
bd74402ca1a39ec303075fefb1212d7e18a71531Timo Sirainen if (memcmp(response_hex, auth->response, 32) != 0) {
c6ae908f6a2313573625d782bdd4e0ff3882c44aTimo Sirainen "password mismatch",
ef11d3930c3602fc86349a4e3a53442df470b601Timo Sirainen auth->rspauth = p_strconcat(auth->pool, "rspauth=",
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen const char *const *tmp;
3dd0679b6f24be0287cc42d7a60bbf59cdf8b637Timo Sirainen for (tmp = auth_realms; *tmp != NULL; tmp++) {
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainenstatic int parse_next(char **data, char **key, char **value)
8039af9679af6fb56116b353fe44f7dd4c08f031Timo Sirainen /* @UNSAFE */
8039af9679af6fb56116b353fe44f7dd4c08f031Timo Sirainen while (IS_LWS(*p)) p++;
a4bc2c3962b94f83c7bb7bebe7af364f4dee7883Timo Sirainen /* get key */
a4bc2c3962b94f83c7bb7bebe7af364f4dee7883Timo Sirainen if (*p != '=') {
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen /* skip trailing whitespace in key */
a4bc2c3962b94f83c7bb7bebe7af364f4dee7883Timo Sirainen /* get value */
a4bc2c3962b94f83c7bb7bebe7af364f4dee7883Timo Sirainen while (IS_LWS(*p)) p++;
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen if (*p != '"') {
17da42c31202b1b3e7e308121ea17d922c24da1bTimo Sirainen /* quoted string */
923eb3dde28e4d8841c14fd6b4a69635b7070c3eTimo Sirainen/* remove leading and trailing whitespace */
923eb3dde28e4d8841c14fd6b4a69635b7070c3eTimo Sirainen const char *ret;
923eb3dde28e4d8841c14fd6b4a69635b7070c3eTimo Sirainenstatic int auth_handle_response(struct digest_auth_request *auth,
a4bc2c3962b94f83c7bb7bebe7af364f4dee7883Timo Sirainen *error = "username must not exist more than once";
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen /* nonce must be same */
923eb3dde28e4d8841c14fd6b4a69635b7070c3eTimo Sirainen *error = "cnonce must not exist more than once";
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen *error = "nonce-count must not exist more than once";
923eb3dde28e4d8841c14fd6b4a69635b7070c3eTimo Sirainen auth->nonce_count = p_strdup(auth->pool, value);
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen for (i = 0; i < QOP_COUNT; i++) {
d42c9a8f362b76740418c4f9f9441eb7fc661e57Timo Sirainen auth->qop_value = p_strdup(auth->pool, value);
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen /* type / host / serv-name */
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen const char *const *uri = t_strsplit(value, "/");
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen /* FIXME: RFC recommends that we verify the host/serv-type.
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen But isn't the realm enough already? That'd be just extra
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen configuration.. Maybe optionally list valid hosts in
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen config file? */
a2f250a332dfc1e6cd4ffd196c621eb9dbf7b8a1Timo Sirainen auth->digest_uri = p_strdup(auth->pool, value);
88286b0527bcc0711e312e9db65ca121a45213e3Timo Sirainen *error = "maxbuf must not exist more than once";
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen /* not supported, ignore */
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen /* not supported, abort */
1939d1843ee6c7ca5e5baa3967b0332341440005Timo Sirainen /* unknown key, ignore */
1939d1843ee6c7ca5e5baa3967b0332341440005Timo Sirainenstatic int parse_digest_response(struct digest_auth_request *auth,
1939d1843ee6c7ca5e5baa3967b0332341440005Timo Sirainen const char **error)
2d340205d897e23fbecb40c8e63a4ca49bd6739bTimo Sirainen realm="realm"
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen username="username"
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen nonce="randomized data"
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen qop="auth|auth-int|auth-conf"
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen digest-uri="serv-type/host[/serv-name]"
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen response=32 HEX digits
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen maxbuf=number (with auth-int, auth-conf, defaults to 64k)
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen charset="utf-8" (iso-8859-1 if it doesn't exist)
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen cipher="cipher-value"
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen authzid="authzid-value"
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen copy = t_strdup_noconst(t_strndup(data, size));
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen if (!auth_handle_response(auth, key, value, error)) {
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth->nonce_count = p_strdup(auth->pool, "00000001");
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth->qop_value = p_strdup(auth->pool, "auth");
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainenstatic void credentials_callback(const char *result,
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen request->callback(&reply, auth->rspauth, request->conn);
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainenmech_digest_md5_auth_continue(struct auth_request *auth_request,
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen const unsigned char *data,
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen /* initialize reply */
c4267cf4c40fb1f866b5958ff122ef836b8c5dfbTimo Sirainen /* authentication is done, we were just waiting the last
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen word from client */
d756ebcfa96bd7cff02097c8f26df9df368b81b1Timo Sirainen mech_auth_finish(auth_request, NULL, 0, TRUE);
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen if (parse_digest_response(auth, data, request->data_size, &error)) {
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen realm = auth->realm != NULL ? auth->realm : default_realm;
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth_request->user = p_strdup(auth_request->pool,
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth_request->user = p_strconcat(auth_request->pool,
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen if (mech_is_valid_username(auth_request->user)) {
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen passdb->lookup_credentials(&auth->auth_request,
ccd83028a34cc6e2b6370eb7ecf1cf25e717c2d3Timo Sirainen auth->username == NULL ? "" : auth->username, error);
1939d1843ee6c7ca5e5baa3967b0332341440005Timo Sirainenstatic void mech_digest_md5_auth_free(struct auth_request *auth_request)
2d340205d897e23fbecb40c8e63a4ca49bd6739bTimo Sirainenmech_digest_md5_auth_new(struct auth_client_connection *conn,
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen pool = pool_alloconly_create("digest_md5_auth_request", 2048);
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth = p_new(pool, struct digest_auth_request, 1);
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth->auth_request.auth_continue = mech_digest_md5_auth_continue;
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen auth->auth_request.auth_free = mech_digest_md5_auth_free;
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen /* initialize reply */
c96eb61168670cfdd7596baba18856d3f086a093Timo Sirainen /* send the initial challenge */