Source code

Revision control

Copy as Markdown

Other Tools

/* 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 <array>
#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<uint8_t, EncryptedRandomAccessBlock::HeaderSize +
sizeof(BlockIndexType)>;
EncryptedRandomAccessStreamBase(
MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> 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<nsCOMPtr<nsIRandomAccessStream>> mBaseStream;
nsCOMPtr<nsIRandomGenerator> 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<uint8_t, sMaxTextLength> mPlainBuffer{};
TextLengthType mCurrentBlockTextLength = 0;
bool mBlockLoaded = false;
bool mBlockDirty = false;
bool mClosed = false;
};
template <typename CipherStrategy>
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<RefPtr<EncryptedRandomAccessStream<CipherStrategy>>, nsresult>
Create(CipherStrategy aStrategy,
MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> aStream,
typename CipherStrategy::KeyType aMasterKey);
private:
template <typename T, typename... Args>
friend RefPtr<T> mozilla::MakeRefPtr(Args&&... aArgs);
// This class must be initialized by |Create()|.
EncryptedRandomAccessStream<CipherStrategy>(
MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> 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_