chiark / gitweb /
Import release 0.1.14
[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");
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(struct cloc loc, 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     cfgfile_postreadcheck(loc,f);
164     return r;
165 }
166
167 static uint16_t keyfile_get_short(struct cloc loc, FILE *f)
168 {
169     uint16_t r;
170     r=fgetc(f)<<8;
171     r|=fgetc(f);
172     cfgfile_postreadcheck(loc,f);
173     return r;
174 }
175
176 static list_t *rsapriv_apply(closure_t *self, struct cloc loc, dict_t *context,
177                              list_t *args)
178 {
179     struct rsapriv *st;
180     FILE *f;
181     string_t filename;
182     item_t *i;
183     long length;
184     uint8_t *b, *c;
185     int cipher_type;
186     MP_INT e,sig,plain,check;
187
188     st=safe_malloc(sizeof(*st),"rsapriv_apply");
189     st->cl.description="rsapriv";
190     st->cl.type=CL_RSAPRIVKEY;
191     st->cl.apply=NULL;
192     st->cl.interface=&st->ops;
193     st->ops.st=st;
194     st->ops.sign=rsa_sign;
195     st->loc=loc;
196
197     /* Argument is filename pointing to SSH1 private key file */
198     i=list_elem(args,0);
199     if (i) {
200         if (i->type!=t_string) {
201             cfgfatal(i->loc,"rsa-public","first argument must be a string");
202         }
203         filename=i->data.string;
204     } else {
205         filename=""; /* Make compiler happy */
206         cfgfatal(loc,"rsa-private","you must provide a filename\n");
207     }
208
209     f=fopen(filename,"rb");
210     if (!f) {
211         if (just_check_config) {
212             Message(M_WARNING,"rsa-private (%s:%d): cannot open keyfile "
213                     "\"%s\"; assuming it's valid while we check the "
214                     "rest of the configuration\n",loc.file,loc.line,filename);
215             goto assume_valid;
216         } else {
217             fatal_perror("rsa-private (%s:%d): cannot open file \"%s\"",
218                          loc.file,loc.line,filename);
219         }
220     }
221
222     /* Check that the ID string is correct */
223     length=strlen(AUTHFILE_ID_STRING)+1;
224     b=safe_malloc(length,"rsapriv_apply");
225     if (fread(b,length,1,f)!=1 || memcmp(b,AUTHFILE_ID_STRING,length)!=0) {
226         cfgfatal_maybefile(f,loc,"rsa-private","failed to read magic ID"
227                            " string from SSH1 private keyfile \"%s\"\n",
228                            filename);
229     }
230     free(b);
231
232     cipher_type=fgetc(f);
233     keyfile_get_int(loc,f); /* "Reserved data" */
234     if (cipher_type != 0) {
235         cfgfatal(loc,"rsa-private","we don't support encrypted keyfiles\n");
236     }
237
238     /* Read the public key */
239     keyfile_get_int(loc,f); /* Not sure what this is */
240     length=(keyfile_get_short(loc,f)+7)/8;
241     if (length>1024) {
242         cfgfatal(loc,"rsa-private","implausible length %ld for modulus\n",
243                  length);
244     }
245     b=safe_malloc(length,"rsapriv_apply");
246     if (fread(b,length,1,f) != 1) {
247         cfgfatal_maybefile(f,loc,"rsa-private","error reading modulus");
248     }
249     mpz_init(&st->n);
250     read_mpbin(&st->n,b,length);
251     free(b);
252     length=(keyfile_get_short(loc,f)+7)/8;
253     if (length>1024) {
254         cfgfatal(loc,"rsa-private","implausible length %ld for e\n",length);
255     }
256     b=safe_malloc(length,"rsapriv_apply");
257     if (fread(b,length,1,f)!=1) {
258         cfgfatal_maybefile(f,loc,"rsa-private","error reading e\n");
259     }
260     mpz_init(&e);
261     read_mpbin(&e,b,length);
262     free(b);
263     
264     length=keyfile_get_int(loc,f);
265     if (length>1024) {
266         cfgfatal(loc,"rsa-private","implausibly long (%ld) key comment\n",
267                  length);
268     }
269     c=safe_malloc(length+1,"rsapriv_apply");
270     if (fread(c,length,1,f)!=1) {
271         cfgfatal_maybefile(f,loc,"rsa-private","error reading key comment\n");
272     }
273     c[length]=0;
274
275     /* Check that the next two pairs of characters are identical - the
276        keyfile is not encrypted, so they should be */
277
278     if (keyfile_get_short(loc,f) != keyfile_get_short(loc,f)) {
279         cfgfatal(loc,"rsa-private","corrupt keyfile\n");
280     }
281
282     /* Read d */
283     length=(keyfile_get_short(loc,f)+7)/8;
284     if (length>1024) {
285         cfgfatal(loc,"rsa-private","implausibly long (%ld) decryption key\n",
286                  length);
287     }
288     b=safe_malloc(length,"rsapriv_apply");
289     if (fread(b,length,1,f)!=1) {
290         cfgfatal_maybefile(f,loc,"rsa-private",
291                            "error reading decryption key\n");
292     }
293     mpz_init(&st->d);
294     read_mpbin(&st->d,b,length);
295     free(b);
296     
297     if (fclose(f)!=0) {
298         fatal_perror("rsa-private (%s:%d): fclose",loc.file,loc.line);
299     }
300
301     /* Now do trial signature/check to make sure it's a real keypair:
302        sign the comment string! */
303     i=list_elem(args,1);
304     if (i && i->type==t_bool && i->data.bool==False) {
305         Message(M_INFO,"rsa-private (%s:%d): skipping RSA key validity "
306                 "check\n",loc.file,loc.line);
307     } else {
308         mpz_init(&sig);
309         mpz_init(&plain);
310         mpz_init(&check);
311         read_mpbin(&plain,c,strlen(c));
312         mpz_powm(&sig, &plain, &st->d, &st->n);
313         mpz_powm(&check, &sig, &e, &st->n);
314         if (mpz_cmp(&plain,&check)!=0) {
315             cfgfatal(loc,"rsa-private","file \"%s\" does not contain a "
316                      "valid RSA key!\n",filename);
317         }
318         mpz_clear(&sig);
319         mpz_clear(&plain);
320         mpz_clear(&check);
321     }
322
323     free(c);
324     mpz_clear(&e);
325
326 assume_valid:
327     return new_closure(&st->cl);
328 }
329
330 init_module rsa_module;
331 void rsa_module(dict_t *dict)
332 {
333     add_closure(dict,"rsa-private",rsapriv_apply);
334     add_closure(dict,"rsa-public",rsapub_apply);
335 }