chiark / gitweb /
Import release 0.1.13
[secnet.git] / rsa.c
1 #include <stdio.h>
2 #include <gmp.h>
3 #include "secnet.h"
4 #include "util.h"
5
6 #define AUTHFILE_ID_STRING "SSH PRIVATE KEY FILE FORMAT 1.1\n"
7
8 struct rsapriv {
9     closure_t cl;
10     struct rsaprivkey_if ops;
11     struct cloc loc;
12     MP_INT d;
13     MP_INT n;
14 };
15 struct rsapub {
16     closure_t cl;
17     struct rsapubkey_if ops;
18     struct cloc loc;
19     MP_INT e;
20     MP_INT n;
21 };
22 /* Sign data. NB data must be smaller than modulus */
23
24 static char *hexchars="0123456789abcdef";
25
26 static string_t rsa_sign(void *sst, uint8_t *data, uint32_t datalen)
27 {
28     struct rsapriv *st=sst;
29     MP_INT a, b;
30     char buff[2048];
31     int msize, i;
32     string_t signature;
33
34     mpz_init(&a);
35     mpz_init(&b);
36
37     msize=mpz_sizeinbase(&st->n, 16);
38
39     if (datalen*2+4>=msize) {
40         fatal("rsa_sign: message too big\n");
41     }
42
43     strcpy(buff,"0001");
44
45     for (i=0; i<datalen; i++) {
46         buff[4+i*2]=hexchars[(data[i]&0xf0)>>4];
47         buff[5+i*2]=hexchars[data[i]&0xf];
48     }
49     buff[4+datalen*2]=0;
50     
51     for (i=datalen*2+4; i<msize; i++)
52         buff[i]='f';
53
54     buff[msize]=0;
55
56     mpz_set_str(&a, buff, 16);
57
58     mpz_powm(&b, &a, &st->d, &st->n);
59
60     signature=write_mpstring(&b);
61
62     mpz_clear(&b);
63     mpz_clear(&a);
64     return signature;
65 }
66
67 static bool_t rsa_sig_check(void *sst, uint8_t *data, uint32_t datalen,
68                             string_t signature)
69 {
70     struct rsapub *st=sst;
71     MP_INT a, b, c;
72     char buff[2048];
73     int msize, i;
74     bool_t ok;
75
76     mpz_init(&a);
77     mpz_init(&b);
78     mpz_init(&c);
79
80     msize=mpz_sizeinbase(&st->n, 16);
81
82     strcpy(buff,"0001");
83
84     for (i=0; i<datalen; i++) {
85         buff[4+i*2]=hexchars[(data[i]&0xf0)>>4];
86         buff[5+i*2]=hexchars[data[i]&0xf];
87     }
88     buff[4+datalen*2]=0;
89
90     for (i=datalen*2+4; i<msize; i++)
91         buff[i]='f';
92
93     buff[msize]=0;
94
95     mpz_set_str(&a, buff, 16);
96
97     mpz_set_str(&b, signature, 16);
98
99     mpz_powm(&c, &b, &st->e, &st->n);
100
101     ok=(mpz_cmp(&a, &c)==0);
102
103     mpz_clear(&c);
104     mpz_clear(&b);
105     mpz_clear(&a);
106
107     return ok;
108 }
109
110 static list_t *rsapub_apply(closure_t *self, struct cloc loc, dict_t *context,
111                             list_t *args)
112 {
113     struct rsapub *st;
114     item_t *i;
115     string_t e,n;
116
117     st=safe_malloc(sizeof(*st),"rsapub_apply");
118     st->cl.description="rsapub";
119     st->cl.type=CL_RSAPUBKEY;
120     st->cl.apply=NULL;
121     st->cl.interface=&st->ops;
122     st->ops.st=st;
123     st->ops.check=rsa_sig_check;
124     st->loc=loc;
125
126     i=list_elem(args,0);
127     if (i) {
128         if (i->type!=t_string) {
129             cfgfatal(i->loc,"rsa-public","first argument must be a string");
130         }
131         e=i->data.string;
132         if (mpz_init_set_str(&st->e,e,10)!=0) {
133             cfgfatal(i->loc,"rsa-public","encryption key \"%s\" is not a "
134                      "decimal number string\n",e);
135         }
136     } else {
137         cfgfatal(loc,"rsa-public","you must provide an encryption key\n");
138     }
139     
140     i=list_elem(args,1);
141     if (i) {
142         if (i->type!=t_string) {
143             cfgfatal(i->loc,"rsa-public","second argument must be a string");
144         }
145         n=i->data.string;
146         if (mpz_init_set_str(&st->n,n,10)!=0) {
147             cfgfatal(i->loc,"rsa-public","modulus \"%s\" is not a decimal "
148                      "number string\n",n);
149         }
150     } else {
151         cfgfatal(loc,"rsa-public","you must provide a modulus\n");
152     }
153     return new_closure(&st->cl);
154 }
155
156 static uint32_t keyfile_get_int(FILE *f)
157 {
158     uint32_t r;
159     r=fgetc(f)<<24;
160     r|=fgetc(f)<<16;
161     r|=fgetc(f)<<8;
162     r|=fgetc(f);
163     return r;
164 }
165
166 static uint16_t keyfile_get_short(FILE *f)
167 {
168     uint16_t r;
169     r=fgetc(f)<<8;
170     r|=fgetc(f);
171     return r;
172 }
173
174 static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
175                              list_t *args)
176 {
177     struct rsapriv *st;
178     FILE *f;
179     string_t filename;
180     item_t *i;
181     long length;
182     uint8_t *b, *c;
183     int cipher_type;
184     MP_INT e,sig,plain,check;
185
186     st=safe_malloc(sizeof(*st),"rsapriv_apply");
187     st->cl.description="rsapriv";
188     st->cl.type=CL_RSAPRIVKEY;
189     st->cl.apply=NULL;
190     st->cl.interface=&st->ops;
191     st->ops.st=st;
192     st->ops.sign=rsa_sign;
193     st->loc=loc;
194
195     /* Argument is filename pointing to SSH1 private key file */
196     i=list_elem(args,0);
197     if (i) {
198         if (i->type!=t_string) {
199             cfgfatal(i->loc,"rsa-public","first argument must be a string");
200         }
201         filename=i->data.string;
202     } else {
203         filename=""; /* Make compiler happy */
204         cfgfatal(loc,"rsa-private","you must provide a filename\n");
205     }
206
207     f=fopen(filename,"rb");
208     if (!f) {
209         if (just_check_config) {
210             Message(M_WARNING,"rsa-private (%s:%d): cannot open keyfile "
211                     "\"%s\"; assuming it's valid while we check the "
212                     "rest of the configuration\n",loc.file,loc.line,filename);
213             goto assume_valid;
214         } else {
215             fatal_perror("rsa-private (%s:%d): cannot open file \"%s\"",
216                          loc.file,loc.line,filename);
217         }
218     }
219
220     /* Check that the ID string is correct */
221     length=strlen(AUTHFILE_ID_STRING)+1;
222     b=safe_malloc(length,"rsapriv_apply");
223     if (fread(b,length,1,f)!=1 || memcmp(b,AUTHFILE_ID_STRING,length)!=0) {
224         cfgfatal(loc,"rsa-private","file \"%s\" is not a "
225                  "SSH1 private keyfile\n",filename);
226     }
227     free(b);
228
229     cipher_type=fgetc(f);
230     keyfile_get_int(f); /* "Reserved data" */
231     if (cipher_type != 0) {
232         cfgfatal(loc,"rsa-private","we don't support encrypted keyfiles\n");
233     }
234
235     /* Read the public key */
236     keyfile_get_int(f); /* Not sure what this is */
237     length=(keyfile_get_short(f)+7)/8;
238     if (length>1024) {
239         cfgfatal(loc,"rsa-private","implausible length %ld for modulus\n",
240                  length);
241     }
242     b=safe_malloc(length,"rsapriv_apply");
243     if (fread(b,length,1,f) != 1) {
244         cfgfatal(loc,"rsa-private","error reading modulus\n");
245     }
246     mpz_init(&st->n);
247     read_mpbin(&st->n,b,length);
248     free(b);
249     length=(keyfile_get_short(f)+7)/8;
250     if (length>1024) {
251         cfgfatal(loc,"rsa-private","implausible length %ld for e\n",length);
252     }
253     b=safe_malloc(length,"rsapriv_apply");
254     if (fread(b,length,1,f)!=1) {
255         cfgfatal(loc,"rsa-private","error reading e\n");
256     }
257     mpz_init(&e);
258     read_mpbin(&e,b,length);
259     free(b);
260     
261     length=keyfile_get_int(f);
262     if (length>1024) {
263         cfgfatal(loc,"rsa-private","implausibly long (%ld) key comment\n",
264                  length);
265     }
266     c=safe_malloc(length+1,"rsapriv_apply");
267     if (fread(c,length,1,f)!=1) {
268         cfgfatal(loc,"rsa-private","error reading key comment\n");
269     }
270     c[length]=0;
271
272     /* Check that the next two pairs of characters are identical - the
273        keyfile is not encrypted, so they should be */
274     if (keyfile_get_short(f) != keyfile_get_short(f)) {
275         cfgfatal(loc,"rsa-private","corrupt keyfile\n");
276     }
277
278     /* Read d */
279     length=(keyfile_get_short(f)+7)/8;
280     if (length>1024) {
281         cfgfatal(loc,"rsa-private","implausibly long (%ld) decryption key\n",
282                  length);
283     }
284     b=safe_malloc(length,"rsapriv_apply");
285     if (fread(b,length,1,f)!=1) {
286         cfgfatal(loc,"rsa-private","error reading decryption key\n");
287     }
288     mpz_init(&st->d);
289     read_mpbin(&st->d,b,length);
290     free(b);
291     
292     if (fclose(f)!=0) {
293         fatal_perror("rsa-private (%s:%d): fclose",loc.file,loc.line);
294     }
295
296     /* Now do trial signature/check to make sure it's a real keypair:
297        sign the comment string! */
298     i=list_elem(args,1);
299     if (i && i->type==t_bool && i->data.bool==False) {
300         Message(M_INFO,"rsa-private (%s:%d): skipping RSA key validity "
301                 "check\n",loc.file,loc.line);
302     } else {
303         mpz_init(&sig);
304         mpz_init(&plain);
305         mpz_init(&check);
306         read_mpbin(&plain,c,strlen(c));
307         mpz_powm(&sig, &plain, &st->d, &st->n);
308         mpz_powm(&check, &sig, &e, &st->n);
309         if (mpz_cmp(&plain,&check)!=0) {
310             cfgfatal(loc,"rsa-private","file \"%s\" does not contain a "
311                      "valid RSA key!\n",filename);
312         }
313         mpz_clear(&sig);
314         mpz_clear(&plain);
315         mpz_clear(&check);
316     }
317
318     free(c);
319     mpz_clear(&e);
320
321 assume_valid:
322     return new_closure(&st->cl);
323 }
324
325 init_module rsa_module;
326 void rsa_module(dict_t *dict)
327 {
328     add_closure(dict,"rsa-private",rsapriv_apply);
329     add_closure(dict,"rsa-public",rsapub_apply);
330 }