forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			237 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #ifndef mozilla_dom_quota_EncryptingOutputStream_impl_h
 | |
| #define mozilla_dom_quota_EncryptingOutputStream_impl_h
 | |
| 
 | |
| #include "EncryptingOutputStream.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <utility>
 | |
| #include "CipherStrategy.h"
 | |
| #include "mozilla/Assertions.h"
 | |
| #include "mozilla/Span.h"
 | |
| #include "mozilla/fallible.h"
 | |
| #include "nsDebug.h"
 | |
| #include "nsError.h"
 | |
| 
 | |
| namespace mozilla::dom::quota {
 | |
| template <typename CipherStrategy>
 | |
| EncryptingOutputStream<CipherStrategy>::EncryptingOutputStream(
 | |
|     nsCOMPtr<nsIOutputStream> aBaseStream, size_t aBlockSize,
 | |
|     typename CipherStrategy::KeyType aKey)
 | |
|     : EncryptingOutputStreamBase(std::move(aBaseStream), aBlockSize) {
 | |
|   // XXX Move this to a fallible init function.
 | |
|   MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Encrypt,
 | |
|                                            CipherStrategy::SerializeKey(aKey),
 | |
|                                            CipherStrategy::MakeBlockPrefix()));
 | |
| 
 | |
|   MOZ_ASSERT(mBlockSize > 0);
 | |
|   MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0);
 | |
|   static_assert(
 | |
|       CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0);
 | |
| 
 | |
|   // This implementation only supports sync base streams.  Verify this in debug
 | |
|   // builds.
 | |
| #ifdef DEBUG
 | |
|   bool baseNonBlocking;
 | |
