]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_keychain/Security/SecImportExportPem.cpp
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / Security / SecImportExportPem.cpp
diff --git a/OSX/libsecurity_keychain/Security/SecImportExportPem.cpp b/OSX/libsecurity_keychain/Security/SecImportExportPem.cpp
new file mode 100644 (file)
index 0000000..df67556
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) 2004,2011-2014 Apple Inc. All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ *
+ * SecImportExportPem.cpp - private PEM routines for SecImportExport
+ */
+
+#include "SecImportExportPem.h"
+#include "SecExternalRep.h"
+#include "SecImportExportUtils.h"
+#include <security_cdsa_utils/cuEnc64.h>
+#include <security_cdsa_utils/cuPem.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+/* 
+ * Text parsing routines. 
+ *
+ * Search incoming text for specified string. Does not assume inText is
+ * NULL terminated. Returns pointer to start of found string in inText.
+ */
+static const char *findStr(
+       const char *inText,
+       unsigned inTextLen,
+       const char *str)                                // NULL terminated - search for this
+{
+       /* probably not the hottest string search algorithm... */
+       const char *cp;
+       unsigned srchStrLen = (unsigned)strlen(str);
+       char c = str[0];
+       
+       /* last char * we can search in inText for start of str */
+       const char *endCp = inText + inTextLen - srchStrLen;
+       
+       for(cp=inText; cp<=endCp; cp++) {
+               if(*cp == c) {
+                       if(!memcmp(cp, str, srchStrLen)) {
+                               return cp;
+                       }
+               }
+       }
+       return NULL;
+}
+
+/*
+ * Obtain one line from current text. Returns a mallocd, NULL-terminated string
+ * which caller must free(). Also returns the number of chars consumed including
+ * the returned chars PLUS EOL terminators (\n and/or \r).
+ *
+ * ALWAYS returns a mallocd string if there is ANY data remaining per the 
+ * incoming inTextLen. Returns NULL if inTextLen is zero.
+ */
+static const char *getLine(
+       const char *inText,
+       unsigned inTextLen,                     // RETURNED
+       unsigned *consumed)                     // RETURNED
+       
+{
+       *consumed = 0;
+       const char *cp = inText;
+       const char *newline = NULL;             // if we found a newline, this points to the first one
+       
+       while(inTextLen) {
+               char c = *cp;
+               if((c == '\r') || (c == '\n')) {
+                       if(newline == NULL) {
+                               /* first newline */
+                               newline = cp;
+                       }
+               }
+               else if(newline != NULL) {
+                       /* non newline after newline, done */
+                       break;
+               }
+               (*consumed)++;
+               inTextLen--;
+               cp++;
+       }
+       unsigned linelen;
+       if(newline) {
+               linelen = (unsigned)(newline - inText);
+       }
+       else {
+               linelen = *consumed;
+       }
+       char *rtn = (char *)malloc(linelen + 1);
+       memmove(rtn, inText, linelen);
+       rtn[linelen] = 0;
+       return rtn;
+}
+
+/*
+ * Table to facilitate conversion of known PEM header strings to 
+ * the things we know about.
+ */
+typedef struct {
+       const char                      *pemStr;                        // e.g. PEM_STRING_X509, "CERTIFICATE"
+       SecExternalItemType itemType;
+       SecExternalFormat   format;
+       CSSM_ALGORITHMS         keyAlg;
+} PemHeader;
+
+#define NOALG   CSSM_ALGID_NONE
+
+static const PemHeader PemHeaders[] = 
+{
+       /* from openssl/pem.h standard header */
+       { PEM_STRING_X509_OLD, kSecItemTypeCertificate, kSecFormatX509Cert, NOALG},
+       { PEM_STRING_X509,  kSecItemTypeCertificate, kSecFormatX509Cert, NOALG },
+       { PEM_STRING_EVP_PKEY, kSecItemTypePrivateKey, kSecFormatOpenSSL, NOALG},
+       { PEM_STRING_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, NOALG },
+       { PEM_STRING_RSA, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_RSA },
+       { PEM_STRING_RSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_RSA },
+       { PEM_STRING_DSA, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_DSA },
+       { PEM_STRING_DSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_DSA },
+       { PEM_STRING_PKCS7, kSecItemTypeAggregate, kSecFormatPKCS7, NOALG },
+       { PEM_STRING_PKCS8, kSecItemTypePrivateKey, kSecFormatWrappedPKCS8, NOALG },
+       { PEM_STRING_PKCS8INF, kSecItemTypePrivateKey, kSecFormatUnknown, NOALG },
+       /* we define these  */
+       { PEM_STRING_DH_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_DH },
+       { PEM_STRING_DH_PRIVATE, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_DH },
+       { PEM_STRING_PKCS12, kSecItemTypeAggregate, kSecFormatPKCS12, NOALG },
+       { PEM_STRING_SESSION, kSecItemTypeSessionKey, kSecFormatRawKey, NOALG },
+       { PEM_STRING_ECDSA_PUBLIC, kSecItemTypePublicKey, kSecFormatOpenSSL, CSSM_ALGID_ECDSA },
+       { PEM_STRING_ECDSA_PRIVATE, kSecItemTypePrivateKey, kSecFormatOpenSSL, CSSM_ALGID_ECDSA }
+};
+#define NUM_PEM_HEADERS (sizeof(PemHeaders) / sizeof(PemHeader))
+
+/*
+ * PEM decode incoming data which we've previously determined to contain
+ * exactly one reasonably well formed PEM blob (it has no more than one
+ * START and END line - though it may have none - and is all ASCII).
+ *
+ * Returned SecImportRep may or may not have a known type and format and 
+ * (if it is a key) algorithm. 
+ */
+static OSStatus impExpImportSinglePEM(
+       const char                      *currCp,
+       unsigned                        lenToGo,
+       CFMutableArrayRef       importReps)             // output appended here
+{
+       unsigned consumed;
+       const char *currLine = NULL;            // mallocd by getLine()
+       const char *lastCp = currCp;
+       CFMutableArrayRef pemParamLines = NULL;
+       OSStatus ortn = errSecSuccess;
+       CFDataRef cdata = NULL;
+       Security::KeychainCore::SecImportRep *rep = NULL;
+       const char *start64;
+       unsigned base64Len;     
+       const char *end64;
+       unsigned char *decData;
+       unsigned decDataLen;
+       
+       /* we try to glean these from the header, but it's not fatal if we can not */
+       SecExternalFormat format = kSecFormatUnknown;
+       SecExternalItemType itemType = kSecItemTypeUnknown;
+       CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE;
+       
+       /* search to START line, parse it to get type/format/alg */
+       const char *startLine = findStr(currCp, lenToGo, "-----BEGIN");
+       if(startLine != NULL) {
+               /* possibly skip over leading garbage */
+               consumed = (unsigned)(startLine - currCp);
+               lenToGo -= consumed;
+               currCp = startLine;
+               
+               /* get C string of START line */
+               currLine = getLine(startLine, lenToGo, &consumed);
+               if(currLine == NULL) {
+                       /* somehow got here with no data */
+                       assert(lenToGo == 0);
+                       SecImpInferDbg("impExpImportSinglePEM empty data");
+                       ortn = errSecUnsupportedFormat;
+                       goto errOut;
+               }
+               assert(consumed <= lenToGo);
+               currCp  += consumed;
+               lenToGo -= consumed;
+
+               /*
+                * Search currLine for known PEM header strings.
+                * It is not an error if we don't recognize this
+                * header.
+                */
+               for(unsigned dex=0; dex<NUM_PEM_HEADERS; dex++) {
+                       const PemHeader *ph = &PemHeaders[dex];
+                       if(!strstr(currLine, ph->pemStr)) {
+                               continue;
+                       }
+                       /* found one! */
+                       format   = ph->format;
+                       itemType = ph->itemType;
+                       keyAlg   = ph->keyAlg;
+                       break;
+               }
+               
+               free((void *)currLine);
+       }
+
+       /* 
+        * Skip empty lines. Save all lines containing ':' (used by openssl 
+        * to specify key wrapping parameters). These will be saved in 
+        * outgoing SecImportReps' pemParamLines.
+        */
+       for( ; ; ) {
+               currLine = getLine(currCp, lenToGo, &consumed);
+               if(currLine == NULL || currCp == lastCp) {
+                       /* out of data (unable to advance to next line) */
+                       SecImpInferDbg("impExpImportSinglePEM out of data");
+                       if (currLine) free((void *)currLine);
+                       ortn = errSecUnsupportedFormat;
+                       goto errOut;
+               }
+               lastCp = currCp;
+               
+               bool skipThis = false;
+               unsigned lineLen = (unsigned)strlen(currLine);
+               if(lineLen == 0) {
+                       /* empty line */
+                       skipThis = true;
+               }
+               if(strchr(currLine, ':')) {
+                       /* 
+                        * Save this PEM header info. Used for traditional openssl
+                        * wrapped keys to indicate IV.
+                        */
+                       SecImpInferDbg("import PEM: param line %s", currLine);
+                       CFStringRef cfStr = CFStringCreateWithCString(NULL, currLine,
+                               kCFStringEncodingASCII);
+                       if(pemParamLines == NULL) {
+                               /* first param line */
+                               pemParamLines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+                               
+                               /* 
+                                * If it says "ENCRYPTED" and this is a private key,
+                                * flag the fact that it's wrapped in openssl format
+                                */
+                               if(strstr(currLine, "ENCRYPTED")) {
+                                       if((format == kSecFormatOpenSSL) &&
+                                          (itemType == kSecItemTypePrivateKey)) {
+                                               format = kSecFormatWrappedOpenSSL;
+                                       }
+                               }
+                       }
+                       CFArrayAppendValue(pemParamLines, cfStr);
+                       CFRelease(cfStr);               // array owns it 
+                       skipThis = true;
+               }
+               free((void *)currLine);
+               if(!skipThis) {
+                       /* looks like good stuff; process */
+                       break;
+               }
+               /* skip this line */
+               assert(consumed <= lenToGo);
+               currCp  += consumed;
+               lenToGo -= consumed;
+       }
+       if(lenToGo <= 2) {
+               SecImpInferDbg("impExpImportSinglePEM no valid base64 data");
+               ortn = errSecUnsupportedFormat;
+               goto errOut;
+       }
+
+       /* 
+        * currCP points to start of base64 data - mark it and search for end line.
+        * We skip everything after the end line.
+        */
+       start64 = currCp;
+       base64Len = lenToGo;                    // if no END
+       end64 = findStr(currCp, lenToGo, "-----END");
+       if(end64 != NULL) {
+               if(end64 == start64) {
+                       /* Empty, nothing between START and END */
+                       SecImpInferDbg("impExpImportSinglePEM no base64 between terminators");
+                       ortn = errSecUnsupportedFormat;
+                       goto errOut;
+               }
+               base64Len = (unsigned)(end64 - start64);
+       }
+       /* else no END, no reason to complain about that as long as base64 decode works OK */
+       
+       /* Base 64 decode */
+       decData = cuDec64((const unsigned char *)start64, base64Len, &decDataLen);
+       if(decData == NULL) {
+               SecImpInferDbg("impExpImportSinglePEM bad base64 data");
+               ortn = errSecUnsupportedFormat;
+               goto errOut;
+       }
+       
+       cdata = CFDataCreate(NULL, decData, decDataLen);
+       free((void *)decData);
+       rep = new Security::KeychainCore::SecImportRep(cdata, itemType, format, keyAlg,
+               pemParamLines);
+       CFArrayAppendValue(importReps, rep);
+       CFRelease(cdata);               // SecImportRep holds ref
+       return errSecSuccess;
+       
+errOut:
+       if(pemParamLines != NULL) {
+               CFRelease(pemParamLines);
+       }
+       return ortn;
+}
+
+/*
+ * PEM decode incoming data, appending SecImportRep's to specified array.
+ * Returned SecImportReps may or may not have a known type and format and 
+ * (if they are keys) algorithm. 
+ */
+OSStatus impExpParsePemToImportRefs(
+       CFDataRef                       importedData,
+       CFMutableArrayRef       importReps,             // output appended here
+       bool                            *isPem)                 // true means we think it was PEM regardless of 
+                                                                               // final return code    
+{
+       /*
+        * First task: is this PEM or at least base64 encoded?
+        */
+       const char *currCp = (const char *)CFDataGetBytePtr(importedData);
+       const char *cp = currCp;
+       unsigned lenToGo = (unsigned)CFDataGetLength(importedData);
+       OSStatus ortn;
+       
+       *isPem = false;
+       unsigned dex;
+       bool allBlanks = true;
+       
+       for(dex=0; dex<lenToGo; dex++, cp++) {
+               if (!isspace(*cp)) {
+                       // it's not a space.  Is it a non-ascii character?
+                       if (!isascii(*cp)) {
+                               return errSecSuccess;
+                       }
+                       
+                       // is it a control character?
+                       if (iscntrl(*cp))
+                       {
+                               return errSecSuccess;
+                       }
+                       
+                       // no, mark that an acceptable character was encountered and keep going
+                       allBlanks = false;
+               }
+       }
+
+       if (allBlanks)
+       {
+               return errSecSuccess;
+       }
+       
+       /* search for START line */
+       const char *startLine = findStr(currCp, lenToGo, "-----BEGIN");
+       if(startLine == NULL) {
+               /* Assume one item, raw base64 */
+               SecImpInferDbg("impExpParsePemToImportRefs no PEM headers, assuming raw base64");
+               ortn = impExpImportSinglePEM(currCp, lenToGo, importReps);
+               if(ortn == errSecSuccess) {
+                       *isPem = true;
+               }
+               return ortn;
+       }
+
+       /* break up input into chunks between START and END lines */
+       ortn = errSecSuccess;
+       bool gotSomePem = false;
+       do {
+               /* get to beginning of START line */
+               startLine = findStr(currCp, lenToGo, "-----BEGIN");
+               if(startLine == NULL) {
+                       break;
+               }
+               unsigned consumed = (unsigned)(startLine - currCp);
+               assert(consumed <= lenToGo);
+               lenToGo -= consumed;
+               currCp  += consumed;
+               
+               /* get to beginning of END line */
+               const char *endLine = findStr(currCp+10, lenToGo, "-----END");
+               unsigned toDecode = lenToGo;
+               if(endLine) {
+                       consumed = (unsigned)(endLine - startLine);
+                       assert(consumed <= lenToGo);
+                       currCp  += consumed;
+                       lenToGo -= consumed;
+                       
+                       /* find end of END line */
+                       const char *tmpLine = getLine(endLine, lenToGo, &consumed);
+                       assert((tmpLine != NULL) && (tmpLine[0] != 0));
+                       /* don't decode the terminators */
+                       toDecode = (unsigned)(endLine - startLine + strlen(tmpLine));
+                       free((void *)tmpLine);
+                       
+                       /* skip past END line and newlines */
+                       assert(consumed <= lenToGo);
+                       currCp  += consumed;
+                       lenToGo -= consumed;
+               }
+               else {
+                       /* no END line, we'll allow that - decode to end of file */
+                       lenToGo = 0;
+               }
+               
+               ortn = impExpImportSinglePEM(startLine, toDecode, importReps);
+               if(ortn) {
+                       break;
+               }
+               gotSomePem = true;
+       } while(lenToGo != 0);
+       if(ortn == errSecSuccess) {
+               if(gotSomePem) {
+                       *isPem = true;
+               }
+               else {
+                       SecImpInferDbg("impExpParsePemToImportRefs empty at EOF, no PEM found");
+                       ortn = kSecFormatUnknown;
+               }
+       }
+       return ortn;
+}
+       
+
+/*
+ * PEM encode a single SecExportRep's data, appending to a CFData.
+ */
+OSStatus impExpPemEncodeExportRep(
+       CFDataRef                       derData,
+       const char                      *pemHeader,
+       CFArrayRef                      pemParamLines,  // optional 
+       CFMutableDataRef        outData)
+{
+       unsigned char *enc;
+       unsigned encLen;
+       
+       char headerLine[200];
+       if(strlen(pemHeader) > 150) {
+               return errSecParam;
+       }
+
+       /* First base64 encode */
+       enc = cuEnc64WithLines(CFDataGetBytePtr(derData), (unsigned)CFDataGetLength(derData),
+               64, &encLen);
+       if(enc == NULL) {
+               /* malloc error is actually the only known failure */
+               SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure");
+               return errSecAllocate;
+       }
+       
+       /* strip off trailing NULL */
+       if((encLen != 0) && (enc[encLen - 1] == '\0')) {
+               encLen--;
+       }
+       sprintf(headerLine, "-----BEGIN %s-----\n", pemHeader);
+       CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine));
+       
+       /* optional PEM parameters lines (currently used for openssl wrap format only) */
+       if(pemParamLines != NULL) {
+               CFIndex numLines = CFArrayGetCount(pemParamLines);
+               for(CFIndex dex=0; dex<numLines; dex++) {
+                       CFStringRef cfStr = 
+                               (CFStringRef)CFArrayGetValueAtIndex(pemParamLines, dex);
+                       char cStr[512];
+                       UInt8 nl = '\n';
+                       if(!CFStringGetCString(cfStr, cStr, sizeof(cStr), 
+                                       kCFStringEncodingASCII)) {
+                               /* 
+                                * Should never happen; this module created this CFString
+                                * from a C string with ASCII encoding. Keep going, though
+                                * this is probably fatal to the exported representation.
+                                */
+                               SecImpExpDbg("impExpPemEncodeExportRep: pemParamLine screwup");
+                               continue; 
+                       }
+                       CFDataAppendBytes(outData, (const UInt8 *)cStr, strlen(cStr));
+                       CFDataAppendBytes(outData, &nl, 1);
+               }
+       }
+       CFDataAppendBytes(outData, enc, encLen);
+       sprintf(headerLine, "-----END %s-----\n", pemHeader);
+       CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine));
+       free((void *)enc);
+       return errSecSuccess;
+}
+