/* * Copyright (C) 2000-2012 Free Software Foundation, Inc. * Copyright (C) 2017 Red Hat, Inc. * * Author: Nikos Mavrogiannopoulos * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ /* Functions that relate to base64 encoding and decoding. */ #include "gnutls_int.h" #include "errors.h" #include #include #include #define INCR(what, size, max_len) \ do { \ what+=size; \ if (what > max_len) { \ gnutls_assert(); \ gnutls_free( result->data); result->data = NULL; \ return GNUTLS_E_INTERNAL_ERROR; \ } \ } while(0) /* encodes data and puts the result into result (locally allocated) * The result_size (including the null terminator) is the return value. */ int _gnutls_fbase64_encode(const char *msg, const uint8_t * data, size_t data_size, gnutls_datum_t * result) { int tmp; unsigned int i; uint8_t tmpres[66]; uint8_t *ptr; char top[80]; char bottom[80]; size_t size, max, bytes; int pos, top_len = 0, bottom_len = 0; unsigned raw_encoding = 0; if (msg == NULL || msg[0] == 0) raw_encoding = 1; if (!raw_encoding) { if (strlen(msg) > 50) { gnutls_assert(); return GNUTLS_E_BASE64_ENCODING_ERROR; } _gnutls_str_cpy(top, sizeof(top), "-----BEGIN "); _gnutls_str_cat(top, sizeof(top), msg); _gnutls_str_cat(top, sizeof(top), "-----\n"); _gnutls_str_cpy(bottom, sizeof(bottom), "-----END "); _gnutls_str_cat(bottom, sizeof(bottom), msg); _gnutls_str_cat(bottom, sizeof(bottom), "-----\n"); top_len = strlen(top); bottom_len = strlen(bottom); } max = B64FSIZE(top_len + bottom_len, data_size); result->data = gnutls_malloc(max + 1); if (result->data == NULL) { gnutls_assert(); return GNUTLS_E_MEMORY_ERROR; } bytes = pos = 0; INCR(bytes, top_len, max); pos = top_len; memcpy(result->data, top, top_len); for (i = 0; i < data_size; i += 48) { if (data_size - i < 48) tmp = data_size - i; else tmp = 48; size = BASE64_ENCODE_RAW_LENGTH(tmp); if (sizeof(tmpres) < size) return gnutls_assert_val(GNUTLS_E_BASE64_ENCODING_ERROR); base64_encode_raw((void*)tmpres, tmp, &data[i]); INCR(bytes, size + 1, max); ptr = &result->data[pos]; memcpy(ptr, tmpres, size); ptr += size; pos += size; if (!raw_encoding) { *ptr++ = '\n'; pos++; } else { bytes--; } } INCR(bytes, bottom_len, max); memcpy(&result->data[bytes - bottom_len], bottom, bottom_len); result->data[bytes] = 0; result->size = bytes; return max + 1; } /** * gnutls_pem_base64_encode: * @msg: is a message to be put in the header (may be %NULL) * @data: contain the raw data * @result: the place where base64 data will be copied * @result_size: holds the size of the result * * This function will convert the given data to printable data, using * the base64 encoding. This is the encoding used in PEM messages. * * The output string will be null terminated, although the output size will * not include the terminating null. * * Returns: On success %GNUTLS_E_SUCCESS (0) is returned, * %GNUTLS_E_SHORT_MEMORY_BUFFER is returned if the buffer given is * not long enough, or 0 on success. **/ int gnutls_pem_base64_encode(const char *msg, const gnutls_datum_t * data, char *result, size_t * result_size) { gnutls_datum_t res; int ret; ret = _gnutls_fbase64_encode(msg, data->data, data->size, &res); if (ret < 0) return ret; if (result == NULL || *result_size < (unsigned) res.size) { gnutls_free(res.data); *result_size = res.size + 1; return GNUTLS_E_SHORT_MEMORY_BUFFER; } else { memcpy(result, res.data, res.size); gnutls_free(res.data); *result_size = res.size; } return 0; } /** * gnutls_pem_base64_encode2: * @header: is a message to be put in the encoded header (may be %NULL) * @data: contains the raw data * @result: will hold the newly allocated encoded data * * This function will convert the given data to printable data, using * the base64 encoding. This is the encoding used in PEM messages. * This function will allocate the required memory to hold the encoded * data. * * You should use gnutls_free() to free the returned data. * * Note, that prior to GnuTLS 3.4.0 this function was available * under the name gnutls_pem_base64_encode_alloc(). There is * compatibility macro pointing to this function. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. * * Since: 3.4.0 **/ int gnutls_pem_base64_encode2(const char *header, const gnutls_datum_t * data, gnutls_datum_t * result) { int ret; if (result == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = _gnutls_fbase64_encode(header, data->data, data->size, result); if (ret < 0) return gnutls_assert_val(ret); return 0; } /* copies data to result but removes newlines and * returns the size of the data copied. * * It will fail with GNUTLS_E_BASE64_DECODING_ERROR if the * end-result is the empty string. */ inline static int cpydata(const uint8_t * data, int data_size, gnutls_datum_t * result) { int i, j; result->data = gnutls_malloc(data_size + 1); if (result->data == NULL) return GNUTLS_E_MEMORY_ERROR; for (j = i = 0; i < data_size; i++) { if (data[i] == '\n' || data[i] == '\r' || data[i] == ' ' || data[i] == '\t') continue; else if (data[i] == '-') break; result->data[j] = data[i]; j++; } result->size = j; result->data[j] = 0; if (j==0) { gnutls_free(result->data); return gnutls_assert_val(GNUTLS_E_BASE64_DECODING_ERROR); } return j; } /* decodes data and puts the result into result (locally allocated). * Note that encodings of zero-length strings are being rejected * with GNUTLS_E_BASE64_DECODING_ERROR. * * The result_size is the return value. */ int _gnutls_base64_decode(const uint8_t * data, size_t data_size, gnutls_datum_t * result) { int ret; size_t size; gnutls_datum_t pdata; struct base64_decode_ctx ctx; if (data_size == 0) { result->data = (unsigned char*)gnutls_strdup(""); if (result->data == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); result->size = 0; return 0; } ret = cpydata(data, data_size, &pdata); if (ret < 0) { gnutls_assert(); return ret; } base64_decode_init(&ctx); size = BASE64_DECODE_LENGTH(pdata.size); if (size == 0) { ret = gnutls_assert_val(GNUTLS_E_BASE64_DECODING_ERROR); goto cleanup; } result->data = gnutls_malloc(size); if (result->data == NULL) { ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); goto cleanup; } ret = base64_decode_update(&ctx, &size, result->data, pdata.size, (void*)pdata.data); if (ret == 0 || size == 0) { gnutls_assert(); ret = GNUTLS_E_BASE64_DECODING_ERROR; goto fail; } ret = base64_decode_final(&ctx); if (ret != 1) { ret = gnutls_assert_val(GNUTLS_E_BASE64_DECODING_ERROR); goto fail; } result->size = size; ret = size; goto cleanup; fail: gnutls_free(result->data); cleanup: gnutls_free(pdata.data); return ret; } /* Searches the given string for ONE PEM encoded certificate, and * stores it in the result. * * The result_size (always non-zero) is the return value, * or a negative error code. */ #define ENDSTR "-----" int _gnutls_fbase64_decode(const char *header, const uint8_t * data, size_t data_size, gnutls_datum_t * result) { int ret; static const char top[] = "-----BEGIN "; static const char bottom[] = "-----END "; uint8_t *rdata, *kdata; int rdata_size; char pem_header[128]; _gnutls_str_cpy(pem_header, sizeof(pem_header), top); if (header != NULL) _gnutls_str_cat(pem_header, sizeof(pem_header), header); rdata = memmem(data, data_size, pem_header, strlen(pem_header)); if (rdata == NULL) { gnutls_assert(); _gnutls_hard_log("Could not find '%s'\n", pem_header); return GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR; } data_size -= MEMSUB(rdata, data); if (data_size < 4 + strlen(bottom)) { gnutls_assert(); return GNUTLS_E_BASE64_DECODING_ERROR; } kdata = memmem(rdata + 1, data_size - 1, ENDSTR, sizeof(ENDSTR) - 1); /* allow CR as well. */ if (kdata == NULL) { gnutls_assert(); _gnutls_hard_log("Could not find '%s'\n", ENDSTR); return GNUTLS_E_BASE64_DECODING_ERROR; } data_size -= strlen(ENDSTR); data_size -= MEMSUB(kdata, rdata); rdata = kdata + strlen(ENDSTR); /* position is now after the ---BEGIN--- headers */ kdata = memmem(rdata, data_size, bottom, strlen(bottom)); if (kdata == NULL) { gnutls_assert(); return GNUTLS_E_BASE64_DECODING_ERROR; } /* position of kdata is before the ----END--- footer */ rdata_size = MEMSUB(kdata, rdata); if (rdata_size < 4) { gnutls_assert(); return GNUTLS_E_BASE64_DECODING_ERROR; } if ((ret = _gnutls_base64_decode(rdata, rdata_size, result)) < 0) { gnutls_assert(); return GNUTLS_E_BASE64_DECODING_ERROR; } return ret; } /** * gnutls_pem_base64_decode: * @header: A null terminated string with the PEM header (eg. CERTIFICATE) * @b64_data: contain the encoded data * @result: the place where decoded data will be copied * @result_size: holds the size of the result * * This function will decode the given encoded data. If the header * given is non %NULL this function will search for "-----BEGIN header" * and decode only this part. Otherwise it will decode the first PEM * packet found. * * Returns: On success %GNUTLS_E_SUCCESS (0) is returned, * %GNUTLS_E_SHORT_MEMORY_BUFFER is returned if the buffer given is * not long enough, or 0 on success. **/ int gnutls_pem_base64_decode(const char *header, const gnutls_datum_t * b64_data, unsigned char *result, size_t * result_size) { gnutls_datum_t res; int ret; ret = _gnutls_fbase64_decode(header, b64_data->data, b64_data->size, &res); if (ret < 0) return gnutls_assert_val(ret); if (result == NULL || *result_size < (unsigned) res.size) { gnutls_free(res.data); *result_size = res.size; return GNUTLS_E_SHORT_MEMORY_BUFFER; } else { memcpy(result, res.data, res.size); gnutls_free(res.data); *result_size = res.size; } return 0; } /** * gnutls_pem_base64_decode2: * @header: The PEM header (eg. CERTIFICATE) * @b64_data: contains the encoded data * @result: the location of decoded data * * This function will decode the given encoded data. The decoded data * will be allocated, and stored into result. If the header given is * non null this function will search for "-----BEGIN header" and * decode only this part. Otherwise it will decode the first PEM * packet found. * * You should use gnutls_free() to free the returned data. * * Note, that prior to GnuTLS 3.4.0 this function was available * under the name gnutls_pem_base64_decode_alloc(). There is * compatibility macro pointing to this function. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. * * Since: 3.4.0 **/ int gnutls_pem_base64_decode2(const char *header, const gnutls_datum_t * b64_data, gnutls_datum_t * result) { int ret; if (result == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = _gnutls_fbase64_decode(header, b64_data->data, b64_data->size, result); if (ret < 0) return gnutls_assert_val(ret); return 0; } /** * gnutls_base64_decode2: * @base64: contains the encoded data * @result: the location of decoded data * * This function will decode the given base64 encoded data. The decoded data * will be allocated, and stored into result. * * You should use gnutls_free() to free the returned data. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. * * Since: 3.6.0 **/ int gnutls_base64_decode2(const gnutls_datum_t *base64, gnutls_datum_t *result) { int ret; ret = _gnutls_base64_decode(base64->data, base64->size, result); if (ret < 0) { return gnutls_assert_val(ret); } return 0; } /** * gnutls_base64_encode2: * @data: contains the raw data * @result: will hold the newly allocated encoded data * * This function will convert the given data to printable data, using * the base64 encoding. This function will allocate the required * memory to hold the encoded data. * * You should use gnutls_free() to free the returned data. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. * * Since: 3.6.0 **/ int gnutls_base64_encode2(const gnutls_datum_t *data, gnutls_datum_t *result) { int ret; if (result == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = _gnutls_fbase64_encode(NULL, data->data, data->size, result); if (ret < 0) return gnutls_assert_val(ret); return 0; }