summaryrefslogtreecommitdiffstats
path: root/dom/quota/DecryptingInputStream_impl.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/quota/DecryptingInputStream_impl.h')
-rw-r--r--dom/quota/DecryptingInputStream_impl.h531
1 files changed, 531 insertions, 0 deletions
diff --git a/dom/quota/DecryptingInputStream_impl.h b/dom/quota/DecryptingInputStream_impl.h
new file mode 100644
index 0000000000..48100f114e
--- /dev/null
+++ b/dom/quota/DecryptingInputStream_impl.h
@@ -0,0 +1,531 @@
+/* -*- 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_DecryptingInputStream_impl_h
+#define mozilla_dom_quota_DecryptingInputStream_impl_h
+
+#include "DecryptingInputStream.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <type_traits>
+#include <utility>
+#include "CipherStrategy.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Span.h"
+#include "mozilla/fallible.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsFileStreams.h"
+#include "nsID.h"
+#include "nsIFileStreams.h"
+
+namespace mozilla::dom::quota {
+
+template <typename CipherStrategy>
+DecryptingInputStream<CipherStrategy>::DecryptingInputStream(
+ MovingNotNull<nsCOMPtr<nsIInputStream>> aBaseStream, size_t aBlockSize,
+ typename CipherStrategy::KeyType aKey)
+ : DecryptingInputStreamBase(std::move(aBaseStream), aBlockSize),
+ mKey(aKey) {
+ // XXX Move this to a fallible init function.
+ MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Decrypt,
+ CipherStrategy::SerializeKey(aKey)));
+
+ // 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>
+DecryptingInputStream<CipherStrategy>::~DecryptingInputStream() {
+ Close();
+}
+
+template <typename CipherStrategy>
+DecryptingInputStream<CipherStrategy>::DecryptingInputStream()
+ : DecryptingInputStreamBase{} {}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Close() {
+ if (!mBaseStream) {
+ return NS_OK;
+ }
+
+ (*mBaseStream)->Close();
+ mBaseStream.destroy();
+
+ mPlainBuffer.Clear();
+ mEncryptedBlock.reset();
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Available(
+ uint64_t* aLengthOut) {
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t oldPos, endPos;
+ nsresult rv = Tell(&oldPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Seek(SEEK_END, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Tell(&endPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Seek(SEEK_SET, oldPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aLengthOut = endPos - oldPos;
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::StreamStatus() {
+ return mBaseStream ? NS_OK : NS_BASE_STREAM_CLOSED;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::ReadSegments(
+ nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aBytesReadOut) {
+ *aBytesReadOut = 0;
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv;
+
+ // Do not try to use the base stream's ReadSegments here. Its very
+ // unlikely we will get a single buffer that contains all of the encrypted
+ // data and therefore would have to copy into our own buffer anyways.
+ // Instead, focus on making efficient use of the Read() interface.
+
+ while (aCount > 0) {
+ // We have some decrypted data in our buffer. Provide it to the callers
+ // writer function.
+ if (mPlainBytes > 0) {
+ MOZ_ASSERT(!mPlainBuffer.IsEmpty());
+ uint32_t remaining = PlainLength();
+ uint32_t numToWrite = std::min(aCount, remaining);
+ uint32_t numWritten;
+ rv = aWriter(this, aClosure,
+ reinterpret_cast<const char*>(&mPlainBuffer[mNextByte]),
+ *aBytesReadOut, numToWrite, &numWritten);
+
+ // As defined in nsIInputputStream.idl, do not pass writer func errors.
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // End-of-file
+ if (numWritten == 0) {
+ return NS_OK;
+ }
+
+ *aBytesReadOut += numWritten;
+ mNextByte += numWritten;
+ MOZ_ASSERT(mNextByte <= mPlainBytes);
+
+ if (mNextByte == mPlainBytes) {
+ mNextByte = 0;
+ mLastBlockLength = mPlainBytes;
+ mPlainBytes = 0;
+ }
+
+ aCount -= numWritten;
+
+ continue;
+ }
+
+ // Otherwise decrypt the next chunk and loop. Any resulting data
+ // will set mPlainBytes which we check at the top of the loop.
+ uint32_t bytesRead;
+ rv = ParseNextChunk(&bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If we couldn't read anything and there is no more data to provide
+ // to the caller, then this is eof.
+ if (bytesRead == 0 && mPlainBytes == 0) {
+ return NS_OK;
+ }
+
+ mPlainBytes += bytesRead;
+ }
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+nsresult DecryptingInputStream<CipherStrategy>::ParseNextChunk(
+ uint32_t* const aBytesReadOut) {
+ // There must not be any plain data already in mPlainBuffer.
+ MOZ_ASSERT(mPlainBytes == 0);
+ MOZ_ASSERT(mNextByte == 0);
+
+ *aBytesReadOut = 0;
+
+ if (!EnsureBuffers()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Read the data to our internal encrypted buffer.
+ auto wholeBlock = mEncryptedBlock->MutableWholeBlock();
+ nsresult rv =
+ ReadAll(AsWritableChars(wholeBlock).Elements(), wholeBlock.Length(),
+ wholeBlock.Length(), aBytesReadOut);
+ if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) {
+ return rv;
+ }
+
+ // XXX Do we need to know the actual decrypted size?
+ rv = mCipherStrategy.Cipher(mEncryptedBlock->MutableCipherPrefix(),
+ mEncryptedBlock->Payload(),
+ AsWritableBytes(Span{mPlainBuffer}));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aBytesReadOut = mEncryptedBlock->ActualPayloadLength();
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+nsresult DecryptingInputStream<CipherStrategy>::ReadAll(
+ char* aBuf, uint32_t aCount, uint32_t aMinValidCount,
+ uint32_t* aBytesReadOut) {
+ MOZ_ASSERT(aCount >= aMinValidCount);
+ MOZ_ASSERT(mBaseStream);
+
+ *aBytesReadOut = 0;
+
+ uint32_t offset = 0;
+ while (aCount > 0) {
+ uint32_t bytesRead = 0;
+ nsresult rv = (*mBaseStream)->Read(aBuf + offset, aCount, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // EOF, but don't immediately return. We need to validate min read bytes
+ // below.
+ if (bytesRead == 0) {
+ break;
+ }
+
+ *aBytesReadOut += bytesRead;
+ offset += bytesRead;
+ aCount -= bytesRead;
+ }
+
+ // Reading zero bytes is not an error. Its the expected EOF condition.
+ // Only compare to the minimum valid count if we read at least one byte.
+ if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+bool DecryptingInputStream<CipherStrategy>::EnsureBuffers() {
+ // Lazily create our two buffers so we can report OOM during stream
+ // operation. These allocations only happens once. The buffers are reused
+ // 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(mPlainBuffer.IsEmpty());
+ if (NS_WARN_IF(!mPlainBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
+ fallible))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Tell(
+ int64_t* const aRetval) {
+ MOZ_ASSERT(aRetval);
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!EnsureBuffers()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int64_t basePosition;
+ nsresult rv = (*mBaseSeekableStream)->Tell(&basePosition);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ const auto fullBlocks = basePosition / *mBlockSize;
+ MOZ_ASSERT(0 == basePosition % *mBlockSize);
+
+ *aRetval = (fullBlocks - ((mPlainBytes || mLastBlockLength) ? 1 : 0)) *
+ mEncryptedBlock->MaxPayloadLength() +
+ mNextByte + (mNextByte ? 0 : mLastBlockLength);
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Seek(const int32_t aWhence,
+ int64_t aOffset) {
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!EnsureBuffers()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int64_t baseBlocksOffset;
+ int64_t nextByteOffset;
+ switch (aWhence) {
+ case NS_SEEK_CUR:
+ // XXX Simplify this without using Tell.
+ {
+ int64_t current;
+ nsresult rv = Tell(&current);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aOffset += current;
+ }
+ break;
+ case NS_SEEK_SET:
+ break;
+
+ case NS_SEEK_END:
+ // XXX Simplify this without using Seek/Tell.
+ {
+ // XXX The size of the stream could also be queried and stored once
+ // only.
+ nsresult rv = (*mBaseSeekableStream)->Seek(NS_SEEK_SET, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint64_t baseStreamSize;
+ rv = (*mBaseStream)->Available(&baseStreamSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ auto decryptedStreamSizeOrErr = [baseStreamSize,
+ this]() -> Result<int64_t, nsresult> {
+ if (!baseStreamSize) {
+ return 0;
+ }
+
+ nsresult rv =
+ (*mBaseSeekableStream)
+ ->Seek(NS_SEEK_END, -static_cast<int64_t>(*mBlockSize));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ mNextByte = 0;
+ mPlainBytes = 0;
+
+ uint32_t bytesRead;
+ rv = ParseNextChunk(&bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+ MOZ_ASSERT(bytesRead);
+
+ // XXX Shouldn't ParseNextChunk better update mPlainBytes?
+ mPlainBytes = bytesRead;
+
+ mNextByte = bytesRead;
+
+ int64_t current;
+ rv = Tell(&current);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ return current;
+ }();
+
+ if (decryptedStreamSizeOrErr.isErr()) {
+ return decryptedStreamSizeOrErr.unwrapErr();
+ }
+
+ aOffset += decryptedStreamSizeOrErr.unwrap();
+ }
+ break;
+
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ baseBlocksOffset = aOffset / mEncryptedBlock->MaxPayloadLength();
+ nextByteOffset = aOffset % mEncryptedBlock->MaxPayloadLength();
+
+ // XXX If we remain in the same block as before, we can skip this.
+ nsresult rv =
+ (*mBaseSeekableStream)->Seek(NS_SEEK_SET, baseBlocksOffset * *mBlockSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mNextByte = 0;
+ mPlainBytes = 0;
+
+ uint32_t readBytes;
+ rv = ParseNextChunk(&readBytes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // XXX Do we need to do more here? Restore any previous state?
+ return rv;
+ }
+
+ // We positioned after the last block, we must read that to know its size.
+ // XXX We could know earlier if we positioned us after the last block.
+ if (!readBytes) {
+ if (baseBlocksOffset == 0) {
+ // The stream is empty.
+ return aOffset == 0 ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = (*mBaseSeekableStream)->Seek(NS_SEEK_CUR, -*mBlockSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = ParseNextChunk(&readBytes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // XXX Do we need to do more here? Restore any previous state?
+ return rv;
+ }
+ }
+
+ mPlainBytes = readBytes;
+ mNextByte = nextByteOffset;
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Clone(
+ nsIInputStream** _retval) {
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!(*mBaseCloneableInputStream)->GetCloneable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv =
+ (*mBaseCloneableInputStream)->Clone(getter_AddRefs(clonedStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *_retval = MakeAndAddRef<DecryptingInputStream>(
+ WrapNotNull(std::move(clonedStream)), *mBlockSize, *mKey)
+ .take();
+
+ return NS_OK;
+}
+
+template <typename CipherStrategy>
+void DecryptingInputStream<CipherStrategy>::Serialize(
+ mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(mBaseStream);
+ MOZ_ASSERT(mBaseIPCSerializableInputStream);
+
+ mozilla::ipc::InputStreamParams baseStreamParams;
+ (*mBaseIPCSerializableInputStream)
+ ->Serialize(baseStreamParams, aMaxSize, aSizeUsed);
+
+ MOZ_ASSERT(baseStreamParams.type() ==
+ mozilla::ipc::InputStreamParams::TFileInputStreamParams);
+
+ mozilla::ipc::EncryptedFileInputStreamParams encryptedFileInputStreamParams;
+ encryptedFileInputStreamParams.fileInputStreamParams() =
+ std::move(baseStreamParams);
+ encryptedFileInputStreamParams.key().AppendElements(
+ mCipherStrategy.SerializeKey(*mKey));
+ encryptedFileInputStreamParams.blockSize() = *mBlockSize;
+
+ aParams = std::move(encryptedFileInputStreamParams);
+}
+
+template <typename CipherStrategy>
+bool DecryptingInputStream<CipherStrategy>::Deserialize(
+ const mozilla::ipc::InputStreamParams& aParams) {
+ MOZ_ASSERT(aParams.type() ==
+ mozilla::ipc::InputStreamParams::TEncryptedFileInputStreamParams);
+ const auto& params = aParams.get_EncryptedFileInputStreamParams();
+
+ nsCOMPtr<nsIFileInputStream> stream;
+ nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream),
+ getter_AddRefs(stream));
+ nsCOMPtr<nsIIPCSerializableInputStream> baseSerializable =
+ do_QueryInterface(stream);
+
+ if (NS_WARN_IF(
+ !baseSerializable->Deserialize(params.fileInputStreamParams()))) {
+ return false;
+ }
+
+ Init(WrapNotNull<nsCOMPtr<nsIInputStream>>(std::move(stream)),
+ params.blockSize());
+
+ auto key = mCipherStrategy.DeserializeKey(params.key());
+ if (NS_WARN_IF(!key)) {
+ return false;
+ }
+
+ mKey.init(*key);
+ if (NS_WARN_IF(
+ NS_FAILED(mCipherStrategy.Init(CipherMode::Decrypt, params.key())))) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla::dom::quota
+
+#endif