tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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(&current);
    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(&current);
    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