SecImportExportPem.cpp [plain text]
#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>
#include <security_utilities/simulatecrash_assert.h>
static const char *findStr(
const char *inText,
unsigned inTextLen,
const char *str) {
const char *cp;
unsigned srchStrLen = (unsigned)strlen(str);
char c = str[0];
const char *endCp = inText + inTextLen - srchStrLen;
for(cp=inText; cp<=endCp; cp++) {
if(*cp == c) {
if(!memcmp(cp, str, srchStrLen)) {
return cp;
}
}
}
return NULL;
}
static const char *getLine(
const char *inText,
unsigned inTextLen, unsigned *consumed)
{
*consumed = 0;
const char *cp = inText;
const char *newline = NULL;
while(inTextLen) {
char c = *cp;
if((c == '\r') || (c == '\n')) {
if(newline == NULL) {
newline = cp;
}
}
else if(newline != NULL) {
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;
}
typedef struct {
const char *pemStr; SecExternalItemType itemType;
SecExternalFormat format;
CSSM_ALGORITHMS keyAlg;
} PemHeader;
#define NOALG CSSM_ALGID_NONE
static const PemHeader PemHeaders[] =
{
{ 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 },
{ 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))
static OSStatus impExpImportSinglePEM(
const char *currCp,
unsigned lenToGo,
CFMutableArrayRef importReps) {
unsigned consumed;
const char *currLine = NULL; 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;
SecExternalFormat format = kSecFormatUnknown;
SecExternalItemType itemType = kSecItemTypeUnknown;
CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE;
const char *startLine = findStr(currCp, lenToGo, "-----BEGIN");
if(startLine != NULL) {
consumed = (unsigned)(startLine - currCp);
lenToGo -= consumed;
currCp = startLine;
currLine = getLine(startLine, lenToGo, &consumed);
if(currLine == NULL) {
assert(lenToGo == 0);
SecImpInferDbg("impExpImportSinglePEM empty data");
ortn = errSecUnsupportedFormat;
goto errOut;
}
assert(consumed <= lenToGo);
currCp += consumed;
lenToGo -= consumed;
for(unsigned dex=0; dex<NUM_PEM_HEADERS; dex++) {
const PemHeader *ph = &PemHeaders[dex];
if(!strstr(currLine, ph->pemStr)) {
continue;
}
format = ph->format;
itemType = ph->itemType;
keyAlg = ph->keyAlg;
break;
}
free((void *)currLine);
}
for( ; ; ) {
currLine = getLine(currCp, lenToGo, &consumed);
if(currLine == NULL || currCp == lastCp) {
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) {
skipThis = true;
}
if(strchr(currLine, ':')) {
SecImpInferDbg("import PEM: param line %s", currLine);
CFStringRef cfStr = CFStringCreateWithCString(NULL, currLine,
kCFStringEncodingASCII);
if(pemParamLines == NULL) {
pemParamLines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if(strstr(currLine, "ENCRYPTED")) {
if((format == kSecFormatOpenSSL) &&
(itemType == kSecItemTypePrivateKey)) {
format = kSecFormatWrappedOpenSSL;
}
}
}
CFArrayAppendValue(pemParamLines, cfStr);
CFRelease(cfStr); skipThis = true;
}
free((void *)currLine);
if(!skipThis) {
break;
}
assert(consumed <= lenToGo);
currCp += consumed;
lenToGo -= consumed;
}
if(lenToGo <= 2) {
SecImpInferDbg("impExpImportSinglePEM no valid base64 data");
ortn = errSecUnsupportedFormat;
goto errOut;
}
start64 = currCp;
base64Len = lenToGo; end64 = findStr(currCp, lenToGo, "-----END");
if(end64 != NULL) {
if(end64 == start64) {
SecImpInferDbg("impExpImportSinglePEM no base64 between terminators");
ortn = errSecUnsupportedFormat;
goto errOut;
}
base64Len = (unsigned)(end64 - start64);
}
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); return errSecSuccess;
errOut:
if(pemParamLines != NULL) {
CFRelease(pemParamLines);
}
return ortn;
}
OSStatus impExpParsePemToImportRefs(
CFDataRef importedData,
CFMutableArrayRef importReps, bool *isPem) {
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)) {
if (!isascii(*cp)) {
return errSecSuccess;
}
if (iscntrl(*cp))
{
return errSecSuccess;
}
allBlanks = false;
}
}
if (allBlanks)
{
return errSecSuccess;
}
const char *startLine = findStr(currCp, lenToGo, "-----BEGIN");
if(startLine == NULL) {
SecImpInferDbg("impExpParsePemToImportRefs no PEM headers, assuming raw base64");
ortn = impExpImportSinglePEM(currCp, lenToGo, importReps);
if(ortn == errSecSuccess) {
*isPem = true;
}
return ortn;
}
ortn = errSecSuccess;
bool gotSomePem = false;
do {
startLine = findStr(currCp, lenToGo, "-----BEGIN");
if(startLine == NULL) {
break;
}
unsigned consumed = (unsigned)(startLine - currCp);
assert(consumed <= lenToGo);
lenToGo -= consumed;
currCp += consumed;
const char *endLine = findStr(currCp+10, lenToGo, "-----END");
unsigned toDecode = lenToGo;
if(endLine) {
consumed = (unsigned)(endLine - startLine);
assert(consumed <= lenToGo);
currCp += consumed;
lenToGo -= consumed;
const char *tmpLine = getLine(endLine, lenToGo, &consumed);
assert((tmpLine != NULL) && (tmpLine[0] != 0));
toDecode = (unsigned)(endLine - startLine + strlen(tmpLine));
free((void *)tmpLine);
assert(consumed <= lenToGo);
currCp += consumed;
lenToGo -= consumed;
}
else {
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;
}
OSStatus impExpPemEncodeExportRep(
CFDataRef derData,
const char *pemHeader,
CFArrayRef pemParamLines, CFMutableDataRef outData)
{
unsigned char *enc;
unsigned encLen;
char headerLine[200];
if(strlen(pemHeader) > 150) {
return errSecParam;
}
enc = cuEnc64WithLines(CFDataGetBytePtr(derData), (unsigned)CFDataGetLength(derData),
64, &encLen);
if(enc == NULL) {
SecImpExpDbg("impExpPemEncodeExportRep: cuEnc64WithLines failure");
return errSecAllocate;
}
if((encLen != 0) && (enc[encLen - 1] == '\0')) {
encLen--;
}
sprintf(headerLine, "-----BEGIN %s-----\n", pemHeader);
CFDataAppendBytes(outData, (const UInt8 *)headerLine, strlen(headerLine));
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)) {
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;
}