1 /*
2  * Copyright (c) 2014, Vsevolod Stakhov
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *	 * Redistributions of source code must retain the above copyright
9  *	   notice, this list of conditions and the following disclaimer.
10  *	 * Redistributions in binary form must reproduce the above copyright
11  *	   notice, this list of conditions and the following disclaimer in the
12  *	   documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <iostream>
31 #include <cerrno>
32 #include <cstring>
33 #include <arpa/inet.h>
34 #include "encrypt.h"
35 #include "nonce.h"
36 #include "aead.h"
37 #include "util.h"
38 #include "kdf.h"
39 #include "thread_pool.h"
40 #include "aligned_alloc.h"
41 
42 namespace hpenc
43 {
44 
45 class HPEncEncrypt::impl {
46 public:
47 	std::unique_ptr<HPEncKDF> kdf;
48 	std::vector <std::shared_ptr<HPencAead> > ciphers;
49 	std::unique_ptr<HPEncNonce> nonce;
50 	int fd_in, fd_out;
51 	unsigned block_size;
52 	std::vector<std::shared_ptr<aligned_vector> > in_bufs;
53 	std::vector<std::shared_ptr<aligned_vector> > out_bufs;
54 	HPEncHeader hdr;
55 	bool encode;
56 	bool random_mode;
57 	std::unique_ptr<ThreadPool> pool;
58 
impl(std::unique_ptr<HPEncKDF> && _kdf,const std::string & in,const std::string & out,AeadAlgorithm alg,unsigned _block_size,unsigned nthreads=0,bool _rmode=false)59 	impl(std::unique_ptr<HPEncKDF> &&_kdf,
60 		const std::string &in,
61 		const std::string &out,
62 		AeadAlgorithm alg,
63 		unsigned _block_size,
64 		unsigned nthreads = 0,
65 		bool _rmode = false) : kdf(std::move(_kdf)), block_size(_block_size),
66 			hdr(alg, _block_size), random_mode(_rmode)
67 	{
68 
69 		if (!in.empty()) {
70 			fd_in = open(in.c_str(), O_RDONLY);
71 			if (fd_in == -1) {
72 				std::cerr << "Cannot open input file '" << in << "': "
73 					<< ::strerror(errno) << std::endl;
74 			}
75 		}
76 		else {
77 			fd_in = STDIN_FILENO;
78 		}
79 
80 		if (!out.empty()) {
81 			fd_out = open(out.c_str(), O_WRONLY | O_TRUNC);
82 			if (fd_out == -1) {
83 				std::cerr << "Cannot open output file '" << out << "': "
84 						<< ::strerror(errno) << std::endl;
85 			}
86 		}
87 		else {
88 			fd_out = STDOUT_FILENO;
89 		}
90 
91 		encode = false;
92 		pool.reset(new ThreadPool(nthreads));
93 		in_bufs.resize(pool->size());
94 		out_bufs.resize(pool->size());
95 		hdr.nonce = kdf->initialNonce();
96 		auto klen = AeadKeyLengths[static_cast<int>(alg)];
97 		auto key = kdf->genKey(klen);
98 
99 		for (auto i = 0U; i < pool->size(); i ++) {
100 			auto cipher = std::make_shared<HPencAead>(alg, _rmode);
101 			cipher->setKey(key);
102 			if (!nonce) {
103 				nonce.reset(new HPEncNonce(cipher->noncelen()));
104 			}
105 			ciphers.push_back(cipher);
106 			in_bufs[i] = std::make_shared<aligned_vector>();
107 			in_bufs[i]->resize(block_size);
108 			out_bufs[i] = std::make_shared<aligned_vector>();
109 			out_bufs[i]->resize(block_size + ciphers[0]->taglen());
110 		}
111 	}
112 
~impl()113 	virtual ~impl()
114 	{
115 		if (fd_in != -1) close(fd_in);
116 		if (fd_out != -1) close(fd_out);
117 	}
118 
writeHeader()119 	bool writeHeader()
120 	{
121 		return hdr.toFd(fd_out, encode);
122 	}
123 
writeBlock(ssize_t rd,aligned_vector * in_buf,aligned_vector * out_buf,const std::vector<byte> & n,std::shared_ptr<HPencAead> const & cipher)124 	ssize_t writeBlock(ssize_t rd, aligned_vector *in_buf, aligned_vector *out_buf,
125 			const std::vector<byte> &n, std::shared_ptr<HPencAead> const &cipher)
126 	{
127 		if (rd > 0) {
128 			auto bs = htonl(rd);
129 			auto tag = cipher->encrypt(reinterpret_cast<byte *>(&bs), sizeof(bs),
130 					n.data(), n.size(), in_buf->data(), rd, out_buf->data());
131 
132 			if (!random_mode) {
133 				if (!tag) {
134 					return -1;
135 				}
136 
137 				auto mac_pos = out_buf->data() + rd;
138 				std::copy(tag->data, tag->data + tag->datalen, mac_pos);
139 				return rd + tag->datalen;
140 			}
141 
142 			return rd;
143 
144 		}
145 		return -1;
146 	}
147 
readBlock(aligned_vector * io_buf)148 	size_t readBlock(aligned_vector *io_buf)
149 	{
150 		return util::atomicRead(fd_in, io_buf->data(), block_size);
151 	}
152 };
153 
HPEncEncrypt(std::unique_ptr<HPEncKDF> && kdf,const std::string & in,const std::string & out,AeadAlgorithm alg,unsigned block_size,unsigned nthreads,bool random_mode)154 HPEncEncrypt::HPEncEncrypt(std::unique_ptr<HPEncKDF> &&kdf,
155 		const std::string &in,
156 		const std::string &out,
157 		AeadAlgorithm alg,
158 		unsigned block_size,
159 		unsigned nthreads,
160 		bool random_mode) :
161 	pimpl(new impl(std::move(kdf), in, out, alg, block_size, nthreads, random_mode))
162 {
163 }
164 
~HPEncEncrypt()165 HPEncEncrypt::~HPEncEncrypt()
166 {
167 }
168 
encrypt(bool encode,unsigned count)169 void HPEncEncrypt::encrypt(bool encode, unsigned count)
170 {
171 	pimpl->encode = encode;
172 	bool last = false;
173 	auto remain = count;
174 
175 	if (pimpl->random_mode || pimpl->writeHeader()) {
176 		auto nblocks = 0U;
177 		for (;;) {
178 			auto blocks_read = 0;
179 			std::vector< std::future<ssize_t> > results;
180 			auto i = 0U;
181 			for (auto &buf : pimpl->in_bufs) {
182 				if (count > 0) {
183 					if (remain == 0) {
184 						last = true;
185 						break;
186 					}
187 					remain --;
188 				}
189 
190 				auto rd = pimpl->block_size;
191 				if (!pimpl->random_mode) {
192 					// For random mode we skip reading
193 					rd = pimpl->readBlock(buf.get());
194 				}
195 
196 				if (rd > 0) {
197 					auto n = pimpl->nonce->incAndGet();
198 					results.emplace_back(
199 							pimpl->pool->enqueue(
200 								&impl::writeBlock, pimpl.get(), rd, buf.get(),
201 								pimpl->out_bufs[i].get(),
202 								n, pimpl->ciphers[i]
203 							));
204 					blocks_read ++;
205 				}
206 				else {
207 					last = true;
208 					break;
209 				}
210 				i ++;
211 			}
212 
213 			i = 0;
214 			for(auto && result: results) {
215 				result.wait();
216 				auto rd = result.get();
217 				if (rd == -1) {
218 					throw std::runtime_error("Cannot encrypt block");
219 				}
220 				else {
221 					if (rd > 0) {
222 						const auto &io_buf = pimpl->out_bufs[i].get();
223 						if (encode) {
224 							auto b64_out = util::base64Encode(io_buf->data(), rd);
225 							if (util::atomicWrite(pimpl->fd_out,
226 									reinterpret_cast<const byte *>(b64_out.data()),
227 									b64_out.size()) == 0) {
228 								if (pimpl->random_mode) {
229 									// Assume that we are done
230 									std::cerr << "Cannot write output: " <<
231 											strerror(errno);
232 									return;
233 								}
234 								else {
235 									throw std::runtime_error("Cannot write "
236 											"encrypted block");
237 								}
238 							}
239 						}
240 						else {
241 							if (util::atomicWrite(pimpl->fd_out, io_buf->data(),
242 									rd) == 0) {
243 								if (pimpl->random_mode) {
244 									// Assume that we are done
245 									if (errno != ENOSPC && errno != EPIPE) {
246 										std::cerr << "Cannot write output: " <<
247 											strerror(errno) << std::endl;
248 									}
249 									return;
250 								}
251 								else {
252 									throw std::runtime_error("Cannot write "
253 											"encrypted block");
254 								}
255 							}
256 						}
257 					}
258 					if (rd < pimpl->block_size) {
259 						// We are done
260 						return;
261 					}
262 				}
263 				i++;
264 			}
265 
266 			if (last) {
267 				return;
268 			}
269 
270 			if (++nblocks % rekey_blocks == 0) {
271 				// Rekey all cipers
272 				auto nkey = pimpl->kdf->genKey(pimpl->ciphers[0]->keylen());
273 				for (auto const &cipher : pimpl->ciphers) {
274 					cipher->setKey(nkey);
275 				}
276 			}
277 		}
278 	}
279 }
280 
281 } /* namespace hpenc */
282