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

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

Generated by: LCOV version 2.0-1