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 48uint32_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 62void 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 */ 77static 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) */ 97static 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 */ 138static 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 */ 162static 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) */ 177static 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 */ 187static 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 */ 203static 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 */ 223static 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 277CSSM_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)); 319errOut: 320 CFRelease(cfOut); 321 return ourRtn; 322} 323 324CSSM_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 383CSSM_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 400extern 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 411CSSM_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)); 460errOut: 461 CFRelease(cfOut); 462 if(b64) { 463 free(b64); 464 } 465 return ourRtn; 466} 467 468CSSM_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 530errOut: 531 free(decodedBlob); 532 return ourRtn; 533} 534 535#pragma mark -- DSA OpenSSHv2 --- 536 537CSSM_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 595errOut: 596 CFRelease(cfOut); 597 if(b64) { 598 free(b64); 599 } 600 return ourRtn; 601} 602 603CSSM_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 677errOut: 678 free(decodedBlob); 679 return ourRtn; 680 681} 682 683