]> git.saurik.com Git - apple/security.git/blob - libsecurity_keychain/lib/SecImportExportPem.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_keychain / lib / SecImportExportPem.cpp
1 /*
2 * Copyright (c) 2004 Apple Computer, 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 <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 /*
37 * Text parsing routines.
38 *
39 * Search incoming text for specified string. Does not assume inText is
40 * NULL terminated. Returns pointer to start of found string in inText.
41 */
42 static const char *findStr(
43 const char *inText,
44 unsigned inTextLen,
45 const char *str) // NULL terminated - search for this
46 {
47 /* probably not the hottest string search algorithm... */
48 const char *cp;
49 unsigned srchStrLen = strlen(str);
50 char c = str[0];
51
52 /* last char * we can search in inText for start of str */
53 const char *endCp = inText + inTextLen - srchStrLen;
54
55 for(cp=inText; cp<=endCp; cp++) {
56 if(*cp == c) {
57 if(!memcmp(cp, str, srchStrLen)) {
58 return cp;
59 }
60 }
61 }
62 return NULL;
63 }
64
65 /*
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).
69 *
70 * ALWAYS returns a mallocd string if there is ANY data remaining per the
71 * incoming inTextLen. Returns NULL if inTextLen is zero.
72 */
73 static const char *getLine(
74 const char *inText,
75 unsigned inTextLen, // RETURNED
76 unsigned *consumed) // RETURNED
77
78 {
79 *consumed = 0;
80 const char *cp = inText;
81 const char *newline = NULL; // if we found a newline, this points to the first one
82
83 while(inTextLen) {
84 char c = *cp;
85 if((c == '\r') || (c == '\n')) {
86 if(newline == NULL) {
87 /* first newline */
88 newline = cp;
89 }
90 }
91 else if(newline != NULL) {
92 /* non newline after newline, done */
93 break;
94 }
95 (*consumed)++;
96 inTextLen--;
97 cp++;
98 }
99 unsigned linelen;
100 if(newline) {
101 linelen = newline - inText;
102 }
103 else {
104 linelen = *consumed;
105 }
106 char *rtn = (char *)malloc(linelen + 1);
107 memmove(rtn, inText, linelen);
108 rtn[linelen] = 0;
109 return rtn;
110 }
111
112 /*
113 * Table to facilitate conversion of known PEM header strings to
114 * the things we know about.
115 */
116 typedef struct {
117 const char *pemStr; // e.g. PEM_STRING_X509, "CERTIFICATE"
118 SecExternalItemType itemType;
119 SecExternalFormat format;
120 CSSM_ALGORITHMS keyAlg;
121 } PemHeader;
122
123 #define NOALG CSSM_ALGID_NONE
124
125 static const PemHeader PemHeaders[] =
126 {
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 }
146 };
147 #define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader))
148
149 /*
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).
153 *
154 * Returned SecImportRep may or may not have a known type and format and
155 * (if it is a key) algorithm.
156 */
157 static OSStatus impExpImportSinglePEM(
158 const char *currCp,
159 unsigned lenToGo,
160 CFMutableArrayRef importReps) // output appended here
161 {
162 unsigned consumed;
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;
169 const char *start64;
170 unsigned base64Len;
171 const char *end64;
172 unsigned char *decData;
173 unsigned decDataLen;
174
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;
179
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;
185 lenToGo -= consumed;
186 currCp = startLine;
187
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;
195 goto errOut;
196 }
197 assert(consumed <= lenToGo);
198 currCp += consumed;
199 lenToGo -= consumed;
200
201 /*
202 * Search currLine for known PEM header strings.
203 * It is not an error if we don't recognize this
204 * header.
205 */
206 for(unsigned dex=0; dex<NUM_PEM_HEADERS; dex++) {
207 const PemHeader *ph = &PemHeaders[dex];
208 if(!strstr(currLine, ph->pemStr)) {
209 continue;
210 }
211 /* found one! */
212 format = ph->format;
213 itemType = ph->itemType;
214 keyAlg = ph->keyAlg;
215 break;
216 }
217
218 free((void *)currLine);
219 }
220
221 /*
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.
225 */
226 for( ; ; ) {
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;
233 goto errOut;
234 }
235 lastCp = currCp;
236
237 bool skipThis = false;
238 unsigned lineLen = strlen(currLine);
239 if(lineLen == 0) {
240 /* empty line */
241 skipThis = true;
242 }
243 if(strchr(currLine, ':')) {
244 /*
245 * Save this PEM header info. Used for traditional openssl
246 * wrapped keys to indicate IV.
247 */
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);
254
255 /*
256 * If it says "ENCRYPTED" and this is a private key,
257 * flag the fact that it's wrapped in openssl format
258 */
259 if(strstr(currLine, "ENCRYPTED")) {
260 if((format == kSecFormatOpenSSL) &&
261 (itemType == kSecItemTypePrivateKey)) {
262 format = kSecFormatWrappedOpenSSL;
263 }
264 }
265 }
266 CFArrayAppendValue(pemParamLines, cfStr);
267 CFRelease(cfStr); // array owns it
268 skipThis = true;
269 }
270 free((void *)currLine);
271 if(!skipThis) {
272 /* looks like good stuff; process */
273 break;
274 }
275 /* skip this line */
276 assert(consumed <= lenToGo);
277 currCp += consumed;
278 lenToGo -= consumed;
279 }
280 if(lenToGo <= 2) {
281 SecImpInferDbg("impExpImportSinglePEM no valid base64 data");
282 ortn = errSecUnsupportedFormat;
283 goto errOut;
284 }
285
286 /*
287 * currCP points to start of base64 data - mark it and search for end line.
288 * We skip everything after the end line.
289 */
290 start64 = currCp;
291 base64Len = lenToGo; // if no END
292 end64 = findStr(currCp, lenToGo, "-----END");
293 if(end64 != NULL) {
294 if(end64 == start64) {
295 /* Empty, nothing between START and END */
296 SecImpInferDbg("impExpImportSinglePEM no base64 between terminators");
297 ortn = errSecUnsupportedFormat;
298 goto errOut;
299 }
300 base64Len = end64 - start64;
301 }
302 /* else no END, no reason to complain about that as long as base64 decode works OK */
303
304 /* Base 64 decode */
305 decData = cuDec64((const unsigned char *)start64, base64Len, &decDataLen);
306 if(decData == NULL) {
307 SecImpInferDbg("impExpImportSinglePEM bad base64 data");
308 ortn = errSecUnsupportedFormat;
309 goto errOut;
310 }
311
312 cdata = CFDataCreate(NULL, decData, decDataLen);
313 free((void *)decData);
314 rep = new Security::KeychainCore::SecImportRep(cdata, itemType, format, keyAlg,
315 pemParamLines);
316 CFArrayAppendValue(importReps, rep);
317 CFRelease(cdata); // SecImportRep holds ref
318 return noErr;
319
320 errOut:
321 if(pemParamLines != NULL) {
322 CFRelease(pemParamLines);
323 }
324 return ortn;
325 }
326
327 /*
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.
331 */
332 OSStatus impExpParsePemToImportRefs(
333 CFDataRef importedData,
334 CFMutableArrayRef importReps, // output appended here
335 bool *isPem) // true means we think it was PEM regardless of
336 // final return code
337 {
338 /*
339 * First task: is this PEM or at least base64 encoded?
340 */
341 const char *currCp = (const char *)CFDataGetBytePtr(importedData);
342 const char *cp = currCp;
343 unsigned lenToGo = CFDataGetLength(importedData);
344 OSStatus ortn;
345
346 *isPem = false;
347 unsigned dex;
348 bool allBlanks = true;
349
350 for(dex=0; dex<lenToGo; dex++, cp++) {
351 if (!isspace(*cp)) {
352 // it's not a space. Is it a non-ascii character?
353 if (!isascii(*cp)) {
354 return noErr;
355 }
356
357 // is it a control character?
358 if (iscntrl(*cp))
359 {
360 return noErr;
361 }
362
363 // no, mark that an acceptable character was encountered and keep going
364 allBlanks = false;
365 }
366 }
367
368 if (allBlanks)
369 {
370 return noErr;
371 }
372
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);
379 if(ortn == noErr) {
380 *isPem = true;
381 }
382 return ortn;
383 }
384
385 /* break up input into chunks between START and END lines */
386 ortn = noErr;
387 bool gotSomePem = false;
388 do {
389 /* get to beginning of START line */
390 startLine = findStr(currCp, lenToGo, "-----BEGIN");
391 if(startLine == NULL) {
392 break;
393 }
394 unsigned consumed = startLine - currCp;
395 assert(consumed <= lenToGo);
396 lenToGo -= consumed;
397 currCp += consumed;
398
399 /* get to beginning of END line */
400 const char *endLine = findStr(currCp+10, lenToGo, "-----END");
401 unsigned toDecode = lenToGo;
402 if(endLine) {
403 consumed = endLine - startLine;
404 assert(consumed <= lenToGo);
405 currCp += consumed;
406 lenToGo -= consumed;
407
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);
414
415 /* skip past END line and newlines */
416 assert(consumed <= lenToGo);
417 currCp += consumed;
418 lenToGo -= consumed;
419 }
420 else {
421 /* no END line, we'll allow that - decode to end of file */
422 lenToGo = 0;
423 }
424
425 ortn = impExpImportSinglePEM(startLine, toDecode, importReps);
426 if(ortn) {
427 break;
428 }
429 gotSomePem = true;
430 } while(lenToGo != 0);
431 if(ortn == noErr) {
432 if(gotSomePem) {
433 *isPem = true;
434 }
435 else {
436 SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found");
437 ortn = kSecFormatUnknown;
438 }
439 }
440 return ortn;
441 }
442
443
444 /*
445 * PEM encode a single SecExportRep's data, appending to a CFData.
446 */
447 OSStatus impExpPemEncodeExportRep(
448 CFDataRef derData,
449 const char *pemHeader,
450 CFArrayRef pemParamLines, // optional
451 CFMutableDataRef outData)
452 {
453 unsigned char *enc;
454 unsigned encLen;
455
456 char headerLine[200];
457 if(strlen(pemHeader) > 150) {
458 return paramErr;
459 }
460
461 /* First base64 encode */
462 enc = cuEnc64WithLines(CFDataGetBytePtr(derData), CFDataGetLength(derData),
463 64, &encLen);
464 if(enc == NULL) {
465 /* malloc error is actually the only known failure */
466 SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure");
467 return memFullErr;
468 }
469
470 /* strip off trailing NULL */
471 if((encLen != 0) && (enc[encLen - 1] == '\0')) {
472 encLen--;
473 }
474 sprintf(headerLine, "-----BEGIN %s-----\n", pemHeader);
475 CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine));
476
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++) {
481 CFStringRef cfStr =
482 (CFStringRef)CFArrayGetValueAtIndex(pemParamLines, dex);
483 char cStr[512];
484 UInt8 nl = '\n';
485 if(!CFStringGetCString(cfStr, cStr, sizeof(cStr),
486 kCFStringEncodingASCII)) {
487 /*
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.
491 */
492 SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup");
493 continue;
494 }
495 CFDataAppendBytes(outData, (const UInt8 *)cStr, strlen(cStr));
496 CFDataAppendBytes(outData, &nl, 1);
497 }
498 }
499 CFDataAppendBytes(outData, enc, encLen);
500 sprintf(headerLine, "-----END %s-----\n", pemHeader);
501 CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine));
502 free((void *)enc);
503 return noErr;
504 }
505