]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
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> | |
b1ab9ed8 A |
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; | |
427c49bc | 48 | unsigned srchStrLen = (unsigned)strlen(str); |
b1ab9ed8 A |
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) { | |
427c49bc | 100 | linelen = (unsigned)(newline - inText); |
b1ab9ed8 A |
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; | |
427c49bc | 165 | OSStatus ortn = errSecSuccess; |
b1ab9ed8 A |
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 */ | |
427c49bc | 183 | consumed = (unsigned)(startLine - currCp); |
b1ab9ed8 A |
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; | |
427c49bc | 237 | unsigned lineLen = (unsigned)strlen(currLine); |
b1ab9ed8 A |
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 | } | |
427c49bc | 299 | base64Len = (unsigned)(end64 - start64); |
b1ab9ed8 A |
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 | |
427c49bc | 317 | return errSecSuccess; |
b1ab9ed8 A |
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; | |
427c49bc | 342 | unsigned lenToGo = (unsigned)CFDataGetLength(importedData); |
b1ab9ed8 A |
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)) { | |
427c49bc | 353 | return errSecSuccess; |
b1ab9ed8 A |
354 | } |
355 | ||
356 | // is it a control character? | |
357 | if (iscntrl(*cp)) | |
358 | { | |
427c49bc | 359 | return errSecSuccess; |
b1ab9ed8 A |
360 | } |
361 | ||
362 | // no, mark that an acceptable character was encountered and keep going | |
363 | allBlanks = false; | |
364 | } | |
365 | } | |
366 | ||
367 | if (allBlanks) | |
368 | { | |
427c49bc | 369 | return errSecSuccess; |
b1ab9ed8 A |
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); | |
427c49bc | 378 | if(ortn == errSecSuccess) { |
b1ab9ed8 A |
379 | *isPem = true; |
380 | } | |
381 | return ortn; | |
382 | } | |
383 | ||
384 | /* break up input into chunks between START and END lines */ | |
427c49bc | 385 | ortn = errSecSuccess; |
b1ab9ed8 A |
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 | } | |
427c49bc | 393 | unsigned consumed = (unsigned)(startLine - currCp); |
b1ab9ed8 A |
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) { | |
427c49bc | 402 | consumed = (unsigned)(endLine - startLine); |
b1ab9ed8 A |
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 */ | |
427c49bc | 411 | toDecode = (unsigned)(endLine - startLine + strlen(tmpLine)); |
b1ab9ed8 A |
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); | |
427c49bc | 430 | if(ortn == errSecSuccess) { |
b1ab9ed8 A |
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) { | |
427c49bc | 457 | return errSecParam; |
b1ab9ed8 A |
458 | } |
459 | ||
460 | /* First base64 encode */ | |
427c49bc | 461 | enc = cuEnc64WithLines(CFDataGetBytePtr(derData), (unsigned)CFDataGetLength(derData), |
b1ab9ed8 A |
462 | 64, &encLen); |
463 | if(enc == NULL) { | |
464 | /* malloc error is actually the only known failure */ | |
465 | SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure"); | |
427c49bc | 466 | return errSecAllocate; |
b1ab9ed8 A |
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); | |
427c49bc | 502 | return errSecSuccess; |
b1ab9ed8 A |
503 | } |
504 |