summaryrefslogtreecommitdiffstats
path: root/dom/quota/EncryptingOutputStream_impl.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/quota/EncryptingOutputStream_impl.h')
-rw-r--r--dom/quota/EncryptingOutputStream_impl.h281
1 files changed, 281 insertions, 0 deletions
diff --git a/dom/quota/EncryptingOutputStream_impl.h b/dom/quota/EncryptingOutputStream_impl.h
new file mode 100644
index 0000000000..bf48d49f69
--- /dev/null
+++ b/dom/quota/EncryptingOutputStream_impl.h
@@ -0,0 +1,281 @@
+/* -*- 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"
+#include "nsIAsyncOutputStream.h"
+#include "nsIRandomGenerator.h"
+#include "nsServiceManagerUtils.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. Note, this is a bit complicated because the streams we support
+ // advertise different capabilities:
+ // - nsFileOutputStream - blocking and sync
+ // - FixedBufferOutputStream - non-blocking and sync
+ // - nsPipeOutputStream - can be blocking, but provides async interface
+#ifdef DEBUG
+ bool baseNonBlocking;
+ nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (baseNonBlocking) {
+ nsCOMPtr<nsIAsyncOutputStream> async =
+ do_QueryInterface((*mBaseStream).get());
+ MOZ_ASSERT(!async);
+ }
+#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>::StreamStatus() {
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ return (*mBaseStream)->StreamStatus();
+}
+
+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;
+ }
+
+ if (mNextByte < mEncryptedBlock->MaxPayloadLength()) {
+ if (!mRandomGenerator) {
+ mRandomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1");
+ if (NS_WARN_IF(!mRandomGenerator)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const auto payload = mEncryptedBlock->MutablePayload();
+
+ const auto unusedPayload = payload.From(mNextByte);
+
+ uint8_t* buffer;
+ nsresult rv =
+ mRandomGenerator->GenerateRandomBytes(unusedPayload.Length(), &buffer);
+ if (NS_WARN_IF(NS_FAILED(rv)) || !buffer) {
+ return rv;
+ }
+
+ memcpy(unusedPayload.Elements(), buffer, unusedPayload.Length());
+
+ free(buffer);
+ }
+
+ // 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