From: Vasily K. <se...@op...> - 2015-01-12 08:51:36
|
This patch adds support for using certificates stored in the Mac OSX Keychain to authenticate with the OpenVPN server. This works with certificates stored on the computer as well as certificates on hardware tokens that support Apple's tokend interface. The patch is based on the Windows Crypto API certificate functionality that currently exists in OpenVPN. This patch version implements management client which handles rsa_sign command for RSA offloading. Also it handled new 'Cert' request to pass a certificate from the keychain to OpenVPN. This is a PoC version of the patch. If the concept is OK for OpenVPN, I'll integrate it into autoconf, add documentation based on the manpage from the previous version of the patch, and add more strict argv checks for interferred options. The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. The previous version of the patch: http://thread.gmane.org/gmane.network.openvpn.devel/9320 Signed-off-by: Vasily Kulikov <se...@op...> --- diff --git a/.gitignore b/.gitignore index 538c020..f504ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ Debug Win32-Output .deps .libs -Makefile Makefile.in aclocal.m4 autodefs.h 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..edfa21b --- /dev/null +++ b/contrib/keychain-mcd/cert_data.c @@ -0,0 +1,761 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <CommonCrypto/CommonDigest.h> +#include <openssl/ssl.h> + +#include "common_osx.h" +#include "crypto_osx.h" +#include <err.h> + +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); + 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); + + 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 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); +} + +void 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; + + for (i=0; i<sizeof(fields)/sizeof(fields[0]); i++) + tryAppendSingleCertField(pCert, fields[i].field, fields[i].key, name, value); +} + +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; + } + + appendCertField(pCertName, name, 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); + else if (CFStringCompareWithOptions(pDescData->name, kCertDataSerialName, CFRangeMake(0, CFStringGetLength(pDescData->name)), kCFCompareCaseInsensitive) == kCFCompareEqualTo) + pCertData->serial = CFRetain(pDescData->value); + else + warnx("WARNING: no key in keychain"); +} + +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); + 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); + + 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; +} + +void printString(const void *val, void *context) +{ + CFStringRef str = (CFStringRef)val; + const char *label = (const char *)context; + warnx("%s = %s\n", label, CFStringGetCStringPtr(str, kCFStringEncodingUTF8)); +} + +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"); +} + +void arrayPrintCertName(const void *val, void *context) +{ + CertNameRef pCertName = (CertNameRef) val; + printCertName(pCertName); +} + +void printCertData(CertDataRef pCertData) +{ + warnx("*** Subject ***\n"); + if (pCertData->subject) + CFArrayApplyFunction(pCertData->subject, CFRangeMake(0, CFArrayGetCount(pCertData->subject)), arrayPrintCertName, NULL); + + warnx("*** Issuer ***\n"); + if (pCertData->issuer) + CFArrayApplyFunction(pCertData->issuer, CFRangeMake(0, CFArrayGetCount(pCertData->issuer)), arrayPrintCertName, NULL); + + warnx("*** Fingerprints ***\n"); + + warnx("SHA1 = "); + if (pCertData->sha1) + printCFString(pCertData->sha1); + + warnx("\n"); + + warnx("MD5 = "); + if (pCertData->md5) + printCFString(pCertData->md5); + + warnx("\n"); + + warnx("Serial Number = "); + if (pCertData->serial) + printCFString(pCertData->serial); + + warnx("\n"); +} + +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<CFArrayGetCount(result); i++) + { + SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(result, i); + if (identity == NULL) + { + warnx ("identity == NULL"); + continue; + } + + SecCertificateRef certificate = NULL; + SecIdentityCopyCertificate (identity, &certificate); + if (certificate == NULL) + { + warnx ("SecIdentityCopyCertificate() returned NULL"); + continue; + } + + CertDataRef pCertData2 = createCertDataFromCertificate(certificate); + if (pCertData2 == NULL) + { + warnx ("createCertDataFromCertificate() returned NULL"); + goto release_cert; + } + bool bMatches = certDataMatchesTemplate(pCertData2, pCertDataTemplate); + bool bExpired = certExpired(certificate); + destroyCertData(pCertData2); + + if (bMatches && !bExpired) + { + CFDateRef notBeforeDate = GetDateFieldFromCertificate(certificate, kSecOIDX509V1ValidityNotBefore); + if (!notBeforeDate) + { + warnx ("GetDateFieldFromCertificate() returned NULL"); + goto release_cert; + } + if (bestIdentity == NULL) + { + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + else if (CFDateCompare(bestNotBeforeDate, notBeforeDate, NULL) == kCFCompareLessThan) + { + CFRelease(bestIdentity); + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + CFRelease(notBeforeDate); + } + release_cert: + CFRelease(certificate); + } + CFRelease(result); + + return bestIdentity; +} diff --git a/contrib/keychain-mcd/cert_data.h b/contrib/keychain-mcd/cert_data.h new file mode 100644 index 0000000..261c1d1 --- /dev/null +++ b/contrib/keychain-mcd/cert_data.h @@ -0,0 +1,46 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2014 Vasily Kulikov <se...@op...> + * + * 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 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..fd9275a --- /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 <br...@ir...> + * Copyright (C) 2013-2014 Vasily Kulikov <se...@op...> + * + * 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 <err.h> + +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..f21230e --- /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 <br...@ir...> + * Copyright (C) 2013-2014 Vasily Kulikov <se...@op...> + * + * 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__ diff --git a/contrib/keychain-mcd/crypto_osx.c b/contrib/keychain-mcd/crypto_osx.c new file mode 100644 index 0000000..9c874e7 --- /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 <br...@ir...> + * Copyright (C) 2013-2014 Vasily Kulikov <se...@op...> + * + * 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 <Security/SecKey.h> +#include <Security/Security.h> + +#include "crypto_osx.h" +#include <err.h> + +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..705e5f4 --- /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 <br...@ir...> + * Copyright (C) 2013-2014 Vasily Kulikov <se...@op...> + * + * 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> + +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/main.c b/contrib/keychain-mcd/main.c new file mode 100644 index 0000000..393b2cf --- /dev/null +++ b/contrib/keychain-mcd/main.c @@ -0,0 +1,204 @@ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <err.h> +#include <netdb.h> + +#include <Security/Security.h> +#include <CoreServices/CoreServices.h> + +#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_needcert(FILE *man_file, SecIdentityRef identity) +{ + OSStatus status; + SecCertificateRef certificate = NULL; + CFDataRef data; + const unsigned char *cert; + size_t cert_len; + char *result_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, "needok CERT %s\n", result_b64); +#endif + fprintf(man_file, "needok CERT '%s'\n", result_b64); + + 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-OK:Need 'CERT'")) + handle_needcert(man, identity); + } +} + +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 <identity_template> <management_ip> <management_port> [<pw-file>]", argv[0]); + + char *cert_prop = 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 = template_to_identity(cert_prop); + 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/src/openvpn/manage.c b/src/openvpn/manage.c index 9f44cd9..c101055 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -1894,6 +1894,46 @@ int managment_android_persisttun_action (struct management *man) #endif +char *cert_to_pem(const char *cert) +{ + const char s1[] = "-----BEGIN CERTIFICATE-----\n"; + const char s2[] = "-----END CERTIFICATE-----\n"; + char *buf = malloc(strlen(cert)*67/64 + strlen(s1) + strlen(s2) + 4); + char *p; + const char *cert_p; + buf[0] = 0; + const int line_len = 64; + + p = buf + sprintf(buf, "%s", s1); + cert_p = cert; + while (*cert_p) { + int n = snprintf(p, line_len, "%s", cert_p); + if (n >= line_len) n = line_len-1; + p += n; + cert_p += n; + p[0] = '\n'; + p[1] = 0; + p += 1; + } + + strcat(p, s2); + +#if 0 + fprintf(stderr, "External certificate:\n%s\nEND\n", buf); +#endif + return buf; +} + +char* management_query_cert (struct management *man) +{ + struct user_pass up; + + CLEAR(up); + strncpy (up.username, "Certificate", sizeof(up.username)-1); + management_query_user_pass(management, &up , "CERT", GET_USER_PASS_NEED_OK,(void*) 0); + return cert_to_pem(up.password); +} + static int man_read (struct management *man) { @@ -2209,7 +2249,7 @@ man_connection_init (struct management *man) * Allocate helper objects for command line input and * command output from/to the socket. */ - man->connection.in = command_line_new (1024); + man->connection.in = command_line_new (4096); man->connection.out = buffer_list_new (0); /* diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 1c8dda6..5bb393f 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -338,6 +338,7 @@ struct management *management_init (void); #define MF_UP_DOWN (1<<10) #define MF_QUERY_REMOTE (1<<11) #define MF_QUERY_PROXY (1<<12) +#define MF_EXTERNAL_CERT (1<<13) bool management_open (struct management *man, const char *addr, @@ -384,6 +385,7 @@ bool management_android_control (struct management *man, const char *command, co #define ANDROID_OPEN_BEFORE_CLOSE 3 int managment_android_persisttun_action (struct management *man); #endif +char* management_query_cert (struct management *man); bool management_should_daemonize (struct management *man); bool management_would_hold (struct management *man); diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 41748bd..a8858d3 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -205,7 +205,7 @@ struct user_pass # ifdef ENABLE_PKCS11 # define USER_PASS_LEN 4096 # else -# define USER_PASS_LEN 128 +# define USER_PASS_LEN 12800 # endif char username[USER_PASS_LEN]; char password[USER_PASS_LEN]; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 64ded09..8f5f7e7 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1591,6 +1591,9 @@ show_settings (const struct options *o) SHOW_STR (ca_file); SHOW_STR (ca_path); SHOW_STR (dh_file); + if((o->management_flags & MF_EXTERNAL_CERT)) + SHOW_PARM ("cert_file","EXTERNAL_CERT","%s"); + else SHOW_STR (cert_file); #ifdef MANAGMENT_EXTERNAL_KEY @@ -2262,6 +2265,7 @@ options_postprocess_verify_ce (const struct options *options, const struct conne } else { + if (!(options->management_flags & MF_EXTERNAL_CERT)) notnull (options->cert_file, "certificate file (--cert) or PKCS#12 file (--pkcs12)"); #ifdef MANAGMENT_EXTERNAL_KEY if (!(options->management_flags & MF_EXTERNAL_KEY)) @@ -4250,6 +4254,11 @@ add_option (struct options *options, options->management_flags |= MF_EXTERNAL_KEY; } #endif + else if (streq (p[0], "management-external-cert")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->management_flags |= MF_EXTERNAL_CERT; + } #ifdef MANAGEMENT_DEF_AUTH else if (streq (p[0], "management-client-auth")) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 5ba4f2f..2b29406 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -52,8 +52,8 @@ /* * Max size of options line and parameter. */ -#define OPTION_PARM_SIZE 256 -#define OPTION_LINE_SIZE 256 +#define OPTION_PARM_SIZE 4096 +#define OPTION_LINE_SIZE 4096 extern const char title_string[]; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index c81659f..cb8a739 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -516,10 +516,18 @@ init_ssl (const struct options *options, struct tls_root_ctx *new_ctx) } #endif #ifdef MANAGMENT_EXTERNAL_KEY - else if ((options->management_flags & MF_EXTERNAL_KEY) && options->cert_file) - { - tls_ctx_use_external_private_key(new_ctx, options->cert_file, - options->cert_file_inline); + else if ((options->management_flags & MF_EXTERNAL_KEY) && + (options->cert_file || options->management_flags & MF_EXTERNAL_CERT)) + { + if (options->cert_file) { + tls_ctx_use_external_private_key(new_ctx, options->cert_file, + options->cert_file_inline); + } else { + char *external_certificate = management_query_cert(management); + tls_ctx_use_external_private_key(new_ctx, INLINE_FILE_TAG, + external_certificate); + free(external_certificate); + } } #endif else |
From: David W. <dw...@in...> - 2015-01-12 11:45:43
Attachments:
smime.p7s
|
On Mon, 2015-01-12 at 11:51 +0300, Vasily Kulikov wrote: > This patch adds support for using certificates stored in the Mac OSX > Keychain to authenticate with the OpenVPN server. This works with > certificates stored on the computer as well as certificates on hardware > tokens that support Apple's tokend interface. The patch is based on > the Windows Crypto API certificate functionality that currently exists > in OpenVPN. > > This patch version implements management client which handles rsa_sign > command for RSA offloading. FWIW we really ought to be supporting key types other than RSA by now. But I appreciate that's not a new limitation and not your fault. It would be interesting to get feedback from those working on NetworkManager-openvpn, which may well want to use this API to allow key operations to happen in the user's session while OpenVPN is running as root. -- David Woodhouse Open Source Technology Centre Dav...@in... Intel Corporation |
From: Arne S. <ar...@rf...> - 2015-01-12 12:55:03
|
Am 12.01.15 12:45, schrieb David Woodhouse: > On Mon, 2015-01-12 at 11:51 +0300, Vasily Kulikov wrote: >> This patch adds support for using certificates stored in the Mac OSX >> Keychain to authenticate with the OpenVPN server. This works with >> certificates stored on the computer as well as certificates on hardware >> tokens that support Apple's tokend interface. The patch is based on >> the Windows Crypto API certificate functionality that currently exists >> in OpenVPN. I wonder why only certifcates and not ca certifcates. It would be logical to get all certifcates from the keychain. >> >> This patch version implements management client which handles rsa_sign >> command for RSA offloading. > FWIW we really ought to be supporting key types other than RSA by now. > But I appreciate that's not a new limitation and not your fault. Well although rsa-sign at the momemnt probably only supports RSA (it is implemented using rsa_method iirc) the API is not rsa specific. It is just: "Please sign this hash with the private key". In the case of an RSA certificate this happens to be RSA encrypt in ECB mode with PKCS#1 padding. I am not sure if there is an equivalent of rsa_method for EC in OpenSSL or if you have to use the engine functionality of OpenSSL for EC. > It would be interesting to get feedback from those working on > NetworkManager-openvpn, which may well want to use this API to allow key > operations to happen in the user's session while OpenVPN is running as > root. |
From: David W. <dw...@in...> - 2015-01-12 13:17:05
Attachments:
smime.p7s
|
On Mon, 2015-01-12 at 13:54 +0100, Arne Schwabe wrote: > I wonder why only certifcates and not ca certifcates. It would be > logical to get all certifcates from the keychain. Yes, that makes some sense. Although perhaps it should be the other way round — you present the peer's cert to the management client and it just gives you a "yes" or "no" answer. Perhaps after asking the user, if the certificate *wasn't* automatically trusted. > Well although rsa-sign at the momemnt probably only supports RSA (it is > implemented using rsa_method iirc) the API is not rsa specific. It is > just: "Please sign this hash with the private key". In the case of an > RSA certificate this happens to be RSA encrypt in ECB mode with PKCS#1 > padding. I hope it goes without saying, but obviously if we making that more generic, we should be careful not to do dangerous things like allowing the management client to do RSA encryption *without* padding. :) > I am not sure if there is an equivalent of rsa_method for EC in OpenSSL > or if you have to use the engine functionality of OpenSSL for EC. An externally-built ENGINE can't do anything more than you can. Hence http://rt.openssl.org/Ticket/Display.html?id=2459&user=guest&pass=guest -- dwmw2 |
From: Vasily K. <se...@op...> - 2015-01-13 09:05:39
|
On Mon, Jan 12, 2015 at 13:54 +0100, Arne Schwabe wrote: > > Am 12.01.15 12:45, schrieb David Woodhouse: > > On Mon, 2015-01-12 at 11:51 +0300, Vasily Kulikov wrote: > >> This patch adds support for using certificates stored in the Mac OSX > >> Keychain to authenticate with the OpenVPN server. This works with > >> certificates stored on the computer as well as certificates on hardware > >> tokens that support Apple's tokend interface. The patch is based on > >> the Windows Crypto API certificate functionality that currently exists > >> in OpenVPN. > > I wonder why only certifcates and not ca certifcates. It would be > logical to get all certifcates from the keychain. A user tells keychain-mcd which keychain identity to use for openvpn. The identity contains a certificate and a private key (might be non-extractable). CA is not related to this identity. It can be added to keychain-mcd and openvpn but a user should pass another certificate match string for CA. > >> > >> This patch version implements management client which handles rsa_sign > >> command for RSA offloading. > > FWIW we really ought to be supporting key types other than RSA by now. > > But I appreciate that's not a new limitation and not your fault. > > Well although rsa-sign at the momemnt probably only supports RSA (it is > implemented using rsa_method iirc) the API is not rsa specific. It is > just: "Please sign this hash with the private key". In the case of an > RSA certificate this happens to be RSA encrypt in ECB mode with PKCS#1 > padding. Right. And my patch is not specific to RSA too. It uses generic Mac OS X API which should work with any key type: SecIdentityCopyPrivateKey() SecKeyRawSign() -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: Vasily K. <se...@op...> - 2015-01-24 17:04:54
|
This patch adds support for using certificates stored in the Mac OSX Keychain to authenticate with the OpenVPN server. This works with certificates stored on the computer as well as certificates on hardware tokens that support Apple's tokend interface. This patch version implements management client which handles RSA-SIGN command for RSA offloading. Also it handles new 'NEED-CERTIFICATE' request to pass a certificate from the keychain to OpenVPN. OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when --management-external-cert is used. It is implemented as a multiline command very similar to an existing 'RSA-SIGN' command. The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. v3: - used new 'NEED-CERTIFICATE' command for certificate data request instead of 'NEED-OK' - improved option checking - improved invalid certificate selection string handling - added man page for keychain-mcd - handle INFO, FATAL commands from openvpn and show them to user v2 (http://sourceforge.net/p/openvpn/mailman/message/33225603/): - used management interface to communicate with OpenVPN process v1 (http://sourceforge.net/p/openvpn/mailman/message/33125844/): - used RSA_METHOD to extend openvpn itself - used autoconf and automake scripts - used newer Mac OS X API - improved crypto API errors checking Brian Raderman's version: http://thread.gmane.org/gmane.network.openvpn.devel/3631 Signed-off-by: Vasily Kulikov <se...@op...> -- diff --git a/.gitignore b/.gitignore index 538c020..f504ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ Debug Win32-Output .deps .libs -Makefile Makefile.in aclocal.m4 autodefs.h 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..4f06cdf --- /dev/null +++ b/contrib/keychain-mcd/cert_data.c @@ -0,0 +1,733 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <CommonCrypto/CommonDigest.h> +#include <openssl/ssl.h> + +#include "common_osx.h" +#include "crypto_osx.h" +#include <err.h> + +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); + 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; i<sizeof(fields)/sizeof(fields[0]); i++) + ret += tryAppendSingleCertField(pCert, fields[i].field, fields[i].key, name, value); + return ret; +} + +int parseCertName(CFStringRef nameDesc, CFMutableArrayRef names) +{ + CFArrayRef nameStrings = CFStringCreateArrayBySeparatingStrings(NULL, nameDesc, kCertNameFwdSlash); + int count = CFArrayGetCount(nameStrings); + int i; + int ret = 1; + + CertNameRef pCertName = createCertName(); + + for(i = 0;i < count;i++) + { + CFMutableStringRef dn = CFStringCreateMutableCopy(NULL, 0, CFArrayGetValueAtIndex(nameStrings, i)); + CFStringTrimWhitespace(dn); + + CFStringRef name, value; + + if (!parseNameComponent(dn, &name, &value)) + ret = 0; + + if (!name || !value) + { + if (name) + CFRelease(name); + + if (value) + CFRelease(value); + if (name && !value) + ret = 0; + + CFRelease(dn); + continue; + } + + if (!appendCertField(pCertName, name, value)) + ret = 0; + CFRelease(name); + CFRelease(value); + CFRelease(dn); + } + + CFArrayAppendValue(names, pCertName); + CFRelease(nameStrings); + return ret; +} + +int arrayParseDescDataPair(const void *val, void *context) +{ + DescDataRef pDescData = (DescDataRef)val; + CertDataRef pCertData = (CertDataRef)context; + int ret = 1; + + if (!pDescData->name || !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; i<CFArrayGetCount(pairs); i++) + if (!arrayParseDescDataPair(CFArrayGetValueAtIndex(pairs, i), pCertData)) { + arrayDestroyDescData(pCertData, NULL); + CFArrayApplyFunction(pairs, CFRangeMake(0, CFArrayGetCount(pairs)), arrayDestroyDescData, NULL); + CFRelease(pairs); + return 0; + } + + 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); + + 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<CFArrayGetCount(result); i++) + { + SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(result, i); + if (identity == NULL) + { + warnx ("identity == NULL"); + continue; + } + + SecCertificateRef certificate = NULL; + SecIdentityCopyCertificate (identity, &certificate); + if (certificate == NULL) + { + warnx ("SecIdentityCopyCertificate() returned NULL"); + continue; + } + + CertDataRef pCertData2 = createCertDataFromCertificate(certificate); + if (pCertData2 == NULL) + { + warnx ("createCertDataFromCertificate() returned NULL"); + goto release_cert; + } + bool bMatches = certDataMatchesTemplate(pCertData2, pCertDataTemplate); + bool bExpired = certExpired(certificate); + destroyCertData(pCertData2); + + if (bMatches && !bExpired) + { + CFDateRef notBeforeDate = GetDateFieldFromCertificate(certificate, kSecOIDX509V1ValidityNotBefore); + if (!notBeforeDate) + { + warnx ("GetDateFieldFromCertificate() returned NULL"); + goto release_cert; + } + if (bestIdentity == NULL) + { + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + else if (CFDateCompare(bestNotBeforeDate, notBeforeDate, NULL) == kCFCompareLessThan) + { + CFRelease(bestIdentity); + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + CFRelease(notBeforeDate); + } + release_cert: + CFRelease(certificate); + } + CFRelease(result); + + return bestIdentity; +} diff --git a/contrib/keychain-mcd/cert_data.h b/contrib/keychain-mcd/cert_data.h new file mode 100644 index 0000000..407cca1 --- /dev/null +++ b/contrib/keychain-mcd/cert_data.h @@ -0,0 +1,46 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <err.h> + +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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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__ diff --git a/contrib/keychain-mcd/crypto_osx.c b/contrib/keychain-mcd/crypto_osx.c new file mode 100644 index 0000000..eda7121 --- /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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <Security/SecKey.h> +#include <Security/Security.h> + +#include "crypto_osx.h" +#include <err.h> + +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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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> + +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..20a67e4 --- /dev/null +++ b/contrib/keychain-mcd/keychain-mcd.8 @@ -0,0 +1,150 @@ +.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. + +\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" + +.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 <se...@op...> + +.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..c6bf06b --- /dev/null +++ b/contrib/keychain-mcd/main.c @@ -0,0 +1,244 @@ +/* + * 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 <se...@op...> + * + * 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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <err.h> +#include <netdb.h> + +#include <Security/Security.h> +#include <CoreServices/CoreServices.h> + +#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")) + 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 <identity_template> <management_ip> <management_port> [<pw-file>]", argv[0]); + + char *cert_prop = 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 = template_to_identity(cert_prop); + 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/src/openvpn/buffer.c b/src/openvpn/buffer.c index 46f874b..421d60e 100644 --- a/src/openvpn/buffer.c +++ b/src/openvpn/buffer.c @@ -1066,8 +1066,10 @@ buffer_list_peek (struct buffer_list *ol) } void -buffer_list_aggregate (struct buffer_list *bl, const size_t max) +buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep) { + int sep_len = strlen(sep); + if (bl->head) { struct buffer_entry *more = bl->head; @@ -1075,7 +1077,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) int count = 0; for (count = 0; more && size <= max; ++count) { - size += BLEN(&more->buf); + size += BLEN(&more->buf) + sep_len; more = more->next; } @@ -1092,6 +1094,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) { struct buffer_entry *next = e->next; buf_copy (&f->buf, &e->buf); + buf_write(&f->buf, sep, sep_len); free_buf (&e->buf); free (e); e = next; @@ -1105,6 +1108,12 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) } void +buffer_list_aggregate (struct buffer_list *bl, const size_t max) +{ + buffer_list_aggregate_separator(bl, max, ""); +} + +void buffer_list_pop (struct buffer_list *ol) { if (ol && ol->head) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 7469da6..5695f64 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -931,6 +931,7 @@ void buffer_list_advance (struct buffer_list *ol, int n); void buffer_list_pop (struct buffer_list *ol); void buffer_list_aggregate (struct buffer_list *bl, const size_t max); +void buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep); struct buffer_list *buffer_list_file (const char *fn, int max_line_len); #endif /* BUFFER_H */ diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 9f44cd9..29be871 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -113,6 +113,8 @@ man_help () #ifdef MANAGMENT_EXTERNAL_KEY msg (M_CLIENT, "rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge"); msg (M_CLIENT, " Enter signature base64 on subsequent lines followed by END"); + msg (M_CLIENT, "certificate : Enter a client certificate in response to >NEED-CERT challenge"); + msg (M_CLIENT, " Enter certificate base64 on subsequent lines followed by END"); #endif msg (M_CLIENT, "signal s : Send signal s to daemon,"); msg (M_CLIENT, " s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2."); @@ -868,6 +870,12 @@ in_extra_dispatch (struct management *man) man->connection.ext_key_input = man->connection.in_extra; man->connection.in_extra = NULL; return; + case IEC_CERTIFICATE: + man->connection.ext_cert_state = EKS_READY; + buffer_list_free (man->connection.ext_cert_input); + man->connection.ext_cert_input = man->connection.in_extra; + man->connection.in_extra = NULL; + return; #endif } in_extra_reset (&man->connection, IER_RESET); @@ -1030,6 +1038,20 @@ man_rsa_sig (struct management *man) msg (M_CLIENT, "ERROR: The rsa-sig command is not currently available"); } +static void +man_certificate (struct management *man) +{ + struct man_connection *mc = &man->connection; + if (mc->ext_cert_state == EKS_SOLICIT) + { + mc->ext_cert_state = EKS_INPUT; + mc->in_extra_cmd = IEC_CERTIFICATE; + in_extra_reset (mc, IER_NEW); + } + else + msg (M_CLIENT, "ERROR: The certificate command is not currently available"); +} + #endif static void @@ -1311,6 +1333,10 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch { man_rsa_sig (man); } + else if (streq (p[0], "certificate")) + { + man_certificate (man); + } #endif #ifdef ENABLE_PKCS11 else if (streq (p[0], "pkcs11-id-count")) @@ -3097,15 +3123,14 @@ management_query_user_pass (struct management *man, #ifdef MANAGMENT_EXTERNAL_KEY -char * /* returns allocated base64 signature */ -management_query_rsa_sig (struct management *man, - const char *b64_data) +int +management_query_multiline (struct management *man, + const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input) { struct gc_arena gc = gc_new (); - char *ret = NULL; + int ret = 0; volatile int signal_received = 0; struct buffer alert_msg = clear_buf(); - struct buffer *buf; const bool standalone_disabled_save = man->persist.standalone_disabled; struct man_connection *mc = &man->connection; @@ -3114,10 +3139,15 @@ management_query_rsa_sig (struct management *man, man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ man->persist.special_state_msg = NULL; - mc->ext_key_state = EKS_SOLICIT; + *state = EKS_SOLICIT; - alert_msg = alloc_buf_gc (strlen(b64_data)+64, &gc); - buf_printf (&alert_msg, ">RSA_SIGN:%s", b64_data); + if (b64_data) { + alert_msg = alloc_buf_gc (strlen(b64_data)+strlen(prompt)+3, &gc); + buf_printf (&alert_msg, ">%s:%s", prompt, b64_data); + } else { + alert_msg = alloc_buf_gc (strlen(prompt)+3, &gc); + buf_printf (&alert_msg, ">%s", prompt); + } man_wait_for_client_connection (man, &signal_received, 0, MWCC_OTHER_WAIT); @@ -3135,40 +3165,99 @@ management_query_rsa_sig (struct management *man, man_check_for_signals (&signal_received); if (signal_received) goto done; - } while (mc->ext_key_state != EKS_READY); + } while (*state != EKS_READY); - if (buffer_list_defined(mc->ext_key_input)) - { - buffer_list_aggregate (mc->ext_key_input, 2048); - buf = buffer_list_peek (mc->ext_key_input); - if (buf && BLEN(buf) > 0) - { - ret = (char *) malloc(BLEN(buf)+1); - check_malloc_return(ret); - memcpy(ret, buf->data, BLEN(buf)); - ret[BLEN(buf)] = '\0'; - } - } + ret = 1; } done: - if (mc->ext_key_state == EKS_READY && ret) - msg (M_CLIENT, "SUCCESS: rsa-sig command succeeded"); - else if (mc->ext_key_state == EKS_INPUT || mc->ext_key_state == EKS_READY) - msg (M_CLIENT, "ERROR: rsa-sig command failed"); + if (*state == EKS_READY && ret) + msg (M_CLIENT, "SUCCESS: %s command succeeded", cmd); + else if (*state == EKS_INPUT || *state == EKS_READY) + msg (M_CLIENT, "ERROR: %s command failed", cmd); /* revert state */ man->persist.standalone_disabled = standalone_disabled_save; man->persist.special_state_msg = NULL; in_extra_reset (mc, IER_RESET); - mc->ext_key_state = EKS_UNDEF; - buffer_list_free (mc->ext_key_input); - mc->ext_key_input = NULL; + *state = EKS_UNDEF; gc_free (&gc); return ret; } +char * /* returns allocated base64 signature */ +management_query_multiline_flatten_newline (struct management *man, + const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input) +{ + int ok; + char *result = NULL; + struct buffer *buf; + + ok = management_query_multiline(man, b64_data, prompt, cmd, state, input); + if (ok && buffer_list_defined(*input)) + { + buffer_list_aggregate_separator (*input, 10000, "\n"); + buf = buffer_list_peek (*input); + if (buf && BLEN(buf) > 0) + { + result = (char *) malloc(BLEN(buf)+1); + check_malloc_return(result); + memcpy(result, buf->data, BLEN(buf)); + result[BLEN(buf)] = '\0'; + } + } + + buffer_list_free (*input); + *input = NULL; + + return result; +} + +char * /* returns allocated base64 signature */ +management_query_multiline_flatten (struct management *man, + const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input) +{ + int ok; + char *result = NULL; + struct buffer *buf; + + ok = management_query_multiline(man, b64_data, prompt, cmd, state, input); + if (ok && buffer_list_defined(*input)) + { + buffer_list_aggregate (*input, 2048); + buf = buffer_list_peek (*input); + if (buf && BLEN(buf) > 0) + { + result = (char *) malloc(BLEN(buf)+1); + check_malloc_return(result); + memcpy(result, buf->data, BLEN(buf)); + result[BLEN(buf)] = '\0'; + } + } + + buffer_list_free (*input); + *input = NULL; + + return result; +} + +char * /* returns allocated base64 signature */ +management_query_rsa_sig (struct management *man, + const char *b64_data) +{ + return management_query_multiline_flatten(man, b64_data, "RSA_SIGN", "rsa-sign", + &man->connection.ext_key_state, &man->connection.ext_key_input); +} + + +char* management_query_cert (struct management *man) +{ + return management_query_multiline_flatten_newline(management, + NULL, "NEED-CERTIFICATE", "certificate", + &man->connection.ext_cert_state, &man->connection.ext_cert_input); +} + #endif /* diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 1c8dda6..95d2776 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -268,6 +268,7 @@ struct man_connection { # define IEC_CLIENT_AUTH 1 # define IEC_CLIENT_PF 2 # define IEC_RSA_SIGN 3 +# define IEC_CERTIFICATE 4 int in_extra_cmd; struct buffer_list *in_extra; #ifdef MANAGEMENT_DEF_AUTH @@ -281,6 +282,8 @@ struct man_connection { # define EKS_READY 3 int ext_key_state; struct buffer_list *ext_key_input; + int ext_cert_state; + struct buffer_list *ext_cert_input; #endif #endif struct event_set *es; @@ -338,6 +341,7 @@ struct management *management_init (void); #define MF_UP_DOWN (1<<10) #define MF_QUERY_REMOTE (1<<11) #define MF_QUERY_PROXY (1<<12) +#define MF_EXTERNAL_CERT (1<<13) bool management_open (struct management *man, const char *addr, @@ -420,6 +424,7 @@ void management_learn_addr (struct management *management, #ifdef MANAGMENT_EXTERNAL_KEY char *management_query_rsa_sig (struct management *man, const char *b64_data); +char* management_query_cert (struct management *man); #endif diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 64ded09..6ef14a3 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -1591,6 +1591,11 @@ show_settings (const struct options *o) SHOW_STR (ca_file); SHOW_STR (ca_path); SHOW_STR (dh_file); +#ifdef MANAGMENT_EXTERNAL_KEY + if((o->management_flags & MF_EXTERNAL_CERT)) + SHOW_PARM ("cert_file","EXTERNAL_CERT","%s"); + else +#endif SHOW_STR (cert_file); #ifdef MANAGMENT_EXTERNAL_KEY @@ -2172,6 +2177,8 @... [truncated message content] |
From: Arne S. <ar...@rf...> - 2015-02-15 21:05:21
Attachments:
signature.asc
|
Am 24.01.15 um 18:04 schrieb Vasily Kulikov: > This patch adds support for using certificates stored in the Mac OSX > Keychain to authenticate with the OpenVPN server. This works with > certificates stored on the computer as well as certificates on hardware > tokens that support Apple's tokend interface. > > This patch version implements management client which handles RSA-SIGN > command for RSA offloading. Also it handles new 'NEED-CERTIFICATE' > request to pass a certificate from the keychain to OpenVPN. > > OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when > --management-external-cert is used. It is implemented as a multiline > command very similar to an existing 'RSA-SIGN' command. > > The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. > > ACK from me to the OpenVPN part. I also tested the patch in OpenVPN for Android and the RSA-SIGN still works as expected. I have not reviewed the OS X contrib program (other than a quick glance at the code) but I think marking it as contrib it should be allowed to be still included. Arne |
From: Gert D. <ge...@gr...> - 2015-02-15 22:01:58
|
Hi, On Sun, Feb 15, 2015 at 10:05:07PM +0100, Arne Schwabe wrote: > Am 24.01.15 um 18:04 schrieb Vasily Kulikov: [..] > > OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when > > --management-external-cert is used. It is implemented as a multiline > > command very similar to an existing 'RSA-SIGN' command. > > > > The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. > ACK from me to the OpenVPN part. I also tested the patch in OpenVPN for > Android and the RSA-SIGN still works as expected. I have not reviewed > the OS X contrib program (other than a quick glance at the code) but I > think marking it as contrib it should be allowed to be still included. I hear Arne, and James also ACKed this ("based on testing", which Arne did). I'm not merging it yet, though - Vasily, please provide a v4 of the patch that adds: - documentation of --management-external-cert in doc/openvpn.8 - documentation of the new management command and response in doc/management-notes.txt - fix the typos in the options here (please fix the other one, too): @@ -2221,6 +2230,8 @@ options_postprocess_verify_ce (const struct options *optio ns, const struct conne #ifdef MANAGMENT_EXTERNAL_KEY if (options->management_flags & MF_EXTERNAL_KEY) msg(M_USAGE, "Parameter --external-management-key cannot be used whe n --pkcs12 is also specified."); + if (options->management_flags & MF_EXTERNAL_CERT) + msg(M_USAGE, "Parameter --external-management-cert cannot be used wh en --pkcs12 is also specified."); #endif #endif } ... it's "--management-external-*", not "--external-management-*". With that, I'll merge right away :-) thanks, gert -- USENET is *not* the non-clickable part of WWW! //www.muc.de/~gert/ Gert Doering - Munich, Germany ge...@gr... fax: +49-89-35655025 ge...@ne... |
From: Vasily K. <se...@op...> - 2015-02-20 09:23:53
|
Hi Gert, On Sun, Feb 15, 2015 at 23:01 +0100, Gert Doering wrote: > I hear Arne, and James also ACKed this ("based on testing", which Arne > did). > > I'm not merging it yet, though - Vasily, please provide a v4 of the patch > that adds: ... > With that, I'll merge right away :-) Thank you for the review, I'll send the patch during the weekends. -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: Vasily K. <se...@op...> - 2015-02-23 08:04:14
|
Hi, On Sun, Feb 15, 2015 at 23:01 +0100, Gert Doering wrote: > Hi, > > On Sun, Feb 15, 2015 at 10:05:07PM +0100, Arne Schwabe wrote: > > Am 24.01.15 um 18:04 schrieb Vasily Kulikov: > [..] > > > OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when > > > --management-external-cert is used. It is implemented as a multiline > > > command very similar to an existing 'RSA-SIGN' command. > > > > > > The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. > > ACK from me to the OpenVPN part. I also tested the patch in OpenVPN for > > Android and the RSA-SIGN still works as expected. I have not reviewed > > the OS X contrib program (other than a quick glance at the code) but I > > think marking it as contrib it should be allowed to be still included. > > I hear Arne, and James also ACKed this ("based on testing", which Arne > did). > > I'm not merging it yet, though - Vasily, please provide a v4 of the patch > that adds: > > - documentation of --management-external-cert in doc/openvpn.8 > - documentation of the new management command and response in > doc/management-notes.txt > - fix the typos in the options here (please fix the other one, too): > > @@ -2221,6 +2230,8 @@ options_postprocess_verify_ce (const struct options *optio > ns, const struct conne > #ifdef MANAGMENT_EXTERNAL_KEY > if (options->management_flags & MF_EXTERNAL_KEY) > msg(M_USAGE, "Parameter --external-management-key cannot be used whe > n --pkcs12 is also specified."); > + if (options->management_flags & MF_EXTERNAL_CERT) > + msg(M_USAGE, "Parameter --external-management-cert cannot be used wh > en --pkcs12 is also specified."); > #endif > #endif > } > > ... it's "--management-external-*", not "--external-management-*". > > With that, I'll merge right away :-) I've implemented the changes above, but don't send the patch yet. While talking with Jonathan Bullard we identified that with the current design it is very hard to implement a simple identity template passing independently of GUI implementation. Currently identity template is passed to keychain-mcd leaving aside openvpn itself and thus the openvpn configuration file doesn't contain the identity template. Tunnelblick or another GUI has to start keychain-mcd and pass the identity template itself. The way a user notifies TB/openvpn what identity template to use is TB-dependent: it can be e.g. storing XXX-keychain-identityTemplate preference in Info.plist file inside .tblk directory. Other GUI might use a different configuration method and thus store the identity template in some other place. It would be better to have a single GUI-independent way of configuring keychain-mcd. Jonathan suggested the following method. --management-external-cert has a single argument, the identity template. This template is passed as-is to management interface client as an argument to NEED-CERTIFICATE request. Management interface client parses this string and chooses an identity based on the argument. In this case the argument is an opaque value which is simply passed to a management interface client, which handles it as it likes (or ignores). Also an argument can be used with an arbitrary management interface client, not only keychain-mcd. We can restrict a bit a format of XXX in '--management-external-cert XXX' option argument. It can consist of two parts, e.g.: --management-external-cert 'macosx-keychain:SUBJECT:c=US' The first part is a hint how management client should handle the argument. In case of keychain-mcd it parses the argument, checks whether it starts with 'macosx-keychain:', and uses the end of the argument as an identity template. If the change is implemented openvpn config can contain all information needed to start openvpn connection. A user has to start openvpn with the config file and to start keychain-mcd. Specifically it would contain a line like the following: management-external-cert 'macosx-keychain:SUBJECT:c=US' With the approach in patch v3 a user has to start openvpn with the config file, start keychain-mcd, and pass identity template as an argument to keychain-mcd. What do you think of the change? Thanks, -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: Arne S. <ar...@rf...> - 2015-02-23 08:28:41
|
Am 23.02.15 um 09:04 schrieb Vasily Kulikov: > management-external-cert 'macosx-keychain:SUBJECT:c=US' > > With the approach in patch v3 a user has to start openvpn with the > config file, start keychain-mcd, and pass identity template as an > argument to keychain-mcd. > > What do you think of the change? I like the idea. You could make the macos-keychain in the string optional. Arne |
From: Gert D. <ge...@gr...> - 2015-02-23 09:01:06
|
Hi, On Mon, Feb 23, 2015 at 09:28:31AM +0100, Arne Schwabe wrote: > > What do you think of the change? > I like the idea. You could make the macos-keychain in the string optional. What Arne said (both parts of it) :-) gert -- USENET is *not* the non-clickable part of WWW! //www.muc.de/~gert/ Gert Doering - Munich, Germany ge...@gr... fax: +49-89-35655025 ge...@ne... |
From: Vasily K. <se...@op...> - 2015-02-23 12:26:24
|
On Mon, Feb 23, 2015 at 10:00 +0100, Gert Doering wrote: > Hi, > > On Mon, Feb 23, 2015 at 09:28:31AM +0100, Arne Schwabe wrote: > > > What do you think of the change? > > I like the idea. You could make the macos-keychain in the string optional. > > What Arne said (both parts of it) :-) Excellent! -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: David W. <dw...@in...> - 2015-02-23 12:55:20
Attachments:
smime.p7s
|
On Mon, 2015-02-23 at 09:28 +0100, Arne Schwabe wrote: > > Am 23.02.15 um 09:04 schrieb Vasily Kulikov: > > management-external-cert 'macosx-keychain:SUBJECT:c=US' > > > > With the approach in patch v3 a user has to start openvpn with the > > config file, start keychain-mcd, and pass identity template as an > > argument to keychain-mcd. > > > > What do you think of the change? > I like the idea. You could make the macos-keychain in the string optional. I wouldn't make it optional. Keep it URI-like, with 'macos-keychain' as the URI scheme. We'll also want to support pkcs11: and file: URIs. In both cases we will have the same issue, with objects accessible only by a user *other* than the user that OpenVPN runs as. GnuTLS HEAD also has support for CNG under Windows, and a system:id=XXX URI format to specify keys therein. We might even want to support an agent using that too. If any URI scheme is going to be the default, and specifying it is going to be optional. I'd probably suggest that it be the file: scheme. But better still, just don't have a default. Certainly not macos-keychain :) It might also be worth looking to see if the URI format we're using for the macos-keychain: URIs could be made more similar to the PKCS#11 URI standard. -- David Woodhouse Open Source Technology Centre Dav...@in... Intel Corporation |
From: Vasily K. <se...@op...> - 2015-02-23 14:43:02
|
On Mon, Feb 23, 2015 at 12:55 +0000, David Woodhouse wrote: > On Mon, 2015-02-23 at 09:28 +0100, Arne Schwabe wrote: > > > > Am 23.02.15 um 09:04 schrieb Vasily Kulikov: > > > management-external-cert 'macosx-keychain:SUBJECT:c=US' > > > > > > With the approach in patch v3 a user has to start openvpn with the > > > config file, start keychain-mcd, and pass identity template as an > > > argument to keychain-mcd. > > > > > > What do you think of the change? > > I like the idea. You could make the macos-keychain in the string optional. > > I wouldn't make it optional. Keep it URI-like, with 'macos-keychain' as > the URI scheme. I'll keep it. I don't have a strict understanding what is the purpose of a default scheme, so I don't declare any default. Also please note that openvpn itself doesn't know about any "scheme", it simply passes the value to a management interface client. A client is the process which parses the value and must know about schemes, default scheme, etc. Thanks, -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: Arne S. <ar...@rf...> - 2015-02-23 12:59:50
|
Am 23.02.15 um 13:55 schrieb David Woodhouse: > On Mon, 2015-02-23 at 09:28 +0100, Arne Schwabe wrote: >> Am 23.02.15 um 09:04 schrieb Vasily Kulikov: >>> management-external-cert 'macosx-keychain:SUBJECT:c=US' >>> >>> With the approach in patch v3 a user has to start openvpn with the >>> config file, start keychain-mcd, and pass identity template as an >>> argument to keychain-mcd. >>> >>> What do you think of the change? >> I like the idea. You could make the macos-keychain in the string optional. > I wouldn't make it optional. Keep it URI-like, with 'macos-keychain' as > the URI scheme. > > We'll also want to support pkcs11: and file: URIs. In both cases we will > have the same issue, with objects accessible only by a user *other* than > the user that OpenVPN runs as. > > GnuTLS HEAD also has support for CNG under Windows, and a system:id=XXX > URI format to specify keys therein. We might even want to support an > agent using that too. > > If any URI scheme is going to be the default, and specifying it is going > to be optional. I'd probably suggest that it be the file: scheme. But > better still, just don't have a default. Certainly not macos-keychain :) > > It might also be worth looking to see if the URI format we're using for > the macos-keychain: URIs could be made more similar to the PKCS#11 URI > standard. > All fine. My rationale was like, if I want a certificate with a certain SUBJECT (e.g. CN=sc...@my...) etc. it should not matter for men wether I get it from OS X, Windows or Android Certificate store. Arne |
From: Jonathan K. B. <jkb...@gm...> - 2015-02-23 13:05:22
|
On Mon, Feb 23, 2015 at 4:00 AM, Gert Doering <ge...@gr...> wrote: > > On Mon, Feb 23, 2015 at 09:28:31AM +0100, Arne Schwabe wrote: > > > What do you think of the change? > > I like the idea. You could make the macos-keychain in the string optional. > > What Arne said (both parts of it) :-) I agree -- the argument to --needs-external-cert should be optional. Note: the argument to --needs-external-cert should be passed on to "RSA_SIGN", too. (I think Vasily omitted that from his writeup.) So the idea would be: * Add an optional UTF-8 string argument to --needs-external-cert. (Perhaps the docs should say this requires support from the management interface software and that currently such support is only available when using certain GUIs on OS X.) * OpenVPN passes that argument to RSA_SIGN and NEEDS-CERTIFICATE, passing an empty string if the argument does not appear. * OS X GUIs such as Tunnelblick and Viscosity see the new RSA_SIGN or NEEDS-CERTIFICATE argument and use keychain-mcd to deal with it. Other GUIs ignore it or use something that does something equivalent to what keychain-mcd does on OS X. I'm not sure exactly how to add an argument to RSA_SIGN and NEEDS-CERTIFICATE without breaking existing management interface software but assume that is possible. (Also, the argument may need to be escaped when it is passed to RSA_SIGN or NEEDS-CERTIFICATE if it contains characters that are used as delimiters.) |
From: David W. <dw...@in...> - 2015-02-23 13:10:26
Attachments:
smime.p7s
|
On Mon, 2015-02-23 at 13:59 +0100, Arne Schwabe wrote: > > All fine. My rationale was like, if I want a certificate with a certain > SUBJECT (e.g. CN=sc...@my...) etc. it should not matter for men > wether I get it from OS X, Windows or Android Certificate store. The canonical way of representing that would be pkcs11:object=sc...@my... -- dwmw2 |
From: Steffan K. <ste...@fo...> - 2015-02-23 13:37:22
|
On 02/23/2015 02:10 PM, David Woodhouse wrote: > On Mon, 2015-02-23 at 13:59 +0100, Arne Schwabe wrote: >> >> All fine. My rationale was like, if I want a certificate with a certain >> SUBJECT (e.g. CN=sc...@my...) etc. it should not matter for men >> wether I get it from OS X, Windows or Android Certificate store. > > The canonical way of representing that would be > pkcs11:object=sc...@my... But that implies that pkcs11 is somehow in the loop. I might (1) use a different mechanism, such as a separate daemon that directly uses a private key file (as is possible on Android), or (2) simply don't care for what mechanism my daemon uses. That said, I *do* agree that it would be worthwhile to see if we can use the same URI format as pkcs11-uris (most recent draft: https://tools.ietf.org/html/draft-pechanec-pkcs11uri-21). If I recall correctly, there even are pkcs11-uri parsing libs available. I think omitting 'pkcs11:' or 'osxkeychain:' should be possible, and if omitted the backend application should just use whatever it prefers. -Steffan |
From: Jonathan K. B. <jkb...@gm...> - 2015-02-23 13:59:34
|
On Mon, Feb 23, 2015 at 8:10 AM, David Woodhouse <dw...@in...> wrote: > On Mon, 2015-02-23 at 13:59 +0100, Arne Schwabe wrote: >> >> All fine. My rationale was like, if I want a certificate with a certain >> SUBJECT (e.g. CN=sc...@my...) etc. it should not matter for men >> wether I get it from OS X, Windows or Android Certificate store. > > The canonical way of representing that would be > pkcs11:object=sc...@my... That representation loses the "CN=" (and maybe doesn't allow for additional search options -- I'm not sure). OpenVPN already has an entire set of options that deal with PKCS-11. This patch is a completely different, parallel way of dealing with certificates, so I'm not sure why a "pkcs11:" scheme is needed. I probably would not implement it in Tunnelblick myself. There are two things here: (A) what this "select" string is and (B) where/how to find the cert. As to "what this 'select' string is" -- well, for "future-proofing", I would want it to be labeled as a selection string in case we want to pass something else at some time in the future. Adding a scheme makes sense to me -- the "file:" scheme looks interesting, but it isn't part of this patch. But I think the scheme should be optional, so the configuration could include (the platform-independent): --management-external-keychain select:CN=sc...@my... and have it use the Keychain on OS X, Android Certificate Store on Android, something-I-don't-know on Windows, etc. The tricky aspect of that is that the default would really depend on the management interface software, which really means it depends on the GUI. It is conceivable that two GUIs for the same platform could have different defaults (although I assume both Tunnelblick and Viscosity would use Keychain as the default, accessing it with keychain-mcd). |
From: Vasily K. <se...@op...> - 2015-02-23 14:40:27
|
On Mon, Feb 23, 2015 at 08:04 -0500, Jonathan K. Bullard wrote: > On Mon, Feb 23, 2015 at 4:00 AM, Gert Doering <ge...@gr...> wrote: > > > > On Mon, Feb 23, 2015 at 09:28:31AM +0100, Arne Schwabe wrote: > > > > What do you think of the change? > > > I like the idea. You could make the macos-keychain in the string optional. > > > > What Arne said (both parts of it) :-) > > I agree -- the argument to --needs-external-cert should be optional. Note: Arne said about 'macos-keychain' prefix in the argument being optional, not the argument itself being optional. Acually, I don't think making the argument optional is a good idea -- its parsing would be ambiguous unless it is the last argument in argv. > Note: the argument to --needs-external-cert should be passed on to > "RSA_SIGN", too. (I think Vasily omitted that from his writeup.) > > So the idea would be: > > * Add an optional UTF-8 string argument to --needs-external-cert. > (Perhaps the docs should say this requires support from the management > interface software and that currently such support is only available > when using certain GUIs on OS X.) Right. > * OpenVPN passes that argument to RSA_SIGN and NEEDS-CERTIFICATE, > passing an empty string if the argument does not appear. Why rsa-sign? I think doing it with NEEDS-CERTIFICATE is enough. Identity contains both cert and private key, certificate request is made at first. Rsa-sign uses already chosen identity. > * OS X GUIs such as Tunnelblick and Viscosity see the new RSA_SIGN or > NEEDS-CERTIFICATE argument and use keychain-mcd to deal with it. Other > GUIs ignore it or use something that does something equivalent to what > keychain-mcd does on OS X. Right. > I'm not sure exactly how to add an argument to RSA_SIGN and > NEEDS-CERTIFICATE without breaking existing management interface > software but assume that is possible. (Also, the argument may need to > be escaped when it is passed to RSA_SIGN or NEEDS-CERTIFICATE if it > contains characters that are used as delimiters.) IMNSHO don't change rsa-sign at all and have no API breakage. -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |
From: Gert D. <ge...@gr...> - 2015-02-23 15:48:24
|
Hi, On Mon, Feb 23, 2015 at 05:40:11PM +0300, Vasily Kulikov wrote: > > I agree -- the argument to --needs-external-cert should be optional. > > Note: Arne said about 'macos-keychain' prefix in the argument being > optional, not the argument itself being optional. Acually, I don't > think making the argument optional is a good idea -- its parsing would > be ambiguous unless it is the last argument in argv. Oh, *that* is something our config parser does all the time :-) The question to me is "if we don't want to specify anything particular, because there is only one cert anyway, how would we do it?" - or is the assumption that we better should specify it always? [..] > > I'm not sure exactly how to add an argument to RSA_SIGN and > > NEEDS-CERTIFICATE without breaking existing management interface > > software but assume that is possible. (Also, the argument may need to > > be escaped when it is passed to RSA_SIGN or NEEDS-CERTIFICATE if it > > contains characters that are used as delimiters.) > > IMNSHO don't change rsa-sign at all and have no API breakage. That sounds like a reasonable approach to me. gert -- USENET is *not* the non-clickable part of WWW! //www.muc.de/~gert/ Gert Doering - Munich, Germany ge...@gr... fax: +49-89-35655025 ge...@ne... |
From: Vasily K. <se...@op...> - 2015-02-25 16:07:35
Attachments:
openvpn-macosx-keychain-mcd-4.interdiff
|
This patch adds support for using certificates stored in the Mac OSX Keychain to authenticate with the OpenVPN server. This works with certificates stored on the computer as well as certificates on hardware tokens that support Apple's tokend interface. The patch is based on the Windows Crypto API certificate functionality that currently exists in OpenVPN. This patch version implements management client which handles RSA-SIGN command for RSA offloading. Also it handles new 'NEED-CERTIFICATE' request to pass a certificate from the keychain to OpenVPN. OpenVPN itself gets new 'NEED-CERTIFICATE" command which is called when --management-external-cert is used. It is implemented as a multiline command very similar to an existing 'RSA-SIGN' command. The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. v4: - added '--management-external-cert' argument - keychain-mcd now parses NEED-CERTIFICATE argument if 'auto' is passed as cmdline's identity template - fixed typo in help output option name - added '--management-external-cert' info in openvpn(8) manpage - added 'certificate' command documentation into doc/management-notes.txt v3: - used new 'NEED-CERTIFICATE' command for certificate data request instead of 'NEED-OK' - improved option checking - improved invalid certificate selection string handling - added man page for keychain-mcd - handle INFO, FATAL commands from openvpn and show them to user * ACK from Arne Schwabe for OpenVPN part * ACK from James based on Arne's testing v2 (http://sourceforge.net/p/openvpn/mailman/message/33225603/): - used management interface to communicate with OpenVPN process v1 (http://sourceforge.net/p/openvpn/mailman/message/33125844/): - used RSA_METHOD to extend openvpn itself Signed-off-by: Vasily Kulikov <se...@op...> -- diff --git a/.gitignore b/.gitignore index 538c020..f504ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ Debug Win32-Output .deps .libs -Makefile Makefile.in aclocal.m4 autodefs.h 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..4f06cdf --- /dev/null +++ b/contrib/keychain-mcd/cert_data.c @@ -0,0 +1,733 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <CommonCrypto/CommonDigest.h> +#include <openssl/ssl.h> + +#include "common_osx.h" +#include "crypto_osx.h" +#include <err.h> + +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); + 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; i<sizeof(fields)/sizeof(fields[0]); i++) + ret += tryAppendSingleCertField(pCert, fields[i].field, fields[i].key, name, value); + return ret; +} + +int parseCertName(CFStringRef nameDesc, CFMutableArrayRef names) +{ + CFArrayRef nameStrings = CFStringCreateArrayBySeparatingStrings(NULL, nameDesc, kCertNameFwdSlash); + int count = CFArrayGetCount(nameStrings); + int i; + int ret = 1; + + CertNameRef pCertName = createCertName(); + + for(i = 0;i < count;i++) + { + CFMutableStringRef dn = CFStringCreateMutableCopy(NULL, 0, CFArrayGetValueAtIndex(nameStrings, i)); + CFStringTrimWhitespace(dn); + + CFStringRef name, value; + + if (!parseNameComponent(dn, &name, &value)) + ret = 0; + + if (!name || !value) + { + if (name) + CFRelease(name); + + if (value) + CFRelease(value); + if (name && !value) + ret = 0; + + CFRelease(dn); + continue; + } + + if (!appendCertField(pCertName, name, value)) + ret = 0; + CFRelease(name); + CFRelease(value); + CFRelease(dn); + } + + CFArrayAppendValue(names, pCertName); + CFRelease(nameStrings); + return ret; +} + +int arrayParseDescDataPair(const void *val, void *context) +{ + DescDataRef pDescData = (DescDataRef)val; + CertDataRef pCertData = (CertDataRef)context; + int ret = 1; + + if (!pDescData->name || !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; i<CFArrayGetCount(pairs); i++) + if (!arrayParseDescDataPair(CFArrayGetValueAtIndex(pairs, i), pCertData)) { + arrayDestroyDescData(pCertData, NULL); + CFArrayApplyFunction(pairs, CFRangeMake(0, CFArrayGetCount(pairs)), arrayDestroyDescData, NULL); + CFRelease(pairs); + return 0; + } + + 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); + + 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<CFArrayGetCount(result); i++) + { + SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(result, i); + if (identity == NULL) + { + warnx ("identity == NULL"); + continue; + } + + SecCertificateRef certificate = NULL; + SecIdentityCopyCertificate (identity, &certificate); + if (certificate == NULL) + { + warnx ("SecIdentityCopyCertificate() returned NULL"); + continue; + } + + CertDataRef pCertData2 = createCertDataFromCertificate(certificate); + if (pCertData2 == NULL) + { + warnx ("createCertDataFromCertificate() returned NULL"); + goto release_cert; + } + bool bMatches = certDataMatchesTemplate(pCertData2, pCertDataTemplate); + bool bExpired = certExpired(certificate); + destroyCertData(pCertData2); + + if (bMatches && !bExpired) + { + CFDateRef notBeforeDate = GetDateFieldFromCertificate(certificate, kSecOIDX509V1ValidityNotBefore); + if (!notBeforeDate) + { + warnx ("GetDateFieldFromCertificate() returned NULL"); + goto release_cert; + } + if (bestIdentity == NULL) + { + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + else if (CFDateCompare(bestNotBeforeDate, notBeforeDate, NULL) == kCFCompareLessThan) + { + CFRelease(bestIdentity); + CFRetain(identity); + bestIdentity = identity; + + bestNotBeforeDate = notBeforeDate; + CFRetain(notBeforeDate); + } + CFRelease(notBeforeDate); + } + release_cert: + CFRelease(certificate); + } + CFRelease(result); + + return bestIdentity; +} diff --git a/contrib/keychain-mcd/cert_data.h b/contrib/keychain-mcd/cert_data.h new file mode 100644 index 0000000..407cca1 --- /dev/null +++ b/contrib/keychain-mcd/cert_data.h @@ -0,0 +1,46 @@ +/* + * 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <err.h> + +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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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__ diff --git a/contrib/keychain-mcd/crypto_osx.c b/contrib/keychain-mcd/crypto_osx.c new file mode 100644 index 0000000..eda7121 --- /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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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 <Security/SecKey.h> +#include <Security/Security.h> + +#include "crypto_osx.h" +#include <err.h> + +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 <br...@ir...> + * Copyright (C) 2013-2015 Vasily Kulikov <se...@op...> + * + * 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> + +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..ca86ea4 --- /dev/null +++ b/contrib/keychain-mcd/keychain-mcd.8 @@ -0,0 +1,162 @@ +.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 <se...@op...> + +.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..0641b03 --- /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 <se...@op...> + * + * 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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <err.h> +#include <netdb.h> + +#include <Security/Security.h> +#include <CoreServices/CoreServices.h> + +#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 <identity_template> <management_ip> <management_port> [<pw-file>]", 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/doc/management-notes.txt b/doc/management-notes.txt index ef39b85..028cf9e 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -777,6 +777,28 @@ correct signature. This capability is intended to allow the use of arbitrary cryptographic service providers with OpenVPN via the management interface. +COMMAND -- certificate (OpenVPN 2.3 or higher) +---------------------------------------------- +Provides support for external storage of the certificate. Requires the +--management-external-cert option. This option can be used instead of "cert" +in client mode. On SSL protocol initialization a notification will be sent +to the management interface with a hint as follows: + +>NEED-CERTIFICATE:macosx-keychain:subject:o=OpenVPN-TEST + +The management interface client should use the hint to obtain the specific +SSL certificate and then return base64 encoded certificate as follows: + +certificate +[BASE64_CERT_LINE] +. +. +. +END + +This capability is intended to allow the use of certificates +stored outside of the filesystem (e.g. in Mac OS X Keychain) +with OpenVPN via the management interface. OUTPUT FORMAT ------------- diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 96ba555..8a9eec7 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -2591,6 +2591,15 @@ Allows usage for external private key file instead of option (client-only). .\"********************************************************* .TP +.B \-\-management-external-cert certificate-hint +Allows usage for external certificate instead of +.B \-\-cert +option (client-only). +.B certificate-hint +is an arbitrary string which is passed to a management +interface client as an argument of NEED-CERTIFICATE notification. +.\"********************************************************* +.TP .B \-\-management-forget-disconnect Make OpenVPN forget passwords when management session disconnects. diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c index 46f874b..421d60e 100644 --- a/src/openvpn/buffer.c +++ b/src/openvpn/buffer.c @@ -1066,8 +1066,10 @@ buffer_list_peek (struct buffer_list *ol) } void -buffer_list_aggregate (struct buffer_list *bl, const size_t max) +buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep) { + int sep_len = strlen(sep); + if (bl->head) { struct buffer_entry *more = bl->head; @@ -1075,7 +1077,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) int count = 0; for (count = 0; more && size <= max; ++count) { - size += BLEN(&more->buf); + size += BLEN(&more->buf) + sep_len; more = more->next; } @@ -1092,6 +1094,7 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) { struct buffer_entry *next = e->next; buf_copy (&f->buf, &e->buf); + buf_write(&f->buf, sep, sep_len); free_buf (&e->buf); free (e); e = next; @@ -1105,6 +1108,12 @@ buffer_list_aggregate (struct buffer_list *bl, const size_t max) } void +buffer_list_aggregate (struct buffer_list *bl, const size_t max) +{ + buffer_list_aggregate_separator(bl, max, ""); +} + +void buffer_list_pop (struct buffer_list *ol) { if (ol && ol->head) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 7469da6..5695f64 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -931,6 +931,7 @@ void buffer_list_advance (struct buffer_list *ol, int n); void buffer_list_pop (struct buffer_list *ol); void buffer_list_aggregate (struct buffer_list *bl, const size_t max); +void buffer_list_aggregate_separator (struct buffer_list *bl, const size_t max, const char *sep); struct buffer_list *buffer_list_file (const char *fn, int max_line_len); #endif /* BUFFER_H */ diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c index 9f44cd9..cdfe2be 100644 --- a/src/openvpn/manage.c +++ b/src/openvpn/manage.c @@ -113,6 +113,8 @@ man_help () #ifdef MANAGMENT_EXTERNAL_KEY msg (M_CLIENT, "rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge"); msg (M_CLIENT, " Enter signature base64 on subsequent lines followed by END"); + msg (M_CLIENT, "certificate : Enter a client certificate in response to >NEED-CERT challenge"); + msg (M_CLIENT, " Enter certificate base64 on subsequent lines followed by END"); #endif msg (M_CLIENT, "signal s : Send signal s to daemon,"); msg (M_CLIENT, " s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2."); @@ -868,6 +870,12 @@ in_extra_dispatch (struct management *man) man->connection.ext_key_input = man->connection.in_extra; man->connection.in_extra = NULL; return; + case IEC_CERTIFICATE: + man->connection.ext_cert_state = EKS_READY; + buffer_list_free (man->connection.ext_cert_input); + man->connection.ext_cert_input = man->connection.in_extra; + man->connection.in_extra = NULL; + return; #endif } in_extra_reset (&man->connection, IER_RESET); @@ -1030,6 +1038,20 @@ man_rsa_sig (struct management *man) msg (M_CLIENT, "ERROR: The rsa-sig command is not currently available"); } +static void +man_certificate (struct management *man) +{ + struct man_connection *mc = &man->connection; + if (mc->ext_cert_state == EKS_SOLICIT) + { + mc->ext_cert_state = EKS_INPUT; + mc->in_extra_cmd = IEC_CERTIFICATE; + in_extra_reset (mc, IER_NEW); + } + else + msg (M_CLIENT, "ERROR: The certificate command is not currently available"); +} + #endif static void @@ -1311,6 +1333,10 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch { man_rsa_sig (man); } + else if (streq (p[0], "certificate")) + { + man_certificate (man); + } #endif #ifdef ENABLE_PKCS11 else if (streq (p[0], "pkcs11-id-count")) @@ -3097,15 +3123,14 @@ management_query_user_pass (struct management *man, #ifdef MANAGMENT_EXTERNAL_KEY -char * /* returns allocated base64 signature */ -management_query_rsa_sig (struct management *man, - const char *b64_data) +int +management_query_multiline (struct management *man, + const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input) { struct gc_arena gc = gc_new (); - char *ret = NULL; + int ret = 0; volatile int signal_received = 0; struct buffer alert_msg = clear_buf(); - struct buffer *buf; const bool standalone_disabled_save = man->persist.standalone_disabled; struct man_connection *mc = &man->connection; @@ -3114,10 +3139,15 @@ management_query_rsa_sig (struct management *man, man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ man->persist.special_state_msg = NULL; - mc->ext_key_state = EKS_SOLICIT; + *state = EKS_SOLICIT; - alert_msg = alloc_buf_gc (strlen(b64_data)+64, &gc); - buf_printf (&alert_msg, ">RSA_SIGN:%s", b64_data); + if (b64_data) { + alert_msg = alloc_buf_gc (strlen(b64_data)+strlen(prompt)+3, &gc); + buf_printf (&alert_msg, ">%s:%s", prompt, b64_data); + } else { + alert_msg = alloc_buf_gc (strlen(prompt)+3, &gc); + buf_printf (&alert_msg, ">%s", prompt); + } man_wait_for_client_connection (man, &signal_received, 0, MWCC_OTHER_WAIT); @@ -3135,40 +3165,107 @@ management_query_rsa_sig (struct management *man, man_check_for_signals (&signal_received); if (signal_received) goto done; - } while (mc->ext_key_state != EKS_READY); + } while (*state != EKS_READY); - if (buffer_list_defined(mc->ext_key_input)) - { - buffer_list_aggregate (mc->ext_key_input, 2048); - buf = buffer_list_peek (mc->ext_key_input); - if (buf && BLEN(buf) > 0) - { - ret = (char *) malloc(BLEN(buf)+1); - check_malloc_return(ret); - memcpy(ret, buf->data, BLEN(buf)); - ret[BLEN(buf)] = '\0'; - } - } + ret = 1; } done: - if (mc->ext_key_state == EKS_READY && ret) - msg (M_CLIENT, "SUCCESS: rsa-sig command succeeded"); - else if (mc->ext_key_state == EKS_INPUT || mc->ext_key_state == EKS_READY) - msg (M_CLIENT, "ERROR: rsa-sig command failed"); + if (*state == EKS_READY && ret) + msg (M_CLIENT, "SUCCESS: %s command succeeded", cmd); + else if (*state == EKS_INPUT || *state == EKS_READY) + msg (M_CLIENT, "ERROR: %s command failed", cmd); /* revert state */ man->persist.standalone_disabled = standalone_disabled_save; man->persist.special_state_msg = NULL; in_extra_reset (mc, IER_RESET); - mc->ext_key_state = EKS_UNDEF; - buffer_list_free (mc->ext_key_input); - mc->ext_key_input = NULL; + *state = EKS_UNDEF; gc_free (&gc); return ret; } +char * /* returns allocated base64 signature */ +management_query_multiline_flatten_newline (struct management *man, + const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input) +{ + int ok; + char *result = NULL; + struct buffer *buf; + + ok = management_query_multiline(man, b64_data, prompt, cmd, state, input); + if (ok && buffer_list_defined(*input)) + { + buffer_list_aggregate_separator (*input, 10000, "\n"); + buf = buffer_list_peek (*input); + if (buf && BLEN(buf) > 0) + { + result = (char... [truncated message content] |
From: Gert D. <ge...@gr...> - 2015-02-27 18:28:45
|
Hi, On Wed, Feb 25, 2015 at 07:07:18PM +0300, Vasily Kulikov wrote: > The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. > > v4: > - added '--management-external-cert' argument > - keychain-mcd now parses NEED-CERTIFICATE argument if 'auto' is passed > as cmdline's identity template > - fixed typo in help output option name > - added '--management-external-cert' info in openvpn(8) manpage > - added 'certificate' command documentation into doc/management-notes.txt Sorry to be nagging... something in your patch was garbled, it contained stuff like ----------------------- only in patch2: unchanged: --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -777,6 +777,28 @@ correct signature. This capability is intended to allow the use of arbitrary cryptographic service providers with OpenVPN via the management interface. ... ----------------------- (the diff for doc/management-notes.txt is in there twice), and there is a patch for .gitignore in it as well. Please generate the patch with "git format-patch", that should avoid spurious stuff. Also, in the doc/management-notes.txt, it has +COMMAND -- certificate (OpenVPN 2.3 or higher) please make that "2.4", as this code change is too large to go into 2.3 (where we only do bug fixes and long-term stability stuff, but no new features generally) gert -- USENET is *not* the non-clickable part of WWW! //www.muc.de/~gert/ Gert Doering - Munich, Germany ge...@gr... fax: +49-89-35655025 ge...@ne... |
From: Vasily K. <se...@op...> - 2015-02-27 19:27:58
|
Hi Gert, On Fri, Feb 27, 2015 at 19:28 +0100, Gert Doering wrote: > On Wed, Feb 25, 2015 at 07:07:18PM +0300, Vasily Kulikov wrote: > > The patch is against commit 3341a98c2852d1d0c1eafdc70a3bdb218ec29049. > > > > v4: > > - added '--management-external-cert' argument > > - keychain-mcd now parses NEED-CERTIFICATE argument if 'auto' is passed > > as cmdline's identity template > > - fixed typo in help output option name > > - added '--management-external-cert' info in openvpn(8) manpage > > - added 'certificate' command documentation into doc/management-notes.txt > > Sorry to be nagging... something in your patch was garbled, it contained > stuff like > > ----------------------- > only in patch2: > unchanged: > --- a/doc/management-notes.txt > +++ b/doc/management-notes.txt > @@ -777,6 +777,28 @@ correct signature. > This capability is intended to allow the use of arbitrary cryptographic > service providers with OpenVPN via the management interface. > ... > ----------------------- This stuff is missing in the patch itself which is in the email text, and is contained in the attached interdiff file which contains changes between patch v3 and v4. AFAICS, the patch doesn't contain any garbage. > (the diff for doc/management-notes.txt is in there twice), and there > is a patch for .gitignore in it as well. I've included .gitignore changes as my patch adds Makefile changes. It would be rather uncomfortable for openvpn developers to see Makefile and be not able to change it. > Please generate the patch with "git format-patch", that should avoid > spurious stuff. > > > Also, in the doc/management-notes.txt, it has > > +COMMAND -- certificate (OpenVPN 2.3 or higher) > > please make that "2.4", as this code change is too large to go into 2.3 > (where we only do bug fixes and long-term stability stuff, but no new > features generally) It makes sense. -- Vasily Kulikov http://www.openwall.com - bringing security into open computing environments |