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