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