hammer2 - Crypto handshake work for message stream
[dragonfly.git] / sbin / hammer2 / crypto.c
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  */
80 typedef union {
81         struct sockaddr sa;
82         struct sockaddr_in sa_in;
83         struct sockaddr_in6 sa_in6;
84 } sockaddr_any_t;
85
86 void
87 hammer2_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)) {
239 urandfail:
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)) {
353 keyxchgfail:
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         }
378 done:
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 }