Added SILC Thread Queue API
[silc.git] / lib / silcssh / silcssh.c
1 /*
2
3   silcssh.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2007 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21
22 /************************* Static utility functions *************************/
23
24 /* Key fields destructor */
25
26 static void silc_ssh_field_dest(void *key, void *context, void *user_context)
27 {
28   silc_free(key);
29   silc_free(context);
30 }
31
32 /* Parse header line from key.  Doesn't return the line termination
33    characters. */
34
35 SilcBool silc_ssh_parse_line(SilcBuffer key, SilcBuffer line,
36                              SilcBool cont)
37 {
38   char *tmp;
39   int i, data_len;
40   SilcBool valid = cont;
41
42   data_len = silc_buffer_len(key);
43   tmp = silc_buffer_data(key);
44   for (i = 0; i < data_len; i++) {
45     /* All header lines must have ':' character */
46     if (!cont && tmp[i] == ':')
47       valid = TRUE;
48
49     if ((data_len - i >= 1 && tmp[i] == '\r') ||
50         (data_len - i >= 1 && tmp[i] == '\n')) {
51
52       if (!valid)
53         return FALSE;
54
55       if (line)
56         silc_buffer_set(line, tmp, i);
57
58       if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n')
59         silc_buffer_pull(key, i + 2);
60       else
61         silc_buffer_pull(key, i + 1);
62
63       return TRUE;
64     }
65   }
66
67   return FALSE;
68 }
69
70 /* Allocate fields hash table */
71
72 SilcHashTable silc_ssh_allocate_fields(void)
73 {
74   return silc_hash_table_alloc(NULL, 0, silc_hash_string_case, NULL,
75                                silc_hash_string_case_compare, NULL,
76                                silc_ssh_field_dest, NULL, TRUE);
77 }
78
79 /* Parse key headers and return them into a hash table */
80
81 SilcHashTable silc_ssh_parse_headers(SilcBuffer key)
82 {
83   SilcHashTable fields;
84   unsigned char *field, *value;
85   SilcBufferStruct line, v;
86   SilcBool quoted = FALSE;
87
88   SILC_LOG_DEBUG(("Parsing SSH key headers"));
89
90   fields = silc_ssh_allocate_fields();
91   if (!fields)
92     return NULL;
93
94   /* Parse the fields */
95   while (silc_buffer_len(key) > 0) {
96     if (!silc_ssh_parse_line(key, &line, FALSE))
97       break;
98
99     /* Get field */
100
101     field = strchr(silc_buffer_data(&line), ':');
102     if (!field)
103       goto err;
104     if (field - silc_buffer_data(&line) > 64)
105       goto err;
106     field = silc_memdup(silc_buffer_data(&line),
107                         field - silc_buffer_data(&line));
108     if (!field)
109       goto err;
110
111     /* Skip ':' and following whitespace */
112     if (!silc_buffer_pull(&line, strlen(field) + 2))
113       goto err;
114
115     /* Get value */
116
117     memset(&v, 0, sizeof(v));
118     silc_buffer_format(&v,
119                        SILC_STR_DATA(silc_buffer_data(&line),
120                                      silc_buffer_len(&line)),
121                        SILC_STR_END);
122
123     /* Handle quoted Comment lines by removing the quotation */
124     if (*silc_buffer_data(&v) == '"' && !strcmp(field, "Comment"))
125       quoted = TRUE;
126
127     /* Handle wrapping value lines */
128     while (silc_buffer_len(&v) > 0) {
129       if (*silc_buffer_data(&v) == '\\') {
130         if (!silc_ssh_parse_line(key, &line, TRUE))
131           goto err;
132         silc_buffer_format(&v,
133                            SILC_STR_DATA(silc_buffer_data(&line),
134                                          silc_buffer_len(&line)),
135                            SILC_STR_END);
136         continue;
137       }
138       silc_buffer_pull(&v, 1);
139     }
140     silc_buffer_start(&v);
141
142     if (silc_buffer_len(&v) > 1024)
143       goto err;
144
145     if (quoted) {
146       /* If the last character is quotation also, remove the quotation */
147       if (*(silc_buffer_data(&v) + silc_buffer_len(&v) - 1) == '"') {
148         silc_buffer_pull(&v, 1);
149         silc_buffer_push_tail(&v, 1);
150       }
151     }
152
153     value = silc_memdup(silc_buffer_data(&v), silc_buffer_len(&v));
154     if (!value)
155       goto err;
156     silc_buffer_purge(&v);
157
158     /* Add to hash table */
159     SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
160     silc_hash_table_add(fields, field, value);
161   }
162
163   return fields;
164
165  err:
166   SILC_LOG_ERROR(("Malformed SSH2 key headers"));
167   silc_hash_table_free(fields);
168   return NULL;
169 }
170
171 /******************************* SILC SSH API *******************************/
172
173 /* Generate key pair */
174
175 SilcBool silc_ssh_generate_key(const char *algorithm,
176                                int bits_len, SilcRng rng,
177                                const char *subject,
178                                SilcPublicKey *ret_public_key,
179                                SilcPrivateKey *ret_private_key)
180 {
181   SilcSshPublicKey pubkey;
182   SilcSshPrivateKey privkey;
183   const SilcPKCSAlgorithm *alg;
184   const SilcPKCSObject *pkcs;
185
186   SILC_LOG_DEBUG(("Generating SSH2 %s key pair with key length %d bits",
187                   algorithm, bits_len));
188
189   if (!rng)
190     return FALSE;
191
192   pkcs = silc_pkcs_find_pkcs(SILC_PKCS_SSH2);
193   if (!pkcs)
194     return FALSE;
195
196   /* Allocate SSH public key */
197   pubkey = silc_calloc(1, sizeof(*pubkey));
198   if (!pubkey)
199     return FALSE;
200
201   /* Allocate algorithm */
202   alg = silc_pkcs_find_algorithm(algorithm, "ssh");
203   if (!alg) {
204     SILC_LOG_ERROR(("Public key algorithm %s/ssh not supported", algorithm));
205     silc_free(pubkey);
206     return FALSE;
207   }
208   pubkey->pkcs = alg;
209   pubkey->type = SILC_SSH_KEY_OPENSSH;
210
211   /* Allocate SSH private key */
212   privkey = silc_calloc(1, sizeof(*privkey));
213   if (!privkey) {
214     silc_free(pubkey);
215     return FALSE;
216   }
217   privkey->pkcs = alg;
218   privkey->type = SILC_SSH_KEY_OPENSSH;
219
220   /* Allocate public key */
221   *ret_public_key = silc_calloc(1, sizeof(**ret_public_key));
222   if (!(*ret_public_key)) {
223     silc_free(pubkey);
224     silc_free(privkey);
225     return FALSE;
226   }
227   (*ret_public_key)->pkcs = (SilcPKCSObject *)pkcs;
228   (*ret_public_key)->alg = alg;
229   (*ret_public_key)->public_key = pubkey;
230
231   /* Allocate private key */
232   *ret_private_key = silc_calloc(1, sizeof(**ret_private_key));
233   if (!(*ret_private_key)) {
234     silc_free(pubkey);
235     silc_free(privkey);
236     silc_free(*ret_public_key);
237     return FALSE;
238   }
239   (*ret_private_key)->pkcs = (SilcPKCSObject *)pkcs;
240   (*ret_private_key)->alg = alg;
241   (*ret_private_key)->private_key = privkey;
242
243   /* Generate the algorithm key pair */
244   if (!alg->generate_key(alg, bits_len, rng, &pubkey->public_key,
245                          &privkey->private_key)) {
246     silc_free(pubkey);
247     silc_free(privkey);
248     silc_free(*ret_public_key);
249     silc_free(*ret_private_key);
250     return FALSE;
251   }
252
253   if (subject)
254     silc_ssh_public_key_add_field(pubkey, "Subject", subject);
255
256   return TRUE;
257 }
258
259 /* Decode SSH public key. */
260
261 int silc_ssh_public_key_decode(unsigned char *key, SilcUInt32 key_len,
262                                SilcSshPublicKey *ret_public_key)
263 {
264   SilcSshPublicKey public_key;
265   const SilcPKCSAlgorithm *alg;
266   SilcBufferStruct keybuf;
267   char *type = NULL;
268
269   SILC_LOG_DEBUG(("Parse SSH2 public key"));
270
271   if (!ret_public_key)
272     return 0;
273
274   public_key = silc_calloc(1, sizeof(*public_key));
275   if (!public_key)
276     return 0;
277
278   silc_buffer_set(&keybuf, key, key_len);
279
280   SILC_LOG_HEXDUMP(("SSH public key, len %d", key_len), key, key_len);
281
282   /* Parse public key type */
283   if (silc_buffer_unformat(&keybuf,
284                            SILC_STR_ADVANCE,
285                            SILC_STR_UI32_STRING_ALLOC(&type),
286                            SILC_STR_END) < 0) {
287     SILC_LOG_ERROR(("Malformed SSH2 public key"));
288     goto err;
289   }
290
291   SILC_LOG_DEBUG(("SSH2 public key type %s", type));
292
293   if (!strcmp(type, "ssh-rsa")) {
294     /* RSA public key */
295     alg = silc_pkcs_find_algorithm("rsa", "ssh");
296     if (!alg) {
297       SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type));
298       goto err;
299     }
300     public_key->pkcs = alg;
301
302   } else if (!strcmp(type, "ssh-dss")) {
303     /* DSS public key */
304     alg = silc_pkcs_find_algorithm("dsa", "ssh");
305     if (!alg) {
306       SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type));
307       goto err;
308     }
309     public_key->pkcs = alg;
310
311   } else {
312     SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type));
313     goto err;
314   }
315
316   /* Parse the algorithm specific public key */
317   if (!alg->import_public_key(alg, silc_buffer_data(&keybuf),
318                               silc_buffer_len(&keybuf),
319                               &public_key->public_key))
320     goto err;
321
322   silc_free(type);
323
324   *ret_public_key = public_key;
325
326   return key_len;
327
328  err:
329   silc_free(type);
330   silc_free(public_key);
331   return 0;
332 }
333
334 /* Encode SSH public key */
335
336 unsigned char *silc_ssh_public_key_encode(SilcStack stack,
337                                           SilcSshPublicKey public_key,
338                                           SilcUInt32 *ret_key_len)
339 {
340   const SilcPKCSAlgorithm *alg = public_key->pkcs;
341   SilcBufferStruct buf;
342   unsigned char *pk = NULL, tmp[16];
343   SilcUInt32 pk_len;
344
345   SILC_LOG_DEBUG(("Encode SSH2 public key"));
346
347   /* Get algorithm name */
348   if (!strcmp(alg->name, "rsa"))
349     silc_snprintf(tmp, sizeof(tmp), "ssh-rsa");
350   else if (!strcmp(alg->name, "dsa"))
351     silc_snprintf(tmp, sizeof(tmp), "ssh-dss");
352   else
353     return NULL;
354
355   /* Export PKCS algorithm public key */
356   if (alg->export_public_key)
357     pk = alg->export_public_key(alg, stack, public_key->public_key, &pk_len);
358   if (!pk) {
359     SILC_LOG_ERROR(("Error exporting PKCS algorithm key"));
360     return NULL;
361   }
362
363   /* Encode public key */
364   memset(&buf, 0, sizeof(buf));
365   if (silc_buffer_sformat(stack, &buf,
366                           SILC_STR_UI_INT(strlen(tmp)),
367                           SILC_STR_UI32_STRING(tmp),
368                           SILC_STR_UI_XNSTRING(pk, pk_len),
369                           SILC_STR_END) < 0) {
370     silc_sfree(stack, pk);
371     return NULL;
372   }
373
374   silc_sfree(stack, pk);
375   pk = silc_buffer_steal(&buf, ret_key_len);
376
377   return pk;
378 }
379
380 /* Free public key */
381
382 void silc_ssh_public_key_free(SilcSshPublicKey public_key)
383 {
384   if (public_key->fields)
385     silc_hash_table_free(public_key->fields);
386   public_key->pkcs->public_key_free(public_key->pkcs,
387                                     public_key->public_key);
388   silc_free(public_key);
389 }
390
391 /* Return public key header field value */
392
393 const char *silc_ssh_public_key_get_field(SilcSshPublicKey public_key,
394                                           const char *field)
395 {
396   char *value;
397
398   if (!field || !public_key->fields)
399     return NULL;
400
401   if (!silc_hash_table_find(public_key->fields, (void *)field,
402                             NULL, (void *)&value))
403     return NULL;
404
405   return (const char *)value;
406 }
407
408 /* Add public key header value */
409
410 SilcBool silc_ssh_public_key_add_field(SilcSshPublicKey public_key,
411                                        const char *field,
412                                        const char *value)
413 {
414   if (!field || !value)
415     return FALSE;
416
417   if (!public_key->fields) {
418     public_key->fields =
419       silc_hash_table_alloc(NULL, 0, silc_hash_string_case, NULL,
420                             silc_hash_string_case_compare, NULL,
421                             silc_ssh_field_dest, NULL, TRUE);
422     if (!public_key->fields)
423       return FALSE;
424   }
425
426   return silc_hash_table_add(public_key->fields, strdup(field), strdup(value));
427 }
428
429 /* Set public key type */
430
431 void silc_ssh_public_key_set_type(SilcSshPublicKey public_key,
432                                   SilcSshKeyType type)
433 {
434   public_key->type = type;
435 }