+++ /dev/null
-/*
- * 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;
-}
-