TestEncryptedStream.cpp (33379B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include <algorithm> 8 #include <cstdint> 9 #include <new> 10 #include <numeric> 11 #include <ostream> 12 #include <string> 13 #include <utility> 14 #include <vector> 15 16 #include "ErrorList.h" 17 #include "gtest/gtest.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/Attributes.h" 20 #include "mozilla/FixedBufferOutputStream.h" 21 #include "mozilla/NotNull.h" 22 #include "mozilla/RefPtr.h" 23 #include "mozilla/Span.h" 24 #include "mozilla/UniquePtr.h" 25 #include "mozilla/dom/SafeRefPtr.h" 26 #include "mozilla/dom/quota/DecryptingInputStream_impl.h" 27 #include "mozilla/dom/quota/DummyCipherStrategy.h" 28 #include "mozilla/dom/quota/EncryptedBlock.h" 29 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h" 30 #include "mozilla/dom/quota/NSSCipherStrategy.h" 31 #include "nsCOMPtr.h" 32 #include "nsError.h" 33 #include "nsICloneableInputStream.h" 34 #include "nsIInputStream.h" 35 #include "nsIOutputStream.h" 36 #include "nsISeekableStream.h" 37 #include "nsISupports.h" 38 #include "nsITellableStream.h" 39 #include "nsStreamUtils.h" 40 #include "nsString.h" 41 #include "nsStringFwd.h" 42 #include "nsTArray.h" 43 #include "nscore.h" 44 #include "nss.h" 45 46 namespace mozilla::dom::quota { 47 48 // Similar to ArrayBufferInputStream from netwerk/base/ArrayBufferInputStream.h, 49 // but this is initialized from a Span on construction, rather than lazily from 50 // a JS ArrayBuffer. 51 class ArrayBufferInputStream : public nsIInputStream, 52 public nsISeekableStream, 53 public nsICloneableInputStream { 54 public: 55 explicit ArrayBufferInputStream(mozilla::Span<const uint8_t> aData); 56 bool SetCloseOnEOF(bool value) { return mCloseOnEOF = value; } 57 58 NS_DECL_THREADSAFE_ISUPPORTS 59 NS_DECL_NSIINPUTSTREAM 60 NS_DECL_NSITELLABLESTREAM 61 NS_DECL_NSISEEKABLESTREAM 62 NS_DECL_NSICLONEABLEINPUTSTREAM 63 64 private: 65 virtual ~ArrayBufferInputStream() = default; 66 67 mozilla::UniquePtr<char[]> mArrayBuffer; 68 uint32_t mBufferLength; 69 uint32_t mPos; 70 bool mClosed; 71 bool mCloseOnEOF; 72 }; 73 74 NS_IMPL_ADDREF(ArrayBufferInputStream); 75 NS_IMPL_RELEASE(ArrayBufferInputStream); 76 77 NS_INTERFACE_MAP_BEGIN(ArrayBufferInputStream) 78 NS_INTERFACE_MAP_ENTRY(nsIInputStream) 79 NS_INTERFACE_MAP_ENTRY(nsISeekableStream) 80 NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream) 81 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) 82 NS_INTERFACE_MAP_END 83 84 ArrayBufferInputStream::ArrayBufferInputStream( 85 mozilla::Span<const uint8_t> aData) 86 : mArrayBuffer(MakeUnique<char[]>(aData.Length())), 87 mBufferLength(aData.Length()), 88 mPos(0), 89 mClosed(false), 90 mCloseOnEOF(false) { 91 std::copy(aData.cbegin(), aData.cend(), mArrayBuffer.get()); 92 } 93 94 NS_IMETHODIMP 95 ArrayBufferInputStream::Close() { 96 mClosed = true; 97 return NS_OK; 98 } 99 100 NS_IMETHODIMP 101 ArrayBufferInputStream::Available(uint64_t* aCount) { 102 if (mClosed) { 103 return NS_BASE_STREAM_CLOSED; 104 } 105 106 if (mArrayBuffer) { 107 *aCount = mBufferLength ? mBufferLength - mPos : 0; 108 } else { 109 *aCount = 0; 110 } 111 112 return NS_OK; 113 } 114 115 NS_IMETHODIMP 116 ArrayBufferInputStream::StreamStatus() { 117 return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; 118 } 119 120 NS_IMETHODIMP 121 ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, 122 uint32_t* aReadCount) { 123 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); 124 } 125 126 NS_IMETHODIMP 127 ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, 128 uint32_t aCount, uint32_t* result) { 129 MOZ_RELEASE_ASSERT(result, "null ptr"); 130 MOZ_RELEASE_ASSERT(mBufferLength >= mPos, "bad stream state"); 131 132 if (mClosed) { 133 *result = 0; 134 return NS_OK; 135 } 136 137 MOZ_RELEASE_ASSERT(mArrayBuffer || (mPos == mBufferLength), 138 "stream inited incorrectly"); 139 140 *result = 0; 141 while (mPos < mBufferLength) { 142 uint32_t remaining = mBufferLength - mPos; 143 MOZ_RELEASE_ASSERT(mArrayBuffer); 144 145 uint32_t count = std::min(aCount, remaining); 146 if (count == 0) { 147 break; 148 } 149 150 uint32_t written; 151 nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, 152 &written); 153 if (NS_FAILED(rv)) { 154 // InputStreams do not propagate errors to caller. 155 return NS_OK; 156 } 157 158 MOZ_RELEASE_ASSERT( 159 written <= count, 160 "writer should not write more than we asked it to write"); 161 mPos += written; 162 *result += written; 163 aCount -= written; 164 } 165 166 if (*result == 0 && mCloseOnEOF) { 167 Close(); 168 } 169 170 return NS_OK; 171 } 172 173 NS_IMETHODIMP 174 ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) { 175 // Actually, the stream never blocks, but we lie about it because of the 176 // assumptions in DecryptingInputStream. 177 *aNonBlocking = false; 178 return NS_OK; 179 } 180 181 NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) { 182 MOZ_RELEASE_ASSERT(aRetval); 183 184 if (mClosed) { 185 return NS_BASE_STREAM_CLOSED; 186 } 187 *aRetval = mPos; 188 189 return NS_OK; 190 } 191 192 NS_IMETHODIMP ArrayBufferInputStream::Seek(const int32_t aWhence, 193 const int64_t aOffset) { 194 if (mClosed) { 195 return NS_BASE_STREAM_CLOSED; 196 } 197 198 // XXX This is not safe. it's hard to use CheckedInt here, though. As long as 199 // the class is only used for testing purposes, that's probably fine. 200 201 int32_t newPos = mPos; 202 switch (aWhence) { 203 case NS_SEEK_SET: 204 newPos = aOffset; 205 break; 206 case NS_SEEK_CUR: 207 newPos += aOffset; 208 break; 209 case NS_SEEK_END: 210 newPos = mBufferLength; 211 newPos += aOffset; 212 break; 213 default: 214 return NS_ERROR_ILLEGAL_VALUE; 215 } 216 if (newPos < 0 || static_cast<uint32_t>(newPos) > mBufferLength) { 217 return NS_ERROR_ILLEGAL_VALUE; 218 } 219 mPos = newPos; 220 221 return NS_OK; 222 } 223 224 NS_IMETHODIMP ArrayBufferInputStream::SetEOF() { 225 // Truncating is not supported on a read-only stream. 226 return NS_ERROR_NOT_IMPLEMENTED; 227 } 228 229 NS_IMETHODIMP ArrayBufferInputStream::GetCloneable(bool* aCloneable) { 230 *aCloneable = true; 231 return NS_OK; 232 } 233 234 NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) { 235 *_retval = MakeAndAddRef<ArrayBufferInputStream>( 236 AsBytes(Span{mArrayBuffer.get(), mBufferLength})) 237 .take(); 238 239 return NS_OK; 240 } 241 } // namespace mozilla::dom::quota 242 243 using namespace mozilla; 244 using namespace mozilla::dom::quota; 245 246 class DOM_Quota_EncryptedStream : public ::testing::Test { 247 public: 248 static void SetUpTestCase() { 249 // Do this only once, do not tear it down per test case. 250 if (!sNssContext) { 251 sNssContext.reset( 252 NSS_InitContext("", "", "", "", nullptr, 253 NSS_INIT_READONLY | NSS_INIT_NOCERTDB | 254 NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | 255 NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT)); 256 } 257 } 258 259 static void TearDownTestCase() { sNssContext = nullptr; } 260 261 private: 262 struct NSSInitContextDeleter { 263 void operator()(NSSInitContext* p) { NSS_ShutdownContext(p); } 264 }; 265 static std::unique_ptr<NSSInitContext, NSSInitContextDeleter> sNssContext; 266 }; 267 268 constinit std::unique_ptr<NSSInitContext, 269 DOM_Quota_EncryptedStream::NSSInitContextDeleter> 270 DOM_Quota_EncryptedStream::sNssContext; 271 272 enum struct FlushMode { AfterEachChunk, Never }; 273 enum struct ChunkSize { SingleByte, Unaligned, DataSize }; 274 275 using PackedTestParams = 276 std::tuple<size_t, ChunkSize, ChunkSize, size_t, FlushMode, bool>; 277 278 static size_t EffectiveChunkSize(const ChunkSize aChunkSize, 279 const size_t aDataSize) { 280 switch (aChunkSize) { 281 case ChunkSize::SingleByte: 282 return 1; 283 case ChunkSize::Unaligned: 284 return 17; 285 case ChunkSize::DataSize: 286 return aDataSize; 287 } 288 MOZ_CRASH("Unknown ChunkSize"); 289 } 290 291 struct TestParams { 292 MOZ_IMPLICIT constexpr TestParams(const PackedTestParams& aPackedParams) 293 : mDataSize(std::get<0>(aPackedParams)), 294 mWriteChunkSize(std::get<1>(aPackedParams)), 295 mReadChunkSize(std::get<2>(aPackedParams)), 296 mBlockSize(std::get<3>(aPackedParams)), 297 mFlushMode(std::get<4>(aPackedParams)), 298 mCloseOnEOF(std::get<5>(aPackedParams)) {} 299 300 constexpr size_t DataSize() const { return mDataSize; } 301 302 size_t EffectiveWriteChunkSize() const { 303 return EffectiveChunkSize(mWriteChunkSize, mDataSize); 304 } 305 306 size_t EffectiveReadChunkSize() const { 307 return EffectiveChunkSize(mReadChunkSize, mDataSize); 308 } 309 310 constexpr size_t BlockSize() const { return mBlockSize; } 311 312 constexpr enum FlushMode FlushMode() const { return mFlushMode; } 313 314 constexpr bool CloseOnEOF() const { return mCloseOnEOF; } 315 316 private: 317 size_t mDataSize; 318 319 ChunkSize mWriteChunkSize; 320 ChunkSize mReadChunkSize; 321 322 size_t mBlockSize; 323 enum FlushMode mFlushMode; 324 bool mCloseOnEOF; 325 }; 326 327 std::string TestParamToString( 328 const testing::TestParamInfo<PackedTestParams>& aTestParams) { 329 const TestParams& testParams = aTestParams.param; 330 331 static constexpr char kSeparator[] = "_"; 332 333 std::stringstream ss; 334 ss << "data" << testParams.DataSize() << kSeparator << "writechunk" 335 << testParams.EffectiveWriteChunkSize() << kSeparator << "readchunk" 336 << testParams.EffectiveReadChunkSize() << kSeparator << "block" 337 << testParams.BlockSize() << kSeparator; 338 switch (testParams.FlushMode()) { 339 case FlushMode::Never: 340 ss << "FlushNever"; 341 break; 342 case FlushMode::AfterEachChunk: 343 ss << "FlushAfterEachChunk"; 344 break; 345 }; 346 ss << kSeparator 347 << (testParams.CloseOnEOF() ? "closeOnEOF" : "keepOpenOnEOF"); 348 return ss.str(); 349 } 350 351 class ParametrizedCryptTest 352 : public DOM_Quota_EncryptedStream, 353 public testing::WithParamInterface<PackedTestParams> {}; 354 355 static auto MakeTestData(const size_t aDataSize) { 356 auto data = nsTArray<uint8_t>(); 357 data.SetLength(aDataSize); 358 std::iota(data.begin(), data.end(), 0); 359 return data; 360 } 361 362 template <typename CipherStrategy> 363 static void WriteTestData(nsCOMPtr<nsIOutputStream>&& aBaseOutputStream, 364 const Span<const uint8_t> aData, 365 const size_t aWriteChunkSize, const size_t aBlockSize, 366 const typename CipherStrategy::KeyType& aKey, 367 const FlushMode aFlushMode) { 368 auto outStream = MakeSafeRefPtr<EncryptingOutputStream<CipherStrategy>>( 369 std::move(aBaseOutputStream), aBlockSize, aKey); 370 371 for (auto remaining = aData; !remaining.IsEmpty();) { 372 auto [currentChunk, newRemaining] = 373 remaining.SplitAt(std::min(aWriteChunkSize, remaining.Length())); 374 remaining = newRemaining; 375 376 uint32_t written; 377 EXPECT_EQ(NS_OK, outStream->Write( 378 reinterpret_cast<const char*>(currentChunk.Elements()), 379 currentChunk.Length(), &written)); 380 EXPECT_EQ(currentChunk.Length(), written); 381 382 if (aFlushMode == FlushMode::AfterEachChunk) { 383 outStream->Flush(); 384 } 385 } 386 387 // Close explicitly so we can check the result. 388 EXPECT_EQ(NS_OK, outStream->Close()); 389 } 390 391 template <typename CipherStrategy> 392 static void NoExtraChecks(DecryptingInputStream<CipherStrategy>& aInputStream, 393 Span<const uint8_t> aExpectedData, 394 Span<const uint8_t> aRemainder) {} 395 396 template <typename CipherStrategy, 397 typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)> 398 static void ReadTestData( 399 DecryptingInputStream<CipherStrategy>& aDecryptingInputStream, 400 const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize, 401 const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) { 402 auto readData = nsTArray<uint8_t>(); 403 readData.SetLength(aReadChunkSize); 404 405 // sanity check: total file length and expectedData length must always match 406 uint64_t availableBytes = 0; 407 EXPECT_EQ(NS_OK, aDecryptingInputStream.Available(&availableBytes)); 408 EXPECT_EQ(aExpectedData.LengthBytes(), availableBytes); 409 410 for (auto remainder = aExpectedData; !remainder.IsEmpty();) { 411 auto [currentExpected, newExpectedRemainder] = 412 remainder.SplitAt(std::min(aReadChunkSize, remainder.Length())); 413 remainder = newExpectedRemainder; 414 415 uint32_t read; 416 EXPECT_EQ(NS_OK, aDecryptingInputStream.Read( 417 reinterpret_cast<char*>(readData.Elements()), 418 currentExpected.Length(), &read)); 419 EXPECT_EQ(currentExpected.Length(), read); 420 EXPECT_EQ(currentExpected, 421 Span{readData}.First(currentExpected.Length()).AsConst()); 422 423 aExtraChecks(aDecryptingInputStream, aExpectedData, remainder); 424 } 425 426 // Expect EOF. 427 uint32_t read; 428 EXPECT_EQ(NS_OK, aDecryptingInputStream.Read( 429 reinterpret_cast<char*>(readData.Elements()), 430 readData.Length(), &read)); 431 EXPECT_EQ(0u, read); 432 } 433 434 template <typename CipherStrategy, 435 typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)> 436 static auto ReadTestData( 437 MovingNotNull<nsCOMPtr<nsIInputStream>>&& aBaseInputStream, 438 const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize, 439 const size_t aBlockSize, const typename CipherStrategy::KeyType& aKey, 440 const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) { 441 auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>( 442 std::move(aBaseInputStream), aBlockSize, aKey); 443 444 ReadTestData(*inStream, aExpectedData, aReadChunkSize, aExtraChecks); 445 446 return inStream; 447 } 448 449 // XXX Change to return the buffer instead. 450 template <typename CipherStrategy, 451 typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)> 452 static RefPtr<FixedBufferOutputStream> DoRoundtripTest( 453 const size_t aDataSize, const size_t aWriteChunkSize, 454 const size_t aReadChunkSize, const size_t aBlockSize, 455 const typename CipherStrategy::KeyType& aKey, const FlushMode aFlushMode, 456 bool aCloseOnEOF, 457 const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) { 458 // XXX Add deduction guide for RefPtr from already_AddRefed 459 const auto baseOutputStream = WrapNotNull( 460 RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)}); 461 462 const auto data = MakeTestData(aDataSize); 463 464 WriteTestData<CipherStrategy>( 465 nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data}, 466 aWriteChunkSize, aBlockSize, aKey, aFlushMode); 467 468 const auto baseInputStream = 469 MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData()); 470 471 baseInputStream->SetCloseOnEOF(aCloseOnEOF); 472 473 ReadTestData<CipherStrategy>( 474 WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data}, 475 aReadChunkSize, aBlockSize, aKey, aExtraChecks); 476 477 return baseOutputStream; 478 } 479 480 TEST_P(ParametrizedCryptTest, NSSCipherStrategy) { 481 using CipherStrategy = NSSCipherStrategy; 482 const TestParams& testParams = GetParam(); 483 484 auto keyOrErr = CipherStrategy::GenerateKey(); 485 ASSERT_FALSE(keyOrErr.isErr()); 486 487 DoRoundtripTest<CipherStrategy>( 488 testParams.DataSize(), testParams.EffectiveWriteChunkSize(), 489 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 490 keyOrErr.unwrap(), testParams.FlushMode(), testParams.CloseOnEOF()); 491 } 492 493 TEST_P(ParametrizedCryptTest, NSSCipherStrategy_Available) { 494 using CipherStrategy = NSSCipherStrategy; 495 const TestParams& testParams = GetParam(); 496 497 DoRoundtripTest<CipherStrategy>( 498 testParams.DataSize(), testParams.EffectiveWriteChunkSize(), 499 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 500 CipherStrategy::KeyType{}, testParams.FlushMode(), 501 testParams.CloseOnEOF(), 502 [](auto& inStream, Span<const uint8_t> expectedData, 503 Span<const uint8_t> remainder) { 504 // Check that Available tells the right remainder. 505 uint64_t available; 506 EXPECT_EQ(NS_OK, inStream.Available(&available)); 507 EXPECT_EQ(remainder.Length(), available); 508 }); 509 } 510 511 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) { 512 using CipherStrategy = DummyCipherStrategy; 513 const TestParams& testParams = GetParam(); 514 515 const auto encryptedDataStream = DoRoundtripTest<CipherStrategy>( 516 testParams.DataSize(), testParams.EffectiveWriteChunkSize(), 517 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 518 CipherStrategy::KeyType{}, testParams.FlushMode(), 519 testParams.CloseOnEOF()); 520 521 if (HasFailure()) { 522 return; 523 } 524 525 const auto encryptedData = encryptedDataStream->WrittenData(); 526 const auto encryptedDataSpan = AsBytes(Span(encryptedData)); 527 528 const auto plainTestData = MakeTestData(testParams.DataSize()); 529 auto encryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength, 530 DummyCipherStrategy::BasicBlockSize>{ 531 testParams.BlockSize(), 532 }; 533 for (auto [encryptedRemainder, plainRemainder] = 534 std::pair(encryptedDataSpan, Span(plainTestData)); 535 !encryptedRemainder.IsEmpty();) { 536 const auto [currentBlock, newEncryptedRemainder] = 537 encryptedRemainder.SplitAt(testParams.BlockSize()); 538 encryptedRemainder = newEncryptedRemainder; 539 540 std::copy(currentBlock.cbegin(), currentBlock.cend(), 541 encryptedBlock.MutableWholeBlock().begin()); 542 543 ASSERT_FALSE(plainRemainder.IsEmpty()); 544 const auto [currentPlain, newPlainRemainder] = 545 plainRemainder.SplitAt(encryptedBlock.ActualPayloadLength()); 546 plainRemainder = newPlainRemainder; 547 548 const auto pseudoIV = encryptedBlock.CipherPrefix(); 549 const auto payload = encryptedBlock.Payload(); 550 551 EXPECT_EQ(Span(DummyCipherStrategy::MakeBlockPrefix()), pseudoIV); 552 553 auto untransformedPayload = nsTArray<uint8_t>(); 554 untransformedPayload.SetLength(testParams.BlockSize()); 555 DummyCipherStrategy::DummyTransform(payload, untransformedPayload); 556 557 EXPECT_EQ( 558 currentPlain, 559 Span(untransformedPayload).AsConst().First(currentPlain.Length())); 560 } 561 } 562 563 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Tell) { 564 using CipherStrategy = DummyCipherStrategy; 565 const TestParams& testParams = GetParam(); 566 567 DoRoundtripTest<CipherStrategy>( 568 testParams.DataSize(), testParams.EffectiveWriteChunkSize(), 569 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 570 CipherStrategy::KeyType{}, testParams.FlushMode(), 571 testParams.CloseOnEOF(), 572 [](auto& inStream, Span<const uint8_t> expectedData, 573 Span<const uint8_t> remainder) { 574 // Check that Tell tells the right position. 575 int64_t pos; 576 EXPECT_EQ(NS_OK, inStream.Tell(&pos)); 577 EXPECT_EQ(expectedData.Length() - remainder.Length(), 578 static_cast<uint64_t>(pos)); 579 }); 580 } 581 582 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Available) { 583 using CipherStrategy = DummyCipherStrategy; 584 const TestParams& testParams = GetParam(); 585 586 DoRoundtripTest<CipherStrategy>( 587 testParams.DataSize(), testParams.EffectiveWriteChunkSize(), 588 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 589 CipherStrategy::KeyType{}, testParams.FlushMode(), 590 testParams.CloseOnEOF(), 591 [](auto& inStream, Span<const uint8_t> expectedData, 592 Span<const uint8_t> remainder) { 593 // Check that Available tells the right remainder. 594 uint64_t available; 595 EXPECT_EQ(NS_OK, inStream.Available(&available)); 596 // stream should still be valid. 597 EXPECT_EQ(NS_OK, inStream.BaseStreamStatus()); 598 EXPECT_EQ(remainder.Length(), available); 599 }); 600 } 601 602 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Clone) { 603 using CipherStrategy = DummyCipherStrategy; 604 const TestParams& testParams = GetParam(); 605 606 // XXX Add deduction guide for RefPtr from already_AddRefed 607 const auto baseOutputStream = WrapNotNull( 608 RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)}); 609 610 const auto data = MakeTestData(testParams.DataSize()); 611 612 WriteTestData<CipherStrategy>( 613 nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data}, 614 testParams.EffectiveWriteChunkSize(), testParams.BlockSize(), 615 CipherStrategy::KeyType{}, testParams.FlushMode()); 616 617 const auto baseInputStream = 618 MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData()); 619 620 const auto inStream = ReadTestData<CipherStrategy>( 621 WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data}, 622 testParams.EffectiveReadChunkSize(), testParams.BlockSize(), 623 CipherStrategy::KeyType{}); 624 625 nsCOMPtr<nsIInputStream> clonedInputStream; 626 EXPECT_EQ(NS_OK, inStream->Clone(getter_AddRefs(clonedInputStream))); 627 628 ReadTestData( 629 static_cast<DecryptingInputStream<CipherStrategy>&>(*clonedInputStream), 630 Span{data}, testParams.EffectiveReadChunkSize()); 631 } 632 633 // XXX This test is actually only parametrized on the block size. 634 TEST_P(ParametrizedCryptTest, DummyCipherStrategy_IncompleteBlock) { 635 using CipherStrategy = DummyCipherStrategy; 636 const TestParams& testParams = GetParam(); 637 638 // Provide half a block, content doesn't matter. 639 nsTArray<uint8_t> data; 640 data.SetLength(testParams.BlockSize() / 2); 641 642 const auto baseInputStream = MakeRefPtr<ArrayBufferInputStream>(data); 643 644 const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>( 645 WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), 646 testParams.BlockSize(), CipherStrategy::KeyType{}); 647 648 nsTArray<uint8_t> readData; 649 readData.SetLength(testParams.BlockSize()); 650 uint32_t read; 651 EXPECT_EQ(NS_ERROR_CORRUPTED_CONTENT, 652 inStream->Read(reinterpret_cast<char*>(readData.Elements()), 653 readData.Length(), &read)); 654 } 655 656 TEST_P(ParametrizedCryptTest, zeroInitializedEncryptedBlock) { 657 const TestParams& testParams = GetParam(); 658 659 using EncryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength, 660 DummyCipherStrategy::BasicBlockSize>; 661 662 EncryptedBlock encryptedBlock{testParams.BlockSize()}; 663 auto firstBlock = 664 encryptedBlock.WholeBlock().First<DummyCipherStrategy::BasicBlockSize>(); 665 auto unusedBytesInFirstBlock = firstBlock.from(sizeof(uint16_t)); 666 667 EXPECT_TRUE(std::all_of(unusedBytesInFirstBlock.begin(), 668 unusedBytesInFirstBlock.end(), 669 [](const auto& e) { return 0ul == e; })); 670 } 671 672 enum struct SeekOffset { 673 Zero, 674 MinusHalfDataSize, 675 PlusHalfDataSize, 676 PlusDataSize, 677 MinusDataSize, 678 MinusDataSizeAndOne, 679 PlusOne, 680 MinusOne 681 }; 682 using SeekOp = std::tuple<int32_t, SeekOffset, nsresult>; 683 684 using PackedSeekTestParams = 685 std::tuple<size_t, size_t, std::vector<SeekOp>, bool>; 686 687 struct SeekTestParams { 688 size_t mDataSize; 689 size_t mBlockSize; 690 std::vector<SeekOp> mSeekOps; 691 bool mCloseOnEOF; 692 693 MOZ_IMPLICIT SeekTestParams(const PackedSeekTestParams& aPackedParams) 694 : mDataSize(std::get<0>(aPackedParams)), 695 mBlockSize(std::get<1>(aPackedParams)), 696 mSeekOps(std::get<2>(aPackedParams)), 697 mCloseOnEOF(std::get<3>(aPackedParams)) {} 698 }; 699 700 std::string SeekTestParamToString( 701 const testing::TestParamInfo<PackedSeekTestParams>& aTestParams) { 702 const SeekTestParams& testParams = aTestParams.param; 703 704 static constexpr char kSeparator[] = "_"; 705 706 std::stringstream ss; 707 ss << "data" << testParams.mDataSize << kSeparator << "writechunk" 708 << testParams.mBlockSize << kSeparator; 709 for (const auto& seekOp : testParams.mSeekOps) { 710 switch (std::get<0>(seekOp)) { 711 case nsISeekableStream::NS_SEEK_SET: 712 ss << "Set"; 713 break; 714 case nsISeekableStream::NS_SEEK_CUR: 715 ss << "Cur"; 716 break; 717 case nsISeekableStream::NS_SEEK_END: 718 ss << "End"; 719 break; 720 default: 721 MOZ_CRASH("Unknown whence"); 722 }; 723 switch (std::get<1>(seekOp)) { 724 case SeekOffset::Zero: 725 ss << "Zero"; 726 break; 727 case SeekOffset::MinusHalfDataSize: 728 ss << "MinusHalfDataSize"; 729 break; 730 case SeekOffset::PlusHalfDataSize: 731 ss << "PlusHalfDataSize"; 732 break; 733 case SeekOffset::MinusDataSize: 734 ss << "MinusDataSize"; 735 break; 736 case SeekOffset::MinusDataSizeAndOne: 737 ss << "MinusDataSizeAndOne"; 738 break; 739 case SeekOffset::PlusDataSize: 740 ss << "PlusDataSize"; 741 break; 742 case SeekOffset::PlusOne: 743 ss << "PlusOne"; 744 break; 745 case SeekOffset::MinusOne: 746 ss << "MinusOne"; 747 break; 748 }; 749 } 750 ss << kSeparator << (testParams.mCloseOnEOF ? "closeOnEOF" : "keepOpenOnEOF"); 751 752 return ss.str(); 753 } 754 755 class ParametrizedSeekCryptTest 756 : public DOM_Quota_EncryptedStream, 757 public testing::WithParamInterface<PackedSeekTestParams> { 758 public: 759 template <typename CipherStrategy> 760 void DoSeekTest() { 761 const SeekTestParams& testParams = GetParam(); 762 763 const auto baseOutputStream = WrapNotNull( 764 RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)}); 765 766 const auto data = MakeTestData(testParams.mDataSize); 767 768 WriteTestData<CipherStrategy>( 769 nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data}, 770 testParams.mDataSize, testParams.mBlockSize, 771 typename CipherStrategy::KeyType{}, FlushMode::Never); 772 773 const auto baseInputStream = 774 MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData()); 775 776 const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>( 777 WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), 778 testParams.mBlockSize, typename CipherStrategy::KeyType{}); 779 780 baseInputStream->SetCloseOnEOF(testParams.mCloseOnEOF); 781 782 uint32_t accumulatedOffset = 0; 783 for (const auto& seekOp : testParams.mSeekOps) { 784 const auto offset = [offsetKind = std::get<1>(seekOp), 785 dataSize = testParams.mDataSize]() -> int64_t { 786 switch (offsetKind) { 787 case SeekOffset::Zero: 788 return 0; 789 case SeekOffset::MinusHalfDataSize: 790 return -static_cast<int64_t>(dataSize) / 2; 791 case SeekOffset::PlusHalfDataSize: 792 return static_cast<int64_t>(dataSize) / 2; 793 case SeekOffset::MinusDataSize: 794 return -static_cast<int64_t>(dataSize); 795 case SeekOffset::MinusDataSizeAndOne: 796 return -static_cast<int64_t>(dataSize + 1); 797 case SeekOffset::PlusDataSize: 798 return static_cast<int64_t>(dataSize); 799 case SeekOffset::PlusOne: 800 return 1; 801 case SeekOffset::MinusOne: 802 return -1; 803 } 804 MOZ_CRASH("Unknown SeekOffset"); 805 }(); 806 nsresult rv = inStream->Seek(std::get<0>(seekOp), offset); 807 EXPECT_EQ(std::get<2>(seekOp), rv); 808 if (NS_SUCCEEDED(rv)) { 809 switch (std::get<0>(seekOp)) { 810 case nsISeekableStream::NS_SEEK_SET: 811 accumulatedOffset = offset; 812 break; 813 case nsISeekableStream::NS_SEEK_CUR: 814 accumulatedOffset += offset; 815 break; 816 case nsISeekableStream::NS_SEEK_END: 817 accumulatedOffset = testParams.mDataSize + offset; 818 break; 819 default: 820 MOZ_CRASH("Unknown whence"); 821 } 822 } 823 } 824 825 { 826 int64_t actualOffset; 827 EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset)); 828 829 EXPECT_EQ(actualOffset, accumulatedOffset); 830 } 831 832 auto readData = nsTArray<uint8_t>(); 833 readData.SetLength(data.Length()); 834 uint32_t read; 835 EXPECT_EQ(NS_OK, 836 inStream->Read(reinterpret_cast<char*>(readData.Elements()), 837 readData.Length(), &read)); 838 // XXX Or should 'read' indicate the actual number of bytes read, 839 // including the encryption overhead? 840 EXPECT_EQ(testParams.mDataSize - accumulatedOffset, read); 841 EXPECT_EQ(Span{data}.SplitAt(accumulatedOffset).second, 842 Span{readData}.First(read).AsConst()); 843 844 // For some closeOnEOF combinations, above Read method can lead to stream 845 // closure. Skip calling Tell method below if the underlying stream was 846 // already closed. 847 if (!testParams.mCloseOnEOF || 848 baseInputStream->StreamStatus() != NS_BASE_STREAM_CLOSED) { 849 int64_t actualOffset; 850 EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset)); 851 852 EXPECT_EQ(static_cast<uint64_t>(actualOffset), data.Length()); 853 } 854 } 855 }; 856 857 TEST_P(ParametrizedSeekCryptTest, DummyCipherStrategy_Seek) { 858 DoSeekTest<DummyCipherStrategy>(); 859 } 860 861 TEST_P(ParametrizedSeekCryptTest, NSSCipherStrategy_Seek) { 862 DoSeekTest<NSSCipherStrategy>(); 863 } 864 865 // The data size 244 has been calculated as 256 (block size) minus 8 866 // (DummyCipherStrategy::BlockPrefixLength) minus 4 867 // (DummyCipherStrategy::BasicBlockSize). 868 // The data size 1012 has been calculated as 1024 (block size) minus 8 869 // (DummyCipherStrategy::BlockPrefixLength) minus 4 870 // (DummyCipherStrategy::BasicBlockSize). 871 static_assert(DummyCipherStrategy::BlockPrefixLength == 8); 872 static_assert(DummyCipherStrategy::BasicBlockSize == 4); 873 874 // The data size 208 has been calculated as 256 (block size) minus 32 875 // (NSSCipherStrategy::BlockPrefixLength) minus 16 876 // (NSSCipherStrategy::BasicBlockSize). 877 // The data size 976 has been calculated as 1024 (block size) minus 32 878 // (NSSCipherStrategy::BlockPrefixLength) minus 16 879 // (NSSCipherStrategy::BasicBlockSize). 880 static_assert(NSSCipherStrategy::BlockPrefixLength == 32); 881 static_assert(NSSCipherStrategy::BasicBlockSize == 16); 882 883 INSTANTIATE_TEST_SUITE_P( 884 DOM_Quota_EncryptedStream_Parametrized, ParametrizedCryptTest, 885 testing::Combine( 886 /* dataSize */ testing::Values(0u, 16u, 208u, 244u, 256u, 512u, 513u, 887 976u, 1012u), 888 /* writeChunkSize */ 889 testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned, 890 ChunkSize::DataSize), 891 /* readChunkSize */ 892 testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned, 893 ChunkSize::DataSize), 894 /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/), 895 /* flushMode */ 896 testing::Values(FlushMode::Never, FlushMode::AfterEachChunk), 897 /* closeOnEOF */ 898 testing::Values(true, false)), 899 TestParamToString); 900 901 INSTANTIATE_TEST_SUITE_P( 902 DOM_IndexedDB_EncryptedStream_ParametrizedSeek, ParametrizedSeekCryptTest, 903 testing::Combine( 904 /* dataSize */ testing::Values(0u, 16u, 208u, 244u, 256u, 512u, 513u, 905 976u, 1012u), 906 /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/), 907 /* seekOperations */ 908 testing::Values(/* NS_SEEK_SET only, single ops */ 909 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET, 910 SeekOffset::PlusDataSize, NS_OK}}, 911 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET, 912 SeekOffset::PlusHalfDataSize, 913 NS_OK}}, 914 /* NS_SEEK_SET only, multiple ops */ 915 std::vector<SeekOp>{ 916 {nsISeekableStream::NS_SEEK_SET, 917 SeekOffset::PlusHalfDataSize, NS_OK}, 918 {nsISeekableStream::NS_SEEK_SET, SeekOffset::Zero, 919 NS_OK}}, 920 /* NS_SEEK_CUR only, single ops */ 921 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR, 922 SeekOffset::Zero, NS_OK}}, 923 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR, 924 SeekOffset::PlusDataSize, NS_OK}}, 925 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR, 926 SeekOffset::PlusHalfDataSize, 927 NS_OK}}, 928 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR, 929 SeekOffset::MinusOne, 930 NS_ERROR_ILLEGAL_VALUE}}, 931 /* NS_SEEK_END only, single ops */ 932 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END, 933 SeekOffset::Zero, NS_OK}}, 934 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END, 935 SeekOffset::MinusDataSize, NS_OK}}, 936 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END, 937 SeekOffset::MinusDataSizeAndOne, 938 NS_ERROR_ILLEGAL_VALUE}}, 939 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END, 940 SeekOffset::MinusHalfDataSize, 941 NS_OK}}, 942 std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END, 943 SeekOffset::PlusOne, 944 NS_ERROR_ILLEGAL_VALUE}}), 945 /* closeOnEOF */ 946 testing::Values(true, false)), 947 SeekTestParamToString);