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>
36 * Text parsing routines.
38 * Search incoming text for specified string. Does not assume inText is
39 * NULL terminated. Returns pointer to start of found string in inText.
41 static const char *findStr(
44 const char *str
) // NULL terminated - search for this
46 /* probably not the hottest string search algorithm... */
48 unsigned srchStrLen
= (unsigned)strlen(str
);
51 /* last char * we can search in inText for start of str */
52 const char *endCp
= inText
+ inTextLen
- srchStrLen
;
54 for(cp
=inText
; cp
<=endCp
; cp
++) {
56 if(!memcmp(cp
, str
, srchStrLen
)) {
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).
69 * ALWAYS returns a mallocd string if there is ANY data remaining per the
70 * incoming inTextLen. Returns NULL if inTextLen is zero.
72 static const char *getLine(
74 unsigned inTextLen
, // RETURNED
75 unsigned *consumed
) // RETURNED
79 const char *cp
= inText
;
80 const char *newline
= NULL
; // if we found a newline, this points to the first one
84 if((c
== '\r') || (c
== '\n')) {
90 else if(newline
!= NULL
) {
91 /* non newline after newline, done */
100 linelen
= (unsigned)(newline
- inText
);
105 char *rtn
= (char *)malloc(linelen
+ 1);
106 memmove(rtn
, inText
, linelen
);
112 * Table to facilitate conversion of known PEM header strings to
113 * the things we know about.
116 const char *pemStr
; // e.g. PEM_STRING_X509, "CERTIFICATE"
117 SecExternalItemType itemType
;
118 SecExternalFormat format
;
119 CSSM_ALGORITHMS keyAlg
;
122 #define NOALG CSSM_ALGID_NONE
124 static const PemHeader PemHeaders
[] =
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
}
146 #define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader))
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).
153 * Returned SecImportRep may or may not have a known type and format and
154 * (if it is a key) algorithm.
156 static OSStatus
impExpImportSinglePEM(
159 CFMutableArrayRef importReps
) // output appended here
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
;
171 unsigned char *decData
;
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
;
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
);
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
;
196 assert(consumed
<= lenToGo
);
201 * Search currLine for known PEM header strings.
202 * It is not an error if we don't recognize this
205 for(unsigned dex
=0; dex
<NUM_PEM_HEADERS
; dex
++) {
206 const PemHeader
*ph
= &PemHeaders
[dex
];
207 if(!strstr(currLine
, ph
->pemStr
)) {
212 itemType
= ph
->itemType
;
217 free((void *)currLine
);
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.
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
;
236 bool skipThis
= false;
237 unsigned lineLen
= (unsigned)strlen(currLine
);
242 if(strchr(currLine
, ':')) {
244 * Save this PEM header info. Used for traditional openssl
245 * wrapped keys to indicate IV.
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
);
255 * If it says "ENCRYPTED" and this is a private key,
256 * flag the fact that it's wrapped in openssl format
258 if(strstr(currLine
, "ENCRYPTED")) {
259 if((format
== kSecFormatOpenSSL
) &&
260 (itemType
== kSecItemTypePrivateKey
)) {
261 format
= kSecFormatWrappedOpenSSL
;
265 CFArrayAppendValue(pemParamLines
, cfStr
);
266 CFRelease(cfStr
); // array owns it
269 free((void *)currLine
);
271 /* looks like good stuff; process */
275 assert(consumed
<= lenToGo
);
280 SecImpInferDbg("impExpImportSinglePEM no valid base64 data");
281 ortn
= errSecUnsupportedFormat
;
286 * currCP points to start of base64 data - mark it and search for end line.
287 * We skip everything after the end line.
290 base64Len
= lenToGo
; // if no END
291 end64
= findStr(currCp
, lenToGo
, "-----END");
293 if(end64
== start64
) {
294 /* Empty, nothing between START and END */
295 SecImpInferDbg("impExpImportSinglePEM no base64 between terminators");
296 ortn
= errSecUnsupportedFormat
;
299 base64Len
= (unsigned)(end64
- start64
);
301 /* else no END, no reason to complain about that as long as base64 decode works OK */
304 decData
= cuDec64((const unsigned char *)start64
, base64Len
, &decDataLen
);
305 if(decData
== NULL
) {
306 SecImpInferDbg("impExpImportSinglePEM bad base64 data");
307 ortn
= errSecUnsupportedFormat
;
311 cdata
= CFDataCreate(NULL
, decData
, decDataLen
);
312 free((void *)decData
);
313 rep
= new Security::KeychainCore::SecImportRep(cdata
, itemType
, format
, keyAlg
,
315 CFArrayAppendValue(importReps
, rep
);
316 CFRelease(cdata
); // SecImportRep holds ref
317 return errSecSuccess
;
320 if(pemParamLines
!= NULL
) {
321 CFRelease(pemParamLines
);
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.
331 OSStatus
impExpParsePemToImportRefs(
332 CFDataRef importedData
,
333 CFMutableArrayRef importReps
, // output appended here
334 bool *isPem
) // true means we think it was PEM regardless of
338 * First task: is this PEM or at least base64 encoded?
340 const char *currCp
= (const char *)CFDataGetBytePtr(importedData
);
341 const char *cp
= currCp
;
342 unsigned lenToGo
= (unsigned)CFDataGetLength(importedData
);
347 bool allBlanks
= true;
349 for(dex
=0; dex
<lenToGo
; dex
++, cp
++) {
351 // it's not a space. Is it a non-ascii character?
353 return errSecSuccess
;
356 // is it a control character?
359 return errSecSuccess
;
362 // no, mark that an acceptable character was encountered and keep going
369 return errSecSuccess
;
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
) {
384 /* break up input into chunks between START and END lines */
385 ortn
= errSecSuccess
;
386 bool gotSomePem
= false;
388 /* get to beginning of START line */
389 startLine
= findStr(currCp
, lenToGo
, "-----BEGIN");
390 if(startLine
== NULL
) {
393 unsigned consumed
= (unsigned)(startLine
- currCp
);
394 assert(consumed
<= lenToGo
);
398 /* get to beginning of END line */
399 const char *endLine
= findStr(currCp
+10, lenToGo
, "-----END");
400 unsigned toDecode
= lenToGo
;
402 consumed
= (unsigned)(endLine
- startLine
);
403 assert(consumed
<= lenToGo
);
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
);
414 /* skip past END line and newlines */
415 assert(consumed
<= lenToGo
);
420 /* no END line, we'll allow that - decode to end of file */
424 ortn
= impExpImportSinglePEM(startLine
, toDecode
, importReps
);
429 } while(lenToGo
!= 0);
430 if(ortn
== errSecSuccess
) {
435 SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found");
436 ortn
= kSecFormatUnknown
;
444 * PEM encode a single SecExportRep's data, appending to a CFData.
446 OSStatus
impExpPemEncodeExportRep(
448 const char *pemHeader
,
449 CFArrayRef pemParamLines
, // optional
450 CFMutableDataRef outData
)
455 char headerLine
[200];
456 if(strlen(pemHeader
) > 150) {
460 /* First base64 encode */
461 enc
= cuEnc64WithLines(CFDataGetBytePtr(derData
), (unsigned)CFDataGetLength(derData
),
464 /* malloc error is actually the only known failure */
465 SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure");
466 return errSecAllocate
;
469 /* strip off trailing NULL */
470 if((encLen
!= 0) && (enc
[encLen
- 1] == '\0')) {
473 sprintf(headerLine
, "-----BEGIN %s-----\n", pemHeader
);
474 CFDataAppendBytes(outData
, (const UInt8
*)headerLine
, strlen(headerLine
));
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
++) {
481 (CFStringRef
)CFArrayGetValueAtIndex(pemParamLines
, dex
);
484 if(!CFStringGetCString(cfStr
, cStr
, sizeof(cStr
),
485 kCFStringEncodingASCII
)) {
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.
491 SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup");
494 CFDataAppendBytes(outData
, (const UInt8
*)cStr
, strlen(cStr
));
495 CFDataAppendBytes(outData
, &nl
, 1);
498 CFDataAppendBytes(outData
, enc
, encLen
);
499 sprintf(headerLine
, "-----END %s-----\n", pemHeader
);
500 CFDataAppendBytes(outData
, (const UInt8
*)headerLine
, strlen(headerLine
));
502 return errSecSuccess
;