/* SPDX-License-Identifier: MIT OR GPL-3.0-only */ /* crypto.c * strophe XMPP client library -- public interface for digests, encodings * * Copyright (C) 2016 Dmitry Podgorny * * This software is provided AS-IS with no warranty, either express * or implied. * * This program is dual licensed under the MIT or GPLv3 licenses. */ /** @file * Public interface for digests and encodings used in XEPs. */ /** @defgroup Digests Message digests */ /** @defgroup Encodings Encodings */ #include #include /* memset, memcpy */ #include "common.h" /* strophe_alloc */ #include "ostypes.h" /* uint8_t, size_t */ #include "sha1.h" #include "snprintf.h" /* xmpp_snprintf */ #include "strophe.h" /* xmpp_ctx_t, strophe_free */ struct _xmpp_sha1_t { xmpp_ctx_t *xmpp_ctx; SHA1_CTX ctx; uint8_t digest[SHA1_DIGEST_SIZE]; }; static char *digest_to_string(const uint8_t *digest, char *s, size_t len) { int i; if (len < SHA1_DIGEST_SIZE * 2 + 1) return NULL; for (i = 0; i < SHA1_DIGEST_SIZE; ++i) strophe_snprintf(s + i * 2, 3, "%02x", digest[i]); return s; } static char *digest_to_string_alloc(xmpp_ctx_t *ctx, const uint8_t *digest) { char *s; size_t slen; slen = SHA1_DIGEST_SIZE * 2 + 1; s = strophe_alloc(ctx, slen); if (s) { s = digest_to_string(digest, s, slen); assert(s != NULL); } return s; } /** Compute SHA1 message digest. * Returns an allocated string which represents SHA1 message digest in * hexadecimal notation. The string must be freed with xmpp_free(). * * @param ctx a Strophe context object * @param data buffer for digest computation * @param len size of the data buffer * * @return an allocated string or NULL on allocation error * * @ingroup Digests */ char *xmpp_sha1(xmpp_ctx_t *ctx, const unsigned char *data, size_t len) { uint8_t digest[SHA1_DIGEST_SIZE]; crypto_SHA1((const uint8_t *)data, len, digest); return digest_to_string_alloc(ctx, digest); } /** Compute SHA1 message digest. * Stores digest in user's buffer which must be at least XMPP_SHA1_DIGEST_SIZE * bytes long. * * @param data buffer for digest computation * @param len size of the data buffer * @param digest output buffer of XMPP_SHA1_DIGEST_SIZE bytes * * @ingroup Digests */ void xmpp_sha1_digest(const unsigned char *data, size_t len, unsigned char *digest) { crypto_SHA1((const uint8_t *)data, len, digest); } /** Create new SHA1 object. * SHA1 object is used to compute SHA1 digest of a buffer that is split * in multiple chunks or provided in stream mode. A single buffer can be * processed by short functions xmpp_sha1() and xmpp_sha1_digest(). * Follow the next use-case for xmpp_sha1_t object: * @code * xmpp_sha1_t *sha1 = xmpp_sha1_new(ctx); * // Repeat update for all chunks of data * xmpp_sha1_update(sha1, data, len); * xmpp_sha1_final(sha1); * char *digest = xmpp_sha1_to_string_alloc(sha1); * xmpp_sha1_free(sha1); * @endcode * * @param ctx a Strophe context object * * @return new SHA1 object * * @ingroup Digests */ xmpp_sha1_t *xmpp_sha1_new(xmpp_ctx_t *ctx) { xmpp_sha1_t *sha1; sha1 = strophe_alloc(ctx, sizeof(*sha1)); if (sha1) { memset(sha1, 0, sizeof(*sha1)); crypto_SHA1_Init(&sha1->ctx); sha1->xmpp_ctx = ctx; } return sha1; } /** Destroy SHA1 object. * * @param sha1 a SHA1 object * * @ingroup Digests */ void xmpp_sha1_free(xmpp_sha1_t *sha1) { strophe_free(sha1->xmpp_ctx, sha1); } /** Update SHA1 context with the next portion of data. * Can be called repeatedly. * * @param sha1 a SHA1 object * @param data pointer to a buffer to be hashed * @param len size of the data buffer * * @ingroup Digests */ void xmpp_sha1_update(xmpp_sha1_t *sha1, const unsigned char *data, size_t len) { crypto_SHA1_Update(&sha1->ctx, data, len); } /** Finish SHA1 computation. * Don't call xmpp_sha1_update() after this function. Retrieve resulting * message digest with xmpp_sha1_to_string() or xmpp_sha1_to_digest(). * * @param sha1 a SHA1 object * * @ingroup Digests */ void xmpp_sha1_final(xmpp_sha1_t *sha1) { crypto_SHA1_Final(&sha1->ctx, sha1->digest); } /** Return message digest rendered as a string. * Stores the string to a user's buffer and returns the buffer. Call this * function after xmpp_sha1_final(). * * @param sha1 a SHA1 object * @param s output string * @param slen size reserved for the string including '\0' * * @return pointer s or NULL if resulting string is bigger than slen bytes * * @ingroup Digests */ char *xmpp_sha1_to_string(xmpp_sha1_t *sha1, char *s, size_t slen) { return digest_to_string(sha1->digest, s, slen); } /** Return message digest rendered as a string. * Returns an allocated string. Free the string by calling xmpp_free() using * the Strophe context which is passed to xmpp_sha1_new(). Call this function * after xmpp_sha1_final(). * * @param sha1 a SHA1 object * * @return an allocated string * * @ingroup Digests */ char *xmpp_sha1_to_string_alloc(xmpp_sha1_t *sha1) { return digest_to_string_alloc(sha1->xmpp_ctx, sha1->digest); } /** Stores message digest to a user's buffer. * * @param sha1 a SHA1 object * @param digest output buffer of XMPP_SHA1_DIGEST_SIZE bytes * * @ingroup Digests */ void xmpp_sha1_to_digest(xmpp_sha1_t *sha1, unsigned char *digest) { assert(SHA1_DIGEST_SIZE == XMPP_SHA1_DIGEST_SIZE); memcpy(digest, sha1->digest, SHA1_DIGEST_SIZE); } /* Base64 encoding routines. Implemented according to RFC 3548. */ /* map of all byte values to the base64 values, or to '65' which indicates an invalid character. '=' is '64' */ static const unsigned char _base64_invcharmap[256] = { 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 62, 65, 65, 65, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 65, 65, 65, 64, 65, 65, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 65, 65, 65, 65, 65, 65, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65}; /* map of all 6-bit values to their corresponding byte in the base64 alphabet. Padding char is the value '64' */ static const char _base64_charmap[65] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='}; static size_t base64_encoded_len(size_t len) { /* encoded steam is 4 bytes for every three, rounded up */ return ((len + 2) / 3) << 2; } static char * base64_encode(xmpp_ctx_t *ctx, const unsigned char *buffer, size_t len) { size_t clen; char *cbuf, *c; uint32_t word, hextet; size_t i; clen = base64_encoded_len(len); cbuf = strophe_alloc(ctx, clen + 1); if (cbuf != NULL) { c = cbuf; /* loop over data, turning every 3 bytes into 4 characters */ for (i = 0; i + 2 < len; i += 3) { word = buffer[i] << 16 | buffer[i + 1] << 8 | buffer[i + 2]; hextet = (word & 0x00FC0000) >> 18; *c++ = _base64_charmap[hextet]; hextet = (word & 0x0003F000) >> 12; *c++ = _base64_charmap[hextet]; hextet = (word & 0x00000FC0) >> 6; *c++ = _base64_charmap[hextet]; hextet = (word & 0x000003F); *c++ = _base64_charmap[hextet]; } /* zero, one or two bytes left */ switch (len - i) { case 0: break; case 1: hextet = (buffer[len - 1] & 0xFC) >> 2; *c++ = _base64_charmap[hextet]; hextet = (buffer[len - 1] & 0x03) << 4; *c++ = _base64_charmap[hextet]; *c++ = _base64_charmap[64]; /* pad */ *c++ = _base64_charmap[64]; /* pad */ break; case 2: hextet = (buffer[len - 2] & 0xFC) >> 2; *c++ = _base64_charmap[hextet]; hextet = ((buffer[len - 2] & 0x03) << 4) | ((buffer[len - 1] & 0xF0) >> 4); *c++ = _base64_charmap[hextet]; hextet = (buffer[len - 1] & 0x0F) << 2; *c++ = _base64_charmap[hextet]; *c++ = _base64_charmap[64]; /* pad */ break; } /* add a terminal null */ *c = '\0'; } return cbuf; } static size_t base64_decoded_len(const char *buffer, size_t len) { size_t nudge = 0; unsigned char c; size_t i; if (len < 4) return 0; /* count the padding characters for the remainder */ for (i = len; i > 0; --i) { c = _base64_invcharmap[(unsigned char)buffer[i - 1]]; if (c < 64) break; if (c == 64) ++nudge; if (c > 64) return 0; } if (nudge > 2) return 0; /* decoded steam is 3 bytes for every four */ return 3 * (len >> 2) - nudge; } static void base64_decode(xmpp_ctx_t *ctx, const char *buffer, size_t len, unsigned char **out, size_t *outlen) { size_t dlen; unsigned char *dbuf, *d; uint32_t word, hextet = 0; size_t i; /* len must be a multiple of 4 */ if (len & 0x03) goto _base64_error; dlen = base64_decoded_len(buffer, len); if (dlen == 0) goto _base64_error; dbuf = strophe_alloc(ctx, dlen + 1); if (dbuf != NULL) { d = dbuf; /* loop over each set of 4 characters, decoding 3 bytes */ for (i = 0; i + 3 < len; i += 4) { hextet = _base64_invcharmap[(unsigned char)buffer[i]]; if (hextet & 0xC0) break; word = hextet << 18; hextet = _base64_invcharmap[(unsigned char)buffer[i + 1]]; if (hextet & 0xC0) break; word |= hextet << 12; hextet = _base64_invcharmap[(unsigned char)buffer[i + 2]]; if (hextet & 0xC0) break; word |= hextet << 6; hextet = _base64_invcharmap[(unsigned char)buffer[i + 3]]; if (hextet & 0xC0) break; word |= hextet; *d++ = (word & 0x00FF0000) >> 16; *d++ = (word & 0x0000FF00) >> 8; *d++ = (word & 0x000000FF); } if (hextet > 64) goto _base64_decode_error; /* handle the remainder */ switch (dlen % 3) { case 0: /* nothing to do */ break; case 1: /* redo the last quartet, checking for correctness */ hextet = _base64_invcharmap[(unsigned char)buffer[len - 4]]; if (hextet & 0xC0) goto _base64_decode_error; word = hextet << 2; hextet = _base64_invcharmap[(unsigned char)buffer[len - 3]]; if (hextet & 0xC0) goto _base64_decode_error; word |= hextet >> 4; *d++ = word & 0xFF; hextet = _base64_invcharmap[(unsigned char)buffer[len - 2]]; if (hextet != 64) goto _base64_decode_error; hextet = _base64_invcharmap[(unsigned char)buffer[len - 1]]; if (hextet != 64) goto _base64_decode_error; break; case 2: /* redo the last quartet, checking for correctness */ hextet = _base64_invcharmap[(unsigned char)buffer[len - 4]]; if (hextet & 0xC0) goto _base64_decode_error; word = hextet << 10; hextet = _base64_invcharmap[(unsigned char)buffer[len - 3]]; if (hextet & 0xC0) goto _base64_decode_error; word |= hextet << 4; hextet = _base64_invcharmap[(unsigned char)buffer[len - 2]]; if (hextet & 0xC0) goto _base64_decode_error; word |= hextet >> 2; *d++ = (word & 0xFF00) >> 8; *d++ = (word & 0x00FF); hextet = _base64_invcharmap[(unsigned char)buffer[len - 1]]; if (hextet != 64) goto _base64_decode_error; break; } *d = '\0'; } *out = dbuf; *outlen = dbuf == NULL ? 0 : dlen; return; _base64_decode_error: /* invalid character; abort decoding! */ strophe_free(ctx, dbuf); _base64_error: *out = NULL; *outlen = 0; } /** Base64 encoding routine. * Returns an allocated string which must be freed with xmpp_free(). * * @param ctx a Strophe context * @param data buffer to encode * @param len size of the data buffer * * @return an allocated null-terminated string or NULL on error * * @ingroup Encodings */ char *xmpp_base64_encode(xmpp_ctx_t *ctx, const unsigned char *data, size_t len) { return base64_encode(ctx, data, len); } /** Base64 decoding routine. * Returns an allocated string which must be freed with xmpp_free(). User * calls this function when the result must be a string. When decoded buffer * contains '\0' NULL is returned. * * @param ctx a Strophe context * @param base64 encoded buffer * @param len size of the buffer * * @return an allocated null-terminated string or NULL on error * * @ingroup Encodings */ char *xmpp_base64_decode_str(xmpp_ctx_t *ctx, const char *base64, size_t len) { unsigned char *buf = NULL; size_t buflen; if (len == 0) { /* handle empty string */ buf = strophe_alloc(ctx, 1); if (buf) buf[0] = '\0'; buflen = 0; } else { base64_decode(ctx, base64, len, &buf, &buflen); } if (buf) { if (buflen != strlen((char *)buf)) { strophe_free(ctx, buf); buf = NULL; } } return (char *)buf; } /** Base64 decoding routine. * Returns an allocated buffer which must be freed with xmpp_free(). * * @param ctx a Strophe context * @param base64 encoded buffer * @param len size of the encoded buffer * @param out allocated buffer is stored here * @param outlen size of the allocated buffer * * @note on an error the `*out` will be NULL * * @ingroup Encodings */ void xmpp_base64_decode_bin(xmpp_ctx_t *ctx, const char *base64, size_t len, unsigned char **out, size_t *outlen) { base64_decode(ctx, base64, len, out, outlen); }