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