1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
#pragma once
#include "exceptions.h"
#include "myutils.h"
#include <memory>
#include <utility>
namespace securefs
{
/**
* Base classes for byte streams.
**/
class StreamBase
{
public:
StreamBase() {}
virtual ~StreamBase() {}
DISABLE_COPY_MOVE(StreamBase)
/**
* Returns the number of bytes actually read into the buffer `output`.
* Always read in full unless beyond the end, i.e., offset + length > size.
**/
virtual length_type read(void* output, offset_type offset, length_type length) = 0;
/**
* Write must always succeed as a whole or throw an exception otherwise.
* If the offset is beyond the end of the stream, the gap should be filled with zeros.
**/
virtual void write(const void* input, offset_type offset, length_type length) = 0;
virtual length_type size() const = 0;
virtual void flush() = 0;
/**
* Similar to ftruncate().
* Discard extra data when shrinking, zero-fill when extending.
**/
virtual void resize(length_type) = 0;
/**
* Sparse streams can be extended with zeros in constant time.
* Some algorithms may be specialized on sparse streams.
*/
virtual bool is_sparse() const noexcept { return false; }
/**
* Certain streams are more efficient when reads and writes are aligned to blocks
*/
virtual length_type optimal_block_size() const noexcept { return 1; }
};
/**
* Interface that supports a fixed size buffer to store headers for files
*/
class HeaderBase
{
public:
HeaderBase() {}
virtual ~HeaderBase() {}
DISABLE_COPY_MOVE(HeaderBase)
virtual length_type max_header_length() const noexcept = 0;
/**
* Returns: true if read in full, false if no header is present.
* Never reads in part.
*/
virtual bool read_header(void* output, length_type length) = 0;
/**
* Always write in full.
*/
virtual void write_header(const void* input, length_type length) = 0;
virtual void flush_header() = 0;
};
std::shared_ptr<StreamBase> make_stream_hmac(const key_type& key_,
const id_type& id_,
std::shared_ptr<StreamBase> stream,
bool check);
class BlockBasedStream : public StreamBase
{
protected:
length_type m_block_size;
protected:
virtual length_type read_block(offset_type block_number, void* output) = 0;
virtual void write_block(offset_type block_number, const void* input, length_type length) = 0;
virtual void adjust_logical_size(length_type length) = 0;
private:
length_type
read_block(offset_type block_number, void* output, offset_type begin, offset_type end);
void read_then_write_block(offset_type block_number,
const void* input,
offset_type begin,
offset_type end);
void unchecked_write(const void* input, offset_type offset, length_type length);
void zero_fill(offset_type offset, length_type length);
void unchecked_resize(length_type current_size, length_type new_size);
public:
BlockBasedStream(length_type block_size) : m_block_size(block_size) {}
~BlockBasedStream() {}
length_type read(void* output, offset_type offset, length_type length) override;
void write(const void* input, offset_type offset, length_type length) override;
void resize(length_type new_length) override;
length_type optimal_block_size() const noexcept override { return m_block_size; }
};
/**
* Base classes for streams that encrypt and decrypt data transparently
* The transformation is done in blocks,
* and must always output data of the same length as input.
*
* Subclasses should use additional storage, such as another stream, to store IVs and MACs.
*
* The CryptStream supports sparse streams if the subclass can tell whether all zero block
* are ciphertext or sparse parts of the underlying stream.
*/
class CryptStream : public BlockBasedStream
{
protected:
std::shared_ptr<StreamBase> m_stream;
// Both encrypt/decrypt should not change the length of the block.
// input/output may alias.
virtual void
encrypt(offset_type block_number, const void* input, void* output, length_type length)
= 0;
virtual void
decrypt(offset_type block_number, const void* input, void* output, length_type length)
= 0;
void adjust_logical_size(length_type length) override { m_stream->resize(length); }
private:
length_type read_block(offset_type block_number, void* output) override;
void write_block(offset_type block_number, const void* input, length_type length) override;
public:
explicit CryptStream(std::shared_ptr<StreamBase> stream, length_type block_size)
: BlockBasedStream(block_size), m_stream(std::move(stream))
{
if (!m_stream)
throwVFSException(EFAULT);
if (m_block_size < 1)
throwInvalidArgumentException("Too small block size");
}
void flush() override { m_stream->flush(); }
length_type size() const override { return m_stream->size(); }
};
/**
* AESGCMCryptStream is both a CryptStream and a HeaderBase.
*
* Returns a pair because the client does not need to know whether the two interfaces are
* implemented by the same class.
*/
std::pair<std::shared_ptr<CryptStream>, std::shared_ptr<HeaderBase>>
make_cryptstream_aes_gcm(std::shared_ptr<StreamBase> data_stream,
std::shared_ptr<StreamBase> meta_stream,
const key_type& data_key,
const key_type& meta_key,
const id_type& id_,
bool check,
unsigned block_size,
unsigned iv_size,
unsigned header_size = 32);
} // namespace securefs
|