diff options
author | Jose <jose@zeroc.com> | 2016-11-24 21:31:26 +0100 |
---|---|---|
committer | Jose <jose@zeroc.com> | 2016-11-24 21:31:26 +0100 |
commit | 64a9231b01a869bccd3585abeb9eec75d9697bcf (patch) | |
tree | 00b9d15949c5159dce8821339e458215570ff22a /cpp | |
parent | Port IceSSL/configuration test to UWP and fixes to UWP IceSSL implementation (diff) | |
download | ice-64a9231b01a869bccd3585abeb9eec75d9697bcf.tar.bz2 ice-64a9231b01a869bccd3585abeb9eec75d9697bcf.tar.xz ice-64a9231b01a869bccd3585abeb9eec75d9697bcf.zip |
UWP IceSSL implementation improvements
- Add support for IceSSL.CertFile
- Add support for IceSSL password prompt
Diffstat (limited to 'cpp')
-rwxr-xr-x | cpp/src/IceSSL/Util.cpp | 218 | ||||
-rw-r--r-- | cpp/src/IceSSL/Util.h | 7 | ||||
-rwxr-xr-x | cpp/src/IceSSL/WinRTEngine.cpp | 17 | ||||
-rw-r--r-- | cpp/test/IceSSL/configuration/AllTests.cpp | 101 | ||||
-rw-r--r-- | cpp/test/uwp/msbuild/uwp.vcxproj | 8 | ||||
-rw-r--r-- | cpp/test/uwp/msbuild/uwp.vcxproj.filters | 3 |
6 files changed, 292 insertions, 62 deletions
diff --git a/cpp/src/IceSSL/Util.cpp b/cpp/src/IceSSL/Util.cpp index 6979e34cd7b..0c227c7e279 100755 --- a/cpp/src/IceSSL/Util.cpp +++ b/cpp/src/IceSSL/Util.cpp @@ -1693,6 +1693,195 @@ 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) { @@ -1786,26 +1975,25 @@ IceSSL::findCertificates(const string& name, const string& value) } std::promise<IVectorView<Certificates::Certificate^>^> p; - HRESULT error = 0; - create_task(CertificateStores::FindAllAsync(query)).then( - [&](task<IVectorView<Certificates::Certificate^>^> previous) + 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 { - p.set_value(previous.get()); + t.get(); } - catch(Platform::Exception^ err) + catch(Platform::Exception^ ex) { - try - { - Ice::SyscallException ex(__FILE__, __LINE__); - ex.error = err->HResult; - throw ex; - } - catch(...) - { - p.set_exception(current_exception()); - } + p.set_exception( + make_exception_ptr(PluginInitializationException(__FILE__, __LINE__, "IceSSL: certificate error:\n" + + wstringToString(ex->Message->Data())))); } }, task_continuation_context::use_arbitrary()); diff --git a/cpp/src/IceSSL/Util.h b/cpp/src/IceSSL/Util.h index 59ff813a1a8..f10f5f25a72 100644 --- a/cpp/src/IceSSL/Util.h +++ b/cpp/src/IceSSL/Util.h @@ -181,12 +181,12 @@ toCFString(const std::string& s) std::string errorToString(CFErrorRef); std::string errorToString(OSStatus); -#if !defined(__APPLE__) || TARGET_OS_IPHONE == 0 +# if defined(ICE_USE_SECURE_TRANSPORT_MACOS) // // Retrieve a certificate property // CFDictionaryRef getCertificateProperty(SecCertificateRef, CFTypeRef); -#endif +# endif // // Read certificate from a file. @@ -203,6 +203,9 @@ CFArrayRef findCertificateChain(const std::string&, const std::string&, const st std::vector<PCCERT_CONTEXT> findCertificates(const std::string&, const std::string&, const std::string&, std::vector<HCERTSTORE>&); #elif defined(ICE_OS_WINRT) +Windows::Security::Cryptography::Certificates::Certificate^ +importPersonalCertificate(const std::string&, std::function<std::string()>, bool, int); + Windows::Foundation::Collections::IVectorView<Windows::Security::Cryptography::Certificates::Certificate^>^ findCertificates(const std::string&, const std::string&); #endif diff --git a/cpp/src/IceSSL/WinRTEngine.cpp b/cpp/src/IceSSL/WinRTEngine.cpp index b2bbf069e34..b955c8ce4a1 100755 --- a/cpp/src/IceSSL/WinRTEngine.cpp +++ b/cpp/src/IceSSL/WinRTEngine.cpp @@ -52,8 +52,23 @@ WinRTEngine::initialize() // // Load client certificate // + const int passwordRetryMax = properties->getPropertyAsIntWithDefault("IceSSL.PasswordRetryMax", 3); + setPassword(properties->getProperty("IceSSL.Password")); + + string certFile = properties->getProperty("IceSSL.CertFile"); string findCert = properties->getProperty("IceSSL.FindCert"); - if(!findCert.empty()) + if(!certFile.empty()) + { + _certificate = make_shared<IceSSL::Certificate>(importPersonalCertificate( + certFile, + [this]() + { + return password(false); + }, + getPasswordPrompt != nullptr, + passwordRetryMax)); + } + else if(!findCert.empty()) { auto certs = findCertificates(properties->getPropertyWithDefault("IceSSL.CertStore", "My"), findCert); if(certs->Size == 0) diff --git a/cpp/test/IceSSL/configuration/AllTests.cpp b/cpp/test/IceSSL/configuration/AllTests.cpp index 302ae15e846..24047abfabf 100644 --- a/cpp/test/IceSSL/configuration/AllTests.cpp +++ b/cpp/test/IceSSL/configuration/AllTests.cpp @@ -95,13 +95,15 @@ importPersonalCertificate(const string& friendlyName, const string& file, const { std::promise<bool> p; auto uri = ref new Uri(ref new String(stringToWstring(file).c_str())); - create_task(StorageFile::GetFileFromApplicationUriAsync(uri)).then( - [](StorageFile^ file) + create_task(StorageFile::GetFileFromApplicationUriAsync(uri)) + + .then([](StorageFile^ file) { return FileIO::ReadBufferAsync(file); - } - ).then( - [&password, &friendlyName](IBuffer^ buffer) + }, + task_continuation_context::use_arbitrary()) + + .then([&password, &friendlyName](IBuffer^ buffer) { return CertificateEnrollmentManager::ImportPfxDataAsync(CryptographicBuffer::EncodeToBase64String(buffer), ref new String(stringToWstring(password).c_str()), @@ -109,59 +111,76 @@ importPersonalCertificate(const string& friendlyName, const string& file, const KeyProtectionLevel::NoConsent, InstallOptions::None, ref new String(stringToWstring(friendlyName).c_str())); - } - ).then([&p](task<void> previous) + }, + task_continuation_context::use_arbitrary()) + + .then([&p]() + { + p.set_value(true); + }, + task_continuation_context::use_arbitrary()) + + .then([&p](task<void> t) { try { - previous.get(); - p.set_value(true); + t.get(); } catch(...) { p.set_exception(current_exception()); } - }); + }, + task_continuation_context::use_arbitrary()); + return p.get_future().get(); } bool -removeCertificate(String^ storeName, const string& friendlyName) +removeCertificate(String^ storeName, const string& friendlyName = "") { promise<bool> p; CertificateQuery^ query = ref new CertificateQuery(); query->IncludeDuplicates = true; query->IncludeExpiredCertificates = true; - query->FriendlyName = ref new String(stringToWstring(friendlyName).c_str()); + if(!friendlyName.empty()) + { + query->FriendlyName = ref new String(stringToWstring(friendlyName).c_str()); + } query->StoreName = storeName; - create_task(CertificateStores::FindAllAsync(query)).then( - [](IVectorView<Certificate^>^ certs) + + create_task(CertificateStores::FindAllAsync(query)) + + .then([&p](IVectorView<Certificate^>^ certs) { for(unsigned int i = 0; i < certs->Size; ++i) { Certificate^ cert = certs->GetAt(i); - CertificateStore^ store = CertificateStores::GetStoreByName(cert->StoreName); - store->Delete(cert); + CertificateStores::GetStoreByName(cert->StoreName)->Delete(cert); } - } - ).then( - [&p](task<void> previous) + p.set_value(true); + }, + task_continuation_context::use_arbitrary()) + + .then( + [&p](task<void> t) { try { - previous.get(); - p.set_value(true); + t.get(); } catch(...) { p.set_exception(current_exception()); } - }); + }, + task_continuation_context::use_arbitrary()); + return p.get_future().get(); } bool -removePersonalCertificate(const string& friendlyName) +removePersonalCertificate(const string& friendlyName = "") { return removeCertificate(StandardCertificateStoreNames::Personal, friendlyName); } @@ -618,14 +637,9 @@ createClientProps(const Ice::PropertiesPtr& defaultProps, const string& defaultD importCaCertificate(ca, "ms-appx:///" + ca + ".pem"); } - removePersonalCertificate("c_rsa_ca1"); - removePersonalCertificate("c_rsa_ca1_exp"); - removePersonalCertificate("c_rsa_ca2"); - if(!cert.empty()) { - importPersonalCertificate(cert, "ms-appx:///" + cert + ".p12", "password"); - properties->setProperty("IceSSL.FindCert", "FRIENDLYNAME:'" + cert + "'"); + properties->setProperty("IceSSL.CertFile", "ms-appx:///" + cert + ".p12"); } #else if(!ca.empty()) @@ -2096,16 +2110,12 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) #endif // - // IceSSL.Password is not supported with WinRT - // -# ifndef ICE_OS_WINRT - // // SChannel doesn't support PEM Password protected certificates certificates // -# ifdef ICE_USE_SCHANNEL +#ifdef ICE_USE_SCHANNEL if(p12) { -# endif +#endif cout << "testing password prompt... " << flush; { // @@ -2122,11 +2132,11 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) test(plugin); PasswordPromptIPtr prompt = ICE_MAKE_SHARED(PasswordPromptI, "client"); -# ifdef ICE_CPP11_MAPPING +#ifdef ICE_CPP11_MAPPING plugin->setPasswordPrompt([prompt]{ return prompt->getPassword(); }); -# else +#else plugin->setPasswordPrompt(prompt); -# endif +#endif pm->initializePlugins(); test(prompt->count() == 1); Test::ServerFactoryPrxPtr fact = ICE_CHECKED_CAST(Test::ServerFactoryPrx, comm->stringToProxy(factoryRef)); @@ -2148,7 +2158,10 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) // // Use an incorrect password and check that retries are attempted. // - initData.properties = createClientProps(defaultProps, defaultDir, defaultHost, p12, "c_rsa_pass_ca1","cacert1"); +#ifdef ICE_OS_WINRT + removePersonalCertificate(); +#endif + initData.properties = createClientProps(defaultProps, defaultDir, defaultHost, p12, "c_rsa_pass_ca1", "cacert1"); initData.properties->setProperty("IceSSL.Password", ""); // Clear password initData.properties->setProperty("IceSSL.PasswordRetryMax", "4"); initData.properties->setProperty("Ice.InitPlugins", "0"); @@ -2158,11 +2171,11 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) test(plugin); prompt = ICE_MAKE_SHARED(PasswordPromptI, "invalid"); -# ifdef ICE_CPP11_MAPPING +#ifdef ICE_CPP11_MAPPING plugin->setPasswordPrompt([prompt]{ return prompt->getPassword(); }); -# else +#else plugin->setPasswordPrompt(prompt); -# endif +#endif try { pm->initializePlugins(); @@ -2181,9 +2194,8 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) comm->destroy(); } cout << "ok" << endl; -# ifdef ICE_USE_SCHANNEL +#ifdef ICE_USE_SCHANNEL } -# endif #endif // @@ -3538,6 +3550,7 @@ allTests(const CommunicatorPtr& communicator, const string& testDir, bool p12) 0 }; + removePersonalCertificate(); importPersonalCertificate("c_rsa_ca1", "ms-appx:///c_rsa_ca1.p12", "password"); for(int i = 0; clientFindCertProperties[i] != 0; i++) diff --git a/cpp/test/uwp/msbuild/uwp.vcxproj b/cpp/test/uwp/msbuild/uwp.vcxproj index 8afb079a43c..29bf9f7396c 100644 --- a/cpp/test/uwp/msbuild/uwp.vcxproj +++ b/cpp/test/uwp/msbuild/uwp.vcxproj @@ -350,6 +350,14 @@ <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent> <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent> </None> + <None Include="..\..\IceSSL\certs\c_rsa_pass_ca1.p12"> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent> + <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent> + </None> <None Include="..\..\IceSSL\certs\s_rsa_ca1_exp_pub.pem"> <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent> <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent> diff --git a/cpp/test/uwp/msbuild/uwp.vcxproj.filters b/cpp/test/uwp/msbuild/uwp.vcxproj.filters index 2180b8057fc..c39cf594af4 100644 --- a/cpp/test/uwp/msbuild/uwp.vcxproj.filters +++ b/cpp/test/uwp/msbuild/uwp.vcxproj.filters @@ -1481,5 +1481,8 @@ <None Include="..\..\IceSSL\certs\cacerts.pem"> <Filter>certs</Filter> </None> + <None Include="..\..\IceSSL\certs\c_rsa_pass_ca1.p12"> + <Filter>certs</Filter> + </None> </ItemGroup> </Project>
\ No newline at end of file |