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