LCOV - code coverage report
Current view: top level - src - sasl.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 285 0
Test Date: 2024-08-20 10:03:45 Functions: 0.0 % 8 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            0 :     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 2.0-1