/* 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 DOM_QUOTA_ENCRYPTEDRANDOMACCESSSTREAM_H_ #define DOM_QUOTA_ENCRYPTEDRANDOMACCESSSTREAM_H_ #include #include "EncryptedRandomAccessBlock.h" #include "EncryptedRandomAccessBlockView.h" #include "ErrorList.h" #include "mozilla/NotNull.h" #include "mozilla/RefPtr.h" #include "mozilla/Result.h" #include "nsCOMPtr.h" #include "nsError.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsIRandomAccessStream.h" #include "nsIRandomGenerator.h" #include "nsISeekableStream.h" #include "nsStreamUtils.h" #include "nscore.h" namespace mozilla::dom::quota { /** * |EncryptedRandomAccessStream| provides random access to plaintext backed by a * sequence of |EncryptedRandomAccessBlock| objects. See * |EncryptedRandomAccessBlock| and the corresponding view classes for the * on-disk layout. * * The stream exposes plaintext offsets. These differ from physical offsets in * the encrypted base stream, where each block has the fixed size |sBlockSize|. * On top of its plaintext, every block also stores per-block overhead that the * stream never exposes: a plaintext header, the cipher metadata, and, inside * the decrypted payload, a text-length field (see |EncryptedRandomAccessBlock| * and |DecryptedRandomAccessBlockCipherPayloadView|). |mLogicalPosition| and * |mLogicalSize| count only the plaintext text bytes and exclude all of this * overhead, as well as the padding in the final block. All non-final blocks * hold |sMaxTextLength| text bytes; only the final block may hold fewer. * * For example, a base stream containing three encrypted blocks: * * On-disk layout, where "ovhd" is the header, metadata and text-length field. * Each block occupies |sBlockSize| bytes: * * +----------------------+----------------------+----------------------+ * | ovhd | text 0 | ovhd | text 1 | ovhd | text 2 | pad | * +----------------------+----------------------+----------------------+ * block 0 block 1 block 2 (final) * * Plaintext the stream exposes, i.e. the texts concatenated with the overhead * and padding removed: * * mLogicalPosition = sMaxTextLength + X * | * v * +----------------------+----------------------+-----------+ * | |<----- X ----->| | | * |<-- sMaxTextLength -->|<-- sMaxTextLength -->|<--- Y --->| * +----------------------+----------------------+-----------+ * |<-------- mLogicalSize = 2 * sMaxTextLength + Y -------->| * * mTotalBlockCount = 3 * * Physical I/O is delayed where possible. For example, |Seek()| updates only * the logical position. A block is loaded when a subsequent read needs it. But, * only the final block is loaded eagerly by |Create()| to derive * |mLogicalSize|. Other blocks are loaded lazily when a read needs them. So, * malformed non-final blocks may not be detected until they are read. * * Rules of blocks: * * A block can be in three places: the in-memory current block, the base stream * (not necessarily synced to disk), and the disk. * |LoadBlock()| reads a block from the base stream into memory, * |SaveCurrentBlock()| writes the current block to the base stream, and only * |Flush()| / |Close()| sync the base stream to disk. * * - |mTotalBlockCount| is the number of encrypted blocks written to the base * stream. It is derived from the base stream size in |Create()| and only * ever bumped by |SaveCurrentBlock()| after it writes a brand-new block to * the base stream. * * - While |mBlockLoaded|, a single "current block" is held in memory, * represented by its plaintext (|mPlainBuffer|) and valid length * (|mCurrentBlockTextLength|). When loaded, |mCurrentBlockIndex| relates to * the base stream as follows * (|mCurrentBlockIndex > mTotalBlockCount| is never valid): * * block index: 0 1 2 | 3 == mTotalBlockCount * +--------+--------+--------+ +- - - - - - + * base stream: | block0 | block1 | block2 | | (none) | * +--------+--------+--------+ +- - - - - - + * |<-------- written ------->| |<- memory ->| * only * mCurrentBlockIndex < mTotalBlockCount: a block in the base stream * mCurrentBlockIndex == mTotalBlockCount: a pending append block, not in * the base stream yet (created in * memory by * |LoadNewBlockAtEnd()|) * * - |LoadBlock()| reads blocks from the base stream. That means it rejects * |aBlockIndex >= mTotalBlockCount|. So it doesn't read an in-memory block * not yet on the stream. * * - |LoadNewBlockAtEnd()| only sets up the pending append block in memory, * doing no I/O. * * - Only the current block can be |mBlockDirty|, and it is always written to * the base stream before moving to another block. * * - I/O never relies on the base stream cursor position: every read/write * explicitly seeks first. * * This class also implements |nsIInputStream| and |nsIOutputStream| so it can * be used with facilities such as |NS_AsyncCopy|. */ class EncryptedRandomAccessStreamBase : public nsIRandomAccessStream, public nsIInputStream, public nsIOutputStream { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITELLABLESTREAM NS_DECL_NSISEEKABLESTREAM NS_DECL_NSIRANDOMACCESSSTREAM NS_DECL_NSIOUTPUTSTREAM // nsIInputStream NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override; NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, uint32_t* _retval) override; NS_IMETHOD Available(uint64_t* _retval) override; protected: using BlockIndexType = uint64_t; using TextLengthType = DecryptedRandomAccessBlockCipherPayloadView::TextLengthType; using AadType = std::array; EncryptedRandomAccessStreamBase( MovingNotNull> aStream) : mBaseStream(std::move(aStream)) {} virtual ~EncryptedRandomAccessStreamBase() = default; static constexpr auto sBlockSize = EncryptedRandomAccessBlock::BlockSize; static constexpr auto sTextLengthFieldSize = DecryptedRandomAccessBlockCipherPayloadView::TextLengthFieldSize; static constexpr auto sMaxTextLength = DecryptedRandomAccessBlockCipherPayloadView::MaxTextLength; virtual nsresult LoadBlock(BlockIndexType aBlockIndex) = 0; nsresult LoadNewBlockAtEnd(); virtual nsresult SaveCurrentBlock() = 0; nsresult ReadEncryptedBlockFromBaseStream( BlockIndexType aBlockIndex, EncryptedRandomAccessBlock& aEncryptedBlock); nsresult WriteEncryptedBlockToBaseStream( BlockIndexType aBlockIndex, const EncryptedRandomAccessBlock& aEncryptedBlock); nsresult ZeroExtendTo(uint64_t aNewLogicalSize); nsresult PadPlainBuffer(); nsresult GenerateRandomBytes(uint8_t* aBuffer, uint32_t aLength); static AadType BuildAad(const EncryptedRandomAccessBlock& aEncryptedBlock, BlockIndexType aBlockIndex); // Because the current cipher strategies for random access are // stateless, this class doesn't have to own a strategy. const NotNull> mBaseStream; nsCOMPtr mRandomGenerator; // |mLogicalPosition| is the current read/write position and |mLogicalSize| is // the total size of the plaintext stream, both measured in plaintext bytes // from the start of the plaintext stream (see the class comment). These are // not physical offsets into the encrypted base stream. // They use |uint64_t|, because the file behind the // stream can be large. uint64_t mLogicalPosition = 0; uint64_t mLogicalSize = 0; // It's initialized in |Create()|. BlockIndexType mTotalBlockCount = 0; // It's initialized in |Create()|. BlockIndexType mCurrentBlockIndex = 0; std::array mPlainBuffer{}; TextLengthType mCurrentBlockTextLength = 0; bool mBlockLoaded = false; bool mBlockDirty = false; bool mClosed = false; }; template class EncryptedRandomAccessStream final : public EncryptedRandomAccessStreamBase { public: /** * Creating the stream can fail because initialization reads the final block * to derive the logical plaintext size and validate its format. */ static Result>, nsresult> Create(CipherStrategy aStrategy, MovingNotNull> aStream, typename CipherStrategy::KeyType aMasterKey); private: template friend RefPtr mozilla::MakeRefPtr(Args&&... aArgs); // This class must be initialized by |Create()|. EncryptedRandomAccessStream( MovingNotNull> aStream, typename CipherStrategy::KeyType aMasterKey) : EncryptedRandomAccessStreamBase(std::move(aStream)), mMasterKey(aMasterKey) {} ~EncryptedRandomAccessStream(); nsresult LoadBlock(BlockIndexType aBlockIndex) override; nsresult SaveCurrentBlock() override; nsresult EncryptBlockVersion1(EncryptedRandomAccessBlock& aEncryptedBlock, BlockIndexType aBlockIndex); nsresult DecryptBlockVersion1(EncryptedRandomAccessBlock& aEncryptedBlock, BlockIndexType aBlockIndex); // Because the current cipher strategies for random access are // stateless, this class doesn't have to own a strategy. const typename CipherStrategy::KeyType mMasterKey; }; } // namespace mozilla::dom::quota #endif // DOM_QUOTA_ENCRYPTEDRANDOMACCESSSTREAM_H_