]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
1 | /* |
2 | * sshKey.cpp - Standalone SSH key parser and converter. Uses libcrypto for | |
3 | * representing and storing RSA and DSA keys and for | |
4 | * writing and reading BIGNUMS to/from memory. | |
5 | */ | |
6 | ||
7 | #include <stdlib.h> | |
8 | #include <strings.h> | |
9 | #include <stdio.h> | |
10 | #include <unistd.h> | |
11 | #include <CommonCrypto/CommonDigest.h> | |
12 | #include <CommonCrypto/CommonCryptor.h> | |
13 | #include <security_cdsa_utils/cuFileIo.h> | |
14 | #include <security_cdsa_utils/cuEnc64.h> | |
15 | #include <openssl/rsa.h> | |
16 | #include <openssl/dsa.h> | |
17 | #include <CoreFoundation/CoreFoundation.h> | |
18 | #include <security_utilities/devrandom.h> | |
19 | #include <ctype.h> | |
20 | ||
21 | #define dprintf(s...) printf(s) | |
22 | ||
23 | static void usage(char **argv) | |
24 | { | |
25 | printf("usage: %s [options]\n", argv[0]); | |
26 | printf("Options:\n"); | |
27 | printf(" -i inFile\n"); | |
28 | printf(" -o outFile\n"); | |
29 | printf(" -v -- private key input; default is public\n"); | |
30 | printf(" -V -- private key output; default is public\n"); | |
31 | printf(" -d -- DSA; default is RSA\n"); | |
32 | printf(" -r -- parse & print inFile\n"); | |
33 | printf(" -f ssh1|ssh2 -- input format; default = ssh2\n"); | |
34 | printf(" -F ssh1|ssh2 -- output format; default = ssh2\n"); | |
35 | printf(" -p password\n"); | |
36 | printf(" -P -- no password; private keys in the clear\n"); | |
37 | printf(" -c comment\n"); | |
38 | exit(1); | |
39 | } | |
40 | ||
41 | static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n"; | |
42 | ||
43 | /* from openssh cipher.h */ | |
44 | #define SSH_CIPHER_NONE 0 /* no encryption */ | |
45 | #define SSH_CIPHER_IDEA 1 /* IDEA CFB */ | |
46 | #define SSH_CIPHER_DES 2 /* DES CBC */ | |
47 | #define SSH_CIPHER_3DES 3 /* 3DES CBC */ | |
48 | #define SSH_CIPHER_BROKEN_TSS 4 /* TRI's Simple Stream encryption CBC */ | |
49 | #define SSH_CIPHER_BROKEN_RC4 5 /* Alleged RC4 */ | |
50 | #define SSH_CIPHER_BLOWFISH 6 | |
51 | #define SSH_CIPHER_RESERVED 7 | |
52 | ||
53 | #define SSH2_RSA_HEADER "ssh-rsa" | |
54 | #define SSH2_DSA_HEADER "ssh-dss" | |
55 | ||
56 | #pragma mark --- commmon code --- | |
57 | ||
58 | static uint32_t readUint32( | |
59 | const unsigned char *&cp, // IN/OUT | |
60 | unsigned &len) // IN/OUT | |
61 | { | |
62 | uint32_t r = 0; | |
63 | ||
64 | for(unsigned dex=0; dex<sizeof(uint32_t); dex++) { | |
65 | r <<= 8; | |
66 | r |= *cp++; | |
67 | } | |
68 | len -= 4; | |
69 | return r; | |
70 | } | |
71 | ||
72 | static uint16_t readUint16( | |
73 | const unsigned char *&cp, // IN/OUT | |
74 | unsigned &len) // IN/OUT | |
75 | { | |
76 | uint16_t r = *cp++; | |
77 | r <<= 8; | |
78 | r |= *cp++; | |
79 | len -= 2; | |
80 | return r; | |
81 | } | |
82 | ||
83 | static void appendUint32( | |
84 | CFMutableDataRef cfOut, | |
85 | uint32_t ui) | |
86 | { | |
87 | UInt8 buf[sizeof(uint32_t)]; | |
88 | ||
89 | for(int dex=(sizeof(uint32_t) - 1); dex>=0; dex--) { | |
90 | buf[dex] = ui & 0xff; | |
91 | ui >>= 8; | |
92 | } | |
93 | CFDataAppendBytes(cfOut, buf, sizeof(uint32_t)); | |
94 | } | |
95 | ||
96 | static void appendUint16( | |
97 | CFMutableDataRef cfOut, | |
98 | uint16_t ui) | |
99 | { | |
100 | UInt8 buf[sizeof(uint16_t)]; | |
101 | ||
102 | buf[1] = ui & 0xff; | |
103 | ui >>= 8; | |
104 | buf[0] = ui; | |
105 | CFDataAppendBytes(cfOut, buf, sizeof(uint16_t)); | |
106 | } | |
107 | ||
108 | /* parse text as decimal, return BIGNUM */ | |
109 | static BIGNUM *parseDecimalBn( | |
110 | const unsigned char *cp, | |
111 | unsigned len) | |
112 | { | |
113 | for(unsigned dex=0; dex<len; dex++) { | |
114 | char c = *cp; | |
115 | if((c < '0') || (c > '9')) { | |
116 | return NULL; | |
117 | } | |
118 | } | |
119 | char *str = (char *)malloc(len + 1); | |
120 | memmove(str, cp, len); | |
121 | str[len] = '\0'; | |
122 | BIGNUM *bn = NULL; | |
123 | BN_dec2bn(&bn, str); | |
124 | free(str); | |
125 | return bn; | |
126 | } | |
127 | ||
128 | /* Read BIGNUM, OpenSSH-1 version */ | |
129 | static BIGNUM *readBigNum( | |
130 | const unsigned char *&cp, // IN/OUT | |
131 | unsigned &remLen) // IN/OUT | |
132 | { | |
133 | if(remLen < sizeof(uint16_t)) { | |
134 | dprintf("readBigNum: short record(1)\n"); | |
135 | return NULL; | |
136 | } | |
137 | uint16_t numBits = readUint16(cp, remLen); | |
138 | unsigned bytes = (numBits + 7) / 8; | |
139 | if(remLen < bytes) { | |
140 | dprintf("readBigNum: short record(2)\n"); | |
141 | return NULL; | |
142 | } | |
143 | BIGNUM *bn = BN_bin2bn(cp, bytes, NULL); | |
144 | if(bn == NULL) { | |
145 | dprintf("readBigNum: BN_bin2bn error\n"); | |
146 | return NULL; | |
147 | } | |
148 | cp += bytes; | |
149 | remLen -= bytes; | |
150 | return bn; | |
151 | } | |
152 | ||
153 | /* Write BIGNUM, OpenSSH-1 version */ | |
154 | static int appendBigNum( | |
155 | CFMutableDataRef cfOut, | |
156 | const BIGNUM *bn) | |
157 | { | |
158 | /* 16 bits of numbits */ | |
159 | unsigned numBits = BN_num_bits(bn); | |
160 | appendUint16(cfOut, numBits); | |
161 | ||
162 | /* serialize the bytes */ | |
163 | int numBytes = (numBits + 7) / 8; | |
164 | unsigned char outBytes[numBytes]; // gcc is so cool... | |
165 | int moved = BN_bn2bin(bn, outBytes); | |
166 | if(moved != numBytes) { | |
167 | dprintf("appendBigNum: BN_bn2bin() screwup\n"); | |
168 | return -1; | |
169 | } | |
170 | CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes); | |
171 | return 0; | |
172 | } | |
173 | ||
174 | /* read BIGNUM, OpenSSH-2 mpint version */ | |
175 | static BIGNUM *readBigNum2( | |
176 | const unsigned char *&cp, // IN/OUT | |
177 | unsigned &remLen) // IN/OUT | |
178 | { | |
179 | if(remLen < 4) { | |
180 | dprintf("readBigNum2: short record(1)\n"); | |
181 | return NULL; | |
182 | } | |
183 | uint32_t bytes = readUint32(cp, remLen); | |
184 | if(remLen < bytes) { | |
185 | dprintf("readBigNum2: short record(2)\n"); | |
186 | return NULL; | |
187 | } | |
188 | BIGNUM *bn = BN_bin2bn(cp, bytes, NULL); | |
189 | if(bn == NULL) { | |
190 | dprintf("readBigNum2: BN_bin2bn error\n"); | |
191 | return NULL; | |
192 | } | |
193 | cp += bytes; | |
194 | remLen -= bytes; | |
195 | return bn; | |
196 | } | |
197 | ||
198 | /* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */ | |
199 | static int appendBigNum2( | |
200 | CFMutableDataRef cfOut, | |
201 | const BIGNUM *bn) | |
202 | { | |
203 | if(bn == NULL) { | |
204 | dprintf("appendBigNum2: NULL bn"); | |
205 | return -1; | |
206 | } | |
207 | if (BN_is_zero(bn)) { | |
208 | appendUint32(cfOut, 0); | |
209 | return 0; | |
210 | } | |
211 | if(bn->neg) { | |
212 | dprintf("appendBigNum2: negative numbers not supported\n"); | |
213 | return -1; | |
214 | } | |
215 | int numBytes = BN_num_bytes(bn); | |
216 | unsigned char buf[numBytes]; | |
217 | int moved = BN_bn2bin(bn, buf); | |
218 | if(moved != numBytes) { | |
219 | dprintf("appendBigNum: BN_bn2bin() screwup\n"); | |
220 | return -1; | |
221 | } | |
222 | bool appendZero = false; | |
223 | if(buf[0] & 0x80) { | |
224 | /* prepend leading zero to make it positive */ | |
225 | appendZero = true; | |
226 | numBytes++; // to encode the correct 4-byte length | |
227 | } | |
228 | appendUint32(cfOut, (uint32_t)numBytes); | |
229 | if(appendZero) { | |
230 | UInt8 z = 0; | |
231 | CFDataAppendBytes(cfOut, &z, 1); | |
232 | numBytes--; // to append the correct number of bytes | |
233 | } | |
234 | CFDataAppendBytes(cfOut, buf, numBytes); | |
235 | memset(buf, 0, numBytes); | |
236 | return 0; | |
237 | } | |
238 | ||
239 | /* Write BIGNUM, OpenSSH-1 decimal (public key) version */ | |
240 | static int appendBigNumDec( | |
241 | CFMutableDataRef cfOut, | |
242 | const BIGNUM *bn) | |
243 | { | |
244 | char *buf = BN_bn2dec(bn); | |
245 | if(buf == NULL) { | |
246 | dprintf("appendBigNumDec: BN_bn2dec() error"); | |
247 | return -1; | |
248 | } | |
249 | CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf)); | |
250 | OPENSSL_free(buf); | |
251 | return 0; | |
252 | } | |
253 | ||
254 | /* write string, OpenSSH v2 format (with a 4-byte byte count) */ | |
255 | static void appendString( | |
256 | CFMutableDataRef cfOut, | |
257 | const char *str, | |
258 | unsigned strLen) | |
259 | { | |
260 | appendUint32(cfOut, (uint32_t)strLen); | |
261 | CFDataAppendBytes(cfOut, (UInt8 *)str, strLen); | |
262 | } | |
263 | ||
264 | /* skip whitespace */ | |
265 | static void skipWhite( | |
266 | const unsigned char *&cp, | |
267 | unsigned &bytesLeft) | |
268 | { | |
269 | while(bytesLeft != 0) { | |
270 | if(isspace((int)(*cp))) { | |
271 | cp++; | |
272 | bytesLeft--; | |
273 | } | |
274 | else { | |
275 | return; | |
276 | } | |
277 | } | |
278 | } | |
279 | ||
280 | /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */ | |
281 | static const unsigned char *findNextWhite( | |
282 | const unsigned char *cp, | |
283 | unsigned &bytesLeft) | |
284 | { | |
285 | while(bytesLeft != 0) { | |
286 | if(isspace((int)(*cp))) { | |
287 | return cp; | |
288 | } | |
289 | cp++; | |
290 | bytesLeft--; | |
291 | } | |
292 | return cp; | |
293 | } | |
294 | ||
295 | ||
296 | /* | |
297 | * Calculate d mod{p-1,q-1} | |
298 | * Used when decoding OpenSSH-1 private RSA key. | |
299 | */ | |
300 | static int | |
301 | rsa_generate_additional_parameters(RSA *rsa) | |
302 | { | |
303 | BIGNUM *aux; | |
304 | BN_CTX *ctx; | |
305 | ||
306 | if((rsa->dmq1 = BN_new()) == NULL) { | |
307 | dprintf("rsa_generate_additional_parameters: BN_new failed"); | |
308 | return -1; | |
309 | } | |
310 | if((rsa->dmp1 = BN_new()) == NULL) { | |
311 | dprintf("rsa_generate_additional_parameters: BN_new failed"); | |
312 | return -1; | |
313 | } | |
314 | if ((aux = BN_new()) == NULL) { | |
315 | dprintf("rsa_generate_additional_parameters: BN_new failed"); | |
316 | return -1; | |
317 | } | |
318 | if ((ctx = BN_CTX_new()) == NULL) { | |
319 | dprintf("rsa_generate_additional_parameters: BN_CTX_new failed"); | |
320 | BN_clear_free(aux); | |
321 | return -1; | |
322 | } | |
323 | ||
324 | BN_sub(aux, rsa->q, BN_value_one()); | |
325 | BN_mod(rsa->dmq1, rsa->d, aux, ctx); | |
326 | ||
327 | BN_sub(aux, rsa->p, BN_value_one()); | |
328 | BN_mod(rsa->dmp1, rsa->d, aux, ctx); | |
329 | ||
330 | BN_clear_free(aux); | |
331 | BN_CTX_free(ctx); | |
332 | return 0; | |
333 | } | |
334 | ||
335 | #pragma mark --- OpenSSH-1 crypto --- | |
336 | ||
337 | static int ssh1DES3Crypt( | |
338 | unsigned char cipher, | |
339 | bool doEncrypt, | |
340 | const unsigned char *inText, | |
341 | unsigned inTextLen, | |
342 | const char *password, // C string | |
343 | unsigned char *outText, // data RETURNED here, caller mallocs | |
344 | unsigned *outTextLen) // RETURNED | |
345 | { | |
346 | switch(cipher) { | |
347 | case SSH_CIPHER_3DES: | |
348 | break; | |
349 | case SSH_CIPHER_NONE: | |
350 | /* cleartext RSA private key, e.g. host key. */ | |
351 | memmove(outText, inText, inTextLen); | |
352 | *outTextLen = inTextLen; | |
353 | return 0; | |
354 | default: | |
355 | /* who knows how we're going to figure these out */ | |
356 | printf("***Unsupported cipher (%u)\n", cipher); | |
357 | return -1; | |
358 | } | |
359 | ||
360 | /* key starts with MD5(password) */ | |
361 | unsigned char pwdDigest[CC_MD5_DIGEST_LENGTH]; | |
362 | CC_MD5(password, strlen(password), pwdDigest); | |
363 | ||
364 | /* three keys from that, like so: */ | |
365 | unsigned char k1[kCCKeySizeDES]; | |
366 | unsigned char k2[kCCKeySizeDES]; | |
367 | unsigned char k3[kCCKeySizeDES]; | |
368 | memmove(k1, pwdDigest, kCCKeySizeDES); | |
369 | memmove(k2, pwdDigest + kCCKeySizeDES, kCCKeySizeDES); | |
370 | memmove(k3, pwdDigest, kCCKeySizeDES); | |
371 | ||
372 | CCOperation op1_3; | |
373 | CCOperation op2; | |
374 | if(doEncrypt) { | |
375 | op1_3 = kCCEncrypt; | |
376 | op2 = kCCDecrypt; | |
377 | } | |
378 | else { | |
379 | op1_3 = kCCDecrypt; | |
380 | op2 = kCCEncrypt; | |
381 | } | |
382 | ||
383 | /* the openssh v1 pseudo triple DES. Each DES pass has its own CBC. */ | |
384 | size_t moved = 0; | |
385 | ||
386 | CCCryptorStatus cstat = CCCrypt(op1_3, kCCAlgorithmDES, | |
387 | 0, // no padding | |
388 | k1, kCCKeySizeDES, | |
389 | NULL, // IV | |
390 | inText, inTextLen, | |
391 | outText, inTextLen, &moved); | |
392 | if(cstat) { | |
393 | dprintf("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat); | |
394 | return -1; | |
395 | } | |
396 | cstat = CCCrypt(op2, kCCAlgorithmDES, | |
397 | 0, // no padding - SSH does that itself | |
398 | k2, kCCKeySizeDES, | |
399 | NULL, // IV | |
400 | outText, moved, | |
401 | outText, inTextLen, &moved); | |
402 | if(cstat) { | |
403 | dprintf("***ssh1DES3Crypt: CCCrypt()(2) returned %u\n", (unsigned)cstat); | |
404 | return -1; | |
405 | } | |
406 | cstat = CCCrypt(op1_3, kCCAlgorithmDES, | |
407 | 0, // no padding - SSH does that itself | |
408 | k3, kCCKeySizeDES, | |
409 | NULL, // IV | |
410 | outText, moved, | |
411 | outText, inTextLen, &moved); | |
412 | if(cstat) { | |
413 | dprintf("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat); | |
414 | return -1; | |
415 | } | |
416 | ||
417 | *outTextLen = moved; | |
418 | return 0; | |
419 | } | |
420 | ||
421 | #pragma mark --- OpenSSH-1 decode --- | |
422 | ||
423 | /* Decode OpenSSH-1 RSA private key */ | |
424 | static int decodeSSH1RSAPrivKey( | |
425 | const unsigned char *key, | |
426 | unsigned keyLen, | |
427 | char *password, | |
428 | RSA *rsa, // returned | |
429 | char **comment) // returned | |
430 | { | |
431 | const unsigned char *cp = key; // running pointer | |
432 | unsigned remLen = keyLen; | |
433 | unsigned len = strlen(authfile_id_string); | |
434 | ||
435 | /* length: ID string, NULL, Cipher, 4-byte spare */ | |
436 | if(remLen < (len + 6)) { | |
437 | dprintf("decodeSSH1RSAPrivKey: short record(1)\n"); | |
438 | return -1; | |
439 | } | |
440 | ||
441 | /* ID string plus a NULL */ | |
442 | if(memcmp(authfile_id_string, cp, len)) { | |
443 | dprintf("decodeSSH1RSAPrivKey: bad header\n"); | |
444 | return -1; | |
445 | } | |
446 | cp += (len + 1); | |
447 | remLen -= (len + 1); | |
448 | ||
449 | /* cipher */ | |
450 | unsigned char cipherSpec = *cp; | |
451 | switch(cipherSpec) { | |
452 | case SSH_CIPHER_NONE: | |
453 | if(password != NULL) { | |
454 | dprintf("decodeSSH1RSAPrivKey: Attempt to decrypt plaintext key\n"); | |
455 | return -1; | |
456 | } | |
457 | break; | |
458 | case SSH_CIPHER_3DES: | |
459 | if(password == NULL) { | |
460 | dprintf("decodeSSH1RSAPrivKey: Encrypted key with no decryptKey\n"); | |
461 | return -1; | |
462 | } | |
463 | break; | |
464 | default: | |
465 | /* I hope we don't see any other values here */ | |
466 | dprintf("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec); | |
467 | return -1; | |
468 | } | |
469 | ||
470 | /* skip cipher, spares */ | |
471 | cp += 5; | |
472 | remLen -= 5; | |
473 | ||
474 | /* | |
475 | * Clear text public key: | |
476 | * uint32 bits | |
477 | * bignum n | |
478 | * bignum e | |
479 | */ | |
480 | if(remLen < sizeof(uint32_t)) { | |
481 | dprintf("decodeSSH1RSAPrivKey: bad len(1)\n"); | |
482 | return -1; | |
483 | } | |
484 | /* skip over keybits */ | |
485 | readUint32(cp, remLen); | |
486 | rsa->n = readBigNum(cp, remLen); | |
487 | if(rsa->n == NULL) { | |
488 | dprintf("decodeSSH1RSAPrivKey: error decoding n\n"); | |
489 | return -1; | |
490 | } | |
491 | rsa->e = readBigNum(cp, remLen); | |
492 | if(rsa->e == NULL) { | |
493 | dprintf("decodeSSH1RSAPrivKey: error decoding e\n"); | |
494 | return -1; | |
495 | } | |
496 | ||
497 | /* comment string: 4-byte length and the string w/o NULL */ | |
498 | if(remLen < sizeof(uint32_t)) { | |
499 | dprintf("decodeSSH1RSAPrivKey: bad len(2)\n"); | |
500 | return -1; | |
501 | } | |
502 | uint32_t commentLen = readUint32(cp, remLen); | |
503 | if(commentLen > remLen) { | |
504 | dprintf("decodeSSH1RSAPrivKey: bad len(3)\n"); | |
505 | return -1; | |
506 | } | |
507 | *comment = (char *)malloc(commentLen + 1); | |
508 | memmove(*comment, cp, commentLen); | |
509 | (*comment)[commentLen] = '\0'; | |
510 | cp += commentLen; | |
511 | remLen -= commentLen; | |
512 | ||
513 | /* everything that remains is ciphertext */ | |
514 | unsigned char ptext[remLen]; | |
515 | unsigned ptextLen = 0; | |
516 | if(ssh1DES3Crypt(cipherSpec, false, cp, remLen, password, ptext, &ptextLen)) { | |
517 | dprintf("decodeSSH1RSAPrivKey: decrypt error\n"); | |
518 | return -1; | |
519 | } | |
520 | /* subsequent errors to errOut: */ | |
521 | ||
522 | int ourRtn = 0; | |
523 | ||
524 | /* plaintext contents: | |
525 | ||
526 | [0-1] -- random bytes | |
527 | [2-3] -- copy of [01] for passphrase validity checking | |
528 | buffer_put_bignum(d) | |
529 | buffer_put_bignum(iqmp) | |
530 | buffer_put_bignum(q) | |
531 | buffer_put_bignum(p) | |
532 | pad to block size | |
533 | */ | |
534 | cp = ptext; | |
535 | remLen = ptextLen; | |
536 | if(remLen < 4) { | |
537 | dprintf("decodeSSH1RSAPrivKey: bad len(4)\n"); | |
538 | ourRtn = -1; | |
539 | goto errOut; | |
540 | } | |
541 | if((cp[0] != cp[2]) || (cp[1] != cp[3])) { | |
542 | /* decrypt fail */ | |
543 | dprintf("decodeSSH1RSAPrivKey: check byte error\n"); | |
544 | ourRtn = -1; | |
545 | goto errOut; | |
546 | } | |
547 | cp += 4; | |
548 | remLen -= 4; | |
549 | ||
550 | /* remainder comprises private portion of RSA key */ | |
551 | rsa->d = readBigNum(cp, remLen); | |
552 | if(rsa->d == NULL) { | |
553 | dprintf("decodeSSH1RSAPrivKey: error decoding d\n"); | |
554 | return -1; | |
555 | } | |
556 | rsa->iqmp = readBigNum(cp, remLen); | |
557 | if(rsa->iqmp == NULL) { | |
558 | dprintf("decodeSSH1RSAPrivKey: error decoding iqmp\n"); | |
559 | return -1; | |
560 | } | |
561 | rsa->q = readBigNum(cp, remLen); | |
562 | if(rsa->q == NULL) { | |
563 | dprintf("decodeSSH1RSAPrivKey: error decoding q\n"); | |
564 | return -1; | |
565 | } | |
566 | rsa->p = readBigNum(cp, remLen); | |
567 | if(rsa->p == NULL) { | |
568 | dprintf("decodeSSH1RSAPrivKey: error decoding p\n"); | |
569 | return -1; | |
570 | } | |
571 | ||
572 | /* calculate d mod{p-1,q-1} */ | |
573 | ourRtn = rsa_generate_additional_parameters(rsa); | |
574 | ||
575 | errOut: | |
576 | memset(ptext, 0, ptextLen); | |
577 | return ourRtn; | |
578 | } | |
579 | ||
580 | /* Decode OpenSSH-1 RSA public key */ | |
581 | static int decodeSSH1RSAPubKey( | |
582 | const unsigned char *key, | |
583 | unsigned keyLen, | |
584 | RSA *rsa, // returned | |
585 | char **comment) // returned | |
586 | { | |
587 | const unsigned char *cp = key; // running pointer | |
588 | unsigned remLen = keyLen; | |
589 | ||
590 | *comment = NULL; | |
591 | skipWhite(cp, remLen); | |
592 | ||
593 | /* | |
594 | * cp points to start of size_in_bits in ASCII decimal' we really don't care about | |
595 | * this field. Find next space. | |
596 | */ | |
597 | cp = findNextWhite(cp, remLen); | |
598 | if(remLen == 0) { | |
599 | dprintf("decodeSSH1RSAPubKey: short key (1)\n"); | |
600 | return -1; | |
601 | } | |
602 | skipWhite(cp, remLen); | |
603 | if(remLen == 0) { | |
604 | dprintf("decodeSSH1RSAPubKey: short key (2)\n"); | |
605 | return -1; | |
606 | } | |
607 | ||
608 | /* | |
609 | * cp points to start of e | |
610 | */ | |
611 | const unsigned char *ep = findNextWhite(cp, remLen); | |
612 | if(remLen == 0) { | |
613 | dprintf("decodeSSH1RSAPubKey: short key (3)\n"); | |
614 | return -1; | |
615 | } | |
616 | unsigned len = ep - cp; | |
617 | rsa->e = parseDecimalBn(cp, len); | |
618 | if(rsa->e == NULL) { | |
619 | return -1; | |
620 | } | |
621 | cp += len; | |
622 | remLen -= len; | |
623 | ||
624 | skipWhite(cp, remLen); | |
625 | if(remLen == 0) { | |
626 | dprintf("decodeSSH1RSAPubKey: short key (4)\n"); | |
627 | return -1; | |
628 | } | |
629 | ||
630 | /* cp points to start of n */ | |
631 | ep = findNextWhite(cp, remLen); | |
632 | len = ep - cp; | |
633 | rsa->n = parseDecimalBn(cp, len); | |
634 | if(rsa->n == NULL) { | |
635 | return -1; | |
636 | } | |
637 | cp += len; | |
638 | remLen -= len; | |
639 | skipWhite(cp, remLen); | |
640 | if(remLen == 0) { | |
641 | /* no comment; we're done */ | |
642 | return 0; | |
643 | } | |
644 | ||
645 | ep = findNextWhite(cp, remLen); | |
646 | len = ep - cp; | |
647 | if(len == 0) { | |
648 | return 0; | |
649 | } | |
650 | *comment = (char *)malloc(len + 1); | |
651 | memmove(*comment, cp, len); | |
652 | if((*comment)[len - 1] == '\n') { | |
653 | /* normal case closes with a newline, not part of the comment */ | |
654 | len--; | |
655 | } | |
656 | (*comment)[len] = '\0'; | |
657 | return 0; | |
658 | ||
659 | } | |
660 | ||
661 | #pragma mark --- OpenSSH-1 encode --- | |
662 | ||
663 | /* Encode OpenSSH-1 RSA private key */ | |
664 | static int encodeSSH1RSAPrivKey( | |
665 | RSA *rsa, | |
666 | const char *password, | |
667 | const char *comment, | |
668 | unsigned char **outKey, // mallocd and RETURNED | |
669 | unsigned *outKeyLen) // RETURNED | |
670 | { | |
671 | CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); | |
672 | ||
673 | /* ID string including NULL */ | |
674 | CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1); | |
675 | ||
676 | /* one byte cipher */ | |
677 | UInt8 cipherSpec = SSH_CIPHER_3DES; | |
678 | CFDataAppendBytes(cfOut, &cipherSpec, 1); | |
679 | ||
680 | /* spares */ | |
681 | UInt8 spares[4] = {0}; | |
682 | CFDataAppendBytes(cfOut, spares, 4); | |
683 | ||
684 | /* | |
685 | * Clear text public key: | |
686 | * uint32 bits | |
687 | * bignum n | |
688 | * bignum e | |
689 | */ | |
690 | uint32_t keybits = RSA_size(rsa) * 8; | |
691 | appendUint32(cfOut, keybits); | |
692 | appendBigNum(cfOut, rsa->n); | |
693 | appendBigNum(cfOut, rsa->e); | |
694 | ||
695 | /* comment string: 4-byte length and the string w/o NULL */ | |
696 | if(comment) { | |
697 | uint32_t len = strlen(comment); | |
698 | appendUint32(cfOut, len); | |
699 | CFDataAppendBytes(cfOut, (const UInt8 *)comment, len); | |
700 | } | |
701 | ||
702 | /* | |
703 | * Remainder is encrypted, consisting of | |
704 | * | |
705 | * [0-1] -- random bytes | |
706 | * [2-3] -- copy of [01] for passphrase validity checking | |
707 | * buffer_put_bignum(d) | |
708 | * buffer_put_bignum(iqmp) | |
709 | * buffer_put_bignum(q) | |
710 | * buffer_put_bignum(p) | |
711 | * pad to block size | |
712 | */ | |
713 | CFMutableDataRef ptext = CFDataCreateMutable(NULL, 0); | |
714 | ||
715 | /* [0..3] check bytes */ | |
716 | UInt8 checkBytes[4]; | |
717 | DevRandomGenerator rng = DevRandomGenerator(); | |
718 | rng.random(checkBytes, 2); | |
719 | checkBytes[2] = checkBytes[0]; | |
720 | checkBytes[3] = checkBytes[1]; | |
721 | CFDataAppendBytes(ptext, checkBytes, 4); | |
722 | ||
723 | /* d, iqmp, q, p */ | |
724 | appendBigNum(ptext, rsa->d); | |
725 | appendBigNum(ptext, rsa->iqmp); | |
726 | appendBigNum(ptext, rsa->q); | |
727 | appendBigNum(ptext, rsa->p); | |
728 | ||
729 | /* encrypt it */ | |
730 | unsigned ptextLen = CFDataGetLength(ptext); | |
731 | unsigned padding = 0; | |
732 | unsigned rem = ptextLen & 0x7; | |
733 | if(rem) { | |
734 | padding = 8 - rem; | |
735 | } | |
736 | UInt8 padByte = 0; | |
737 | for(unsigned dex=0; dex<padding; dex++) { | |
738 | CFDataAppendBytes(ptext, &padByte, 1); | |
739 | } | |
740 | ptextLen = CFDataGetLength(ptext); | |
741 | unsigned char ctext[ptextLen]; | |
742 | unsigned ctextLen; | |
743 | int ourRtn = ssh1DES3Crypt(SSH_CIPHER_3DES, true, | |
744 | (unsigned char *)CFDataGetBytePtr(ptext), ptextLen, | |
745 | password, | |
746 | ctext, &ctextLen); | |
747 | if(ourRtn != 0) { | |
748 | goto errOut; | |
749 | } | |
750 | ||
751 | /* appended encrypted portion */ | |
752 | CFDataAppendBytes(cfOut, ctext, ctextLen); | |
753 | *outKeyLen = (unsigned)CFDataGetLength(cfOut); | |
754 | *outKey = (unsigned char *)malloc(*outKeyLen); | |
755 | memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen); | |
756 | errOut: | |
757 | CFRelease(cfOut); | |
758 | /* it would be proper to zero out ptext here, but we can't do that to a CFData */ | |
759 | CFRelease(ptext); | |
760 | return ourRtn; | |
761 | } | |
762 | ||
763 | /* Encode OpenSSH-1 RSA public key */ | |
764 | static int encodeSSH1RSAPubKey( | |
765 | RSA *rsa, | |
766 | const char *comment, | |
767 | unsigned char **outKey, // mallocd and RETURNED | |
768 | unsigned *outKeyLen) // RETURNED | |
769 | { | |
770 | CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); | |
771 | ||
772 | /* | |
773 | * Format is | |
774 | * num_bits in decimal | |
775 | * <space> | |
776 | * e, bignum in decimal | |
777 | * <space> | |
778 | * n, bignum in decimal | |
779 | * <space> | |
780 | * optional comment | |
781 | * newline | |
782 | */ | |
783 | int ourRtn = 0; | |
784 | unsigned numBits = BN_num_bits(rsa->n); | |
785 | char bitString[20]; | |
786 | UInt8 c = ' '; | |
787 | ||
788 | snprintf(bitString, sizeof(bitString), "%u ", numBits); | |
789 | CFDataAppendBytes(cfOut, (const UInt8 *)bitString, strlen(bitString)); | |
790 | if(appendBigNumDec(cfOut, rsa->e)) { | |
791 | ourRtn = -1; | |
792 | goto errOut; | |
793 | } | |
794 | CFDataAppendBytes(cfOut, &c, 1); | |
795 | if(appendBigNumDec(cfOut, rsa->n)) { | |
796 | ourRtn = -1; | |
797 | goto errOut; | |
798 | } | |
799 | if(comment != NULL) { | |
800 | CFDataAppendBytes(cfOut, &c, 1); | |
801 | CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment)); | |
802 | } | |
803 | c = '\n'; | |
804 | CFDataAppendBytes(cfOut, &c, 1); | |
805 | *outKeyLen = CFDataGetLength(cfOut); | |
806 | *outKey = (unsigned char *)malloc(*outKeyLen); | |
807 | memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen); | |
808 | errOut: | |
809 | CFRelease(cfOut); | |
810 | return ourRtn; | |
811 | } | |
812 | ||
813 | #pragma mark --- OpenSSH-2 public key decode --- | |
814 | ||
815 | /* | |
816 | * Decode components from an SSHv2 public key. | |
817 | * Also verifies the leading header, e.g. "ssh-rsa". | |
818 | * The returned decodedBlob is algorithm-specific. | |
819 | */ | |
820 | static int parseSSH2PubKey( | |
821 | const unsigned char *key, | |
822 | unsigned keyLen, | |
823 | const char *header, // SSH2_RSA_HEADER, SSH2_DSA_HEADER | |
824 | unsigned char **decodedBlob, // mallocd and RETURNED | |
825 | unsigned *decodedBlobLen, // RETURNED | |
826 | char **comment) // optionally mallocd and RETURNED, NULL terminated | |
827 | { | |
828 | unsigned len = strlen(header); | |
829 | const unsigned char *endOfKey = key + keyLen; | |
830 | *decodedBlob = NULL; | |
831 | *comment = NULL; | |
832 | ||
833 | /* ID string plus at least one space */ | |
834 | if(keyLen < (len + 1)) { | |
835 | dprintf("parseSSH2PubKey: short record(1)\n"); | |
836 | return -1; | |
837 | } | |
838 | ||
839 | if(memcmp(header, key, len)) { | |
840 | dprintf("parseSSH2PubKey: bad header (1)\n"); | |
841 | return -1; | |
842 | } | |
843 | key += len; | |
844 | if(*key++ != ' ') { | |
845 | dprintf("parseSSH2PubKey: bad header (2)\n"); | |
846 | return -1; | |
847 | } | |
848 | keyLen -= (len + 1); | |
849 | ||
850 | /* key points to first whitespace after header */ | |
851 | skipWhite(key, keyLen); | |
852 | if(keyLen == 0) { | |
853 | dprintf("parseSSH2PubKey: short key\n"); | |
854 | return -1; | |
855 | } | |
856 | ||
857 | /* key is start of base64 blob */ | |
858 | const unsigned char *encodedBlob = key; | |
859 | const unsigned char *endBlob = findNextWhite(key, keyLen); | |
860 | unsigned encodedBlobLen = endBlob - encodedBlob; | |
861 | ||
862 | /* decode base 64 */ | |
863 | *decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen); | |
864 | if(*decodedBlob == NULL) { | |
865 | dprintf("parseSSH2PubKey: base64 decode error\n"); | |
866 | return -1; | |
867 | } | |
868 | ||
869 | /* skip over the encoded blob and possible whitespace after it */ | |
870 | key = endBlob; | |
871 | keyLen = endOfKey - endBlob; | |
872 | skipWhite(key, keyLen); | |
873 | if(keyLen == 0) { | |
874 | /* nothing remains, no comment, no error */ | |
875 | return 0; | |
876 | } | |
877 | ||
878 | /* optional comment */ | |
879 | *comment = (char *)malloc(keyLen + 1); | |
880 | memmove(*comment, key, keyLen); | |
881 | if((*comment)[keyLen - 1] == '\n') { | |
882 | /* normal case closes with a newline, not part of the comment */ | |
883 | keyLen--; | |
884 | } | |
885 | (*comment)[keyLen] = '\0'; | |
886 | return 0; | |
887 | } | |
888 | ||
889 | static int decodeSSH2RSAPubKey( | |
890 | const unsigned char *key, | |
891 | unsigned keyLen, | |
892 | RSA *rsa, // returned | |
893 | char **comment) // returned | |
894 | { | |
895 | /* | |
896 | * Verify header | |
897 | * get base64-decoded blob plus optional comment | |
898 | */ | |
899 | unsigned char *decodedBlob = NULL; | |
900 | unsigned decodedBlobLen = 0; | |
901 | if(parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) { | |
902 | return -1; | |
903 | } | |
904 | /* subsequent errors to errOut: */ | |
905 | ||
906 | /* | |
907 | * The inner base64-decoded blob, consisting of | |
908 | * ssh-rsa | |
909 | * e | |
910 | * n | |
911 | */ | |
912 | uint32_t decLen; | |
913 | unsigned len; | |
914 | int ourRtn = 0; | |
915 | ||
916 | key = decodedBlob; | |
917 | keyLen = decodedBlobLen; | |
918 | if(keyLen < 12) { | |
919 | /* three length fields at least */ | |
920 | dprintf("decodeSSH2RSAPubKey: short record(2)\n"); | |
921 | ourRtn = -1; | |
922 | goto errOut; | |
923 | } | |
924 | decLen = readUint32(key, keyLen); | |
925 | len = strlen(SSH2_RSA_HEADER); | |
926 | if(decLen != len) { | |
927 | dprintf("decodeSSH2RSAPubKey: bad header (2)\n"); | |
928 | ourRtn = -1; | |
929 | goto errOut; | |
930 | } | |
931 | if(memcmp(SSH2_RSA_HEADER, key, len)) { | |
932 | dprintf("decodeSSH2RSAPubKey: bad header (1)\n"); | |
933 | return -1; | |
934 | } | |
935 | key += len; | |
936 | keyLen -= len; | |
937 | ||
938 | rsa->e = readBigNum2(key, keyLen); | |
939 | if(rsa->e == NULL) { | |
940 | ourRtn = -1; | |
941 | goto errOut; | |
942 | } | |
943 | rsa->n = readBigNum2(key, keyLen); | |
944 | if(rsa->n == NULL) { | |
945 | ourRtn = -1; | |
946 | goto errOut; | |
947 | } | |
948 | ||
949 | errOut: | |
950 | free(decodedBlob); | |
951 | return ourRtn; | |
952 | } | |
953 | ||
954 | static int decodeSSH2DSAPubKey( | |
955 | const unsigned char *key, | |
956 | unsigned keyLen, | |
957 | DSA *dsa, // returned | |
958 | char **comment) // returned | |
959 | { | |
960 | /* | |
961 | * Verify header | |
962 | * get base64-decoded blob plus optional comment | |
963 | */ | |
964 | unsigned char *decodedBlob = NULL; | |
965 | unsigned decodedBlobLen = 0; | |
966 | if(parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) { | |
967 | return -1; | |
968 | } | |
969 | /* subsequent errors to errOut: */ | |
970 | ||
971 | /* | |
972 | * The inner base64-decoded blob, consisting of | |
973 | * ssh-dss | |
974 | * p | |
975 | * q | |
976 | * g | |
977 | * pub_key | |
978 | */ | |
979 | uint32_t decLen; | |
980 | int ourRtn = 0; | |
981 | unsigned len; | |
982 | ||
983 | key = decodedBlob; | |
984 | keyLen = decodedBlobLen; | |
985 | if(keyLen < 20) { | |
986 | /* five length fields at least */ | |
987 | dprintf("decodeSSH2DSAPubKey: short record(2)\n"); | |
988 | ourRtn = -1; | |
989 | goto errOut; | |
990 | } | |
991 | decLen = readUint32(key, keyLen); | |
992 | len = strlen(SSH2_DSA_HEADER); | |
993 | if(decLen != len) { | |
994 | dprintf("decodeSSH2DSAPubKey: bad header (2)\n"); | |
995 | ourRtn = -1; | |
996 | goto errOut; | |
997 | } | |
998 | if(memcmp(SSH2_DSA_HEADER, key, len)) { | |
999 | dprintf("decodeSSH2DSAPubKey: bad header (1)\n"); | |
1000 | return -1; | |
1001 | } | |
1002 | key += len; | |
1003 | keyLen -= len; | |
1004 | ||
1005 | dsa->p = readBigNum2(key, keyLen); | |
1006 | if(dsa->p == NULL) { | |
1007 | ourRtn = -1; | |
1008 | goto errOut; | |
1009 | } | |
1010 | dsa->q = readBigNum2(key, keyLen); | |
1011 | if(dsa->q == NULL) { | |
1012 | ourRtn = -1; | |
1013 | goto errOut; | |
1014 | } | |
1015 | dsa->g = readBigNum2(key, keyLen); | |
1016 | if(dsa->g == NULL) { | |
1017 | ourRtn = -1; | |
1018 | goto errOut; | |
1019 | } | |
1020 | dsa->pub_key = readBigNum2(key, keyLen); | |
1021 | if(dsa->pub_key == NULL) { | |
1022 | ourRtn = -1; | |
1023 | goto errOut; | |
1024 | } | |
1025 | ||
1026 | errOut: | |
1027 | free(decodedBlob); | |
1028 | return ourRtn; | |
1029 | } | |
1030 | ||
1031 | #pragma mark --- OpenSSH-2 public key encode --- | |
1032 | ||
1033 | static int encodeSSH2RSAPubKey( | |
1034 | RSA *rsa, | |
1035 | const char *comment, | |
1036 | unsigned char **outKey, // mallocd and RETURNED | |
1037 | unsigned *outKeyLen) // RETURNED | |
1038 | { | |
1039 | unsigned char *b64 = NULL; | |
1040 | unsigned b64Len; | |
1041 | UInt8 c; | |
1042 | ||
1043 | /* | |
1044 | * First, the inner base64-encoded blob, consisting of | |
1045 | * ssh-rsa | |
1046 | * e | |
1047 | * n | |
1048 | */ | |
1049 | CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); | |
1050 | int ourRtn = 0; | |
1051 | appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER)); | |
1052 | ourRtn = appendBigNum2(cfOut, rsa->e); | |
1053 | if(ourRtn) { | |
1054 | goto errOut; | |
1055 | } | |
1056 | ourRtn = appendBigNum2(cfOut, rsa->n); | |
1057 | if(ourRtn) { | |
1058 | goto errOut; | |
1059 | } | |
1060 | ||
1061 | /* base64 encode that */ | |
1062 | b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len); | |
1063 | ||
1064 | /* cuEnc64 added newline and NULL, which we really don't want */ | |
1065 | b64Len -= 2; | |
1066 | ||
1067 | /* Now start over, dropping that base64 into a public blob. */ | |
1068 | CFDataSetLength(cfOut, 0); | |
1069 | CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER)); | |
1070 | c = ' '; | |
1071 | CFDataAppendBytes(cfOut, &c, 1); | |
1072 | CFDataAppendBytes(cfOut, b64, b64Len); | |
1073 | ||
1074 | /* optional comment */ | |
1075 | if(comment) { | |
1076 | CFDataAppendBytes(cfOut, &c, 1); | |
1077 | CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment)); | |
1078 | } | |
1079 | ||
1080 | /* finish it with a newline */ | |
1081 | c = '\n'; | |
1082 | CFDataAppendBytes(cfOut, &c, 1); | |
1083 | ||
1084 | *outKeyLen = (unsigned)CFDataGetLength(cfOut); | |
1085 | *outKey = (unsigned char *)malloc(*outKeyLen); | |
1086 | memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen); | |
1087 | ||
1088 | errOut: | |
1089 | CFRelease(cfOut); | |
1090 | if(b64) { | |
1091 | free(b64); | |
1092 | } | |
1093 | return ourRtn; | |
1094 | } | |
1095 | ||
1096 | static int encodeSSH2DSAPubKey( | |
1097 | DSA *dsa, | |
1098 | const char *comment, | |
1099 | unsigned char **outKey, // mallocd and RETURNED | |
1100 | unsigned *outKeyLen) // RETURNED | |
1101 | { | |
1102 | unsigned char *b64 = NULL; | |
1103 | unsigned b64Len; | |
1104 | UInt8 c; | |
1105 | ||
1106 | /* | |
1107 | * First, the inner base64-encoded blob, consisting of | |
1108 | * ssh-dss | |
1109 | * p | |
1110 | * q | |
1111 | * g | |
1112 | * pub_key | |
1113 | */ | |
1114 | CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); | |
1115 | int ourRtn = 0; | |
1116 | appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER)); | |
1117 | ourRtn = appendBigNum2(cfOut, dsa->p); | |
1118 | if(ourRtn) { | |
1119 | goto errOut; | |
1120 | } | |
1121 | ourRtn = appendBigNum2(cfOut, dsa->q); | |
1122 | if(ourRtn) { | |
1123 | goto errOut; | |
1124 | } | |
1125 | ourRtn = appendBigNum2(cfOut, dsa->g); | |
1126 | if(ourRtn) { | |
1127 | goto errOut; | |
1128 | } | |
1129 | ourRtn = appendBigNum2(cfOut, dsa->pub_key); | |
1130 | if(ourRtn) { | |
1131 | goto errOut; | |
1132 | } | |
1133 | ||
1134 | /* base64 encode that */ | |
1135 | b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len); | |
1136 | ||
1137 | /* cuEnc64 added newline and NULL, which we really don't want */ | |
1138 | b64Len -= 2; | |
1139 | ||
1140 | /* Now start over, dropping that base64 into a public blob. */ | |
1141 | CFDataSetLength(cfOut, 0); | |
1142 | CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER)); | |
1143 | c = ' '; | |
1144 | CFDataAppendBytes(cfOut, &c, 1); | |
1145 | CFDataAppendBytes(cfOut, b64, b64Len); | |
1146 | ||
1147 | /* optional comment */ | |
1148 | if(comment) { | |
1149 | CFDataAppendBytes(cfOut, &c, 1); | |
1150 | CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment)); | |
1151 | } | |
1152 | ||
1153 | /* finish it with a newline */ | |
1154 | c = '\n'; | |
1155 | CFDataAppendBytes(cfOut, &c, 1); | |
1156 | ||
1157 | *outKeyLen = (unsigned)CFDataGetLength(cfOut); | |
1158 | *outKey = (unsigned char *)malloc(*outKeyLen); | |
1159 | memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen); | |
1160 | ||
1161 | errOut: | |
1162 | CFRelease(cfOut); | |
1163 | if(b64) { | |
1164 | free(b64); | |
1165 | } | |
1166 | return ourRtn; | |
1167 | } | |
1168 | ||
1169 | ||
1170 | #pragma mark --- print RSA/DSA keys --- | |
1171 | ||
1172 | static void printBNLong( | |
1173 | BN_ULONG bnl) | |
1174 | { | |
1175 | /* for now assume it's 32 bits */ | |
1176 | unsigned i = bnl >> 24; | |
1177 | printf("%02X ", i); | |
1178 | i = (bnl >> 16) & 0xff; | |
1179 | printf("%02X ", i); | |
1180 | i = (bnl >> 8) & 0xff; | |
1181 | printf("%02X ", i); | |
1182 | i = bnl & 0xff; | |
1183 | printf("%02X ", i); | |
1184 | } | |
1185 | ||
1186 | static void printBN( | |
1187 | const char *label, | |
1188 | BIGNUM *bn) | |
1189 | { | |
1190 | printf("%s: %d bits: bn->top %d: ", label, BN_num_bits(bn), bn->top); | |
1191 | for(int dex=bn->top-1; dex>=0; dex--) { | |
1192 | printBNLong(bn->d[dex]); | |
1193 | } | |
1194 | printf("\n"); | |
1195 | } | |
1196 | static void printRSA( | |
1197 | RSA *rsa) | |
1198 | { | |
1199 | if(rsa->n) { | |
1200 | printBN(" n", rsa->n); | |
1201 | } | |
1202 | if(rsa->e) { | |
1203 | printBN(" e", rsa->e); | |
1204 | } | |
1205 | if(rsa->d) { | |
1206 | printBN(" d", rsa->d); | |
1207 | } | |
1208 | if(rsa->p) { | |
1209 | printBN(" p", rsa->p); | |
1210 | } | |
1211 | if(rsa->q) { | |
1212 | printBN(" q", rsa->q); | |
1213 | } | |
1214 | if(rsa->dmp1) { | |
1215 | printBN("dmp1", rsa->dmp1); | |
1216 | } | |
1217 | if(rsa->dmq1) { | |
1218 | printBN("dmq1", rsa->dmq1); | |
1219 | } | |
1220 | if(rsa->iqmp) { | |
1221 | printBN("iqmp", rsa->iqmp); | |
1222 | } | |
1223 | } | |
1224 | ||
1225 | /* only public keys here */ | |
1226 | static void printDSA( | |
1227 | DSA *dsa) | |
1228 | { | |
1229 | if(dsa->p) { | |
1230 | printBN(" p", dsa->p); | |
1231 | } | |
1232 | if(dsa->q) { | |
1233 | printBN(" q", dsa->q); | |
1234 | } | |
1235 | if(dsa->g) { | |
1236 | printBN(" g", dsa->g); | |
1237 | } | |
1238 | if(dsa->pub_key) { | |
1239 | printBN(" pub", dsa->pub_key); | |
1240 | } | |
1241 | } | |
1242 | ||
1243 | /* parse format string, returns nonzero on error */ | |
1244 | static int parseFormat( | |
1245 | const char *formatStr, | |
1246 | bool *isSSH1) | |
1247 | { | |
1248 | if(!strcmp(formatStr, "ssh1")) { | |
1249 | *isSSH1 = true; | |
1250 | return 0; | |
1251 | } | |
1252 | else if(!strcmp(formatStr, "ssh2")) { | |
1253 | *isSSH1 = false; | |
1254 | return 0; | |
1255 | } | |
1256 | else { | |
1257 | return -1; | |
1258 | } | |
1259 | } | |
1260 | ||
1261 | #pragma mark --- main --- | |
1262 | ||
1263 | /* parse format string */ | |
1264 | int main(int argc, char **argv) | |
1265 | { | |
1266 | char *inFile = NULL; | |
1267 | char *outFile = NULL; | |
1268 | bool privKeyIn = false; | |
1269 | bool privKeyOut = false; | |
1270 | char *password = NULL; | |
1271 | char *comment = NULL; | |
1272 | bool doPrint = false; | |
1273 | bool isDSA = false; | |
1274 | bool inputSSH1 = false; | |
1275 | bool outputSSH1 = false; | |
1276 | bool clearPrivKeys = false; | |
1277 | ||
1278 | int ourRtn = 0; | |
1279 | ||
1280 | extern char *optarg; | |
1281 | int arg; | |
1282 | while ((arg = getopt(argc, argv, "i:o:vVdrf:F:p:Pc:h")) != -1) { | |
1283 | switch (arg) { | |
1284 | case 'i': | |
1285 | inFile = optarg; | |
1286 | break; | |
1287 | case 'o': | |
1288 | outFile = optarg; | |
1289 | break; | |
1290 | case 'v': | |
1291 | privKeyIn = true; | |
1292 | break; | |
1293 | case 'V': | |
1294 | privKeyOut = true; | |
1295 | break; | |
1296 | case 'd': | |
1297 | isDSA = true; | |
1298 | break; | |
1299 | case 'r': | |
1300 | doPrint = true; | |
1301 | break; | |
1302 | case 'f': | |
1303 | if(parseFormat(optarg, &inputSSH1)) { | |
1304 | usage(argv); | |
1305 | } | |
1306 | break; | |
1307 | case 'F': | |
1308 | if(parseFormat(optarg, &outputSSH1)) { | |
1309 | usage(argv); | |
1310 | } | |
1311 | break; | |
1312 | case 'p': | |
1313 | password = optarg; | |
1314 | break; | |
1315 | case 'P': | |
1316 | clearPrivKeys = true; | |
1317 | break; | |
1318 | case 'c': | |
1319 | comment = optarg; | |
1320 | break; | |
1321 | case 'h': | |
1322 | default: | |
1323 | usage(argv); | |
1324 | } | |
1325 | } | |
1326 | ||
1327 | if(inFile == NULL) { | |
1328 | printf("***You must specify an input file.\n"); | |
1329 | usage(argv); | |
1330 | } | |
1331 | if((privKeyIn && !inputSSH1) || (privKeyOut && !outputSSH1)) { | |
1332 | printf("***Private keys in SSH2 format are handled elsewhere - Wrapped OpenSSL.\n"); | |
1333 | exit(1); | |
1334 | } | |
1335 | if((privKeyIn || privKeyOut) && (password == NULL) & !clearPrivKeys) { | |
1336 | printf("***Private key handling requires a password or the -P option.\n"); | |
1337 | usage(argv); | |
1338 | } | |
1339 | unsigned char *inKey = NULL; | |
1340 | unsigned inKeyLen = 0; | |
1341 | if(readFile(inFile, &inKey, &inKeyLen)) { | |
1342 | printf("Error reading %s. Aborting.\n", inFile); | |
1343 | exit(1); | |
1344 | } | |
1345 | ||
1346 | RSA *rsa = NULL; | |
1347 | DSA *dsa = NULL; | |
1348 | ||
1349 | /* parse incoming key */ | |
1350 | if(isDSA) { | |
1351 | if(inputSSH1) { | |
1352 | printf("***SSHv1 did not support DSA keys.\n"); | |
1353 | exit(1); | |
1354 | } | |
1355 | /* already verified that this is not SSH2 & priv (Wrapped OpenSSL) */ | |
1356 | dsa = DSA_new(); | |
1357 | if(decodeSSH2DSAPubKey(inKey, inKeyLen, dsa, &comment)) { | |
1358 | printf("***Error decoding SSH2 DSA public key.\n"); | |
1359 | exit(1); | |
1360 | } | |
1361 | } | |
1362 | else { | |
1363 | rsa = RSA_new(); | |
1364 | if(privKeyIn) { | |
1365 | /* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */ | |
1366 | if(decodeSSH1RSAPrivKey(inKey, inKeyLen, password, rsa, &comment)) { | |
1367 | printf("***Error decoding SSH1 RSA Private key.\n"); | |
1368 | exit(1); | |
1369 | } | |
1370 | } | |
1371 | else { | |
1372 | if(inputSSH1) { | |
1373 | if(decodeSSH1RSAPubKey(inKey, inKeyLen, rsa, &comment)) { | |
1374 | printf("***Error decoding SSH1 RSA Public key.\n"); | |
1375 | exit(1); | |
1376 | } | |
1377 | } | |
1378 | else { | |
1379 | if(decodeSSH2RSAPubKey(inKey, inKeyLen, rsa, &comment)) { | |
1380 | printf("***Error decoding SSH2 RSA Public key.\n"); | |
1381 | exit(1); | |
1382 | } | |
1383 | } | |
1384 | } | |
1385 | } | |
1386 | ||
1387 | /* optionally display the key */ | |
1388 | if(doPrint) { | |
1389 | if(isDSA) { | |
1390 | printf("DSA key:\n"); | |
1391 | printDSA(dsa); | |
1392 | printf("Comment: %s\n", comment); | |
1393 | } | |
1394 | else { | |
1395 | printf("RSA key:\n"); | |
1396 | printRSA(rsa); | |
1397 | printf("Comment: %s\n", comment); | |
1398 | } | |
1399 | } | |
1400 | ||
1401 | /* optionally convert to (optionally different) output format */ | |
1402 | ||
1403 | if(outFile) { | |
1404 | unsigned char *outKey = NULL; | |
1405 | unsigned outKeyLen = 0; | |
1406 | ||
1407 | if(isDSA) { | |
1408 | if(outputSSH1 || privKeyOut) { | |
1409 | printf("***DSA: Only public SSHv2 keys allowed.\n"); | |
1410 | exit(1); | |
1411 | } | |
1412 | if(encodeSSH2DSAPubKey(dsa, comment, &outKey, &outKeyLen)) { | |
1413 | printf("***Error encoding DSA public key.\n"); | |
1414 | exit(1); | |
1415 | } | |
1416 | } | |
1417 | else { | |
1418 | if(privKeyOut) { | |
1419 | /* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */ | |
1420 | if(encodeSSH1RSAPrivKey(rsa, password, comment, &outKey, &outKeyLen)) { | |
1421 | printf("***Error encoding RSA private key.\n"); | |
1422 | exit(1); | |
1423 | } | |
1424 | } | |
1425 | else { | |
1426 | if(outputSSH1) { | |
1427 | if(encodeSSH1RSAPubKey(rsa, comment, &outKey, &outKeyLen)) { | |
1428 | printf("***Error encoding RSA public key.\n"); | |
1429 | exit(1); | |
1430 | } | |
1431 | } | |
1432 | else { | |
1433 | if(encodeSSH2RSAPubKey(rsa, comment, &outKey, &outKeyLen)) { | |
1434 | printf("***Error encoding RSA public key.\n"); | |
1435 | exit(1); | |
1436 | } | |
1437 | } | |
1438 | } /* RSA public */ | |
1439 | } /* RSA */ | |
1440 | ||
1441 | if(writeFile(outFile, outKey, outKeyLen)) { | |
1442 | printf("***Error writing to %s.\n", outFile); | |
1443 | ourRtn = -1; | |
1444 | } | |
1445 | else { | |
1446 | printf("...wrote %u bytes to %s.\n", outKeyLen, outFile); | |
1447 | } | |
1448 | free(outKey); | |
1449 | } | |
1450 | else if(!doPrint) { | |
1451 | printf("...parsed a key but you didn't ask me to do anything with it.\n"); | |
1452 | } | |
1453 | if(rsa) { | |
1454 | RSA_free(rsa); | |
1455 | } | |
1456 | if(dsa) { | |
1457 | DSA_free(dsa); | |
1458 | } | |
1459 | ||
1460 | return 0; | |
1461 | } |