/** * Copyright (C) 2022 Matt Burt, all xrdp contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /** * @file common/base64.c * @brief Base64 encoder / decoder */ #if defined(HAVE_CONFIG_H) #include #endif #include "string_calls.h" #include "base64.h" /* * Values for invalid and padding characters, used in the charmap * for converting base64 to binary * * These values are specially chosen to make it easy to detect padding or * invalid characters by or-ing together the values looked up in * a base64 quantum */ #define E_INVALID 0x40 #define E_PAD 0x80 /* Determine the character set on this platform */ #if ('a' == 0x61 && 'z' == 0x7a ) && \ ('A' == 0x41 && 'Z' == 0x5a ) && \ ('0' == 0x30 && '9' == 0x39 ) # define PLATFORM_IS_ASCII 1 #else # error "Unrecognised character set on this platform" #endif /* character set check */ /* * Define a table to map the base64 character values to bit values. */ #ifdef PLATFORM_IS_ASCII #define CHARMAP_BASE 0x28 #define E_IV E_INVALID /* For table alignment */ const unsigned char charmap[] = { /* 0x28 */ E_IV, E_IV, E_IV, 0x3e, E_IV, E_IV, E_IV, 0x3f, /* 0x30 */ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, /* 0x38 */ 0x3c, 0x3d, E_IV, E_IV, E_IV, E_PAD, E_IV, E_IV, /* 0x40 */ E_IV, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 0x48 */ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, /* 0x50 */ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 0x58 */ 0x17, 0x18, 0x19, E_IV, E_IV, E_IV, E_IV, E_IV, /* 0x60 */ E_IV, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, /* 0x68 */ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, /* 0x70 */ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, /* 0x78 */ 0x31, 0x32, 0x33 }; #undef E_IV #endif /* PLATFORM_IS_ASCII */ /** * Lookup a value in the charmap * * @param x - byte to lookup. Only referenced once so can safely have * side effects. * @param dest - destination to assign result to. */ #define CM_LOOKUP(x,dest) \ { \ unsigned int t = (unsigned int)(x) - CHARMAP_BASE;\ dest = (t < sizeof(charmap)) ? charmap[t] : E_INVALID; \ } /*****************************************************************************/ int base64_decode(const char *src, char *dst, size_t dst_len, size_t *actual_len) { *actual_len = 0; size_t src_len; size_t src_i = 0; size_t dst_i = 0; unsigned int a; /* Four characters of base64 quantum */ unsigned int b; unsigned int c; unsigned int d; unsigned int v; #define OUTPUT_CHAR(x) \ { \ if (dst_i < dst_len) \ { \ dst[dst_i] = (x);\ } \ ++dst_i; \ } src_len = g_strlen(src); while (src_i < src_len) { if ((src_len - src_i) >= 4) { /* Usual case - full quantum */ CM_LOOKUP(src[src_i++], a); CM_LOOKUP(src[src_i++], b); CM_LOOKUP(src[src_i++], c); CM_LOOKUP(src[src_i++], d); } else { /* Add padding on the end to make up the full quantum */ CM_LOOKUP(src[src_i++], a); b = E_PAD; c = E_PAD; d = E_PAD; if ((src_len - src_i) > 0) { CM_LOOKUP(src[src_i++], b); } if ((src_len - src_i) > 0) { CM_LOOKUP(src[src_i++], c); } } /* * Bitwise-or the translated quantum values together, so that * any invalid or padding characters can be detected with a * single test */ v = a | b | c | d; if ((v & E_INVALID) != 0) { return -1; /* At least one invalid character */ } if ((v & E_PAD) == 0) { /* No padding - a full quantum */ v = (a << 18) | (b << 12) | (c << 6) | d; OUTPUT_CHAR(v >> 16); OUTPUT_CHAR((v >> 8) & 0xff); OUTPUT_CHAR(v & 0xff); } else if (((a | b | c) & E_PAD) == 0) { /* No padding in the first 3 chars, so the padding must * be at the end */ v = (a << 10) | (b << 4) | (c >> 2); OUTPUT_CHAR(v >> 8); OUTPUT_CHAR(v & 0xff); } else if (((a | b) & E_PAD) == 0 && c == d) { /* No padding in first two chars, so if the last two chars are * equal, they must both be padding */ v = (a << 2) | (b >> 4); OUTPUT_CHAR(v); } else { /* Illegal padding */ return -1; } } *actual_len = dst_i; return 0; #undef OUTPUT_CHAR } /*****************************************************************************/ size_t base64_encode(const char *src, size_t src_len, char *dst, size_t dst_len) { char *p = dst; size_t src_i = 0; size_t max_src_len; unsigned int v; static const char *b64chr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/="; /* Each three octets of the source results in four bytes at the output, * plus we need a terminator. So we can work out the maximum number of * source octets we can process */ if (dst_len == 0) { max_src_len = 0; } else { max_src_len = (dst_len - 1) / 4 * 3; } if (src_len > max_src_len) { src_len = max_src_len; } while (src_i < src_len) { switch (src_len - src_i) { case 1: v = (unsigned int)(unsigned char)src[src_i++] << 4; *p++ = b64chr[v >> 6]; *p++ = b64chr[v & 0x3f]; *p++ = b64chr[64]; *p++ = b64chr[64]; break; case 2: v = (unsigned int)(unsigned char)src[src_i++] << 10; v |= (unsigned int)(unsigned char)src[src_i++] << 2; *p++ = b64chr[v >> 12]; *p++ = b64chr[(v >> 6) & 0x3f]; *p++ = b64chr[v & 0x3f]; *p++ = b64chr[64]; break; default: v = (unsigned int)(unsigned char)src[src_i++] << 16; v |= (unsigned int)(unsigned char)src[src_i++] << 8; v |= (unsigned int)(unsigned char)src[src_i++]; *p++ = b64chr[v >> 18]; *p++ = b64chr[(v >> 12) & 0x3f]; *p++ = b64chr[(v >> 6) & 0x3f]; *p++ = b64chr[v & 0x3f]; break; } } *p = '\0'; return src_len; }