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