diff options
Diffstat (limited to 'cpp/src/IceSSL/Util.cpp')
-rwxr-xr-x[-rw-r--r--] | cpp/src/IceSSL/Util.cpp | 837 |
1 files changed, 711 insertions, 126 deletions
diff --git a/cpp/src/IceSSL/Util.cpp b/cpp/src/IceSSL/Util.cpp index c61bafbac3e..52cd3c5144c 100644..100755 --- a/cpp/src/IceSSL/Util.cpp +++ b/cpp/src/IceSSL/Util.cpp @@ -8,7 +8,7 @@ // ********************************************************************** #include <IceUtil/Config.h> -#ifdef _WIN32 +#if defined(_WIN32) && !defined(ICE_OS_UWP) # include <winsock2.h> #endif @@ -16,9 +16,12 @@ #include <IceUtil/FileUtil.h> #include <IceUtil/StringUtil.h> +#include <Ice/Base64.h> #include <Ice/LocalException.h> #include <Ice/Network.h> #include <Ice/Object.h> +#include <Ice/StringConverter.h> +#include <fstream> #ifdef ICE_USE_OPENSSL # include <openssl/err.h> @@ -30,33 +33,48 @@ using namespace std; using namespace Ice; +using namespace IceInternal; using namespace IceUtil; using namespace IceSSL; -void -IceSSL::readFile(const string& file, vector<char>& buffer) +#ifdef ICE_OS_UWP +using namespace concurrency; +using namespace Platform; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; +using namespace Windows::Security::Cryptography; +using namespace Windows::Security::Cryptography::Core; +using namespace Windows::Security::Cryptography::Certificates; +#endif + +#ifdef ICE_CPP11_MAPPING +IceSSL::CertificateVerifier::CertificateVerifier(std::function<bool(const std::shared_ptr<NativeConnectionInfo>&)> v) : + _verify(std::move(v)) { - IceUtilInternal::ifstream is(file, ios::in | ios::binary); - if(!is.good()) - { - throw CertificateReadException(__FILE__, __LINE__, "error opening file " + file); - } +} - is.seekg(0, is.end); - buffer.resize(static_cast<int>(is.tellg())); - is.seekg(0, is.beg); +bool +IceSSL::CertificateVerifier::verify(const NativeConnectionInfoPtr& info) +{ + return _verify(info); +} - if(!buffer.empty()) - { - is.read(&buffer[0], buffer.size()); - if(!is.good()) - { - throw CertificateReadException(__FILE__, __LINE__, "error reading file " + file); - } - } +IceSSL::PasswordPrompt::PasswordPrompt(std::function<std::string()> p) : + _prompt(std::move(p)) +{ } -#ifndef ICE_USE_OPENSSL +std::string +IceSSL::PasswordPrompt::getPassword() +{ + return _prompt(); +} +#endif + + +#if !defined(ICE_USE_OPENSSL) namespace { @@ -539,7 +557,7 @@ IceSSL::getSslErrors(bool verbose) else { const char* reason = ERR_reason_error_string(err); - ostr << (reason == NULL ? "unknown reason" : reason); + ostr << (reason == ICE_NULLPTR ? "unknown reason" : reason); if(flags & ERR_TXT_STRING) { ostr << ": " << data; @@ -562,9 +580,8 @@ IceSSL::errorToString(CFErrorRef err) ostringstream os; if(err) { - CFStringRef s = CFErrorCopyDescription(err); - os << "(error: " << CFErrorGetCode(err) << " description: " << fromCFString(s) << ")"; - CFRelease(s); + UniqueRef<CFStringRef> s(CFErrorCopyDescription(err)); + os << "(error: " << CFErrorGetCode(err) << " description: " << fromCFString(s.get()) << ")"; } return os.str(); } @@ -574,12 +591,13 @@ IceSSL::errorToString(OSStatus status) { ostringstream os; os << "(error: " << status; - CFStringRef s = SecCopyErrorMessageString(status, 0); +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) + UniqueRef<CFStringRef> s(SecCopyErrorMessageString(status, 0)); if(s) { - os << " description: " << fromCFString(s); - CFRelease(s); + os << " description: " << fromCFString(s.get()); } +#endif os << ")"; return os.str(); } @@ -599,13 +617,14 @@ IceSSL::fromCFString(CFStringRef v) return s; } +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) CFDictionaryRef IceSSL::getCertificateProperty(SecCertificateRef cert, CFTypeRef key) { - CFArrayRef keys = CFArrayCreate(NULL, &key , 1, &kCFTypeArrayCallBacks); - CFErrorRef err = 0; - CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); - CFRelease(keys); + 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; @@ -614,18 +633,38 @@ IceSSL::getCertificateProperty(SecCertificateRef cert, CFTypeRef key) } assert(values); - CFDictionaryRef property = (CFDictionaryRef)CFDictionaryGetValue(values, key); - if(property) - { - CFRetain(property); - } - CFRelease(values); - return property; + property.retain(static_cast<CFDictionaryRef>(CFDictionaryGetValue(values.get(), key))); + return property.release(); } +#endif 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(); +} + +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) // // Check the certificate basic constraints to check if the certificate is marked as a CA. // @@ -635,14 +674,14 @@ isCA(SecCertificateRef cert) UniqueRef<CFDictionaryRef> property(getCertificateProperty(cert, kSecOIDBasicConstraints)); if(property) { - CFArrayRef propertyValues = (CFArrayRef)CFDictionaryGetValue(property.get(), kSecPropertyKeyValue); + CFArrayRef propertyValues = static_cast<CFArrayRef>(CFDictionaryGetValue(property.get(), kSecPropertyKeyValue)); for(int i = 0, size = CFArrayGetCount(propertyValues); i < size; ++i) { - CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(propertyValues, i); - CFStringRef label = (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyLabel); + CFDictionaryRef dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(propertyValues, i)); + CFStringRef label = static_cast<CFStringRef>(CFDictionaryGetValue(dict, kSecPropertyKeyLabel)); if(CFEqual(label, CFSTR("Certificate Authority"))) { - return CFEqual((CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyValue), CFSTR("Yes")); + return CFEqual(static_cast<CFStringRef>(CFDictionaryGetValue(dict, kSecPropertyKeyValue)), CFSTR("Yes")); } } } @@ -657,12 +696,7 @@ CFArrayRef loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef keychain, const string& passphrase, const PasswordPromptPtr& prompt, int retryMax) { - vector<char> buffer; - readFile(file, buffer); - UniqueRef<CFDataRef> data(CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, - reinterpret_cast<const UInt8*>(&buffer[0]), - buffer.size(), - kCFAllocatorNull)); + UniqueRef<CFMutableDataRef> data(readCertFile(file)); SecItemImportExportKeyParameters params; memset(¶ms, 0, sizeof(params)); @@ -673,17 +707,19 @@ loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef k params.passphrase = toCFString(passphrase); } - CFArrayRef items; + 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); + 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)) { @@ -692,7 +728,8 @@ loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef k params.flags |= kSecKeySecurePassphrase; ostringstream os; os << "Enter the password for\n" << file; - params.alertPrompt = toCFString(os.str()); + alertPromptHolder.reset(toCFString(os.str())); + params.alertPrompt = alertPromptHolder.get(); } int count = 0; @@ -701,25 +738,12 @@ loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef k { if(prompt) { - if(params.passphrase) - { - CFRelease(params.passphrase); - } - params.passphrase = toCFString(prompt->getPassword()); + passphraseHolder.reset(toCFString(prompt->getPassword())); + params.passphrase = passphraseHolder.get(); } - err = SecItemImport(data.get(), path.get(), &format, &importType, 0, ¶ms, keychain, &items); + err = SecItemImport(data.get(), path.get(), &format, &importType, 0, ¶ms, keychain, &items.get()); ++count; } - - if(params.alertPrompt) - { - CFRelease(params.alertPrompt); - } - } - - if(params.passphrase) - { - CFRelease(params.passphrase); } if(err != noErr) @@ -732,24 +756,100 @@ loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef k if(type != kSecItemTypeUnknown && importType != kSecItemTypeAggregate && importType != type) { - CFRelease(items); 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; + 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" + errorToString(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" + errorToString(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" + errorToString(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" + errorToString(err)); + } + } + else + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to open keychain:\n" + errorToString(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" + errorToString(err)); + } + + return keychain.release(); } // // Imports a certificate private key and optionally add it to a keychain. // SecIdentityRef -IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRef keychain, const string& password, - const PasswordPromptPtr& prompt, int retryMax) +loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRef keychain, const string& password, + const PasswordPromptPtr& prompt, int retryMax) { // // Check if we already imported the certificate @@ -758,13 +858,14 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe UniqueRef<CFDictionaryRef> subjectKeyProperty(getCertificateProperty(cert, kSecOIDSubjectKeyIdentifier)); if(subjectKeyProperty) { - CFArrayRef values = (CFArrayRef)CFDictionaryGetValue(subjectKeyProperty.get(), kSecPropertyKeyValue); + CFArrayRef values = static_cast<CFArrayRef>(CFDictionaryGetValue(subjectKeyProperty.get(), + kSecPropertyKeyValue)); for(int i = 0; i < CFArrayGetCount(values); ++i) { - CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(values, i); + CFDictionaryRef dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(values, i)); if(CFEqual(CFDictionaryGetValue(dict, kSecPropertyKeyLabel), CFSTR("Key Identifier"))) { - hash.retain(CFDictionaryGetValue(dict, kSecPropertyKeyValue)); + hash.retain(static_cast<CFDataRef>(CFDictionaryGetValue(dict, kSecPropertyKeyValue))); break; } } @@ -784,24 +885,24 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe CFDictionarySetValue(query.get(), kSecAttrSubjectKeyID, hash.get()); CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); - CFTypeRef value = 0; - OSStatus err = SecItemCopyMatching(query.get(), &value); - UniqueRef<SecCertificateRef> item(value); + 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. // - SecIdentityRef identity; - err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity); + UniqueRef<SecIdentityRef> identity; + err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity.get()); if(err != noErr) { ostringstream os; os << "IceSSL: error creating certificate identity:\n" << errorToString(err); throw CertificateReadException(__FILE__, __LINE__, os.str()); } - return identity; + return identity.release(); } else if(err != errSecItemNotFound) { @@ -819,10 +920,11 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe UniqueRef<SecKeyRef> key; for(int i = 0; i < count; ++i) { - SecKeychainItemRef item = (SecKeychainItemRef)CFArrayGetValueAtIndex(items.get(), 0); + SecKeychainItemRef item = + static_cast<SecKeychainItemRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), 0))); if(SecKeyGetTypeID() == CFGetTypeID(item)) { - key.retain(item); + key.retain(reinterpret_cast<SecKeyRef>(item)); break; } } @@ -844,16 +946,16 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe CFDictionarySetValue(query.get(), kSecValueRef, cert); CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); - value = 0; - err = SecItemAdd(query.get(), (CFTypeRef*)&value); - UniqueRef<CFArrayRef> added(value); + 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" << errorToString(err); throw CertificateReadException(__FILE__, __LINE__, os.str()); } - item.retain(CFArrayGetValueAtIndex(added.get(), 0)); + item.retain(static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(added.get(), 0)))); // // Create the association between the private key and the certificate, @@ -864,7 +966,7 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe { SecKeychainAttribute attr; attr.tag = kSecKeyLabel; - attr.data = (void*)CFDataGetBytePtr(hash.get()); + attr.data = const_cast<UInt8*>(CFDataGetBytePtr(hash.get())); attr.length = CFDataGetLength(hash.get()); attributes.push_back(attr); } @@ -874,15 +976,13 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe // name. // string label; - CFStringRef commonName = 0; - if(SecCertificateCopyCommonName(item.get(), &commonName) == noErr) + UniqueRef<CFStringRef> commonName(0); + if(SecCertificateCopyCommonName(item.get(), &commonName.get()) == noErr) { - label = fromCFString(commonName); - CFRelease(commonName); - + label = fromCFString(commonName.get()); SecKeychainAttribute attr; attr.tag = kSecKeyPrintName; - attr.data = (void*)label.c_str(); + attr.data = const_cast<char*>(label.c_str()); attr.length = label.size(); attributes.push_back(attr); } @@ -890,29 +990,147 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe SecKeychainAttributeList attrs; attrs.attr = &attributes[0]; attrs.count = attributes.size(); - SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)key.get(), &attrs, 0, 0); + SecKeychainItemModifyAttributesAndData(reinterpret_cast<SecKeychainItemRef>(key.get()), &attrs, 0, 0); - SecIdentityRef identity; - err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity); + UniqueRef<SecIdentityRef> identity; + err = SecIdentityCreateWithCertificate(keychain, item.get(), &identity.get()); if(err != noErr) { ostringstream os; os << "IceSSL: error creating certificate identity:\n" << errorToString(err); throw CertificateReadException(__FILE__, __LINE__, os.str()); } - return identity; + return identity.release(); +} +#else + +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::loadCertificateChain(const string& file, const string& keyFile, SecKeychainRef keychain, - const string& password, const PasswordPromptPtr& prompt, int retryMax) +IceSSL::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()) { - return loadKeychainItems(file, kSecItemTypeUnknown, keychain, password, prompt, retryMax); + chain.reset(loadKeychainItems(file, kSecItemTypeUnknown, keychain.get(), password, prompt, retryMax)); } else { @@ -921,7 +1139,8 @@ IceSSL::loadCertificateChain(const string& file, const string& keyFile, SecKeych // might already have been imported. // UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, password, prompt, retryMax)); - SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items.get(), 0); + SecCertificateRef cert = + static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), 0))); if(SecCertificateGetTypeID() != CFGetTypeID(cert)) { ostringstream os; @@ -934,43 +1153,54 @@ IceSSL::loadCertificateChain(const string& file, const string& keyFile, SecKeych // add the certificate/key to the keychain if they aren't // already present in the keychain. // - UniqueRef<SecIdentityRef> identity(loadPrivateKey(keyFile, cert, keychain, password, prompt, retryMax)); - CFMutableArrayRef a = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, items.get()); - CFArraySetValueAtIndex(a, 0, identity.get()); - return a; + 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::loadCertificate(const string& file) { - CFArrayRef items = loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0); - SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items, 0); - CFRetain(cert); - CFRelease(items); - return cert; + 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::loadCACertificates(const string& file) { +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + return loadCerts(file); +#else UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0)); - CFMutableArrayRef certificateAuthorities = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + UniqueRef<CFArrayRef> certificateAuthorities(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); int count = CFArrayGetCount(items.get()); for(CFIndex i = 0; i < count; ++i) { - SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items.get(), i); + SecCertificateRef cert = + static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(items.get(), i))); assert(SecCertificateGetTypeID() == CFGetTypeID(cert)); if(isCA(cert)) { - CFArrayAppendValue(certificateAuthorities, cert); + CFArrayAppendValue(const_cast<CFMutableArrayRef>(certificateAuthorities.get()), cert); } } - return certificateAuthorities; + return certificateAuthorities.release(); +#endif } -SecCertificateRef -IceSSL::findCertificate(SecKeychainRef keychain, const string& value) +CFArrayRef +IceSSL::findCertificateChain(const std::string& keychainPath, const std::string& keychainPassword, const string& value) { // // Search the keychain using key:value pairs. The following keys are supported: @@ -987,17 +1217,20 @@ IceSSL::findCertificate(SecKeychainRef keychain, const string& value) &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); - const void* values[] = { keychain }; +#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(), kSecMatchLimit, kSecMatchLimitOne); 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))); @@ -1057,6 +1290,7 @@ IceSSL::findCertificate(SecKeychainRef keychain, const string& value) { UniqueRef<CFStringRef> v(toCFString(arg)); CFDictionarySetValue(query.get(), field == "LABEL" ? kSecAttrLabel : kSecMatchSubjectContains, v.get()); + valid = true; } else if(field == "SUBJECTKEYID" || field == "SERIAL") { @@ -1068,22 +1302,101 @@ IceSSL::findCertificate(SecKeychainRef keychain, const string& value) UniqueRef<CFDataRef> v(CFDataCreate(kCFAllocatorDefault, &buffer[0], buffer.size())); CFDictionarySetValue(query.get(), field == "SUBJECTKEYID" ? kSecAttrSubjectKeyID : kSecAttrSerialNumber, v.get()); + valid = true; } } - if(CFDictionaryGetCount(query.get()) == 5) + if(!valid) { throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: invalid value `" + value + "'"); } - SecCertificateRef cert = 0; - OSStatus err = SecItemCopyMatching(query.get(), (CFTypeRef*)&cert); + UniqueRef<SecCertificateRef> cert; + OSStatus err = SecItemCopyMatching(query.get(), (CFTypeRef*)&cert.get()); if(err != noErr) { throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: find certificate `" + value + "' failed:\n" + errorToString(err)); } - return cert; + + // + // 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" + errorToString(err) : "")); + } + + SecTrustResultType trustResult; + if((err = SecTrustEvaluate(trust.get(), &trustResult))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error evaluating trust:\n" + errorToString(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" << errorToString(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" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + CFArraySetValueAtIndex(const_cast<CFMutableArrayRef>(items.get()), 0, identity.get()); + return items.release(); } #elif defined(ICE_USE_SCHANNEL) @@ -1125,7 +1438,7 @@ IceSSL::findCertificates(const string& location, const string& name, const strin storeLoc = CERT_SYSTEM_STORE_LOCAL_MACHINE; } - HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, storeLoc, stringToWstring(name).c_str()); + HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, storeLoc, Ice::stringToWstring(name).c_str()); if(!store) { throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: failed to open certificate store `" + name + @@ -1226,13 +1539,13 @@ IceSSL::findCertificates(const string& location, const string& name, const strin if(field == "SUBJECT" || field == "ISSUER") { - const wstring argW = stringToWstring(arg); + const wstring argW = Ice::stringToWstring(arg); DWORD findType = field == "SUBJECT" ? CERT_FIND_SUBJECT_STR : CERT_FIND_ISSUER_STR; addMatchingCertificates(store, tmpStore, findType, argW.c_str()); } else if(field == "SUBJECTDN" || field == "ISSUERDN") { - const wstring argW = stringToWstring(arg); + const wstring argW = Ice::stringToWstring(arg); DWORD flags[] = { CERT_OID_NAME_STR, CERT_OID_NAME_STR | CERT_NAME_STR_REVERSE_FLAG, @@ -1344,11 +1657,283 @@ IceSSL::findCertificates(const string& location, const string& name, const strin } return certs; } +#elif defined (ICE_OS_UWP) + +namespace +{ + +// +// Find a certificate in the Application Personal certificate store +// with the given friendly name. Returns the matching certificate or +// nullptr if none is found. +// +Certificates::Certificate^ +findPersonalCertificate(String^ friendlyName) +{ + CertificateQuery^ query = ref new CertificateQuery(); + query->IncludeDuplicates = true; + query->IncludeExpiredCertificates = true; + query->FriendlyName = friendlyName; + query->StoreName = StandardCertificateStoreNames::Personal; + + try + { + auto certificates = IceInternal::runSync(CertificateStores::FindAllAsync(query)); + return certificates->Size > 0 ? certificates->GetAt(0) : nullptr; + } + catch(Platform::Exception^ ex) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: certificate error:\n" + wstringToString(ex->Message->Data())); + } +} + +// +// Import a certificate in the Application Personal certificate store +// with the given friendly name. Returns true if there was a password +// error and false otherwise. If the import fails because a different +// error PluginInitializationException exception is throw. +// +bool +importPfxData(String^ friendlyName, String^ data, String^ password) +{ + try + { + IceInternal::runSync(CertificateEnrollmentManager::ImportPfxDataAsync( + data, + password, + ExportOption::NotExportable, + KeyProtectionLevel::NoConsent, + InstallOptions::None, + friendlyName)); + return false; // The import succcess + } + catch(Platform::Exception^ ex) + { + if(HRESULT_CODE(ex->HResult) == ERROR_DECRYPTION_FAILED) + { + return true; // Password error + } + else + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: certificate error:\n" + wstringToString(ex->Message->Data())); + } + } +} + +} + +Certificates::Certificate^ +IceSSL::importPersonalCertificate(const string& file, function<string ()> password, bool passwordPrompt, + int passwordRetryMax) +{ + auto uri = ref new Uri(ref new String(stringToWstring(file).c_str())); + try + { + auto file = IceInternal::runSync(StorageFile::GetFileFromApplicationUriAsync(uri)); + auto buffer = IceInternal::runSync(FileIO::ReadBufferAsync(file)); + + // + // Create a hash of the certificate to use as a friendly name, this will allow us + // to uniquely identify the certificate in the store. + // + auto hasher = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1); + auto hash = hasher->CreateHash(); + + hash->Append(buffer); + String^ friendlyName = CryptographicBuffer::EncodeToBase64String(hash->GetValueAndReset()); + + // + // If the certificate is already in the store we avoid importing it. + // + Certificates::Certificate^ cert = findPersonalCertificate(friendlyName); + if(cert) + { + return cert; + } + else + { + String^ data = CryptographicBuffer::EncodeToBase64String(buffer); + int count = 0; + bool passwordErr = false; + do + { + passwordErr = importPfxData(friendlyName, data, + ref new String(stringToWstring(password()).c_str())); + } + while(passwordPrompt && passwordErr && ++count < passwordRetryMax); + if(passwordErr) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: error decoding certificate"); + } + return findPersonalCertificate(friendlyName); + } + } + catch(Platform::Exception^ ex) + { + if(HRESULT_CODE(ex->HResult) == ERROR_FILE_NOT_FOUND) + { + throw PluginInitializationException(__FILE__, __LINE__, "certificate file not found:\n" + file); + } + else + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: certificate error:\n" + wstringToString(ex->Message->Data())); + } + } +} + +IVectorView<Certificates::Certificate^>^ +IceSSL::findCertificates(const string& name, const string& value) +{ + CertificateQuery^ query = ref new CertificateQuery(); + query->StoreName = ref new String(stringToWstring(name).c_str()); + query->IncludeDuplicates = true; + query->IncludeExpiredCertificates = true; + + if(value != "*") + { + if(value.find(':', 0) == string::npos) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: no key in `" + value + "'"); + } + size_t start = 0; + size_t pos; + while((pos = value.find(':', start)) != string::npos) + { + string field = IceUtilInternal::toUpper(IceUtilInternal::trim(value.substr(start, pos - start))); + if(field != "ISSUER" && field != "THUMBPRINT" && field != "FRIENDLYNAME") + { + 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 + "'"); + } + + string arg; + 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 == "ISSUER") + { + query->IssuerName = ref new String(stringToWstring(arg).c_str()); + } + else if(field == "FRIENDLYNAME") + { + query->FriendlyName = ref new String(stringToWstring(arg).c_str()); + } + else if(field == "THUMBPRINT") + { + vector<BYTE> buffer; + if(!parseBytes(arg, buffer)) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: invalid `IceSSL.FindCert' property: can't decode the value"); + } + query->Thumbprint = ref new Array<unsigned char>(&buffer[0], static_cast<unsigned int>(buffer.size())); + } + } + } + + try + { + return IceInternal::runSync(CertificateStores::FindAllAsync(query)); + } + catch(Platform::Exception^ ex) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: certificate error:\n" + wstringToString(ex->Message->Data())); + } +} #endif +void +IceSSL::readFile(const string& file, vector<char>& buffer) +{ + 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); + buffer.resize(static_cast<int>(is.tellg())); + is.seekg(0, is.beg); + + if(!buffer.empty()) + { + is.read(&buffer[0], buffer.size()); + if(!is.good()) + { + throw CertificateReadException(__FILE__, __LINE__, "error reading file " + file); + } + } +} + bool IceSSL::checkPath(const string& path, const string& defaultDir, bool dir, string& resolved) { +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + CFBundleRef bundle = CFBundleGetMainBundle(); + if(bundle) + { + UniqueRef<CFStringRef> resourceName(toCFString(path)); + UniqueRef<CFStringRef> subDirName(toCFString(defaultDir)); + UniqueRef<CFURLRef> url(CFBundleCopyResourceURL(bundle, resourceName.get(), 0, subDirName.get())); + + UInt8 filePath[PATH_MAX]; + if(CFURLGetFileSystemRepresentation(url.get(), true, filePath, sizeof(filePath))) + { + string tmp = string(reinterpret_cast<char*>(filePath)); + if((dir && IceUtilInternal::directoryExists(tmp)) || (!dir && IceUtilInternal::fileExists(tmp))) + { + resolved = tmp; + return true; + } + } + } +#endif if(IceUtilInternal::isAbsolutePath(path)) { if((dir && IceUtilInternal::directoryExists(path)) || (!dir && IceUtilInternal::fileExists(path))) |