Skip to content

Instantly share code, notes, and snippets.

@rbsgn
Created October 23, 2011 17:17
Show Gist options
  • Save rbsgn/1307595 to your computer and use it in GitHub Desktop.
Save rbsgn/1307595 to your computer and use it in GitHub Desktop.
Openvpn patch that allows loading of VPN certificate from Mac OS X keychain. Originally created by Brian Raderman (<brian@irregularexpression.org>).
From a53d19a34be1301c4f4c37e9767ca698885b84fb Mon Sep 17 00:00:00 2001
From: Roman Busyghin <nskboy@gmail.com>
Date: Sun, 23 Oct 2011 20:37:52 +0400
Subject: [PATCH] Ability to lookup OS X keychain to load VPN certificate
This patch is created by Brian Raderman <brian@irregularexpression.org> and modified a bit in order to be compiled with make instead of Xcode
---
Makefile.am | 9 +
cert_data.c | 631 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cert_data.h | 44 ++++
common_osx.c | 98 +++++++++
common_osx.h | 35 ++++
configure.ac | 7 +
crypto_osx.c | 75 +++++++
crypto_osx.h | 34 +++
keychain.c | 274 +++++++++++++++++++++++++
keychain.h | 6 +
openvpn.8 | 57 ++++++
options.c | 31 +++
options.h | 2 +
ssl.c | 4 +
14 files changed, 1307 insertions(+), 0 deletions(-)
create mode 100644 cert_data.c
create mode 100644 cert_data.h
create mode 100644 common_osx.c
create mode 100644 common_osx.h
create mode 100644 crypto_osx.c
create mode 100644 crypto_osx.h
create mode 100644 keychain.c
create mode 100644 keychain.h
diff --git a/Makefile.am b/Makefile.am
index 3228798..0e1443b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -145,6 +145,15 @@ openvpn_SOURCES = \
tun.c tun.h \
win32.h win32.c \
cryptoapi.h cryptoapi.c
+
+if APPLE
+openvpn_SOURCES += \
+ cert_data.c cert_data.h \
+ common_osx.c common_osx.h \
+ crypto_osx.c crypto_osx.h \
+ keychain.c keychain.h
+
+endif
nodist_openvpn_SOURCES = configure.h
options.$(OBJEXT): configure.h
diff --git a/cert_data.c b/cert_data.c
new file mode 100644
index 0000000..c357640
--- /dev/null
+++ b/cert_data.c
@@ -0,0 +1,631 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <CommonCrypto/CommonDigest.h>
+#include "cert_data.h"
+#include "common_osx.h"
+
+CFStringRef kCertDataSubjectName = CFSTR("subject"), kCertDataIssuerName = CFSTR("issuer"), kCertDataSha1Name = CFSTR("SHA1"), kCertDataMd5Name = CFSTR("MD5"), kCertNameFwdSlash = CFSTR("/"), kCertNameEquals = CFSTR("=");
+CFStringRef kCertNameOrganization = CFSTR("o"), kCertNameOrganizationalUnit = CFSTR("ou"), kCertNameCountry = CFSTR("c"), kCertNameLocality = CFSTR("l"), kCertNameState = CFSTR("st"), kCertNameCommonName = CFSTR("cn"), kCertNameEmail = CFSTR("e"), kCertNameDomainComponent = CFSTR("dc");
+CFStringRef kStringSpace = CFSTR(" "), kStringEmpty = CFSTR("");
+
+typedef struct _CertName
+{
+ CFArrayRef countryName, organization, organizationalUnit, commonName, description, emailAddress,
+ stateName, localityName, distinguishedName, domainComponent;
+} CertName, *CertNameRef;
+
+typedef struct _DescData
+{
+ CFStringRef name, value;
+} DescData, *DescDataRef;
+
+
+CertNameRef createCertName()
+{
+ CertNameRef pCertName = (CertNameRef)malloc(sizeof(CertName));
+ pCertName->countryName = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->organization = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->organizationalUnit = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->commonName = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->description = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->emailAddress = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->stateName = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->localityName = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->distinguishedName = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ pCertName->domainComponent = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ return pCertName;
+}
+
+void destroyCertName(CertNameRef pCertName)
+{
+ if (!pCertName)
+ return;
+
+ CFRelease(pCertName->countryName);
+ CFRelease(pCertName->organization);
+ CFRelease(pCertName->organizationalUnit);
+ CFRelease(pCertName->commonName);
+ CFRelease(pCertName->description);
+ CFRelease(pCertName->emailAddress);
+ CFRelease(pCertName->stateName);
+ CFRelease(pCertName->localityName);
+ CFRelease(pCertName->distinguishedName);
+ CFRelease(pCertName->domainComponent);
+ free(pCertName);
+}
+
+
+CertNameRef dataToName(CSSM_DATA_PTR pData)
+{
+ CSSM_X509_NAME_PTR pName = (CSSM_X509_NAME_PTR)pData->Data;
+ CertNameRef pCertName = createCertName();
+ int i, j;
+
+ for(i = 0;i < pName->numberOfRDNs;i++)
+ {
+ CSSM_X509_RDN rdn = pName->RelativeDistinguishedName[i];
+
+ for(j = 0;j < rdn.numberOfPairs;j++)
+ {
+ CSSM_X509_TYPE_VALUE_PAIR nvp = rdn.AttributeTypeAndValue[j];
+ CFStringRef str = CFStringCreateWithBytes (NULL, nvp.value.Data, nvp.value.Length, kCFStringEncodingUTF8, false);
+
+ if (memcmp(nvp.type.Data, CSSMOID_CountryName.Data, CSSMOID_CountryName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->countryName, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_OrganizationName.Data, CSSMOID_OrganizationName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->organization, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_OrganizationalUnitName.Data, CSSMOID_OrganizationalUnitName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->organizationalUnit, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_CommonName.Data, CSSMOID_CommonName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->commonName, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_Description.Data, CSSMOID_Description.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->description, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_EmailAddress.Data, CSSMOID_EmailAddress.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->emailAddress, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_StateProvinceName.Data, CSSMOID_StateProvinceName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->stateName, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_LocalityName.Data, CSSMOID_LocalityName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->localityName, str);
+ else if (memcmp(nvp.type.Data, CSSMOID_DistinguishedName.Data, CSSMOID_DistinguishedName.Length) == 0)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->distinguishedName, str);
+
+ CFRelease(str);
+ }
+ }
+
+ return pCertName;
+}
+
+CFArrayRef GetFieldsFromCssmCertData(CSSM_CL_HANDLE hCL, CSSM_DATA_PTR pCertData, CSSM_OID oid)
+{
+ CSSM_DATA_PTR pData = NULL;
+ CFMutableArrayRef fields = CFArrayCreateMutable(NULL, 0, NULL);
+ uint32 numFields, i;
+
+ CSSM_HANDLE ResultsHandle = (CSSM_HANDLE)NULL;
+ numFields = 0;
+
+ CSSM_CL_CertGetFirstFieldValue(hCL, pCertData, &oid, &ResultsHandle, &numFields, &pData);
+
+ if (!pData)
+ {
+ CSSM_CL_CertAbortQuery(hCL, ResultsHandle);
+ return NULL;
+ }
+
+ for (i = 0; i < numFields; i++)
+ {
+ CertNameRef pName = dataToName(pData);
+ CFArrayAppendValue(fields, pName);
+ CSSM_CL_FreeFieldValue (hCL, &oid, pData);
+ if (i < (numFields - 1))
+ CSSM_CL_CertGetNextFieldValue(hCL, ResultsHandle, &pData);
+ }
+
+ CSSM_CL_CertAbortQuery(hCL, ResultsHandle);
+ return fields;
+}
+
+CFArrayRef GetFieldsFromCertificate(SecCertificateRef certificate, CSSM_OID oid)
+{
+ CSSM_CL_HANDLE hCL;
+ CSSM_DATA certData;
+ CFArrayRef fieldValues;
+
+ SecCertificateGetCLHandle(certificate, &hCL);
+ SecCertificateGetData(certificate, &certData);
+
+ fieldValues = GetFieldsFromCssmCertData(hCL, &certData, oid);
+
+ if (fieldValues == NULL)
+ {
+ return NULL;
+ }
+ else if (CFArrayGetCount(fieldValues) == 0)
+ {
+ CFRelease(fieldValues);
+ return NULL;
+ }
+
+ return fieldValues;
+}
+
+CertDataRef createCertDataFromCertificate(SecCertificateRef certificate)
+{
+ CertDataRef pCertData = (CertDataRef)malloc(sizeof(CertData));
+ pCertData->subject = GetFieldsFromCertificate(certificate, CSSMOID_X509V1SubjectNameCStruct);
+ pCertData->issuer = GetFieldsFromCertificate(certificate, CSSMOID_X509V1IssuerNameCStruct);
+
+ CSSM_DATA cssmCertData;
+ SecCertificateGetData (certificate, &cssmCertData);
+
+ unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(cssmCertData.Data, cssmCertData.Length, sha1);
+ pCertData->sha1 = createHexString(sha1, CC_SHA1_DIGEST_LENGTH);
+
+ unsigned char md5[CC_MD5_DIGEST_LENGTH];
+ CC_MD5(cssmCertData.Data, cssmCertData.Length, md5);
+ pCertData->md5 = createHexString((unsigned char*)md5, CC_MD5_DIGEST_LENGTH);
+
+ return pCertData;
+}
+
+CFStringRef stringFromRange(const char *cstring, CFRange range)
+{
+ CFStringRef str = CFStringCreateWithBytes (NULL, (uint8*)&cstring[range.location], range.length, kCFStringEncodingUTF8, false);
+ CFMutableStringRef mutableStr = CFStringCreateMutableCopy(NULL, 0, str);
+ CFStringTrimWhitespace(mutableStr);
+ CFRelease(str);
+ return mutableStr;
+}
+
+DescDataRef createDescData(const char *description, CFRange nameRange, CFRange valueRange)
+{
+ DescDataRef pRetVal = (DescDataRef)malloc(sizeof(DescData));
+
+ memset(pRetVal, 0, sizeof(DescData));
+
+ if (nameRange.length > 0)
+ pRetVal->name = stringFromRange(description, nameRange);
+
+ if (valueRange.length > 0)
+ pRetVal->value = stringFromRange(description, valueRange);
+
+ return pRetVal;
+}
+
+void destroyDescData(DescDataRef pData)
+{
+ if (pData->name)
+ CFRelease(pData->name);
+
+ if (pData->value)
+ CFRelease(pData->value);
+
+ free(pData);
+}
+
+CFArrayRef createDescDataPairs(const char *description)
+{
+ int numChars = strlen(description);
+ CFRange nameRange, valueRange;
+ DescDataRef pData;
+ CFMutableArrayRef retVal = CFArrayCreateMutable(NULL, 0, NULL);
+
+ int i = 0;
+
+ nameRange = CFRangeMake(0, 0);
+ valueRange = CFRangeMake(0, 0);
+ bool bInValue = false;
+
+ while(i < numChars)
+ {
+ if (!bInValue && (description[i] != ':'))
+ {
+ nameRange.length++;
+ }
+ else if (bInValue && (description[i] != ':'))
+ {
+ valueRange.length++;
+ }
+ else if(!bInValue)
+ {
+ bInValue = true;
+ valueRange.location = i + 1;
+ valueRange.length = 0;
+ }
+ else //(bInValue)
+ {
+ bInValue = false;
+ while(description[i] != ' ')
+ {
+ valueRange.length--;
+ i--;
+ }
+
+ pData = createDescData(description, nameRange, valueRange);
+ CFArrayAppendValue(retVal, pData);
+
+ nameRange.location = i + 1;
+ nameRange.length = 0;
+ }
+
+ i++;
+ }
+
+ pData = createDescData(description, nameRange, valueRange);
+ CFArrayAppendValue(retVal, pData);
+ return retVal;
+}
+
+void arrayDestroyDescData(const void *val, void *context)
+{
+ DescDataRef pData = (DescDataRef) val;
+ destroyDescData(pData);
+}
+
+
+void parseNameComponent(CFStringRef dn, CFStringRef *pName, CFStringRef *pValue)
+{
+ CFArrayRef nameStrings = CFStringCreateArrayBySeparatingStrings(NULL, dn, kCertNameEquals);
+
+ *pName = *pValue = NULL;
+
+ if (CFArrayGetCount(nameStrings) != 2)
+ return;
+
+ CFMutableStringRef str;
+
+ str = CFStringCreateMutableCopy(NULL, 0, CFArrayGetValueAtIndex(nameStrings, 0));
+ CFStringTrimWhitespace(str);
+ *pName = str;
+
+ str = CFStringCreateMutableCopy(NULL, 0, CFArrayGetValueAtIndex(nameStrings, 1));
+ CFStringTrimWhitespace(str);
+ *pValue = str;
+
+ CFRelease(nameStrings);
+}
+
+void parseCertName(CFStringRef nameDesc, CFMutableArrayRef names)
+{
+ CFArrayRef nameStrings = CFStringCreateArrayBySeparatingStrings(NULL, nameDesc, kCertNameFwdSlash);
+ int count = CFArrayGetCount(nameStrings);
+ int i;
+
+ CertNameRef pCertName = createCertName();
+
+ for(i = 0;i < count;i++)
+ {
+ CFMutableStringRef dn = CFStringCreateMutableCopy(NULL, 0, CFArrayGetValueAtIndex(nameStrings, i));
+ CFStringTrimWhitespace(dn);
+
+ CFStringRef name, value;
+
+ parseNameComponent(dn, &name, &value);
+
+ if (!name || !value)
+ {
+ if (name)
+ CFRelease(name);
+
+ if (value)
+ CFRelease(value);
+
+ CFRelease(dn);
+ continue;
+ }
+
+ if (CFStringCompareWithOptions(name, kCertNameOrganization, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->organization, value);
+ else if (CFStringCompareWithOptions(name, kCertNameOrganizationalUnit, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->organizationalUnit, value);
+ else if (CFStringCompareWithOptions(name, kCertNameCountry, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->countryName, value);
+ else if (CFStringCompareWithOptions(name, kCertNameLocality, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->localityName, value);
+ else if (CFStringCompareWithOptions(name, kCertNameState, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->stateName, value);
+ else if (CFStringCompareWithOptions(name, kCertNameCommonName, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->commonName, value);
+ else if (CFStringCompareWithOptions(name, kCertNameEmail, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->emailAddress, value);
+ else if (CFStringCompareWithOptions(name, kCertNameDomainComponent, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ CFArrayAppendValue((CFMutableArrayRef)pCertName->domainComponent, value);
+
+ CFRelease(name);
+ CFRelease(value);
+ CFRelease(dn);
+ }
+
+ CFArrayAppendValue(names, pCertName);
+ CFRelease(nameStrings);
+}
+
+void arrayParseDescDataPair(const void *val, void *context)
+{
+ DescDataRef pDescData = (DescDataRef)val;
+ CertDataRef pCertData = (CertDataRef)context;
+
+ if (!pDescData->name || !pDescData->value)
+ return;
+
+ if (CFStringCompareWithOptions(pDescData->name, kCertDataSubjectName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ parseCertName(pDescData->value, (CFMutableArrayRef)pCertData->subject);
+ else if (CFStringCompareWithOptions(pDescData->name, kCertDataIssuerName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ parseCertName(pDescData->value, (CFMutableArrayRef)pCertData->issuer);
+ else if (CFStringCompareWithOptions(pDescData->name, kCertDataSha1Name, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ pCertData->sha1 = CFRetain(pDescData->value);
+ else if (CFStringCompareWithOptions(pDescData->name, kCertDataMd5Name, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ pCertData->md5 = CFRetain(pDescData->value);
+}
+
+CertDataRef createCertDataFromString(const char *description)
+{
+ CertDataRef pCertData = (CertDataRef)malloc(sizeof(CertData));
+ pCertData->subject = CFArrayCreateMutable(NULL, 0, NULL);
+ pCertData->issuer = CFArrayCreateMutable(NULL, 0, NULL);
+ pCertData->sha1 = NULL;
+ pCertData->md5 = NULL;
+
+ CFArrayRef pairs = createDescDataPairs(description);
+ CFArrayApplyFunction(pairs, CFRangeMake(0, CFArrayGetCount(pairs)), arrayParseDescDataPair, pCertData);
+ CFArrayApplyFunction(pairs, CFRangeMake(0, CFArrayGetCount(pairs)), arrayDestroyDescData, NULL);
+ CFRelease(pairs);
+ return pCertData;
+}
+
+void arrayDestroyCertName(const void *val, void *context)
+{
+ CertNameRef pCertName = (CertNameRef)val;
+ destroyCertName(pCertName);
+}
+
+void destroyCertData(CertDataRef pCertData)
+{
+ if (pCertData->subject)
+ {
+ CFArrayApplyFunction(pCertData->subject, CFRangeMake(0, CFArrayGetCount(pCertData->subject)), arrayDestroyCertName, NULL);
+ CFRelease(pCertData->subject);
+ }
+
+ if (pCertData->issuer)
+ {
+ CFArrayApplyFunction(pCertData->issuer, CFRangeMake(0, CFArrayGetCount(pCertData->issuer)), arrayDestroyCertName, NULL);
+ CFRelease(pCertData->issuer);
+ }
+
+ if (pCertData->sha1)
+ CFRelease(pCertData->sha1);
+
+ if (pCertData->md5)
+ CFRelease(pCertData->md5);
+
+ free(pCertData);
+}
+
+bool stringArrayMatchesTemplate(CFArrayRef strings, CFArrayRef templateArray)
+{
+ int templateCount, stringCount, i;
+
+ templateCount = CFArrayGetCount(templateArray);
+
+ if (templateCount > 0)
+ {
+ stringCount = CFArrayGetCount(strings);
+ if (stringCount != templateCount)
+ return false;
+
+ for(i = 0;i < stringCount;i++)
+ {
+ CFStringRef str, template;
+
+ template = (CFStringRef)CFArrayGetValueAtIndex(templateArray, i);
+ str = (CFStringRef)CFArrayGetValueAtIndex(strings, i);
+
+ if (CFStringCompareWithOptions(template, str, CFRangeMake(0, CFStringGetLength(template)), kCFCompareCaseInsensitive) != kCFCompareEqualTo)
+ return false;
+ }
+ }
+
+ return true;
+
+}
+
+bool certNameMatchesTemplate(CertNameRef pCertName, CertNameRef pTemplate)
+{
+ if (!stringArrayMatchesTemplate(pCertName->countryName, pTemplate->countryName))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->organization, pTemplate->organization))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->organizationalUnit, pTemplate->organizationalUnit))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->commonName, pTemplate->commonName))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->emailAddress, pTemplate->emailAddress))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->stateName, pTemplate->stateName))
+ return false;
+ else if (!stringArrayMatchesTemplate(pCertName->localityName, pTemplate->localityName))
+ return false;
+ else
+ return true;
+}
+
+bool certNameArrayMatchesTemplate(CFArrayRef certNameArray, CFArrayRef templateArray)
+{
+ int templateCount, certCount, i;
+
+ templateCount = CFArrayGetCount(templateArray);
+
+ if (templateCount > 0)
+ {
+ certCount = CFArrayGetCount(certNameArray);
+ if (certCount != templateCount)
+ return false;
+
+ for(i = 0;i < certCount;i++)
+ {
+ CertNameRef pName, pTemplateName;
+
+ pTemplateName = (CertNameRef)CFArrayGetValueAtIndex(templateArray, i);
+ pName = (CertNameRef)CFArrayGetValueAtIndex(certNameArray, i);
+
+ if (!certNameMatchesTemplate(pName, pTemplateName))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool hexStringMatchesTemplate(CFStringRef str, CFStringRef template)
+{
+ if (template)
+ {
+ if (!str)
+ return false;
+
+ CFMutableStringRef strMutable, templateMutable;
+
+ strMutable = CFStringCreateMutableCopy(NULL, 0, str);
+ templateMutable = CFStringCreateMutableCopy(NULL, 0, template);
+
+ CFStringFindAndReplace(strMutable, kStringSpace, kStringEmpty, CFRangeMake(0, CFStringGetLength(strMutable)), 0);
+ CFStringFindAndReplace(templateMutable, kStringSpace, kStringEmpty, CFRangeMake(0, CFStringGetLength(templateMutable)), 0);
+
+ CFComparisonResult result = CFStringCompareWithOptions(templateMutable, strMutable, CFRangeMake(0, CFStringGetLength(templateMutable)), kCFCompareCaseInsensitive);
+
+ CFRelease(strMutable);
+ CFRelease(templateMutable);
+
+ if (result != kCFCompareEqualTo)
+ return false;
+ }
+
+ return true;
+}
+
+bool certDataMatchesTemplate(CertDataRef pCertData, CertDataRef pTemplate)
+{
+ if (!certNameArrayMatchesTemplate(pCertData->subject, pTemplate->subject))
+ return false;
+
+ if (!certNameArrayMatchesTemplate(pCertData->issuer, pTemplate->issuer))
+ return false;
+
+ if (!hexStringMatchesTemplate(pCertData->sha1, pTemplate->sha1))
+ return false;
+
+ if (!hexStringMatchesTemplate(pCertData->md5, pTemplate->md5))
+ return false;
+
+ return true;
+}
+
+void printString(const void *val, void *context)
+{
+ CFStringRef str = (CFStringRef)val;
+ const char *label = (const char *)context;
+ printf("%s = ", label);
+ printCFString(str);
+ printf("\n");
+}
+
+void printCertName(CertNameRef pCertName)
+{
+ CFArrayApplyFunction(pCertName->countryName, CFRangeMake(0, CFArrayGetCount(pCertName->countryName)), printString, "Country Name");
+ CFArrayApplyFunction(pCertName->organization, CFRangeMake(0, CFArrayGetCount(pCertName->organization)), printString, "Organization");
+ CFArrayApplyFunction(pCertName->organizationalUnit, CFRangeMake(0, CFArrayGetCount(pCertName->organizationalUnit)), printString, "Organizational Unit");
+ CFArrayApplyFunction(pCertName->commonName, CFRangeMake(0, CFArrayGetCount(pCertName->commonName)), printString, "Common Name");
+ CFArrayApplyFunction(pCertName->description, CFRangeMake(0, CFArrayGetCount(pCertName->description)), printString, "Description");
+ CFArrayApplyFunction(pCertName->emailAddress, CFRangeMake(0, CFArrayGetCount(pCertName->emailAddress)), printString, "Email Address");
+ CFArrayApplyFunction(pCertName->stateName, CFRangeMake(0, CFArrayGetCount(pCertName->stateName)), printString, "State Name");
+ CFArrayApplyFunction(pCertName->localityName, CFRangeMake(0, CFArrayGetCount(pCertName->localityName)), printString, "Locality Name");
+ CFArrayApplyFunction(pCertName->distinguishedName, CFRangeMake(0, CFArrayGetCount(pCertName->distinguishedName)), printString, "Distinguished Name");
+}
+
+void arrayPrintCertName(const void *val, void *context)
+{
+ CertNameRef pCertName = (CertNameRef) val;
+ printCertName(pCertName);
+}
+
+void printCertData(CertDataRef pCertData)
+{
+ printf("*** Subject ***\n");
+ if (pCertData->subject)
+ CFArrayApplyFunction(pCertData->subject, CFRangeMake(0, CFArrayGetCount(pCertData->subject)), arrayPrintCertName, NULL);
+
+ printf("*** Issuer ***\n");
+ if (pCertData->issuer)
+ CFArrayApplyFunction(pCertData->issuer, CFRangeMake(0, CFArrayGetCount(pCertData->issuer)), arrayPrintCertName, NULL);
+
+ printf("*** Fingerprints ***\n");
+
+ printf("SHA1 = ");
+ if (pCertData->sha1)
+ printCFString(pCertData->sha1);
+
+ printf("\n");
+
+ printf("MD5 = ");
+ if (pCertData->md5)
+ printCFString(pCertData->md5);
+
+ printf("\n");
+}
+
+SecIdentityRef findIdentity(CertDataRef pCertDataTemplate)
+{
+ SecIdentitySearchRef search;
+ SecIdentitySearchCreate(NULL, 0, &search);
+
+ SecIdentityRef identity;
+ while(SecIdentitySearchCopyNext(search, &identity) != errSecItemNotFound)
+ {
+ SecCertificateRef certificate;
+ SecIdentityCopyCertificate (identity, &certificate);
+
+ CertDataRef pCertData = createCertDataFromCertificate(certificate);
+ bool bMatches = certDataMatchesTemplate(pCertData, pCertDataTemplate);
+
+ destroyCertData(pCertData);
+ CFRelease(certificate);
+
+ if (bMatches)
+ {
+ break;
+ }
+ else
+ {
+ CFRelease(identity);
+ identity = NULL;
+ }
+ }
+
+ CFRelease(search);
+ return identity;
+}
\ No newline at end of file
diff --git a/cert_data.h b/cert_data.h
new file mode 100644
index 0000000..b3092de
--- /dev/null
+++ b/cert_data.h
@@ -0,0 +1,44 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __cert_data_h__
+#define __cert_data_h__
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+
+typedef struct _CertData
+{
+ CFArrayRef subject;
+ CFArrayRef issuer;
+ CFStringRef md5, sha1;
+} CertData, *CertDataRef;
+
+CertDataRef createCertDataFromCertificate(SecCertificateRef certificate);
+CertDataRef createCertDataFromString(const char *description);
+void destroyCertData(CertDataRef pCertData);
+bool certDataMatchesTemplate(CertDataRef pCertData, CertDataRef pTemplate);
+void printCertData(CertDataRef pCertData);
+SecIdentityRef findIdentity(CertDataRef pCertDataTemplate);
+
+#endif
\ No newline at end of file
diff --git a/common_osx.c b/common_osx.c
new file mode 100644
index 0000000..fd39f50
--- /dev/null
+++ b/common_osx.c
@@ -0,0 +1,98 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "common_osx.h"
+
+void printCFString(CFStringRef str)
+{
+ CFIndex bufferLength = CFStringGetLength(str) + 1;
+ char *pBuffer = (char*)malloc(sizeof(char) * bufferLength);
+ CFStringGetCString(str, pBuffer, bufferLength, kCFStringEncodingUTF8);
+ printf("%s", pBuffer);
+ free(pBuffer);
+}
+
+char* cfstringToCstr(CFStringRef str)
+{
+ CFIndex bufferLength = CFStringGetLength(str) + 1;
+ char *pBuffer = (char*)malloc(sizeof(char) * bufferLength);
+ CFStringGetCString(str, pBuffer, bufferLength, kCFStringEncodingUTF8);
+ return pBuffer;
+}
+
+void appendHexChar(CFMutableStringRef str, unsigned char halfByte)
+{
+ if (halfByte < 10)
+ CFStringAppendFormat (str, NULL, CFSTR("%d"), halfByte);
+
+ switch(halfByte)
+ {
+ case 10:
+ CFStringAppendCString(str, "A", kCFStringEncodingUTF8);
+ break;
+ case 11:
+ CFStringAppendCString(str, "B", kCFStringEncodingUTF8);
+ break;
+ case 12:
+ CFStringAppendCString(str, "C", kCFStringEncodingUTF8);
+ break;
+ case 13:
+ CFStringAppendCString(str, "D", kCFStringEncodingUTF8);
+ break;
+ case 14:
+ CFStringAppendCString(str, "E", kCFStringEncodingUTF8);
+ break;
+ case 15:
+ CFStringAppendCString(str, "F", kCFStringEncodingUTF8);
+ break;
+ }
+}
+
+CFStringRef createHexString(unsigned char *pData, int length)
+{
+ unsigned char byte, low, high;
+ int i;
+ CFMutableStringRef str = CFStringCreateMutable(NULL, 0);
+
+ for(i = 0;i < length;i++)
+ {
+ byte = pData[i];
+ low = byte & 0x0F;
+ high = (byte >> 4);
+
+ appendHexChar(str, high);
+ appendHexChar(str, low);
+
+ if (i != (length - 1))
+ CFStringAppendCString(str, " ", kCFStringEncodingUTF8);
+ }
+
+ return str;
+}
+
+void printHex(unsigned char *pData, int length)
+{
+ CFStringRef hexStr = createHexString(pData, length);
+ printCFString(hexStr);
+ CFRelease(hexStr);
+}
\ No newline at end of file
diff --git a/common_osx.h b/common_osx.h
new file mode 100644
index 0000000..7cf6582
--- /dev/null
+++ b/common_osx.h
@@ -0,0 +1,35 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __common_osx_h__
+#define __common_osx_h__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+void printCFString(CFStringRef str);
+char* cfstringToCstr(CFStringRef str);
+CFStringRef createHexString(unsigned char *pData, int length);
+void printHex(unsigned char *pData, int length);
+
+#endif //__Common_osx_h__
\ No newline at end of file
diff --git a/configure.ac b/configure.ac
index a6b3019..114b6f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,7 +42,11 @@ AC_ARG_WITH(cygwin-native,
WIN32="no"
CYGWIN="no"
+APPLE="no"
case "${host}" in
+ *darwin*)
+ APPLE="yes"
+ ;;
*-mingw*)
WIN32="yes"
cross_compiling="yes"
@@ -335,6 +339,8 @@ case "$host" in
dnl some Mac OS X tendering (we use vararg macros...)
AC_DEFINE(TARGET_DARWIN, 1, [Are we running on Mac OS X?])
CPPFLAGS="$CPPFLAGS -no-cpp-precomp"
+ CFLAGS="$CFLAGS -D__APPLE__"
+ LDFLAGS="$LDFLAGS -framework CoreFoundation -framework Security"
;;
*-mingw*)
AC_DEFINE(TARGET_WIN32, 1, [Are we running WIN32?])
@@ -962,6 +968,7 @@ AC_SUBST(TAP_WIN32_MIN_MINOR)
win32datadir="\${datadir}/${PACKAGE}-win32"
AC_SUBST(win32datadir)
AM_CONDITIONAL(WIN32, test "${WIN32}" = "yes")
+AM_CONDITIONAL(APPLE, test "${APPLE}" = "yes")
# workaround for <autoconf-2.60
if test -z "${docdir}"; then
diff --git a/crypto_osx.c b/crypto_osx.c
new file mode 100644
index 0000000..3d137e4
--- /dev/null
+++ b/crypto_osx.c
@@ -0,0 +1,75 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "crypto_osx.h"
+#include <CommonCrypto/CommonDigest.h>
+
+CSSM_DATA signData(SecIdentityRef identity, CSSM_DATA dataBuf)
+{
+ SecKeyRef privateKey;
+
+ SecIdentityCopyPrivateKey(identity, &privateKey);
+ const CSSM_ACCESS_CREDENTIALS *pCredentials;
+ SecKeyGetCredentials(privateKey, CSSM_ACL_AUTHORIZATION_SIGN, kSecCredentialTypeDefault, &pCredentials);
+
+ CSSM_CSP_HANDLE cspHandle;
+ SecKeyGetCSPHandle(privateKey, &cspHandle);
+
+ const CSSM_KEY *pCssmKey;
+ SecKeyGetCSSMKey (privateKey, &pCssmKey);
+
+ CSSM_DATA signBuf;
+ signBuf.Data = NULL;
+ signBuf.Length = 0;
+
+ if (!(pCssmKey->KeyHeader.KeyUsage & CSSM_KEYUSE_SIGN))
+ {
+ CFRelease(privateKey);
+ return signBuf;
+ }
+
+ CSSM_CC_HANDLE cryptoContextHandle;
+ CSSM_CSP_CreateSignatureContext(cspHandle, CSSM_ALGID_RSA, pCredentials, pCssmKey, &cryptoContextHandle);
+
+ CSSM_SignData(cryptoContextHandle, &dataBuf, 1, CSSM_ALGID_NONE, &signBuf);
+
+ CSSM_DeleteContext(cryptoContextHandle);
+ CFRelease(privateKey);
+ return signBuf;
+}
+
+void freeSignature(SecIdentityRef identity, CSSM_DATA sigBuf)
+{
+ SecKeyRef privateKey;
+
+ SecIdentityCopyPrivateKey(identity, &privateKey);
+
+ CSSM_CSP_HANDLE cspHandle;
+ SecKeyGetCSPHandle(privateKey, &cspHandle);
+
+ CSSM_API_MEMORY_FUNCS memFuncs;
+ CSSM_GetAPIMemoryFunctions(cspHandle, &memFuncs);
+
+ memFuncs.free_func(sigBuf.Data, memFuncs.AllocRef);
+}
diff --git a/crypto_osx.h b/crypto_osx.h
new file mode 100644
index 0000000..858e130
--- /dev/null
+++ b/crypto_osx.h
@@ -0,0 +1,34 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2010 Brian Raderman <brian@irregularexpression.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __crypto_osx_h__
+#define __crypto_osx_h__
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+
+CSSM_DATA signData(SecIdentityRef identity, CSSM_DATA dataBuf);
+void freeSignature(SecIdentityRef identity, CSSM_DATA sigBuf);
+
+#endif //__crypto_osx_h__
\ No newline at end of file
diff --git a/keychain.c b/keychain.c
new file mode 100644
index 0000000..d7c4d52
--- /dev/null
+++ b/keychain.c
@@ -0,0 +1,274 @@
+/*
+ * File: keyhchain.c. Original was named cryptoapi.c.
+ *
+ * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modifi-
+ * cation, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright no-
+ * tice, this list of conditions and the following disclaimer in the do-
+ * cumentation and/or other materials provided with the distribution.
+ *
+ * o The names of the contributors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI-
+ * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
+ * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
+ * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
+ * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This code has been modified from the original version by Brian Raderman
+ * <brian@irregularexpression.org>. It was changed to work with the Mac OSX
+ * Keychain services instead of the Microsoft Crypto API, which was its original
+ * intent.
+ */
+
+#include "syshead.h"
+
+#if defined(__APPLE__) && defined(USE_CRYPTO) && defined(USE_SSL)
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <assert.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include "cert_data.h"
+#include "common_osx.h"
+#include "crypto_osx.h"
+
+/* Size of an SSL signature: MD5+SHA1 */
+#define SSL_SIG_LENGTH 36
+
+/* try to funnel any Keychain/CSSM error messages to OpenSSL ERR_... */
+#define ERR_LIB_KEYCHAIN (ERR_LIB_USER + 70)
+#define KeychainErr(f) err_put_apple_error((f), __FILE__, __LINE__)
+#define KEYCHAIN_F_FIND_IDENTITY 101
+#define KEYCHAIN_F_CREATE_CERT_DATA_FROM_STRING 102
+#define KEYCHAIN_F_SIGN_DATA 103
+
+static ERR_STRING_DATA KEYCHAIN_str_functs[] = {
+ { ERR_PACK(ERR_LIB_KEYCHAIN, 0, 0), "Mac OS X Keychain"},
+ { ERR_PACK(0, KEYCHAIN_F_FIND_IDENTITY, 0), "findIdentity" },
+ { ERR_PACK(0, KEYCHAIN_F_CREATE_CERT_DATA_FROM_STRING, 0), "createCertDataFromString" },
+ { ERR_PACK(0, KEYCHAIN_F_SIGN_DATA, 0), "signData" },
+ { 0, NULL }
+};
+
+static void err_put_apple_error(int func, const char *file, int line)
+{
+ static int init = 0;
+
+ if (!init) {
+ ERR_load_strings(ERR_LIB_KEYCHAIN, KEYCHAIN_str_functs);
+
+ ERR_STRING_DATA *esd = calloc(4, sizeof(ERR_STRING_DATA));
+ if (esd)
+ {
+ esd[0].error = 101;
+ esd[0].string = "Unable to find identity in keychain (certificate + private key)";
+
+ esd[1].error = 102;
+ esd[1].string = "Unable to parse certificate description string";
+
+ esd[2].error = 103;
+ esd[2].string = "Unable to sign data with private key";
+
+ ERR_load_strings(ERR_LIB_KEYCHAIN, esd);
+ }
+
+ init++;
+ }
+
+ ERR_PUT_error(ERR_LIB_KEYCHAIN, func, 0, file, line);
+}
+
+/* encrypt */
+static int rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+ return 0;
+}
+
+/* verify arbitrary data */
+static int rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+ return 0;
+}
+
+/* sign arbitrary data */
+static int rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ SecIdentityRef identity = (SecIdentityRef) rsa->meth->app_data;
+ int length;
+
+ if (!identity)
+ {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+
+ if (padding != RSA_PKCS1_PADDING)
+ {
+ RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+ return 0;
+ }
+
+ CSSM_DATA fromData;
+ fromData.Data = (uint8*)from;
+ fromData.Length = flen;
+ CSSM_DATA sigBuf = signData(identity, fromData);
+ length = sigBuf.Length;
+ memcpy(to, sigBuf.Data, sigBuf.Length);
+ freeSignature(identity, sigBuf);
+ return length;
+}
+
+/* decrypt */
+static int rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* I haven't been able to trigger this one, but I want to know if it happens... */
+ assert(0);
+ return 0;
+}
+
+/* called at RSA_new */
+static int init(RSA *rsa)
+{
+ return 0;
+}
+
+/* called at RSA_free */
+static int finish(RSA *rsa)
+{
+ SecIdentityRef identity = (SecIdentityRef) rsa->meth->app_data;
+
+ if (!identity)
+ return 0;
+
+ CFRelease(identity);
+ free((char *) rsa->meth);
+ rsa->meth = NULL;
+ return 1;
+}
+
+int SSL_CTX_use_Keychain_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
+{
+ X509 *cert = NULL;
+ RSA *rsa = NULL, *pub_rsa;
+ RSA_METHOD *my_rsa_method = calloc(1, sizeof(RSA_METHOD));
+ SecIdentityRef identity = NULL;
+
+ if (my_rsa_method == NULL)
+ {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ CertDataRef pCertDataTemplate = createCertDataFromString(cert_prop);
+ identity = findIdentity(pCertDataTemplate);
+ destroyCertData(pCertDataTemplate);
+
+ if (!identity)
+ {
+ KeychainErr(KEYCHAIN_F_FIND_IDENTITY);
+ goto err;
+ }
+
+ CSSM_DATA cssmCertData;
+ SecCertificateRef certificate;
+
+ SecIdentityCopyCertificate(identity, &certificate);
+ SecCertificateGetData (certificate, &cssmCertData);
+ CFRelease(certificate);
+
+ /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
+ cert = d2i_X509(NULL, (unsigned char **) &cssmCertData.Data, cssmCertData.Length);
+ if (cert == NULL)
+ {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
+ goto err;
+ }
+
+ my_rsa_method->name = "Mac OS X Keychain RSA Method";
+ my_rsa_method->rsa_pub_enc = rsa_pub_enc;
+ my_rsa_method->rsa_pub_dec = rsa_pub_dec;
+ my_rsa_method->rsa_priv_enc = rsa_priv_enc;
+ my_rsa_method->rsa_priv_dec = rsa_priv_dec;
+ /* my_rsa_method->init = init; */
+ my_rsa_method->finish = finish;
+ my_rsa_method->flags = RSA_METHOD_FLAG_NO_CHECK;
+ my_rsa_method->app_data = (char *) identity;
+
+ rsa = RSA_new();
+ if (rsa == NULL)
+ {
+ SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(),
+ * so we do it here then... */
+ if (!SSL_CTX_use_certificate(ssl_ctx, cert))
+ goto err;
+
+ /* the public key */
+ pub_rsa = cert->cert_info->key->pkey->pkey.rsa;
+ /* SSL_CTX_use_certificate() increased the reference count in 'cert', so
+ * we decrease it here with X509_free(), or it will never be cleaned up. */
+ X509_free(cert);
+ cert = NULL;
+
+ /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */
+ /* rsa->n indicates the key size */
+ rsa->n = BN_dup(pub_rsa->n);
+ rsa->flags |= RSA_FLAG_EXT_PKEY;
+ if (!RSA_set_method(rsa, my_rsa_method))
+ goto err;
+
+ if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
+ goto err;
+
+ /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
+ * we decrease it here with RSA_free(), or it will never be cleaned up. */
+ RSA_free(rsa);
+ return 1;
+
+err:
+ if (cert)
+ X509_free(cert);
+
+ if (rsa)
+ {
+ RSA_free(rsa);
+ }
+ else
+ {
+ if (my_rsa_method)
+ free(my_rsa_method);
+
+ if (identity)
+ CFRelease(identity);
+ }
+ return 0;
+}
+
+#else
+static void dummy1 (void) {}
+#endif /* __APPLE__ */
\ No newline at end of file
diff --git a/keychain.h b/keychain.h
new file mode 100644
index 0000000..84a1936
--- /dev/null
+++ b/keychain.h
@@ -0,0 +1,6 @@
+#ifndef _OSXKEYCHAIN_H_
+#define _OSXKEYCHAIN_H_
+
+int SSL_CTX_use_Keychain_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);
+
+#endif /* !_OSXKEYCHAIN_H_ */
diff --git a/openvpn.8 b/openvpn.8
index b8594e1..6fed3ff 100644
--- a/openvpn.8
+++ b/openvpn.8
@@ -4116,6 +4116,63 @@ Mode is encoded as hex number, and can be a mask one of the following:
.br
.\"*********************************************************
.TP
+.B --keychaincert select-string
+Load the certificate and private key from the
+Mac OS X Keychain (Mac OS X Only).
+
+Use this option instead of
+.B --cert
+and
+.B --key.
+
+This makes it possible to use any smart card supported by Mac OSX using the tokend interface, but also any
+kind of certificate, residing in the Keychain, where you have access to
+the private key. This option has been tested on the client side with an Aladdin eToken
+on Mac OSX Leopard and with software certificates stored in the Keychain on Mac OSX Leopard and
+Mac OSX Snow Leopard. As of this writing (4/27/10) Aladdin has not yet released Snow Leopard drivers.
+
+To select a certificate based on a string search in the
+certificate's subject and/or issuer:
+
+.B keychaincert
+"SUBJECT:c=US/o=Apple Inc./ou=me.com/cn=username ISSUER:c=US/o=Apple Computer, Inc./ou=Apple Computer Certificate Authority/cn=Apple .Mac Certificate Authority"
+
+.I "Distinguished Name Component Abbreviations:"
+.br
+o = organization
+.br
+ou = organizational unit
+.br
+c = country
+.br
+l = locality
+.br
+st = state
+.br
+cn = common name
+.br
+e = email
+.br
+
+All of the distinguished name components are optional, although you do need to specify at least one of them. You can
+add spaces around the '/' and '=' characters, e.g. "SUBJECT: c = US / o = Apple Inc.". You do not need to specify
+both the subject and the issuer, one or the other will work fine. The identity searching algorithm will return the first
+certificate it finds that matches all of the criteria you have specified. You can also include the MD5 and/or SHA1 thumbprints
+along with the subject and issuer (see example of thumbprint search criteria below).
+
+To select a certificate based on certificate's MD5 or SHA1 thumbprint:
+
+.B keychaincert
+"SHA1: 30 F7 3A 7A B7 73 2A 98 54 33 4A A7 00 6F 6E AC EC D1 EF 02 MD5: D5 F5 11 F1 38 EB 5F 4D CF 23 B6 94 E8 33 D8 B5"
+
+Again, you can include both the SHA1 and the MD5 thumbprints, but you can also use just one of them. The thumbprint hex
+strings can easily be copy-and-pasted from the OSX Keychain Access GUI in the Applications/Utilities folder. The hex string comparison is
+not case sensitive.
+
+Note that this option cannot be used with the --daemon option. Mac OS X needs to present the user with an authentication GUI when the Keychain
+is accessed by openvpn and the --daemon option will prevent the GUI from being shown and therefore cause the authentication to silently fail.
+.\"*********************************************************
+.TP
.B \-\-cryptoapicert select-string
Load the certificate and private key from the
Windows Certificate System Store (Windows/OpenSSL Only).
diff --git a/options.c b/options.c
index 39e7a57..0bcf1bd 100644
--- a/options.c
+++ b/options.c
@@ -570,6 +570,10 @@ static const char usage_message[] =
"--cryptoapicert select-string : Load the certificate and private key from the\n"
" Windows Certificate System Store.\n"
#endif
+#ifdef __APPLE__
+ "--keychaincert select-string : Load the certificate and private key from the\n"
+ " Mac OS X Keychain.\n"
+#endif
"--tls-cipher l : A list l of allowable TLS ciphers separated by : (optional).\n"
" : Use --show-tls to see a list of supported TLS ciphers.\n"
"--tls-timeout n : Packet retransmit timeout on TLS control channel\n"
@@ -1579,6 +1583,9 @@ show_settings (const struct options *o)
#ifdef ENABLE_CRYPTOAPI
SHOW_STR (cryptoapi_cert);
#endif
+#ifdef __APPLE__
+ SHOW_STR (keychain_cert);
+#endif
SHOW_STR (cipher_list);
SHOW_STR (tls_verify);
SHOW_STR (tls_export_cert);
@@ -2259,6 +2266,9 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
#ifdef ENABLE_CRYPTOAPI
if (options->cryptoapi_cert)
msg(M_USAGE, "Parameter --cryptoapicert cannot be used when --pkcs11-provider is also specified.");
+#elif defined(__APPLE__)
+ if (options->keychain_cert)
+ msg(M_USAGE, "Parameter --keychaincert cannot be used when --pkcs11-provider is also specified.");
#endif
}
else
@@ -2276,6 +2286,21 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
msg(M_USAGE, "Parameter --pkcs12 cannot be used when --cryptoapicert is also specified.");
}
else
+#elif defined(__APPLE__)
+ if (options->keychain_cert)
+ {
+ if ((!(options->ca_file)) && (!(options->ca_path)))
+ msg(M_USAGE, "You must define CA file (--ca) or CA path (--capath)");
+ if (options->cert_file)
+ msg(M_USAGE, "Parameter --cert cannot be used when --keychaincert is also specified.");
+ if (options->priv_key_file)
+ msg(M_USAGE, "Parameter --key cannot be used when --keychaincert is also specified.");
+ if (options->pkcs12_file)
+ msg(M_USAGE, "Parameter --pkcs12 cannot be used when --keychaincert is also specified.");
+ if (options->daemon)
+ msg(M_USAGE, "Parameter --daemon cannot be used when --keychaincert is also specified.");
+ }
+ else
#endif
if (options->pkcs12_file)
{
@@ -6250,6 +6275,12 @@ add_option (struct options *options,
VERIFY_PERMISSION (OPT_P_GENERAL);
options->cryptoapi_cert = p[1];
}
+#elif defined(__APPLE__)
+ else if (streq (p[0], "keychaincert") && p[1])
+ {
+ VERIFY_PERMISSION (OPT_P_GENERAL);
+ options->keychain_cert = p[1];
+ }
#endif
else if (streq (p[0], "key") && p[1])
{
diff --git a/options.h b/options.h
index abdddba..2d2181b 100644
--- a/options.h
+++ b/options.h
@@ -557,6 +557,8 @@ struct options
#ifdef ENABLE_CRYPTOAPI
const char *cryptoapi_cert;
+#elif defined(__APPLE__)
+ const char *keychain_cert;
#endif
/* data channel key exchange method */
diff --git a/ssl.c b/ssl.c
index 30f6eb2..98d72de 100644
--- a/ssl.c
+++ b/ssl.c
@@ -66,6 +66,10 @@
#include "cryptoapi.h"
#endif
+#ifdef __APPLE__
+#include "keychain.h"
+#endif
+
#include "memdbg.h"
#ifndef ENABLE_OCC
--
1.7.6
@christopher5106
Copy link

Where is the original code version this DIFF is compared with ? thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment