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