/ OSX / libsecurity_keychain / lib / SecImportExportPem.cpp
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