From 20c8675ba46bda97330a4117c459a59a9f1c465e Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Iniesta Date: Mon, 21 Nov 2016 09:37:33 +0100 Subject: New upstream version 2.4~beta1 --- contrib/keychain-mcd/Makefile | 13 + contrib/keychain-mcd/cert_data.c | 734 ++++++++++++++++++++++++++++++++++++ contrib/keychain-mcd/cert_data.h | 46 +++ contrib/keychain-mcd/common_osx.c | 94 +++++ contrib/keychain-mcd/common_osx.h | 36 ++ contrib/keychain-mcd/crypto_osx.c | 75 ++++ contrib/keychain-mcd/crypto_osx.h | 44 +++ contrib/keychain-mcd/keychain-mcd.8 | 161 ++++++++ contrib/keychain-mcd/main.c | 255 +++++++++++++ contrib/pull-resolv-conf/client.up | 13 +- 10 files changed, 1466 insertions(+), 5 deletions(-) create mode 100644 contrib/keychain-mcd/Makefile create mode 100644 contrib/keychain-mcd/cert_data.c create mode 100644 contrib/keychain-mcd/cert_data.h create mode 100644 contrib/keychain-mcd/common_osx.c create mode 100644 contrib/keychain-mcd/common_osx.h create mode 100644 contrib/keychain-mcd/crypto_osx.c create mode 100644 contrib/keychain-mcd/crypto_osx.h create mode 100644 contrib/keychain-mcd/keychain-mcd.8 create mode 100644 contrib/keychain-mcd/main.c (limited to 'contrib') diff --git a/contrib/keychain-mcd/Makefile b/contrib/keychain-mcd/Makefile new file mode 100644 index 0000000..c6431df --- /dev/null +++ b/contrib/keychain-mcd/Makefile @@ -0,0 +1,13 @@ +CFILES = cert_data.c common_osx.c crypto_osx.c main.c +OFILES = $(CFILES:.c=.o) ../../src/openvpn/base64.o +prog = keychain-mcd + +CC = gcc +CFLAGS = -Wall +LDFLAGS = -framework CoreFoundation -framework Security -framework CoreServices + +$(prog): $(OFILES) + $(CC) $(LDFLAGS) $(OFILES) -o $(prog) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ diff --git a/contrib/keychain-mcd/cert_data.c b/contrib/keychain-mcd/cert_data.c new file mode 100644 index 0000000..a04bf79 --- /dev/null +++ b/contrib/keychain-mcd/cert_data.c @@ -0,0 +1,734 @@ +/* + * 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 + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 "cert_data.h" +#include +#include + +#include "common_osx.h" +#include "crypto_osx.h" +#include + +CFStringRef kCertDataSubjectName = CFSTR("subject"), + kCertDataIssuerName = CFSTR("issuer"), + kCertDataSha1Name = CFSTR("SHA1"), + kCertDataMd5Name = CFSTR("MD5"), + kCertDataSerialName = CFSTR("serial"), + 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"); +CFStringRef kStringSpace = CFSTR(" "), + kStringEmpty = CFSTR(""); + +typedef struct _CertName +{ + CFArrayRef countryName, organization, organizationalUnit, commonName, description, emailAddress, + stateName, localityName; +} CertName, *CertNameRef; + +typedef struct _DescData +{ + CFStringRef name, value; +} DescData, *DescDataRef; + +void destroyDescData(DescDataRef pData); + +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); + 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); + free(pCertName); +} + +bool CFStringRefCmpCString(CFStringRef cfstr, const char *str) +{ + CFStringRef tmp = CFStringCreateWithCStringNoCopy(NULL, str, kCFStringEncodingUTF8, kCFAllocatorNull); + CFComparisonResult cresult = CFStringCompare(cfstr, tmp, 0); + bool result = cresult == kCFCompareEqualTo; + CFRelease(tmp); + return result; +} + +CFDateRef GetDateFieldFromCertificate(SecCertificateRef certificate, CFTypeRef oid) +{ + const void *keys[] = { oid }; + CFDictionaryRef dict = NULL; + CFErrorRef error; + CFDateRef date = NULL; + + CFArrayRef keySelection = CFArrayCreate(NULL, keys , sizeof(keys)/sizeof(keys[0]), &kCFTypeArrayCallBacks); + dict = SecCertificateCopyValues(certificate, keySelection, &error); + if (dict == NULL) + { + printErrorMsg("GetDateFieldFromCertificate: SecCertificateCopyValues", error); + goto release_ks; + } + CFDictionaryRef vals = dict ? CFDictionaryGetValue(dict, oid) : NULL; + CFNumberRef vals2 = vals ? CFDictionaryGetValue(vals, kSecPropertyKeyValue) : NULL; + if (vals2 == NULL) + goto release_dict; + + CFAbsoluteTime validityNotBefore; + if (CFNumberGetValue(vals2, kCFNumberDoubleType, &validityNotBefore)) + date = CFDateCreate(kCFAllocatorDefault,validityNotBefore); + +release_dict: + CFRelease(dict); +release_ks: + CFRelease(keySelection); + return date; +} + +CFArrayRef GetFieldsFromCertificate(SecCertificateRef certificate, CFTypeRef oid) +{ + CFMutableArrayRef fields = CFArrayCreateMutable(NULL, 0, NULL); + CertNameRef pCertName = createCertName(); + const void* keys[] = { oid, }; + CFDictionaryRef dict; + CFErrorRef error; + + CFArrayRef keySelection = CFArrayCreate(NULL, keys , 1, NULL); + + dict = SecCertificateCopyValues(certificate, keySelection, &error); + if (dict == NULL) { + printErrorMsg("GetFieldsFromCertificate: SecCertificateCopyValues", error); + CFRelease(keySelection); + CFRelease(fields); + destroyCertName(pCertName); + return NULL; + } + CFDictionaryRef vals = CFDictionaryGetValue(dict, oid); + CFArrayRef vals2 = vals ? CFDictionaryGetValue(vals, kSecPropertyKeyValue) : NULL; + if (vals2) + { + for(int i = 0; i < CFArrayGetCount(vals2); i++) { + CFDictionaryRef subDict = CFArrayGetValueAtIndex(vals2, i); + CFStringRef label = CFDictionaryGetValue(subDict, kSecPropertyKeyLabel); + CFStringRef value = CFDictionaryGetValue(subDict, kSecPropertyKeyValue); + + if (CFStringCompare(label, kSecOIDEmailAddress, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->emailAddress, value); + else if (CFStringCompare(label, kSecOIDCountryName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->countryName, value); + else if (CFStringCompare(label, kSecOIDOrganizationName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->organization, value); + else if (CFStringCompare(label, kSecOIDOrganizationalUnitName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->organizationalUnit, value); + else if (CFStringCompare(label, kSecOIDCommonName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->commonName, value); + else if (CFStringCompare(label, kSecOIDDescription, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->description, value); + else if (CFStringCompare(label, kSecOIDStateProvinceName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->stateName, value); + else if (CFStringCompare(label, kSecOIDLocalityName, 0) == kCFCompareEqualTo) + CFArrayAppendValue((CFMutableArrayRef)pCertName->localityName, value); + } + CFArrayAppendValue(fields, pCertName); + } + + CFRelease(dict); + CFRelease(keySelection); + return fields; +} + +CertDataRef createCertDataFromCertificate(SecCertificateRef certificate) +{ + CertDataRef pCertData = (CertDataRef)malloc(sizeof(CertData)); + pCertData->subject = GetFieldsFromCertificate(certificate, kSecOIDX509V1SubjectName); + pCertData->issuer = GetFieldsFromCertificate(certificate, kSecOIDX509V1IssuerName); + + CFDataRef data = SecCertificateCopyData(certificate); + if (data == NULL) + { + warnx("SecCertificateCopyData() returned NULL"); + destroyCertData(pCertData); + return NULL; + } + + unsigned char sha1[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1(CFDataGetBytePtr(data), CFDataGetLength(data), sha1); + pCertData->sha1 = createHexString(sha1, CC_SHA1_DIGEST_LENGTH); + + unsigned char md5[CC_MD5_DIGEST_LENGTH]; + CC_MD5(CFDataGetBytePtr(data), CFDataGetLength(data), md5); + pCertData->md5 = createHexString((unsigned char*)md5, CC_MD5_DIGEST_LENGTH); + + CFDataRef serial = SecCertificateCopySerialNumber(certificate, NULL); + pCertData->serial = createHexString((unsigned char *)CFDataGetBytePtr(serial), CFDataGetLength(serial)); + CFRelease(serial); + + 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); + +#if 0 + fprintf(stderr, "name = '%s', value = '%s'\n", + CFStringGetCStringPtr(pRetVal->name, kCFStringEncodingUTF8), + CFStringGetCStringPtr(pRetVal->value, kCFStringEncodingUTF8)); +#endif + 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); +} + + +int parseNameComponent(CFStringRef dn, CFStringRef *pName, CFStringRef *pValue) +{ + CFArrayRef nameStrings = CFStringCreateArrayBySeparatingStrings(NULL, dn, kCertNameEquals); + + *pName = *pValue = NULL; + + if (CFArrayGetCount(nameStrings) != 2) + return 0; + + 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); + return 1; +} + +int tryAppendSingleCertField(CertNameRef pCertName, CFArrayRef where, CFStringRef key, + CFStringRef name, CFStringRef value) +{ + if (CFStringCompareWithOptions(name, key, CFRangeMake(0, CFStringGetLength(name)), kCFCompareCaseInsensitive) + == kCFCompareEqualTo) { + CFArrayAppendValue((CFMutableArrayRef)where, value); + return 1; + } + return 0; +} + +int appendCertField(CertNameRef pCert, CFStringRef name, CFStringRef value) +{ + struct { + CFArrayRef field; + CFStringRef key; + } fields[] = { + { pCert->organization, kCertNameOrganization}, + { pCert->organizationalUnit, kCertNameOrganizationalUnit}, + { pCert->countryName, kCertNameCountry}, + { pCert->localityName, kCertNameLocality}, + { pCert->stateName, kCertNameState}, + { pCert->commonName, kCertNameCommonName}, + { pCert->emailAddress, kCertNameEmail}, + }; + int i; + int ret = 0; + + for (i=0; iname || !pDescData->value) + return 0; + + if (CFStringCompareWithOptions(pDescData->name, kCertDataSubjectName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + ret = parseCertName(pDescData->value, (CFMutableArrayRef)pCertData->subject); + else if (CFStringCompareWithOptions(pDescData->name, kCertDataIssuerName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + ret = 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); + else if (CFStringCompareWithOptions(pDescData->name, kCertDataSerialName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + pCertData->serial = CFRetain(pDescData->value); + else + return 0; + + return ret; +} + +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; + pCertData->serial = NULL; + + CFArrayRef pairs = createDescDataPairs(description); + for (int i=0; isubject) + { + 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); + + if (pCertData->serial) + CFRelease(pCertData->serial); + + 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; + + if (!hexStringMatchesTemplate(pCertData->serial, pTemplate->serial)) + return false; + + return true; +} + +bool certExpired(SecCertificateRef certificate) +{ + bool result; + CFDateRef notAfter = GetDateFieldFromCertificate(certificate, kSecOIDX509V1ValidityNotAfter); + CFDateRef notBefore = GetDateFieldFromCertificate(certificate, kSecOIDX509V1ValidityNotBefore); + CFDateRef now = CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent()); + + if (!notAfter || !notBefore || !now) + { + warnx("GetDateFieldFromCertificate() returned NULL"); + result = true; + } + else + { + if (CFDateCompare(notBefore, now, NULL) != kCFCompareLessThan || + CFDateCompare(now, notAfter, NULL) != kCFCompareLessThan) + result = true; + else + result = false; + } + + CFRelease(notAfter); + CFRelease(notBefore); + CFRelease(now); + return result; +} + +SecIdentityRef findIdentity(CertDataRef pCertDataTemplate) +{ + const void *keys[] = { + kSecClass, + kSecReturnRef, + kSecMatchLimit + }; + const void *values[] = { + kSecClassIdentity, + kCFBooleanTrue, + kSecMatchLimitAll + }; + CFArrayRef result = NULL; + + CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, + sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + OSStatus status = SecItemCopyMatching(query, (CFTypeRef*)&result); + CFRelease(query); + if (status != noErr) + { + warnx ("No identities in keychain found"); + return NULL; + } + + SecIdentityRef bestIdentity = NULL; + CFDateRef bestNotBeforeDate = NULL; + + for (int i=0; i + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 +#include + +typedef struct _CertData +{ + CFArrayRef subject; + CFArrayRef issuer; + CFStringRef serial; + 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 diff --git a/contrib/keychain-mcd/common_osx.c b/contrib/keychain-mcd/common_osx.c new file mode 100644 index 0000000..3effa8b --- /dev/null +++ b/contrib/keychain-mcd/common_osx.c @@ -0,0 +1,94 @@ +/* + * 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 + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 "config.h" +#include "syshead.h" +#include "common.h" +#include "buffer.h" +#include "error.h" +*/ + +#include "common_osx.h" +#include + +void printCFString(CFStringRef str) +{ + CFIndex bufferLength = CFStringGetLength(str) + 1; + char *pBuffer = (char*)malloc(sizeof(char) * bufferLength); + CFStringGetCString(str, pBuffer, bufferLength, kCFStringEncodingUTF8); + warnx("%s\n", 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); + } + else + { + char tmp[2] = {'A'+halfByte-10, 0}; + CFStringAppendCString(str, tmp, kCFStringEncodingUTF8); + } +} + +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); +} diff --git a/contrib/keychain-mcd/common_osx.h b/contrib/keychain-mcd/common_osx.h new file mode 100644 index 0000000..4273548 --- /dev/null +++ b/contrib/keychain-mcd/common_osx.h @@ -0,0 +1,36 @@ +/* + * 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 + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 + +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__ diff --git a/contrib/keychain-mcd/crypto_osx.c b/contrib/keychain-mcd/crypto_osx.c new file mode 100644 index 0000000..87ba09b --- /dev/null +++ b/contrib/keychain-mcd/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 + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 +#include +#include + +#include "crypto_osx.h" +#include + +void printErrorMsg(const char *func, CFErrorRef error) +{ + CFStringRef desc = CFErrorCopyDescription(error); + warnx("%s failed: %s", func, CFStringGetCStringPtr(desc, kCFStringEncodingUTF8)); + CFRelease(desc); +} + +void printErrorStatusMsg(const char *func, OSStatus status) +{ + CFStringRef error; + error = SecCopyErrorMessageString(status, NULL); + if (error) + { + warnx("%s failed: %s", func, CFStringGetCStringPtr(error, kCFStringEncodingUTF8)); + CFRelease(error); + } + else + warnx("%s failed: %X", func, (int)status); +} + +void signData(SecIdentityRef identity, const uint8_t *from, int flen, uint8_t *to, size_t *tlen) +{ + SecKeyRef privateKey = NULL; + OSStatus status; + + status = SecIdentityCopyPrivateKey(identity, &privateKey); + if (status != noErr) + { + printErrorStatusMsg("signData: SecIdentityCopyPrivateKey", status); + *tlen = 0; + return; + } + + status = SecKeyRawSign(privateKey, kSecPaddingPKCS1, from, flen, to, tlen); + CFRelease(privateKey); + if (status != noErr) + { + printErrorStatusMsg("signData: SecKeyRawSign", status); + *tlen = 0; + return; + } +} diff --git a/contrib/keychain-mcd/crypto_osx.h b/contrib/keychain-mcd/crypto_osx.h new file mode 100644 index 0000000..0da58b6 --- /dev/null +++ b/contrib/keychain-mcd/crypto_osx.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 + * Copyright (C) 2013-2015 Vasily Kulikov + * + * 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 +#include + +extern OSStatus SecKeyRawSign ( + SecKeyRef key, + SecPadding padding, + const uint8_t *dataToSign, + size_t dataToSignLen, + uint8_t *sig, + size_t *sigLen +); + +void signData(SecIdentityRef identity, const uint8_t *from, int flen, uint8_t *to, size_t *tlen); +void printErrorMsg(const char *func, CFErrorRef error); + +#endif //__crypto_osx_h__ diff --git a/contrib/keychain-mcd/keychain-mcd.8 b/contrib/keychain-mcd/keychain-mcd.8 new file mode 100644 index 0000000..676b164 --- /dev/null +++ b/contrib/keychain-mcd/keychain-mcd.8 @@ -0,0 +1,161 @@ +.TH keychain-mcd 8 +.SH NAME + +keychain-mcd \- Mac OS X Keychain management daemon for OpenVPN + +.SH SYNOPSIS + +.B keychain-mcd +.I identity-template management-server-ip management-server-port +[ +.I password-file +] + +.SH DESCRIPTION + +.B keychain-mcd +is Mac OS X Keychain management daemon for OpenVPN. +It loads the certificate and private key from the Mac OSX Keychain (Mac OSX Only). +.B keychain-mcd +connects to OpenVPN via management interface and handles +certificate and private key commands (namely +.B NEED-CERTIFICATE +and +.B RSA-SIGN +commands). + +.B keychain-mcd +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 OS X. + +Note that Mac OS X might need to present the user with an authentication GUI when the Keychain +is accessed by keychain-mcd. + +Use +.B keychain-mcd +along with +.B --management-external-key +and/or +.B --management-external-cert +passed to +.B openvpn. + +.SH OPTIONS + +.TP +.BR identity-template + +A select string which is used to choose a keychain identity from +Mac OS X Keychain or +.I auto +if the identity template is passed from openvpn. + +\fBSubject\fR, \fBIssuer\fR, \fBSerial\fR, \fBSHA1\fR, \fBMD5\fR selectors can be used. + +To select a certificate based on a string search in the +certificate's subject and/or issuer: + +.nf + +"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" + +.fi + +.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 +certificate it finds that matches all of the criteria you have specified. +If there are several certificates matching all of the criteria then the youngest certificate is returned +(i.e. with the greater "not before" validity field). +You can also include the MD5 and/or SHA1 thumbprints and/or serial number +along with the subject and issuer. + +To select a certificate based on certificate's MD5 or SHA1 thumbprint: + +.nf +"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" +.fi + +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. + +To select a certificate based on certificate's serial number: + +"Serial: 3E 9B 6F 02 00 00 00 01 1F 20" + +If +.BR identity-template +equals to +.I auto +then the actual identity template is +obtained from argument of NEED-CERTIFICATE notification of openvpn. +In this case the argument of NEED-CERTIFICATE must begin with 'macosx-keychain:' prefix +and the rest of it must contain the actual identity template in the format described above. + + +.TP +.BR management-server-ip +OpenVPN management IP to connect to. +Both IPv4 and IPv6 addresses can be used. + +.TP +.BR management-server-port +OpenVPN management port to connect to. +Use +.B unix +for +.I management-server-port +and socket path for +.I management-server-ip +to connect to a local unix socket. + +.TP +.BR password-file + +Password file containing the management password on first line. +The password will be used to connect to +.B openvpn +management interface. + +Pass +.I password-file +to +.B keychain-mcd +if +.I pw-file +was specified in +.B --management +option to +.B openvpn. + + +.SH AUTHOR + +Vasily Kulikov + +.SH "SEE ALSO" + +.BR openvpn (8) diff --git a/contrib/keychain-mcd/main.c b/contrib/keychain-mcd/main.c new file mode 100644 index 0000000..2263b7d --- /dev/null +++ b/contrib/keychain-mcd/main.c @@ -0,0 +1,255 @@ +/* + * 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) 2015 Vasily Kulikov + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "cert_data.h" +#include "crypto_osx.h" +#include "../../src/openvpn/base64.h" + + +SecIdentityRef template_to_identity(const char *template) +{ + SecIdentityRef identity; + CertDataRef pCertDataTemplate = createCertDataFromString(template); + if (pCertDataTemplate == NULL) + errx(1, "Bad certificate template"); + identity = findIdentity(pCertDataTemplate); + if (identity == NULL) + errx(1, "No such identify"); + fprintf(stderr, "Identity found\n"); + destroyCertData(pCertDataTemplate); + return identity; +} + +int connect_to_management_server(const char *ip, const char *port) +{ + int fd; + struct sockaddr_un addr_un; + struct sockaddr *addr; + size_t addr_len; + + if (strcmp(port, "unix") == 0) { + addr = (struct sockaddr*)&addr_un; + addr_len = sizeof(addr_un); + + addr_un.sun_family = AF_UNIX; + strncpy(addr_un.sun_path, ip, sizeof(addr_un.sun_path)); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + } + else { + int rv; + struct addrinfo *result; + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + rv = getaddrinfo(ip, port, &hints, &result); + if (rv < 0) + errx(1, "getaddrinfo: %s", gai_strerror(rv)); + if (result == NULL) + errx(1, "getaddrinfo returned 0 addressed"); + + /* Use the first found address */ + fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + addr = result->ai_addr; + addr_len = result->ai_addrlen; + } + if (fd < 0) + err(1, "socket"); + + if (connect(fd, addr, addr_len) < 0) + err(1, "connect"); + + return fd; +} + +int is_prefix(const char *s, const char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)) == 0; +} + +void handle_rsasign(FILE *man_file, SecIdentityRef identity, const char *input) +{ + const char *input_b64 = strchr(input, ':') + 1; + char *input_binary; + int input_len; + char *output_binary; + size_t output_len; + char *output_b64; + + input_len = strlen(input_b64)*8/6 + 4; + input_binary = malloc(input_len); + input_len = openvpn_base64_decode(input_b64, input_binary, input_len); + if (input_len < 0) + errx(1, "openvpn_base64_decode: overflow"); + + output_len = 1024; + output_binary = malloc(output_len); + signData(identity, (const uint8_t *)input_binary, input_len, (uint8_t *)output_binary, &output_len); + if (output_len == 0) + errx(1, "handle_rsasign: failed to sign data"); + + openvpn_base64_encode(output_binary, output_len, &output_b64); + fprintf(man_file, "rsa-sig\n%s\nEND\n", output_b64); + free(output_b64); + free(input_binary); + free(output_binary); + + fprintf(stderr, "Handled RSA_SIGN command\n"); +} + +void handle_needcertificate(FILE *man_file, SecIdentityRef identity) +{ + OSStatus status; + SecCertificateRef certificate = NULL; + CFDataRef data; + const unsigned char *cert; + size_t cert_len; + char *result_b64, *tmp_b64; + + status = SecIdentityCopyCertificate(identity, &certificate); + if (status != noErr) { + const char *msg = GetMacOSStatusErrorString(status); + err(1, "SecIdentityCopyCertificate() failed: %s", msg); + } + + data = SecCertificateCopyData(certificate); + if (data == NULL) + err(1, "SecCertificateCopyData() returned NULL"); + + cert = CFDataGetBytePtr(data); + cert_len = CFDataGetLength(data); + + openvpn_base64_encode(cert, cert_len, &result_b64); +#if 0 + fprintf(stderr, "certificate %s\n", result_b64); +#endif + + fprintf(man_file, "certificate\n"); + fprintf(man_file, "-----BEGIN CERTIFICATE-----\n"); + tmp_b64 = result_b64; + while (strlen(tmp_b64) > 64) { + fprintf(man_file, "%.64s\n", tmp_b64); + tmp_b64 += 64; + } + if (*tmp_b64) + fprintf(man_file, "%s\n", tmp_b64); + fprintf(man_file, "-----END CERTIFICATE-----\n"); + fprintf(man_file, "END\n"); + + free(result_b64); + CFRelease(data); + CFRelease(certificate); + + fprintf(stderr, "Handled NEED 'cert' command\n"); +} + +void management_loop(SecIdentityRef identity, int man_fd, const char *password) +{ + char *buffer = NULL; + size_t buffer_len = 0; + FILE *man = fdopen(man_fd, "w+"); + if (man == 0) + err(1, "fdopen"); + + if (password) + fprintf(man, "%s\n", password); + + while (1) { + if (getline(&buffer, &buffer_len, man) < 0) + err(1, "getline"); +#if 0 + fprintf(stderr, "M: %s", buffer); +#endif + + if (is_prefix(buffer, ">RSA_SIGN:")) + handle_rsasign(man, identity, buffer); + if (is_prefix(buffer, ">NEED-CERTIFICATE")) { + if (!identity) { + const char prefix[] = ">NEED-CERTIFICATE:macosx-keychain:"; + if (!is_prefix(buffer, prefix)) + errx(1, "No identity template is passed via command line and " \ + "NEED-CERTIFICATE management interface command " \ + "misses 'macosx-keychain' prefix."); + identity = template_to_identity(buffer+strlen(prefix)); + } + handle_needcertificate(man, identity); + } + if (is_prefix(buffer, ">FATAL")) + fprintf(stderr, "Fatal message from OpenVPN: %s\n", buffer+7); + if (is_prefix(buffer, ">INFO")) + fprintf(stderr, "INFO message from OpenVPN: %s\n", buffer+6); + } +} + +char *read_password(const char *fname) +{ + char *password = NULL; + FILE *pwf = fopen(fname, "r"); + size_t n = 0; + + if (pwf == NULL) + errx(1, "fopen(%s) failed", fname); + if (getline(&password, &n, pwf) < 0) + err(1, "getline"); + fclose(pwf); + return password; +} + +int main(int argc, char* argv[]) +{ + if (argc < 4) + err(1, "usage: %s []", argv[0]); + + char *identity_template = argv[1]; + char *s_ip = argv[2]; + char *s_port = argv[3]; + char *password = NULL; + int man_fd; + + if (argc > 4) { + char *s_pw_file = argv[4]; + password = read_password(s_pw_file); + } + + SecIdentityRef identity = NULL; + if (strcmp(identity_template, "auto")) + identity = template_to_identity(identity_template); + man_fd = connect_to_management_server(s_ip, s_port); + fprintf(stderr, "Successfully connected to openvpn\n"); + + management_loop(identity, man_fd, password); +} diff --git a/contrib/pull-resolv-conf/client.up b/contrib/pull-resolv-conf/client.up index b28d4d1..8858b47 100644 --- a/contrib/pull-resolv-conf/client.up +++ b/contrib/pull-resolv-conf/client.up @@ -50,9 +50,10 @@ nl=' # or # "dhcp-option DNS 10.10.10.10" (multiple allowed) -# each DNS option becomes a "nameserver" option in resolv.con +# each DNS option becomes a "nameserver" option in resolv.conf # if we get one DOMAIN, that becomes "domain" in resolv.conf # if we get multiple DOMAINS, those become "search" lines in resolv.conf +# if we get no DOMAINS, then don't use either domain or search. while true; do eval fopt=\$foreign_option_${i} @@ -78,13 +79,15 @@ while true; do i=$((i + 1)) done -ds=domain -if [ $ndoms -gt 1 ]; then - ds=search +ds="" +if [ $ndoms -eq 1 ]; then + ds="${nl}domain" +elif [ $ndoms -gt 1 ]; then + ds="${nl}search" fi # This is the complete file - "$domains" has a leading space already -out="# resolv.conf autogenerated by ${0} (${1})${nl}${dns}${nl}${ds}${domains}" +out="# resolv.conf autogenerated by ${0} (${1})${nl}${dns}${ds}${domains}" # use resolvconf if it's available if type resolvconf >/dev/null 2>&1; then -- cgit v1.2.3