diff options
Diffstat (limited to 'cpp/src/IceSSL/Util.cpp')
-rwxr-xr-x[-rw-r--r--] | cpp/src/IceSSL/Util.cpp | 792 |
1 files changed, 747 insertions, 45 deletions
diff --git a/cpp/src/IceSSL/Util.cpp b/cpp/src/IceSSL/Util.cpp index 50c47f3cc29..f6fd6a92563 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_WINRT) # include <winsock2.h> #endif @@ -16,9 +16,16 @@ #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_OS_WINRT +# include <ppltasks.h> +#endif #ifdef ICE_USE_OPENSSL # include <openssl/err.h> @@ -33,30 +40,44 @@ using namespace Ice; using namespace IceUtil; using namespace IceSSL; -void -IceSSL::readFile(const string& file, vector<char>& buffer) +#ifdef ICE_OS_WINRT +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 { @@ -574,12 +595,14 @@ IceSSL::errorToString(OSStatus status) { ostringstream os; os << "(error: " << status; +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) CFStringRef s = SecCopyErrorMessageString(status, 0); if(s) { os << " description: " << fromCFString(s); CFRelease(s); } +#endif os << ")"; return os.str(); } @@ -599,6 +622,7 @@ IceSSL::fromCFString(CFStringRef v) return s; } +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) CFDictionaryRef IceSSL::getCertificateProperty(SecCertificateRef cert, CFTypeRef key) { @@ -622,10 +646,36 @@ IceSSL::getCertificateProperty(SecCertificateRef cert, CFTypeRef key) CFRelease(values); return property; } +#endif namespace { +CFDataRef +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); + + CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, size); + CFDataSetLength(data, size); + is.read(reinterpret_cast<char*>(CFDataGetMutableBytePtr(data)), size); + if(!is.good()) + { + CFRelease(data); + throw CertificateReadException(__FILE__, __LINE__, "error reading file " + file); + } + return data; +} + +#if defined(ICE_USE_SECURE_TRANSPORT_MACOS) // // Check the certificate basic constraints to check if the certificate is marked as a CA. // @@ -657,12 +707,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<CFDataRef> data(readCertFile(file)); SecItemImportExportKeyParameters params; memset(¶ms, 0, sizeof(params)); @@ -742,14 +787,93 @@ loadKeychainItems(const string& file, SecExternalItemType type, SecKeychainRef k return items; } +SecKeychainRef +openKeychain(const std::string& path, const std::string& keychainPassword) +{ + string keychainPath = path; + SecKeychainRef keychain = 0; + OSStatus err = 0; + if(keychainPath.empty()) + { + if((err = SecKeychainCopyDefault(&keychain))) + { + 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))) + { + throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: unable to open keychain: `" + + keychainPath + "'\n" + errorToString(err)); + } + } + + UniqueRef<SecKeychainRef> k(keychain); + + SecKeychainStatus status; + err = SecKeychainGetStatus(keychain, &status); + if(err == noErr) + { + const char* pass = keychainPassword.empty() ? 0 : keychainPassword.c_str(); + if((err = SecKeychainUnlock(keychain, 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(); + if((err = SecKeychainCreate(keychainPath.c_str(), keychainPassword.size(), pass, pass == 0, 0, &keychain))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: unable to create keychain:\n" + errorToString(err)); + } + k.reset(keychain); + } + 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, &settings))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error setting keychain settings:\n" + errorToString(err)); + } + + return k.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 @@ -902,17 +1026,139 @@ IceSSL::loadPrivateKey(const string& file, SecCertificateRef cert, SecKeychainRe } return identity; } +#else + +CFArrayRef +loadCerts(const string& file) +{ + UniqueRef<CFMutableArrayRef> 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())); + 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(certs.get(), cert); + CFRelease(cert); + first = false; + } + } + else + { + UniqueRef<CFDataRef> data(readCertFile(file)); + 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(certs.get(), cert); + CFRelease(cert); + } + 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) { +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + UniqueRef<CFDataRef> cert(readCertFile(file)); + + UniqueRef<CFMutableDictionaryRef> settings(CFDictionaryCreateMutable(0, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + CFArrayRef items = 0; + OSStatus err; + if(password.empty() && prompt) + { + int count = 0; + do + { + UniqueRef<CFStringRef> pass(toCFString(prompt->getPassword())); + CFDictionarySetValue(settings.get(), kSecImportExportPassphrase, pass.get()); + err = SecPKCS12Import(cert.get(), settings.get(), &items); + ++count; + } + while(err == errSecAuthFailed && count < retryMax); + } + else + { + UniqueRef<CFStringRef> pass(toCFString(password)); + CFDictionarySetValue(settings.get(), kSecImportExportPassphrase, pass.get()); + err = SecPKCS12Import(cert.get(), settings.get(), &items); + } + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to import certificate from file " << file << " (error = " << err << ")"; + throw InitializationException(__FILE__, __LINE__, os.str()); + } + + UniqueRef<CFArrayRef> itemsHolder(items);; + for(int i = 0; i < CFArrayGetCount(items); ++i) + { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(items, i); + SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(dict, kSecImportItemIdentity); + if(identity) + { + CFArrayRef certs = (CFArrayRef)CFDictionaryGetValue(dict, kSecImportItemCertChain); + CFMutableArrayRef a = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certs); + CFArraySetValueAtIndex(a, 0, identity); + return a; + } + } + 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); + return loadKeychainItems(file, kSecItemTypeUnknown, keychain.get(), password, prompt, retryMax); } else { @@ -934,26 +1180,37 @@ 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)); + UniqueRef<SecIdentityRef> identity(loadPrivateKey(keyFile, cert, keychain.get(), password, prompt, retryMax)); CFMutableArrayRef a = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, items.get()); CFArraySetValueAtIndex(a, 0, identity.get()); return a; } +#endif } SecCertificateRef IceSSL::loadCertificate(const string& file) { - CFArrayRef items = loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0); - SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items, 0); +#if defined(ICE_USE_SECURE_TRANSPORT_IOS) + UniqueRef<CFArrayRef> certs(loadCerts(file)); + assert(CFArrayGetCount(certs.get()) > 0); + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs.get(), 0); CFRetain(cert); - CFRelease(items); return cert; +#else + UniqueRef<CFArrayRef> items(loadKeychainItems(file, kSecItemTypeCertificate, 0, "", 0, 0)); + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items.get(), 0); + CFRetain(cert); + return cert; +#endif } 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); int count = CFArrayGetCount(items.get()); @@ -967,10 +1224,11 @@ IceSSL::loadCACertificates(const string& file) } } return certificateAuthorities; +#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 +1245,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 +1318,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,10 +1330,11 @@ 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 + "'"); } @@ -1083,7 +1346,92 @@ IceSSL::findCertificate(SecKeychainRef keychain, const string& value) throw PluginInitializationException(__FILE__, __LINE__, "IceSSL: find certificate `" + value + "' failed:\n" + errorToString(err)); } - return cert; + + UniqueRef<SecCertificateRef> certHolder(cert); + + // + // Retrieve the certificate chain + // + UniqueRef<SecPolicyRef> policy(SecPolicyCreateSSL(true, 0)); + SecTrustRef trust = 0; + err = SecTrustCreateWithCertificates((CFArrayRef)cert, policy.get(), &trust); + if(err || !trust) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error creating trust object" + + (err ? ":\n" + errorToString(err) : "")); + } + UniqueRef<SecTrustRef> v(trust); + + SecTrustResultType trustResult; + if((err = SecTrustEvaluate(trust, &trustResult))) + { + throw PluginInitializationException(__FILE__, __LINE__, + "IceSSL: error evaluating trust:\n" + errorToString(err)); + } + + int chainLength = SecTrustGetCertificateCount(trust); + CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, chainLength, &kCFTypeArrayCallBacks); + UniqueRef<CFMutableArrayRef> itemsHolder(items); + for(int i = 0; i < chainLength; ++i) + { + CFArrayAppendValue(items, SecTrustGetCertificateAtIndex(trust, i)); + } + + // + // Replace the first certificate in the chain with the + // identity. + // + 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); + CFDictionarySetValue(query.get(), kSecReturnAttributes, kCFBooleanTrue); + CFDictionaryRef attributes; + err = SecItemCopyMatching(query.get(), (CFTypeRef*)&attributes); + 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()); + } + UniqueRef<CFDictionaryRef> attributesHolder(attributes); + + // 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, kSecAttrLabel)); + CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue); + err = SecItemCopyMatching(query.get(), (CFTypeRef*)&identity); + if(err == noErr) + { + SecCertificateRef cert2 = NULL; + if((err = SecIdentityCopyCertificate(identity, &cert2)) == noErr) + { + err = CFEqual(cert2, cert) ? noErr : errSecItemNotFound; + CFRelease(cert2); + } + } +#else + err = SecIdentityCreateWithCertificate(keychain.get(), cert, &identity); +#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(items, 0, identity); + CFRelease(identity); + return itemsHolder.release(); } #elif defined(ICE_USE_SCHANNEL) @@ -1125,7 +1473,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 +1574,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 +1692,365 @@ IceSSL::findCertificates(const string& location, const string& name, const strin } return certs; } +#elif defined (ICE_OS_WINRT) + +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) +{ + std::promise<Certificates::Certificate^> p; + + CertificateQuery^ query = ref new CertificateQuery(); + query->IncludeDuplicates = true; + query->IncludeExpiredCertificates = true; + query->FriendlyName = friendlyName; + query->StoreName = StandardCertificateStoreNames::Personal; + + create_task(CertificateStores::FindAllAsync(query)) + + .then([&p](IVectorView<Certificates::Certificate^>^ certificates) + { + if(certificates->Size > 0) + { + p.set_value(certificates->GetAt(0)); + } + else + { + p.set_value(nullptr); + } + }, + task_continuation_context::use_arbitrary()) + + .then([&](task<void> t) + { + try + { + t.get(); + } + catch(Platform::Exception^ ex) + { + p.set_exception(make_exception_ptr( + PluginInitializationException(__FILE__, __LINE__, "IceSSL: certificate error:\n" + + wstringToString(ex->Message->Data())))); + } + }, + task_continuation_context::use_arbitrary()); + + return p.get_future().get(); +} + +// +// 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) +{ + promise<bool> p; + + create_task(CertificateEnrollmentManager::ImportPfxDataAsync( + data, + password, + ExportOption::NotExportable, + KeyProtectionLevel::NoConsent, + InstallOptions::None, + friendlyName)) + + .then([&p]() + { + p.set_value(false); // The import succcess + }, + task_continuation_context::use_arbitrary()) + + .then([&p](task<void> t) + { + try + { + t.get(); + } + catch(Platform::Exception^ ex) + { + if(HRESULT_CODE(ex->HResult) == ERROR_DECRYPTION_FAILED) + { + p.set_value(true); // Password error + } + else + { + p.set_exception(make_exception_ptr( + PluginInitializationException(__FILE__, __LINE__, "IceSSL: certificate error:\n" + + wstringToString(ex->Message->Data())))); + } + } + }, + task_continuation_context::use_arbitrary()); + + return p.get_future().get(); +} + +} + +Certificates::Certificate^ +IceSSL::importPersonalCertificate(const string& file, function<string ()> password, bool passwordPrompt, + int passwordRetryMax) +{ + std::promise<Certificates::Certificate^> p; + auto uri = ref new Uri(ref new String(stringToWstring(file).c_str())); + create_task(StorageFile::GetFileFromApplicationUriAsync(uri)) + + .then([](StorageFile^ file) + { + return FileIO::ReadBufferAsync(file); + }, + task_continuation_context::use_arbitrary()) + + .then([&file, &password, &p, passwordPrompt, passwordRetryMax](IBuffer^ buffer) + { + // + // 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) + { + p.set_value(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"); + } + p.set_value(findPersonalCertificate(friendlyName)); + } + }, + task_continuation_context::use_arbitrary()) + + .then([&p, &file](task<void> t) + { + try + { + t.get(); + } + catch(Platform::Exception^ ex) + { + if(HRESULT_CODE(ex->HResult) == ERROR_FILE_NOT_FOUND) + { + p.set_exception(make_exception_ptr( + PluginInitializationException(__FILE__, __LINE__, "certificate file not found:\n" + file))); + } + else + { + p.set_exception(make_exception_ptr( + PluginInitializationException(__FILE__, __LINE__, "IceSSL: certificate error:\n" + + wstringToString(ex->Message->Data())))); + } + } + catch(...) + { + p.set_exception(current_exception()); + } + }, + task_continuation_context::use_arbitrary()); + + return p.get_future().get(); +} + +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())); + } + } + } + + std::promise<IVectorView<Certificates::Certificate^>^> p; + create_task(CertificateStores::FindAllAsync(query)) + + .then([&p](IVectorView<Certificates::Certificate^>^ certificates) + { + p.set_value(certificates); + }, + task_continuation_context::use_arbitrary()) + + .then([&p](task<void> t) + { + try + { + t.get(); + } + catch(Platform::Exception^ ex) + { + p.set_exception( + make_exception_ptr(PluginInitializationException(__FILE__, __LINE__, "IceSSL: certificate error:\n" + + wstringToString(ex->Message->Data())))); + } + }, + task_continuation_context::use_arbitrary()); + + return p.get_future().get(); +} #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) + CFURLRef url = 0; + CFBundleRef bundle = CFBundleGetMainBundle(); + if(bundle) + { + CFStringRef resourceName = toCFString(path); + CFStringRef subDirName = toCFString(defaultDir); + url = CFBundleCopyResourceURL(bundle, resourceName, 0, subDirName); + CFRelease(resourceName); + CFRelease(subDirName); + + UInt8 filePath[PATH_MAX]; + if(CFURLGetFileSystemRepresentation(url, 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))) |