David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 1 | /* Copyright (c) 2014, Google Inc. |
| 2 | * |
| 3 | * Permission to use, copy, modify, and/or distribute this software for any |
| 4 | * purpose with or without fee is hereby granted, provided that the above |
| 5 | * copyright notice and this permission notice appear in all copies. |
| 6 | * |
| 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| 10 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| 12 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| 13 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| 14 | |
| 15 | #include <stdio.h> |
| 16 | #include <string.h> |
| 17 | |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 18 | #include <string> |
| 19 | #include <vector> |
| 20 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 21 | #include <gtest/gtest.h> |
| 22 | |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 23 | #include <openssl/base64.h> |
David Benjamin | a70c75c | 2014-09-11 19:11:15 -0400 | [diff] [blame] | 24 | #include <openssl/crypto.h> |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 25 | #include <openssl/err.h> |
| 26 | |
Steven Valdez | cb96654 | 2016-08-17 16:56:14 -0400 | [diff] [blame] | 27 | #include "../internal.h" |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 28 | #include "../test/test_util.h" |
Steven Valdez | cb96654 | 2016-08-17 16:56:14 -0400 | [diff] [blame] | 29 | |
Adam Langley | 904a32e | 2014-07-17 10:33:16 -0700 | [diff] [blame] | 30 | |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 31 | enum encoding_relation { |
| 32 | // canonical indicates that the encoding is the expected encoding of the |
| 33 | // input. |
| 34 | canonical, |
| 35 | // valid indicates that the encoding is /a/ valid encoding of the input, but |
| 36 | // need not be the canonical one. |
| 37 | valid, |
| 38 | // invalid indicates that the encoded data is valid. |
| 39 | invalid, |
| 40 | }; |
| 41 | |
David Benjamin | e8261a0 | 2015-03-26 01:19:49 -0400 | [diff] [blame] | 42 | struct TestVector { |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 43 | enum encoding_relation relation; |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 44 | const char *decoded; |
| 45 | const char *encoded; |
David Benjamin | e8261a0 | 2015-03-26 01:19:49 -0400 | [diff] [blame] | 46 | }; |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 47 | |
David Benjamin | e8261a0 | 2015-03-26 01:19:49 -0400 | [diff] [blame] | 48 | // Test vectors from RFC 4648. |
| 49 | static const TestVector kTestVectors[] = { |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 50 | {canonical, "", ""}, |
| 51 | {canonical, "f", "Zg==\n"}, |
| 52 | {canonical, "fo", "Zm8=\n"}, |
| 53 | {canonical, "foo", "Zm9v\n"}, |
| 54 | {canonical, "foob", "Zm9vYg==\n"}, |
| 55 | {canonical, "fooba", "Zm9vYmE=\n"}, |
| 56 | {canonical, "foobar", "Zm9vYmFy\n"}, |
| 57 | {valid, "foobar", "Zm9vYmFy\n\n"}, |
| 58 | {valid, "foobar", " Zm9vYmFy\n\n"}, |
| 59 | {valid, "foobar", " Z m 9 v Y m F y\n\n"}, |
| 60 | {invalid, "", "Zm9vYmFy=\n"}, |
| 61 | {invalid, "", "Zm9vYmFy==\n"}, |
| 62 | {invalid, "", "Zm9vYmFy===\n"}, |
| 63 | {invalid, "", "Z"}, |
| 64 | {invalid, "", "Z\n"}, |
| 65 | {invalid, "", "ab!c"}, |
| 66 | {invalid, "", "ab=c"}, |
| 67 | {invalid, "", "abc"}, |
| 68 | |
| 69 | {canonical, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 70 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA==\n"}, |
| 71 | {valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 72 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA\n==\n"}, |
| 73 | {valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 74 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA=\n=\n"}, |
| 75 | {invalid, "", |
| 76 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA=\n==\n"}, |
| 77 | {canonical, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 78 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4\neHh4eHh" |
| 79 | "4eHh4eHh4\n"}, |
| 80 | {canonical, |
| 81 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 82 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4\neHh4eHh" |
| 83 | "4eHh4eHh4eHh4eA==\n"}, |
| 84 | {valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 85 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh\n4eHh4eHh" |
| 86 | "4eHh4eHh4eHh4eA==\n"}, |
| 87 | {valid, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 88 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e" |
| 89 | "Hh4eHh4eHh4eA==\n"}, |
| 90 | {invalid, "", |
| 91 | "eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA==" |
| 92 | "\neHh4eHh4eHh4eHh4eHh4eHh4\n"}, |
| 93 | |
| 94 | // A '-' has traditionally been treated as the end of the data by OpenSSL |
| 95 | // and anything following would be ignored. BoringSSL does not accept this |
| 96 | // non-standard extension. |
| 97 | {invalid, "", "Zm9vYmFy-anythinggoes"}, |
| 98 | {invalid, "", "Zm9vYmFy\n-anythinggoes"}, |
| 99 | |
| 100 | // CVE-2015-0292 |
| 101 | {invalid, "", |
| 102 | "ZW5jb2RlIG1lCg===========================================================" |
| 103 | "=======\n"}, |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 104 | }; |
Adam Langley | 904a32e | 2014-07-17 10:33:16 -0700 | [diff] [blame] | 105 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 106 | class Base64Test : public testing::TestWithParam<TestVector> {}; |
| 107 | |
| 108 | INSTANTIATE_TEST_CASE_P(, Base64Test, testing::ValuesIn(kTestVectors)); |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 109 | |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 110 | // RemoveNewlines returns a copy of |in| with all '\n' characters removed. |
| 111 | static std::string RemoveNewlines(const char *in) { |
| 112 | std::string ret; |
| 113 | const size_t in_len = strlen(in); |
| 114 | |
David Benjamin | 5409123 | 2016-09-05 12:47:25 -0400 | [diff] [blame] | 115 | for (size_t i = 0; i < in_len; i++) { |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 116 | if (in[i] != '\n') { |
| 117 | ret.push_back(in[i]); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | return ret; |
| 122 | } |
| 123 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 124 | TEST_P(Base64Test, EncodeBlock) { |
| 125 | const TestVector &t = GetParam(); |
| 126 | if (t.relation != canonical) { |
| 127 | return; |
| 128 | } |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 129 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 130 | const size_t decoded_len = strlen(t.decoded); |
| 131 | size_t max_encoded_len; |
| 132 | ASSERT_TRUE(EVP_EncodedLength(&max_encoded_len, decoded_len)); |
| 133 | |
| 134 | std::vector<uint8_t> out_vec(max_encoded_len); |
| 135 | uint8_t *out = out_vec.data(); |
| 136 | size_t len = EVP_EncodeBlock(out, (const uint8_t *)t.decoded, decoded_len); |
| 137 | |
| 138 | std::string encoded(RemoveNewlines(t.encoded)); |
| 139 | EXPECT_EQ(Bytes(encoded), Bytes(out, len)); |
| 140 | } |
| 141 | |
| 142 | TEST_P(Base64Test, DecodeBase64) { |
| 143 | const TestVector &t = GetParam(); |
| 144 | if (t.relation == valid) { |
| 145 | // The non-canonical encodings will generally have odd whitespace etc |
| 146 | // that |EVP_DecodeBase64| will reject. |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | const std::string encoded(RemoveNewlines(t.encoded)); |
| 151 | std::vector<uint8_t> out_vec(encoded.size()); |
| 152 | uint8_t *out = out_vec.data(); |
| 153 | |
| 154 | size_t len; |
| 155 | int ok = EVP_DecodeBase64(out, &len, out_vec.size(), |
| 156 | (const uint8_t *)encoded.data(), encoded.size()); |
| 157 | |
| 158 | if (t.relation == invalid) { |
| 159 | EXPECT_FALSE(ok); |
| 160 | } else if (t.relation == canonical) { |
| 161 | ASSERT_TRUE(ok); |
| 162 | EXPECT_EQ(Bytes(t.decoded), Bytes(out, len)); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | TEST_P(Base64Test, DecodeBlock) { |
| 167 | const TestVector &t = GetParam(); |
| 168 | if (t.relation != canonical) { |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | std::string encoded(RemoveNewlines(t.encoded)); |
| 173 | |
| 174 | std::vector<uint8_t> out_vec(encoded.size()); |
| 175 | uint8_t *out = out_vec.data(); |
| 176 | |
| 177 | // Test that the padding behavior of the deprecated API is preserved. |
| 178 | int ret = |
| 179 | EVP_DecodeBlock(out, (const uint8_t *)encoded.data(), encoded.size()); |
| 180 | ASSERT_GE(ret, 0); |
| 181 | // EVP_DecodeBlock should ignore padding. |
| 182 | ASSERT_EQ(0, ret % 3); |
| 183 | size_t expected_len = strlen(t.decoded); |
| 184 | if (expected_len % 3 != 0) { |
| 185 | ret -= 3 - (expected_len % 3); |
| 186 | } |
| 187 | EXPECT_EQ(Bytes(t.decoded), Bytes(out, static_cast<size_t>(ret))); |
| 188 | } |
| 189 | |
| 190 | TEST_P(Base64Test, EncodeDecode) { |
| 191 | const TestVector &t = GetParam(); |
| 192 | |
| 193 | EVP_ENCODE_CTX ctx; |
| 194 | const size_t decoded_len = strlen(t.decoded); |
| 195 | |
| 196 | if (t.relation == canonical) { |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 197 | size_t max_encoded_len; |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 198 | ASSERT_TRUE(EVP_EncodedLength(&max_encoded_len, decoded_len)); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 199 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 200 | // EVP_EncodeUpdate will output new lines every 64 bytes of output so we |
| 201 | // need slightly more than |EVP_EncodedLength| returns. */ |
| 202 | max_encoded_len += (max_encoded_len + 63) >> 6; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 203 | std::vector<uint8_t> out_vec(max_encoded_len); |
| 204 | uint8_t *out = out_vec.data(); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 205 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 206 | EVP_EncodeInit(&ctx); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 207 | |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 208 | int out_len; |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 209 | EVP_EncodeUpdate(&ctx, out, &out_len, |
| 210 | reinterpret_cast<const uint8_t *>(t.decoded), |
| 211 | decoded_len); |
| 212 | size_t total = out_len; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 213 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 214 | EVP_EncodeFinal(&ctx, out + total, &out_len); |
| 215 | total += out_len; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 216 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 217 | EXPECT_EQ(Bytes(t.encoded), Bytes(out, total)); |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 218 | } |
| 219 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 220 | std::vector<uint8_t> out_vec(strlen(t.encoded)); |
| 221 | uint8_t *out = out_vec.data(); |
| 222 | |
| 223 | EVP_DecodeInit(&ctx); |
| 224 | int out_len; |
| 225 | size_t total = 0; |
| 226 | int ret = EVP_DecodeUpdate(&ctx, out, &out_len, |
| 227 | reinterpret_cast<const uint8_t *>(t.encoded), |
| 228 | strlen(t.encoded)); |
| 229 | if (ret != -1) { |
| 230 | total = out_len; |
| 231 | ret = EVP_DecodeFinal(&ctx, out + total, &out_len); |
| 232 | total += out_len; |
| 233 | } |
| 234 | |
| 235 | switch (t.relation) { |
| 236 | case canonical: |
| 237 | case valid: |
| 238 | ASSERT_NE(-1, ret); |
| 239 | EXPECT_EQ(Bytes(t.decoded), Bytes(out, total)); |
| 240 | break; |
| 241 | |
| 242 | case invalid: |
| 243 | EXPECT_EQ(-1, ret); |
| 244 | break; |
| 245 | } |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 246 | } |
David Benjamin | d698f32 | 2014-08-24 00:49:21 -0400 | [diff] [blame] | 247 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 248 | TEST_P(Base64Test, DecodeUpdateStreaming) { |
| 249 | const TestVector &t = GetParam(); |
| 250 | if (t.relation == invalid) { |
| 251 | return; |
| 252 | } |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 253 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 254 | const size_t encoded_len = strlen(t.encoded); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 255 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 256 | std::vector<uint8_t> out(encoded_len); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 257 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 258 | for (size_t chunk_size = 1; chunk_size <= encoded_len; chunk_size++) { |
| 259 | SCOPED_TRACE(chunk_size); |
| 260 | size_t out_len = 0; |
| 261 | EVP_ENCODE_CTX ctx; |
| 262 | EVP_DecodeInit(&ctx); |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 263 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 264 | for (size_t i = 0; i < encoded_len;) { |
| 265 | size_t todo = encoded_len - i; |
| 266 | if (todo > chunk_size) { |
| 267 | todo = chunk_size; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 268 | } |
| 269 | |
| 270 | int bytes_written; |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 271 | int ret = EVP_DecodeUpdate( |
| 272 | &ctx, out.data() + out_len, &bytes_written, |
| 273 | reinterpret_cast<const uint8_t *>(t.encoded + i), todo); |
| 274 | i += todo; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 275 | |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 276 | switch (ret) { |
| 277 | case -1: |
| 278 | FAIL() << "EVP_DecodeUpdate failed"; |
| 279 | case 0: |
| 280 | out_len += bytes_written; |
| 281 | if (i == encoded_len || |
| 282 | (i + 1 == encoded_len && t.encoded[i] == '\n') || |
David Benjamin | 808f832 | 2017-08-18 14:06:02 -0400 | [diff] [blame] | 283 | // If there was an '-' in the input (which means “EOF”) then |
| 284 | // this loop will continue to test that |EVP_DecodeUpdate| will |
| 285 | // ignore the remainder of the input. |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 286 | strchr(t.encoded, '-') != nullptr) { |
| 287 | break; |
| 288 | } |
| 289 | |
| 290 | FAIL() |
| 291 | << "EVP_DecodeUpdate returned zero before end of encoded data."; |
| 292 | case 1: |
| 293 | out_len += bytes_written; |
| 294 | break; |
| 295 | default: |
| 296 | FAIL() << "Invalid return value " << ret; |
Adam Langley | d09175f | 2016-05-20 10:51:48 -0700 | [diff] [blame] | 297 | } |
| 298 | } |
David Benjamin | 76dd180 | 2017-04-17 10:04:39 -0400 | [diff] [blame] | 299 | |
| 300 | int bytes_written; |
| 301 | int ret = EVP_DecodeFinal(&ctx, out.data() + out_len, &bytes_written); |
| 302 | ASSERT_NE(ret, -1); |
| 303 | out_len += bytes_written; |
| 304 | |
| 305 | EXPECT_EQ(Bytes(t.decoded), Bytes(out.data(), out_len)); |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 306 | } |
David Benjamin | 7b35b58 | 2014-07-17 01:51:59 -0400 | [diff] [blame] | 307 | } |