2  * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   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 
  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. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  23  * SecImportExportPem.cpp - private PEM routines for SecImportExport 
  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 <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> 
  37  * Text parsing routines.  
  39  * Search incoming text for specified string. Does not assume inText is 
  40  * NULL terminated. Returns pointer to start of found string in inText. 
  42 static const char *findStr( 
  45         const char *str
)                                // NULL terminated - search for this 
  47         /* probably not the hottest string search algorithm... */ 
  49         unsigned srchStrLen 
= strlen(str
); 
  52         /* last char * we can search in inText for start of str */ 
  53         const char *endCp 
= inText 
+ inTextLen 
- srchStrLen
; 
  55         for(cp
=inText
; cp
<=endCp
; cp
++) { 
  57                         if(!memcmp(cp
, str
, srchStrLen
)) { 
  66  * Obtain one line from current text. Returns a mallocd, NULL-terminated string 
  67  * which caller must free(). Also returns the number of chars consumed including 
  68  * the returned chars PLUS EOL terminators (\n and/or \r). 
  70  * ALWAYS returns a mallocd string if there is ANY data remaining per the  
  71  * incoming inTextLen. Returns NULL if inTextLen is zero. 
  73 static const char *getLine( 
  75         unsigned inTextLen
,                     // RETURNED 
  76         unsigned *consumed
)                     // RETURNED 
  80         const char *cp 
= inText
; 
  81         const char *newline 
= NULL
;             // if we found a newline, this points to the first one 
  85                 if((c 
== '\r') || (c 
== '\n')) { 
  91                 else if(newline 
!= NULL
) { 
  92                         /* non newline after newline, done */ 
 101                 linelen 
= newline 
- inText
; 
 106         char *rtn 
= (char *)malloc(linelen 
+ 1); 
 107         memmove(rtn
, inText
, linelen
); 
 113  * Table to facilitate conversion of known PEM header strings to  
 114  * the things we know about. 
 117         const char                      *pemStr
;                        // e.g. PEM_STRING_X509, "CERTIFICATE" 
 118         SecExternalItemType itemType
; 
 119         SecExternalFormat   format
; 
 120         CSSM_ALGORITHMS         keyAlg
; 
 123 #define NOALG   CSSM_ALGID_NONE 
 125 static const PemHeader PemHeaders
[] =  
 127         /* from openssl/pem.h standard header */ 
 128         { PEM_STRING_X509_OLD
, kSecItemTypeCertificate
, kSecFormatX509Cert
, NOALG
}, 
 129         { PEM_STRING_X509
,  kSecItemTypeCertificate
, kSecFormatX509Cert
, NOALG 
}, 
 130         { PEM_STRING_EVP_PKEY
, kSecItemTypePrivateKey
, kSecFormatOpenSSL
, NOALG
}, 
 131         { PEM_STRING_PUBLIC
, kSecItemTypePublicKey
, kSecFormatOpenSSL
, NOALG 
}, 
 132         { PEM_STRING_RSA
, kSecItemTypePrivateKey
, kSecFormatOpenSSL
, CSSM_ALGID_RSA 
}, 
 133         { PEM_STRING_RSA_PUBLIC
, kSecItemTypePublicKey
, kSecFormatOpenSSL
, CSSM_ALGID_RSA 
}, 
 134         { PEM_STRING_DSA
, kSecItemTypePrivateKey
, kSecFormatOpenSSL
, CSSM_ALGID_DSA 
}, 
 135         { PEM_STRING_DSA_PUBLIC
, kSecItemTypePublicKey
, kSecFormatOpenSSL
, CSSM_ALGID_DSA 
}, 
 136         { PEM_STRING_PKCS7
, kSecItemTypeAggregate
, kSecFormatPKCS7
, NOALG 
}, 
 137         { PEM_STRING_PKCS8
, kSecItemTypePrivateKey
, kSecFormatWrappedPKCS8
, NOALG 
}, 
 138         { PEM_STRING_PKCS8INF
, kSecItemTypePrivateKey
, kSecFormatUnknown
, NOALG 
}, 
 139         /* we define these  */ 
 140         { PEM_STRING_DH_PUBLIC
, kSecItemTypePublicKey
, kSecFormatOpenSSL
, CSSM_ALGID_DH 
}, 
 141         { PEM_STRING_DH_PRIVATE
, kSecItemTypePrivateKey
, kSecFormatOpenSSL
, CSSM_ALGID_DH 
}, 
 142         { PEM_STRING_PKCS12
, kSecItemTypeAggregate
, kSecFormatPKCS12
, NOALG 
}, 
 143         { PEM_STRING_SESSION
, kSecItemTypeSessionKey
, kSecFormatRawKey
, NOALG 
}, 
 144         { PEM_STRING_ECDSA_PUBLIC
, kSecItemTypePublicKey
, kSecFormatOpenSSL
, CSSM_ALGID_ECDSA 
}, 
 145         { PEM_STRING_ECDSA_PRIVATE
, kSecItemTypePrivateKey
, kSecFormatOpenSSL
, CSSM_ALGID_ECDSA 
} 
 147 #define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader)) 
 150  * PEM decode incoming data which we've previously determined to contain 
 151  * exactly one reasonably well formed PEM blob (it has no more than one 
 152  * START and END line - though it may have none - and is all ASCII). 
 154  * Returned SecImportRep may or may not have a known type and format and  
 155  * (if it is a key) algorithm.  
 157 static OSStatus 
impExpImportSinglePEM( 
 160         CFMutableArrayRef       importReps
)             // output appended here 
 163         const char *currLine 
= NULL
;            // mallocd by getLine() 
 164         const char *lastCp 
= currCp
; 
 165         CFMutableArrayRef pemParamLines 
= NULL
; 
 166         OSStatus ortn 
= noErr
; 
 167         CFDataRef cdata 
= NULL
; 
 168         Security::KeychainCore::SecImportRep 
*rep 
= NULL
; 
 172         unsigned char *decData
; 
 175         /* we try to glean these from the header, but it's not fatal if we can not */ 
 176         SecExternalFormat format 
= kSecFormatUnknown
; 
 177         SecExternalItemType itemType 
= kSecItemTypeUnknown
; 
 178         CSSM_ALGORITHMS keyAlg 
= CSSM_ALGID_NONE
; 
 180         /* search to START line, parse it to get type/format/alg */ 
 181         const char *startLine 
= findStr(currCp
, lenToGo
, "-----BEGIN"); 
 182         if(startLine 
!= NULL
) { 
 183                 /* possibly skip over leading garbage */ 
 184                 consumed 
= startLine 
- currCp
; 
 188                 /* get C string of START line */ 
 189                 currLine 
= getLine(startLine
, lenToGo
, &consumed
); 
 190                 if(currLine 
== NULL
) { 
 191                         /* somehow got here with no data */ 
 192                         assert(lenToGo 
== 0); 
 193                         SecImpInferDbg("impExpImportSinglePEM empty data"); 
 194                         ortn 
= errSecUnsupportedFormat
; 
 197                 assert(consumed 
<= lenToGo
); 
 202                  * Search currLine for known PEM header strings. 
 203                  * It is not an error if we don't recognize this 
 206                 for(unsigned dex
=0; dex
<NUM_PEM_HEADERS
; dex
++) { 
 207                         const PemHeader 
*ph 
= &PemHeaders
[dex
]; 
 208                         if(!strstr(currLine
, ph
->pemStr
)) { 
 213                         itemType 
= ph
->itemType
; 
 218                 free((void *)currLine
); 
 222          * Skip empty lines. Save all lines containing ':' (used by openssl  
 223          * to specify key wrapping parameters). These will be saved in  
 224          * outgoing SecImportReps' pemParamLines. 
 227                 currLine 
= getLine(currCp
, lenToGo
, &consumed
); 
 228                 if(currLine 
== NULL 
|| currCp 
== lastCp
) { 
 229                         /* out of data (unable to advance to next line) */ 
 230                         SecImpInferDbg("impExpImportSinglePEM out of data"); 
 231                         if (currLine
) free((void *)currLine
); 
 232                         ortn 
= errSecUnsupportedFormat
; 
 237                 bool skipThis 
= false; 
 238                 unsigned lineLen 
= strlen(currLine
); 
 243                 if(strchr(currLine
, ':')) { 
 245                          * Save this PEM header info. Used for traditional openssl 
 246                          * wrapped keys to indicate IV. 
 248                         SecImpInferDbg("import PEM: param line %s", currLine
); 
 249                         CFStringRef cfStr 
= CFStringCreateWithCString(NULL
, currLine
, 
 250                                 kCFStringEncodingASCII
); 
 251                         if(pemParamLines 
== NULL
) { 
 252                                 /* first param line */ 
 253                                 pemParamLines 
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
); 
 256                                  * If it says "ENCRYPTED" and this is a private key, 
 257                                  * flag the fact that it's wrapped in openssl format 
 259                                 if(strstr(currLine
, "ENCRYPTED")) { 
 260                                         if((format 
== kSecFormatOpenSSL
) && 
 261                                            (itemType 
== kSecItemTypePrivateKey
)) { 
 262                                                 format 
= kSecFormatWrappedOpenSSL
; 
 266                         CFArrayAppendValue(pemParamLines
, cfStr
); 
 267                         CFRelease(cfStr
);               // array owns it  
 270                 free((void *)currLine
); 
 272                         /* looks like good stuff; process */ 
 276                 assert(consumed 
<= lenToGo
); 
 281                 SecImpInferDbg("impExpImportSinglePEM no valid base64 data"); 
 282                 ortn 
= errSecUnsupportedFormat
; 
 287          * currCP points to start of base64 data - mark it and search for end line. 
 288          * We skip everything after the end line. 
 291         base64Len 
= lenToGo
;                    // if no END 
 292         end64 
= findStr(currCp
, lenToGo
, "-----END"); 
 294                 if(end64 
== start64
) { 
 295                         /* Empty, nothing between START and END */ 
 296                         SecImpInferDbg("impExpImportSinglePEM no base64 between terminators"); 
 297                         ortn 
= errSecUnsupportedFormat
; 
 300                 base64Len 
= end64 
- start64
; 
 302         /* else no END, no reason to complain about that as long as base64 decode works OK */ 
 305         decData 
= cuDec64((const unsigned char *)start64
, base64Len
, &decDataLen
); 
 306         if(decData 
== NULL
) { 
 307                 SecImpInferDbg("impExpImportSinglePEM bad base64 data"); 
 308                 ortn 
= errSecUnsupportedFormat
; 
 312         cdata 
= CFDataCreate(NULL
, decData
, decDataLen
); 
 313         free((void *)decData
); 
 314         rep 
= new Security::KeychainCore::SecImportRep(cdata
, itemType
, format
, keyAlg
, 
 316         CFArrayAppendValue(importReps
, rep
); 
 317         CFRelease(cdata
);               // SecImportRep holds ref 
 321         if(pemParamLines 
!= NULL
) { 
 322                 CFRelease(pemParamLines
); 
 328  * PEM decode incoming data, appending SecImportRep's to specified array. 
 329  * Returned SecImportReps may or may not have a known type and format and  
 330  * (if they are keys) algorithm.  
 332 OSStatus 
impExpParsePemToImportRefs( 
 333         CFDataRef                       importedData
, 
 334         CFMutableArrayRef       importReps
,             // output appended here 
 335         bool                            *isPem
)                 // true means we think it was PEM regardless of  
 339          * First task: is this PEM or at least base64 encoded? 
 341         const char *currCp 
= (const char *)CFDataGetBytePtr(importedData
); 
 342         const char *cp 
= currCp
; 
 343         unsigned lenToGo 
= CFDataGetLength(importedData
); 
 348         bool allBlanks 
= true; 
 350         for(dex
=0; dex
<lenToGo
; dex
++, cp
++) { 
 352                         // it's not a space.  Is it a non-ascii character? 
 357                         // is it a control character? 
 363                         // no, mark that an acceptable character was encountered and keep going 
 373         /* search for START line */ 
 374         const char *startLine 
= findStr(currCp
, lenToGo
, "-----BEGIN"); 
 375         if(startLine 
== NULL
) { 
 376                 /* Assume one item, raw base64 */ 
 377                 SecImpInferDbg("impExpParsePemToImportRefs no PEM headers, assuming raw base64"); 
 378                 ortn 
= impExpImportSinglePEM(currCp
, lenToGo
, importReps
); 
 385         /* break up input into chunks between START and END lines */ 
 387         bool gotSomePem 
= false; 
 389                 /* get to beginning of START line */ 
 390                 startLine 
= findStr(currCp
, lenToGo
, "-----BEGIN"); 
 391                 if(startLine 
== NULL
) { 
 394                 unsigned consumed 
= startLine 
- currCp
; 
 395                 assert(consumed 
<= lenToGo
); 
 399                 /* get to beginning of END line */ 
 400                 const char *endLine 
= findStr(currCp
+10, lenToGo
, "-----END"); 
 401                 unsigned toDecode 
= lenToGo
; 
 403                         consumed 
= endLine 
- startLine
; 
 404                         assert(consumed 
<= lenToGo
); 
 408                         /* find end of END line */ 
 409                         const char *tmpLine 
= getLine(endLine
, lenToGo
, &consumed
); 
 410                         assert((tmpLine 
!= NULL
) && (tmpLine
[0] != 0)); 
 411                         /* don't decode the terminators */ 
 412                         toDecode 
= endLine 
- startLine 
+ strlen(tmpLine
); 
 413                         free((void *)tmpLine
); 
 415                         /* skip past END line and newlines */ 
 416                         assert(consumed 
<= lenToGo
); 
 421                         /* no END line, we'll allow that - decode to end of file */ 
 425                 ortn 
= impExpImportSinglePEM(startLine
, toDecode
, importReps
); 
 430         } while(lenToGo 
!= 0); 
 436                         SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found"); 
 437                         ortn 
= kSecFormatUnknown
; 
 445  * PEM encode a single SecExportRep's data, appending to a CFData. 
 447 OSStatus 
impExpPemEncodeExportRep( 
 449         const char                      *pemHeader
, 
 450         CFArrayRef                      pemParamLines
,  // optional  
 451         CFMutableDataRef        outData
) 
 456         char headerLine
[200]; 
 457         if(strlen(pemHeader
) > 150) { 
 461         /* First base64 encode */ 
 462         enc 
= cuEnc64WithLines(CFDataGetBytePtr(derData
), CFDataGetLength(derData
),  
 465                 /* malloc error is actually the only known failure */ 
 466                 SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure"); 
 470         /* strip off trailing NULL */ 
 471         if((encLen 
!= 0) && (enc
[encLen 
- 1] == '\0')) { 
 474         sprintf(headerLine
, "-----BEGIN %s-----\n", pemHeader
); 
 475         CFDataAppendBytes(outData
, (const UInt8 
*)headerLine
, strlen(headerLine
)); 
 477         /* optional PEM parameters lines (currently used for openssl wrap format only) */ 
 478         if(pemParamLines 
!= NULL
) { 
 479                 CFIndex numLines 
= CFArrayGetCount(pemParamLines
); 
 480                 for(CFIndex dex
=0; dex
<numLines
; dex
++) { 
 482                                 (CFStringRef
)CFArrayGetValueAtIndex(pemParamLines
, dex
); 
 485                         if(!CFStringGetCString(cfStr
, cStr
, sizeof(cStr
),  
 486                                         kCFStringEncodingASCII
)) { 
 488                                  * Should never happen; this module created this CFString 
 489                                  * from a C string with ASCII encoding. Keep going, though 
 490                                  * this is probably fatal to the exported representation. 
 492                                 SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup"); 
 495                         CFDataAppendBytes(outData
, (const UInt8 
*)cStr
, strlen(cStr
)); 
 496                         CFDataAppendBytes(outData
, &nl
, 1); 
 499         CFDataAppendBytes(outData
, enc
, encLen
); 
 500         sprintf(headerLine
, "-----END %s-----\n", pemHeader
); 
 501         CFDataAppendBytes(outData
, (const UInt8 
*)headerLine
, strlen(headerLine
));