2 * Copyright (c) 2004,2011-2014 Apple 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>
35 #include <security_utilities/simulatecrash_assert.h>
38 * Text parsing routines.
40 * Search incoming text for specified string. Does not assume inText is
41 * NULL terminated. Returns pointer to start of found string in inText.
43 static const char *findStr(
46 const char *str
) // NULL terminated - search for this
48 /* probably not the hottest string search algorithm... */
50 unsigned srchStrLen
= (unsigned)strlen(str
);
53 /* last char * we can search in inText for start of str */
54 const char *endCp
= inText
+ inTextLen
- srchStrLen
;
56 for(cp
=inText
; cp
<=endCp
; cp
++) {
58 if(!memcmp(cp
, str
, srchStrLen
)) {
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).
71 * ALWAYS returns a mallocd string if there is ANY data remaining per the
72 * incoming inTextLen. Returns NULL if inTextLen is zero.
74 static const char *getLine(
76 unsigned inTextLen
, // RETURNED
77 unsigned *consumed
) // RETURNED
81 const char *cp
= inText
;
82 const char *newline
= NULL
; // if we found a newline, this points to the first one
86 if((c
== '\r') || (c
== '\n')) {
92 else if(newline
!= NULL
) {
93 /* non newline after newline, done */
102 linelen
= (unsigned)(newline
- inText
);
107 char *rtn
= (char *)malloc(linelen
+ 1);
108 memmove(rtn
, inText
, linelen
);
114 * Table to facilitate conversion of known PEM header strings to
115 * the things we know about.
118 const char *pemStr
; // e.g. PEM_STRING_X509, "CERTIFICATE"
119 SecExternalItemType itemType
;
120 SecExternalFormat format
;
121 CSSM_ALGORITHMS keyAlg
;
124 #define NOALG CSSM_ALGID_NONE
126 static const PemHeader PemHeaders
[] =
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
}
148 #define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader))
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).
155 * Returned SecImportRep may or may not have a known type and format and
156 * (if it is a key) algorithm.
158 static OSStatus
impExpImportSinglePEM(
161 CFMutableArrayRef importReps
) // output appended here
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
;
173 unsigned char *decData
;
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
;
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
);
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
;
198 assert(consumed
<= lenToGo
);
203 * Search currLine for known PEM header strings.
204 * It is not an error if we don't recognize this
207 for(unsigned dex
=0; dex
<NUM_PEM_HEADERS
; dex
++) {
208 const PemHeader
*ph
= &PemHeaders
[dex
];
209 if(!strstr(currLine
, ph
->pemStr
)) {
214 itemType
= ph
->itemType
;
219 free((void *)currLine
);
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.
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
;
238 bool skipThis
= false;
239 unsigned lineLen
= (unsigned)strlen(currLine
);
244 if(strchr(currLine
, ':')) {
246 * Save this PEM header info. Used for traditional openssl
247 * wrapped keys to indicate IV.
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
);
257 * If it says "ENCRYPTED" and this is a private key,
258 * flag the fact that it's wrapped in openssl format
260 if(strstr(currLine
, "ENCRYPTED")) {
261 if((format
== kSecFormatOpenSSL
) &&
262 (itemType
== kSecItemTypePrivateKey
)) {
263 format
= kSecFormatWrappedOpenSSL
;
267 CFArrayAppendValue(pemParamLines
, cfStr
);
268 CFRelease(cfStr
); // array owns it
271 free((void *)currLine
);
273 /* looks like good stuff; process */
277 assert(consumed
<= lenToGo
);
282 SecImpInferDbg("impExpImportSinglePEM no valid base64 data");
283 ortn
= errSecUnsupportedFormat
;
288 * currCP points to start of base64 data - mark it and search for end line.
289 * We skip everything after the end line.
292 base64Len
= lenToGo
; // if no END
293 end64
= findStr(currCp
, lenToGo
, "-----END");
295 if(end64
== start64
) {
296 /* Empty, nothing between START and END */
297 SecImpInferDbg("impExpImportSinglePEM no base64 between terminators");
298 ortn
= errSecUnsupportedFormat
;
301 base64Len
= (unsigned)(end64
- start64
);
303 /* else no END, no reason to complain about that as long as base64 decode works OK */
306 decData
= cuDec64((const unsigned char *)start64
, base64Len
, &decDataLen
);
307 if(decData
== NULL
) {
308 SecImpInferDbg("impExpImportSinglePEM bad base64 data");
309 ortn
= errSecUnsupportedFormat
;
313 cdata
= CFDataCreate(NULL
, decData
, decDataLen
);
314 free((void *)decData
);
315 rep
= new Security::KeychainCore::SecImportRep(cdata
, itemType
, format
, keyAlg
,
317 CFArrayAppendValue(importReps
, rep
);
318 CFRelease(cdata
); // SecImportRep holds ref
319 return errSecSuccess
;
322 if(pemParamLines
!= NULL
) {
323 CFRelease(pemParamLines
);
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.
333 OSStatus
impExpParsePemToImportRefs(
334 CFDataRef importedData
,
335 CFMutableArrayRef importReps
, // output appended here
336 bool *isPem
) // true means we think it was PEM regardless of
340 * First task: is this PEM or at least base64 encoded?
342 const char *currCp
= (const char *)CFDataGetBytePtr(importedData
);
343 const char *cp
= currCp
;
344 unsigned lenToGo
= (unsigned)CFDataGetLength(importedData
);
349 bool allBlanks
= true;
351 for(dex
=0; dex
<lenToGo
; dex
++, cp
++) {
353 // it's not a space. Is it a non-ascii character?
355 return errSecSuccess
;
358 // is it a control character?
361 return errSecSuccess
;
364 // no, mark that an acceptable character was encountered and keep going
371 return errSecSuccess
;
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
) {
386 /* break up input into chunks between START and END lines */
387 ortn
= errSecSuccess
;
388 bool gotSomePem
= false;
390 /* get to beginning of START line */
391 startLine
= findStr(currCp
, lenToGo
, "-----BEGIN");
392 if(startLine
== NULL
) {
395 unsigned consumed
= (unsigned)(startLine
- currCp
);
396 assert(consumed
<= lenToGo
);
400 /* get to beginning of END line */
401 const char *endLine
= findStr(currCp
+10, lenToGo
, "-----END");
402 unsigned toDecode
= lenToGo
;
404 consumed
= (unsigned)(endLine
- startLine
);
405 assert(consumed
<= lenToGo
);
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
);
416 /* skip past END line and newlines */
417 assert(consumed
<= lenToGo
);
422 /* no END line, we'll allow that - decode to end of file */
426 ortn
= impExpImportSinglePEM(startLine
, toDecode
, importReps
);
431 } while(lenToGo
!= 0);
432 if(ortn
== errSecSuccess
) {
437 SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found");
438 ortn
= kSecFormatUnknown
;
446 * PEM encode a single SecExportRep's data, appending to a CFData.
448 OSStatus
impExpPemEncodeExportRep(
450 const char *pemHeader
,
451 CFArrayRef pemParamLines
, // optional
452 CFMutableDataRef outData
)
457 char headerLine
[200];
458 if(strlen(pemHeader
) > 150) {
462 /* First base64 encode */
463 enc
= cuEnc64WithLines(CFDataGetBytePtr(derData
), (unsigned)CFDataGetLength(derData
),
466 /* malloc error is actually the only known failure */
467 SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure");
468 return errSecAllocate
;
471 /* strip off trailing NULL */
472 if((encLen
!= 0) && (enc
[encLen
- 1] == '\0')) {
475 sprintf(headerLine
, "-----BEGIN %s-----\n", pemHeader
);
476 CFDataAppendBytes(outData
, (const UInt8
*)headerLine
, strlen(headerLine
));
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
++) {
483 (CFStringRef
)CFArrayGetValueAtIndex(pemParamLines
, dex
);
486 if(!CFStringGetCString(cfStr
, cStr
, sizeof(cStr
),
487 kCFStringEncodingASCII
)) {
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.
493 SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup");
496 CFDataAppendBytes(outData
, (const UInt8
*)cStr
, strlen(cStr
));
497 CFDataAppendBytes(outData
, &nl
, 1);
500 CFDataAppendBytes(outData
, enc
, encLen
);
501 sprintf(headerLine
, "-----END %s-----\n", pemHeader
);
502 CFDataAppendBytes(outData
, (const UInt8
*)headerLine
, strlen(headerLine
));
504 return errSecSuccess
;