chiark / gitweb /
549e94b44ad2798688d57b7ecaeceefb1981898d
[become] / src / crypt.c
1 /* -*-c-*-
2  *
3  * $Id: crypt.c,v 1.5 1998/06/18 15:08:49 mdw Exp $
4  *
5  * Cryptographic transfer of `become' requests
6  *
7  * (c) 1998 EBI
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of `become'
13  *
14  * `Become' is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * `Become' is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with `become'; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------*
30  *
31  * $Log: crypt.c,v $
32  * Revision 1.5  1998/06/18 15:08:49  mdw
33  * Paranoia: set close-on-exec flag for seed file.
34  *
35  * Revision 1.4  1998/01/12 16:45:55  mdw
36  * Fix copyright date.
37  *
38  * Revision 1.3  1997/09/26 09:14:58  mdw
39  * Merged blowfish branch into trunk.
40  *
41  * Revision 1.2.2.1  1997/09/26 09:08:02  mdw
42  * Use the Blowfish encryption algorithm instead of IDEA.  This is partly
43  * because I prefer Blowfish (without any particularly strong evidence) but
44  * mainly because IDEA is patented and Blowfish isn't.
45  *
46  * Revision 1.2  1997/08/04 10:24:21  mdw
47  * Sources placed under CVS control.
48  *
49  * Revision 1.1  1997/07/21  13:47:51  mdw
50  * Initial revision
51  *
52  */
53
54 /*----- Header files ------------------------------------------------------*/
55
56 /* --- ANSI headers --- */
57
58 #include <ctype.h>
59 #include <errno.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <time.h>
64
65 /* --- Unix headers --- */
66
67 #include <sys/types.h>
68 #include <sys/time.h>
69 #include <unistd.h>
70 #include <syslog.h>
71 #include <fcntl.h>
72
73 /* --- Local headers --- */
74
75 #include "become.h"
76 #include "blowfish.h"
77 #include "config.h"
78 #include "crypt.h"
79 #include "icrypt.h"
80 #include "md5.h"
81 #include "noise.h"
82 #include "rand.h"
83 #include "tx.h"
84 #include "utils.h"
85
86 /*----- Magic numbers -----------------------------------------------------*/
87
88 #define crypt__timeError 60             /* Seconds error to permit */
89
90 /*----- Main code ---------------------------------------------------------*/
91
92 /* --- @crypt__sessionKey@ --- *
93  *
94  * Arguments:   @const char *seedfile@ = pointer to name of seed file
95  *              @unsigned char *k@ = our secret key
96  *              @unsigned *sk@ = where to store the session key
97  *              @unsigned char *iv@ = where to store the IV
98  *
99  * Returns:     ---
100  *
101  * Use:         Decides on a random session key and initialisation vector.
102  */
103
104 static void crypt__sessionKey(const char *seedfile, unsigned char *k,
105                               unsigned char *sk, unsigned char *iv)
106 {
107   FILE *fp;                             /* File handle for reading */
108   struct flock l;
109   int ok = 1;
110
111   /* --- Open the random seed file --- *
112    *
113    * If I can't manage that, create a new one.
114    */
115
116   if ((fp = fopen(seedfile, "r+")) == 0) {
117     ok = 0;
118     if ((fp = fopen(seedfile, "w+")) == 0)
119       die("can't create random number file: %s", strerror(errno));
120     rand_clear();
121   }
122   if (fcntl(fileno(fp), F_SETFD, 1) < 0) {
123     die("can't set close-on-exec for random number file: %s",
124         strerror(errno));
125   }
126
127   /* --- Lock the seed file against concurrency problems --- */
128
129   l.l_type = F_WRLCK;
130   l.l_whence = SEEK_SET;
131   l.l_start = 0;
132   l.l_len = 0;
133   if (fcntl(fileno(fp), F_SETLKW, &l) < 0)
134     die("can't lock random number file: %s", strerror(errno));
135
136     /* --- Now read the file, and launder the seed --- */
137
138   if (ok)
139     rand_read(fp);
140
141   /* --- Encrypt the pool using the secret key --- */
142
143   {
144     icrypt_job j;
145     icrypt_init(&j, k, BLOWFISH_KEYSIZE, 0);
146     rand_encrypt(&j);
147     burn(j);
148   }
149
150   /* --- Generate the session key and IV --- */
151
152   noise_acquire();
153   rand_extract(sk, BLOWFISH_KEYSIZE);
154   rand_extract(iv, BLOWFISH_BLKSIZE);
155
156   IF_TRACING(TRACE_CRYPTO,
157     traceblk(TRACE_CRYPTO, "crypto: session key:", sk, BLOWFISH_KEYSIZE);
158     traceblk(TRACE_CRYPTO, "crypto: initialisation vector:",
159              iv, BLOWFISH_BLKSIZE);
160   );
161
162   /* --- Write the seed back --- */
163
164   rewind(fp);
165   rand_write(fp);
166   fclose(fp);
167
168 }
169
170 /* --- @crypt_packRequest@ --- *
171  *
172  * Arguments:   @request *rq@ = pointer to request block
173  *              @unsigned char *buff@ = pointer to a buffer
174  *              @time_t t@ = the current time
175  *              @pid_t pid@ = my process ID
176  *              @unsigned char *k@ = pointer to 128-bit key
177  *              @unsigned char *sk@ = where to put the session key
178  *
179  * Returns:     ---
180  *
181  * Use:         Packs a request block into a buffer.  The buffer should have
182  *              space for at least @crq_size@ bytes.  The buffer comes back
183  *              encrypted and ready to send.
184  */
185
186 void crypt_packRequest(request *rq, unsigned char *buff,
187                        time_t t, pid_t pid,
188                        unsigned char *k, unsigned char *sk)
189 {
190   /* --- First, build the easy stuff in the block --- */
191
192   buff[crq_cryptType] = cryptType_blowfish;
193   store32(buff + crq_time, t);
194   store32(buff + crq_pid, pid);
195   store32(buff + crq_from, rq->from);
196   store32(buff + crq_to, rq->to);
197
198   /* --- Now generate session keys and things --- */
199
200   crypt__sessionKey(file_RANDSEED, k, sk, buff + crq_iv);
201   memcpy(buff + crq_session, sk, BLOWFISH_KEYSIZE);
202
203   /* --- The string causes a few problems --- *
204    *
205    * There's a good chance that the string will be a good deal shorter than
206    * the space allowed for it.  This will probably mean lots of zeroes, and a
207    * very easy known-plaintext job for a potential attacker.  (An early
208    * version of this code used @strncpy@ which is even worse!)
209    *
210    * I'll fill the block with random (from @rand@(3) -- nothing too
211    * elaborate) and then encrypt it using Blowfish in CFB mode, using the
212    * first few bytes as the key.  This should provide a sufficiently
213    * unpredictable background for the block.
214    */
215
216   {
217     icrypt_job j;
218     unsigned char *p;
219     unsigned u;
220     md5 md;
221     unsigned char qk[BLOWFISH_KEYSIZE];
222
223     /* --- Initialise the buffer with junk --- */
224
225     srand((unsigned int)(t ^ pid));     /* Seed the (bad) RNG */
226     for (p = buff + crq_cmd; p < buff + crq_cmd + CMDLEN_MAX; p++) {
227       u = rand(); *p = u ^ (u >> 8);
228     }
229
230     /* --- Now make the junk a whole lot harder to predict --- */
231
232     p = buff + crq_cmd;
233     md5_init(&md); md5_buffer(&md, p, CMDLEN_MAX); md5_final(&md, qk);
234     icrypt_init(&j, qk, BLOWFISH_KEYSIZE, 0);
235     icrypt_encrypt(&j, p, p, CMDLEN_MAX);
236     burn(j); burn(qk); burn(md);
237
238     /* --- Copy the string into here --- */
239
240     strcpy((char *)buff + crq_cmd, rq->cmd);
241   }
242
243   /* --- Checksum the finished data --- */
244
245   {
246     md5 md;
247     unsigned char mdbuf[MD5_HASHSIZE];
248
249     md5_init(&md);
250     md5_buffer(&md, buff + crq_cipher, crq_check - crq_cipher);
251     md5_final(&md, mdbuf);
252     memcpy(buff + crq_check, mdbuf, 4);
253     burn(md); burn(mdbuf);
254   }
255
256   /* --- Encrypt the block --- *
257    *
258    * First, encrypt the session key using the master key.  Since the session
259    * key is effectively random, this makes cracking the master key much
260    * harder.  The rest of the block is then encrypted with the session key,
261    * using the IV left over from encrypting the session key.
262    */
263
264   {
265     icrypt_job j;
266
267     T( traceblk(TRACE_CRYPTO, "crypto: plaintext request:",
268                 buff, crq_size); )
269
270     T( traceblk(TRACE_CRYPTO, "crypto: master key:", k, BLOWFISH_KEYSIZE); )
271     T( traceblk(TRACE_CRYPTO, "crypto: initial iv:",
272                 buff + crq_iv, BLOWFISH_BLKSIZE); )
273     T( traceblk(TRACE_CRYPTO, "crypto: session key:",
274                 sk, BLOWFISH_KEYSIZE); )
275
276     icrypt_init(&j, k, BLOWFISH_KEYSIZE, buff + crq_iv);
277
278     icrypt_encrypt(&j, buff + crq_session,
279                    buff + crq_session, BLOWFISH_KEYSIZE);
280     T( traceblk(TRACE_CRYPTO, "crypto: encrypted session key:",
281                 buff + crq_session, BLOWFISH_KEYSIZE); )
282
283     icrypt_reset(&j, sk, BLOWFISH_KEYSIZE, 0);
284
285     T( traceblk(TRACE_CRYPTO, "crypto: partial iv:",
286                 j.iv, BLOWFISH_BLKSIZE); )
287
288     icrypt_encrypt(&j, buff + crq_cipher,
289                    buff + crq_cipher, crq_size - crq_cipher);
290     burn(j);
291
292     T( traceblk(TRACE_CRYPTO, "crypto: ciphertext request:",
293                 buff, crq_size); )
294   }
295 }
296
297 /* --- @crypt_unpackRequest@ --- *
298  *
299  * Arguments:   @reqest *rq@ = pointer to destination request block
300  *              @unsigned char *buff@ = pointer to source buffer
301  *              @unsigned char *k@ = pointer to encryption key
302  *              @unsigned char *sk@ = pointer to where to store session key
303  *              @unsigned char *rpl@ = where to start building reply
304  *
305  * Returns:     Nonzero if it was decrypted OK
306  *
307  * Use:         Decrypts and unpacks a request buffer.
308  */
309
310 int crypt_unpackRequest(request *rq, unsigned char *buff,
311                         unsigned char *k, unsigned char *sk,
312                         unsigned char *rpl)
313 {
314   {
315     /* --- Check the encryption format --- */
316
317     if (buff[crq_cryptType] != cryptType_blowfish)
318       return (0);
319   }
320
321   {
322     /* --- First things first: decrypt the block --- */
323
324     icrypt_job j;
325
326     T( traceblk(TRACE_CRYPTO, "crypto: ciphertext request:",
327                 buff, crq_size); )
328
329     T( traceblk(TRACE_CRYPTO, "crypto: master key:", k, BLOWFISH_KEYSIZE); )
330     T( traceblk(TRACE_CRYPTO, "crypto: initial iv:",
331                 buff + crq_iv, BLOWFISH_BLKSIZE); )
332
333     icrypt_init(&j, k, BLOWFISH_KEYSIZE, buff + crq_iv);
334     T( traceblk(TRACE_CRYPTO, "crypto: job block:", &j, sizeof(j)); )
335
336     T( traceblk(TRACE_CRYPTO, "crypto: encrypted session key:",
337                 buff + crq_session, BLOWFISH_KEYSIZE); )
338     icrypt_decrypt(&j, buff + crq_session,
339                    buff + crq_session, BLOWFISH_KEYSIZE);
340     memcpy(sk, buff + crq_session, BLOWFISH_KEYSIZE);
341     T( traceblk(TRACE_CRYPTO, "crypto: session key:",
342                 sk, BLOWFISH_KEYSIZE); )
343
344     icrypt_reset(&j, sk, BLOWFISH_KEYSIZE, 0);
345
346     T( traceblk(TRACE_CRYPTO, "crypto: partial iv:",
347                 j.iv, BLOWFISH_BLKSIZE); )
348
349     icrypt_decrypt(&j, buff + crq_cipher,
350                    buff + crq_cipher, crq_size - crq_cipher);
351     icrypt_saveIV(&j, rpl + crp_iv);
352
353     T( traceblk(TRACE_CRYPTO, "crypto: plaintext request:",
354                 buff, crq_size); )
355
356     memset(buff + crq_session, 0, BLOWFISH_KEYSIZE); /* Burn, baby, burn */
357     burn(j);
358   }
359
360   {
361     /* --- Check the validity of the data therein --- */
362
363     md5 md;
364     unsigned char mdbuf[MD5_HASHSIZE];
365
366     md5_init(&md);
367     md5_buffer(&md, buff + crq_cipher, crq_check - crq_cipher);
368     md5_final(&md, mdbuf);
369     if (memcmp(mdbuf, buff + crq_check, 4) != 0) {
370       syslog(LOG_INFO, "packet rejected: bad checksum");
371       T( trace(TRACE_CRYPTO, "crypto: bad checksum on incoming request"); )
372       return (0);
373     }
374     burn(md); burn(mdbuf);
375   }
376
377   {
378     /* --- Extract fields from the block --- */
379
380     rq->from = load32(buff + crq_from);
381     rq->to = load32(buff + crq_to);
382     memcpy(rq->cmd, buff + crq_cmd, CMDLEN_MAX);
383   }
384
385   {
386     /* --- Fill in bits of the reply block --- */
387
388     long t = (long)time(0);
389     long u = (long)load32(buff + crq_time);
390
391     if (t - u > crypt__timeError || u - t > crypt__timeError) {
392       syslog(LOG_INFO, "packet rejected: bad time");
393       T( trace(TRACE_CRYPTO, "crypto: bad time on incoming request"); )
394       return (0);
395     }
396     memcpy(rpl + crp_time, buff + crq_time, 8);
397   }
398
399   /* --- Done --- */
400
401   T( trace(TRACE_CRYPTO, "crypto: valid request received"); )
402   return (1);
403 }
404
405 /* --- @crypt_packReply@ --- *
406  *
407  * Arguments:   @char *buff@ = pointer to reply block
408  *              @unsigned char *sk@ = pointer to session key
409  *              @int answer@ = yes or no
410  *
411  * Returns:     ---
412  *
413  * Use:         Packs and encrypts a reply block.
414  */
415
416 void crypt_packReply(unsigned char *buff, unsigned char *sk, int answer)
417 {
418   {
419     /* --- Store the answer --- */
420
421     buff[crp_answer] = (answer != 0);
422   }
423
424   {
425     /* --- Build the checksum --- */
426
427     md5 md;
428     unsigned char mdbuf[MD5_HASHSIZE];
429
430     md5_init(&md);
431     md5_buffer(&md, buff + crp_cipher, crp_check - crp_cipher);
432     md5_final(&md, mdbuf);
433     memcpy(buff + crp_check, mdbuf, 4);
434     burn(md); burn(mdbuf);
435   }
436
437   {
438     /* --- Encrypt the buffer --- */
439
440     icrypt_job j;
441
442     T( traceblk(TRACE_CRYPTO, "crypto: plaintext reply:", buff, crp_size); )
443
444     icrypt_init(&j, sk, BLOWFISH_KEYSIZE, buff + crp_iv);
445     icrypt_encrypt(&j, buff + crp_cipher,
446                    buff + crp_cipher, crp_size - crp_cipher);
447     burn(j);
448
449     T( traceblk(TRACE_CRYPTO, "crypto: ciphertext reply:", buff, crp_size); )
450   }
451 }
452
453 /* --- @crypt_unpackReply@ --- *
454  *
455  * Arguments:   @unsigned char *buff@ = pointer to reply buffer
456  *              @unsigned char *sk@ = pointer to session key
457  *              @time_t t@ = time at which request was sent
458  *              @pid_t pid@ = my process ID
459  *
460  * Returns:     >0 if request granted, zero if denied, <0 if reply rejected
461  *
462  * Use:         Unpacks a reply block, and informs the caller of the outcome.
463  */
464
465 int crypt_unpackReply(unsigned char *buff, unsigned char *sk,
466                       time_t t, pid_t pid)
467 {
468   {
469     /* --- Decrypt my reply block --- */
470
471     icrypt_job j;
472
473     T( traceblk(TRACE_CRYPTO, "crypto: ciphertext reply:", buff, crp_size); )
474
475     icrypt_init(&j, sk, BLOWFISH_KEYSIZE, buff + crp_iv);
476     icrypt_decrypt(&j, buff + crp_cipher,
477                    buff + crp_cipher, crp_size - crp_cipher);
478     burn(j);
479
480     T( traceblk(TRACE_CRYPTO, "crypto: plaintext reply:", buff, crp_size); )
481   }
482
483   {
484     /* --- Check validity --- */
485
486     md5 md;
487     unsigned char mdbuf[MD5_HASHSIZE];
488     char b[8];
489
490     /* --- Check the checksum --- */
491
492     md5_init(&md);
493     md5_buffer(&md, buff + crp_cipher, crp_check - crp_cipher);
494     md5_final(&md, mdbuf);
495     if (memcmp(buff + crp_check, mdbuf, 4) != 0) {
496       syslog(LOG_INFO, "reply rejected: bad checksum");
497       T( trace(TRACE_CRYPTO, "crypto: bad checksum on reply"); )
498       return (-1);
499     }
500
501     /* --- Check the identifier --- */
502
503     store32(b + 0, t); store32(b + 4, pid);
504     if (memcmp(b, buff + crp_time, sizeof(b)) != 0) {
505       syslog(LOG_INFO, "reply rejected: bad identification marker");
506       T( trace(TRACE_CRYPTO, "crypto: bad id on reply"); )
507       return (-1);
508     }
509   }
510
511   /* --- Return the value --- */
512
513   T( trace(TRACE_CRYPTO, "crypto: valid reply received"); )
514   return (buff[crp_answer]);
515 }
516
517 /*----- Test rig ----------------------------------------------------------*/
518
519 #ifdef TEST_RIG
520
521 int main(int argc, char *argv[])
522 {
523   unsigned char buff[8];
524   unsigned char sk[BLOWFISH_KEYSIZE], k[BLOWFISH_KEYSIZE];
525   FILE *fp;
526
527   ego(argv[0]);
528   traceon(stdout, TRACE_CRYPTO);
529   if (argc < 3)
530     die("bad args");
531   fp = fopen(argv[1], "r");
532   if (!fp)
533     die("fopen: %s", strerror(errno));
534   tx_getBits(k, 128, fp);
535   fclose(fp);
536   crypt__sessionKey(argv[2], k, sk, buff);
537   return (0);
538 }
539
540 #endif
541
542 /*----- That's all, folks -------------------------------------------------*/