DecryptingInputStream_impl.h (15916B)
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 #ifndef mozilla_dom_quota_DecryptingInputStream_impl_h 8 #define mozilla_dom_quota_DecryptingInputStream_impl_h 9 10 #include <algorithm> 11 #include <cstdio> 12 #include <utility> 13 14 #include "CipherStrategy.h" 15 #include "DecryptingInputStream.h" 16 #include "mozilla/Assertions.h" 17 #include "mozilla/RefPtr.h" 18 #include "mozilla/Result.h" 19 #include "mozilla/Span.h" 20 #include "mozilla/fallible.h" 21 #include "mozilla/ipc/InputStreamUtils.h" 22 #include "nsDebug.h" 23 #include "nsError.h" 24 #include "nsFileStreams.h" 25 #include "nsID.h" 26 #include "nsIFileStreams.h" 27 28 namespace mozilla::dom::quota { 29 30 template <typename CipherStrategy> 31 DecryptingInputStream<CipherStrategy>::DecryptingInputStream( 32 MovingNotNull<nsCOMPtr<nsIInputStream>> aBaseStream, size_t aBlockSize, 33 typename CipherStrategy::KeyType aKey) 34 : DecryptingInputStreamBase(std::move(aBaseStream), aBlockSize), 35 mKey(aKey) { 36 // XXX Move this to a fallible init function. 37 MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Decrypt, 38 CipherStrategy::SerializeKey(aKey))); 39 40 // We used to assert the underlying stream was blocking in DEBUG builds as a 41 // proxy for providing synchronous read access (we can't handle AsyncWait), 42 // but IsNonBlocking is a bad proxy for this since classes like 43 // nsStringInputStream provide sync read access, it just is known to never 44 // block because the data is always available. 45 } 46 47 template <typename CipherStrategy> 48 DecryptingInputStream<CipherStrategy>::~DecryptingInputStream() { 49 Close(); 50 } 51 52 template <typename CipherStrategy> 53 DecryptingInputStream<CipherStrategy>::DecryptingInputStream() 54 : DecryptingInputStreamBase{} {} 55 56 template <typename CipherStrategy> 57 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Close() { 58 if (!mBaseStream) { 59 return NS_OK; 60 } 61 62 (*mBaseStream)->Close(); 63 mBaseStream.destroy(); 64 65 mPlainBuffer.Clear(); 66 mEncryptedBlock.reset(); 67 68 return NS_OK; 69 } 70 71 template <typename CipherStrategy> 72 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Available( 73 uint64_t* aLengthOut) { 74 if (!mBaseStream) { 75 return NS_BASE_STREAM_CLOSED; 76 } 77 78 int64_t oldPos, endPos; 79 nsresult rv = Tell(&oldPos); 80 if (NS_WARN_IF(NS_FAILED(rv))) { 81 return rv; 82 } 83 84 rv = Seek(SEEK_END, 0); 85 if (NS_WARN_IF(NS_FAILED(rv))) { 86 return rv; 87 } 88 89 rv = Tell(&endPos); 90 if (NS_WARN_IF(NS_FAILED(rv))) { 91 return rv; 92 } 93 94 rv = Seek(SEEK_SET, oldPos); 95 if (NS_WARN_IF(NS_FAILED(rv))) { 96 return rv; 97 } 98 99 *aLengthOut = endPos - oldPos; 100 return NS_OK; 101 } 102 103 template <typename CipherStrategy> 104 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::StreamStatus() { 105 return mBaseStream ? NS_OK : NS_BASE_STREAM_CLOSED; 106 } 107 108 template <typename CipherStrategy> 109 nsresult DecryptingInputStream<CipherStrategy>::BaseStreamStatus() { 110 return mBaseStream ? (*mBaseStream)->StreamStatus() : NS_BASE_STREAM_CLOSED; 111 } 112 113 template <typename CipherStrategy> 114 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::ReadSegments( 115 nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, 116 uint32_t* aBytesReadOut) { 117 *aBytesReadOut = 0; 118 119 if (!mBaseStream) { 120 return NS_BASE_STREAM_CLOSED; 121 } 122 123 nsresult rv; 124 125 // Do not try to use the base stream's ReadSegments here. Its very 126 // unlikely we will get a single buffer that contains all of the encrypted 127 // data and therefore would have to copy into our own buffer anyways. 128 // Instead, focus on making efficient use of the Read() interface. 129 130 while (aCount > 0) { 131 // We have some decrypted data in our buffer. Provide it to the callers 132 // writer function. 133 if (mNextByte < mPlainBytes) { 134 MOZ_ASSERT(!mPlainBuffer.IsEmpty()); 135 uint32_t remaining = PlainLength(); 136 uint32_t numToWrite = std::min(aCount, remaining); 137 uint32_t numWritten; 138 rv = aWriter(this, aClosure, 139 reinterpret_cast<const char*>(&mPlainBuffer[mNextByte]), 140 *aBytesReadOut, numToWrite, &numWritten); 141 142 // As defined in nsIInputputStream.idl, do not pass writer func errors. 143 if (NS_FAILED(rv)) { 144 return NS_OK; 145 } 146 147 // End-of-file 148 if (numWritten == 0) { 149 return NS_OK; 150 } 151 152 *aBytesReadOut += numWritten; 153 mNextByte += numWritten; 154 MOZ_ASSERT(mNextByte <= mPlainBytes); 155 156 aCount -= numWritten; 157 158 continue; 159 } 160 161 // Otherwise decrypt the next chunk and loop. Any resulting data will set 162 // mPlainBytes and mNextByte which we check at the top of the loop. 163 uint32_t bytesRead; 164 rv = ParseNextChunk(false /* aCheckAvailableBytes */, &bytesRead); 165 if (NS_FAILED(rv)) { 166 return rv; 167 } 168 169 // If we couldn't read anything, then this is eof. 170 if (bytesRead == 0) { 171 return NS_OK; 172 } 173 174 mPlainBytes = bytesRead; 175 mNextByte = 0; 176 } 177 178 return NS_OK; 179 } 180 181 template <typename CipherStrategy> 182 nsresult DecryptingInputStream<CipherStrategy>::ParseNextChunk( 183 bool aCheckAvailableBytes, uint32_t* const aBytesReadOut) { 184 *aBytesReadOut = 0; 185 186 if (!EnsureBuffers()) { 187 return NS_ERROR_OUT_OF_MEMORY; 188 } 189 190 // Read the data to our internal encrypted buffer. 191 auto wholeBlock = mEncryptedBlock->MutableWholeBlock(); 192 nsresult rv = 193 ReadAll(AsWritableChars(wholeBlock).Elements(), wholeBlock.Length(), 194 wholeBlock.Length(), aCheckAvailableBytes, aBytesReadOut); 195 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { 196 return rv; 197 } 198 199 // XXX Do we need to know the actual decrypted size? 200 rv = mCipherStrategy.Cipher(mEncryptedBlock->MutableCipherPrefix(), 201 mEncryptedBlock->Payload(), 202 AsWritableBytes(Span{mPlainBuffer})); 203 if (NS_WARN_IF(NS_FAILED(rv))) { 204 return rv; 205 } 206 207 *aBytesReadOut = mEncryptedBlock->ActualPayloadLength(); 208 209 return NS_OK; 210 } 211 212 template <typename CipherStrategy> 213 nsresult DecryptingInputStream<CipherStrategy>::ReadAll( 214 char* aBuf, uint32_t aCount, uint32_t aMinValidCount, 215 bool aCheckAvailableBytes, uint32_t* aBytesReadOut) { 216 MOZ_ASSERT(aCount >= aMinValidCount); 217 MOZ_ASSERT(mBaseStream); 218 219 nsresult rv = NS_OK; 220 *aBytesReadOut = 0; 221 222 uint32_t offset = 0; 223 while (aCount > 0) { 224 Maybe<uint64_t> availableBytes; 225 if (aCheckAvailableBytes) { 226 uint64_t available; 227 rv = (*mBaseStream)->Available(&available); 228 if (NS_WARN_IF(NS_FAILED(rv))) { 229 if (rv == NS_BASE_STREAM_CLOSED) { 230 rv = NS_OK; 231 } 232 break; 233 } 234 235 if (available == 0) { 236 break; 237 } 238 239 availableBytes = Some(available); 240 } 241 242 uint32_t bytesRead = 0; 243 rv = (*mBaseStream)->Read(aBuf + offset, aCount, &bytesRead); 244 if (NS_WARN_IF(NS_FAILED(rv))) { 245 break; 246 } 247 248 // EOF, but don't immediately return. We need to validate min read bytes 249 // below. 250 if (bytesRead == 0) { 251 break; 252 } 253 254 MOZ_DIAGNOSTIC_ASSERT(!availableBytes || bytesRead <= *availableBytes); 255 256 *aBytesReadOut += bytesRead; 257 offset += bytesRead; 258 aCount -= bytesRead; 259 } 260 261 // Reading zero bytes is not an error. Its the expected EOF condition. 262 // Only compare to the minimum valid count if we read at least one byte. 263 if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) { 264 return NS_ERROR_CORRUPTED_CONTENT; 265 } 266 267 return rv; 268 } 269 270 template <typename CipherStrategy> 271 bool DecryptingInputStream<CipherStrategy>::EnsureBuffers() { 272 // Lazily create our two buffers so we can report OOM during stream 273 // operation. These allocations only happens once. The buffers are reused 274 // until the stream is closed. 275 if (!mEncryptedBlock) { 276 // XXX Do we need to do this fallible (as the comment above suggests)? 277 mEncryptedBlock.emplace(*mBlockSize); 278 279 MOZ_ASSERT(mPlainBuffer.IsEmpty()); 280 if (NS_WARN_IF(!mPlainBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(), 281 fallible))) { 282 return false; 283 } 284 285 // Make sure we seek our stream to its start before we do anything. This is 286 // primarily intended to deal with the case of IPC serialization, but this 287 // is reasonable in all cases. 288 (*mBaseSeekableStream)->Seek(NS_SEEK_SET, 0); 289 } 290 291 return true; 292 } 293 294 template <typename CipherStrategy> 295 nsresult DecryptingInputStream<CipherStrategy>::EnsureDecryptedStreamSize() { 296 if (mDecryptedStreamSize) { 297 return NS_OK; 298 } 299 300 auto decryptedStreamSizeOrErr = [this]() -> Result<int64_t, nsresult> { 301 nsresult rv = (*mBaseSeekableStream)->Seek(NS_SEEK_SET, 0); 302 if (NS_WARN_IF(NS_FAILED(rv))) { 303 return Err(rv); 304 } 305 306 uint64_t baseStreamSize; 307 rv = (*mBaseStream)->Available(&baseStreamSize); 308 if (NS_WARN_IF(NS_FAILED(rv))) { 309 return Err(rv); 310 } 311 312 if (!baseStreamSize) { 313 return 0; 314 } 315 316 rv = (*mBaseSeekableStream) 317 ->Seek(NS_SEEK_END, -static_cast<int64_t>(*mBlockSize)); 318 if (NS_WARN_IF(NS_FAILED(rv))) { 319 return Err(rv); 320 } 321 322 uint32_t bytesRead; 323 rv = ParseNextChunk(true /* aCheckAvailableBytes */, &bytesRead); 324 if (NS_WARN_IF(NS_FAILED(rv))) { 325 return Err(rv); 326 } 327 MOZ_ASSERT(bytesRead); 328 329 mPlainBytes = bytesRead; 330 331 mNextByte = bytesRead; 332 333 int64_t current; 334 rv = Tell(¤t); 335 if (NS_WARN_IF(NS_FAILED(rv))) { 336 return Err(rv); 337 } 338 339 return current; 340 }(); 341 342 if (decryptedStreamSizeOrErr.isErr()) { 343 return decryptedStreamSizeOrErr.unwrapErr(); 344 } 345 346 mDecryptedStreamSize.init(decryptedStreamSizeOrErr.inspect()); 347 348 return NS_OK; 349 } 350 351 template <typename CipherStrategy> 352 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Tell( 353 int64_t* const aRetval) { 354 MOZ_ASSERT(aRetval); 355 356 if (!mBaseStream) { 357 return NS_BASE_STREAM_CLOSED; 358 } 359 360 if (!EnsureBuffers()) { 361 return NS_ERROR_OUT_OF_MEMORY; 362 } 363 364 int64_t basePosition; 365 nsresult rv = (*mBaseSeekableStream)->Tell(&basePosition); 366 if (NS_WARN_IF(NS_FAILED(rv))) { 367 return rv; 368 } 369 370 if (basePosition == 0) { 371 *aRetval = 0; 372 return NS_OK; 373 } 374 375 MOZ_ASSERT(0 == basePosition % *mBlockSize); 376 377 const auto fullBlocks = basePosition / *mBlockSize; 378 MOZ_ASSERT(fullBlocks); 379 380 *aRetval = (fullBlocks - 1) * mEncryptedBlock->MaxPayloadLength() + mNextByte; 381 return NS_OK; 382 } 383 384 template <typename CipherStrategy> 385 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Seek(const int32_t aWhence, 386 int64_t aOffset) { 387 if (!mBaseStream) { 388 return NS_BASE_STREAM_CLOSED; 389 } 390 391 if (!EnsureBuffers()) { 392 return NS_ERROR_OUT_OF_MEMORY; 393 } 394 395 int64_t baseCurrent; 396 nsresult rv = (*mBaseSeekableStream)->Tell(&baseCurrent); 397 if (rv == NS_BASE_STREAM_CLOSED) { 398 // In the case our underlying stream is CLOSE_ON_EOF and REOPEN_ON_REWIND, 399 // as is the case for IDB Files/Blobs in the parent process, then this call 400 // to Tell can fail with NS_BASE_STREAM_CLOSED. 401 // 402 // Requesting any seek in this condition will re-open the file if the flags 403 // are set, so try that (but will fail if they are not set). 404 rv = (*mBaseSeekableStream)->Seek(NS_SEEK_CUR, 0); 405 // If that succeeded, perform the tell call again. 406 if (NS_SUCCEEDED(rv)) { 407 rv = (*mBaseSeekableStream)->Tell(&baseCurrent); 408 } 409 } 410 if (NS_WARN_IF(NS_FAILED(rv))) { 411 return Err(rv); 412 } 413 414 // Can't call this just in NS_SEEK_CUR case, because ensuring the decrypted 415 // size below may change the current position. 416 int64_t current; 417 rv = Tell(¤t); 418 if (NS_WARN_IF(NS_FAILED(rv))) { 419 return rv; 420 } 421 422 // If there's a failure we need to restore any previous state. 423 auto autoRestorePreviousState = 424 MakeScopeExit([baseSeekableStream = *mBaseSeekableStream, 425 savedBaseCurrent = baseCurrent, 426 savedPlainBytes = mPlainBytes, savedNextByte = mNextByte, 427 &plainBytes = mPlainBytes, &nextByte = mNextByte] { 428 nsresult rv = baseSeekableStream->Seek(NS_SEEK_SET, savedBaseCurrent); 429 (void)NS_WARN_IF(NS_FAILED(rv)); 430 plainBytes = savedPlainBytes; 431 nextByte = savedNextByte; 432 }); 433 434 rv = EnsureDecryptedStreamSize(); 435 if (NS_WARN_IF(NS_FAILED(rv))) { 436 return rv; 437 } 438 439 int64_t baseBlocksOffset; 440 int64_t nextByteOffset; 441 switch (aWhence) { 442 case NS_SEEK_CUR: 443 // XXX Simplify this without using Tell. 444 aOffset += current; 445 break; 446 447 case NS_SEEK_SET: 448 break; 449 450 case NS_SEEK_END: 451 // XXX Simplify this without using Seek/Tell. 452 aOffset += *mDecryptedStreamSize; 453 break; 454 455 default: 456 return NS_ERROR_ILLEGAL_VALUE; 457 } 458 459 if (aOffset < 0 || aOffset > *mDecryptedStreamSize) { 460 return NS_ERROR_ILLEGAL_VALUE; 461 } 462 463 baseBlocksOffset = aOffset / mEncryptedBlock->MaxPayloadLength(); 464 nextByteOffset = aOffset % mEncryptedBlock->MaxPayloadLength(); 465 466 // XXX If we remain in the same block as before, we can skip this. 467 rv = 468 (*mBaseSeekableStream)->Seek(NS_SEEK_SET, baseBlocksOffset * *mBlockSize); 469 if (NS_WARN_IF(NS_FAILED(rv))) { 470 return rv; 471 } 472 473 uint32_t readBytes; 474 rv = ParseNextChunk(true /* aCheckAvailableBytes */, &readBytes); 475 if (NS_WARN_IF(NS_FAILED(rv))) { 476 return rv; 477 } 478 479 if (readBytes == 0 && baseBlocksOffset != 0) { 480 mPlainBytes = mEncryptedBlock->MaxPayloadLength(); 481 mNextByte = mEncryptedBlock->MaxPayloadLength(); 482 } else { 483 mPlainBytes = readBytes; 484 mNextByte = nextByteOffset; 485 } 486 487 autoRestorePreviousState.release(); 488 489 return NS_OK; 490 } 491 492 template <typename CipherStrategy> 493 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Clone( 494 nsIInputStream** _retval) { 495 if (!mBaseStream) { 496 return NS_BASE_STREAM_CLOSED; 497 } 498 499 if (!(*mBaseCloneableInputStream)->GetCloneable()) { 500 return NS_ERROR_FAILURE; 501 } 502 503 nsCOMPtr<nsIInputStream> clonedStream; 504 nsresult rv = 505 (*mBaseCloneableInputStream)->Clone(getter_AddRefs(clonedStream)); 506 if (NS_WARN_IF(NS_FAILED(rv))) { 507 return rv; 508 } 509 510 *_retval = MakeAndAddRef<DecryptingInputStream>( 511 WrapNotNull(std::move(clonedStream)), *mBlockSize, *mKey) 512 .take(); 513 514 return NS_OK; 515 } 516 517 template <typename CipherStrategy> 518 void DecryptingInputStream<CipherStrategy>::Serialize( 519 mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize, 520 uint32_t* aSizeUsed) { 521 MOZ_ASSERT(mBaseStream); 522 MOZ_ASSERT(mBaseIPCSerializableInputStream); 523 524 mozilla::ipc::EncryptedFileInputStreamParams encryptedFileInputStreamParams; 525 mozilla::ipc::InputStreamHelper::SerializeInputStream( 526 *mBaseStream, encryptedFileInputStreamParams.inputStreamParams(), 527 aMaxSize, aSizeUsed); 528 529 encryptedFileInputStreamParams.key().AppendElements( 530 mCipherStrategy.SerializeKey(*mKey)); 531 encryptedFileInputStreamParams.blockSize() = *mBlockSize; 532 533 aParams = std::move(encryptedFileInputStreamParams); 534 } 535 536 template <typename CipherStrategy> 537 bool DecryptingInputStream<CipherStrategy>::Deserialize( 538 const mozilla::ipc::InputStreamParams& aParams) { 539 const auto& params = aParams.get_EncryptedFileInputStreamParams(); 540 541 nsCOMPtr<nsIInputStream> stream = 542 mozilla::ipc::InputStreamHelper::DeserializeInputStream( 543 params.inputStreamParams()); 544 if (NS_WARN_IF(!stream)) { 545 return false; 546 } 547 548 Init(WrapNotNull<nsCOMPtr<nsIInputStream>>(std::move(stream)), 549 params.blockSize()); 550 551 auto key = mCipherStrategy.DeserializeKey(params.key()); 552 if (NS_WARN_IF(!key)) { 553 return false; 554 } 555 556 mKey.init(*key); 557 if (NS_WARN_IF( 558 NS_FAILED(mCipherStrategy.Init(CipherMode::Decrypt, params.key())))) { 559 return false; 560 } 561 562 return true; 563 } 564 565 } // namespace mozilla::dom::quota 566 567 #endif