| /* 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; |
| } |