LCOV - code coverage report
Current view: top level - src - crypto.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 132 193 68.4 %
Date: 2023-08-10 00:00:00 Functions: 7 17 41.2 %

          Line data    Source code
       1             : /* crypto.c
       2             :  * strophe XMPP client library -- public interface for digests, encodings
       3             :  *
       4             :  * Copyright (C) 2016 Dmitry Podgorny <pasis.ua@gmail.com>
       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             :  *  Public interface for digests and encodings used in XEPs.
      14             :  */
      15             : 
      16             : /** @defgroup Digests Message digests
      17             :  */
      18             : 
      19             : /** @defgroup Encodings Encodings
      20             :  */
      21             : 
      22             : #include <assert.h>
      23             : #include <string.h> /* memset, memcpy */
      24             : 
      25             : #include "common.h"  /* strophe_alloc */
      26             : #include "ostypes.h" /* uint8_t, size_t */
      27             : #include "sha1.h"
      28             : #include "snprintf.h" /* xmpp_snprintf */
      29             : #include "strophe.h"  /* xmpp_ctx_t, strophe_free */
      30             : 
      31             : struct _xmpp_sha1_t {
      32             :     xmpp_ctx_t *xmpp_ctx;
      33             :     SHA1_CTX ctx;
      34             :     uint8_t digest[SHA1_DIGEST_SIZE];
      35             : };
      36             : 
      37           0 : static char *digest_to_string(const uint8_t *digest, char *s, size_t len)
      38             : {
      39           0 :     int i;
      40             : 
      41           0 :     if (len < SHA1_DIGEST_SIZE * 2 + 1)
      42             :         return NULL;
      43             : 
      44           0 :     for (i = 0; i < SHA1_DIGEST_SIZE; ++i)
      45           0 :         strophe_snprintf(s + i * 2, 3, "%02x", digest[i]);
      46             : 
      47             :     return s;
      48             : }
      49             : 
      50           0 : static char *digest_to_string_alloc(xmpp_ctx_t *ctx, const uint8_t *digest)
      51             : {
      52           0 :     char *s;
      53           0 :     size_t slen;
      54             : 
      55           0 :     slen = SHA1_DIGEST_SIZE * 2 + 1;
      56           0 :     s = strophe_alloc(ctx, slen);
      57           0 :     if (s) {
      58           0 :         s = digest_to_string(digest, s, slen);
      59           0 :         assert(s != NULL);
      60             :     }
      61           0 :     return s;
      62             : }
      63             : 
      64             : /** Compute SHA1 message digest.
      65             :  *  Returns an allocated string which represents SHA1 message digest in
      66             :  *  hexadecimal notation. The string must be freed with xmpp_free().
      67             :  *
      68             :  *  @param ctx a Strophe context object
      69             :  *  @param data buffer for digest computation
      70             :  *  @param len size of the data buffer
      71             :  *
      72             :  *  @return an allocated string or NULL on allocation error
      73             :  *
      74             :  *  @ingroup Digests
      75             :  */
      76           0 : char *xmpp_sha1(xmpp_ctx_t *ctx, const unsigned char *data, size_t len)
      77             : {
      78           0 :     uint8_t digest[SHA1_DIGEST_SIZE];
      79             : 
      80           0 :     crypto_SHA1((const uint8_t *)data, len, digest);
      81           0 :     return digest_to_string_alloc(ctx, digest);
      82             : }
      83             : 
      84             : /** Compute SHA1 message digest.
      85             :  *  Stores digest in user's buffer which must be at least XMPP_SHA1_DIGEST_SIZE
      86             :  *  bytes long.
      87             :  *
      88             :  *  @param data buffer for digest computation
      89             :  *  @param len size of the data buffer
      90             :  *  @param digest output buffer of XMPP_SHA1_DIGEST_SIZE bytes
      91             :  *
      92             :  *  @ingroup Digests
      93             :  */
      94           8 : void xmpp_sha1_digest(const unsigned char *data,
      95             :                       size_t len,
      96             :                       unsigned char *digest)
      97             : {
      98           8 :     crypto_SHA1((const uint8_t *)data, len, digest);
      99           8 : }
     100             : 
     101             : /** Create new SHA1 object.
     102             :  *  SHA1 object is used to compute SHA1 digest of a buffer that is split
     103             :  *  in multiple chunks or provided in stream mode. A single buffer can be
     104             :  *  processed by short functions xmpp_sha1() and xmpp_sha1_digest().
     105             :  *  Follow the next use-case for xmpp_sha1_t object:
     106             :  *  @code
     107             :  *      xmpp_sha1_t *sha1 = xmpp_sha1_new(ctx);
     108             :  *      // Repeat update for all chunks of data
     109             :  *      xmpp_sha1_update(sha1, data, len);
     110             :  *      xmpp_sha1_final(sha1);
     111             :  *      char *digest = xmpp_sha1_to_string_alloc(sha1);
     112             :  *      xmpp_sha1_free(sha1);
     113             :  *  @endcode
     114             :  *
     115             :  *  @param ctx a Strophe context object
     116             :  *
     117             :  *  @return new SHA1 object
     118             :  *
     119             :  *  @ingroup Digests
     120             :  */
     121           0 : xmpp_sha1_t *xmpp_sha1_new(xmpp_ctx_t *ctx)
     122             : {
     123           0 :     xmpp_sha1_t *sha1;
     124             : 
     125           0 :     sha1 = strophe_alloc(ctx, sizeof(*sha1));
     126           0 :     if (sha1) {
     127           0 :         memset(sha1, 0, sizeof(*sha1));
     128           0 :         crypto_SHA1_Init(&sha1->ctx);
     129           0 :         sha1->xmpp_ctx = ctx;
     130             :     }
     131           0 :     return sha1;
     132             : }
     133             : 
     134             : /** Destroy SHA1 object.
     135             :  *
     136             :  *  @param sha1 a SHA1 object
     137             :  *
     138             :  *  @ingroup Digests
     139             :  */
     140           0 : void xmpp_sha1_free(xmpp_sha1_t *sha1)
     141             : {
     142           0 :     strophe_free(sha1->xmpp_ctx, sha1);
     143           0 : }
     144             : 
     145             : /** Update SHA1 context with the next portion of data.
     146             :  *  Can be called repeatedly.
     147             :  *
     148             :  *  @param sha1 a SHA1 object
     149             :  *  @param data pointer to a buffer to be hashed
     150             :  *  @param len size of the data buffer
     151             :  *
     152             :  *  @ingroup Digests
     153             :  */
     154           0 : void xmpp_sha1_update(xmpp_sha1_t *sha1, const unsigned char *data, size_t len)
     155             : {
     156           0 :     crypto_SHA1_Update(&sha1->ctx, data, len);
     157           0 : }
     158             : 
     159             : /** Finish SHA1 computation.
     160             :  *  Don't call xmpp_sha1_update() after this function. Retrieve resulting
     161             :  *  message digest with xmpp_sha1_to_string() or xmpp_sha1_to_digest().
     162             :  *
     163             :  *  @param sha1 a SHA1 object
     164             :  *
     165             :  *  @ingroup Digests
     166             :  */
     167           0 : void xmpp_sha1_final(xmpp_sha1_t *sha1)
     168             : {
     169           0 :     crypto_SHA1_Final(&sha1->ctx, sha1->digest);
     170           0 : }
     171             : 
     172             : /** Return message digest rendered as a string.
     173             :  *  Stores the string to a user's buffer and returns the buffer. Call this
     174             :  *  function after xmpp_sha1_final().
     175             :  *
     176             :  *  @param sha1 a SHA1 object
     177             :  *  @param s output string
     178             :  *  @param slen size reserved for the string including '\0'
     179             :  *
     180             :  *  @return pointer s or NULL if resulting string is bigger than slen bytes
     181             :  *
     182             :  *  @ingroup Digests
     183             :  */
     184           0 : char *xmpp_sha1_to_string(xmpp_sha1_t *sha1, char *s, size_t slen)
     185             : {
     186           0 :     return digest_to_string(sha1->digest, s, slen);
     187             : }
     188             : 
     189             : /** Return message digest rendered as a string.
     190             :  *  Returns an allocated string. Free the string by calling xmpp_free() using
     191             :  *  the Strophe context which is passed to xmpp_sha1_new(). Call this function
     192             :  *  after xmpp_sha1_final().
     193             :  *
     194             :  *  @param sha1 a SHA1 object
     195             :  *
     196             :  *  @return an allocated string
     197             :  *
     198             :  *  @ingroup Digests
     199             :  */
     200           0 : char *xmpp_sha1_to_string_alloc(xmpp_sha1_t *sha1)
     201             : {
     202           0 :     return digest_to_string_alloc(sha1->xmpp_ctx, sha1->digest);
     203             : }
     204             : 
     205             : /** Stores message digest to a user's buffer.
     206             :  *
     207             :  *  @param sha1 a SHA1 object
     208             :  *  @param digest output buffer of XMPP_SHA1_DIGEST_SIZE bytes
     209             :  *
     210             :  *  @ingroup Digests
     211             :  */
     212           0 : void xmpp_sha1_to_digest(xmpp_sha1_t *sha1, unsigned char *digest)
     213             : {
     214           0 :     assert(SHA1_DIGEST_SIZE == XMPP_SHA1_DIGEST_SIZE);
     215           0 :     memcpy(digest, sha1->digest, SHA1_DIGEST_SIZE);
     216           0 : }
     217             : 
     218             : /* Base64 encoding routines. Implemented according to RFC 3548. */
     219             : 
     220             : /* map of all byte values to the base64 values, or to
     221             :    '65' which indicates an invalid character. '=' is '64' */
     222             : static const unsigned char _base64_invcharmap[256] = {
     223             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     224             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     225             :     65, 65, 65, 65, 65, 62, 65, 65, 65, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
     226             :     61, 65, 65, 65, 64, 65, 65, 65, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
     227             :     11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 65, 65, 65, 65,
     228             :     65, 65, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
     229             :     43, 44, 45, 46, 47, 48, 49, 50, 51, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     230             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     231             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     232             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     233             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     234             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     235             :     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
     236             :     65, 65, 65, 65, 65, 65, 65, 65, 65};
     237             : 
     238             : /* map of all 6-bit values to their corresponding byte
     239             :    in the base64 alphabet. Padding char is the value '64' */
     240             : static const char _base64_charmap[65] = {
     241             :     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
     242             :     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
     243             :     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
     244             :     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
     245             :     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='};
     246             : 
     247          13 : static size_t base64_encoded_len(size_t len)
     248             : {
     249             :     /* encoded steam is 4 bytes for every three, rounded up */
     250          13 :     return ((len + 2) / 3) << 2;
     251             : }
     252             : 
     253             : static char *
     254          13 : base64_encode(xmpp_ctx_t *ctx, const unsigned char *buffer, size_t len)
     255             : {
     256          13 :     size_t clen;
     257          13 :     char *cbuf, *c;
     258          13 :     uint32_t word, hextet;
     259          13 :     size_t i;
     260             : 
     261          13 :     clen = base64_encoded_len(len);
     262          13 :     cbuf = strophe_alloc(ctx, clen + 1);
     263          13 :     if (cbuf != NULL) {
     264             :         c = cbuf;
     265             :         /* loop over data, turning every 3 bytes into 4 characters */
     266         212 :         for (i = 0; i + 2 < len; i += 3) {
     267         199 :             word = buffer[i] << 16 | buffer[i + 1] << 8 | buffer[i + 2];
     268         199 :             hextet = (word & 0x00FC0000) >> 18;
     269         199 :             *c++ = _base64_charmap[hextet];
     270         199 :             hextet = (word & 0x0003F000) >> 12;
     271         199 :             *c++ = _base64_charmap[hextet];
     272         199 :             hextet = (word & 0x00000FC0) >> 6;
     273         199 :             *c++ = _base64_charmap[hextet];
     274         199 :             hextet = (word & 0x000003F);
     275         199 :             *c++ = _base64_charmap[hextet];
     276             :         }
     277             :         /* zero, one or two bytes left */
     278          13 :         switch (len - i) {
     279             :         case 0:
     280             :             break;
     281           4 :         case 1:
     282           4 :             hextet = (buffer[len - 1] & 0xFC) >> 2;
     283           4 :             *c++ = _base64_charmap[hextet];
     284           4 :             hextet = (buffer[len - 1] & 0x03) << 4;
     285           4 :             *c++ = _base64_charmap[hextet];
     286           4 :             *c++ = _base64_charmap[64]; /* pad */
     287           4 :             *c++ = _base64_charmap[64]; /* pad */
     288           4 :             break;
     289           3 :         case 2:
     290           3 :             hextet = (buffer[len - 2] & 0xFC) >> 2;
     291           3 :             *c++ = _base64_charmap[hextet];
     292           3 :             hextet = ((buffer[len - 2] & 0x03) << 4) |
     293           3 :                      ((buffer[len - 1] & 0xF0) >> 4);
     294           3 :             *c++ = _base64_charmap[hextet];
     295           3 :             hextet = (buffer[len - 1] & 0x0F) << 2;
     296           3 :             *c++ = _base64_charmap[hextet];
     297           3 :             *c++ = _base64_charmap[64]; /* pad */
     298           3 :             break;
     299             :         }
     300             :         /* add a terminal null */
     301          13 :         *c = '\0';
     302             :     }
     303          13 :     return cbuf;
     304             : }
     305             : 
     306          12 : static size_t base64_decoded_len(const char *buffer, size_t len)
     307             : {
     308          12 :     size_t nudge = 0;
     309          12 :     unsigned char c;
     310          12 :     size_t i;
     311             : 
     312          12 :     if (len < 4)
     313             :         return 0;
     314             : 
     315             :     /* count the padding characters for the remainder */
     316          23 :     for (i = len; i > 0; --i) {
     317          23 :         c = _base64_invcharmap[(unsigned char)buffer[i - 1]];
     318          23 :         if (c < 64)
     319             :             break;
     320          11 :         if (c == 64)
     321          11 :             ++nudge;
     322          11 :         if (c > 64)
     323             :             return 0;
     324             :     }
     325          12 :     if (nudge > 2)
     326             :         return 0;
     327             : 
     328             :     /* decoded steam is 3 bytes for every four */
     329          12 :     return 3 * (len >> 2) - nudge;
     330             : }
     331             : 
     332          12 : static void base64_decode(xmpp_ctx_t *ctx,
     333             :                           const char *buffer,
     334             :                           size_t len,
     335             :                           unsigned char **out,
     336             :                           size_t *outlen)
     337             : {
     338          12 :     size_t dlen;
     339          12 :     unsigned char *dbuf, *d;
     340          12 :     uint32_t word, hextet = 0;
     341          12 :     size_t i;
     342             : 
     343             :     /* len must be a multiple of 4 */
     344          12 :     if (len & 0x03)
     345           0 :         goto _base64_error;
     346             : 
     347          12 :     dlen = base64_decoded_len(buffer, len);
     348          12 :     if (dlen == 0)
     349           0 :         goto _base64_error;
     350             : 
     351          12 :     dbuf = strophe_alloc(ctx, dlen + 1);
     352          12 :     if (dbuf != NULL) {
     353             :         d = dbuf;
     354             :         /* loop over each set of 4 characters, decoding 3 bytes */
     355         211 :         for (i = 0; i + 3 < len; i += 4) {
     356         206 :             hextet = _base64_invcharmap[(unsigned char)buffer[i]];
     357         206 :             if (hextet & 0xC0)
     358             :                 break;
     359         206 :             word = hextet << 18;
     360         206 :             hextet = _base64_invcharmap[(unsigned char)buffer[i + 1]];
     361         206 :             if (hextet & 0xC0)
     362             :                 break;
     363         206 :             word |= hextet << 12;
     364         206 :             hextet = _base64_invcharmap[(unsigned char)buffer[i + 2]];
     365         206 :             if (hextet & 0xC0)
     366             :                 break;
     367         202 :             word |= hextet << 6;
     368         202 :             hextet = _base64_invcharmap[(unsigned char)buffer[i + 3]];
     369         202 :             if (hextet & 0xC0)
     370             :                 break;
     371         199 :             word |= hextet;
     372         199 :             *d++ = (word & 0x00FF0000) >> 16;
     373         199 :             *d++ = (word & 0x0000FF00) >> 8;
     374         199 :             *d++ = (word & 0x000000FF);
     375             :         }
     376          12 :         if (hextet > 64)
     377           0 :             goto _base64_decode_error;
     378             :         /* handle the remainder */
     379          12 :         switch (dlen % 3) {
     380             :         case 0:
     381             :             /* nothing to do */
     382             :             break;
     383           4 :         case 1:
     384             :             /* redo the last quartet, checking for correctness */
     385           4 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 4]];
     386           4 :             if (hextet & 0xC0)
     387           0 :                 goto _base64_decode_error;
     388           4 :             word = hextet << 2;
     389           4 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 3]];
     390           4 :             if (hextet & 0xC0)
     391           0 :                 goto _base64_decode_error;
     392           4 :             word |= hextet >> 4;
     393           4 :             *d++ = word & 0xFF;
     394           4 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 2]];
     395           4 :             if (hextet != 64)
     396           0 :                 goto _base64_decode_error;
     397           4 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 1]];
     398           4 :             if (hextet != 64)
     399           0 :                 goto _base64_decode_error;
     400             :             break;
     401           3 :         case 2:
     402             :             /* redo the last quartet, checking for correctness */
     403           3 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 4]];
     404           3 :             if (hextet & 0xC0)
     405           0 :                 goto _base64_decode_error;
     406           3 :             word = hextet << 10;
     407           3 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 3]];
     408           3 :             if (hextet & 0xC0)
     409           0 :                 goto _base64_decode_error;
     410           3 :             word |= hextet << 4;
     411           3 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 2]];
     412           3 :             if (hextet & 0xC0)
     413           0 :                 goto _base64_decode_error;
     414           3 :             word |= hextet >> 2;
     415           3 :             *d++ = (word & 0xFF00) >> 8;
     416           3 :             *d++ = (word & 0x00FF);
     417           3 :             hextet = _base64_invcharmap[(unsigned char)buffer[len - 1]];
     418           3 :             if (hextet != 64)
     419           0 :                 goto _base64_decode_error;
     420             :             break;
     421             :         }
     422          12 :         *d = '\0';
     423             :     }
     424          12 :     *out = dbuf;
     425          12 :     *outlen = dbuf == NULL ? 0 : dlen;
     426          12 :     return;
     427             : 
     428           0 : _base64_decode_error:
     429             :     /* invalid character; abort decoding! */
     430           0 :     strophe_free(ctx, dbuf);
     431           0 : _base64_error:
     432           0 :     *out = NULL;
     433           0 :     *outlen = 0;
     434             : }
     435             : 
     436             : /** Base64 encoding routine.
     437             :  *  Returns an allocated string which must be freed with xmpp_free().
     438             :  *
     439             :  *  @param ctx a Strophe context
     440             :  *  @param data buffer to encode
     441             :  *  @param len size of the data buffer
     442             :  *
     443             :  *  @return an allocated null-terminated string or NULL on error
     444             :  *
     445             :  *  @ingroup Encodings
     446             :  */
     447          13 : char *xmpp_base64_encode(xmpp_ctx_t *ctx, const unsigned char *data, size_t len)
     448             : {
     449          13 :     return base64_encode(ctx, data, len);
     450             : }
     451             : 
     452             : /** Base64 decoding routine.
     453             :  *  Returns an allocated string which must be freed with xmpp_free(). User
     454             :  *  calls this function when the result must be a string. When decoded buffer
     455             :  *  contains '\0' NULL is returned.
     456             :  *
     457             :  *  @param ctx a Strophe context
     458             :  *  @param base64 encoded buffer
     459             :  *  @param len size of the buffer
     460             :  *
     461             :  *  @return an allocated null-terminated string or NULL on error
     462             :  *
     463             :  *  @ingroup Encodings
     464             :  */
     465          12 : char *xmpp_base64_decode_str(xmpp_ctx_t *ctx, const char *base64, size_t len)
     466             : {
     467          12 :     unsigned char *buf = NULL;
     468          12 :     size_t buflen;
     469             : 
     470          12 :     if (len == 0) {
     471             :         /* handle empty string */
     472           1 :         buf = strophe_alloc(ctx, 1);
     473           1 :         if (buf)
     474           1 :             buf[0] = '\0';
     475           1 :         buflen = 0;
     476             :     } else {
     477          11 :         base64_decode(ctx, base64, len, &buf, &buflen);
     478             :     }
     479          12 :     if (buf) {
     480          12 :         if (buflen != strlen((char *)buf)) {
     481           0 :             strophe_free(ctx, buf);
     482           0 :             buf = NULL;
     483             :         }
     484             :     }
     485          12 :     return (char *)buf;
     486             : }
     487             : 
     488             : /** Base64 decoding routine.
     489             :  *  Returns an allocated buffer which must be freed with xmpp_free().
     490             :  *
     491             :  *  @param ctx a Strophe context
     492             :  *  @param base64 encoded buffer
     493             :  *  @param len size of the encoded buffer
     494             :  *  @param out allocated buffer is stored here
     495             :  *  @param outlen size of the allocated buffer
     496             :  *
     497             :  *  @note on an error the `*out` will be NULL
     498             :  *
     499             :  *  @ingroup Encodings
     500             :  */
     501           1 : void xmpp_base64_decode_bin(xmpp_ctx_t *ctx,
     502             :                             const char *base64,
     503             :                             size_t len,
     504             :                             unsigned char **out,
     505             :                             size_t *outlen)
     506             : {
     507           1 :     base64_decode(ctx, base64, len, out, outlen);
     508           1 : }

Generated by: LCOV version 1.14