Differential Revision: https://phabricator.services.mozilla.com/D311011
249 lines
10 KiB
C++
249 lines
10 KiB
C++
/* 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(
|
|
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_
|