LCOV - code coverage report
Current view: top level - src - sasl.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 284 0.0 %
Date: 2024-02-03 00:00:00 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
       2             : /* sasl.c
       3             : ** strophe XMPP client library -- SASL authentication helpers
       4             : **
       5             : ** Copyright (C) 2005-2009 Collecta, Inc.
       6             : **
       7             : **  This software is provided AS-IS with no warranty, either express
       8             : **  or implied.
       9             : **
      10             : **  This program is dual licensed under the MIT or GPLv3 licenses.
      11             : */
      12             : 
      13             : /** @file
      14             :  *  SASL authentication.
      15             :  */
      16             : 
      17             : #include <stdlib.h>
      18             : #include <string.h>
      19             : 
      20             : #include "strophe.h"
      21             : #include "common.h"
      22             : #include "ostypes.h"
      23             : #include "sasl.h"
      24             : #include "md5.h"
      25             : #include "scram.h"
      26             : #include "util.h"
      27             : 
      28             : /* strtok_s() has appeared in visual studio 2005.
      29             :    Use own implementation for older versions. */
      30             : #ifdef _MSC_VER
      31             : #if (_MSC_VER >= 1400)
      32             : #define strtok_r strtok_s
      33             : #else
      34             : #define strtok_r xmpp_strtok_r
      35             : #endif
      36             : #endif /* _MSC_VER */
      37             : 
      38             : /** generate authentication string for the SASL PLAIN mechanism */
      39           0 : char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password)
      40             : {
      41           0 :     size_t idlen, passlen;
      42           0 :     size_t msglen;
      43           0 :     char *result = NULL;
      44           0 :     char *msg;
      45             : 
      46             :     /* our message is Base64(authzid,\0,authid,\0,password)
      47             :        if there is no authzid, that field is left empty */
      48             : 
      49           0 :     idlen = strlen(authid);
      50           0 :     passlen = strlen(password);
      51           0 :     msglen = 2 + idlen + passlen;
      52           0 :     msg = strophe_alloc(ctx, msglen);
      53           0 :     if (msg != NULL) {
      54           0 :         msg[0] = '\0';
      55           0 :         memcpy(msg + 1, authid, idlen);
      56           0 :         msg[1 + idlen] = '\0';
      57           0 :         memcpy(msg + 1 + idlen + 1, password, passlen);
      58           0 :         result = xmpp_base64_encode(ctx, (unsigned char *)msg, msglen);
      59           0 :         strophe_free(ctx, msg);
      60             :     }
      61             : 
      62           0 :     return result;
      63             : }
      64             : 
      65             : /** helpers for digest auth */
      66             : 
      67             : /* create a new, null-terminated string from a substring */
      68           0 : static char *_make_string(xmpp_ctx_t *ctx, const char *s, unsigned len)
      69             : {
      70           0 :     char *result;
      71             : 
      72           0 :     result = strophe_alloc(ctx, len + 1);
      73           0 :     if (result != NULL) {
      74           0 :         memcpy(result, s, len);
      75           0 :         result[len] = '\0';
      76             :     }
      77           0 :     return result;
      78             : }
      79             : 
      80             : /* create a new, null-terminated string quoting another string */
      81           0 : static char *_make_quoted(xmpp_ctx_t *ctx, const char *s)
      82             : {
      83           0 :     char *result;
      84           0 :     size_t len = strlen(s);
      85             : 
      86           0 :     result = strophe_alloc(ctx, len + 3);
      87           0 :     if (result != NULL) {
      88           0 :         result[0] = '"';
      89           0 :         memcpy(result + 1, s, len);
      90           0 :         result[len + 1] = '"';
      91           0 :         result[len + 2] = '\0';
      92             :     }
      93           0 :     return result;
      94             : }
      95             : 
      96             : /* split key, value pairs into a hash */
      97           0 : static hash_t *_parse_digest_challenge(xmpp_ctx_t *ctx, const char *msg)
      98             : {
      99           0 :     hash_t *result;
     100           0 :     unsigned char *text;
     101           0 :     char *key, *value;
     102           0 :     unsigned char *s, *t;
     103             : 
     104           0 :     text = (unsigned char *)xmpp_base64_decode_str(ctx, msg, strlen(msg));
     105           0 :     if (text == NULL) {
     106           0 :         strophe_error(ctx, "SASL", "couldn't Base64 decode challenge!");
     107           0 :         return NULL;
     108             :     }
     109             : 
     110           0 :     result = hash_new(ctx, 10, strophe_free);
     111           0 :     if (result != NULL) {
     112             :         s = text;
     113           0 :         while (*s != '\0') {
     114             :             /* skip any leading commas and spaces */
     115           0 :             while ((*s == ',') || (*s == ' '))
     116           0 :                 s++;
     117             :             /* accumulate a key ending at '=' */
     118             :             t = s;
     119           0 :             while ((*t != '=') && (*t != '\0'))
     120           0 :                 t++;
     121           0 :             if (*t == '\0')
     122             :                 break; /* bad string */
     123           0 :             key = _make_string(ctx, (char *)s, (t - s));
     124           0 :             if (key == NULL)
     125             :                 break;
     126             :             /* advance our start pointer past the key */
     127           0 :             s = t + 1;
     128           0 :             t = s;
     129             :             /* if we see quotes, grab the string in between */
     130           0 :             if ((*s == '\'') || (*s == '"')) {
     131           0 :                 t++;
     132           0 :                 while ((*t != *s) && (*t != '\0'))
     133           0 :                     t++;
     134           0 :                 value = _make_string(ctx, (char *)s + 1, (t - s - 1));
     135           0 :                 if (*t == *s) {
     136           0 :                     s = t + 1;
     137             :                 } else {
     138             :                     s = t;
     139             :                 }
     140             :                 /* otherwise, accumulate a value ending in ',' or '\0' */
     141             :             } else {
     142           0 :                 while ((*t != ',') && (*t != '\0'))
     143           0 :                     t++;
     144           0 :                 value = _make_string(ctx, (char *)s, (t - s));
     145           0 :                 s = t;
     146             :             }
     147           0 :             if (value == NULL) {
     148           0 :                 strophe_free(ctx, key);
     149           0 :                 break;
     150             :             }
     151             :             /* TODO: check for collisions per spec */
     152           0 :             hash_add(result, key, value);
     153             :             /* hash table now owns the value, free the key */
     154           0 :             strophe_free(ctx, key);
     155             :         }
     156             :     }
     157           0 :     strophe_free(ctx, text);
     158             : 
     159           0 :     return result;
     160             : }
     161             : 
     162             : /** expand a 16 byte MD5 digest to a 32 byte hex representation */
     163           0 : static void _digest_to_hex(const char *digest, char *hex)
     164             : {
     165           0 :     int i;
     166           0 :     const char hexdigit[] = "0123456789abcdef";
     167             : 
     168           0 :     for (i = 0; i < 16; i++) {
     169           0 :         *hex++ = hexdigit[(digest[i] >> 4) & 0x0F];
     170           0 :         *hex++ = hexdigit[digest[i] & 0x0F];
     171             :     }
     172           0 : }
     173             : 
     174             : /** append 'key="value"' to a buffer, growing as necessary */
     175             : static char *
     176           0 : _add_key(xmpp_ctx_t *ctx, hash_t *table, const char *key, char *buf, int quote)
     177             : {
     178           0 :     int olen, nlen;
     179           0 :     int keylen, valuelen;
     180           0 :     const char *value, *qvalue;
     181           0 :     char *c;
     182             : 
     183             :     /* allocate a zero-length string if necessary */
     184           0 :     if (buf == NULL) {
     185           0 :         buf = strophe_alloc(ctx, 1);
     186           0 :         buf[0] = '\0';
     187             :     }
     188           0 :     if (buf == NULL)
     189             :         return NULL;
     190             : 
     191             :     /* get current string length */
     192           0 :     olen = strlen(buf);
     193           0 :     value = hash_get(table, key);
     194           0 :     if (value == NULL) {
     195           0 :         strophe_error(ctx, "SASL", "couldn't retrieve value for '%s'", key);
     196           0 :         value = "";
     197             :     }
     198           0 :     if (quote) {
     199           0 :         qvalue = _make_quoted(ctx, value);
     200             :     } else {
     201             :         qvalue = value;
     202             :     }
     203             :     /* added length is key + '=' + value */
     204             :     /*   (+ ',' if we're not the first entry   */
     205           0 :     keylen = strlen(key);
     206           0 :     valuelen = strlen(qvalue);
     207           0 :     nlen = (olen ? 1 : 0) + keylen + 1 + valuelen + 1;
     208           0 :     buf = strophe_realloc(ctx, buf, olen + nlen);
     209             : 
     210           0 :     if (buf != NULL) {
     211           0 :         c = buf + olen;
     212           0 :         if (olen)
     213           0 :             *c++ = ',';
     214           0 :         memcpy(c, key, keylen);
     215           0 :         c += keylen;
     216           0 :         *c++ = '=';
     217           0 :         memcpy(c, qvalue, valuelen);
     218           0 :         c += valuelen;
     219           0 :         *c++ = '\0';
     220             :     }
     221             : 
     222           0 :     if (quote)
     223           0 :         strophe_free(ctx, (char *)qvalue);
     224             : 
     225             :     return buf;
     226             : }
     227             : 
     228             : /** generate auth response string for the SASL DIGEST-MD5 mechanism */
     229           0 : char *sasl_digest_md5(xmpp_ctx_t *ctx,
     230             :                       const char *challenge,
     231             :                       const char *jid,
     232             :                       const char *password)
     233             : {
     234           0 :     hash_t *table;
     235           0 :     char *result = NULL;
     236           0 :     char *node, *domain, *realm;
     237           0 :     char *value;
     238           0 :     char *response;
     239           0 :     struct MD5Context MD5;
     240           0 :     unsigned char digest[16], HA1[16], HA2[16];
     241           0 :     char hex[32];
     242           0 :     char cnonce[13];
     243             : 
     244             :     /* our digest response is
     245             :     Hex( KD( HEX(MD5(A1)),
     246             :       nonce ':' nc ':' cnonce ':' qop ':' HEX(MD5(A2))
     247             :     ))
     248             : 
     249             :        where KD(k, s) = MD5(k ':' s),
     250             :     A1 = MD5( node ':' realm ':' password ) ':' nonce ':' cnonce
     251             :     A2 = "AUTHENTICATE" ':' "xmpp/" domain
     252             : 
     253             :        If there is an authzid it is ':'-appended to A1 */
     254             : 
     255             :     /* parse the challenge */
     256           0 :     table = _parse_digest_challenge(ctx, challenge);
     257           0 :     if (table == NULL) {
     258           0 :         strophe_error(ctx, "SASL", "couldn't parse digest challenge");
     259           0 :         return NULL;
     260             :     }
     261             : 
     262           0 :     node = xmpp_jid_node(ctx, jid);
     263           0 :     domain = xmpp_jid_domain(ctx, jid);
     264             : 
     265             :     /* generate default realm of domain if one didn't come from the
     266             :        server */
     267           0 :     realm = hash_get(table, "realm");
     268           0 :     if (realm == NULL || strlen(realm) == 0) {
     269           0 :         hash_add(table, "realm", strophe_strdup(ctx, domain));
     270           0 :         realm = hash_get(table, "realm");
     271             :     }
     272             : 
     273             :     /* add our response fields */
     274           0 :     hash_add(table, "username", strophe_strdup(ctx, node));
     275           0 :     xmpp_rand_nonce(ctx->rand, cnonce, sizeof(cnonce));
     276           0 :     hash_add(table, "cnonce", strophe_strdup(ctx, cnonce));
     277           0 :     hash_add(table, "nc", strophe_strdup(ctx, "00000001"));
     278           0 :     if (hash_get(table, "qop") == NULL)
     279           0 :         hash_add(table, "qop", strophe_strdup(ctx, "auth"));
     280           0 :     value = strophe_alloc(ctx, 5 + strlen(domain) + 1);
     281           0 :     memcpy(value, "xmpp/", 5);
     282           0 :     memcpy(value + 5, domain, strlen(domain));
     283           0 :     value[5 + strlen(domain)] = '\0';
     284           0 :     hash_add(table, "digest-uri", value);
     285             : 
     286             :     /* generate response */
     287             : 
     288             :     /* construct MD5(node : realm : password) */
     289           0 :     MD5Init(&MD5);
     290           0 :     MD5Update(&MD5, (unsigned char *)node, strlen(node));
     291           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     292           0 :     MD5Update(&MD5, (unsigned char *)realm, strlen(realm));
     293           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     294           0 :     MD5Update(&MD5, (unsigned char *)password, strlen(password));
     295           0 :     MD5Final(digest, &MD5);
     296             : 
     297             :     /* digest now contains the first field of A1 */
     298             : 
     299           0 :     MD5Init(&MD5);
     300           0 :     MD5Update(&MD5, digest, 16);
     301           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     302           0 :     value = hash_get(table, "nonce");
     303           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     304           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     305           0 :     value = hash_get(table, "cnonce");
     306           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     307           0 :     MD5Final(digest, &MD5);
     308             : 
     309             :     /* now digest is MD5(A1) */
     310           0 :     memcpy(HA1, digest, 16);
     311             : 
     312             :     /* construct MD5(A2) */
     313           0 :     MD5Init(&MD5);
     314           0 :     MD5Update(&MD5, (unsigned char *)"AUTHENTICATE:", 13);
     315           0 :     value = hash_get(table, "digest-uri");
     316           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     317           0 :     if (strcmp(hash_get(table, "qop"), "auth") != 0) {
     318           0 :         MD5Update(&MD5, (unsigned char *)":00000000000000000000000000000000",
     319             :                   33);
     320             :     }
     321           0 :     MD5Final(digest, &MD5);
     322             : 
     323           0 :     memcpy(HA2, digest, 16);
     324             : 
     325             :     /* construct response */
     326           0 :     MD5Init(&MD5);
     327           0 :     _digest_to_hex((char *)HA1, hex);
     328           0 :     MD5Update(&MD5, (unsigned char *)hex, 32);
     329           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     330           0 :     value = hash_get(table, "nonce");
     331           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     332           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     333           0 :     value = hash_get(table, "nc");
     334           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     335           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     336           0 :     value = hash_get(table, "cnonce");
     337           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     338           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     339           0 :     value = hash_get(table, "qop");
     340           0 :     MD5Update(&MD5, (unsigned char *)value, strlen(value));
     341           0 :     MD5Update(&MD5, (unsigned char *)":", 1);
     342           0 :     _digest_to_hex((char *)HA2, hex);
     343           0 :     MD5Update(&MD5, (unsigned char *)hex, 32);
     344           0 :     MD5Final(digest, &MD5);
     345             : 
     346           0 :     response = strophe_alloc(ctx, 32 + 1);
     347           0 :     _digest_to_hex((char *)digest, hex);
     348           0 :     memcpy(response, hex, 32);
     349           0 :     response[32] = '\0';
     350           0 :     hash_add(table, "response", response);
     351             : 
     352             :     /* construct reply */
     353           0 :     result = NULL;
     354           0 :     result = _add_key(ctx, table, "username", result, 1);
     355           0 :     result = _add_key(ctx, table, "realm", result, 1);
     356           0 :     result = _add_key(ctx, table, "nonce", result, 1);
     357           0 :     result = _add_key(ctx, table, "cnonce", result, 1);
     358           0 :     result = _add_key(ctx, table, "nc", result, 0);
     359           0 :     result = _add_key(ctx, table, "qop", result, 0);
     360           0 :     result = _add_key(ctx, table, "digest-uri", result, 1);
     361           0 :     result = _add_key(ctx, table, "response", result, 0);
     362           0 :     result = _add_key(ctx, table, "charset", result, 0);
     363             : 
     364           0 :     strophe_free(ctx, node);
     365           0 :     strophe_free(ctx, domain);
     366           0 :     hash_release(table); /* also frees value strings */
     367             : 
     368             :     /* reuse response for the base64 encode of our result */
     369           0 :     response = xmpp_base64_encode(ctx, (unsigned char *)result, strlen(result));
     370           0 :     strophe_free(ctx, result);
     371             : 
     372             :     return response;
     373             : }
     374             : 
     375             : /** generate auth response string for the SASL SCRAM mechanism */
     376           0 : char *sasl_scram(xmpp_ctx_t *ctx,
     377             :                  const struct hash_alg *alg,
     378             :                  const char *channel_binding,
     379             :                  const char *challenge,
     380             :                  const char *first_bare,
     381             :                  const char *jid,
     382             :                  const char *password)
     383             : {
     384           0 :     uint8_t key[SCRAM_DIGEST_SIZE];
     385           0 :     uint8_t sign[SCRAM_DIGEST_SIZE];
     386           0 :     char *r = NULL;
     387           0 :     char *s = NULL;
     388           0 :     char *i = NULL;
     389           0 :     unsigned char *sval;
     390           0 :     size_t sval_len;
     391           0 :     long ival;
     392           0 :     char *tmp;
     393           0 :     char *ptr;
     394           0 :     char *saveptr = NULL;
     395           0 :     char *response;
     396           0 :     char *auth;
     397           0 :     char *response_b64;
     398           0 :     char *sign_b64;
     399           0 :     char *result = NULL;
     400           0 :     size_t response_len;
     401           0 :     size_t auth_len;
     402           0 :     int l;
     403             : 
     404           0 :     UNUSED(jid);
     405             : 
     406           0 :     tmp = strophe_strdup(ctx, challenge);
     407           0 :     if (!tmp) {
     408           0 :         return NULL;
     409             :     }
     410             : 
     411           0 :     ptr = strtok_r(tmp, ",", &saveptr);
     412           0 :     while (ptr) {
     413           0 :         if (strncmp(ptr, "r=", 2) == 0) {
     414             :             r = ptr;
     415           0 :         } else if (strncmp(ptr, "s=", 2) == 0) {
     416           0 :             s = ptr + 2;
     417           0 :         } else if (strncmp(ptr, "i=", 2) == 0) {
     418           0 :             i = ptr + 2;
     419             :         }
     420           0 :         ptr = strtok_r(NULL, ",", &saveptr);
     421             :     }
     422             : 
     423           0 :     if (!r || !s || !i) {
     424           0 :         goto out;
     425             :     }
     426             : 
     427           0 :     xmpp_base64_decode_bin(ctx, s, strlen(s), &sval, &sval_len);
     428           0 :     if (!sval) {
     429           0 :         goto out;
     430             :     }
     431           0 :     ival = strtol(i, &saveptr, 10);
     432             : 
     433             :     /* "c=<channel_binding>," + r + ",p=" + sign_b64 + '\0' */
     434           0 :     response_len = 3 + strlen(channel_binding) + strlen(r) + 3 +
     435           0 :                    ((alg->digest_size + 2) / 3 * 4) + 1;
     436           0 :     response = strophe_alloc(ctx, response_len);
     437           0 :     if (!response) {
     438           0 :         goto out_sval;
     439             :     }
     440             : 
     441           0 :     auth_len = 3 + response_len + strlen(first_bare) + strlen(challenge);
     442           0 :     auth = strophe_alloc(ctx, auth_len);
     443           0 :     if (!auth) {
     444           0 :         goto out_response;
     445             :     }
     446             : 
     447           0 :     l = strophe_snprintf(response, response_len, "c=%s,%s", channel_binding, r);
     448           0 :     if (l < 0 || (size_t)l >= response_len) {
     449           0 :         goto out_auth;
     450             :     }
     451           0 :     l = strophe_snprintf(auth, auth_len, "%s,%s,%s", first_bare, challenge,
     452             :                          response);
     453           0 :     if (l < 0 || (size_t)l >= auth_len) {
     454           0 :         goto out_auth;
     455             :     }
     456             : 
     457           0 :     SCRAM_ClientKey(alg, (uint8_t *)password, strlen(password), (uint8_t *)sval,
     458             :                     sval_len, (uint32_t)ival, key);
     459           0 :     SCRAM_ClientSignature(alg, key, (uint8_t *)auth, strlen(auth), sign);
     460           0 :     SCRAM_ClientProof(alg, key, sign, sign);
     461             : 
     462           0 :     sign_b64 = xmpp_base64_encode(ctx, sign, alg->digest_size);
     463           0 :     if (!sign_b64) {
     464           0 :         goto out_auth;
     465             :     }
     466             : 
     467             :     /* Check for buffer overflow */
     468           0 :     if (strlen(response) + strlen(sign_b64) + 3 + 1 > response_len) {
     469           0 :         strophe_free(ctx, sign_b64);
     470           0 :         goto out_auth;
     471             :     }
     472           0 :     strcat(response, ",p=");
     473           0 :     strcat(response, sign_b64);
     474           0 :     strophe_free(ctx, sign_b64);
     475             : 
     476           0 :     response_b64 =
     477           0 :         xmpp_base64_encode(ctx, (unsigned char *)response, strlen(response));
     478           0 :     if (!response_b64) {
     479           0 :         goto out_auth;
     480             :     }
     481             :     result = response_b64;
     482             : 
     483           0 : out_auth:
     484           0 :     strophe_free(ctx, auth);
     485           0 : out_response:
     486           0 :     strophe_free(ctx, response);
     487           0 : out_sval:
     488           0 :     strophe_free(ctx, sval);
     489           0 : out:
     490           0 :     strophe_free(ctx, tmp);
     491             :     return result;
     492             : }

Generated by: LCOV version 1.14