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