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