diff options
Diffstat (limited to 'main/openvpn')
-rw-r--r-- | main/openvpn/.gitignore | 1 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/cert_data.c | 733 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/cert_data.h | 46 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/common_osx.c | 94 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/common_osx.h | 36 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/crypto_osx.c | 75 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/crypto_osx.h | 44 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/keychain-mcd.8 | 149 | ||||
-rw-r--r-- | main/openvpn/contrib/keychain-mcd/main.c | 244 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/buffer.c | 13 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/buffer.h | 1 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/error.c | 2 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/init.c | 24 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/manage.c | 145 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/manage.h | 5 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/mtu.h | 6 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/mudp.c | 9 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/options.c | 19 | ||||
-rw-r--r-- | main/openvpn/src/openvpn/ssl.c | 26 |
19 files changed, 1628 insertions, 44 deletions
diff --git a/main/openvpn/.gitignore b/main/openvpn/.gitignore index 06ff7c66..1d5191f3 100644 --- a/main/openvpn/.gitignore +++ b/main/openvpn/.gitignore @@ -19,7 +19,6 @@ Debug Win32-Output .deps .libs -Makefile Makefile.in aclocal.m4 autodefs.h diff --git a/main/openvpn/contrib/keychain-mcd/cert_data.c b/main/openvpn/contrib/keychain-mcd/cert_data.c new file mode 100644 index 00000000..f2b33edc --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/cert_data.h b/main/openvpn/contrib/keychain-mcd/cert_data.h new file mode 100644 index 00000000..407cca1c --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/common_osx.c b/main/openvpn/contrib/keychain-mcd/common_osx.c new file mode 100644 index 00000000..3effa8b0 --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/common_osx.h b/main/openvpn/contrib/keychain-mcd/common_osx.h new file mode 100644 index 00000000..42735486 --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/crypto_osx.c b/main/openvpn/contrib/keychain-mcd/crypto_osx.c new file mode 100644 index 00000000..87ba09ba --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/crypto_osx.h b/main/openvpn/contrib/keychain-mcd/crypto_osx.h new file mode 100644 index 00000000..0da58b60 --- /dev/null +++ b/main/openvpn/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 <brian@irregularexpression.org> + * Copyright (C) 2013-2015 Vasily Kulikov <segoon@openwall.com> + * + * 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/main/openvpn/contrib/keychain-mcd/keychain-mcd.8 b/main/openvpn/contrib/keychain-mcd/keychain-mcd.8 new file mode 100644 index 00000000..c4581e7b --- /dev/null +++ b/main/openvpn/contrib/keychain-mcd/keychain-mcd.8 @@ -0,0 +1,149 @@ +.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 <segoon@openwall.com> + +.SH "SEE ALSO" + +.BR openvpn (8) diff --git a/main/openvpn/contrib/keychain-mcd/main.c b/main/openvpn/contrib/keychain-mcd/main.c new file mode 100644 index 00000000..5f59403c --- /dev/null +++ b/main/openvpn/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 <segoon@openwall.com> + * + * 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/main/openvpn/src/openvpn/buffer.c b/main/openvpn/src/openvpn/buffer.c index 46f874b2..421d60e9 100644 --- a/main/openvpn/src/openvpn/buffer.c +++ b/main/openvpn/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/main/openvpn/src/openvpn/buffer.h b/main/openvpn/src/openvpn/buffer.h index 7469da63..5695f64f 100644 --- a/main/openvpn/src/openvpn/buffer.h +++ b/main/openvpn/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/main/openvpn/src/openvpn/error.c b/main/openvpn/src/openvpn/error.c index 72ebfab6..08503238 100644 --- a/main/openvpn/src/openvpn/error.c +++ b/main/openvpn/src/openvpn/error.c @@ -323,7 +323,7 @@ void x_msg_va (const unsigned int flags, const char *format, va_list arglist) fprintf (fp, "%lu.%06lu %x %s%s%s%s", tv.tv_sec, - tv.tv_usec, + (unsigned long)tv.tv_usec, flags, prefix, prefix_sep, diff --git a/main/openvpn/src/openvpn/init.c b/main/openvpn/src/openvpn/init.c index 77827a57..077b130e 100644 --- a/main/openvpn/src/openvpn/init.c +++ b/main/openvpn/src/openvpn/init.c @@ -1794,6 +1794,19 @@ do_deferred_options (struct context *c, const unsigned int found) msg (D_PUSH, "OPTIONS IMPORT: peer-id set"); c->c2.tls_multi->use_peer_id = true; c->c2.tls_multi->peer_id = c->options.peer_id; + frame_add_to_extra_frame(&c->c2.frame, +3); /* peer-id overhead */ + if ( !c->options.ce.link_mtu_defined ) + { + frame_add_to_link_mtu(&c->c2.frame, +3); + msg (D_PUSH, "OPTIONS IMPORT: adjusting link_mtu to %d", + EXPANDED_SIZE(&c->c2.frame)); + } + else + { + msg (M_WARN, "OPTIONS IMPORT: WARNING: peer-id set, but link-mtu" + " fixed by config - reducing tun-mtu to %d, expect" + " MTU problems", TUN_MTU_SIZE(&c->c2.frame) ); + } } #endif } @@ -2403,6 +2416,17 @@ do_init_frame (struct context *c) #endif #endif /* USE_COMP */ + /* packets with peer-id (P_DATA_V2) need 3 extra bytes in frame (on client) + * and need link_mtu+3 bytes on socket reception (on server). + * + * accomodate receive path in f->extra_link, which has the side effect of + * also increasing send buffers (BUF_SIZE() macro), which need to be + * allocated big enough before receiving peer-id option from server. + * + * f->extra_frame is adjusted when peer-id option is push-received + */ + frame_add_to_extra_link(&c->c2.frame, 3); + #ifdef ENABLE_FRAGMENT /* * Set frame parameter for fragment code. This is necessary because diff --git a/main/openvpn/src/openvpn/manage.c b/main/openvpn/src/openvpn/manage.c index 27164f18..6617ccd4 100644 --- a/main/openvpn/src/openvpn/manage.c +++ b/main/openvpn/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 @@ -1332,6 +1354,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")) @@ -3118,15 +3144,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; @@ -3135,10 +3160,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); @@ -3156,40 +3186,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/main/openvpn/src/openvpn/manage.h b/main/openvpn/src/openvpn/manage.h index c48875c7..d9da0063 100644 --- a/main/openvpn/src/openvpn/manage.h +++ b/main/openvpn/src/openvpn/manage.h @@ -271,6 +271,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 @@ -284,6 +285,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; @@ -341,6 +344,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, @@ -423,6 +427,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/main/openvpn/src/openvpn/mtu.h b/main/openvpn/src/openvpn/mtu.h index 29ec21fd..bccd6810 100644 --- a/main/openvpn/src/openvpn/mtu.h +++ b/main/openvpn/src/openvpn/mtu.h @@ -258,6 +258,12 @@ frame_headroom (const struct frame *f, const unsigned int flag_mask) */ static inline void +frame_add_to_link_mtu (struct frame *frame, const int increment) +{ + frame->link_mtu += increment; +} + +static inline void frame_add_to_extra_frame (struct frame *frame, const int increment) { frame->extra_frame += increment; diff --git a/main/openvpn/src/openvpn/mudp.c b/main/openvpn/src/openvpn/mudp.c index 3e3f7508..57118f87 100644 --- a/main/openvpn/src/openvpn/mudp.c +++ b/main/openvpn/src/openvpn/mudp.c @@ -52,20 +52,19 @@ multi_get_create_instance_udp (struct multi_context *m, bool *floated) struct multi_instance *mi = NULL; struct hash *hash = m->hash; - if (mroute_extract_openvpn_sockaddr (&real, &m->top.c2.from.dest, true)) + if (mroute_extract_openvpn_sockaddr (&real, &m->top.c2.from.dest, true) && + m->top.c2.buf.len > 0) { struct hash_element *he; const uint32_t hv = hash_value (hash, &real); struct hash_bucket *bucket = hash_bucket (hash, hv); uint8_t* ptr = BPTR(&m->top.c2.buf); uint8_t op = ptr[0] >> P_OPCODE_SHIFT; - uint32_t peer_id; - int i; /* make sure buffer has enough length to read opcode (1 byte) and peer-id (3 bytes) */ if (op == P_DATA_V2 && m->top.c2.buf.len >= (1 + 3)) { - peer_id = ntohl(*(uint32_t*)ptr) & 0xFFFFFF; + uint32_t peer_id = ntohl(*(uint32_t*)ptr) & 0xFFFFFF; if ((peer_id < m->max_clients) && (m->instances[peer_id])) { mi = m->instances[peer_id]; @@ -99,6 +98,8 @@ multi_get_create_instance_udp (struct multi_context *m, bool *floated) mi = multi_create_instance (m, &real); if (mi) { + int i; + hash_add_fast (hash, bucket, &mi->real, hv, mi); mi->did_real_hash = true; diff --git a/main/openvpn/src/openvpn/options.c b/main/openvpn/src/openvpn/options.c index e2897984..c2a377b3 100644 --- a/main/openvpn/src/openvpn/options.c +++ b/main/openvpn/src/openvpn/options.c @@ -1568,6 +1568,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 @@ -2144,6 +2149,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne #ifdef MANAGMENT_EXTERNAL_KEY if (options->management_flags & MF_EXTERNAL_KEY) msg(M_USAGE, "Parameter --management-external-key cannot be used when --pkcs11-provider is also specified."); + if (options->management_flags & MF_EXTERNAL_CERT) + msg(M_USAGE, "Parameter --management-external-cert cannot be used when --pkcs11-provider is also specified."); #endif if (options->pkcs12_file) msg(M_USAGE, "Parameter --pkcs12 cannot be used when --pkcs11-provider is also specified."); @@ -2175,6 +2182,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne #ifdef MANAGMENT_EXTERNAL_KEY if (options->management_flags & MF_EXTERNAL_KEY) msg(M_USAGE, "Parameter --management-external-key cannot be used when --cryptoapicert is also specified."); + if (options->management_flags & MF_EXTERNAL_CERT) + msg(M_USAGE, "Parameter --management-external-cert cannot be used when --cryptoapicert is also specified."); #endif } else @@ -2193,6 +2202,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne #ifdef MANAGMENT_EXTERNAL_KEY if (options->management_flags & MF_EXTERNAL_KEY) msg(M_USAGE, "Parameter --external-management-key cannot be used when --pkcs12 is also specified."); + if (options->management_flags & MF_EXTERNAL_CERT) + msg(M_USAGE, "Parameter --external-management-cert cannot be used when --pkcs12 is also specified."); #endif #endif } @@ -2234,6 +2245,9 @@ options_postprocess_verify_ce (const struct options *options, const struct conne } else { +#ifdef MANAGMENT_EXTERNAL_KEY + if (!(options->management_flags & MF_EXTERNAL_CERT)) +#endif notnull (options->cert_file, "certificate file (--cert) or PKCS#12 file (--pkcs12)"); #ifdef MANAGMENT_EXTERNAL_KEY if (!(options->management_flags & MF_EXTERNAL_KEY)) @@ -4232,6 +4246,11 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_GENERAL); options->management_flags |= MF_EXTERNAL_KEY; } + else if (streq (p[0], "management-external-cert")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->management_flags |= MF_EXTERNAL_CERT; + } #endif #ifdef MANAGEMENT_DEF_AUTH else if (streq (p[0], "management-client-auth")) diff --git a/main/openvpn/src/openvpn/ssl.c b/main/openvpn/src/openvpn/ssl.c index 80293efd..59bb2a12 100644 --- a/main/openvpn/src/openvpn/ssl.c +++ b/main/openvpn/src/openvpn/ssl.c @@ -264,14 +264,16 @@ tls_get_cipher_name_pair (const char * cipher_name, size_t len) { return NULL; } -/** - * Max number of bytes we will add for data structures common to both data and - * control channel packets (1 byte opcode + 3 bytes peer-id). +/* + * Max number of bytes we will add + * for data structures common to both + * data and control channel packets. + * (opcode only). */ void tls_adjust_frame_parameters(struct frame *frame) { - frame_add_to_extra_frame (frame, 1 + 3); /* space for opcode + peer-id */ + frame_add_to_extra_frame (frame, 1); /* space for opcode */ } /* @@ -518,10 +520,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 |