9dda6459 |
1 | /* |
2 | * Code for PuTTY to import and export private key files in other |
3 | * SSH clients' formats. |
4 | */ |
5 | |
6 | #include <stdio.h> |
7 | #include <stdlib.h> |
8 | #include <assert.h> |
9 | #include <ctype.h> |
10 | |
11 | #include "ssh.h" |
12 | #include "misc.h" |
13 | |
14 | #define PUT_32BIT(cp, value) do { \ |
15 | (cp)[3] = (value); \ |
16 | (cp)[2] = (value) >> 8; \ |
17 | (cp)[1] = (value) >> 16; \ |
18 | (cp)[0] = (value) >> 24; } while (0) |
19 | |
20 | #define GET_32BIT(cp) \ |
21 | (((unsigned long)(unsigned char)(cp)[0] << 24) | \ |
22 | ((unsigned long)(unsigned char)(cp)[1] << 16) | \ |
23 | ((unsigned long)(unsigned char)(cp)[2] << 8) | \ |
24 | ((unsigned long)(unsigned char)(cp)[3])) |
25 | |
26 | int openssh_encrypted(char *filename); |
27 | struct ssh2_userkey *openssh_read(char *filename, char *passphrase); |
28 | |
29 | /* |
30 | * Given a key type, determine whether we know how to import it. |
31 | */ |
32 | int import_possible(int type) |
33 | { |
34 | if (type == SSH_KEYTYPE_OPENSSH) |
35 | return 1; |
36 | return 0; |
37 | } |
38 | |
39 | /* |
40 | * Given a key type, determine what native key type |
41 | * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once |
42 | * we've imported it. |
43 | */ |
44 | int import_target_type(int type) |
45 | { |
46 | /* |
47 | * There are no known foreign SSH1 key formats. |
48 | */ |
49 | return SSH_KEYTYPE_SSH2; |
50 | } |
51 | |
52 | /* |
53 | * Determine whether a foreign key is encrypted. |
54 | */ |
55 | int import_encrypted(char *filename, int type, char **comment) |
56 | { |
57 | if (type == SSH_KEYTYPE_OPENSSH) { |
58 | *comment = filename; /* OpenSSH doesn't do key comments */ |
59 | return openssh_encrypted(filename); |
60 | } |
61 | return 0; |
62 | } |
63 | |
64 | /* |
65 | * Import an SSH1 key. |
66 | */ |
67 | int import_ssh1(char *filename, int type, struct RSAKey *key, char *passphrase) |
68 | { |
69 | return 0; |
70 | } |
71 | |
72 | /* |
73 | * Import an SSH2 key. |
74 | */ |
75 | struct ssh2_userkey *import_ssh2(char *filename, int type, char *passphrase) |
76 | { |
77 | if (type == SSH_KEYTYPE_OPENSSH) |
78 | return openssh_read(filename, passphrase); |
79 | return NULL; |
80 | } |
81 | |
82 | /* ---------------------------------------------------------------------- |
83 | * Helper routines. (The base64 ones are defined in sshpubk.c.) |
84 | */ |
85 | |
86 | #define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ |
87 | ((c) >= 'a' && (c) <= 'z') || \ |
88 | ((c) >= '0' && (c) <= '9') || \ |
89 | (c) == '+' || (c) == '/' || (c) == '=' \ |
90 | ) |
91 | |
92 | extern int base64_decode_atom(char *atom, unsigned char *out); |
93 | extern int base64_lines(int datalen); |
94 | extern void base64_encode_atom(unsigned char *data, int n, char *out); |
95 | extern void base64_encode(FILE * fp, unsigned char *data, int datalen); |
96 | |
97 | /* |
98 | * Read an ASN.1/BER identifier and length pair. |
99 | * |
100 | * Flags are a combination of the #defines listed below. |
101 | * |
102 | * Returns -1 if unsuccessful; otherwise returns the number of |
103 | * bytes used out of the source data. |
104 | */ |
105 | |
106 | /* ASN.1 tag classes. */ |
107 | #define ASN1_CLASS_UNIVERSAL (0 << 6) |
108 | #define ASN1_CLASS_APPLICATION (1 << 6) |
109 | #define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) |
110 | #define ASN1_CLASS_PRIVATE (3 << 6) |
111 | #define ASN1_CLASS_MASK (3 << 6) |
112 | |
113 | /* Primitive versus constructed bit. */ |
114 | #define ASN1_CONSTRUCTED (1 << 5) |
115 | |
116 | int ber_read_id_len(void *source, int sourcelen, |
117 | int *id, int *length, int *flags) |
118 | { |
119 | unsigned char *p = (unsigned char *) source; |
120 | |
121 | if (sourcelen == 0) |
122 | return -1; |
123 | |
124 | *flags = (*p & 0xE0); |
125 | if ((*p & 0x1F) == 0x1F) { |
126 | *id = 0; |
127 | while (*p & 0x80) { |
128 | *id = (*id << 7) | (*p & 0x7F); |
129 | p++, sourcelen--; |
130 | if (sourcelen == 0) |
131 | return -1; |
132 | } |
133 | *id = (*id << 7) | (*p & 0x7F); |
134 | p++, sourcelen--; |
135 | } else { |
136 | *id = *p & 0x1F; |
137 | p++, sourcelen--; |
138 | } |
139 | |
140 | if (sourcelen == 0) |
141 | return -1; |
142 | |
143 | if (*p & 0x80) { |
144 | int n = *p & 0x7F; |
145 | p++, sourcelen--; |
146 | if (sourcelen < n) |
147 | return -1; |
148 | *length = 0; |
149 | while (n--) |
150 | *length = (*length << 8) | (*p++); |
151 | sourcelen -= n; |
152 | } else { |
153 | *length = *p; |
154 | p++, sourcelen--; |
155 | } |
156 | |
157 | return p - (unsigned char *) source; |
158 | } |
159 | |
160 | /* ---------------------------------------------------------------------- |
161 | * Code to read OpenSSH private keys. |
162 | */ |
163 | |
164 | enum { OSSH_DSA, OSSH_RSA }; |
165 | struct openssh_key { |
166 | int type; |
167 | int encrypted; |
168 | char iv[32]; |
169 | unsigned char *keyblob; |
170 | int keyblob_len, keyblob_size; |
171 | }; |
172 | |
173 | struct openssh_key *load_openssh_key(char *filename) |
174 | { |
175 | struct openssh_key *ret; |
176 | FILE *fp; |
177 | char buffer[256]; |
178 | char *errmsg, *p; |
179 | int headers_done; |
180 | |
181 | ret = smalloc(sizeof(*ret)); |
182 | ret->keyblob = NULL; |
183 | ret->keyblob_len = ret->keyblob_size = 0; |
184 | ret->encrypted = 0; |
185 | memset(ret->iv, 0, sizeof(ret->iv)); |
186 | |
187 | fp = fopen(filename, "r"); |
188 | if (!fp) { |
189 | errmsg = "Unable to open key file"; |
190 | goto error; |
191 | } |
192 | if (!fgets(buffer, sizeof(buffer), fp) || |
193 | 0 != strncmp(buffer, "-----BEGIN ", 11) || |
194 | 0 != strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) { |
195 | errmsg = "File does not begin with OpenSSH key header"; |
196 | goto error; |
197 | } |
198 | if (!strcmp(buffer, "-----BEGIN RSA PRIVATE KEY-----\n")) |
199 | ret->type = OSSH_RSA; |
200 | else if (!strcmp(buffer, "-----BEGIN DSA PRIVATE KEY-----\n")) |
201 | ret->type = OSSH_DSA; |
202 | else { |
203 | errmsg = "Unrecognised key type"; |
204 | goto error; |
205 | } |
206 | |
207 | headers_done = 0; |
208 | while (1) { |
209 | if (!fgets(buffer, sizeof(buffer), fp)) { |
210 | errmsg = "Unexpected end of file"; |
211 | goto error; |
212 | } |
213 | if (0 == strncmp(buffer, "-----END ", 9) && |
214 | 0 == strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) |
215 | break; /* done */ |
216 | if ((p = strchr(buffer, ':')) != NULL) { |
217 | if (headers_done) { |
218 | errmsg = "Header found in body of key data"; |
219 | goto error; |
220 | } |
221 | *p++ = '\0'; |
222 | while (*p && isspace((unsigned char)*p)) p++; |
223 | if (!strcmp(buffer, "Proc-Type")) { |
224 | if (p[0] != '4' || p[1] != ',') { |
225 | errmsg = "Proc-Type is not 4 (only 4 is supported)"; |
226 | goto error; |
227 | } |
228 | p += 2; |
229 | if (!strcmp(p, "ENCRYPTED\n")) |
230 | ret->encrypted = 1; |
231 | } else if (!strcmp(buffer, "DEK-Info")) { |
232 | int i, j; |
233 | |
234 | if (strncmp(p, "DES-EDE3-CBC,", 13)) { |
235 | errmsg = "Ciphers other than DES-EDE3-CBC not supported"; |
236 | goto error; |
237 | } |
238 | p += 13; |
239 | for (i = 0; i < 8; i++) { |
240 | if (1 != sscanf(p, "%2x", &j)) |
241 | break; |
242 | ret->iv[i] = j; |
243 | p += 2; |
244 | } |
245 | if (i < 8) { |
246 | errmsg = "Expected 16-digit iv in DEK-Info"; |
247 | goto error; |
248 | } |
249 | } |
250 | } else { |
251 | headers_done = 1; |
252 | |
253 | p = buffer; |
254 | while (isbase64(p[0]) && isbase64(p[1]) && |
255 | isbase64(p[2]) && isbase64(p[3])) { |
256 | int len; |
257 | unsigned char out[3]; |
258 | |
259 | len = base64_decode_atom(p, out); |
260 | |
261 | if (len <= 0) { |
262 | errmsg = "Invalid base64 encoding"; |
263 | goto error; |
264 | } |
265 | |
266 | if (ret->keyblob_len + len > ret->keyblob_size) { |
267 | ret->keyblob_size = ret->keyblob_len + len + 256; |
268 | ret->keyblob = srealloc(ret->keyblob, ret->keyblob_size); |
269 | } |
270 | |
271 | memcpy(ret->keyblob + ret->keyblob_len, out, len); |
272 | ret->keyblob_len += len; |
273 | |
274 | p += 4; |
275 | } |
276 | |
277 | if (isbase64(*p)) { |
278 | errmsg = "base64 characters left at end of line"; |
279 | goto error; |
280 | } |
281 | } |
282 | } |
283 | |
284 | if (ret->keyblob_len == 0 || !ret->keyblob) { |
285 | errmsg = "Key body not present"; |
286 | goto error; |
287 | } |
288 | |
289 | if (ret->encrypted && ret->keyblob_len % 8 != 0) { |
290 | errmsg = "Encrypted key blob is not a multiple of cipher block size"; |
291 | goto error; |
292 | } |
293 | |
294 | return ret; |
295 | |
296 | error: |
297 | if (ret) { |
298 | if (ret->keyblob) sfree(ret->keyblob); |
299 | sfree(ret); |
300 | } |
301 | return NULL; |
302 | } |
303 | |
304 | int openssh_encrypted(char *filename) |
305 | { |
306 | struct openssh_key *key = load_openssh_key(filename); |
307 | int ret; |
308 | |
309 | if (!key) |
310 | return 0; |
311 | ret = key->encrypted; |
312 | sfree(key->keyblob); |
313 | sfree(key); |
314 | return ret; |
315 | } |
316 | |
317 | struct ssh2_userkey *openssh_read(char *filename, char *passphrase) |
318 | { |
319 | struct openssh_key *key = load_openssh_key(filename); |
320 | struct ssh2_userkey *retkey; |
321 | unsigned char *p; |
322 | int ret, id, len, flags; |
323 | int i, num_integers; |
324 | struct ssh2_userkey *retval = NULL; |
325 | char *errmsg; |
326 | unsigned char *blob; |
327 | int blobptr, privptr; |
328 | char *modptr; |
329 | int modlen; |
330 | |
331 | if (!key) |
332 | return NULL; |
333 | |
334 | if (key->encrypted) { |
335 | /* |
336 | * Derive encryption key from passphrase and iv/salt: |
337 | * |
338 | * - let block A equal MD5(passphrase || iv) |
339 | * - let block B equal MD5(A || passphrase || iv) |
340 | * - block C would be MD5(B || passphrase || iv) and so on |
341 | * - encryption key is the first N bytes of A || B |
342 | */ |
343 | struct MD5Context md5c; |
344 | unsigned char keybuf[32]; |
345 | |
346 | MD5Init(&md5c); |
347 | MD5Update(&md5c, passphrase, strlen(passphrase)); |
348 | MD5Update(&md5c, key->iv, 8); |
349 | MD5Final(keybuf, &md5c); |
350 | |
351 | MD5Init(&md5c); |
352 | MD5Update(&md5c, keybuf, 16); |
353 | MD5Update(&md5c, passphrase, strlen(passphrase)); |
354 | MD5Update(&md5c, key->iv, 8); |
355 | MD5Final(keybuf+16, &md5c); |
356 | |
357 | /* |
358 | * Now decrypt the key blob. |
359 | */ |
360 | des3_decrypt_pubkey_ossh(keybuf, key->iv, |
361 | key->keyblob, key->keyblob_len); |
362 | } |
363 | |
364 | /* |
365 | * Now we have a decrypted key blob, which contains an ASN.1 |
366 | * encoded private key. We must now untangle the ASN.1. |
367 | * |
368 | * We expect the whole key blob to be formatted as a SEQUENCE |
369 | * (0x30 followed by a length code indicating that the rest of |
370 | * the blob is part of the sequence). Within that SEQUENCE we |
371 | * expect to see a bunch of INTEGERs. What those integers mean |
372 | * depends on the key type: |
373 | * |
374 | * - For RSA, we expect the integers to be 0, n, e, d, p, q, |
375 | * dmp1, dmq1, iqmp in that order. (The last three are d mod |
376 | * (p-1), d mod (q-1), inverse of q mod p respectively.) |
377 | * |
378 | * - For DSA, we expect them to be 0, p, q, g, y, x in that |
379 | * order. |
380 | */ |
381 | |
382 | p = key->keyblob; |
383 | |
384 | /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */ |
385 | ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); |
386 | p += ret; |
387 | if (ret < 0 || id != 16) { |
388 | errmsg = "ASN.1 decoding failure"; |
389 | retval = SSH2_WRONG_PASSPHRASE; |
390 | goto error; |
391 | } |
392 | |
393 | /* Expect a load of INTEGERs. */ |
394 | if (key->type == OSSH_RSA) |
395 | num_integers = 9; |
396 | else if (key->type == OSSH_DSA) |
397 | num_integers = 6; |
398 | |
399 | /* |
400 | * Space to create key blob in. |
401 | */ |
402 | blob = smalloc(256+key->keyblob_len); |
403 | PUT_32BIT(blob, 7); |
404 | if (key->type == OSSH_DSA) |
405 | memcpy(blob+4, "ssh-dss", 7); |
406 | else if (key->type == OSSH_RSA) |
407 | memcpy(blob+4, "ssh-rsa", 7); |
408 | blobptr = 4+7; |
409 | privptr = -1; |
410 | |
411 | for (i = 0; i < num_integers; i++) { |
412 | ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, |
413 | &id, &len, &flags); |
414 | p += ret; |
415 | if (ret < 0 || id != 2 || |
416 | key->keyblob+key->keyblob_len-p < len) { |
417 | errmsg = "ASN.1 decoding failure"; |
418 | goto error; |
419 | } |
420 | |
421 | if (i == 0) { |
422 | /* |
423 | * The first integer should be zero always (I think |
424 | * this is some sort of version indication). |
425 | */ |
426 | if (len != 1 || p[0] != 0) { |
427 | errmsg = "Version number mismatch"; |
428 | goto error; |
429 | } |
430 | } else if (key->type == OSSH_RSA) { |
431 | /* |
432 | * Integers 1 and 2 go into the public blob but in the |
433 | * opposite order; integers 3, 4, 5 and 8 go into the |
434 | * private blob. The other two (6 and 7) are ignored. |
435 | */ |
436 | if (i == 1) { |
437 | /* Save the details for after we deal with number 2. */ |
438 | modptr = p; |
439 | modlen = len; |
440 | } else if (i != 6 && i != 7) { |
441 | PUT_32BIT(blob+blobptr, len); |
442 | memcpy(blob+blobptr+4, p, len); |
443 | blobptr += 4+len; |
444 | if (i == 2) { |
445 | PUT_32BIT(blob+blobptr, modlen); |
446 | memcpy(blob+blobptr+4, modptr, modlen); |
447 | blobptr += 4+modlen; |
448 | privptr = blobptr; |
449 | } |
450 | } |
451 | } else if (key->type == OSSH_DSA) { |
452 | /* |
453 | * Integers 1-4 go into the public blob; integer 5 goes |
454 | * into the private blob. |
455 | */ |
456 | PUT_32BIT(blob+blobptr, len); |
457 | memcpy(blob+blobptr+4, p, len); |
458 | blobptr += 4+len; |
459 | if (i == 4) |
460 | privptr = blobptr; |
461 | } |
462 | |
463 | /* Skip past the number. */ |
464 | p += len; |
465 | } |
466 | |
467 | /* |
468 | * Now put together the actual key. Simplest way to do this is |
469 | * to assemble our own key blobs and feed them to the createkey |
470 | * functions; this is a bit faffy but it does mean we get all |
471 | * the sanity checks for free. |
472 | */ |
473 | assert(privptr > 0); /* should have bombed by now if not */ |
474 | retkey = smalloc(sizeof(struct ssh2_userkey)); |
475 | retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss); |
476 | retkey->data = retkey->alg->createkey(blob, privptr, |
477 | blob+privptr, blobptr-privptr); |
478 | if (!retkey->data) { |
479 | sfree(retkey); |
480 | errmsg = "unable to create key data structure"; |
481 | goto error; |
482 | } |
483 | |
484 | retkey->comment = dupstr("imported-openssh-key"); |
485 | if (blob) sfree(blob); |
486 | sfree(key->keyblob); |
487 | sfree(key); |
488 | return retkey; |
489 | |
490 | error: |
491 | if (blob) sfree(blob); |
492 | sfree(key->keyblob); |
493 | sfree(key); |
494 | return retval; |
495 | } |