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

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

Generated by: LCOV version 1.14