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 "SecImportExportOpenSSH.h" 32#include "SecImportExportUtils.h" 33#include "SecImportExportCrypto.h" 34#include <ctype.h> 35#include <CommonCrypto/CommonDigest.h> /* for CC_MD5_DIGEST_LENGTH */ 36#include <security_utilities/debugging.h> 37#include <security_cdsa_utils/cuCdsaUtils.h> 38 39#define SecSSHDbg(args...) secdebug("openssh", ## args) 40 41#define SSHv2_PUB_KEY_NAME "OpenSSHv2 Public Key" 42#define SSHv1_PUB_KEY_NAME "OpenSSHv1 Public Key" 43#define SSHv1_PRIV_KEY_NAME "OpenSSHv1 Private Key" 44 45#pragma mark --- Utility functions --- 46 47/* skip whitespace */ 48static void skipWhite( 49 const unsigned char *&cp, 50 unsigned &bytesLeft) 51{ 52 while(bytesLeft != 0) { 53 if(isspace((int)(*cp))) { 54 cp++; 55 bytesLeft--; 56 } 57 else { 58 return; 59 } 60 } 61} 62 63/* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */ 64static const unsigned char *findNextWhite( 65 const unsigned char *cp, 66 unsigned &bytesLeft) 67{ 68 while(bytesLeft != 0) { 69 if(isspace((int)(*cp))) { 70 return cp; 71 } 72 cp++; 73 bytesLeft--; 74 } 75 return cp; 76} 77 78/* obtain comment as the n'th whitespace-delimited field */ 79static char *commentAsNthField( 80 const unsigned char *key, 81 unsigned keyLen, 82 unsigned n) 83 84{ 85 unsigned dex; 86 87 skipWhite(key, keyLen); 88 if(keyLen == 0) { 89 return NULL; 90 } 91 for(dex=0; dex<(n-1); dex++) { 92 key = findNextWhite(key, keyLen); 93 if(keyLen == 0) { 94 return NULL; 95 } 96 skipWhite(key, keyLen); 97 if(keyLen == 0) { 98 return NULL; 99 } 100 } 101 102 /* cp points to start of nth field */ 103 char *rtnStr = (char *)malloc(keyLen + 1); 104 memmove(rtnStr, key, keyLen); 105 if(rtnStr[keyLen - 1] == '\n') { 106 /* normal terminator - snip it off */ 107 rtnStr[keyLen - 1] = '\0'; 108 } 109 else { 110 rtnStr[keyLen] = '\0'; 111 } 112 return rtnStr; 113 114} 115 116static uint32_t readUint32( 117 const unsigned char *&cp, // IN/OUT 118 unsigned &len) // IN/OUT 119{ 120 uint32_t r = 0; 121 122 for(unsigned dex=0; dex<sizeof(uint32_t); dex++) { 123 r <<= 8; 124 r |= *cp++; 125 } 126 len -= 4; 127 return r; 128} 129 130static uint16_t readUint16( 131 const unsigned char *&cp, // IN/OUT 132 unsigned &len) // IN/OUT 133{ 134 uint16_t r = *cp++; 135 r <<= 8; 136 r |= *cp++; 137 len -= 2; 138 return r; 139} 140 141/* Skip over an SSHv1 private key formatted bignum */ 142static void skipBigNum( 143 const unsigned char *&cp, // IN/OUT 144 unsigned &len) // IN/OUT 145{ 146 if(len < 2) { 147 cp += len; 148 len = 0; 149 return; 150 } 151 uint16 numBits = readUint16(cp, len); 152 unsigned numBytes = (numBits + 7) / 8; 153 if(numBytes > len) { 154 cp += len; 155 len = 0; 156 return; 157 } 158 cp += numBytes; 159 len -= numBytes; 160} 161 162static char *genPrintName( 163 const char *header, // e.g. SSHv2_PUB_KEY_NAME 164 const char *comment) // optional, from key 165{ 166 size_t totalLen = strlen(header) + 1; 167 if(comment) { 168 /* append ": <comment>" */ 169 totalLen += strlen(comment); 170 totalLen += 2; 171 } 172 char *rtnStr = (char *)malloc(totalLen); 173 if(comment) { 174 snprintf(rtnStr, totalLen, "%s: %s", header, comment); 175 } 176 else { 177 strcpy(rtnStr, header); 178 } 179 return rtnStr; 180} 181 182#pragma mark --- Infer PrintName attribute from raw keys --- 183 184/* obtain comment from OpenSSHv2 public key */ 185static char *opensshV2PubComment( 186 const unsigned char *key, 187 unsigned keyLen) 188{ 189 /* 190 * The format here is 191 * header 192 * <space> 193 * keyblob 194 * <space> 195 * optional comment 196 * \n 197 */ 198 char *comment = commentAsNthField(key, keyLen, 3); 199 char *rtnStr = genPrintName(SSHv2_PUB_KEY_NAME, comment); 200 if(comment) { 201 free(comment); 202 } 203 return rtnStr; 204} 205 206/* obtain comment from OpenSSHv1 public key */ 207static char *opensshV1PubComment( 208 const unsigned char *key, 209 unsigned keyLen) 210{ 211 /* 212 * Format: 213 * numbits 214 * <space> 215 * e (bignum in decimal) 216 * <space> 217 * n (bignum in decimal) 218 * <space> 219 * optional comment 220 * \n 221 */ 222 char *comment = commentAsNthField(key, keyLen, 4); 223 char *rtnStr = genPrintName(SSHv1_PUB_KEY_NAME, comment); 224 if(comment) { 225 free(comment); 226 } 227 return rtnStr; 228} 229 230static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n"; 231 232/* obtain comment from OpenSSHv1 private key, wrapped or clear */ 233static char *opensshV1PrivComment( 234 const unsigned char *key, 235 unsigned keyLen) 236{ 237 /* 238 * Format: 239 * "SSH PRIVATE KEY FILE FORMAT 1.1\n" 240 * 1 byte cipherSpec 241 * 4 byte spares 242 * 4 bytes numBits 243 * bignum n 244 * bignum e 245 * 4 byte comment length 246 * comment 247 * private key components, possibly encrypted 248 * 249 * A bignum is encoded like so: 250 * 2 bytes numBits 251 * (numBits + 7)/8 bytes of data 252 */ 253 /* length: ID string, NULL, Cipher, 4-byte spare */ 254 size_t len = strlen(authfile_id_string); 255 if(keyLen < (len + 6)) { 256 return NULL; 257 } 258 if(memcmp(authfile_id_string, key, len)) { 259 return NULL; 260 } 261 key += (len + 6); 262 keyLen -= (len + 6); 263 264 /* key points to numBits */ 265 if(keyLen < 4) { 266 return NULL; 267 } 268 key += 4; 269 keyLen -= 4; 270 271 /* key points to n */ 272 skipBigNum(key, keyLen); 273 if(keyLen == 0) { 274 return NULL; 275 } 276 skipBigNum(key, keyLen); 277 if(keyLen == 0) { 278 return NULL; 279 } 280 281 char *comment = NULL; 282 uint32 commentLen = readUint32(key, keyLen); 283 if((commentLen != 0) && (commentLen <= keyLen)) { 284 comment = (char *)malloc(commentLen + 1); 285 memmove(comment, key, commentLen); 286 comment[commentLen] = '\0'; 287 } 288 289 char *rtnStr = genPrintName(SSHv1_PRIV_KEY_NAME, comment); 290 if(comment) { 291 free(comment); 292 } 293 return rtnStr; 294} 295 296/* 297 * Infer PrintName attribute from raw key's 'comment' field. 298 * Returned string is mallocd and must be freed by caller. 299 */ 300char *impExpOpensshInferPrintName( 301 CFDataRef external, 302 SecExternalItemType externType, 303 SecExternalFormat externFormat) 304{ 305 const unsigned char *key = (const unsigned char *)CFDataGetBytePtr(external); 306 unsigned keyLen = (unsigned)CFDataGetLength(external); 307 switch(externType) { 308 case kSecItemTypePublicKey: 309 switch(externFormat) { 310 case kSecFormatSSH: 311 return opensshV1PubComment(key, keyLen); 312 case kSecFormatSSHv2: 313 return opensshV2PubComment(key, keyLen); 314 default: 315 /* impossible, right? */ 316 break; 317 } 318 break; 319 case kSecItemTypePrivateKey: 320 switch(externFormat) { 321 case kSecFormatSSH: 322 case kSecFormatWrappedSSH: 323 return opensshV1PrivComment(key, keyLen); 324 default: 325 break; 326 } 327 break; 328 default: 329 break; 330 } 331 return NULL; 332} 333 334#pragma mark --- Infer DescriptiveData from PrintName --- 335 336/* 337 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName 338 * attribute. 339 */ 340void impExpOpensshInferDescData( 341 SecKeyRef keyRef, 342 CssmOwnedData &descData) 343{ 344 OSStatus ortn; 345 SecKeychainAttributeInfo attrInfo; 346 SecKeychainAttrType attrType = kSecKeyPrintName; 347 attrInfo.count = 1; 348 attrInfo.tag = &attrType; 349 attrInfo.format = NULL; 350 SecKeychainAttributeList *attrList = NULL; 351 352 ortn = SecKeychainItemCopyAttributesAndData( 353 (SecKeychainItemRef)keyRef, 354 &attrInfo, 355 NULL, // itemClass 356 &attrList, 357 NULL, // don't need the data 358 NULL); 359 if(ortn) { 360 SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", (unsigned long)ortn); 361 return; 362 } 363 /* subsequent errors to errOut: */ 364 SecKeychainAttribute *attr = attrList->attr; 365 366 /* 367 * On a previous import, we would have set this to something like 368 * "OpenSSHv2 Public Key: comment". 369 * We want to strip off everything up to the actual comment. 370 */ 371 unsigned toStrip = 0; 372 373 /* min length of attribute value for this code to be meaningful */ 374 unsigned len = strlen(SSHv2_PUB_KEY_NAME) + 1; 375 char *printNameStr = NULL; 376 if(len < attr->length) { 377 printNameStr = (char *)malloc(attr->length + 1); 378 memmove(printNameStr, attr->data, attr->length); 379 printNameStr[attr->length] = '\0'; 380 if(strstr(printNameStr, SSHv2_PUB_KEY_NAME) == printNameStr) { 381 toStrip = strlen(SSHv2_PUB_KEY_NAME); 382 } 383 else if(strstr(printNameStr, SSHv1_PUB_KEY_NAME) == printNameStr) { 384 toStrip = strlen(SSHv1_PUB_KEY_NAME); 385 } 386 else if(strstr(printNameStr, SSHv1_PRIV_KEY_NAME) == printNameStr) { 387 toStrip = strlen(SSHv1_PRIV_KEY_NAME); 388 } 389 if(toStrip) { 390 /* only strip if we have ": " after toStrip bytes */ 391 if((printNameStr[toStrip] == ':') && (printNameStr[toStrip+1] == ' ')) { 392 toStrip += 2; 393 } 394 } 395 } 396 if(printNameStr) { 397 free(printNameStr); 398 } 399 len = attr->length; 400 401 unsigned char *attrVal; 402 403 if(len < toStrip) { 404 SecSSHDbg("impExpOpensshInferDescData: string parse screwup"); 405 goto errOut; 406 } 407 if(len > toStrip) { 408 /* Normal case of stripping off leading header */ 409 len -= toStrip; 410 } 411 else { 412 /* 413 * If equal, then the attr value *is* "OpenSSHv2 Public Key: " with 414 * no comment. Not sure how that could happen, but let's be careful. 415 */ 416 toStrip = 0; 417 } 418 419 attrVal = ((unsigned char *)attr->data) + toStrip; 420 descData.copy(attrVal, len); 421errOut: 422 SecKeychainItemFreeAttributesAndData(attrList, NULL); 423 return; 424} 425 426#pragma mark --- Derive SSHv1 wrap/unwrap key --- 427 428/* 429 * Common code to derive a wrap/unwrap key for OpenSSHv1. 430 * Caller must CSSM_FreeKey when done. 431 */ 432static CSSM_RETURN openSSHv1DeriveKey( 433 CSSM_CSP_HANDLE cspHand, 434 const SecKeyImportExportParameters *keyParams, // required 435 impExpVerifyPhrase verifyPhrase, // for secure passphrase 436 CSSM_KEY_PTR symKey) // RETURNED 437{ 438 CSSM_KEY *passKey = NULL; 439 CFDataRef cfPhrase = NULL; 440 CSSM_RETURN crtn; 441 OSStatus ortn; 442 CSSM_DATA dummyLabel; 443 uint32 keyAttr; 444 CSSM_CC_HANDLE ccHand = 0; 445 CSSM_ACCESS_CREDENTIALS creds; 446 CSSM_CRYPTO_DATA seed; 447 CSSM_DATA nullParam = {0, NULL}; 448 449 memset(symKey, 0, sizeof(CSSM_KEY)); 450 451 /* passphrase or passkey? */ 452 ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, verifyPhrase, 453 (CFTypeRef *)&cfPhrase, &passKey); 454 if(ortn) { 455 return ortn; 456 } 457 /* subsequent errors to errOut: */ 458 459 memset(&seed, 0, sizeof(seed)); 460 if(cfPhrase != NULL) { 461 /* TBD - caller-supplied empty passphrase means "export in the clear" */ 462 size_t len = CFDataGetLength(cfPhrase); 463 seed.Param.Data = (uint8 *)malloc(len); 464 seed.Param.Length = len; 465 memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len); 466 CFRelease(cfPhrase); 467 } 468 469 memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); 470 crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand, 471 CSSM_ALGID_OPENSSH1, 472 CSSM_ALGID_OPENSSH1, 473 CC_MD5_DIGEST_LENGTH * 8, 474 &creds, 475 passKey, // BaseKey 476 0, // iterationCount 477 NULL, // salt 478 &seed, 479 &ccHand); 480 if(crtn) { 481 SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure"); 482 goto errOut; 483 } 484 485 /* not extractable even for the short time this key lives */ 486 keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE; 487 dummyLabel.Data = (uint8 *)"temp unwrap key"; 488 dummyLabel.Length = strlen((char *)dummyLabel.Data); 489 490 crtn = CSSM_DeriveKey(ccHand, 491 &nullParam, 492 CSSM_KEYUSE_ANY, 493 keyAttr, 494 &dummyLabel, 495 NULL, // cred and acl 496 symKey); 497 if(crtn) { 498 SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure"); 499 } 500errOut: 501 if(ccHand != 0) { 502 CSSM_DeleteContext(ccHand); 503 } 504 if(passKey != NULL) { 505 CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE); 506 free(passKey); 507 } 508 if(seed.Param.Data) { 509 memset(seed.Param.Data, 0, seed.Param.Length); 510 free(seed.Param.Data); 511 } 512 return crtn; 513} 514 515#pragma mark -- OpenSSHv1 Wrap/Unwrap --- 516 517/* 518 * If cspHand is provided instead of importKeychain, the CSP 519 * handle MUST be for the CSPDL, not for the raw CSP. 520 */ 521OSStatus impExpWrappedOpenSSHImport( 522 CFDataRef inData, 523 SecKeychainRef importKeychain, // optional 524 CSSM_CSP_HANDLE cspHand, // required 525 SecItemImportExportFlags flags, 526 const SecKeyImportExportParameters *keyParams, // optional 527 const char *printName, 528 CFMutableArrayRef outArray) // optional, append here 529{ 530 OSStatus ortn; 531 impExpKeyUnwrapParams unwrapParams; 532 533 assert(cspHand != 0); 534 if(keyParams == NULL) { 535 return errSecParam; 536 } 537 memset(&unwrapParams, 0, sizeof(unwrapParams)); 538 539 /* derive unwrapping key */ 540 CSSM_KEY unwrappingKey; 541 542 ortn = openSSHv1DeriveKey(cspHand, keyParams, VP_Import, &unwrappingKey); 543 if(ortn) { 544 return ortn; 545 } 546 547 /* set up key to unwrap */ 548 CSSM_KEY wrappedKey; 549 CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader; 550 memset(&wrappedKey, 0, sizeof(CSSM_KEY)); 551 hdr.HeaderVersion = CSSM_KEYHEADER_VERSION; 552 /* CspId : don't care */ 553 hdr.BlobType = CSSM_KEYBLOB_WRAPPED; 554 hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1; 555 hdr.AlgorithmId = CSSM_ALGID_RSA; /* the oly algorithm supported in SSHv1 */ 556 hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY; 557 /* LogicalKeySizeInBits : calculated by CSP during unwrap */ 558 hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE; 559 hdr.KeyUsage = CSSM_KEYUSE_ANY; 560 561 wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(inData); 562 wrappedKey.KeyData.Length = CFDataGetLength(inData); 563 564 unwrapParams.unwrappingKey = &unwrappingKey; 565 unwrapParams.encrAlg = CSSM_ALGID_OPENSSH1; 566 567 /* GO */ 568 ortn = impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand, 569 flags, keyParams, &unwrapParams, printName, outArray); 570 571 if(unwrappingKey.KeyData.Data != NULL) { 572 CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE); 573 } 574 return ortn; 575} 576 577OSStatus impExpWrappedOpenSSHExport( 578 SecKeyRef secKey, 579 SecItemImportExportFlags flags, 580 const SecKeyImportExportParameters *keyParams, // optional 581 const CssmData &descData, 582 CFMutableDataRef outData) // output appended here 583{ 584 CSSM_CSP_HANDLE cspdlHand = 0; 585 OSStatus ortn; 586 bool releaseCspHand = false; 587 CSSM_RETURN crtn; 588 589 if(keyParams == NULL) { 590 return errSecParam; 591 } 592 593 /* we need a CSPDL handle - try to get it from the key */ 594 ortn = SecKeyGetCSPHandle(secKey, &cspdlHand); 595 if(ortn) { 596 cspdlHand = cuCspStartup(CSSM_FALSE); 597 if(cspdlHand == 0) { 598 return CSSMERR_CSSM_ADDIN_LOAD_FAILED; 599 } 600 releaseCspHand = true; 601 } 602 /* subsequent errors to errOut: */ 603 604 /* derive wrapping key */ 605 CSSM_KEY wrappingKey; 606 crtn = openSSHv1DeriveKey(cspdlHand, keyParams, VP_Export, &wrappingKey); 607 if(crtn) { 608 goto errOut; 609 } 610 611 /* GO */ 612 CSSM_KEY wrappedKey; 613 memset(&wrappedKey, 0, sizeof(CSSM_KEY)); 614 615 crtn = impExpExportKeyCommon(cspdlHand, secKey, &wrappingKey, &wrappedKey, 616 CSSM_ALGID_OPENSSH1, CSSM_ALGMODE_NONE, CSSM_PADDING_NONE, 617 CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1, CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE, 618 &descData, 619 NULL); // IV 620 if(crtn) { 621 goto errOut; 622 } 623 624 /* the wrappedKey's KeyData is out output */ 625 CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length); 626 CSSM_FreeKey(cspdlHand, NULL, &wrappedKey, CSSM_FALSE); 627 628errOut: 629 if(releaseCspHand) { 630 cuCspDetachUnload(cspdlHand, CSSM_FALSE); 631 } 632 return crtn; 633 634} 635