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
));