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