tor-browser

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

EncryptingOutputStream_impl.h (8563B)


      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_EncryptingOutputStream_impl_h
      8 #define mozilla_dom_quota_EncryptingOutputStream_impl_h
      9 
     10 #include <algorithm>
     11 #include <utility>
     12 
     13 #include "CipherStrategy.h"
     14 #include "EncryptingOutputStream.h"
     15 #include "mozilla/Assertions.h"
     16 #include "mozilla/Span.h"
     17 #include "mozilla/fallible.h"
     18 #include "nsDebug.h"
     19 #include "nsError.h"
     20 #include "nsIAsyncOutputStream.h"
     21 #include "nsIRandomGenerator.h"
     22 #include "nsServiceManagerUtils.h"
     23 
     24 namespace mozilla::dom::quota {
     25 template <typename CipherStrategy>
     26 EncryptingOutputStream<CipherStrategy>::EncryptingOutputStream(
     27    nsCOMPtr<nsIOutputStream> aBaseStream, size_t aBlockSize,
     28    typename CipherStrategy::KeyType aKey)
     29    : EncryptingOutputStreamBase(std::move(aBaseStream), aBlockSize) {
     30  // XXX Move this to a fallible init function.
     31  MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Encrypt,
     32                                           CipherStrategy::SerializeKey(aKey),
     33                                           CipherStrategy::MakeBlockPrefix()));
     34 
     35  MOZ_ASSERT(mBlockSize > 0);
     36  MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0);
     37  static_assert(
     38      CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0);
     39 
     40  // This implementation only supports sync base streams.  Verify this in debug
     41  // builds.  Note, this is a bit complicated because the streams we support
     42  // advertise different capabilities:
     43  //  - nsFileOutputStream - blocking and sync
     44  //  - FixedBufferOutputStream - non-blocking and sync
     45  //  - nsPipeOutputStream - can be blocking, but provides async interface
     46 #ifdef DEBUG
     47  bool baseNonBlocking;
     48  nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
     49  MOZ_ASSERT(NS_SUCCEEDED(rv));
     50  if (baseNonBlocking) {
     51    nsCOMPtr<nsIAsyncOutputStream> async =
     52        do_QueryInterface((*mBaseStream).get());
     53    MOZ_ASSERT(!async);
     54  }
     55 #endif
     56 }
     57 
     58 template <typename CipherStrategy>
     59 EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() {
     60  Close();
     61 }
     62 
     63 template <typename CipherStrategy>
     64 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() {
     65  if (!mBaseStream) {
     66    return NS_OK;
     67  }
     68 
     69  // When closing, flush to the base stream unconditionally, i.e. even if the
     70  // buffer is not completely full.
     71  nsresult rv = FlushToBaseStream();
     72  if (NS_WARN_IF(NS_FAILED(rv))) {
     73    return rv;
     74  }
     75 
     76  // XXX Maybe this Flush call can be removed, since the base stream is closed
     77  // afterwards anyway.
     78  rv = (*mBaseStream)->Flush();
     79  if (NS_WARN_IF(NS_FAILED(rv))) {
     80    return rv;
     81  }
     82 
     83  // XXX What if closing the base stream failed? Fail this method, or at least
     84  // log a warning?
     85  (*mBaseStream)->Close();
     86  mBaseStream.destroy();
     87 
     88  mBuffer.Clear();
     89  mEncryptedBlock.reset();
     90 
     91  return NS_OK;
     92 }
     93 
     94 template <typename CipherStrategy>
     95 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() {
     96  if (!mBaseStream) {
     97    return NS_BASE_STREAM_CLOSED;
     98  }
     99 
    100  if (!EnsureBuffers()) {
    101    return NS_ERROR_OUT_OF_MEMORY;
    102  }
    103 
    104  // We cannot call FlushBaseStream() here if the buffer is not completely
    105  // full, we would write an incomplete page, which might be read sequentially,
    106  // but we want to support random accesses in DecryptingInputStream, which
    107  // would no longer be feasible.
    108  if (mNextByte && mNextByte == mEncryptedBlock->MaxPayloadLength()) {
    109    nsresult rv = FlushToBaseStream();
    110    if (NS_WARN_IF(NS_FAILED(rv))) {
    111      return rv;
    112    }
    113  }
    114 
    115  return (*mBaseStream)->Flush();
    116 }
    117 
    118 template <typename CipherStrategy>
    119 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::StreamStatus() {
    120  if (!mBaseStream) {
    121    return NS_BASE_STREAM_CLOSED;
    122  }
    123  return (*mBaseStream)->StreamStatus();
    124 }
    125 
    126 template <typename CipherStrategy>
    127 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::WriteSegments(
    128    nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
    129    uint32_t* aBytesWrittenOut) {
    130  *aBytesWrittenOut = 0;
    131 
    132  if (!mBaseStream) {
    133    return NS_BASE_STREAM_CLOSED;
    134  }
    135 
    136  if (!EnsureBuffers()) {
    137    return NS_ERROR_OUT_OF_MEMORY;
    138  }
    139 
    140  const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength();
    141 
    142  while (aCount > 0) {
    143    // Determine how much space is left in our flat, plain buffer.
    144    MOZ_ASSERT(mNextByte <= plainBufferSize);
    145    uint32_t remaining = plainBufferSize - mNextByte;
    146 
    147    // If it is full, then encrypt and flush the data to the base stream.
    148    if (remaining == 0) {
    149      nsresult rv = FlushToBaseStream();
    150      if (NS_WARN_IF(NS_FAILED(rv))) {
    151        return rv;
    152      }
    153 
    154      // Now the entire buffer should be available for copying.
    155      MOZ_ASSERT(!mNextByte);
    156      remaining = plainBufferSize;
    157    }
    158 
    159    uint32_t numToRead = std::min(remaining, aCount);
    160    uint32_t numRead = 0;
    161 
    162    nsresult rv =
    163        aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]),
    164                *aBytesWrittenOut, numToRead, &numRead);
    165 
    166    // As defined in nsIOutputStream.idl, do not pass reader func errors.
    167    if (NS_FAILED(rv)) {
    168      return NS_OK;
    169    }
    170 
    171    // End-of-file
    172    if (numRead == 0) {
    173      return NS_OK;
    174    }
    175 
    176    mNextByte += numRead;
    177    *aBytesWrittenOut += numRead;
    178    aCount -= numRead;
    179  }
    180 
    181  return NS_OK;
    182 }
    183 
    184 template <typename CipherStrategy>
    185 bool EncryptingOutputStream<CipherStrategy>::EnsureBuffers() {
    186  // Lazily create the encrypted buffer on our first flush.  This
    187  // allows us to report OOM during stream operation.  This buffer
    188  // will then get re-used until the stream is closed.
    189  if (!mEncryptedBlock) {
    190    // XXX Do we need to do this fallible (as the comment above suggests)?
    191    mEncryptedBlock.emplace(mBlockSize);
    192    MOZ_ASSERT(mBuffer.IsEmpty());
    193 
    194    if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
    195                                      fallible))) {
    196      return false;
    197    }
    198  }
    199 
    200  return true;
    201 }
    202 
    203 template <typename CipherStrategy>
    204 nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() {
    205  MOZ_ASSERT(mBaseStream);
    206 
    207  if (!mNextByte) {
    208    // Nothing to do.
    209    return NS_OK;
    210  }
    211 
    212  if (mNextByte < mEncryptedBlock->MaxPayloadLength()) {
    213    if (!mRandomGenerator) {
    214      mRandomGenerator =
    215          do_GetService("@mozilla.org/security/random-generator;1");
    216      if (NS_WARN_IF(!mRandomGenerator)) {
    217        return NS_ERROR_FAILURE;
    218      }
    219    }
    220 
    221    const auto payload = mEncryptedBlock->MutablePayload();
    222 
    223    const auto unusedPayload = payload.From(mNextByte);
    224 
    225    nsresult rv = mRandomGenerator->GenerateRandomBytesInto(
    226        unusedPayload.Elements(), unusedPayload.Length());
    227    if (NS_WARN_IF(NS_FAILED(rv))) {
    228      return rv;
    229    }
    230  }
    231 
    232  // XXX The compressing stream implementation this was based on wrote a stream
    233  // identifier, containing e.g. the block size. Should we do something like
    234  // that as well? At the moment, we don't need it, but maybe this were
    235  // convenient if we use this for persistent files in the future across version
    236  // updates, which might change such parameters.
    237 
    238  const auto iv = mCipherStrategy.MakeBlockPrefix();
    239  static_assert(iv.size() * sizeof(decltype(*iv.begin())) ==
    240                CipherStrategy::BlockPrefixLength);
    241  std::copy(iv.cbegin(), iv.cend(),
    242            mEncryptedBlock->MutableCipherPrefix().begin());
    243 
    244  // Encrypt the data to our internal encrypted buffer.
    245  // XXX Do we need to know the actual encrypted size?
    246  nsresult rv = mCipherStrategy.Cipher(
    247      mEncryptedBlock->MutableCipherPrefix(),
    248      mozilla::Span(reinterpret_cast<uint8_t*>(mBuffer.Elements()),
    249                    ((mNextByte + (CipherStrategy::BasicBlockSize - 1)) /
    250                     CipherStrategy::BasicBlockSize) *
    251                        CipherStrategy::BasicBlockSize),
    252      mEncryptedBlock->MutablePayload());
    253  if (NS_WARN_IF(NS_FAILED(rv))) {
    254    return rv;
    255  }
    256 
    257  mEncryptedBlock->SetActualPayloadLength(mNextByte);
    258 
    259  mNextByte = 0;
    260 
    261  // Write the encrypted buffer out to the base stream.
    262  uint32_t numWritten = 0;
    263  const auto& wholeBlock = mEncryptedBlock->WholeBlock();
    264  rv = WriteAll(AsChars(wholeBlock).Elements(), wholeBlock.Length(),
    265                &numWritten);
    266  if (NS_WARN_IF(NS_FAILED(rv))) {
    267    return rv;
    268  }
    269  MOZ_ASSERT(wholeBlock.Length() == numWritten);
    270 
    271  return NS_OK;
    272 }
    273 
    274 }  // namespace mozilla::dom::quota
    275 
    276 #endif