]> git.saurik.com Git - apple/security.git/blob - libsecurity_apple_csp/lib/opensshCoding.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_apple_csp / lib / opensshCoding.cpp
1 /*
2 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 /*
26 * opensshCoding.cpp - Encoding and decoding of OpenSSH format public keys.
27 *
28 * Created 8/29/2006 by dmitch.
29 */
30
31 #include "opensshCoding.h"
32 #include <CoreFoundation/CFData.h>
33 #include <openssl/bn.h>
34 #include <openssl/crypto.h>
35 #include <security_cdsa_utils/cuEnc64.h>
36
37 #define SSH2_RSA_HEADER "ssh-rsa"
38 #define SSH2_DSA_HEADER "ssh-dss"
39
40 #ifndef NDEBUG
41 #include <stdio.h>
42 #define dprintf(s...) printf(s)
43 #else
44 #define dprintf(...)
45 #endif
46
47 #pragma mark --- commmon code ---
48
49 uint32_t readUint32(
50 const unsigned char *&cp, // IN/OUT
51 unsigned &len) // IN/OUT
52 {
53 uint32_t r = 0;
54
55 for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
56 r <<= 8;
57 r |= *cp++;
58 }
59 len -= 4;
60 return r;
61 }
62
63 void appendUint32(
64 CFMutableDataRef cfOut,
65 uint32_t ui)
66 {
67 UInt8 buf[sizeof(uint32_t)];
68
69 for(int dex=(sizeof(uint32_t) - 1); dex>=0; dex--) {
70 buf[dex] = ui & 0xff;
71 ui >>= 8;
72 }
73 CFDataAppendBytes(cfOut, buf, sizeof(uint32_t));
74 }
75
76
77 /* parse text as decimal, return BIGNUM */
78 static BIGNUM *parseDecimalBn(
79 const unsigned char *cp,
80 unsigned len)
81 {
82 for(unsigned dex=0; dex<len; dex++) {
83 char c = *cp;
84 if((c < '0') || (c > '9')) {
85 return NULL;
86 }
87 }
88 char *str = (char *)malloc(len + 1);
89 memmove(str, cp, len);
90 str[len] = '\0';
91 BIGNUM *bn = NULL;
92 BN_dec2bn(&bn, str);
93 free(str);
94 return bn;
95 }
96
97 /* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */
98 static CSSM_RETURN appendBigNum2(
99 CFMutableDataRef cfOut,
100 const BIGNUM *bn)
101 {
102 if(bn == NULL) {
103 dprintf("appendBigNum2: NULL bn");
104 return CSSMERR_CSP_INTERNAL_ERROR;
105 }
106 if (BN_is_zero(bn)) {
107 appendUint32(cfOut, 0);
108 return 0;
109 }
110 if(bn->neg) {
111 dprintf("appendBigNum2: negative numbers not supported\n");
112 return CSSMERR_CSP_INTERNAL_ERROR;
113 }
114 int numBytes = BN_num_bytes(bn);
115 unsigned char buf[numBytes];
116 int moved = BN_bn2bin(bn, buf);
117 if(moved != numBytes) {
118 dprintf("appendBigNum: BN_bn2bin() screwup\n");
119 return CSSMERR_CSP_INTERNAL_ERROR;
120 }
121 bool appendZero = false;
122 if(buf[0] & 0x80) {
123 /* prepend leading zero to make it positive */
124 appendZero = true;
125 numBytes++; // to encode the correct 4-byte length
126 }
127 appendUint32(cfOut, (uint32_t)numBytes);
128 if(appendZero) {
129 UInt8 z = 0;
130 CFDataAppendBytes(cfOut, &z, 1);
131 numBytes--; // to append the correct number of bytes
132 }
133 CFDataAppendBytes(cfOut, buf, numBytes);
134 memset(buf, 0, numBytes);
135 return CSSM_OK;
136 }
137
138 /* read BIGNUM, OpenSSH-2 mpint version */
139 static BIGNUM *readBigNum2(
140 const unsigned char *&cp, // IN/OUT
141 unsigned &remLen) // IN/OUT
142 {
143 if(remLen < 4) {
144 dprintf("readBigNum2: short record(1)\n");
145 return NULL;
146 }
147 uint32_t bytes = readUint32(cp, remLen);
148 if(remLen < bytes) {
149 dprintf("readBigNum2: short record(2)\n");
150 return NULL;
151 }
152 BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
153 if(bn == NULL) {
154 dprintf("readBigNum2: BN_bin2bn error\n");
155 return NULL;
156 }
157 cp += bytes;
158 remLen -= bytes;
159 return bn;
160 }
161
162 /* Write BIGNUM, OpenSSH-1 decimal (public key) version */
163 static CSSM_RETURN appendBigNumDec(
164 CFMutableDataRef cfOut,
165 const BIGNUM *bn)
166 {
167 char *buf = BN_bn2dec(bn);
168 if(buf == NULL) {
169 dprintf("appendBigNumDec: BN_bn2dec() error");
170 return CSSMERR_CSP_INTERNAL_ERROR;
171 }
172 CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf));
173 Free(buf);
174 return CSSM_OK;
175 }
176
177 /* write string, OpenSSH v2 format (with a 4-byte byte count) */
178 static void appendString(
179 CFMutableDataRef cfOut,
180 const char *str,
181 unsigned strLen)
182 {
183 appendUint32(cfOut, (uint32_t)strLen);
184 CFDataAppendBytes(cfOut, (UInt8 *)str, strLen);
185 }
186
187 /* skip whitespace */
188 static void skipWhite(
189 const unsigned char *&cp,
190 unsigned &bytesLeft)
191 {
192 while(bytesLeft != 0) {
193 if(isspace((int)(*cp))) {
194 cp++;
195 bytesLeft--;
196 }
197 else {
198 return;
199 }
200 }
201 }
202
203 /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
204 static const unsigned char *findNextWhite(
205 const unsigned char *cp,
206 unsigned &bytesLeft)
207 {
208 while(bytesLeft != 0) {
209 if(isspace((int)(*cp))) {
210 return cp;
211 }
212 cp++;
213 bytesLeft--;
214 }
215 return cp;
216 }
217
218
219 /*
220 * Decode components from an SSHv2 public key.
221 * Also verifies the leading header, e.g. "ssh-rsa".
222 * The returned decodedBlob is algorithm-specific.
223 */
224 static CSSM_RETURN parseSSH2PubKey(
225 const unsigned char *key,
226 unsigned keyLen,
227 const char *header, // SSH2_RSA_HEADER, SSH2_DSA_HEADER
228 unsigned char **decodedBlob, // mallocd and RETURNED
229 unsigned *decodedBlobLen) // RETURNED
230 {
231 unsigned len = strlen(header);
232 *decodedBlob = NULL;
233
234 /* ID string plus at least one space */
235 if(keyLen < (len + 1)) {
236 dprintf("parseSSH2PubKey: short record(1)\n");
237 return CSSMERR_CSP_INVALID_KEY;
238 }
239
240 if(memcmp(header, key, len)) {
241 dprintf("parseSSH2PubKey: bad header (1)\n");
242 return CSSMERR_CSP_INVALID_KEY;
243 }
244 key += len;
245 if(*key++ != ' ') {
246 dprintf("parseSSH2PubKey: bad header (2)\n");
247 return CSSMERR_CSP_INVALID_KEY;
248 }
249 keyLen -= (len + 1);
250
251 /* key points to first whitespace after header */
252 skipWhite(key, keyLen);
253 if(keyLen == 0) {
254 dprintf("parseSSH2PubKey: short key\n");
255 return CSSMERR_CSP_INVALID_KEY;
256 }
257
258 /* key is start of base64 blob */
259 const unsigned char *encodedBlob = key;
260 const unsigned char *endBlob = findNextWhite(key, keyLen);
261 unsigned encodedBlobLen = endBlob - encodedBlob;
262
263 /* decode base 64 */
264 *decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen);
265 if(*decodedBlob == NULL) {
266 dprintf("parseSSH2PubKey: base64 decode error\n");
267 return CSSMERR_CSP_INVALID_KEY;
268 }
269
270 /* skip remainder; it's comment */
271
272 return CSSM_OK;
273 }
274
275
276 #pragma mark -- RSA OpenSSHv1 ---
277
278 CSSM_RETURN RSAPublicKeyEncodeOpenSSH1(
279 RSA *rsa,
280 const CssmData &descData,
281 CssmOwnedData &encodedKey)
282 {
283 CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
284 CSSM_RETURN ourRtn = CSSM_OK;
285
286 /*
287 * Format is
288 * num_bits in decimal
289 * <space>
290 * e, bignum in decimal
291 * <space>
292 * n, bignum in decimal
293 * <space>
294 * optional comment
295 * newline
296 */
297 unsigned numBits = BN_num_bits(rsa->n);
298 char bitString[20];
299 UInt8 c = ' ';
300
301 snprintf(bitString, sizeof(bitString), "%u ", numBits);
302 CFDataAppendBytes(cfOut, (const UInt8 *)bitString, strlen(bitString));
303 if(ourRtn = appendBigNumDec(cfOut, rsa->e)) {
304 goto errOut;
305 }
306 CFDataAppendBytes(cfOut, &c, 1);
307 if(ourRtn = appendBigNumDec(cfOut, rsa->n)) {
308 goto errOut;
309 }
310
311 if(descData.Length) {
312 /* optional comment */
313 CFDataAppendBytes(cfOut, &c, 1);
314 CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
315 }
316
317 c = '\n';
318 CFDataAppendBytes(cfOut, &c, 1);
319 encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
320 errOut:
321 CFRelease(cfOut);
322 return ourRtn;
323 }
324
325 CSSM_RETURN RSAPublicKeyDecodeOpenSSH1(
326 RSA *rsa,
327 void *p,
328 size_t length)
329 {
330 const unsigned char *cp = (const unsigned char *)p;
331 unsigned remLen = length;
332
333 skipWhite(cp, remLen);
334
335 /*
336 * cp points to start of size_in_bits in ASCII decimal; we really don't care about
337 * this field. Find next space.
338 */
339 cp = findNextWhite(cp, remLen);
340 if(remLen == 0) {
341 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n");
342 return CSSMERR_CSP_INVALID_KEY;
343 }
344 skipWhite(cp, remLen);
345 if(remLen == 0) {
346 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n");
347 return CSSMERR_CSP_INVALID_KEY;
348 }
349
350 /*
351 * cp points to start of e
352 */
353 const unsigned char *ep = findNextWhite(cp, remLen);
354 if(remLen == 0) {
355 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n");
356 return CSSMERR_CSP_INVALID_KEY;
357 }
358 unsigned len = ep - cp;
359 rsa->e = parseDecimalBn(cp, len);
360 if(rsa->e == NULL) {
361 return CSSMERR_CSP_INVALID_KEY;
362 }
363 cp += len;
364
365 skipWhite(cp, remLen);
366 if(remLen == 0) {
367 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n");
368 return -1;
369 }
370
371 /* cp points to start of n */
372 ep = findNextWhite(cp, remLen);
373 len = ep - cp;
374 rsa->n = parseDecimalBn(cp, len);
375 if(rsa->n == NULL) {
376 return CSSMERR_CSP_INVALID_KEY;
377 }
378
379 /* remainder is comment, we ignore */
380 return CSSM_OK;
381
382 }
383
384 CSSM_RETURN RSAPrivateKeyEncodeOpenSSH1(
385 RSA *rsa,
386 const CssmData &descData,
387 CssmOwnedData &encodedKey)
388 {
389 CFDataRef cfOut;
390 CSSM_RETURN ourRtn;
391
392 ourRtn = encodeOpenSSHv1PrivKey(rsa, descData.Data, descData.Length, NULL, &cfOut);
393 if(ourRtn) {
394 return ourRtn;
395 }
396 encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
397 CFRelease(cfOut);
398 return CSSM_OK;
399 }
400
401 extern CSSM_RETURN RSAPrivateKeyDecodeOpenSSH1(
402 RSA *openKey,
403 void *p,
404 size_t length)
405 {
406 return decodeOpenSSHv1PrivKey((const unsigned char *)p, length,
407 openKey, NULL, NULL, NULL);
408 }
409
410 #pragma mark -- RSA OpenSSHv2 ---
411
412 CSSM_RETURN RSAPublicKeyEncodeOpenSSH2(
413 RSA *rsa,
414 const CssmData &descData,
415 CssmOwnedData &encodedKey)
416 {
417 unsigned char *b64 = NULL;
418 unsigned b64Len;
419 UInt8 c;
420
421 /*
422 * First, the inner base64-encoded blob, consisting of
423 * ssh-rsa
424 * e
425 * n
426 */
427 CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
428 CSSM_RETURN ourRtn = CSSM_OK;
429 appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
430 if(ourRtn = appendBigNum2(cfOut, rsa->e)) {
431 goto errOut;
432 }
433 if(ourRtn = appendBigNum2(cfOut, rsa->n)) {
434 goto errOut;
435 }
436
437 /* base64 encode that */
438 b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
439
440 /* cuEnc64 added newline and NULL, which we really don't want */
441 b64Len -= 2;
442
443 /* Now start over, dropping that base64 into a public blob. */
444 CFDataSetLength(cfOut, 0);
445 CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
446 c = ' ';
447 CFDataAppendBytes(cfOut, &c, 1);
448 CFDataAppendBytes(cfOut, b64, b64Len);
449
450 if(descData.Length) {
451 /* optional comment */
452 CFDataAppendBytes(cfOut, &c, 1);
453 CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
454 }
455
456 /* finish it with a newline */
457 c = '\n';
458 CFDataAppendBytes(cfOut, &c, 1);
459
460 encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
461 errOut:
462 CFRelease(cfOut);
463 if(b64) {
464 free(b64);
465 }
466 return ourRtn;
467 }
468
469 CSSM_RETURN RSAPublicKeyDecodeOpenSSH2(
470 RSA *rsa,
471 void *p,
472 size_t length)
473 {
474 const unsigned char *key = (const unsigned char *)p;
475 unsigned keyLen = length;
476 CSSM_RETURN ourRtn;
477
478 /*
479 * Verify header
480 * get base64-decoded blob
481 */
482 unsigned char *decodedBlob = NULL;
483 unsigned decodedBlobLen = 0;
484 if(ourRtn = parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen)) {
485 return ourRtn;
486 }
487 /* subsequent errors to errOut: */
488
489 /*
490 * The inner base64-decoded blob, consisting of
491 * ssh-rsa
492 * e
493 * n
494 */
495 uint32_t decLen;
496 unsigned len;
497
498 key = decodedBlob;
499 keyLen = decodedBlobLen;
500 if(keyLen < 12) {
501 /* three length fields at least */
502 dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n");
503 ourRtn = -1;
504 goto errOut;
505 }
506 decLen = readUint32(key, keyLen);
507 len = strlen(SSH2_RSA_HEADER);
508 if(decLen != len) {
509 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
510 ourRtn = CSSMERR_CSP_INVALID_KEY;
511 goto errOut;
512 }
513 if(memcmp(SSH2_RSA_HEADER, key, len)) {
514 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
515 return CSSMERR_CSP_INVALID_KEY;
516 }
517 key += len;
518 keyLen -= len;
519
520 rsa->e = readBigNum2(key, keyLen);
521 if(rsa->e == NULL) {
522 ourRtn = CSSMERR_CSP_INVALID_KEY;
523 goto errOut;
524 }
525 rsa->n = readBigNum2(key, keyLen);
526 if(rsa->n == NULL) {
527 ourRtn = CSSMERR_CSP_INVALID_KEY;
528 goto errOut;
529 }
530
531 errOut:
532 free(decodedBlob);
533 return ourRtn;
534 }
535
536 #pragma mark -- DSA OpenSSHv2 ---
537
538 CSSM_RETURN DSAPublicKeyEncodeOpenSSH2(
539 DSA *dsa,
540 const CssmData &descData,
541 CssmOwnedData &encodedKey)
542 {
543 unsigned char *b64 = NULL;
544 unsigned b64Len;
545 UInt8 c;
546
547 /*
548 * First, the inner base64-encoded blob, consisting of
549 * ssh-dss
550 * p
551 * q
552 * g
553 * pub_key
554 */
555 CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
556 int ourRtn = 0;
557 appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
558 if(ourRtn = appendBigNum2(cfOut, dsa->p)) {
559 goto errOut;
560 }
561 if(ourRtn = appendBigNum2(cfOut, dsa->q)) {
562 goto errOut;
563 }
564 if(ourRtn = appendBigNum2(cfOut, dsa->g)) {
565 goto errOut;
566 }
567 if(ourRtn = appendBigNum2(cfOut, dsa->pub_key)) {
568 goto errOut;
569 }
570
571 /* base64 encode that */
572 b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
573
574 /* cuEnc64 added newline and NULL, which we really don't want */
575 b64Len -= 2;
576
577 /* Now start over, dropping that base64 into a public blob. */
578 CFDataSetLength(cfOut, 0);
579 CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
580 c = ' ';
581 CFDataAppendBytes(cfOut, &c, 1);
582 CFDataAppendBytes(cfOut, b64, b64Len);
583
584 if(descData.Length) {
585 /* optional comment */
586 CFDataAppendBytes(cfOut, &c, 1);
587 CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
588 }
589
590 /* finish it with a newline */
591 c = '\n';
592 CFDataAppendBytes(cfOut, &c, 1);
593
594 encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
595
596 errOut:
597 CFRelease(cfOut);
598 if(b64) {
599 free(b64);
600 }
601 return ourRtn;
602 }
603
604 CSSM_RETURN DSAPublicKeyDecodeOpenSSH2(
605 DSA *dsa,
606 void *p,
607 size_t length)
608 {
609 const unsigned char *key = (const unsigned char *)p;
610 unsigned keyLen = length;
611 CSSM_RETURN ourRtn;
612
613 /*
614 * Verify header
615 * get base64-decoded blob
616 */
617 unsigned char *decodedBlob = NULL;
618 unsigned decodedBlobLen = 0;
619 if(ourRtn = parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen)) {
620 return ourRtn;
621 }
622 /* subsequent errors to errOut: */
623
624 /*
625 * The inner base64-decoded blob, consisting of
626 * ssh-dss
627 * p
628 * q
629 * g
630 * pub_key
631 */
632 uint32_t decLen;
633 unsigned len;
634
635 key = decodedBlob;
636 keyLen = decodedBlobLen;
637 if(keyLen < 20) {
638 /* five length fields at least */
639 dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n");
640 ourRtn = CSSMERR_CSP_INVALID_KEY;
641 goto errOut;
642 }
643 decLen = readUint32(key, keyLen);
644 len = strlen(SSH2_DSA_HEADER);
645 if(decLen != len) {
646 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
647 ourRtn = CSSMERR_CSP_INVALID_KEY;
648 goto errOut;
649 }
650 if(memcmp(SSH2_DSA_HEADER, key, len)) {
651 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
652 return CSSMERR_CSP_INVALID_KEY;
653 }
654 key += len;
655 keyLen -= len;
656
657 dsa->p = readBigNum2(key, keyLen);
658 if(dsa->p == NULL) {
659 ourRtn = CSSMERR_CSP_INVALID_KEY;
660 goto errOut;
661 }
662 dsa->q = readBigNum2(key, keyLen);
663 if(dsa->q == NULL) {
664 ourRtn = CSSMERR_CSP_INVALID_KEY;
665 goto errOut;
666 }
667 dsa->g = readBigNum2(key, keyLen);
668 if(dsa->g == NULL) {
669 ourRtn = CSSMERR_CSP_INVALID_KEY;
670 goto errOut;
671 }
672 dsa->pub_key = readBigNum2(key, keyLen);
673 if(dsa->pub_key == NULL) {
674 ourRtn = CSSMERR_CSP_INVALID_KEY;
675 goto errOut;
676 }
677
678 errOut:
679 free(decodedBlob);
680 return ourRtn;
681
682 }
683