diff options
Diffstat (limited to 'cpp/src/IceSSL/SecureTransportUtil.cpp')
-rw-r--r-- | cpp/src/IceSSL/SecureTransportUtil.cpp | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/cpp/src/IceSSL/SecureTransportUtil.cpp b/cpp/src/IceSSL/SecureTransportUtil.cpp new file mode 100644 index 00000000000..1108560cd8b --- /dev/null +++ b/cpp/src/IceSSL/SecureTransportUtil.cpp @@ -0,0 +1,855 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#include <IceSSL/SecureTransportUtil.h> +#include <IceSSL/Plugin.h> + +#include <Ice/Base64.h> +#include <Ice/UniqueRef.h> + +#include <IceUtil/FileUtil.h> +#include <IceUtil/StringUtil.h> + +#include <fstream> + +#include <Security/Security.h> +#include <CoreFoundation/CoreFoundation.h> + +using namespace Ice; +using namespace IceInternal; +using namespace IceSSL; +using namespace IceSSL::SecureTransport; +using namespace std; + + +namespace +{ + +CFMutableDataRef +readCertFile(const string& file) +{ + ifstream is(IceUtilInternal::streamFilename(file).c_str(), ios::in | ios::binary); + if(!is.good()) + { + throw CertificateReadException(__FILE__, __LINE__, "error opening file " + file); + } + + is.seekg(0, is.end); + size_t size = is.tellg(); + is.seekg(0, is.beg); + + UniqueRef<CFMutableDataRef> data(CFDataCreateMutable(kCFAllocatorDefault, size)); + CFDataSetLength(data.get(), size); + is.read(reinterpret_cast<char*>(CFDataGetMutableBytePtr(data.get())), size); + if(!is.good()) + { + throw CertificateReadException(__FILE__, __LINE__, "error reading file " + file); + } + return data.release(); +} + +} + +string +IceSSL::SecureTransport::sslErrorToString(CFErrorRef err) +{ + ostringstream os; + if(err) + { + UniqueRef<CFStringRef> s(CFErrorCopyDescription(err)); + os << "(error: " << CFErrorGetCode(err) << " description: " << fromCFString(s.get()) << ")"; + } + return os.str(); +} + +string +IceSSL::SecureTransport::sslErrorToString(OSStatus status) +{ + ostringstream os; + os << "(error: " << status; +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) + UniqueRef<CFStringRef> s(SecCopyErrorMessageString(status, 0)); + if(s) + { + os << " description: " << fromCFString(s.get()); + } +#endif + os << ")"; + return os.str(); +} + +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) +CFDictionaryRef +IceSSL::SecureTransport::getCertificateProperty(SecCertificateRef cert, CFTypeRef key) +{ + UniqueRef<CFDictionaryRef> property; + UniqueRef<CFArrayRef> keys(CFArrayCreate(ICE_NULLPTR, &key , 1, &kCFTypeArrayCallBacks)); + UniqueRef<CFErrorRef> err; + UniqueRef<CFDictionaryRef> values(SecCertificateCopyValues(cert, keys.get(), &err.get())); + if(err) + { + ostringstream os; + os << "IceSSL: error getting property for certificate:\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + + assert(values); + property.retain(static_cast<CFDictionaryRef>(CFDictionaryGetValue(values.get(), key))); + return property.release(); +} + +namespace +{ + +// +// Check the certificate basic constraints to check if the certificate is marked as a CA. +// +bool +isCA(SecCertificateRef cert) +{ + UniqueRef<CFDictionaryRef> property(getCertificateProperty(cert, kSecOIDBasicConstraints)); + if(property) + { + CFArrayRef propertyValues = static_cast<CFArrayRef>(CFDictionaryGetValue(property.get(), kSecPropertyKeyValue)); + for(int i = 0, size = CFArrayGetCount(propertyValues); i < size; ++i) + { + CFDictionaryRef dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(propertyValues, i)); + CFStringRef label = static_cast<CFStringRef>(CFDictionaryGetValue(dict, kSecPropertyKeyLabel)); + if(CFEqual(label, CFSTR("Certificate Authority"))) + { + return CFEqual(static_cast<CFStringRef>(CFDictionaryGetValue(dict, kSecPropertyKeyValue)), CFSTR("Yes")); + } + } + } + return false; +} + +// +// Load keychain items (Certificates or Private Keys) from a file. On return items param contain +// the list of items, the caller must release it. +// +CFArrayRef +loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef keychain, const string& passphrase, + const PasswordPromptPtr& prompt, int retryMax) +{ + UniqueRef<CFMutableDataRef> data(readCertFile(file)); + + SecItemImportExportKeyParameters params; + memset(¶ms, 0, sizeof(params)); + params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + params.flags |= kSecKeyNoAccessControl; + if(!passphrase.empty()) + { + params.passphrase = toCFString(passphrase); + } + + UniqueRef<CFArrayRef> items; + SecExternalItemType importType = type; + SecExternalFormat format = type == kSecItemTypeUnknown ? kSecFormatPKCS12 : kSecFormatUnknown; + UniqueRef<CFStringRef> path(toCFString(file)); + OSStatus err = SecItemImport(data.get(), path.get(), &format, &importType, 0, ¶ms, keychain, &items.get()); + + // + // If passphrase failure and no password was configured, we obtain + // the password from the given prompt or configure the import to + // prompt the user with an alert dialog. + // + UniqueRef<CFStringRef> passphraseHolder; + UniqueRef<CFStringRef> alertPromptHolder; + if(passphrase.empty() && + (err == errSecPassphraseRequired || err == errSecInvalidData || err == errSecPkcs12VerifyFailure)) + { + if(!prompt) + { + params.flags |= kSecKeySecurePassphrase; + ostringstream os; + os << "Enter the password for\n" << file; + alertPromptHolder.reset(toCFString(os.str())); + params.alertPrompt = alertPromptHolder.get(); + } + + int count = 0; + while((err == errSecPassphraseRequired || err == errSecInvalidData || err == errSecPkcs12VerifyFailure) && + count < retryMax) + { + if(prompt) + { + passphraseHolder.reset(toCFString(prompt->getPassword())); + params.passphrase = passphraseHolder.get(); + } + err = SecItemImport(data.get(), path.get(), &format, &importType, 0, ¶ms, keychain, &items.get()); + ++count; + } + } + + if(err != noErr) + { + ostringstream os; + os << "IceSSL: error reading " << (type == kSecItemTypePrivateKey ? "private key" : "certificate"); + os << " `" << file << "':\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + + if(type != kSecItemTypeUnknown && importType != kSecItemTypeAggregate && importType != type) + { + ostringstream os; + os << "IceSSL: error reading " << (type == kSecItemTypePrivateKey ? "private key" : "certificate"); + os << " `" << file << "' doesn't contain the expected item"; + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + + return items.release(); +} + +SecKeychainRef +openKeychain(const std::string& path, const std::string& keychainPassword) +{ + string keychainPath = path; + UniqueRef<SecKeychainRef> keychain; + OSStatus err = 0; + if(keychainPath.empty()) + { + if((err = SecKeychainCopyDefault(&keychain.get()))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to retrieve default keychain:\n" + sslErrorToString(err)); + } + } + else + { + // + // KeyChain path is relative to the current working directory. + // + if(!IceUtilInternal::isAbsolutePath(keychainPath)) + { + string cwd; + if(IceUtilInternal::getcwd(cwd) == 0) + { + keychainPath = string(cwd) + '/' + keychainPath; + } + } + + if((err = SecKeychainOpen(keychainPath.c_str(), &keychain.get()))) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: unable to open keychain: `" + + keychainPath + "'\n" + sslErrorToString(err)); + } + } + + SecKeychainStatus status; + err = SecKeychainGetStatus(keychain.get(), &status); + if(err == noErr) + { + const char* pass = keychainPassword.empty() ? 0 : keychainPassword.c_str(); + if((err = SecKeychainUnlock(keychain.get(), keychainPassword.size(), pass, pass != 0))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to unlock keychain:\n" + sslErrorToString(err)); + } + } + else if(err == errSecNoSuchKeychain) + { + const char* pass = keychainPassword.empty() ? 0 : keychainPassword.c_str(); + keychain.reset(0); + if((err = SecKeychainCreate(keychainPath.c_str(), keychainPassword.size(), pass, pass == 0, 0, &keychain.get()))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to create keychain:\n" + sslErrorToString(err)); + } + } + else + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to open keychain:\n" + sslErrorToString(err)); + } + + // + // Set keychain settings to avoid keychain lock. + // + SecKeychainSettings settings; + settings.version = SEC_KEYCHAIN_SETTINGS_VERS1; + settings.lockOnSleep = FALSE; + settings.useLockInterval = FALSE; + settings.lockInterval = INT_MAX; + if((err = SecKeychainSetSettings(keychain.get(), &settings))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error setting keychain settings:\n" + sslErrorToString(err)); + } + + return keychain.release(); +} + +// +// Imports a certificate private key and optionally add it to a keychain. +// +SecIdentityRef +loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRef keychain, const string& password, + const PasswordPromptPtr& prompt, int retryMax) +{ + // + // Check if we already imported the certificate + // + UniqueRef<CFDataRef> hash; + UniqueRef<CFDictionaryRef> subjectKeyProperty(getCertificateProperty(cert, kSecOIDSubjectKeyIdentifier)); + if(subjectKeyProperty) + { + CFArrayRef values = static_cast<CFArrayRef>(CFDictionaryGetValue(subjectKeyProperty.get(), + kSecPropertyKeyValue)); + for(int i = 0; i < CFArrayGetCount(values); ++i) + { + CFDictionaryRef dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(values, i)); + if(CFEqual(CFDictionaryGetValue(dict, kSecPropertyKeyLabel), CFSTR("Key Identifier"))) + { + hash.retain(static_cast<CFDataRef>(CFDictionaryGetValue(dict, kSecPropertyKeyValue))); + break; + } + } + } + + const void* values[] = { keychain }; + UniqueRef<CFArrayRef> searchList(CFArrayCreate(kCFAllocatorDefault, values, 1, &kCFTypeArrayCallBacks)); + + UniqueRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(0, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CFDictionarySetValue(query.get(), kSecClass, kSecClassCertificate); + CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitOne); + CFDictionarySetValue(query.get(), kSecMatchSearchList, searchList.get()); + CFDictionarySetValue(query.get(), kSecAttrSubjectKeyID, hash.get()); + CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); + + UniqueRef<CFTypeRef> value(0); + OSStatus err = SecItemCopyMatching(query.get(), &value.get()); + UniqueRef<SecCertificateRef> item(static_cast<SecCertificateRef>(const_cast<void*>(value.release()))); + if(err == noErr) + { + // + // If the certificate has already been imported, create the + // identity. The key should also have been imported. + // + UniqueRef<SecIdentityRef> identity; + err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity.get()); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: error creating certificate identity:\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + return identity.release(); + } + else if(err != errSecItemNotFound) + { + ostringstream os; + os << "IceSSL: error searching for keychain items:\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + + // + // If the certificate isn't already in the keychain, load the + // private key into the keychain and add the certificate. + // + UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypePrivateKey, keychain, password, prompt, retryMax)); + int count = CFArrayGetCount(items.get()); + UniqueRef<SecKeyRef> key; + for(int i = 0; i < count; ++i) + { + SecKeychainItemRef item = + static_cast<SecKeychainItemRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), 0))); + if(SecKeyGetTypeID() == CFGetTypeID(item)) + { + key.retain(reinterpret_cast<SecKeyRef>(item)); + break; + } + } + if(!key) + { + throw CertificateReadException(__FILE__, __LINE__, "IceSSL: no key in file `" + file + "'"); + } + + // + // Add the certificate to the keychain + // + query.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CFDictionarySetValue(query.get(), kSecUseKeychain, keychain); + CFDictionarySetValue(query.get(), kSecClass, kSecClassCertificate); + CFDictionarySetValue(query.get(), kSecValueRef, cert); + CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); + + value.reset(0); + err = SecItemAdd(query.get(), static_cast<CFTypeRef*>(&value.get())); + UniqueRef<CFArrayRef> added(static_cast<CFArrayRef>(value.release())); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: failure adding certificate to keychain\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + item.retain(static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(added.get(), 0)))); + + // + // Create the association between the private key and the certificate, + // kSecKeyLabel attribute should match the subject key identifier. + // + vector<SecKeychainAttribute> attributes; + if(hash) + { + SecKeychainAttribute attr; + attr.tag = kSecKeyLabel; + attr.data = const_cast<UInt8*>(CFDataGetBytePtr(hash.get())); + attr.length = CFDataGetLength(hash.get()); + attributes.push_back(attr); + } + + // + // kSecKeyPrintName attribute correspond to the keychain display + // name. + // + string label; + UniqueRef<CFStringRef> commonName(0); + if(SecCertificateCopyCommonName(item.get(), &commonName.get()) == noErr) + { + label = fromCFString(commonName.get()); + SecKeychainAttribute attr; + attr.tag = kSecKeyPrintName; + attr.data = const_cast<char*>(label.c_str()); + attr.length = label.size(); + attributes.push_back(attr); + } + + SecKeychainAttributeList attrs; + attrs.attr = &attributes[0]; + attrs.count = attributes.size(); + SecKeychainItemModifyAttributesAndData(reinterpret_cast<SecKeychainItemRef>(key.get()), &attrs, 0, 0); + + UniqueRef<SecIdentityRef> identity; + err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity.get()); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: error creating certificate identity:\n" << sslErrorToString(err); + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + return identity.release(); +} + +} // anonymous namespace end + +#else + +namespace +{ + +CFArrayRef +loadCerts(const string& file) +{ + UniqueRef<CFArrayRef> certs(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + if(file.find(".pem") != string::npos) + { + vector<char> buffer; + readFile(file, buffer); + string strbuf(buffer.begin(), buffer.end()); + string::size_type size, startpos, endpos = 0; + bool first = true; + while(true) + { + startpos = strbuf.find("-----BEGIN CERTIFICATE-----", endpos); + if(startpos != string::npos) + { + startpos += sizeof("-----BEGIN CERTIFICATE-----"); + endpos = strbuf.find("-----END CERTIFICATE-----", startpos); + if(endpos == string::npos) + { + InitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: certificate " + file + " is not a valid PEM-encoded certificate"; + throw ex; + } + size = endpos - startpos; + } + else if(first) + { + startpos = 0; + endpos = string::npos; + size = strbuf.size(); + } + else + { + break; + } + + vector<unsigned char> data(IceInternal::Base64::decode(string(&buffer[startpos], size))); + UniqueRef<CFDataRef> certdata(CFDataCreate(kCFAllocatorDefault, &data[0], data.size())); + UniqueRef<SecCertificateRef> cert(SecCertificateCreateWithData(0, certdata.get())); + if(!cert) + { + InitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: certificate " + file + " is not a valid PEM-encoded certificate"; + throw ex; + } + CFArrayAppendValue(const_cast<CFMutableArrayRef>(certs.get()), cert.get()); + first = false; + } + } + else + { + UniqueRef<CFDataRef> data(readCertFile(file)); + UniqueRef<SecCertificateRef> cert(SecCertificateCreateWithData(0, data.get())); + if(!cert) + { + InitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: certificate " + file + " is not a valid DER-encoded certificate"; + throw ex; + } + CFArrayAppendValue(const_cast<CFMutableArrayRef>(certs.get()), cert.get()); + } + return certs.release(); +} + +} + +#endif + +// +// Imports a certificate (it might contain an identity or certificate depending on the format). +// +CFArrayRef +IceSSL::SecureTransport::loadCertificateChain(const string& file, + const string& keyFile, + const std::string& keychainPath, + const string& keychainPassword, + const string& password, + const PasswordPromptPtr& prompt, + int retryMax) +{ + UniqueRef<CFArrayRef> chain; +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + UniqueRef<CFDataRef> cert(readCertFile(file)); + + UniqueRef<CFMutableDictionaryRef> settings(CFDictionaryCreateMutable(0, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + UniqueRef<CFArrayRef> items; + OSStatus err; + int count = 0; + do + { + UniqueRef<CFStringRef> pass(toCFString(password.empty() && prompt ? prompt->getPassword() : password)); + CFDictionarySetValue(settings.get(), kSecImportExportPassphrase, pass.get()); + err = SecPKCS12Import(cert.get(), settings.get(), &items.get()); + ++count; + } + while(password.empty() && prompt && err == errSecAuthFailed && count < retryMax); + + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to import certificate from file " << file << " (error = " << err << ")"; + throw InitializationException(__FILE__, __LINE__, os.str()); + } + + for(int i = 0; i < CFArrayGetCount(items.get()); ++i) + { + CFDictionaryRef dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(items.get(), i)); + SecIdentityRef identity = static_cast<SecIdentityRef>( + const_cast<void*>(CFDictionaryGetValue(dict, kSecImportItemIdentity))); + if(identity) + { + CFArrayRef certs = static_cast<CFArrayRef>(CFDictionaryGetValue(dict, kSecImportItemCertChain)); + chain.reset(CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certs)); + CFArraySetValueAtIndex(const_cast<CFMutableArrayRef>(chain.get()), 0, identity); + } + } + + if(!chain) + { + ostringstream os; + os << "IceSSL: couldn't find identity in file " << file; + throw InitializationException(__FILE__, __LINE__, os.str()); + } +#else + UniqueRef<SecKeychainRef> keychain(openKeychain(keychainPath, keychainPassword)); + if(keyFile.empty()) + { + chain.reset(loadKeychainItems(file, kSecItemTypeUnknown, keychain.get(), password, prompt, retryMax)); + } + else + { + // + // Load the certificate, don't load into the keychain as it + // might already have been imported. + // + UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, password, prompt, retryMax)); + SecCertificateRef cert = + static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), 0))); + if(SecCertificateGetTypeID() != CFGetTypeID(cert)) + { + ostringstream os; + os << "IceSSL: couldn't find certificate in `" << file << "'"; + throw CertificateReadException(__FILE__, __LINE__, os.str()); + } + + // + // Load the private key for the given certificate. This will + // add the certificate/key to the keychain if they aren't + // already present in the keychain. + // + UniqueRef<SecIdentityRef> identity(loadPrivateKey(keyFile, cert, keychain.get(), password, prompt, retryMax)); + chain.reset(CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, items.get())); + CFArraySetValueAtIndex(const_cast<CFMutableArrayRef>(chain.get()), 0, identity.get()); + } +#endif + return chain.release(); +} + +SecCertificateRef +IceSSL::SecureTransport::loadCertificate(const string& file) +{ + UniqueRef<SecCertificateRef> cert; +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + UniqueRef<CFArrayRef> certs(loadCerts(file)); + assert(CFArrayGetCount(certs.get()) > 0); + cert.retain((SecCertificateRef)CFArrayGetValueAtIndex(certs.get(), 0)); +#else + UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0)); + cert.retain((SecCertificateRef)CFArrayGetValueAtIndex(items.get(), 0)); +#endif + return cert.release(); +} + +CFArrayRef +IceSSL::SecureTransport::loadCACertificates(const string& file) +{ +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + return loadCerts(file); +#else + UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0)); + UniqueRef<CFArrayRef> certificateAuthorities(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + int count = CFArrayGetCount(items.get()); + for(CFIndex i = 0; i < count; ++i) + { + SecCertificateRef cert = + static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), i))); + assert(SecCertificateGetTypeID() == CFGetTypeID(cert)); + if(isCA(cert)) + { + CFArrayAppendValue(const_cast<CFMutableArrayRef>(certificateAuthorities.get()), cert); + } + } + return certificateAuthorities.release(); +#endif +} + +CFArrayRef +IceSSL::SecureTransport::findCertificateChain(const std::string& keychainPath, + const std::string& keychainPassword, + const string& value) +{ + // + // Search the keychain using key:value pairs. The following keys are supported: + // + // Label + // Serial + // Subject + // SubjectKeyId + // + // A value must be enclosed in single or double quotes if it contains whitespace. + // + UniqueRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(0, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) + UniqueRef<SecKeychainRef> keychain(openKeychain(keychainPath, keychainPassword)); + const void* values[] = { keychain.get() }; + UniqueRef<CFArrayRef> searchList(CFArrayCreate(kCFAllocatorDefault, values, 1, &kCFTypeArrayCallBacks)); + CFDictionarySetValue(query.get(), kSecMatchSearchList, searchList.get()); +#endif + CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitOne); + CFDictionarySetValue(query.get(), kSecClass, kSecClassCertificate); + CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); + CFDictionarySetValue(query.get(), kSecMatchCaseInsensitive, kCFBooleanTrue); + + size_t start = 0; + size_t pos; + bool valid = false; + while((pos = value.find(':', start)) != string::npos) + { + string field = IceUtilInternal::toUpper(IceUtilInternal::trim(value.substr(start, pos - start))); + string arg; + if(field != "LABEL" && field != "SERIAL" && field != "SUBJECT" && field != "SUBJECTKEYID") + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: unknown key in `" + value + "'"); + } + + start = pos + 1; + while(start < value.size() && (value[start] == ' ' || value[start] == '\t')) + { + ++start; + } + + if(start == value.size()) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: missing argument in `" + value + "'"); + } + + if(value[start] == '"' || value[start] == '\'') + { + size_t end = start; + ++end; + while(end < value.size()) + { + if(value[end] == value[start] && value[end - 1] != '\\') + { + break; + } + ++end; + } + if(end == value.size() || value[end] != value[start]) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: unmatched quote in `" + value + "'"); + } + ++start; + arg = value.substr(start, end - start); + start = end + 1; + } + else + { + size_t end = value.find_first_of(" \t", start); + if(end == string::npos) + { + arg = value.substr(start); + start = value.size(); + } + else + { + arg = value.substr(start, end - start); + start = end + 1; + } + } + + if(field == "SUBJECT" || field == "LABEL") + { + UniqueRef<CFStringRef> v(toCFString(arg)); + CFDictionarySetValue(query.get(), field == "LABEL" ? kSecAttrLabel : kSecMatchSubjectContains, v.get()); + valid = true; + } + else if(field == "SUBJECTKEYID" || field == "SERIAL") + { + vector<unsigned char> buffer; + if(!parseBytes(arg, buffer)) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: invalid value `" + value + "'"); + } + UniqueRef<CFDataRef> v(CFDataCreate(kCFAllocatorDefault, &buffer[0], buffer.size())); + CFDictionarySetValue(query.get(), field == "SUBJECTKEYID" ? kSecAttrSubjectKeyID : kSecAttrSerialNumber, + v.get()); + valid = true; + } + } + + if(!valid) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: invalid value `" + value + "'"); + } + + UniqueRef<SecCertificateRef> cert; + OSStatus err = SecItemCopyMatching(query.get(), (CFTypeRef*)&cert.get()); + if(err != noErr) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: find certificate `" + value + "' failed:\n" + sslErrorToString(err)); + } + + // + // Retrieve the certificate chain + // + UniqueRef<SecPolicyRef> policy(SecPolicyCreateSSL(true, 0)); + UniqueRef<SecTrustRef> trust; + err = SecTrustCreateWithCertificates(reinterpret_cast<CFArrayRef>(cert.get()), policy.get(), &trust.get()); + if(err || !trust) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error creating trust object" + + (err ? ":\n" + sslErrorToString(err) : "")); + } + + SecTrustResultType trustResult; + if((err = SecTrustEvaluate(trust.get(), &trustResult))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error evaluating trust:\n" + sslErrorToString(err)); + } + + int chainLength = SecTrustGetCertificateCount(trust.get()); + UniqueRef<CFArrayRef> items(CFArrayCreateMutable(kCFAllocatorDefault, chainLength, &kCFTypeArrayCallBacks)); + for(int i = 0; i < chainLength; ++i) + { + CFArrayAppendValue(const_cast<CFMutableArrayRef>(items.get()), SecTrustGetCertificateAtIndex(trust.get(), i)); + } + + // + // Replace the first certificate in the chain with the + // identity. + // + UniqueRef<SecIdentityRef> identity; +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + + // + // SecIdentityCreateWithCertificate isn't supported on iOS so we lookup the identity + // using the certicate label. If the user added the identity with SecItemAdd the + // identity has the same label as the certificate. + // + query.reset(CFDictionaryCreateMutable(0, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFDictionarySetValue(query.get(), kSecClass, kSecClassCertificate); + CFDictionarySetValue(query.get(), kSecValueRef, cert.get()); + CFDictionarySetValue(query.get(), kSecReturnAttributes, kCFBooleanTrue); + UniqueRef<CFDictionaryRef> attributes; + err = SecItemCopyMatching(query.get(), reinterpret_cast<CFTypeRef*>(&attributes.get())); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: couldn't create identity for certificate found in the keychain:\n" << sslErrorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + + // Now lookup the identity with the label + query.reset(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitOne); + CFDictionarySetValue(query.get(), kSecClass, kSecClassIdentity); + CFDictionarySetValue(query.get(), kSecAttrLabel, (CFDataRef)CFDictionaryGetValue(attributes.get(), kSecAttrLabel)); + CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); + err = SecItemCopyMatching(query.get(), (CFTypeRef*)&identity.get()); + if(err == noErr) + { + UniqueRef<SecCertificateRef> cert2; + if((err = SecIdentityCopyCertificate(identity.get(), &cert2.get())) == noErr) + { + err = CFEqual(cert2.get(), cert.get()) ? noErr : errSecItemNotFound; + } + } +#else + err = SecIdentityCreateWithCertificate(keychain.get(), cert.get(), &identity.get()); +#endif + if(err != noErr) + { + ostringstream os; + os << "IceSSL: couldn't create identity for certificate found in the keychain:\n" << sslErrorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + CFArraySetValueAtIndex(const_cast<CFMutableArrayRef>(items.get()), 0, identity.get()); + return items.release(); +} |