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