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 : }
|