hammer2 - Crypto handshake work for message stream
[dragonfly.git] / sbin / hammer2 / crypto.c
CommitLineData
62efe6ec
MD
1/*
2 * Copyright (c) 2011-2012 The DragonFly Project. All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@dragonflybsd.org>
6 * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org>
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 * 3. Neither the name of The DragonFly Project nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific, prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include "hammer2.h"
37
38#include <openssl/rsa.h>
39#include <openssl/pem.h>
40#include <openssl/err.h>
41
42/*
43 * Synchronously negotiate crypto for a new session. This must occur
44 * within 10 seconds or the connection is error'd out.
45 *
46 * We work off the IP address and/or reverse DNS. The IP address is
47 * checked first, followed by the IP address at various levels of granularity,
48 * followed by the full domain name and domain names at various levels of
49 * granularity.
50 *
51 * /etc/hammer2/remote/<name>.pub - Contains a public key
52 * /etc/hammer2/remote/<name>.none - Indicates no encryption (empty file)
53 * (e.g. localhost.none).
54 *
55 * We first attempt to locate a public key file based on the peer address or
56 * peer FQDN.
57 *
58 * <name>.none - No further negotiation is needed. We simply return.
59 * All communication proceeds without encryption.
60 * No public key handshake occurs in this situation.
61 * (both ends must match).
62 *
63 * <name>.pub - We have located the public key for the peer. Both
64 * sides transmit a block encrypted with their private
65 * keys and the peer's public key.
66 *
67 * Both sides receive a block and decrypt it.
68 *
69 * Both sides formulate a reply using the decrypted
70 * block and transmit it.
71 *
72 * communication proceeds with the negotiated session
73 * key (typically AES-256-CBC).
74 *
75 * If we fail to locate the appropriate file and no floating.db exists the
76 * connection is terminated without further action.
77 *
78 * If floating.db exists the connection proceeds with a floating negotiation.
79 */
80typedef union {
81 struct sockaddr sa;
82 struct sockaddr_in sa_in;
83 struct sockaddr_in6 sa_in6;
84} sockaddr_any_t;
85
86void
87hammer2_crypto_negotiate(hammer2_iocom_t *iocom)
88{
89 sockaddr_any_t sa;
90 socklen_t salen = sizeof(sa);
91 char peername[128];
92 char realname[128];
93 hammer2_handshake_t handtx;
94 hammer2_handshake_t handrx;
95 char buf[sizeof(handtx)];
96 char *ptr;
97 char *path;
98 struct stat st;
99 FILE *fp;
100 RSA *keys[3] = { NULL, NULL, NULL };
101 size_t i;
102 size_t blksize;
103 size_t blkmask;
104 ssize_t n;
105 int fd;
106
107 /*
108 * Get the peer IP address for the connection as a string.
109 */
110 if (getpeername(iocom->sock_fd, &sa.sa, &salen) < 0) {
111 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_NOPEER;
112 iocom->flags |= HAMMER2_IOCOMF_EOF;
113 if (DebugOpt)
114 fprintf(stderr, "accept: getpeername() failed\n");
115 goto done;
116 }
117 if (getnameinfo(&sa.sa, salen, peername, sizeof(peername),
118 NULL, 0, NI_NUMERICHOST) < 0) {
119 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_NOPEER;
120 iocom->flags |= HAMMER2_IOCOMF_EOF;
121 if (DebugOpt)
122 fprintf(stderr, "accept: cannot decode sockaddr\n");
123 goto done;
124 }
125 if (DebugOpt) {
126 if (realhostname_sa(realname, sizeof(realname),
127 &sa.sa, salen) == HOSTNAME_FOUND) {
128 fprintf(stderr, "accept from %s (%s)\n",
129 peername, realname);
130 } else {
131 fprintf(stderr, "accept from %s\n", peername);
132 }
133 }
134
135 /*
136 * Find the remote host's public key
137 */
138 asprintf(&path, "%s/%s.pub", HAMMER2_PATH_REMOTE, peername);
139 if ((fp = fopen(path, "r")) == NULL) {
140 free(path);
141 asprintf(&path, "%s/%s.none",
142 HAMMER2_PATH_REMOTE, peername);
143 if (stat(path, &st) < 0) {
144 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_NORKEY;
145 iocom->flags |= HAMMER2_IOCOMF_EOF;
146 if (DebugOpt)
147 fprintf(stderr, "auth failure: unknown host\n");
148 goto done;
149 }
150 if (DebugOpt)
151 fprintf(stderr, "auth succeeded, unencrypted link\n");
152 }
153 if (fp) {
154 keys[0] = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
155 fclose(fp);
156 if (keys[0] == NULL) {
157 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_KEYFMT;
158 iocom->flags |= HAMMER2_IOCOMF_EOF;
159 if (DebugOpt)
160 fprintf(stderr,
161 "auth failure: bad key format\n");
162 goto done;
163 }
164 }
165
166 /*
167 * Get our public and private keys
168 */
169 free(path);
170 asprintf(&path, HAMMER2_DEFAULT_DIR "/rsa.pub");
171 if ((fp = fopen(path, "r")) == NULL) {
172 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_NOLKEY;
173 iocom->flags |= HAMMER2_IOCOMF_EOF;
174 goto done;
175 }
176 keys[1] = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
177 fclose(fp);
178 if (keys[1] == NULL) {
179 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_KEYFMT;
180 iocom->flags |= HAMMER2_IOCOMF_EOF;
181 if (DebugOpt)
182 fprintf(stderr, "auth failure: bad host key format\n");
183 goto done;
184 }
185
186 free(path);
187 asprintf(&path, HAMMER2_DEFAULT_DIR "/rsa.prv");
188 if ((fp = fopen(path, "r")) == NULL) {
189 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_NOLKEY;
190 iocom->flags |= HAMMER2_IOCOMF_EOF;
191 if (DebugOpt)
192 fprintf(stderr, "auth failure: bad host key format\n");
193 goto done;
194 }
195 keys[2] = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
196 fclose(fp);
197 if (keys[2] == NULL) {
198 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_KEYFMT;
199 iocom->flags |= HAMMER2_IOCOMF_EOF;
200 if (DebugOpt)
201 fprintf(stderr, "auth failure: bad host key format\n");
202 goto done;
203 }
204 free(path);
205 path = NULL;
206
207 /*
208 * public key encrypt/decrypt block size.
209 */
210 if (keys[0]) {
211 blksize = (size_t)RSA_size(keys[0]);
212 if (blksize != (size_t)RSA_size(keys[1]) ||
213 blksize != (size_t)RSA_size(keys[2]) ||
214 sizeof(handtx) % blksize != 0) {
215 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_KEYFMT;
216 iocom->flags |= HAMMER2_IOCOMF_EOF;
217 if (DebugOpt)
218 fprintf(stderr, "auth failure: "
219 "key size mismatch\n");
220 goto done;
221 }
222 } else {
223 blksize = sizeof(handtx);
224 }
225 blkmask = blksize - 1;
226
227 bzero(&handrx, sizeof(handrx));
228 bzero(&handtx, sizeof(handtx));
229
230 /*
231 * Fill all unused fields (particular all junk fields) with random
232 * data, and also set the session key.
233 */
234 fd = open("/dev/urandom", O_RDONLY);
235 if (fd < 0 ||
236 fstat(fd, &st) < 0 || /* something wrong */
237 S_ISREG(st.st_mode) || /* supposed to be a RNG dev! */
238 read(fd, &handtx, sizeof(handtx)) != sizeof(handtx)) {
239urandfail:
240 if (fd >= 0)
241 close(fd);
242 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_BADURANDOM;
243 iocom->flags |= HAMMER2_IOCOMF_EOF;
244 if (DebugOpt)
245 fprintf(stderr, "auth failure: bad rng\n");
246 goto done;
247 }
248 if (bcmp(&handrx, &handtx, sizeof(handtx)) == 0)
249 goto urandfail; /* read all zeros */
250 close(fd);
251 ERR_load_crypto_strings();
252
253 /*
254 * Handshake with the remote.
255 *
256 * Encrypt with my private and remote's public
257 * Decrypt with my private and remote's public
258 *
259 * When encrypting we have to make sure our buffer fits within the
260 * modulus, which typically requires bit 7 o the first byte to be
261 * zero. To be safe make sure that bit 7 and bit 6 is zero.
262 */
263 snprintf(handtx.quickmsg, sizeof(handtx.quickmsg), "Testing 1 2 3");
264 handtx.magic = HAMMER2_MSGHDR_MAGIC;
265 handtx.version = 1;
266 handtx.flags = 0;
267 assert(sizeof(handtx.verf) * 4 == sizeof(handtx.sess));
268 bzero(handtx.verf, sizeof(handtx.verf));
269
270 handtx.pad1[0] &= 0x3f; /* message must fit within modulus */
271 handtx.pad2[0] &= 0x3f; /* message must fit within modulus */
272
273 for (i = 0; i < sizeof(handtx.sess); ++i)
274 handtx.verf[i / 4] ^= handtx.sess[i];
275
276 /*
277 * Write handshake buffer to remote
278 */
279 for (i = 0; i < sizeof(handtx); i += blksize) {
280 ptr = (char *)&handtx + i;
281 if (keys[0]) {
282 /*
283 * Since we are double-encrypting we have to make
284 * sure that the result of the first stage does
285 * not blow out the modulus for the second stage.
286 *
287 * The pointer is pointing to the pad*[] area so
288 * we can mess with that until the first stage
289 * is legal.
290 */
291 do {
292 ++*(int *)(ptr + 4);
293 if (RSA_private_encrypt(blksize, ptr, buf,
294 keys[2], RSA_NO_PADDING) < 0) {
295 iocom->ioq_rx.error =
296 HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
297 }
298 } while (buf[0] & 0xC0);
299
300 if (RSA_public_encrypt(blksize, buf, ptr,
301 keys[0], RSA_NO_PADDING) < 0) {
302 iocom->ioq_rx.error =
303 HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
304 }
305 }
306 if (write(iocom->sock_fd, ptr, blksize) != (ssize_t)blksize) {
307 fprintf(stderr, "WRITE ERROR\n");
308 }
309 }
310 if (iocom->ioq_rx.error) {
311 iocom->flags |= HAMMER2_IOCOMF_EOF;
312 if (DebugOpt)
313 fprintf(stderr, "auth failure: key exchange failure "
314 "during encryption\n");
315 goto done;
316 }
317
318 /*
319 * Read handshake buffer from remote
320 */
321 i = 0;
322 while (i < sizeof(handrx)) {
323 ptr = (char *)&handrx + i;
324 n = read(iocom->sock_fd, ptr, blksize - (i & blkmask));
325 if (n <= 0)
326 break;
327 ptr -= (i & blkmask);
328 i += n;
329 if (keys[0] && (i & blkmask) == 0) {
330 if (RSA_private_decrypt(blksize, ptr, buf,
331 keys[2], RSA_NO_PADDING) < 0)
332 iocom->ioq_rx.error =
333 HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
334 if (RSA_public_decrypt(blksize, buf, ptr,
335 keys[0], RSA_NO_PADDING) < 0)
336 iocom->ioq_rx.error =
337 HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
338 }
339 }
340 if (iocom->ioq_rx.error) {
341 iocom->flags |= HAMMER2_IOCOMF_EOF;
342 if (DebugOpt)
343 fprintf(stderr, "auth failure: key exchange failure "
344 "during decryption\n");
345 goto done;
346 }
347
348 /*
349 * Validate the received data. Try to make this a constant-time
350 * algorithm.
351 */
352 if (i != sizeof(handrx)) {
353keyxchgfail:
354 iocom->ioq_rx.error = HAMMER2_IOQ_ERROR_KEYXCHGFAIL;
355 iocom->flags |= HAMMER2_IOCOMF_EOF;
356 if (DebugOpt)
357 fprintf(stderr, "auth failure: key exchange failure\n");
358 goto done;
359 }
360
361 if (handrx.magic == HAMMER2_MSGHDR_MAGIC_REV) {
362 handrx.version = bswap16(handrx.version);
363 handrx.flags = bswap32(handrx.flags);
364 }
365 for (i = 0; i < sizeof(handrx.sess); ++i)
366 handrx.verf[i / 4] ^= handrx.sess[i];
367 n = 0;
368 for (i = 0; i < sizeof(handrx.verf); ++i)
369 n += handrx.verf[i];
370 if (handrx.version != 1)
371 ++n;
372 if (n != 0)
373 goto keyxchgfail;
374
375 if (DebugOpt) {
376 fprintf(stderr, "Remote data: %s\n", handrx.quickmsg);
377 }
378done:
379 if (path)
380 free(path);
381 if (keys[0])
382 RSA_free(keys[0]);
383 if (keys[1])
384 RSA_free(keys[1]);
385 if (keys[1])
386 RSA_free(keys[2]);
387}