1/* 2 * Copyright (c) 2004,2011-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 * SecImportExportPem.cpp - private PEM routines for SecImportExport 24 */ 25 26#include "SecImportExportPem.h" 27#include "SecExternalRep.h" 28#include "SecImportExportUtils.h" 29#include <security_cdsa_utils/cuEnc64.h> 30#include <security_cdsa_utils/cuPem.h> 31#include <stdlib.h> 32#include <string.h> 33#include <ctype.h> 34 35/* 36 * Text parsing routines. 37 * 38 * Search incoming text for specified string. Does not assume inText is 39 * NULL terminated. Returns pointer to start of found string in inText. 40 */ 41static const char *findStr( 42 const char *inText, 43 unsigned inTextLen, 44 const char *str) // NULL terminated - search for this 45{ 46 /* probably not the hottest string search algorithm... */ 47 const char *cp; 48 unsigned srchStrLen = (unsigned)strlen(str); 49 char c = str[0]; 50 51 /* last char * we can search in inText for start of str */ 52 const char *endCp = inText + inTextLen - srchStrLen; 53 54 for(cp=inText; cp<=endCp; cp++) { 55 if(*cp == c) { 56 if(!memcmp(cp, str, srchStrLen)) { 57 return cp; 58 } 59 } 60 } 61 return NULL; 62} 63 64/* 65 * Obtain one line from current text. Returns a mallocd, NULL-terminated string 66 * which caller must free(). Also returns the number of chars consumed including 67 * the returned chars PLUS EOL terminators (\n and/or \r). 68 * 69 * ALWAYS returns a mallocd string if there is ANY data remaining per the 70 * incoming inTextLen. Returns NULL if inTextLen is zero. 71 */ 72static const char *getLine( 73 const char *inText, 74 unsigned inTextLen, // RETURNED 75 unsigned *consumed) // RETURNED 76 77{ 78 *consumed = 0; 79 const char *cp = inText; 80 const char *newline = NULL; // if we found a newline, this points to the first one 81 82 while(inTextLen) { 83 char c = *cp; 84 if((c == '\r') || (c == '\n')) { 85 if(newline == NULL) { 86 /* first newline */ 87 newline = cp; 88 } 89 } 90 else if(newline != NULL) { 91 /* non newline after newline, done */ 92 break; 93 } 94 (*consumed)++; 95 inTextLen--; 96 cp++; 97 } 98 unsigned linelen; 99 if(newline) { 100 linelen = (unsigned)(newline - inText); 101 } 102 else { 103 linelen = *consumed; 104 } 105 char *rtn = (char *)malloc(linelen + 1); 106 memmove(rtn, inText, linelen); 107 rtn[linelen] = 0; 108 return rtn; 109} 110 111/* 112 * Table to facilitate conversion of known PEM header strings to 113 * the things we know about. 114 */ 115typedef struct { 116 const char *pemStr; // e.g. PEM_STRING_X509, "CERTIFICATE" 117 SecExternalItemType itemType; 118 SecExternalFormat format; 119 CSSM_ALGORITHMS keyAlg; 120} PemHeader; 121 122#define NOALG CSSM_ALGID_NONE 123 124static const PemHeader PemHeaders[] = 125{ 126 /* from openssl/pem.h standard header */ 127 { PEM_STRING_X509_OLD, kSecItemTypeCertificate, kSecFormatX509Cert, NOALG}, 128 { PEM_STRING_X509, kSecItemTypeCertificate, kSecFormatX509Cert, NOALG }, 129 { PEM_STRING_EVP_PKEY, kSecItemTypePrivateKey, kSecFormatOpenSSL, NOALG}, 130 { PEM_STRING_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, NOALG }, 131 { PEM_STRING_RSA, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_RSA }, 132 { PEM_STRING_RSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_RSA }, 133 { PEM_STRING_DSA, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_DSA }, 134 { PEM_STRING_DSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_DSA }, 135 { PEM_STRING_PKCS7, kSecItemTypeAggregate, kSecFormatPKCS7, NOALG }, 136 { PEM_STRING_PKCS8, kSecItemTypePrivateKey, kSecFormatWrappedPKCS8, NOALG }, 137 { PEM_STRING_PKCS8INF, kSecItemTypePrivateKey, kSecFormatUnknown, NOALG }, 138 /* we define these */ 139 { PEM_STRING_DH_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_DH }, 140 { PEM_STRING_DH_PRIVATE, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_DH }, 141 { PEM_STRING_PKCS12, kSecItemTypeAggregate, kSecFormatPKCS12, NOALG }, 142 { PEM_STRING_SESSION, kSecItemTypeSessionKey, kSecFormatRawKey, NOALG }, 143 { PEM_STRING_ECDSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_ECDSA }, 144 { PEM_STRING_ECDSA_PRIVATE, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_ECDSA } 145}; 146#define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader)) 147 148/* 149 * PEM decode incoming data which we've previously determined to contain 150 * exactly one reasonably well formed PEM blob (it has no more than one 151 * START and END line - though it may have none - and is all ASCII). 152 * 153 * Returned SecImportRep may or may not have a known type and format and 154 * (if it is a key) algorithm. 155 */ 156static OSStatus impExpImportSinglePEM( 157 const char *currCp, 158 unsigned lenToGo, 159 CFMutableArrayRef importReps) // output appended here 160{ 161 unsigned consumed; 162 const char *currLine = NULL; // mallocd by getLine() 163 const char *lastCp = currCp; 164 CFMutableArrayRef pemParamLines = NULL; 165 OSStatus ortn = errSecSuccess; 166 CFDataRef cdata = NULL; 167 Security::KeychainCore::SecImportRep *rep = NULL; 168 const char *start64; 169 unsigned base64Len; 170 const char *end64; 171 unsigned char *decData; 172 unsigned decDataLen; 173 174 /* we try to glean these from the header, but it's not fatal if we can not */ 175 SecExternalFormat format = kSecFormatUnknown; 176 SecExternalItemType itemType = kSecItemTypeUnknown; 177 CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE; 178 179 /* search to START line, parse it to get type/format/alg */ 180 const char *startLine = findStr(currCp, lenToGo, "-----BEGIN"); 181 if(startLine != NULL) { 182 /* possibly skip over leading garbage */ 183 consumed = (unsigned)(startLine - currCp); 184 lenToGo -= consumed; 185 currCp = startLine; 186 187 /* get C string of START line */ 188 currLine = getLine(startLine, lenToGo, &consumed); 189 if(currLine == NULL) { 190 /* somehow got here with no data */ 191 assert(lenToGo == 0); 192 SecImpInferDbg("impExpImportSinglePEM empty data"); 193 ortn = errSecUnsupportedFormat; 194 goto errOut; 195 } 196 assert(consumed <= lenToGo); 197 currCp += consumed; 198 lenToGo -= consumed; 199 200 /* 201 * Search currLine for known PEM header strings. 202 * It is not an error if we don't recognize this 203 * header. 204 */ 205 for(unsigned dex=0; dex<NUM_PEM_HEADERS; dex++) { 206 const PemHeader *ph = &PemHeaders[dex]; 207 if(!strstr(currLine, ph->pemStr)) { 208 continue; 209 } 210 /* found one! */ 211 format = ph->format; 212 itemType = ph->itemType; 213 keyAlg = ph->keyAlg; 214 break; 215 } 216 217 free((void *)currLine); 218 } 219 220 /* 221 * Skip empty lines. Save all lines containing ':' (used by openssl 222 * to specify key wrapping parameters). These will be saved in 223 * outgoing SecImportReps' pemParamLines. 224 */ 225 for( ; ; ) { 226 currLine = getLine(currCp, lenToGo, &consumed); 227 if(currLine == NULL || currCp == lastCp) { 228 /* out of data (unable to advance to next line) */ 229 SecImpInferDbg("impExpImportSinglePEM out of data"); 230 if (currLine) free((void *)currLine); 231 ortn = errSecUnsupportedFormat; 232 goto errOut; 233 } 234 lastCp = currCp; 235 236 bool skipThis = false; 237 unsigned lineLen = (unsigned)strlen(currLine); 238 if(lineLen == 0) { 239 /* empty line */ 240 skipThis = true; 241 } 242 if(strchr(currLine, ':')) { 243 /* 244 * Save this PEM header info. Used for traditional openssl 245 * wrapped keys to indicate IV. 246 */ 247 SecImpInferDbg("import PEM: param line %s", currLine); 248 CFStringRef cfStr = CFStringCreateWithCString(NULL, currLine, 249 kCFStringEncodingASCII); 250 if(pemParamLines == NULL) { 251 /* first param line */ 252 pemParamLines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 253 254 /* 255 * If it says "ENCRYPTED" and this is a private key, 256 * flag the fact that it's wrapped in openssl format 257 */ 258 if(strstr(currLine, "ENCRYPTED")) { 259 if((format == kSecFormatOpenSSL) && 260 (itemType == kSecItemTypePrivateKey)) { 261 format = kSecFormatWrappedOpenSSL; 262 } 263 } 264 } 265 CFArrayAppendValue(pemParamLines, cfStr); 266 CFRelease(cfStr); // array owns it 267 skipThis = true; 268 } 269 free((void *)currLine); 270 if(!skipThis) { 271 /* looks like good stuff; process */ 272 break; 273 } 274 /* skip this line */ 275 assert(consumed <= lenToGo); 276 currCp += consumed; 277 lenToGo -= consumed; 278 } 279 if(lenToGo <= 2) { 280 SecImpInferDbg("impExpImportSinglePEM no valid base64 data"); 281 ortn = errSecUnsupportedFormat; 282 goto errOut; 283 } 284 285 /* 286 * currCP points to start of base64 data - mark it and search for end line. 287 * We skip everything after the end line. 288 */ 289 start64 = currCp; 290 base64Len = lenToGo; // if no END 291 end64 = findStr(currCp, lenToGo, "-----END"); 292 if(end64 != NULL) { 293 if(end64 == start64) { 294 /* Empty, nothing between START and END */ 295 SecImpInferDbg("impExpImportSinglePEM no base64 between terminators"); 296 ortn = errSecUnsupportedFormat; 297 goto errOut; 298 } 299 base64Len = (unsigned)(end64 - start64); 300 } 301 /* else no END, no reason to complain about that as long as base64 decode works OK */ 302 303 /* Base 64 decode */ 304 decData = cuDec64((const unsigned char *)start64, base64Len, &decDataLen); 305 if(decData == NULL) { 306 SecImpInferDbg("impExpImportSinglePEM bad base64 data"); 307 ortn = errSecUnsupportedFormat; 308 goto errOut; 309 } 310 311 cdata = CFDataCreate(NULL, decData, decDataLen); 312 free((void *)decData); 313 rep = new Security::KeychainCore::SecImportRep(cdata, itemType, format, keyAlg, 314 pemParamLines); 315 CFArrayAppendValue(importReps, rep); 316 CFRelease(cdata); // SecImportRep holds ref 317 return errSecSuccess; 318 319errOut: 320 if(pemParamLines != NULL) { 321 CFRelease(pemParamLines); 322 } 323 return ortn; 324} 325 326/* 327 * PEM decode incoming data, appending SecImportRep's to specified array. 328 * Returned SecImportReps may or may not have a known type and format and 329 * (if they are keys) algorithm. 330 */ 331OSStatus impExpParsePemToImportRefs( 332 CFDataRef importedData, 333 CFMutableArrayRef importReps, // output appended here 334 bool *isPem) // true means we think it was PEM regardless of 335 // final return code 336{ 337 /* 338 * First task: is this PEM or at least base64 encoded? 339 */ 340 const char *currCp = (const char *)CFDataGetBytePtr(importedData); 341 const char *cp = currCp; 342 unsigned lenToGo = (unsigned)CFDataGetLength(importedData); 343 OSStatus ortn; 344 345 *isPem = false; 346 unsigned dex; 347 bool allBlanks = true; 348 349 for(dex=0; dex<lenToGo; dex++, cp++) { 350 if (!isspace(*cp)) { 351 // it's not a space. Is it a non-ascii character? 352 if (!isascii(*cp)) { 353 return errSecSuccess; 354 } 355 356 // is it a control character? 357 if (iscntrl(*cp)) 358 { 359 return errSecSuccess; 360 } 361 362 // no, mark that an acceptable character was encountered and keep going 363 allBlanks = false; 364 } 365 } 366 367 if (allBlanks) 368 { 369 return errSecSuccess; 370 } 371 372 /* search for START line */ 373 const char *startLine = findStr(currCp, lenToGo, "-----BEGIN"); 374 if(startLine == NULL) { 375 /* Assume one item, raw base64 */ 376 SecImpInferDbg("impExpParsePemToImportRefs no PEM headers, assuming raw base64"); 377 ortn = impExpImportSinglePEM(currCp, lenToGo, importReps); 378 if(ortn == errSecSuccess) { 379 *isPem = true; 380 } 381 return ortn; 382 } 383 384 /* break up input into chunks between START and END lines */ 385 ortn = errSecSuccess; 386 bool gotSomePem = false; 387 do { 388 /* get to beginning of START line */ 389 startLine = findStr(currCp, lenToGo, "-----BEGIN"); 390 if(startLine == NULL) { 391 break; 392 } 393 unsigned consumed = (unsigned)(startLine - currCp); 394 assert(consumed <= lenToGo); 395 lenToGo -= consumed; 396 currCp += consumed; 397 398 /* get to beginning of END line */ 399 const char *endLine = findStr(currCp+10, lenToGo, "-----END"); 400 unsigned toDecode = lenToGo; 401 if(endLine) { 402 consumed = (unsigned)(endLine - startLine); 403 assert(consumed <= lenToGo); 404 currCp += consumed; 405 lenToGo -= consumed; 406 407 /* find end of END line */ 408 const char *tmpLine = getLine(endLine, lenToGo, &consumed); 409 assert((tmpLine != NULL) && (tmpLine[0] != 0)); 410 /* don't decode the terminators */ 411 toDecode = (unsigned)(endLine - startLine + strlen(tmpLine)); 412 free((void *)tmpLine); 413 414 /* skip past END line and newlines */ 415 assert(consumed <= lenToGo); 416 currCp += consumed; 417 lenToGo -= consumed; 418 } 419 else { 420 /* no END line, we'll allow that - decode to end of file */ 421 lenToGo = 0; 422 } 423 424 ortn = impExpImportSinglePEM(startLine, toDecode, importReps); 425 if(ortn) { 426 break; 427 } 428 gotSomePem = true; 429 } while(lenToGo != 0); 430 if(ortn == errSecSuccess) { 431 if(gotSomePem) { 432 *isPem = true; 433 } 434 else { 435 SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found"); 436 ortn = kSecFormatUnknown; 437 } 438 } 439 return ortn; 440} 441 442 443/* 444 * PEM encode a single SecExportRep's data, appending to a CFData. 445 */ 446OSStatus impExpPemEncodeExportRep( 447 CFDataRef derData, 448 const char *pemHeader, 449 CFArrayRef pemParamLines, // optional 450 CFMutableDataRef outData) 451{ 452 unsigned char *enc; 453 unsigned encLen; 454 455 char headerLine[200]; 456 if(strlen(pemHeader) > 150) { 457 return errSecParam; 458 } 459 460 /* First base64 encode */ 461 enc = cuEnc64WithLines(CFDataGetBytePtr(derData), (unsigned)CFDataGetLength(derData), 462 64, &encLen); 463 if(enc == NULL) { 464 /* malloc error is actually the only known failure */ 465 SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure"); 466 return errSecAllocate; 467 } 468 469 /* strip off trailing NULL */ 470 if((encLen != 0) && (enc[encLen - 1] == '\0')) { 471 encLen--; 472 } 473 sprintf(headerLine, "-----BEGIN %s-----\n", pemHeader); 474 CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine)); 475 476 /* optional PEM parameters lines (currently used for openssl wrap format only) */ 477 if(pemParamLines != NULL) { 478 CFIndex numLines = CFArrayGetCount(pemParamLines); 479 for(CFIndex dex=0; dex<numLines; dex++) { 480 CFStringRef cfStr = 481 (CFStringRef)CFArrayGetValueAtIndex(pemParamLines, dex); 482 char cStr[512]; 483 UInt8 nl = '\n'; 484 if(!CFStringGetCString(cfStr, cStr, sizeof(cStr), 485 kCFStringEncodingASCII)) { 486 /* 487 * Should never happen; this module created this CFString 488 * from a C string with ASCII encoding. Keep going, though 489 * this is probably fatal to the exported representation. 490 */ 491 SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup"); 492 continue; 493 } 494 CFDataAppendBytes(outData, (const UInt8 *)cStr, strlen(cStr)); 495 CFDataAppendBytes(outData, &nl, 1); 496 } 497 } 498 CFDataAppendBytes(outData, enc, encLen); 499 sprintf(headerLine, "-----END %s-----\n", pemHeader); 500 CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine)); 501 free((void *)enc); 502 return errSecSuccess; 503} 504 505