17cf2cb1d2
Most C standard library functions are undefined if passed NULL, even when the corresponding length is zero. This gives them (and, in turn, all functions which call them) surprising behavior on empty arrays. Some compilers will miscompile code due to this rule. See also https://www.imperialviolet.org/2016/06/26/nonnull.html Add OPENSSL_memcpy, etc., wrappers which avoid this problem. BUG=23 Change-Id: I95f42b23e92945af0e681264fffaf578e7f8465e Reviewed-on: https://boringssl-review.googlesource.com/12928 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
397 lines
12 KiB
C++
397 lines
12 KiB
C++
/* Copyright (c) 2014, Google Inc.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <openssl/base64.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/err.h>
|
|
|
|
#include "../internal.h"
|
|
|
|
|
|
enum encoding_relation {
|
|
// canonical indicates that the encoding is the expected encoding of the
|
|
// input.
|
|
canonical,
|
|
// valid indicates that the encoding is /a/ valid encoding of the input, but
|
|
// need not be the canonical one.
|
|
valid,
|
|
// invalid indicates that the encoded data is valid.
|
|
invalid,
|
|
};
|
|
|
|
struct TestVector {
|
|
enum encoding_relation relation;
|
|
const char *decoded;
|
|
const char *encoded;
|
|
};
|
|
|
|
// Test vectors from RFC 4648.
|
|
static const TestVector kTestVectors[] = {
|
|
{canonical, "", ""},
|
|
{canonical, "f", "Zg==\n"},
|
|
{canonical, "fo", "Zm8=\n"},
|
|
{canonical, "foo", "Zm9v\n"},
|
|
{canonical, "foob", "Zm9vYg==\n"},
|
|
{canonical, "fooba", "Zm9vYmE=\n"},
|
|
{canonical, "foobar", "Zm9vYmFy\n"},
|
|
{valid, "foobar", "Zm9vYmFy\n\n"},
|
|
{valid, "foobar", " Zm9vYmFy\n\n"},
|
|
{valid, "foobar", " Z m 9 v Y m F y\n\n"},
|
|
{invalid, "", "Zm9vYmFy=\n"},
|
|
{invalid, "", "Zm9vYmFy==\n"},
|
|
{invalid, "", "Zm9vYmFy===\n"},
|
|
{invalid, "", "Z"},
|
|
{invalid, "", "Z\n"},
|
|
{invalid, "", "ab!c"},
|
|
{invalid, "", "ab=c"},
|
|
{invalid, "", "abc"},
|
|
|
|
{canonical, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA==\n"},
|
|
{valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA\n==\n"},
|
|
{valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA=\n=\n"},
|
|
{invalid, "",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA=\n==\n"},
|
|
{canonical, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4\neHh4eHh"
|
|
"4eHh4eHh4\n"},
|
|
{canonical,
|
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4\neHh4eHh"
|
|
"4eHh4eHh4eHh4eA==\n"},
|
|
{valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh\n4eHh4eHh"
|
|
"4eHh4eHh4eHh4eA==\n"},
|
|
{valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e"
|
|
"Hh4eHh4eHh4eA==\n"},
|
|
{invalid, "",
|
|
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA=="
|
|
"\neHh4eHh4eHh4eHh4eHh4eHh4\n"},
|
|
|
|
// A '-' has traditionally been treated as the end of the data by OpenSSL
|
|
// and anything following would be ignored. BoringSSL does not accept this
|
|
// non-standard extension.
|
|
{invalid, "", "Zm9vYmFy-anythinggoes"},
|
|
{invalid, "", "Zm9vYmFy\n-anythinggoes"},
|
|
|
|
// CVE-2015-0292
|
|
{invalid, "",
|
|
"ZW5jb2RlIG1lCg==========================================================="
|
|
"=======\n"},
|
|
};
|
|
|
|
static const size_t kNumTests = OPENSSL_ARRAY_SIZE(kTestVectors);
|
|
|
|
// RemoveNewlines returns a copy of |in| with all '\n' characters removed.
|
|
static std::string RemoveNewlines(const char *in) {
|
|
std::string ret;
|
|
const size_t in_len = strlen(in);
|
|
|
|
for (size_t i = 0; i < in_len; i++) {
|
|
if (in[i] != '\n') {
|
|
ret.push_back(in[i]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool TestEncodeBlock() {
|
|
for (unsigned i = 0; i < kNumTests; i++) {
|
|
const TestVector *t = &kTestVectors[i];
|
|
if (t->relation != canonical) {
|
|
continue;
|
|
}
|
|
|
|
const size_t decoded_len = strlen(t->decoded);
|
|
size_t max_encoded_len;
|
|
if (!EVP_EncodedLength(&max_encoded_len, decoded_len)) {
|
|
fprintf(stderr, "#%u: EVP_EncodedLength failed\n", i);
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> out_vec(max_encoded_len);
|
|
uint8_t *out = out_vec.data();
|
|
size_t len = EVP_EncodeBlock(out, (const uint8_t *)t->decoded, decoded_len);
|
|
|
|
std::string encoded(RemoveNewlines(t->encoded));
|
|
if (len != encoded.size() ||
|
|
OPENSSL_memcmp(out, encoded.data(), len) != 0) {
|
|
fprintf(stderr, "encode(\"%s\") = \"%.*s\", want \"%s\"\n",
|
|
t->decoded, (int)len, (const char*)out, encoded.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestDecodeBase64() {
|
|
size_t len;
|
|
|
|
for (unsigned i = 0; i < kNumTests; i++) {
|
|
const TestVector *t = &kTestVectors[i];
|
|
|
|
if (t->relation == valid) {
|
|
// The non-canonical encodings will generally have odd whitespace etc
|
|
// that |EVP_DecodeBase64| will reject.
|
|
continue;
|
|
}
|
|
|
|
const std::string encoded(RemoveNewlines(t->encoded));
|
|
std::vector<uint8_t> out_vec(encoded.size());
|
|
uint8_t *out = out_vec.data();
|
|
|
|
int ok = EVP_DecodeBase64(out, &len, out_vec.size(),
|
|
(const uint8_t *)encoded.data(), encoded.size());
|
|
|
|
if (t->relation == invalid) {
|
|
if (ok) {
|
|
fprintf(stderr, "decode(\"%s\") didn't fail but should have\n",
|
|
encoded.c_str());
|
|
return false;
|
|
}
|
|
} else if (t->relation == canonical) {
|
|
if (!ok) {
|
|
fprintf(stderr, "decode(\"%s\") failed\n", encoded.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (len != strlen(t->decoded) ||
|
|
OPENSSL_memcmp(out, t->decoded, len) != 0) {
|
|
fprintf(stderr, "decode(\"%s\") = \"%.*s\", want \"%s\"\n",
|
|
encoded.c_str(), (int)len, (const char*)out, t->decoded);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestDecodeBlock() {
|
|
for (unsigned i = 0; i < kNumTests; i++) {
|
|
const TestVector *t = &kTestVectors[i];
|
|
if (t->relation != canonical) {
|
|
continue;
|
|
}
|
|
|
|
std::string encoded(RemoveNewlines(t->encoded));
|
|
|
|
std::vector<uint8_t> out_vec(encoded.size());
|
|
uint8_t *out = out_vec.data();
|
|
|
|
// Test that the padding behavior of the deprecated API is preserved.
|
|
int ret =
|
|
EVP_DecodeBlock(out, (const uint8_t *)encoded.data(), encoded.size());
|
|
if (ret < 0) {
|
|
fprintf(stderr, "EVP_DecodeBlock(\"%s\") failed\n", t->encoded);
|
|
return false;
|
|
}
|
|
if (ret % 3 != 0) {
|
|
fprintf(stderr, "EVP_DecodeBlock did not ignore padding\n");
|
|
return false;
|
|
}
|
|
size_t expected_len = strlen(t->decoded);
|
|
if (expected_len % 3 != 0) {
|
|
ret -= 3 - (expected_len % 3);
|
|
}
|
|
if (static_cast<size_t>(ret) != strlen(t->decoded) ||
|
|
OPENSSL_memcmp(out, t->decoded, ret) != 0) {
|
|
fprintf(stderr, "decode(\"%s\") = \"%.*s\", want \"%s\"\n",
|
|
t->encoded, ret, (const char*)out, t->decoded);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestEncodeDecode() {
|
|
for (unsigned test_num = 0; test_num < kNumTests; test_num++) {
|
|
const TestVector *t = &kTestVectors[test_num];
|
|
|
|
EVP_ENCODE_CTX ctx;
|
|
const size_t decoded_len = strlen(t->decoded);
|
|
|
|
if (t->relation == canonical) {
|
|
size_t max_encoded_len;
|
|
if (!EVP_EncodedLength(&max_encoded_len, decoded_len)) {
|
|
fprintf(stderr, "#%u: EVP_EncodedLength failed\n", test_num);
|
|
return false;
|
|
}
|
|
|
|
// EVP_EncodeUpdate will output new lines every 64 bytes of output so we
|
|
// need slightly more than |EVP_EncodedLength| returns. */
|
|
max_encoded_len += (max_encoded_len + 63) >> 6;
|
|
std::vector<uint8_t> out_vec(max_encoded_len);
|
|
uint8_t *out = out_vec.data();
|
|
|
|
EVP_EncodeInit(&ctx);
|
|
|
|
int out_len;
|
|
EVP_EncodeUpdate(&ctx, out, &out_len,
|
|
reinterpret_cast<const uint8_t *>(t->decoded),
|
|
decoded_len);
|
|
size_t total = out_len;
|
|
|
|
EVP_EncodeFinal(&ctx, out + total, &out_len);
|
|
total += out_len;
|
|
|
|
if (total != strlen(t->encoded) ||
|
|
OPENSSL_memcmp(out, t->encoded, total) != 0) {
|
|
fprintf(stderr, "#%u: EVP_EncodeUpdate produced different output: '%s' (%u)\n",
|
|
test_num, out, static_cast<unsigned>(total));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> out_vec(strlen(t->encoded));
|
|
uint8_t *out = out_vec.data();
|
|
|
|
EVP_DecodeInit(&ctx);
|
|
int out_len;
|
|
size_t total = 0;
|
|
int ret = EVP_DecodeUpdate(&ctx, out, &out_len,
|
|
reinterpret_cast<const uint8_t *>(t->encoded),
|
|
strlen(t->encoded));
|
|
if (ret != -1) {
|
|
total = out_len;
|
|
ret = EVP_DecodeFinal(&ctx, out + total, &out_len);
|
|
total += out_len;
|
|
}
|
|
|
|
switch (t->relation) {
|
|
case canonical:
|
|
case valid:
|
|
if (ret == -1) {
|
|
fprintf(stderr, "#%u: EVP_DecodeUpdate failed\n", test_num);
|
|
return false;
|
|
}
|
|
if (total != decoded_len ||
|
|
OPENSSL_memcmp(out, t->decoded, decoded_len)) {
|
|
fprintf(stderr, "#%u: EVP_DecodeUpdate produced incorrect output\n",
|
|
test_num);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case invalid:
|
|
if (ret != -1) {
|
|
fprintf(stderr, "#%u: EVP_DecodeUpdate was successful but shouldn't have been\n", test_num);
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestDecodeUpdateStreaming() {
|
|
for (unsigned test_num = 0; test_num < kNumTests; test_num++) {
|
|
const TestVector *t = &kTestVectors[test_num];
|
|
if (t->relation == invalid) {
|
|
continue;
|
|
}
|
|
|
|
const size_t encoded_len = strlen(t->encoded);
|
|
|
|
std::vector<uint8_t> out(encoded_len);
|
|
|
|
for (size_t chunk_size = 1; chunk_size <= encoded_len; chunk_size++) {
|
|
size_t out_len = 0;
|
|
EVP_ENCODE_CTX ctx;
|
|
EVP_DecodeInit(&ctx);
|
|
|
|
for (size_t i = 0; i < encoded_len;) {
|
|
size_t todo = encoded_len - i;
|
|
if (todo > chunk_size) {
|
|
todo = chunk_size;
|
|
}
|
|
|
|
int bytes_written;
|
|
int ret = EVP_DecodeUpdate(
|
|
&ctx, out.data() + out_len, &bytes_written,
|
|
reinterpret_cast<const uint8_t *>(t->encoded + i), todo);
|
|
i += todo;
|
|
|
|
switch (ret) {
|
|
case -1:
|
|
fprintf(stderr, "#%u: EVP_DecodeUpdate returned error\n", test_num);
|
|
return 0;
|
|
case 0:
|
|
out_len += bytes_written;
|
|
if (i == encoded_len ||
|
|
(i + 1 == encoded_len && t->encoded[i] == '\n') ||
|
|
/* If there was an '-' in the input (which means “EOF”) then
|
|
* this loop will continue to test that |EVP_DecodeUpdate| will
|
|
* ignore the remainder of the input. */
|
|
strchr(t->encoded, '-') != nullptr) {
|
|
break;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"#%u: EVP_DecodeUpdate returned zero before end of "
|
|
"encoded data\n",
|
|
test_num);
|
|
return 0;
|
|
default:
|
|
out_len += bytes_written;
|
|
}
|
|
}
|
|
|
|
int bytes_written;
|
|
int ret = EVP_DecodeFinal(&ctx, out.data() + out_len, &bytes_written);
|
|
if (ret == -1) {
|
|
fprintf(stderr, "#%u: EVP_DecodeFinal returned error\n", test_num);
|
|
return 0;
|
|
}
|
|
out_len += bytes_written;
|
|
|
|
if (out_len != strlen(t->decoded) ||
|
|
OPENSSL_memcmp(out.data(), t->decoded, out_len) != 0) {
|
|
fprintf(stderr, "#%u: incorrect output\n", test_num);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int main(void) {
|
|
CRYPTO_library_init();
|
|
|
|
if (!TestEncodeBlock() ||
|
|
!TestDecodeBase64() ||
|
|
!TestDecodeBlock() ||
|
|
!TestDecodeUpdateStreaming() ||
|
|
!TestEncodeDecode()) {
|
|
return 1;
|
|
}
|
|
|
|
printf("PASS\n");
|
|
return 0;
|
|
}
|