|   nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|   MOZ_ASSERT(!baseNonBlocking);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() {
 | |
|   Close();
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() {
 | |
|   if (!mBaseStream) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // When closing, flush to the base stream unconditionally, i.e. even if the
 | |
|   // buffer is not completely full.
 | |
|   nsresult rv = FlushToBaseStream();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // XXX Maybe this Flush call can be removed, since the base stream is closed
 | |
|   // afterwards anyway.
 | |
|   rv = (*mBaseStream)->Flush();
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // XXX What if closing the base stream failed? Fail this method, or at least
 | |
|   // log a warning?
 | |
|   (*mBaseStream)->Close();
 | |
|   mBaseStream.destroy();
 | |
| 
 | |
|   mBuffer.Clear();
 | |
|   mEncryptedBlock.reset();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() {
 | |
|   if (!mBaseStream) {
 | |
|     return NS_BASE_STREAM_CLOSED;
 | |
|   }
 | |
| 
 | |
|   if (!EnsureBuffers()) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   // We cannot call FlushBaseStream() here if the buffer is not completely
 | |
|   // full, we would write an incomplete page, which might be read sequentially,
 | |
|   // but we want to support random accesses in DecryptingInputStream, which
 | |
|   // would no longer be feasible.
 | |
|   if (mNextByte && mNextByte == mEncryptedBlock->MaxPayloadLength()) {
 | |
|     nsresult rv = FlushToBaseStream();
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return (*mBaseStream)->Flush();
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::WriteSegments(
 | |
|     nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
 | |
|     uint32_t* aBytesWrittenOut) {
 | |
|   *aBytesWrittenOut = 0;
 | |
| 
 | |
|   if (!mBaseStream) {
 | |
|     return NS_BASE_STREAM_CLOSED;
 | |
|   }
 | |
| 
 | |
|   if (!EnsureBuffers()) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength();
 | |
| 
 | |
|   while (aCount > 0) {
 | |
|     // Determine how much space is left in our flat, plain buffer.
 | |
|     MOZ_ASSERT(mNextByte <= plainBufferSize);
 | |
|     uint32_t remaining = plainBufferSize - mNextByte;
 | |
| 
 | |
|     // If it is full, then encrypt and flush the data to the base stream.
 | |
|     if (remaining == 0) {
 | |
|       nsresult rv = FlushToBaseStream();
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         return rv;
 | |
|       }
 | |
| 
 | |
|       // Now the entire buffer should be available for copying.
 | |
|       MOZ_ASSERT(!mNextByte);
 | |
|       remaining = plainBufferSize;
 | |
|     }
 | |
| 
 | |
|     uint32_t numToRead = std::min(remaining, aCount);
 | |
|     uint32_t numRead = 0;
 | |
| 
 | |
|     nsresult rv =
 | |
|         aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]),
 | |
|                 *aBytesWrittenOut, numToRead, &numRead);
 | |
| 
 | |
|     // As defined in nsIOutputStream.idl, do not pass reader func errors.
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     // End-of-file
 | |
|     if (numRead == 0) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     mNextByte += numRead;
 | |
|     *aBytesWrittenOut += numRead;
 | |
|     aCount -= numRead;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| bool EncryptingOutputStream<CipherStrategy>::EnsureBuffers() {
 | |
|   // Lazily create the encrypted buffer on our first flush.  This
 | |
|   // allows us to report OOM during stream operation.  This buffer
 | |
|   // will then get re-used until the stream is closed.
 | |
|   if (!mEncryptedBlock) {
 | |
|     // XXX Do we need to do this fallible (as the comment above suggests)?
 | |
|     mEncryptedBlock.emplace(mBlockSize);
 | |
|     MOZ_ASSERT(mBuffer.IsEmpty());
 | |
| 
 | |
|     if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
 | |
|                                       fallible))) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| template <typename CipherStrategy>
 | |
| nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() {
 | |
|   MOZ_ASSERT(mBaseStream);
 | |
| 
 | |
|   if (!mNextByte) {
 | |
|     // Nothing to do.
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // XXX The compressing stream implementation this was based on wrote a stream
 | |
|   // identifier, containing e.g. the block size. Should we do something like
 | |
|   // that as well? At the moment, we don't need it, but maybe this were
 | |
|   // convenient if we use this for persistent files in the future across version
 | |
|   // updates, which might change such parameters.
 | |
| 
 | |
|   const auto iv = mCipherStrategy.MakeBlockPrefix();
 | |
|   static_assert(iv.size() * sizeof(decltype(*iv.begin())) ==
 | |
|                 CipherStrategy::BlockPrefixLength);
 | |
|   std::copy(iv.cbegin(), iv.cend(),
 | |
|             mEncryptedBlock->MutableCipherPrefix().begin());
 | |
| 
 | |
|   // Encrypt the data to our internal encrypted buffer.
 | |
|   // XXX Do we need to know the actual encrypted size?
 | |
|   nsresult rv = mCipherStrategy.Cipher(
 | |
|       mEncryptedBlock->MutableCipherPrefix(),
 | |
|       mozilla::Span(reinterpret_cast<uint8_t*>(mBuffer.Elements()),
 | |
|                     ((mNextByte + (CipherStrategy::BasicBlockSize - 1)) /
 | |
|                      CipherStrategy::BasicBlockSize) *
 | |
|                         CipherStrategy::BasicBlockSize),
 | |
|       mEncryptedBlock->MutablePayload());
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mEncryptedBlock->SetActualPayloadLength(mNextByte);
 | |
| 
 | |
|   mNextByte = 0;
 | |
| 
 | |
|   // Write the encrypted buffer out to the base stream.
 | |
|   uint32_t numWritten = 0;
 | |
|   const auto& wholeBlock = mEncryptedBlock->WholeBlock();
 | |
|   rv = WriteAll(AsChars(wholeBlock).Elements(), wholeBlock.Length(),
 | |
|                 &numWritten);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
|   MOZ_ASSERT(wholeBlock.Length() == numWritten);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom::quota
 | |
| 
 | |
| #endif
 | 
