From 4467e7f2692e58e3a56abcccfdddea32b7406819 Mon Sep 17 00:00:00 2001 From: solidstore Date: Thu, 10 Aug 2023 08:05:44 +0100 Subject: [PATCH] Squashed commit of the following: commit 320db2badd9178a21cf4f9fca9eb874181456615 Merge: 5bf39815 59f37d21 Author: Ryan Stevens Date: Thu Aug 10 07:53:33 2023 +0100 Merge branch 'wixtoolset:develop' into develop commit 5bf39815f6593af6b8774cda997f3078388119c6 Author: solidstore Date: Thu Aug 10 07:51:33 2023 +0100 tidy commit 5cef22f0969d45cb906093a6e298a03cc358d677 Author: solidstore Date: Wed Aug 9 14:51:26 2023 +0100 Delete WixHttpSslCertSymbol.cs commit 915c08def01cd09c0f49787168c8531d5d54d567 Author: solidstore Date: Wed Aug 9 14:01:46 2023 +0100 SSL bindings latest commit 451ba3c619ac5d0d9e18fae0d1f4916a7930d93e Author: solidstore Date: Mon Aug 7 15:24:02 2023 +0100 Bindings refactor commit f646f026550069cd394aa78dab70550503bc0b44 Author: solidstore Date: Thu Aug 3 08:47:41 2023 +0100 renaming Certificate fields commit 0be51441f80b4fd8467dd8dd415c52cf7965c2f0 Merge: cef14c60 dad178d0 Author: solidstore Date: Thu Aug 3 07:51:38 2023 +0100 Merge branch 'develop402' into develop commit dad178d0ba0a4b7155069a4c4221f577c3941b72 Author: solidstore Date: Wed Jul 19 10:12:27 2023 +0100 CertificateRef support commit 2f42efad01d5422d492fdc0ee918f19ef80565ee Author: solidstore Date: Tue Jul 18 11:21:21 2023 +0100 SetSslCertSetKey fixes commit 1a4fabfc2e5fb4b9f143c63c094d1dcaaa486375 Author: solidstore Date: Mon Jul 17 13:58:11 2023 +0100 HTTP_SERVICE_CONFIG_SSL_SET support commit 3d9737dca609345599079c0a378e80c0a1a9cd5f Author: Bob Arnson Date: Tue Jul 11 21:19:08 2023 -0400 Partial fix for the weirdly broken... IWindowsInstallerDecompileContext.TreatProductAsModule. https://github.com/wixtoolset/issues/issues/7607 commit 22dadeafca72b87e75ed697162fa33ccc85723dd Author: Bob Arnson Date: Thu Jun 29 21:16:35 2023 -0400 Extract object fields with modularization GUIDs. commit ed2a8d1468f58c734903ed9668700921d7deb32a Author: Bob Arnson Date: Tue Jun 20 20:09:19 2023 -0400 Handle MergeModule.CABinet for extraction. Fixes https://github.com/wixtoolset/issues/issues/7568, commit 140abaa1a8629a1f67f41bf549430acc9768ce1b Author: Bob Arnson Date: Wed Jun 21 19:53:56 2023 -0400 Start WiX Toolset v4.0.2. commit 6e6eb47812742cfb61a1c3328ba662ec2886a4e2 Author: Rob Mensching Date: Sun Jun 4 10:47:29 2023 -0700 WiX Toolset v4.0.1 commit e25aa4354c58d49665517956c0a30b0eaeae7342 Author: Rob Mensching Date: Fri Jun 2 11:45:10 2023 -0700 ProjectReferences to wixlibs should participate in DefineConstants creation Also fix documentation in ResolveWixLibraryReferences target. Fixes 7512 commit 29cec14e4197080bdac5d6c1b66f4aaf9b53e91c Author: Bob Arnson Date: Wed May 31 16:04:27 2023 -0400 Downgrade bad ProductVersion error to warning. Fixes https://github.com/wixtoolset/issues/issues/7522. commit 856658a8cbbf5bac9c0bf5cfced01a009c59f80d Author: Rob Mensching Date: Tue May 30 08:28:14 2023 -0700 Obsolete removed PackageState.Cached value A package's cached status is no longer set via the PackageState. The value was removed in native code but the managed code was missed throwing off the enum mapping. Fixes 7399 commit 36e3408d6f99da692fff5720ff2867faad2803ea Author: Rob Mensching Date: Mon May 22 09:47:17 2023 -0700 Update CLA signatures location commit ddc79979ced959d8e1b7bd326950a735b73343e5 Author: Bob Arnson Date: Wed May 17 18:14:59 2023 -0400 Fix duplicate keys from inline subdirectories. Fixes https://github.com/wixtoolset/issues/issues/7459. commit 1da042fc5f6cef4ea3cce10bd8a151f3a3d115cd Author: Bob Arnson Date: Mon May 15 21:05:50 2023 -0400 Skip processing missing FeatureComponents table. Fixes https://github.com/wixtoolset/issues/issues/7472. commit 5ddb81c8bb79687c19a24d96ec85b09c63fd9274 Author: Bob Arnson Date: Thu May 11 22:57:46 2023 -0400 Fix Dialog table decompilation. Fix duplicate NoMinimize attribute and add attributes that were entirely missing. Fixes https://github.com/wixtoolset/issues/issues/7482. commit f86155f6bd2be3f63b646f259f27c65b607d0368 Author: timberto <33670243+timberto@users.noreply.github.com> Date: Tue Apr 18 12:34:25 2023 +0200 Show correct error message when upgrade is blocked commit 13dbe58b6be7b7c5d6b01ef7273331fc5e2f252c Author: Bob Arnson Date: Tue May 2 20:39:26 2023 -0400 Fix Msmq key table relationship. Fixes https://github.com/wixtoolset/issues/issues/7454. commit 0844d6506420decaed9957e6de77cba02bd579e4 Author: Nir Bar Date: Sun Apr 23 07:55:21 2023 +0300 Fix build failure when bundle has multiple RemoteBundle elements commit 9b78fdffb9c651d5f03cffdde8b0ae6c77598d63 Author: fyodorkor Date: Thu Apr 13 22:37:02 2023 +0300 Fix XmlConfig decompile to produce schema element, Add unitest for XmlDecomile commit 5520259549e1ca3380c8e03d5660e399ed12c48e Author: Bob Arnson Date: Mon May 1 21:09:14 2023 -0400 Tame some wild pointers. Maybe Rust wouldn't be so bad... Fixes https://github.com/wixtoolset/issues/issues/7451. commit 1d7ca4c4d7edfb16be7ca24ca680c9b630d56e7c Author: Bob Arnson Date: Thu Apr 27 21:06:35 2023 -0400 Fix XmlConfig lookup logic. Fixes https://github.com/wixtoolset/issues/issues/7377. Requires fix for https://github.com/wixtoolset/issues/issues/7444. commit 1da29b425658bf1ddc774154d9bccaf940d0bc66 Author: Bob Arnson Date: Mon Apr 24 22:16:34 2023 -0400 Fix WcaErrorMessage `cArgs==-1` case. - Count args before creating message record. - Document terminating NULL requirement. - Add terminating NULL in MessageExit* macros. - Enhance tests for problems encountered fixing this nightmare. Fixes https://github.com/wixtoolset/issues/issues/7422. Fixes https://github.com/wixtoolset/issues/issues/7444. commit 29146461effb7798e054aa9933a88f612237df47 Author: Bob Arnson Date: Wed Apr 26 13:33:59 2023 -0400 Remove InstallDirDlg reference from WixUI_Mondo Fixes https://github.com/wixtoolset/issues/issues/7436. commit 846165622978edbea0f5cf58c32f35bc6bfe2906 Author: Bob Arnson Date: Thu Apr 20 18:23:07 2023 -0400 Fix merged ProductCode from mergemod.cub and... ...work around other bugs in mergemod.cub. Fixes https://github.com/wixtoolset/issues/issues/7413. commit 005a33a0d5ef8233f9d547fa28ae7f72731cad69 Author: Mike Wileczka <22036740+mwileczka@users.noreply.github.com> Date: Wed Apr 19 10:12:24 2023 -0400 Fix harvesting project names with invalid chars Project names with invalid characters, namely spaces, where not being harvested properly. The sanitized name is requires for the `Source="$(var.PROJECT_NAME` output. Updated harvest project unit test for project names with spaces. commit b4fe940cd1cb5798bc3e68a686289b8020cf7110 Author: Rob Mensching Date: Tue Apr 18 09:02:59 2023 -0700 Do not crash on Subdirectory when missing Component Directory attribute Fixes 7407 commit 2fa6703a77d547fb076cc3cdbb99b11bd4f5c039 Author: Rob Mensching Date: Tue Apr 18 08:43:26 2023 -0700 Allow "." as valid Directory SourceName Fixes 7384 commit 6d2fa6ac0a21c881a19767ef036349d31928415a Author: Rob Mensching Date: Fri Jun 2 22:55:23 2023 -0700 Start WiX Toolset v4.0.1 --- .github/workflows/build.yml | 6 +- src/dtf/SfxCA/SfxCA.vcxproj | 4 +- src/ext/Http/ca/CustomMsiErrors.h | 5 + src/ext/Http/ca/certexec.cpp | 431 ++++++++++ src/ext/Http/ca/certificates.cpp | 634 ++++++++++++++ src/ext/Http/ca/certificates.h | 23 + src/ext/Http/ca/cost.h | 5 +- src/ext/Http/ca/httpca.vcxproj | 16 +- src/ext/Http/ca/httpca.vcxproj.filters | 16 +- src/ext/Http/ca/precomp.h | 17 + src/ext/Http/ca/sslbinding.cpp | 778 ++++++++++++++++++ src/ext/Http/ca/sslcert.cpp | 695 ++++++++++++++++ src/ext/Http/ca/wixhttpca.def | 12 + .../HttpExtensionFixture.cs | 34 +- .../TestData/Ssl/Package.en-us.wxl | 9 + .../TestData/Ssl/Package.wxs | 15 + .../TestData/Ssl/PackageComponents.wxs | 20 + .../TestData/Ssl/example.txt | 1 + src/ext/Http/wixext/HttpCompiler.cs | 393 +++++++++ src/ext/Http/wixext/HttpErrors.cs | 6 + src/ext/Http/wixext/HttpTableDefinitions.cs | 67 +- .../wixext/Symbols/HttpSymbolDefinitions.cs | 15 + .../wixext/Symbols/WixHttpCertificateHash.cs | 55 ++ .../Symbols/WixHttpCertificateSymbol.cs | 104 +++ .../WixHttpSslBindingCertificateSymbol.cs | 55 ++ .../wixext/Symbols/WixHttpSslBindingSymbol.cs | 95 +++ .../Http/wixlib/HttpExtension_Platform.wxi | 47 +- src/ext/Http/wixlib/en-us.wxl | 9 +- .../Directory.Packages.props.pp | 1 + 29 files changed, 3548 insertions(+), 20 deletions(-) create mode 100644 src/ext/Http/ca/CustomMsiErrors.h create mode 100644 src/ext/Http/ca/certexec.cpp create mode 100644 src/ext/Http/ca/certificates.cpp create mode 100644 src/ext/Http/ca/certificates.h create mode 100644 src/ext/Http/ca/sslbinding.cpp create mode 100644 src/ext/Http/ca/sslcert.cpp create mode 100644 src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.en-us.wxl create mode 100644 src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.wxs create mode 100644 src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/PackageComponents.wxs create mode 100644 src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/example.txt create mode 100644 src/ext/Http/wixext/Symbols/WixHttpCertificateHash.cs create mode 100644 src/ext/Http/wixext/Symbols/WixHttpCertificateSymbol.cs create mode 100644 src/ext/Http/wixext/Symbols/WixHttpSslBindingCertificateSymbol.cs create mode 100644 src/ext/Http/wixext/Symbols/WixHttpSslBindingSymbol.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47297ac50..ed1acb136 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,14 @@ on: branches: - master - develop + - develop-v4.0.1 + - develop402 pull_request: branches: - master - develop + - develop-v4.0.1 + - develop402 workflow_dispatch: inputs: tags: @@ -88,5 +92,5 @@ jobs: path: build/logs/ - name: Push to GitHub Packages - if: github.event_name == 'push' && github.repository_owner == 'wixtoolset' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + if: github.event_name == 'push' && github.repository_owner == 'wixtoolset' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/develop-v4.0.1' || github.ref == 'refs/heads/develop402') run: dotnet nuget push "build/artifacts/**/*.nupkg" --source https://nuget.pkg.github.com/wixtoolset/index.json --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate diff --git a/src/dtf/SfxCA/SfxCA.vcxproj b/src/dtf/SfxCA/SfxCA.vcxproj index 5c4f674f6..ccebc3860 100644 --- a/src/dtf/SfxCA/SfxCA.vcxproj +++ b/src/dtf/SfxCA/SfxCA.vcxproj @@ -67,7 +67,9 @@ - + + 0x0409 + diff --git a/src/ext/Http/ca/CustomMsiErrors.h b/src/ext/Http/ca/CustomMsiErrors.h new file mode 100644 index 000000000..aaa54e6dd --- /dev/null +++ b/src/ext/Http/ca/CustomMsiErrors.h @@ -0,0 +1,5 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define msierrCERTFailedOpen 26351 +#define msierrCERTFailedAdd 26352 diff --git a/src/ext/Http/ca/certexec.cpp b/src/ext/Http/ca/certexec.cpp new file mode 100644 index 000000000..31a8df823 --- /dev/null +++ b/src/ext/Http/ca/certexec.cpp @@ -0,0 +1,431 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#define SIXTY_FOUR_MEG 64 * 1024 * 1024 + +// prototypes +static HRESULT ExecuteCertificateOperation( + __in MSIHANDLE hInstall, + __in SCA_ACTION saAction, + __in DWORD dwStoreRoot + ); + +static HRESULT ReadCertificateFile( + __in LPCWSTR wzPath, + __out BYTE** prgbData, + __out DWORD* pcbData + ); + +static HRESULT InstallCertificatePackage( + __in HCERTSTORE hStore, + __in BOOL fUserCertificateStore, + __in LPCWSTR wzName, + __in_opt BYTE* rgbData, + __in DWORD cbData, + __in BOOL fVital, + __in_opt LPCWSTR wzPFXPassword + ); + +static HRESULT UninstallCertificatePackage( + __in HCERTSTORE hStore, + __in BOOL fUserCertificateStore, + __in LPCWSTR wzName + ); + +static HRESULT AddCertificate( + __in HCERTSTORE hStore, + __in PCCERT_CONTEXT pCertContext, + __in LPCWSTR wzCertificateUniqueName, + __in BOOL fVital +); + +/* **************************************************************** + AddUserHttpCertificate - CUSTOM ACTION ENTRY POINT for adding per-user + certificates + + * ***************************************************************/ +extern "C" UINT __stdcall AddUserHttpCertificate( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "AddUserHttpCertificate"); + ExitOnFailure(hr, "Failed to initialize AddUserHttpCertificate."); + + hr = ExecuteCertificateOperation(hInstall, SCA_ACTION_INSTALL, CERT_SYSTEM_STORE_CURRENT_USER); + ExitOnFailure(hr, "Failed to install per-user certificate."); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/* **************************************************************** + AddMachineCertificate - CUSTOM ACTION ENTRY POINT for adding + per-machine certificates + + * ***************************************************************/ +extern "C" UINT __stdcall AddMachineHttpCertificate( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "AddMachineHttpCertificate"); + ExitOnFailure(hr, "Failed to initialize AddMachineHttpCertificate."); + + hr = ExecuteCertificateOperation(hInstall, SCA_ACTION_INSTALL, CERT_SYSTEM_STORE_LOCAL_MACHINE); + ExitOnFailure(hr, "Failed to install per-machine certificate."); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/* **************************************************************** + DeleteUserHttpCertificate - CUSTOM ACTION ENTRY POINT for deleting + per-user certificates + + * ***************************************************************/ +extern "C" UINT __stdcall DeleteUserHttpCertificate( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "DeleteUserHttpCertificate"); + ExitOnFailure(hr, "Failed to initialize DeleteUserHttpCertificate."); + + hr = ExecuteCertificateOperation(hInstall, SCA_ACTION_UNINSTALL, CERT_SYSTEM_STORE_CURRENT_USER); + ExitOnFailure(hr, "Failed to uninstall per-user certificate."); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/* **************************************************************** + DeleteMachineHttpCertificate - CUSTOM ACTION ENTRY POINT for deleting + per-machine certificates + + * ***************************************************************/ +extern "C" UINT __stdcall DeleteMachineHttpCertificate( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "DeleteMachineHttpCertificate"); + ExitOnFailure(hr, "Failed to initialize DeleteMachineCertificate."); + + hr = ExecuteCertificateOperation(hInstall, SCA_ACTION_UNINSTALL, CERT_SYSTEM_STORE_LOCAL_MACHINE); + ExitOnFailure(hr, "Failed to uninstall per-machine certificate."); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +static HRESULT ExecuteCertificateOperation( + __in MSIHANDLE /*hInstall*/, + __in SCA_ACTION saAction, + __in DWORD dwStoreLocation + ) +{ + //AssertSz(FALSE, "Debug ExecuteCertificateOperation() here."); + Assert(saAction & SCA_ACTION_INSTALL || saAction & SCA_ACTION_UNINSTALL); + + HRESULT hr = S_OK; + LPWSTR pwzCaData = NULL; + LPWSTR pwz; + LPWSTR pwzName = NULL; + LPWSTR pwzStore = NULL; + int iAttributes = 0; + LPWSTR pwzPFXPassword = NULL; + LPWSTR pwzFilePath = NULL; + BYTE* pbData = NULL; + DWORD cbData = 0; + DWORD_PTR cbPFXPassword = 0; + + BOOL fUserStoreLocation = (CERT_SYSTEM_STORE_CURRENT_USER == dwStoreLocation); + HCERTSTORE hCertStore = NULL; + + hr = WcaGetProperty(L"CustomActionData", &pwzCaData); + ExitOnFailure(hr, "Failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCaData); + + pwz = pwzCaData; + hr = WcaReadStringFromCaData(&pwz, &pwzName); + ExitOnFailure(hr, "Failed to parse certificate name."); + hr = WcaReadStringFromCaData(&pwz, &pwzStore); + ExitOnFailure(hr, "Failed to parse CustomActionData, StoreName"); + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "Failed to parse certificate attribute"); + if (SCA_ACTION_INSTALL == saAction) // install operations need more data + { + hr = WcaReadStreamFromCaData(&pwz, &pbData, (DWORD_PTR*)&cbData); + ExitOnFailure(hr, "Failed to parse certificate stream."); + + hr = WcaReadStringFromCaData(&pwz, &pwzPFXPassword); + ExitOnFailure(hr, "Failed to parse certificate password."); + } + + // Open the right store. + hCertStore = ::CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, dwStoreLocation | CERT_STORE_MAXIMUM_ALLOWED_FLAG, pwzStore); + MessageExitOnNullWithLastError(hCertStore, hr, msierrCERTFailedOpen, "Failed to open certificate store: %ls", pwzStore); + + if (SCA_ACTION_INSTALL == saAction) // install operations need more data + { + // Uninstall existing versions of this package. Ignore any failures + // This is needed to clean up the private key of a cert when we replace an existing cert + // CertAddCertificateContextToStore(CERT_STORE_ADD_REPLACE_EXISTING) does not remove the private key if the cert is replaced + UninstallCertificatePackage(hCertStore, fUserStoreLocation, pwzName); + + hr = InstallCertificatePackage(hCertStore, fUserStoreLocation, pwzName, pbData, cbData, iAttributes & SCA_CERT_ATTRIBUTE_VITAL, pwzPFXPassword); + ExitOnFailure(hr, "Failed to install certificate."); + } + else + { + Assert(SCA_ACTION_UNINSTALL == saAction); + + hr = UninstallCertificatePackage(hCertStore, fUserStoreLocation, pwzName); + ExitOnFailure(hr, "Failed to uninstall certificate."); + } + +LExit: + if (NULL != pwzPFXPassword && SUCCEEDED(StrSize(pwzPFXPassword, &cbPFXPassword))) + { + SecureZeroMemory(pwzPFXPassword, cbPFXPassword); + } + + if (hCertStore) + { + if (!::CertCloseStore(hCertStore, CERT_CLOSE_STORE_CHECK_FLAG)) + { + WcaLog(LOGMSG_VERBOSE, "Cert store was closed but not all resources were freed. Error 0x%x", GetLastError()); + } + } + + ReleaseMem(pbData); + ReleaseStr(pwzFilePath); + ReleaseStr(pwzPFXPassword); + ReleaseStr(pwzStore); + ReleaseStr(pwzName); + ReleaseStr(pwzCaData); + return hr; +} + + +static HRESULT InstallCertificatePackage( + __in HCERTSTORE hStore, + __in BOOL fUserCertificateStore, + __in LPCWSTR wzName, + __in_opt BYTE* rgbData, + __in DWORD cbData, + __in BOOL fVital, + __in_opt LPCWSTR wzPFXPassword + ) +{ + HRESULT hr = S_OK; + + HCERTSTORE hPfxCertStore = NULL; + PCCERT_CONTEXT pCertContext = NULL; + CERT_BLOB blob = { 0 }; + DWORD dwKeyset = fUserCertificateStore ? CRYPT_USER_KEYSET : CRYPT_MACHINE_KEYSET; + DWORD dwEncodingType; + DWORD dwContentType; + DWORD dwFormatType; + LPWSTR pwzUniqueName = NULL; + int iUniqueId = 0; + + // Figure out what type of blob (certificate or PFX) we're dealing with here. + blob.pbData = rgbData; + blob.cbData = cbData; + + if (!::CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob, CERT_QUERY_CONTENT_FLAG_ALL, CERT_QUERY_FORMAT_FLAG_ALL, 0, &dwEncodingType, &dwContentType, &dwFormatType, NULL, NULL, (LPCVOID*)&pCertContext)) + { + ExitWithLastError(hr, "Failed to parse the certificate blob: %ls", wzName); + } + + hr = StrAllocFormatted(&pwzUniqueName, L"%s_wixCert_%d", wzName, ++iUniqueId); + ExitOnFailure(hr, "Failed to format unique name"); + + if (!pCertContext) + { + // If we have a PFX blob, get the first certificate out of the PFX and use that instead of the PFX. + if (dwContentType & CERT_QUERY_CONTENT_PFX) + { + ExitOnNull(wzPFXPassword, hr, E_INVALIDARG, "Failed to import PFX blob because no password was provided"); + + // If we fail and our password is blank, also try passing in NULL for the password (according to the docs) + hPfxCertStore = ::PFXImportCertStore((CRYPT_DATA_BLOB*)&blob, wzPFXPassword, dwKeyset); + if (NULL == hPfxCertStore && !*wzPFXPassword) + { + hPfxCertStore = ::PFXImportCertStore((CRYPT_DATA_BLOB*)&blob, NULL, dwKeyset); + } + ExitOnNullWithLastError(hPfxCertStore, hr, "Failed to open PFX file."); + + // Install all certificates in the PFX + for (pCertContext = ::CertEnumCertificatesInStore(hPfxCertStore, pCertContext); + pCertContext; + pCertContext = ::CertEnumCertificatesInStore(hPfxCertStore, pCertContext)) + { + hr = AddCertificate(hStore, pCertContext, pwzUniqueName, fVital); + MessageExitOnFailure(hr, msierrCERTFailedAdd, "Failed to add certificate to the store."); + + hr = StrAllocFormatted(&pwzUniqueName, L"%s_wixCert_%d", wzName, ++iUniqueId); + ExitOnFailure(hr, "Failed to format unique name"); + } + } + else + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected certificate type processed."); + } + } + else + { + hr = AddCertificate(hStore, pCertContext, pwzUniqueName, fVital); + MessageExitOnFailure(hr, msierrCERTFailedAdd, "Failed to add certificate to the store."); + } + + hr = WcaProgressMessage(COST_CERT_ADD, FALSE); + ExitOnFailure(hr, "Failed to send install progress message."); + +LExit: + ReleaseStr(pwzUniqueName); + + if (pCertContext) + { + ::CertFreeCertificateContext(pCertContext); + } + + // Close the stores after the context's are released. + if (hPfxCertStore) + { + if (!::CertCloseStore(hPfxCertStore, CERT_CLOSE_STORE_CHECK_FLAG)) + { + WcaLog(LOGMSG_VERBOSE, "PFX cert store was closed but not all resources were freed. Error 0x%x", GetLastError()); + } + } + + return hr; +} + + +static HRESULT UninstallCertificatePackage( + __in HCERTSTORE hStore, + __in BOOL fUserCertificateStore, + __in LPCWSTR wzName + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + PCCERT_CONTEXT pCertContext = NULL; + CRYPT_KEY_PROV_INFO* pPrivateKeyInfo = NULL; + LPWSTR pwzUniquePrefix = NULL; + int ccUniquePrefix = 0; + + hr = StrAllocFormatted(&pwzUniquePrefix, L"%s_wixCert_", wzName); + ExitOnFailure(hr, "Failed to format unique name"); + ccUniquePrefix = ::lstrlenW(pwzUniquePrefix); + + WcaLog(LOGMSG_STANDARD, "Deleting certificate that begin with friendly name: %ls", pwzUniquePrefix); + + // Loop through all certificates in the store, deleting the ones that begin with our prefix. + while (NULL != (pCertContext = ::CertFindCertificateInStore(hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, pCertContext))) + { + WCHAR wzFriendlyName[256] = { 0 }; + DWORD cbFriendlyName = sizeof(wzFriendlyName); + + if (::CertGetCertificateContextProperty(pCertContext, CERT_FRIENDLY_NAME_PROP_ID, reinterpret_cast(wzFriendlyName), &cbFriendlyName) && + lstrlenW(wzFriendlyName) >= ccUniquePrefix && + CSTR_EQUAL == ::CompareStringW(LOCALE_SYSTEM_DEFAULT, 0, pwzUniquePrefix, ccUniquePrefix, wzFriendlyName, ccUniquePrefix)) + { + PCCERT_CONTEXT pCertContextDelete = ::CertDuplicateCertificateContext(pCertContext); // duplicate the context so we can delete it with out disrupting the looping + if(pCertContextDelete) + { + // Delete the certificate and if successful delete the matching private key as well. + if (::CertDeleteCertificateFromStore(pCertContextDelete)) + { + // If we found private key info, delete it. + hr = CertReadProperty(pCertContextDelete, CERT_KEY_PROV_INFO_PROP_ID, &pPrivateKeyInfo, NULL); + if (SUCCEEDED(hr)) + { + HCRYPTPROV hProvIgnored = NULL; // ignored on deletes. + DWORD dwKeyset = fUserCertificateStore ? CRYPT_USER_KEYSET : CRYPT_MACHINE_KEYSET; + + if (!::CryptAcquireContextW(&hProvIgnored, pPrivateKeyInfo->pwszContainerName, pPrivateKeyInfo->pwszProvName, pPrivateKeyInfo->dwProvType, dwKeyset | CRYPT_DELETEKEYSET | CRYPT_SILENT)) + { + er = ::GetLastError(); + hr = HRESULT_FROM_WIN32(er); + } + + ReleaseNullMem(pPrivateKeyInfo); + } + else // don't worry about failures to delete private keys. + { + hr = S_OK; + } + } + else + { + er = ::GetLastError(); + hr = HRESULT_FROM_WIN32(er); + } + + if (FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "Failed to delete certificate with friendly name: %ls, continuing anyway. Error: 0x%x", wzFriendlyName, hr); + } + + pCertContextDelete = NULL; + } + } + } + + hr = WcaProgressMessage(COST_CERT_DELETE, FALSE); + ExitOnFailure(hr, "Failed to send uninstall progress message."); + +LExit: + ReleaseStr(pwzUniquePrefix); + ReleaseMem(pPrivateKeyInfo); + if(pCertContext) + { + ::CertFreeCertificateContext(pCertContext); + } + + return hr; +} + +static HRESULT AddCertificate( + __in HCERTSTORE hStore, + __in PCCERT_CONTEXT pCertContext, + __in LPCWSTR wzCertificateUniqueName, + __in BOOL fVital +) +{ + HRESULT hr = S_OK; + + WcaLog(LOGMSG_STANDARD, "Adding certificate: %ls", wzCertificateUniqueName); + + hr = CertInstallSingleCertificate(hStore, pCertContext, wzCertificateUniqueName); + if (FAILED(hr) && !fVital) + { + WcaLog(LOGMSG_STANDARD, "Could not add non-vital certificate: %ls due to error: 0x%x, continuing...", wzCertificateUniqueName, hr); + hr = S_FALSE; + } + + return hr; +} diff --git a/src/ext/Http/ca/certificates.cpp b/src/ext/Http/ca/certificates.cpp new file mode 100644 index 000000000..9e1cf777e --- /dev/null +++ b/src/ext/Http/ca/certificates.cpp @@ -0,0 +1,634 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// prototypes +static HRESULT ConfigureCertificates( + __in SCA_ACTION saAction + ); + +static HRESULT FindExistingCertificate( + __in LPCWSTR wzName, + __in DWORD dwStoreLocation, + __in LPCWSTR wzStore, + __out BYTE** prgbCertificate, + __out DWORD* pcbCertificate + ); + +static HRESULT ResolveCertificate( + __in LPCWSTR wzId, + __in LPCWSTR wzName, + __in DWORD dwStoreLocation, + __in LPCWSTR wzStoreName, + __in DWORD dwAttributess, + __in LPCWSTR wzData, + __in LPCWSTR wzPFXPassword, + __out BYTE** ppbCertificate, + __out DWORD* pcbCertificate + ); + +static HRESULT ReadCertificateFile( + __in LPCWSTR wzPath, + __out BYTE** prgbData, + __out DWORD* pcbData + ); + +static HRESULT CertificateToHash( + __in BYTE* pbCertificate, + __in DWORD cbCertificate, + __in DWORD dwStoreLocation, + __in LPCWSTR wzPFXPassword, + __in BYTE rgbHash[], + __in DWORD cbHash + ); + +LPCWSTR vcsCertQuery = L"SELECT `Certificate`, `Name`, `Component_`, `StoreLocation`, `StoreName`, `Attributes`, `Binary_`, `CertificatePath`, `PFXPassword` FROM `Wix4HttpSslCertificate`"; +enum eCertQuery { cqCertificate = 1, cqName, cqComponent, cqStoreLocation, cqStoreName, cqAttributes, cqCertificateBinary, cqCertificatePath, cqPFXPassword }; + +/******************************************************************** +InstallHttpCertificates - CUSTOM ACTION ENTRY POINT for installing + certificates + +********************************************************************/ +extern "C" UINT __stdcall InstallHttpCertificates( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + // initialize + hr = WcaInitialize(hInstall, "InstallHttpCertificates"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = ConfigureCertificates(SCA_ACTION_INSTALL); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +/******************************************************************** +UninstallHttpCertificates - CUSTOM ACTION ENTRY POINT for uninstalling + certificates + +********************************************************************/ +extern "C" UINT __stdcall UninstallHttpCertificates( + __in MSIHANDLE hInstall + ) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + // initialize + hr = WcaInitialize(hInstall, "UninstallHttpCertificates"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = ConfigureCertificates(SCA_ACTION_UNINSTALL); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +static HRESULT ConfigureCertificates( + __in SCA_ACTION saAction + ) +{ + //AssertSz(FALSE, "debug ConfigureCertificates()."); + + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + PMSIHANDLE hViewCertificate; + PMSIHANDLE hRecCertificate; + INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; + INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; + + WCHAR* pwzId = NULL; + WCHAR* pwzName = NULL; + WCHAR* pwzComponent = NULL; + int iData = 0; + DWORD dwStoreLocation = 0; + LPWSTR pwzStoreName = 0; + DWORD dwAttributes = 0; + WCHAR* pwzData = NULL; + WCHAR* pwzPFXPassword = NULL; + WCHAR* pwzCaData = NULL; + WCHAR* pwzRollbackCaData = NULL; + + BYTE* pbCertificate = NULL; + DWORD cbCertificate = 0; + DWORD_PTR cbPFXPassword = 0; + + // Bail quickly if the Certificate table isn't around. + if (S_OK != WcaTableExists(L"Wix4HttpSslCertificate")) + { + WcaLog(LOGMSG_VERBOSE, "Skipping ConfigureCertificates() - required table not present."); + ExitFunction1(hr = S_FALSE); + } + + // Process the Certificate table. + hr = WcaOpenExecuteView(vcsCertQuery, &hViewCertificate); + ExitOnFailure(hr, "failed to open view on Certificate table"); + + while (SUCCEEDED(hr = WcaFetchRecord(hViewCertificate, &hRecCertificate))) + { + hr = WcaGetRecordString(hRecCertificate, cqCertificate, &pwzId); // the id is just useful to have up front + ExitOnFailure(hr, "failed to get Certificate.Certificate"); + + hr = WcaGetRecordString(hRecCertificate, cqComponent, &pwzComponent); + ExitOnFailure(hr, "failed to get Certificate.Component_"); + + er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzComponent, &isInstalled, &isAction); + hr = HRESULT_FROM_WIN32(er); + ExitOnFailure(hr, "failed to get state for component: %ls", pwzComponent); + + if (!(WcaIsInstalling(isInstalled, isAction) && SCA_ACTION_INSTALL == saAction) && + !(WcaIsUninstalling(isInstalled, isAction) && SCA_ACTION_UNINSTALL == saAction) && + !(WcaIsReInstalling(isInstalled, isAction))) + { + WcaLog(LOGMSG_VERBOSE, "Skipping non-action certificate: %ls", pwzId); + continue; + } + + // extract the rest of the data from the Certificate table + hr = WcaGetRecordFormattedString(hRecCertificate, cqName, &pwzName); + ExitOnFailure(hr, "failed to get Certificate.Name"); + + hr = WcaGetRecordInteger(hRecCertificate, cqStoreLocation, &iData); + ExitOnFailure(hr, "failed to get Certificate.StoreLocation"); + + switch (iData) + { + case SCA_CERTSYSTEMSTORE_CURRENTUSER: + dwStoreLocation = CERT_SYSTEM_STORE_CURRENT_USER; + break; + case SCA_CERTSYSTEMSTORE_LOCALMACHINE: + dwStoreLocation = CERT_SYSTEM_STORE_LOCAL_MACHINE; + break; + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid store location value: %d", iData); + } + + hr = WcaGetRecordString(hRecCertificate, cqStoreName, &pwzStoreName); + ExitOnFailure(hr, "failed to get Certificate.StoreName"); + + hr = WcaGetRecordInteger(hRecCertificate, cqAttributes, reinterpret_cast(&dwAttributes)); + ExitOnFailure(hr, "failed to get Certificate.Attributes"); + + if (dwAttributes & SCA_CERT_ATTRIBUTE_BINARYDATA) + { + hr = WcaGetRecordString(hRecCertificate, cqCertificateBinary, &pwzData); + ExitOnFailure(hr, "failed to get Certificate.Binary_"); + } + else + { + hr = WcaGetRecordFormattedString(hRecCertificate, cqCertificatePath, &pwzData); + ExitOnFailure(hr, "failed to get Certificate.CertificatePath"); + } + + hr = WcaGetRecordFormattedString(hRecCertificate, cqPFXPassword, &pwzPFXPassword); + ExitOnFailure(hr, "failed to get Certificate.PFXPassword"); + + // Write the common data (for both install and uninstall) to the CustomActionData + // to pass data to the deferred CustomAction. + hr = StrAllocString(&pwzCaData, pwzName, 0); + ExitOnFailure(hr, "Failed to pass Certificate.Certificate to deferred CustomAction."); + hr = WcaWriteStringToCaData(pwzStoreName, &pwzCaData); + ExitOnFailure(hr, "Failed to pass Certificate.StoreName to deferred CustomAction."); + hr = WcaWriteIntegerToCaData(dwAttributes, &pwzCaData); + ExitOnFailure(hr, "Failed to pass Certificate.Attributes to deferred CustomAction."); + + // Copy the rollback data from the deferred data because it's the same up to this point. + hr = StrAllocString(&pwzRollbackCaData, pwzCaData, 0); + ExitOnFailure(hr, "Failed to allocate string for rollback CustomAction."); + + // Finally, schedule the correct deferred CustomAction to actually do work. + LPCWSTR wzAction = NULL; + LPCWSTR wzRollbackAction = NULL; + DWORD dwCost = 0; + if (SCA_ACTION_UNINSTALL == saAction) + { + // Find an existing certificate one (if there is one) to so we have it for rollback. + hr = FindExistingCertificate(pwzName, dwStoreLocation, pwzStoreName, &pbCertificate, &cbCertificate); + ExitOnFailure(hr, "Failed to search for existing certificate with friendly name: %ls", pwzName); + + if (pbCertificate) + { + hr = WcaWriteStreamToCaData(pbCertificate, cbCertificate, &pwzRollbackCaData); + ExitOnFailure(hr, "Failed to pass Certificate.Data to rollback CustomAction."); + + hr = WcaWriteStringToCaData(pwzPFXPassword, &pwzRollbackCaData); + ExitOnFailure(hr, "Failed to pass Certificate.PFXPassword to rollback CustomAction."); + + hr = WcaWriteIntegerToCaData(dwAttributes, &pwzCaData); + ExitOnFailure(hr, "Failed to pass Certificate.Attributes to deferred CustomAction."); + } + + // Pick the right action to run based on what store we're uninstalling from. + if (CERT_SYSTEM_STORE_LOCAL_MACHINE == dwStoreLocation) + { + wzAction = CUSTOM_ACTION_DECORATION(L"DeleteMachineHttpCertificate"); + if (pbCertificate) + { + wzRollbackAction = L"RollbackDeleteMachineHttpCertificate"; + } + } + else + { + wzAction = CUSTOM_ACTION_DECORATION(L"DeleteUserHttpCertificate"); + if (pbCertificate) + { + wzRollbackAction = L"RollbackDeleteUserHttpCertificate"; + } + } + dwCost = COST_CERT_DELETE; + } + else + { + // Actually get the certificate, resolve it to a blob, and get the blob's hash. + hr = ResolveCertificate(pwzId, pwzName, dwStoreLocation, pwzStoreName, dwAttributes, pwzData, pwzPFXPassword, &pbCertificate, &cbCertificate); + ExitOnFailure(hr, "Failed to resolve certificate: %ls", pwzId); + + hr = WcaWriteStreamToCaData(pbCertificate, cbCertificate, &pwzCaData); + ExitOnFailure(hr, "Failed to pass Certificate.Data to deferred CustomAction."); + + hr = WcaWriteStringToCaData(pwzPFXPassword, &pwzCaData); + ExitOnFailure(hr, "Failed to pass Certificate.PFXPassword to deferred CustomAction."); + + // Pick the right action to run based on what store we're installing into. + if (CERT_SYSTEM_STORE_LOCAL_MACHINE == dwStoreLocation) + { + wzAction = CUSTOM_ACTION_DECORATION(L"AddMachineHttpCertificate"); + wzRollbackAction = CUSTOM_ACTION_DECORATION(L"RollbackAddMachineHttpCertificate"); + } + else + { + wzAction = CUSTOM_ACTION_DECORATION(L"AddUserHttpCertificate"); + wzRollbackAction = CUSTOM_ACTION_DECORATION(L"RollbackAddUserHttpCertificate"); + } + dwCost = COST_CERT_ADD; + } + + if (wzRollbackAction) + { + hr = WcaDoDeferredAction(wzRollbackAction, pwzRollbackCaData, dwCost); + ExitOnFailure(hr, "Failed to schedule rollback certificate action '%ls' for: %ls", wzRollbackAction, pwzId); + } + + hr = WcaDoDeferredAction(wzAction, pwzCaData, dwCost); + ExitOnFailure(hr, "Failed to schedule certificate action '%ls' for: %ls", wzAction, pwzId); + + // Clean up for the next certificate. + ReleaseNullMem(pbCertificate); + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + +LExit: + if (NULL != pwzPFXPassword && SUCCEEDED(StrSize(pwzPFXPassword, &cbPFXPassword))) + { + SecureZeroMemory(pwzPFXPassword, cbPFXPassword); + } + + ReleaseMem(pbCertificate); + ReleaseStr(pwzCaData); + ReleaseStr(pwzPFXPassword); + ReleaseStr(pwzData); + ReleaseStr(pwzName); + ReleaseStr(pwzStoreName); + ReleaseStr(pwzComponent); + ReleaseStr(pwzId); + + return hr; +} + +static HRESULT ResolveCertificate( + __in LPCWSTR wzId, + __in LPCWSTR /*wzName*/, + __in DWORD dwStoreLocation, + __in LPCWSTR /*wzStoreName*/, + __in DWORD dwAttributes, + __in LPCWSTR wzData, + __in LPCWSTR wzPFXPassword, + __out BYTE** ppbCertificate, + __out DWORD* pcbCertificate + ) +{ + HRESULT hr = S_OK; + + LPWSTR pwzSql = NULL; + PMSIHANDLE hView; + PMSIHANDLE hRec; + MSIHANDLE hCertificateHashView = NULL; + MSIHANDLE hCertificateHashColumns = NULL; + + BYTE rgbCertificateHash[CB_CERTIFICATE_HASH] = { 0 }; + WCHAR wzEncodedCertificateHash[CB_CERTIFICATE_HASH * 2 + 1] = { 0 }; + + PMSIHANDLE hViewCertificateRequest, hRecCertificateRequest; + + WCHAR* pwzDistinguishedName = NULL; + WCHAR* pwzCA = NULL; + + BYTE* pbData = NULL; + DWORD cbData = 0; + + if (dwAttributes & SCA_CERT_ATTRIBUTE_REQUEST) + { + hr = E_NOTIMPL; + ExitOnFailure(hr, "Installing certificates by requesting them from a certificate authority is not currently supported"); + } + else if (dwAttributes & SCA_CERT_ATTRIBUTE_BINARYDATA) + { + // get the binary stream in Binary + hr = WcaTableExists(L"Binary"); + if (S_OK != hr) + { + if (SUCCEEDED(hr)) + { + hr = E_UNEXPECTED; + } + ExitOnFailure(hr, "Binary was referenced but there is no Binary table."); + } + + hr = StrAllocFormatted(&pwzSql, L"SELECT `Data` FROM `Binary` WHERE `Name`=\'%s\'", wzData); + ExitOnFailure(hr, "Failed to allocate Binary table query."); + + hr = WcaOpenExecuteView(pwzSql, &hView); + ExitOnFailure(hr, "Failed to open view on Binary table"); + + hr = WcaFetchSingleRecord(hView, &hRec); + ExitOnFailure(hr, "Failed to retrieve request from Binary table"); + + hr = WcaGetRecordStream(hRec, 1, &pbData, &cbData); + ExitOnFailure(hr, "Failed to ready Binary.Data for certificate."); + } + else if (dwAttributes == SCA_CERT_ATTRIBUTE_DEFAULT) + { + hr = ReadCertificateFile(wzData, &pbData, &cbData); + ExitOnFailure(hr, "Failed to read certificate from file path."); + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid Certificate.Attributes."); + } + + // If we have loaded a certificate, update the Certificate.Hash column. + if (pbData) + { + hr = CertificateToHash(pbData, cbData, dwStoreLocation, wzPFXPassword, rgbCertificateHash, countof(rgbCertificateHash)); + ExitOnFailure(hr, "Failed to get SHA1 hash of certificate."); + + hr = StrHexEncode(rgbCertificateHash, countof(rgbCertificateHash), wzEncodedCertificateHash, countof(wzEncodedCertificateHash)); + ExitOnFailure(hr, "Failed to hex encode SHA1 hash of certificate."); + + // Update the Wix4HttpSslCertificateHash table. + hr = WcaAddTempRecord(&hCertificateHashView, &hCertificateHashColumns, L"Wix4HttpSslCertificateHash", NULL, 0, 2, wzId, wzEncodedCertificateHash); + ExitOnFailure(hr, "Failed to add encoded hash for certificate: %ls", wzId); + } + + *ppbCertificate = pbData; + *pcbCertificate = cbData; + pbData = NULL; + +LExit: + if (hCertificateHashColumns) + { + ::MsiCloseHandle(hCertificateHashColumns); + } + + if (hCertificateHashView) + { + ::MsiCloseHandle(hCertificateHashView); + } + + ReleaseStr(pwzDistinguishedName); + ReleaseStr(pwzCA); + ReleaseMem(pbData); + ReleaseStr(pwzSql); + + return hr; +} + +static HRESULT ReadCertificateFile( + __in LPCWSTR wzPath, + __out BYTE** prgbData, + __out DWORD* pcbData + ) +{ + HRESULT hr = S_OK; + + PCCERT_CONTEXT pCertContext = NULL; + DWORD dwContentType; + BYTE* pbData = NULL; + DWORD cbData = 0; + + if (!::CryptQueryObject(CERT_QUERY_OBJECT_FILE, reinterpret_cast(wzPath), CERT_QUERY_CONTENT_FLAG_ALL, CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &dwContentType, NULL, NULL, NULL, (LPCVOID*)&pCertContext)) + { + ExitOnFailure(hr, "Failed to read certificate from file: %ls", wzPath); + } + + if (pCertContext) + { + cbData = pCertContext->cbCertEncoded; + pbData = static_cast(MemAlloc(cbData, FALSE)); + ExitOnNull(pbData, hr, E_OUTOFMEMORY, "Failed to allocate memory to read certificate from file: %ls", wzPath); + + CopyMemory(pbData, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded); + } + else + { + // If we have a PFX blob, get the first certificate out of the PFX and use that instead of the PFX. + if (dwContentType & CERT_QUERY_CONTENT_PFX) + { + SIZE_T size = 0; + + hr = FileRead(&pbData, &size, wzPath); + ExitOnFailure(hr, "Failed to read PFX file: %ls", wzPath); + + cbData = (DWORD)size; + } + else + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected certificate type read from disk."); + } + } + + *pcbData = cbData; + *prgbData = pbData; + pbData = NULL; + +LExit: + ReleaseMem(pbData); + return hr; +} + +static HRESULT CertificateToHash( + __in BYTE* pbCertificate, + __in DWORD cbCertificate, + __in DWORD dwStoreLocation, + __in LPCWSTR wzPFXPassword, + __in BYTE rgbHash[], + __in DWORD cbHash + ) +{ + HRESULT hr = S_OK; + + HCERTSTORE hPfxCertStore = NULL; + PCCERT_CONTEXT pCertContext = NULL; + PCCERT_CONTEXT pCertContextEnum = NULL; + CRYPT_DATA_BLOB blob = { 0 }; + CRYPT_KEY_PROV_INFO* pPfxInfo = NULL; + DWORD dwKeyset = (CERT_SYSTEM_STORE_CURRENT_USER == dwStoreLocation) ? CRYPT_USER_KEYSET : CRYPT_MACHINE_KEYSET; + DWORD dwEncodingType; + DWORD dwContentType; + DWORD dwFormatType; + + blob.pbData = pbCertificate; + blob.cbData = cbCertificate; + + if (!::CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob, CERT_QUERY_CONTENT_FLAG_ALL, CERT_QUERY_FORMAT_FLAG_ALL, 0, &dwEncodingType, &dwContentType, &dwFormatType, NULL, NULL, (LPCVOID*)&pCertContext)) + { + ExitWithLastError(hr, "Failed to process certificate as a valid certificate."); + } + + if (!pCertContext) + { + // If we have a PFX blob, get the first certificate out of the PFX and use that instead of the PFX. + if (dwContentType & CERT_QUERY_CONTENT_PFX) + { + // If we fail and our password is blank, also try passing in NULL for the password (according to the docs) + hPfxCertStore = ::PFXImportCertStore((CRYPT_DATA_BLOB*)&blob, wzPFXPassword, dwKeyset); + if (NULL == hPfxCertStore && !*wzPFXPassword) + { + hPfxCertStore = ::PFXImportCertStore((CRYPT_DATA_BLOB*)&blob, NULL, dwKeyset); + } + ExitOnNullWithLastError(hPfxCertStore, hr, "Failed to open PFX file."); + + // Find the first cert with a private key, or just use the last one + for (pCertContextEnum = ::CertEnumCertificatesInStore(hPfxCertStore, pCertContextEnum); + pCertContextEnum; + pCertContextEnum = ::CertEnumCertificatesInStore(hPfxCertStore, pCertContextEnum)) + { + pCertContext = pCertContextEnum; + + if (pCertContext && CertHasPrivateKey(pCertContext, NULL)) + { + break; + } + } + + ExitOnNullWithLastError(pCertContext, hr, "Failed to read first certificate out of PFX file."); + + // Ignore failures, the worst that happens is some parts of the PFX get left behind. + CertReadProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, &pPfxInfo, NULL); + } + else + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected certificate type processed."); + } + } + + DWORD cb = cbHash; + if (!::CertGetCertificateContextProperty(pCertContext, CERT_SHA1_HASH_PROP_ID, static_cast(rgbHash), &cb)) + { + ExitWithLastError(hr, "Failed to get certificate SHA1 hash property."); + } + AssertSz(cb == cbHash, "Did not correctly read certificate SHA1 hash."); + +LExit: + if (pCertContext) + { + ::CertFreeCertificateContext(pCertContext); + } + + if (hPfxCertStore) + { + ::CertCloseStore(hPfxCertStore, 0); + } + + if (pPfxInfo) + { + HCRYPTPROV hProvIgnored = NULL; // ignored on deletes. + ::CryptAcquireContextW(&hProvIgnored, pPfxInfo->pwszContainerName, pPfxInfo->pwszProvName, pPfxInfo->dwProvType, dwKeyset | CRYPT_DELETEKEYSET | CRYPT_SILENT); + + MemFree(pPfxInfo); + } + + return hr; +} + +static HRESULT FindExistingCertificate( + __in LPCWSTR wzName, + __in DWORD dwStoreLocation, + __in LPCWSTR wzStore, + __out BYTE** prgbCertificate, + __out DWORD* pcbCertificate + ) +{ + HRESULT hr = S_OK; + HCERTSTORE hCertStore = NULL; + PCCERT_CONTEXT pCertContext = NULL; + BYTE* pbCertificate = NULL; + DWORD cbCertificate = 0; + + hCertStore = ::CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, dwStoreLocation | CERT_STORE_READONLY_FLAG, wzStore); + MessageExitOnNullWithLastError(hCertStore, hr, msierrCERTFailedOpen, "Failed to open certificate store."); + + // Loop through the certificate, looking for certificates that match our friendly name. + pCertContext = CertFindCertificateInStore(hCertStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL); + while (pCertContext) + { + WCHAR wzFriendlyName[256] = { 0 }; + DWORD cbFriendlyName = sizeof(wzFriendlyName); + + if (::CertGetCertificateContextProperty(pCertContext, CERT_FRIENDLY_NAME_PROP_ID, reinterpret_cast(wzFriendlyName), &cbFriendlyName) && + CSTR_EQUAL == ::CompareStringW(LOCALE_SYSTEM_DEFAULT, 0, wzName, -1, wzFriendlyName, -1)) + { + // If the certificate with matching friendly name is valid, let's use that. + long lVerify = ::CertVerifyTimeValidity(NULL, pCertContext->pCertInfo); + if (0 == lVerify) + { + cbCertificate = pCertContext->cbCertEncoded; + pbCertificate = static_cast(MemAlloc(cbCertificate, FALSE)); + ExitOnNull(pbCertificate, hr, E_OUTOFMEMORY, "Failed to allocate memory to copy out exist certificate."); + + CopyMemory(pbCertificate, pCertContext->pbCertEncoded, cbCertificate); + break; // found a matching certificate, no more searching necessary + } + } + + // Next certificate in the store. + PCCERT_CONTEXT pNext = ::CertFindCertificateInStore(hCertStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, pCertContext); + // old pCertContext is freed by CertFindCertificateInStore + pCertContext = pNext; + } + + *prgbCertificate = pbCertificate; + *pcbCertificate = cbCertificate; + pbCertificate = NULL; + +LExit: + ReleaseMem(pbCertificate); + + if (pCertContext) + { + ::CertFreeCertificateContext(pCertContext); + } + + if (hCertStore) + { + ::CertCloseStore(hCertStore, 0); + } + + return hr; +} diff --git a/src/ext/Http/ca/certificates.h b/src/ext/Http/ca/certificates.h new file mode 100644 index 000000000..39b00d3d2 --- /dev/null +++ b/src/ext/Http/ca/certificates.h @@ -0,0 +1,23 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#define CB_CERTIFICATE_HASH 20 + +// Certificate.Attribute +enum SCA_CERT_ATTRIBUTES +{ + SCA_CERT_ATTRIBUTE_DEFAULT = 0, + SCA_CERT_ATTRIBUTE_REQUEST = 1, + SCA_CERT_ATTRIBUTE_BINARYDATA = 2, + SCA_CERT_ATTRIBUTE_OVERWRITE = 4, + SCA_CERT_ATTRIBUTE_VITAL = 8, +}; + + +// Certificate.StoreLocation +enum SCA_CERTSYSTEMSTORE +{ + SCA_CERTSYSTEMSTORE_CURRENTUSER = 1, + SCA_CERTSYSTEMSTORE_LOCALMACHINE = 2, +}; diff --git a/src/ext/Http/ca/cost.h b/src/ext/Http/ca/cost.h index 9677e7e83..cba7c40a8 100644 --- a/src/ext/Http/ca/cost.h +++ b/src/ext/Http/ca/cost.h @@ -1,6 +1,9 @@ #pragma once // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - const UINT COST_HTTP_URL_ACL = 2000; const UINT COST_HTTP_SNI_SSL = 2000; +const UINT COST_HTTP_SSL = 2000; + +const UINT COST_CERT_ADD = 5000; +const UINT COST_CERT_DELETE = 5000; diff --git a/src/ext/Http/ca/httpca.vcxproj b/src/ext/Http/ca/httpca.vcxproj index 42acd85d1..83fa6b023 100644 --- a/src/ext/Http/ca/httpca.vcxproj +++ b/src/ext/Http/ca/httpca.vcxproj @@ -1,6 +1,5 @@ - @@ -28,7 +27,6 @@ ARM64 - {90743805-C043-47C7-B5FF-8F5EE5C8A2DE} DynamicLibrary @@ -37,35 +35,33 @@ wixhttpca.def WiX Toolset Http CustomAction - - crypt32.lib;httpapi.lib;msi.lib;rpcrt4.lib;ws2_32.lib - + + Create + - + + - - - - + \ No newline at end of file diff --git a/src/ext/Http/ca/httpca.vcxproj.filters b/src/ext/Http/ca/httpca.vcxproj.filters index 2ccd604d7..8898362b8 100644 --- a/src/ext/Http/ca/httpca.vcxproj.filters +++ b/src/ext/Http/ca/httpca.vcxproj.filters @@ -24,6 +24,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -32,11 +41,16 @@ Header Files + + Header Files + + + Header Files + Source Files - \ No newline at end of file diff --git a/src/ext/Http/ca/precomp.h b/src/ext/Http/ca/precomp.h index 42287cb27..e2bb61971 100644 --- a/src/ext/Http/ca/precomp.h +++ b/src/ext/Http/ca/precomp.h @@ -1,19 +1,28 @@ #pragma once // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. +#if _WIN32_MSI < 150 +#define _WIN32_MSI 150 +#endif #include #include #include #include "wcautil.h" + +#include "certutil.h" #include "cryputil.h" +#include "fileutil.h" #include "dutil.h" #include "memutil.h" #include "strutil.h" #include "aclutil.h" +#include "wcawrapquery.h" #include "cost.h" +#include "certificates.h" +#include "CustomMsiErrors.h" #include "..\..\caDecor.h" @@ -23,3 +32,11 @@ enum eHandleExisting heIgnore = 1, heFail = 2 }; + +// Generic action enum. +enum SCA_ACTION +{ + SCA_ACTION_NONE, + SCA_ACTION_INSTALL, + SCA_ACTION_UNINSTALL +}; diff --git a/src/ext/Http/ca/sslbinding.cpp b/src/ext/Http/ca/sslbinding.cpp new file mode 100644 index 000000000..05530567b --- /dev/null +++ b/src/ext/Http/ca/sslbinding.cpp @@ -0,0 +1,778 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// prototypes +HRESULT HttpSslCertificateRead( + __in LPCWSTR wzBindingId, + __in WCA_WRAPQUERY_HANDLE hSslCertQuery, + __deref_out_z LPWSTR* ppwzCertificateThumbprint, + __deref_out_z LPWSTR* ppwzCertificateStore + ); + +static UINT SchedHttpSslBindings( + __in WCA_TODO todoSched +); +static HRESULT WriteExistingSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in HTTP_SERVICE_CONFIG_SSL_SET* pSslSet, + __inout_z LPWSTR* psczCustomActionData +); +static HRESULT WriteSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in_z LPCWSTR wzCertificateThumbprint, + __in_z LPCWSTR wzAppId, + __in_z_opt LPCWSTR wzCertificateStore, + __inout_z LPWSTR* psczCustomActionData +); +static HRESULT EnsureAppId( + __inout_z LPWSTR* psczAppId, + __in_opt HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet +); +static HRESULT StringFromGuid( + __in REFGUID rguid, + __inout_z LPWSTR* psczGuid +); +static HRESULT AddSslBinding( + __in_z LPCWSTR wzId, + __in_z LPWSTR wzHost, + __in int iPort, + __in BYTE rgbCertificateThumbprint[], + __in DWORD cbCertificateThumbprint, + __in GUID* pAppId, + __in_z LPWSTR wzSslCertStore +); +static HRESULT GetSslBinding( + __in_z LPWSTR wzHost, + __in int nPort, + __out HTTP_SERVICE_CONFIG_SSL_SET** ppSet +); +static HRESULT RemoveSslBinding( + __in_z LPCWSTR wzId, + __in_z LPWSTR wzHost, + __in int iPort +); +static HRESULT SetSslBindingSetKey( + __in HTTP_SERVICE_CONFIG_SSL_KEY* pKey, + __in_z LPWSTR wzHost, + __in int iPort +); + +LPCWSTR vcsWixHttpSslBindingQuery = +L"SELECT `WixHttpSslBinding`, `Host`, `Port`, `Thumbprint`, `AppId`, `Store`, `HandleExisting`, `Component_` " +L"FROM `Wix4HttpSslBinding`"; + +enum eWixHttpSslBindingQuery { hurqId = 1, hurqHost, hurqPort, hurqCertificateThumbprint, hurqAppId, hurqCertificateStore, hurqHandleExisting, hurqComponent }; + +LPCWSTR vcsSslCertificateQuery = L"SELECT `Wix4HttpSslCertificate`.`StoreName`, `Wix4HttpSslCertificateHash`.`Hash`, `Wix4HttpSslBindingCertificates`.`Binding_` FROM `Wix4HttpSslCertificate`, `Wix4HttpSslCertificateHash`, `Wix4HttpSslBindingCertificates` WHERE `Wix4HttpSslCertificate`.`Certificate`=`Wix4HttpSslCertificateHash`.`Certificate_` AND `Wix4HttpSslCertificateHash`.`Certificate_`=`Wix4HttpSslBindingCertificates`.`Certificate_`"; + +enum eSslCertificateQuery { scqStoreName = 1, scqHash, scqBinding }; + +#define msierrCERTFailedOpen 26351 + +/****************************************************************** + SchedWixHttpSslBindingsInstall - immediate custom action entry + point to prepare adding URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall SchedHttpSslBindingsInstall( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + + hr = WcaInitialize(hInstall, "SchedHttpSslBindingsInstall"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = SchedHttpSslBindings(WCA_TODO_INSTALL); + +LExit: + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +/****************************************************************** + SchedWixHttpSslBindingsUninstall - immediate custom action entry + point to prepare removing URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall SchedHttpSslBindingsUninstall( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + + hr = WcaInitialize(hInstall, "SchedHttpSslBindingsUninstall"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = SchedHttpSslBindings(WCA_TODO_UNINSTALL); + +LExit: + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +/****************************************************************** + ExecHttpSslBindings - deferred custom action entry point to + register and remove URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall ExecHttpSslBindings( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + BOOL fHttpInitialized = FALSE; + LPWSTR sczCustomActionData = NULL; + LPWSTR wz = NULL; + int iTodo = WCA_TODO_UNKNOWN; + LPWSTR sczId = NULL; + LPWSTR sczHost = NULL; + int iPort = 0; + eHandleExisting handleExisting = heIgnore; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPWSTR sczCertificateStore = NULL; + WCA_WRAPQUERY_HANDLE hSslCertQuery = NULL; + + BOOL fRollback = ::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK); + BOOL fRemove = FALSE; + BOOL fAdd = FALSE; + BOOL fFailOnExisting = FALSE; + + GUID guidAppId = { }; + BYTE* pbCertificateThumbprint = NULL; + DWORD cbCertificateThumbprint = 0; + + //AssertSz(FALSE, "Debug ExecHttpSslBindings() here."); + + // Initialize. + hr = WcaInitialize(hInstall, "ExecHttpSslBindings"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = HRESULT_FROM_WIN32(::HttpInitialize(HTTPAPI_VERSION_1, HTTP_INITIALIZE_CONFIG, NULL)); + ExitOnFailure(hr, "Failed to initialize HTTP Server configuration"); + + fHttpInitialized = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &sczCustomActionData); + ExitOnFailure(hr, "Failed to get CustomActionData"); + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", sczCustomActionData); + + wz = sczCustomActionData; + while (wz && *wz) + { + // Extract the custom action data and if rolling back, swap INSTALL and UNINSTALL. + hr = WcaReadIntegerFromCaData(&wz, &iTodo); + ExitOnFailure(hr, "Failed to read todo from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczId); + ExitOnFailure(hr, "Failed to read Id from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczHost); + ExitOnFailure(hr, "Failed to read Host from custom action data"); + + hr = WcaReadIntegerFromCaData(&wz, &iPort); + ExitOnFailure(hr, "Failed to read Port from custom action data"); + + hr = WcaReadIntegerFromCaData(&wz, reinterpret_cast(&handleExisting)); + ExitOnFailure(hr, "Failed to read HandleExisting from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to read CertificateThumbprint from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczAppId); + ExitOnFailure(hr, "Failed to read AppId from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczCertificateStore); + ExitOnFailure(hr, "Failed to read CertificateStore from custom action data"); + + hr = WcaBeginUnwrapQuery(&hSslCertQuery, &wz); + ExitOnFailure(hr, "Failed to unwrap ssl certificate query"); + + switch (iTodo) + { + case WCA_TODO_INSTALL: + case WCA_TODO_REINSTALL: + fRemove = heReplace == handleExisting || fRollback; + fAdd = !fRollback; + fFailOnExisting = heFail == handleExisting && !fRollback; + break; + + case WCA_TODO_UNINSTALL: + fRemove = !fRollback; + fAdd = fRollback; + fFailOnExisting = FALSE; + break; + } + + if (fRemove) + { + hr = RemoveSslBinding(sczId, sczHost, iPort); + if (S_OK == hr) + { + WcaLog(LOGMSG_STANDARD, "Removed SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else if (FAILED(hr)) + { + if (fRollback) + { + WcaLogError(hr, "Failed to remove SSL certificate binding to rollback '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else + { + ExitOnFailure(hr, "Failed to remove SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + } + } + + if (fAdd) + { + WcaLog(LOGMSG_STANDARD, "Adding SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + + // if we have been provided a thumbprint, then use that + if (*sczCertificateThumbprint) + { + } + else + { + hr = HttpSslCertificateRead(sczId, hSslCertQuery, &sczCertificateThumbprint, &sczCertificateStore); + ExitOnFailure(hr, "Failed to get SSL Certificate thumbprint."); + } + + hr = StrAllocHexDecode(sczCertificateThumbprint, &pbCertificateThumbprint, &cbCertificateThumbprint); + ExitOnFailure(hr, "Failed to convert thumbprint to bytes for SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + + hr = ::IIDFromString(sczAppId, &guidAppId); + ExitOnFailure(hr, "Failed to convert AppId '%ls' back to GUID for SSL certificate binding '%ls' for hostname: %ls:%d", sczAppId, sczId, sczHost, iPort); + + hr = AddSslBinding(sczId, sczHost, iPort, pbCertificateThumbprint, cbCertificateThumbprint, &guidAppId, sczCertificateStore && *sczCertificateStore ? sczCertificateStore : L"MY"); + if (S_FALSE == hr && fFailOnExisting) + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + } + + if (S_OK == hr) + { + WcaLog(LOGMSG_STANDARD, "Added SSL certificate binding '%ls' for hostname: %ls:%d with thumbprint: %ls", sczId, sczHost, iPort, sczCertificateThumbprint); + } + else if (FAILED(hr)) + { + if (fRollback) + { + WcaLogError(hr, "Failed to add SSL certificate binding to rollback '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else + { + ExitOnFailure(hr, "Failed to add SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + } + + ReleaseNullMem(pbCertificateThumbprint); + } + } + +LExit: + ReleaseMem(pbCertificateThumbprint); + ReleaseStr(sczCertificateStore); + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + ReleaseStr(sczHost); + ReleaseStr(sczId); + ReleaseStr(sczCustomActionData); + WcaFinishUnwrapQuery(hSslCertQuery); + + if (fHttpInitialized) + { + ::HttpTerminate(HTTP_INITIALIZE_CONFIG, NULL); + } + + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +static UINT SchedHttpSslBindings( + __in WCA_TODO todoSched +) +{ + HRESULT hr = S_OK; + //UINT er = ERROR_SUCCESS; + BOOL fHttpInitialized = FALSE; + DWORD cCertificates = 0; + + PMSIHANDLE hView = NULL; + PMSIHANDLE hRec = NULL; + PMSIHANDLE hQueryReq = NULL; + PMSIHANDLE hAceView = NULL; + + LPWSTR sczCustomActionData = NULL; + LPWSTR sczRollbackCustomActionData = NULL; + + LPWSTR sczId = NULL; + LPWSTR sczComponent = NULL; + WCA_TODO todoComponent = WCA_TODO_UNKNOWN; + LPWSTR sczHost = NULL; + int iPort = 0; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPWSTR sczCertificateStore = NULL; + int iHandleExisting = 0; + + HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet = NULL; + + //AssertSz(FALSE, "Debug SchedHttpSslBindings() here."); + + // Anything to do? + hr = WcaTableExists(L"Wix4HttpSslBinding"); + ExitOnFailure(hr, "Failed to check if the Wix4HttpSslBinding table exists"); + if (S_FALSE == hr) + { + WcaLog(LOGMSG_STANDARD, "Wix4HttpSslBinding table doesn't exist, so there are no URL reservations to configure"); + ExitFunction(); + } + + // Query and loop through all the SSL certificate bindings. + hr = WcaOpenExecuteView(vcsWixHttpSslBindingQuery, &hView); + ExitOnFailure(hr, "Failed to open view on the Wix4HttpSslBinding table"); + + hr = HRESULT_FROM_WIN32(::HttpInitialize(HTTPAPI_VERSION_1, HTTP_INITIALIZE_CONFIG, NULL)); + ExitOnFailure(hr, "Failed to initialize HTTP Server configuration"); + + fHttpInitialized = TRUE; + + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) + { + hr = WcaGetRecordString(hRec, hurqId, &sczId); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.WixHttpSslBinding"); + + hr = WcaGetRecordString(hRec, hurqComponent, &sczComponent); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Component_"); + + // Figure out what we're doing for this reservation, treating reinstall the same as install. + todoComponent = WcaGetComponentToDo(sczComponent); + if ((WCA_TODO_REINSTALL == todoComponent ? WCA_TODO_INSTALL : todoComponent) != todoSched) + { + WcaLog(LOGMSG_STANDARD, "Component '%ls' action state (%d) doesn't match request (%d) for Wix4HttpSslBinding '%ls'", sczComponent, todoComponent, todoSched, sczId); + continue; + } + + hr = WcaGetRecordFormattedString(hRec, hurqHost, &sczHost); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Host"); + + hr = WcaGetRecordFormattedInteger(hRec, hurqPort, &iPort); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Port"); + + if (!sczHost || !*sczHost) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Require a Host value for Wix4HttpSslBinding '%ls'", sczId); + } + + if (!iPort) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Require a Port value for Wix4HttpSslBinding '%ls'", sczId); + } + + hr = WcaGetRecordFormattedString(hRec, hurqCertificateThumbprint, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to get Wix4HttpSniSslCert.CertificateThumbprint"); + + hr = WcaGetRecordFormattedString(hRec, hurqAppId, &sczAppId); + ExitOnFailure(hr, "Failed to get AppId for Wix4HttpSslBinding '%ls'", sczId); + + hr = WcaGetRecordFormattedString(hRec, hurqCertificateStore, &sczCertificateStore); + ExitOnFailure(hr, "Failed to get CertificateStore for Wix4HttpSslBinding '%ls'", sczId); + + hr = WcaGetRecordInteger(hRec, hurqHandleExisting, &iHandleExisting); + ExitOnFailure(hr, "Failed to get HandleExisting for Wix4HttpSslBinding '%ls'", sczId); + + hr = GetSslBinding(sczHost, iPort, &pExistingSslSet); + ExitOnFailure(hr, "Failed to get the existing SSL certificate for Wix4HttpSslBinding '%ls'", sczId); + + hr = EnsureAppId(&sczAppId, pExistingSslSet); + ExitOnFailure(hr, "Failed to ensure AppId for Wix4HttpSslBinding '%ls'", sczId); + + hr = WriteExistingSslBinding(todoComponent, sczId, sczHost, iPort, iHandleExisting, pExistingSslSet, &sczRollbackCustomActionData); + ExitOnFailure(hr, "Failed to write rollback custom action data for Wix4HttpSslBinding '%ls'", sczId); + + hr = WriteSslBinding(todoComponent, sczId, sczHost, iPort, iHandleExisting, sczCertificateThumbprint, sczAppId, sczCertificateStore, &sczCustomActionData); + ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslBinding '%ls'", sczId); + ++cCertificates; + + ReleaseNullMem(pExistingSslSet); + } + + // Reaching the end of the list is not an error. + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failure occurred while processing Wix4HttpSslBinding table"); + + // Schedule ExecHttpSslCerts if there's anything to do. + if (cCertificates) + { + WcaLog(LOGMSG_STANDARD, "Scheduling SSL certificate binding (%ls)", sczCustomActionData); + WcaLog(LOGMSG_STANDARD, "Scheduling rollback SSL certificate binding (%ls)", sczRollbackCustomActionData); + + if (WCA_TODO_INSTALL == todoSched) + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackHttpSslBindingsInstall"), sczRollbackCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule install SSL certificate binding rollback"); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecHttpSslBindingsInstall"), sczCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule install SSL certificate binding execution"); + } + else + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackHttpSslBindingsUninstall"), sczRollbackCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule uninstall SSL certificate binding rollback"); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecHttpSslBindingsUninstall"), sczCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule uninstall SSL certificate binding execution"); + } + } + else + { + WcaLog(LOGMSG_STANDARD, "No SSL certificate bindings scheduled"); + } + +LExit: + ReleaseMem(pExistingSslSet); + ReleaseStr(sczCertificateStore); + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + ReleaseStr(sczHost); + ReleaseStr(sczComponent); + ReleaseStr(sczId); + ReleaseStr(sczRollbackCustomActionData); + ReleaseStr(sczCustomActionData); + + if (fHttpInitialized) + { + ::HttpTerminate(HTTP_INITIALIZE_CONFIG, NULL); + } + + return hr; +} + +static HRESULT WriteExistingSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in HTTP_SERVICE_CONFIG_SSL_SET* pSslSet, + __inout_z LPWSTR* psczCustomActionData +) +{ + HRESULT hr = S_OK; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPCWSTR wzCertificateStore = NULL; + + if (pSslSet) + { + hr = StrAllocHexEncode(reinterpret_cast(pSslSet->ParamDesc.pSslHash), pSslSet->ParamDesc.SslHashLength, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to convert existing certificate thumbprint to hex for Wix4HttpSslBinding '%ls'", wzId); + + hr = StringFromGuid(pSslSet->ParamDesc.AppId, &sczAppId); + ExitOnFailure(hr, "Failed to copy existing AppId for Wix4HttpSslBinding '%ls'", wzId); + + wzCertificateStore = pSslSet->ParamDesc.pSslCertStoreName; + } + + hr = WriteSslBinding(action, wzId, wzHost, iPort, iHandleExisting, sczCertificateThumbprint ? sczCertificateThumbprint : L"", sczAppId ? sczAppId : L"", wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslBinding '%ls'", wzId); + +LExit: + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + + return hr; +} + +static HRESULT WriteSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in_z LPCWSTR wzCertificateThumbprint, + __in_z LPCWSTR wzAppId, + __in_z_opt LPCWSTR wzCertificateStore, + __inout_z LPWSTR* psczCustomActionData +) +{ + HRESULT hr = S_OK; + + hr = WcaWriteIntegerToCaData(action, psczCustomActionData); + ExitOnFailure(hr, "Failed to write action to custom action data"); + + hr = WcaWriteStringToCaData(wzId, psczCustomActionData); + ExitOnFailure(hr, "Failed to write id to custom action data"); + + hr = WcaWriteStringToCaData(wzHost, psczCustomActionData); + ExitOnFailure(hr, "Failed to write Host to custom action data"); + + hr = WcaWriteIntegerToCaData(iPort, psczCustomActionData); + ExitOnFailure(hr, "Failed to write Port to custom action data"); + + hr = WcaWriteIntegerToCaData(iHandleExisting, psczCustomActionData); + ExitOnFailure(hr, "Failed to write HandleExisting to custom action data"); + + hr = WcaWriteStringToCaData(wzCertificateThumbprint ? wzCertificateThumbprint : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write CertificateThumbprint to custom action data"); + + hr = WcaWriteStringToCaData(wzAppId, psczCustomActionData); + ExitOnFailure(hr, "Failed to write AppId to custom action data"); + + hr = WcaWriteStringToCaData(wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write CertificateStore to custom action data"); + + // Wrap vcsSslCertificateQuery to send to deferred CA + if (S_OK == WcaTableExists(L"Wix4HttpSslCertificate") && S_OK == WcaTableExists(L"Wix4HttpSslCertificateHash") && S_OK == WcaTableExists(L"Wix4HttpSslBindingCertificates")) + { + hr = WcaWrapQuery(vcsSslCertificateQuery, psczCustomActionData, 0, 0xFFFFFFFF, 0xFFFFFFFF); + ExitOnFailure(hr, "Failed to wrap SslCertificate query"); + } + else + { + hr = WcaWrapEmptyQuery(psczCustomActionData); + ExitOnFailure(hr, "Failed to wrap SslCertificate empty query"); + } + +LExit: + return hr; +} + +static HRESULT EnsureAppId( + __inout_z LPWSTR* psczAppId, + __in_opt HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet +) +{ + HRESULT hr = S_OK; + RPC_STATUS rs = RPC_S_OK; + GUID guid = { }; + + if (!psczAppId || !*psczAppId || !**psczAppId) + { + if (pExistingSslSet) + { + hr = StringFromGuid(pExistingSslSet->ParamDesc.AppId, psczAppId); + ExitOnFailure(hr, "Failed to ensure AppId guid"); + } + else + { + rs = ::UuidCreate(&guid); + hr = HRESULT_FROM_RPC(rs); + ExitOnRootFailure(hr, "Failed to create guid for AppId"); + + hr = StringFromGuid(guid, psczAppId); + ExitOnFailure(hr, "Failed to ensure AppId guid"); + } + } + +LExit: + return hr; +} + +static HRESULT StringFromGuid( + __in REFGUID rguid, + __inout_z LPWSTR* psczGuid +) +{ + HRESULT hr = S_OK; + WCHAR wzGuid[39]; + + if (!::StringFromGUID2(rguid, wzGuid, countof(wzGuid))) + { + hr = E_OUTOFMEMORY; + ExitOnRootFailure(hr, "Failed to convert guid into string"); + } + + hr = StrAllocString(psczGuid, wzGuid, 0); + ExitOnFailure(hr, "Failed to copy guid"); + +LExit: + return hr; +} + +static HRESULT AddSslBinding( + __in_z LPCWSTR /*wzId*/, + __in_z LPWSTR wzHost, + __in int iPort, + __in BYTE rgbCertificateThumbprint[], + __in DWORD cbCertificateThumbprint, + __in GUID* pAppId, + __in_z LPWSTR wzSslCertStore +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_SET set = { }; + SOCKADDR_STORAGE addr = { }; + + set.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&set.KeyDesc, wzHost, iPort); + set.ParamDesc.SslHashLength = cbCertificateThumbprint; + set.ParamDesc.pSslHash = rgbCertificateThumbprint; + set.ParamDesc.AppId = *pAppId; + set.ParamDesc.pSslCertStoreName = wzSslCertStore; + + er = ::HttpSetServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &set, sizeof(set), NULL); + if (ERROR_ALREADY_EXISTS == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + + return hr; +} + +static HRESULT GetSslBinding( + __in_z LPWSTR wzHost, + __in int nPort, + __out HTTP_SERVICE_CONFIG_SSL_SET** ppSet +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_QUERY query = { }; + HTTP_SERVICE_CONFIG_SSL_SET* pSet = NULL; + ULONG cbSet = 0; + SOCKADDR_STORAGE addr = { }; + + *ppSet = NULL; + + query.QueryDesc = HttpServiceConfigQueryExact; + query.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&query.KeyDesc, wzHost, nPort); + + er = ::HttpQueryServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &query, sizeof(query), pSet, cbSet, &cbSet, NULL); + if (ERROR_INSUFFICIENT_BUFFER == er) + { + pSet = reinterpret_cast(MemAlloc(cbSet, TRUE)); + ExitOnNull(pSet, hr, E_OUTOFMEMORY, "Failed to allocate query SSL certificate buffer"); + + er = ::HttpQueryServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &query, sizeof(query), pSet, cbSet, &cbSet, NULL); + } + + if (ERROR_SUCCESS == er) + { + *ppSet = pSet; + pSet = NULL; + } + else if (ERROR_FILE_NOT_FOUND == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + +LExit: + ReleaseMem(pSet); + + return hr; +} + +static HRESULT RemoveSslBinding( + __in_z LPCWSTR /*wzId*/, + __in_z LPWSTR wzHost, + __in int iPort +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_SET set = { }; + SOCKADDR_STORAGE addr = { }; + + set.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&set.KeyDesc, wzHost, iPort); + + er = ::HttpDeleteServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &set, sizeof(set), NULL); + if (ERROR_FILE_NOT_FOUND == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + + return hr; +} + +static HRESULT SetSslBindingSetKey( + __in HTTP_SERVICE_CONFIG_SSL_KEY* pKey, + __in_z LPWSTR wzHost, + __in int iPort +) +{ + DWORD er = ERROR_SUCCESS; + + SOCKADDR_IN* pss = reinterpret_cast(pKey->pIpPort); + pss->sin_family = AF_INET; + pss->sin_port = htons(static_cast(iPort)); + if (!InetPtonW(AF_INET, wzHost, &pss->sin_addr)) + { + er = WSAGetLastError(); + } + + HRESULT hr = HRESULT_FROM_WIN32(er); + return hr; +} + + +HRESULT HttpSslCertificateRead( + __in LPCWSTR wzBindingId, + __in WCA_WRAPQUERY_HANDLE hSslCertQuery, + __deref_out_z LPWSTR* ppwzCertificateThumbprint, + __deref_out_z LPWSTR* ppwzCertificateStore + ) +{ + HRESULT hr = S_OK; + + MSIHANDLE hRec; + LPWSTR pwzData = NULL; + + WcaFetchWrappedReset(hSslCertQuery); + + // Get the certificate information. + while (S_OK == (hr = WcaFetchWrappedRecordWhereString(hSslCertQuery, scqBinding, wzBindingId, &hRec))) + { + hr = WcaGetRecordString(hRec, scqStoreName, &pwzData); + ExitOnFailure(hr, "Failed to get http ssl certificate store name."); + + hr = StrAllocString(ppwzCertificateStore, pwzData, 0); + ExitOnFailure(hr, "Failed to copy certificate store name"); + + hr = WcaGetRecordString(hRec, scqHash, &pwzData); + ExitOnFailure(hr, "Failed to get hash for http ssl certificate."); + + hr = StrAllocString(ppwzCertificateThumbprint, pwzData, 0); + ExitOnFailure(hr, "Failed to copy http ssl certificate thumbprint."); + + // only one + break; + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to read HttpSslBindingCertificates table."); + +LExit: + ReleaseStr(pwzData); + return hr; +} + diff --git a/src/ext/Http/ca/sslcert.cpp b/src/ext/Http/ca/sslcert.cpp new file mode 100644 index 000000000..f7c226268 --- /dev/null +++ b/src/ext/Http/ca/sslcert.cpp @@ -0,0 +1,695 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static UINT SchedHttpSslBindings( + __in WCA_TODO todoSched +); +static HRESULT WriteExistingSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in HTTP_SERVICE_CONFIG_SSL_SET* pSslSet, + __inout_z LPWSTR* psczCustomActionData +); +static HRESULT WriteSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in_z LPCWSTR wzCertificateThumbprint, + __in_z LPCWSTR wzAppId, + __in_z_opt LPCWSTR wzCertificateStore, + __inout_z LPWSTR* psczCustomActionData +); +static HRESULT EnsureAppId( + __inout_z LPWSTR* psczAppId, + __in_opt HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet +); +static HRESULT StringFromGuid( + __in REFGUID rguid, + __inout_z LPWSTR* psczGuid +); +static HRESULT AddSslBinding( + __in_z LPCWSTR wzId, + __in_z LPWSTR wzHost, + __in int iPort, + __in BYTE rgbCertificateThumbprint[], + __in DWORD cbCertificateThumbprint, + __in GUID* pAppId, + __in_z LPWSTR wzSslCertStore +); +static HRESULT GetSslBinding( + __in_z LPWSTR wzHost, + __in int nPort, + __out HTTP_SERVICE_CONFIG_SSL_SET** ppSet +); +static HRESULT RemoveSslBinding( + __in_z LPCWSTR wzId, + __in_z LPWSTR wzHost, + __in int iPort +); +static HRESULT SetSslBindingSetKey( + __in HTTP_SERVICE_CONFIG_SSL_KEY* pKey, + __in_z LPWSTR wzHost, + __in int iPort +); + +LPCWSTR vcsWixHttpSslBindingQuery = +L"SELECT `WixHttpSslBinding`, `Host`, `Port`, `Thumbprint`, `AppId`, `Store`, `HandleExisting`, `Component_` " +L"FROM `Wix4HttpSslBinding`"; +enum eWixHttpSslBindingQuery { hurqId = 1, hurqHost, hurqPort, hurqCertificateThumbprint, hurqAppId, hurqCertificateStore, hurqHandleExisting, hurqComponent }; + +#define msierrCERTFailedOpen 26351 + +/****************************************************************** + SchedWixHttpSslBindingsInstall - immediate custom action entry + point to prepare adding URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall SchedHttpSslBindingsInstall( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + + hr = WcaInitialize(hInstall, "SchedHttpSslBindingsInstall"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = SchedHttpSslBindings(WCA_TODO_INSTALL); + +LExit: + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +/****************************************************************** + SchedWixHttpSslBindingsUninstall - immediate custom action entry + point to prepare removing URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall SchedHttpSslBindingsUninstall( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + + hr = WcaInitialize(hInstall, "SchedHttpSslBindingsUninstall"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = SchedHttpSslBindings(WCA_TODO_UNINSTALL); + +LExit: + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +/****************************************************************** + ExecHttpSslBindings - deferred custom action entry point to + register and remove URL reservations. + +********************************************************************/ +extern "C" UINT __stdcall ExecHttpSslBindings( + __in MSIHANDLE hInstall +) +{ + HRESULT hr = S_OK; + BOOL fHttpInitialized = FALSE; + LPWSTR sczCustomActionData = NULL; + LPWSTR wz = NULL; + int iTodo = WCA_TODO_UNKNOWN; + LPWSTR sczId = NULL; + LPWSTR sczHost = NULL; + int iPort = 0; + eHandleExisting handleExisting = heIgnore; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPWSTR sczCertificateStore = NULL; + + BOOL fRollback = ::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK); + BOOL fRemove = FALSE; + BOOL fAdd = FALSE; + BOOL fFailOnExisting = FALSE; + + GUID guidAppId = { }; + BYTE* pbCertificateThumbprint = NULL; + DWORD cbCertificateThumbprint = 0; + + // Initialize. + hr = WcaInitialize(hInstall, "ExecHttpSslBindings"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = HRESULT_FROM_WIN32(::HttpInitialize(HTTPAPI_VERSION_1, HTTP_INITIALIZE_CONFIG, NULL)); + ExitOnFailure(hr, "Failed to initialize HTTP Server configuration"); + + fHttpInitialized = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &sczCustomActionData); + ExitOnFailure(hr, "Failed to get CustomActionData"); + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", sczCustomActionData); + + wz = sczCustomActionData; + while (wz && *wz) + { + // Extract the custom action data and if rolling back, swap INSTALL and UNINSTALL. + hr = WcaReadIntegerFromCaData(&wz, &iTodo); + ExitOnFailure(hr, "Failed to read todo from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczId); + ExitOnFailure(hr, "Failed to read Id from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczHost); + ExitOnFailure(hr, "Failed to read Host from custom action data"); + + hr = WcaReadIntegerFromCaData(&wz, &iPort); + ExitOnFailure(hr, "Failed to read Port from custom action data"); + + hr = WcaReadIntegerFromCaData(&wz, reinterpret_cast(&handleExisting)); + ExitOnFailure(hr, "Failed to read HandleExisting from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to read CertificateThumbprint from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczAppId); + ExitOnFailure(hr, "Failed to read AppId from custom action data"); + + hr = WcaReadStringFromCaData(&wz, &sczCertificateStore); + ExitOnFailure(hr, "Failed to read CertificateStore from custom action data"); + + switch (iTodo) + { + case WCA_TODO_INSTALL: + case WCA_TODO_REINSTALL: + fRemove = heReplace == handleExisting || fRollback; + fAdd = !fRollback || *sczCertificateThumbprint; + fFailOnExisting = heFail == handleExisting && !fRollback; + break; + + case WCA_TODO_UNINSTALL: + fRemove = !fRollback; + fAdd = fRollback && *sczCertificateThumbprint; + fFailOnExisting = FALSE; + break; + } + + if (fRemove) + { + hr = RemoveSslBinding(sczId, sczHost, iPort); + if (S_OK == hr) + { + WcaLog(LOGMSG_STANDARD, "Removed SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else if (FAILED(hr)) + { + if (fRollback) + { + WcaLogError(hr, "Failed to remove SSL certificate binding to rollback '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else + { + ExitOnFailure(hr, "Failed to remove SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + } + } + + if (fAdd) + { + WcaLog(LOGMSG_STANDARD, "Adding SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + + hr = StrAllocHexDecode(sczCertificateThumbprint, &pbCertificateThumbprint, &cbCertificateThumbprint); + ExitOnFailure(hr, "Failed to convert thumbprint to bytes for SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + + hr = ::IIDFromString(sczAppId, &guidAppId); + ExitOnFailure(hr, "Failed to convert AppId '%ls' back to GUID for SSL certificate binding '%ls' for hostname: %ls:%d", sczAppId, sczId, sczHost, iPort); + + hr = AddSslBinding(sczId, sczHost, iPort, pbCertificateThumbprint, cbCertificateThumbprint, &guidAppId, sczCertificateStore && *sczCertificateStore ? sczCertificateStore : L"MY"); + if (S_FALSE == hr && fFailOnExisting) + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + } + + if (S_OK == hr) + { + WcaLog(LOGMSG_STANDARD, "Added SSL certificate binding '%ls' for hostname: %ls:%d with thumbprint: %ls", sczId, sczHost, iPort, sczCertificateThumbprint); + } + else if (FAILED(hr)) + { + if (fRollback) + { + WcaLogError(hr, "Failed to add SSL certificate binding to rollback '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + else + { + ExitOnFailure(hr, "Failed to add SSL certificate binding '%ls' for hostname: %ls:%d", sczId, sczHost, iPort); + } + } + + ReleaseNullMem(pbCertificateThumbprint); + } + } + +LExit: + ReleaseMem(pbCertificateThumbprint); + ReleaseStr(sczCertificateStore); + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + ReleaseStr(sczHost); + ReleaseStr(sczId); + ReleaseStr(sczCustomActionData); + + if (fHttpInitialized) + { + ::HttpTerminate(HTTP_INITIALIZE_CONFIG, NULL); + } + + return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS); +} + +static UINT SchedHttpSslBindings( + __in WCA_TODO todoSched +) +{ + HRESULT hr = S_OK; + //UINT er = ERROR_SUCCESS; + BOOL fHttpInitialized = FALSE; + DWORD cCertificates = 0; + + PMSIHANDLE hView = NULL; + PMSIHANDLE hRec = NULL; + PMSIHANDLE hQueryReq = NULL; + PMSIHANDLE hAceView = NULL; + + LPWSTR sczCustomActionData = NULL; + LPWSTR sczRollbackCustomActionData = NULL; + + LPWSTR sczId = NULL; + LPWSTR sczComponent = NULL; + WCA_TODO todoComponent = WCA_TODO_UNKNOWN; + LPWSTR sczHost = NULL; + int iPort = 0; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPWSTR sczCertificateStore = NULL; + int iHandleExisting = 0; + + HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet = NULL; + + // Anything to do? + hr = WcaTableExists(L"Wix4HttpSslBinding"); + ExitOnFailure(hr, "Failed to check if the Wix4HttpSslBinding table exists"); + if (S_FALSE == hr) + { + WcaLog(LOGMSG_STANDARD, "Wix4HttpSslBinding table doesn't exist, so there are no URL reservations to configure"); + ExitFunction(); + } + + // Query and loop through all the SSL certificate bindings. + hr = WcaOpenExecuteView(vcsWixHttpSslBindingQuery, &hView); + ExitOnFailure(hr, "Failed to open view on the Wix4HttpSslBinding table"); + + hr = HRESULT_FROM_WIN32(::HttpInitialize(HTTPAPI_VERSION_1, HTTP_INITIALIZE_CONFIG, NULL)); + ExitOnFailure(hr, "Failed to initialize HTTP Server configuration"); + + fHttpInitialized = TRUE; + + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) + { + hr = WcaGetRecordString(hRec, hurqId, &sczId); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.WixHttpSslBinding"); + + hr = WcaGetRecordString(hRec, hurqComponent, &sczComponent); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Component_"); + + // Figure out what we're doing for this reservation, treating reinstall the same as install. + todoComponent = WcaGetComponentToDo(sczComponent); + if ((WCA_TODO_REINSTALL == todoComponent ? WCA_TODO_INSTALL : todoComponent) != todoSched) + { + WcaLog(LOGMSG_STANDARD, "Component '%ls' action state (%d) doesn't match request (%d) for Wix4HttpSslBinding '%ls'", sczComponent, todoComponent, todoSched, sczId); + continue; + } + + hr = WcaGetRecordFormattedString(hRec, hurqHost, &sczHost); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Host"); + + hr = WcaGetRecordFormattedInteger(hRec, hurqPort, &iPort); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.Port"); + + hr = WcaGetRecordFormattedString(hRec, hurqCertificateThumbprint, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to get Wix4HttpSslBinding.CertificateThumbprint"); + + if (!sczHost || !*sczHost) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Require a Host value for Wix4HttpSslBinding '%ls'", sczId); + } + + if (!iPort) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Require a Port value for Wix4HttpSslBinding '%ls'", sczId); + } + + // XX TODO implement lookup + if (!sczCertificateThumbprint || !*sczCertificateThumbprint) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Require a CertificateThumbprint value for Wix4HttpSslBinding '%ls'", sczId); + } + + hr = WcaGetRecordFormattedString(hRec, hurqAppId, &sczAppId); + ExitOnFailure(hr, "Failed to get AppId for Wix4HttpSslBinding '%ls'", sczId); + + hr = WcaGetRecordFormattedString(hRec, hurqCertificateStore, &sczCertificateStore); + ExitOnFailure(hr, "Failed to get CertificateStore for Wix4HttpSslBinding '%ls'", sczId); + + hr = WcaGetRecordInteger(hRec, hurqHandleExisting, &iHandleExisting); + ExitOnFailure(hr, "Failed to get HandleExisting for Wix4HttpSslBinding '%ls'", sczId); + + hr = GetSslBinding(sczHost, iPort, &pExistingSslSet); + ExitOnFailure(hr, "Failed to get the existing SSL certificate for Wix4HttpSslBinding '%ls'", sczId); + + hr = EnsureAppId(&sczAppId, pExistingSslSet); + ExitOnFailure(hr, "Failed to ensure AppId for Wix4HttpSslBinding '%ls'", sczId); + + hr = WriteExistingSslBinding(todoComponent, sczId, sczHost, iPort, iHandleExisting, pExistingSslSet, &sczRollbackCustomActionData); + ExitOnFailure(hr, "Failed to write rollback custom action data for Wix4HttpSslBinding '%ls'", sczId); + + hr = WriteSslBinding(todoComponent, sczId, sczHost, iPort, iHandleExisting, sczCertificateThumbprint, sczAppId, sczCertificateStore, &sczCustomActionData); + ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslBinding '%ls'", sczId); + ++cCertificates; + + ReleaseNullMem(pExistingSslSet); + } + + // Reaching the end of the list is not an error. + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failure occurred while processing Wix4HttpSslBinding table"); + + // Schedule ExecHttpSslCerts if there's anything to do. + if (cCertificates) + { + WcaLog(LOGMSG_STANDARD, "Scheduling SSL certificate binding (%ls)", sczCustomActionData); + WcaLog(LOGMSG_STANDARD, "Scheduling rollback SSL certificate binding (%ls)", sczRollbackCustomActionData); + + if (WCA_TODO_INSTALL == todoSched) + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackHttpSslBindingsInstall"), sczRollbackCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule install SSL certificate binding rollback"); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecHttpSslBindingsInstall"), sczCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule install SSL certificate binding execution"); + } + else + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RollbackHttpSslBindingsUninstall"), sczRollbackCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule uninstall SSL certificate binding rollback"); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecHttpSslBindingsUninstall"), sczCustomActionData, cCertificates * COST_HTTP_SSL); + ExitOnFailure(hr, "Failed to schedule uninstall SSL certificate binding execution"); + } + } + else + { + WcaLog(LOGMSG_STANDARD, "No SSL certificate bindings scheduled"); + } + +LExit: + ReleaseMem(pExistingSslSet); + ReleaseStr(sczCertificateStore); + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + ReleaseStr(sczHost); + ReleaseStr(sczComponent); + ReleaseStr(sczId); + ReleaseStr(sczRollbackCustomActionData); + ReleaseStr(sczCustomActionData); + + if (fHttpInitialized) + { + ::HttpTerminate(HTTP_INITIALIZE_CONFIG, NULL); + } + + return hr; +} + +static HRESULT WriteExistingSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in HTTP_SERVICE_CONFIG_SSL_SET* pSslSet, + __inout_z LPWSTR* psczCustomActionData +) +{ + HRESULT hr = S_OK; + LPWSTR sczCertificateThumbprint = NULL; + LPWSTR sczAppId = NULL; + LPCWSTR wzCertificateStore = NULL; + + if (pSslSet) + { + hr = StrAllocHexEncode(reinterpret_cast(pSslSet->ParamDesc.pSslHash), pSslSet->ParamDesc.SslHashLength, &sczCertificateThumbprint); + ExitOnFailure(hr, "Failed to convert existing certificate thumbprint to hex for Wix4HttpSslBinding '%ls'", wzId); + + hr = StringFromGuid(pSslSet->ParamDesc.AppId, &sczAppId); + ExitOnFailure(hr, "Failed to copy existing AppId for Wix4HttpSslBinding '%ls'", wzId); + + wzCertificateStore = pSslSet->ParamDesc.pSslCertStoreName; + } + + hr = WriteSslBinding(action, wzId, wzHost, iPort, iHandleExisting, sczCertificateThumbprint ? sczCertificateThumbprint : L"", sczAppId ? sczAppId : L"", wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslBinding '%ls'", wzId); + +LExit: + ReleaseStr(sczAppId); + ReleaseStr(sczCertificateThumbprint); + + return hr; +} + +static HRESULT WriteSslBinding( + __in WCA_TODO action, + __in_z LPCWSTR wzId, + __in_z LPCWSTR wzHost, + __in int iPort, + __in int iHandleExisting, + __in_z LPCWSTR wzCertificateThumbprint, + __in_z LPCWSTR wzAppId, + __in_z_opt LPCWSTR wzCertificateStore, + __inout_z LPWSTR* psczCustomActionData +) +{ + HRESULT hr = S_OK; + + hr = WcaWriteIntegerToCaData(action, psczCustomActionData); + ExitOnFailure(hr, "Failed to write action to custom action data"); + + hr = WcaWriteStringToCaData(wzId, psczCustomActionData); + ExitOnFailure(hr, "Failed to write id to custom action data"); + + hr = WcaWriteStringToCaData(wzHost, psczCustomActionData); + ExitOnFailure(hr, "Failed to write Host to custom action data"); + + hr = WcaWriteIntegerToCaData(iPort, psczCustomActionData); + ExitOnFailure(hr, "Failed to write Port to custom action data"); + + hr = WcaWriteIntegerToCaData(iHandleExisting, psczCustomActionData); + ExitOnFailure(hr, "Failed to write HandleExisting to custom action data"); + + hr = WcaWriteStringToCaData(wzCertificateThumbprint ? wzCertificateThumbprint : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write CertificateThumbprint to custom action data"); + + hr = WcaWriteStringToCaData(wzAppId, psczCustomActionData); + ExitOnFailure(hr, "Failed to write AppId to custom action data"); + + hr = WcaWriteStringToCaData(wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData); + ExitOnFailure(hr, "Failed to write CertificateStore to custom action data"); + +LExit: + return hr; +} + +static HRESULT EnsureAppId( + __inout_z LPWSTR* psczAppId, + __in_opt HTTP_SERVICE_CONFIG_SSL_SET* pExistingSslSet +) +{ + HRESULT hr = S_OK; + RPC_STATUS rs = RPC_S_OK; + GUID guid = { }; + + if (!psczAppId || !*psczAppId || !**psczAppId) + { + if (pExistingSslSet) + { + hr = StringFromGuid(pExistingSslSet->ParamDesc.AppId, psczAppId); + ExitOnFailure(hr, "Failed to ensure AppId guid"); + } + else + { + rs = ::UuidCreate(&guid); + hr = HRESULT_FROM_RPC(rs); + ExitOnRootFailure(hr, "Failed to create guid for AppId"); + + hr = StringFromGuid(guid, psczAppId); + ExitOnFailure(hr, "Failed to ensure AppId guid"); + } + } + +LExit: + return hr; +} + +static HRESULT StringFromGuid( + __in REFGUID rguid, + __inout_z LPWSTR* psczGuid +) +{ + HRESULT hr = S_OK; + WCHAR wzGuid[39]; + + if (!::StringFromGUID2(rguid, wzGuid, countof(wzGuid))) + { + hr = E_OUTOFMEMORY; + ExitOnRootFailure(hr, "Failed to convert guid into string"); + } + + hr = StrAllocString(psczGuid, wzGuid, 0); + ExitOnFailure(hr, "Failed to copy guid"); + +LExit: + return hr; +} + +static HRESULT AddSslBinding( + __in_z LPCWSTR /*wzId*/, + __in_z LPWSTR wzHost, + __in int iPort, + __in BYTE rgbCertificateThumbprint[], + __in DWORD cbCertificateThumbprint, + __in GUID* pAppId, + __in_z LPWSTR wzSslCertStore +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_SET set = { }; + SOCKADDR_STORAGE addr = { }; + + set.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&set.KeyDesc, wzHost, iPort); + set.ParamDesc.SslHashLength = cbCertificateThumbprint; + set.ParamDesc.pSslHash = rgbCertificateThumbprint; + set.ParamDesc.AppId = *pAppId; + set.ParamDesc.pSslCertStoreName = wzSslCertStore; + + er = ::HttpSetServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &set, sizeof(set), NULL); + if (ERROR_ALREADY_EXISTS == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + + return hr; +} + +static HRESULT GetSslBinding( + __in_z LPWSTR wzHost, + __in int nPort, + __out HTTP_SERVICE_CONFIG_SSL_SET** ppSet +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_QUERY query = { }; + HTTP_SERVICE_CONFIG_SSL_SET* pSet = NULL; + ULONG cbSet = 0; + SOCKADDR_STORAGE addr = { }; + + *ppSet = NULL; + + query.QueryDesc = HttpServiceConfigQueryExact; + query.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&query.KeyDesc, wzHost, nPort); + + er = ::HttpQueryServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &query, sizeof(query), pSet, cbSet, &cbSet, NULL); + if (ERROR_INSUFFICIENT_BUFFER == er) + { + pSet = reinterpret_cast(MemAlloc(cbSet, TRUE)); + ExitOnNull(pSet, hr, E_OUTOFMEMORY, "Failed to allocate query SSL certificate buffer"); + + er = ::HttpQueryServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &query, sizeof(query), pSet, cbSet, &cbSet, NULL); + } + + if (ERROR_SUCCESS == er) + { + *ppSet = pSet; + pSet = NULL; + } + else if (ERROR_FILE_NOT_FOUND == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + +LExit: + ReleaseMem(pSet); + + return hr; +} + +static HRESULT RemoveSslBinding( + __in_z LPCWSTR /*wzId*/, + __in_z LPWSTR wzHost, + __in int iPort +) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HTTP_SERVICE_CONFIG_SSL_SET set = { }; + SOCKADDR_STORAGE addr = { }; + + set.KeyDesc.pIpPort = reinterpret_cast(&addr); + SetSslBindingSetKey(&set.KeyDesc, wzHost, iPort); + + er = ::HttpDeleteServiceConfiguration(NULL, HttpServiceConfigSSLCertInfo, &set, sizeof(set), NULL); + if (ERROR_FILE_NOT_FOUND == er) + { + hr = S_FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(er); + } + + return hr; +} + +static HRESULT SetSslBindingSetKey( + __in HTTP_SERVICE_CONFIG_SSL_KEY* pKey, + __in_z LPWSTR wzHost, + __in int iPort +) +{ + DWORD er = ERROR_SUCCESS; + + SOCKADDR_IN* pss = reinterpret_cast(pKey->pIpPort); + pss->sin_family = AF_INET; + pss->sin_port = htons(static_cast(iPort)); + if (!InetPtonW(AF_INET, wzHost, &pss->sin_addr)) + { + er = WSAGetLastError(); + } + + HRESULT hr = HRESULT_FROM_WIN32(er); + return hr; +} diff --git a/src/ext/Http/ca/wixhttpca.def b/src/ext/Http/ca/wixhttpca.def index 281c56317..c9deab5bc 100644 --- a/src/ext/Http/ca/wixhttpca.def +++ b/src/ext/Http/ca/wixhttpca.def @@ -10,3 +10,15 @@ EXPORTS SchedHttpSniSslCertsInstall SchedHttpSniSslCertsUninstall ExecHttpSniSslCerts +; sslbindings.cpp + SchedHttpSslBindingsInstall + SchedHttpSslBindingsUninstall + ExecHttpSslBindings +; certificates.cpp + InstallHttpCertificates + UninstallHttpCertificates +; certexec.cpp + AddUserHttpCertificate + AddMachineHttpCertificate + DeleteUserHttpCertificate + DeleteMachineHttpCertificate diff --git a/src/ext/Http/test/WixToolsetTest.Http/HttpExtensionFixture.cs b/src/ext/Http/test/WixToolsetTest.Http/HttpExtensionFixture.cs index 14b40bc86..4af19577b 100644 --- a/src/ext/Http/test/WixToolsetTest.Http/HttpExtensionFixture.cs +++ b/src/ext/Http/test/WixToolsetTest.Http/HttpExtensionFixture.cs @@ -10,7 +10,7 @@ namespace WixToolsetTest.Http public class HttpExtensionFixture { [Fact] - public void CanBuildUsingSniSssl() + public void CanBuildUsingSniSsl() { var folder = TestData.Get("TestData", "SniSsl"); var build = new Builder(folder, typeof(HttpExtensionFactory), new[] { folder }); @@ -28,6 +28,38 @@ public void CanBuildUsingSniSssl() }, results); } + [Fact] + public void CanBuildUsingSslBinding() + { + var folder = TestData.Get("TestData", "Ssl"); + var build = new Builder(folder, typeof(HttpExtensionFactory), new[] { folder }); + + var results = build.BuildAndQuery(Build, "CustomAction", "Wix4HttpSslCertificate", "Wix4HttpSslBinding", "Wix4HttpSslBindingCertificates"); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Wix4AddMachineHttpCertificate_X86\t11265\tWix4HttpCA_X86\tAddMachineHttpCertificate\t", + "CustomAction:Wix4AddUserHttpCertificate_X86\t25601\tWix4HttpCA_X86\tAddUserHttpCertificate\t", + "CustomAction:Wix4DeleteMachineHttpCertificate_X86\t11265\tWix4HttpCA_X86\tDeleteMachineHttpCertificate\t", + "CustomAction:Wix4DeleteUserHttpCertificate_X86\t25601\tWix4HttpCA_X86\tDeleteUserHttpCertificate\t", + "CustomAction:Wix4ExecHttpSslBindingsInstall_X86\t11265\tWix4HttpCA_X86\tExecHttpSslBindings\t", + "CustomAction:Wix4ExecHttpSslBindingsUninstall_X86\t11265\tWix4HttpCA_X86\tExecHttpSslBindings\t", + "CustomAction:Wix4InstallHttpCertificates_X86\t1\tWix4HttpCA_X86\tInstallHttpCertificates\t", + "CustomAction:Wix4RollbackAddMachineHttpCertificate_X86\t11521\tWix4HttpCA_X86\tDeleteMachineHttpCertificate\t", + "CustomAction:Wix4RollbackAddUserHttpCertificate_X86\t25857\tWix4HttpCA_X86\tDeleteUserHttpCertificate\t", + "CustomAction:Wix4RollbackDeleteMachineHttpCertificate_X86\t11521\tWix4HttpCA_X86\tAddMachineHttpCertificate\t", + "CustomAction:Wix4RollbackDeleteUserHttpCertificate_X86\t25857\tWix4HttpCA_X86\tAddUserHttpCertificate\t", + "CustomAction:Wix4RollbackHttpSslBindingsInstall_X86\t11521\tWix4HttpCA_X86\tExecHttpSslBindings\t", + "CustomAction:Wix4RollbackHttpSslBindingsUninstall_X86\t11521\tWix4HttpCA_X86\tExecHttpSslBindings\t", + "CustomAction:Wix4SchedHttpSslBindingsInstall_X86\t8193\tWix4HttpCA_X86\tSchedHttpSslBindingsInstall\t", + "CustomAction:Wix4SchedHttpSslBindingsUninstall_X86\t8193\tWix4HttpCA_X86\tSchedHttpSslBindingsUninstall\t", + //"CustomAction:Wix4RollbackDeleteMachineHttpCertificate_X86\t1\tWix4HttpCA_X86\tAddMachineHttpCertificate\t", + "CustomAction:Wix4UninstallHttpCertificates_X86\t1\tWix4HttpCA_X86\tUninstallHttpCertificates\t", + "Wix4HttpSslBinding:ssltjEpdUFkxO7rNF2TrXuGLJg5NwE\t0.0.0.0\t8081\t\t\t\t2\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo", + "Wix4HttpSslBindingCertificates:ssltjEpdUFkxO7rNF2TrXuGLJg5NwE\tSomeCertificate", + "Wix4HttpSslCertificate:SomeCertificate\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tSome Certificate\t2\tMY\t8\t\t[SOME_PATH]\t[PFX_PASS]", + }, results); + } + [Fact] public void CanBuildUsingUrlReservation() { diff --git a/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.en-us.wxl b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.en-us.wxl new file mode 100644 index 000000000..f1df1234b --- /dev/null +++ b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.en-us.wxl @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.wxs b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.wxs new file mode 100644 index 000000000..e13c018bb --- /dev/null +++ b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/Package.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/PackageComponents.wxs b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/PackageComponents.wxs new file mode 100644 index 000000000..5f5f1e274 --- /dev/null +++ b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/PackageComponents.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/example.txt b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/example.txt new file mode 100644 index 000000000..1b4ffe8a4 --- /dev/null +++ b/src/ext/Http/test/WixToolsetTest.Http/TestData/Ssl/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/ext/Http/wixext/HttpCompiler.cs b/src/ext/Http/wixext/HttpCompiler.cs index 51fdfebcc..03bbfc114 100644 --- a/src/ext/Http/wixext/HttpCompiler.cs +++ b/src/ext/Http/wixext/HttpCompiler.cs @@ -9,6 +9,7 @@ namespace WixToolset.Http using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Http.Symbols; + using static System.Net.Mime.MediaTypeNames; /// /// The compiler for the WiX Toolset Http Extension. @@ -52,6 +53,14 @@ public override void ParseElement(Intermediate intermediate, IntermediateSection this.ParseSniSslCertificateElement(intermediate, section, element, componentId); break; + case "SslBinding": + this.ParseSslBindingElement(intermediate, section, element, componentId); + break; + + case "Certificate": + this.ParseCertificateElement(intermediate, section, element, componentId); + break; + case "UrlReservation": this.ParseUrlReservationElement(intermediate, section, element, componentId, null); break; @@ -178,6 +187,338 @@ private void ParseSniSslCertificateElement(Intermediate intermediate, Intermedia } } + /// + /// Parses a Ssl element. + /// + /// The element to parse. + /// Identifier of the component that owns this SSL Certificate. + private void ParseSslBindingElement(Intermediate intermediate, IntermediateSection section, XElement node, string componentId) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); + Identifier id = null; + string host = null; + string port = null; + string appId = null; + string store = null; + string certificateRef = null; + string thumbprint = null; + var handleExisting = HandleExisting.Replace; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "AppId": + appId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HandleExisting": + var handleExistingValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + switch (handleExistingValue) + { + case "replace": + handleExisting = HandleExisting.Replace; + break; + case "ignore": + handleExisting = HandleExisting.Ignore; + break; + case "fail": + handleExisting = HandleExisting.Fail; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "HandleExisting", handleExistingValue, "replace", "ignore", "fail")); + break; + } + break; + case "Host": + host = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Port": + port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Thumbprint": + thumbprint = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Store": + store = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.ParseHelper.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); + } + } + + // Need the element ID for child element processing, so generate now if not authored. + if (null == id) + { + id = this.ParseHelper.CreateIdentifier("ssl", componentId, host, port); + } + + // Required attributes. + if (null == host) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Host")); + } + + if (null == port) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Port")); + } + + foreach (var child in node.Elements()) + { + if (this.Namespace == child.Name.Namespace) + { + var childSourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "CertificateRef": + if (null != thumbprint) + { + this.Messaging.Write(ErrorMessages.UnexpectedElementWithAttribute(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "Thumbprint")); + } + + if (null == componentId) + { + this.Messaging.Write(HttpErrors.IllegalElementWithoutComponent(childSourceLineNumbers, child.Name.LocalName)); + } + certificateRef = this.ParseCertificateRefElement(intermediate, section, child, id?.Id); + + if (null == certificateRef) + { + this.Messaging.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "CertificateRef")); + } + + break; + default: + this.ParseHelper.UnexpectedElement(node, child); + break; + } + } + else + { + this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, node, child); + } + } + + if (null == thumbprint && certificateRef == null) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint")); + } + + if (!this.Messaging.EncounteredError) + { + section.AddSymbol(new WixHttpSslBindingSymbol(sourceLineNumbers, id) + { + Host = host, + Port = port, + Thumbprint = thumbprint, + AppId = appId, + Store = store, + HandleExisting = handleExisting, + ComponentRef = componentId, + }); + + this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedHttpSslBindingsInstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedHttpSslBindingsUninstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + } + } + + /// + /// Parses a certificate element. + /// + /// Element to parse. + /// Identifier for parent component. + private void ParseCertificateElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + int attributes = 8; // SCA_CERT_ATTRIBUTE_VITAL + string binaryRef = null; + string certificatePath = null; + string name = null; + string pfxPassword = null; + int storeLocation = 0; + string storeName = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "BinaryRef": + attributes |= 2; // SCA_CERT_ATTRIBUTE_BINARYDATA + binaryRef = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Binary, binaryRef); + break; + case "CertificatePath": + certificatePath = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Overwrite": + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 4; // SCA_CERT_ATTRIBUTE_OVERWRITE + } + else + { + attributes &= ~4; // SCA_CERT_ATTRIBUTE_OVERWRITE + } + break; + case "PFXPassword": + pfxPassword = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Request": + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 1; // SCA_CERT_ATTRIBUTE_REQUEST + } + else + { + attributes &= ~1; // SCA_CERT_ATTRIBUTE_REQUEST + } + break; + case "StoreLocation": + var storeLocationValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + if (0 < storeLocationValue.Length) + { + switch (storeLocationValue) + { + case "currentUser": + storeLocation = 1; // SCA_CERTSYSTEMSTORE_CURRENTUSER + break; + case "localMachine": + storeLocation = 2; // SCA_CERTSYSTEMSTORE_LOCALMACHINE + break; + default: + storeLocation = -1; + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "StoreLocation", storeLocationValue, "currentUser", "localMachine")); + break; + } + } + break; + case "StoreName": + var storeNameValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + if (0 < storeNameValue.Length) + { + switch (storeNameValue) + { + case "ca": + storeName = "CA"; + break; + case "my": + case "personal": + storeName = "MY"; + break; + case "request": + storeName = "REQUEST"; + break; + case "root": + storeName = "Root"; + break; + case "otherPeople": + storeName = "AddressBook"; + break; + case "trustedPeople": + storeName = "TrustedPeople"; + break; + case "trustedPublisher": + storeName = "TrustedPublisher"; + break; + default: + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "StoreName", storeNameValue, "ca", "my", "request", "root", "otherPeople", "trustedPeople", "trustedPublisher")); + break; + } + } + break; + case "Vital": + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 8; // SCA_CERT_ATTRIBUTE_VITAL + } + else + { + attributes &= ~8; // SCA_CERT_ATTRIBUTE_VITAL + } + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + if (null == id) + { + id = this.ParseHelper.CreateIdentifier("crt", componentId, binaryRef, certificatePath); + } + + if (null == name) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); + } + + if (0 == storeLocation) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "StoreLocation")); + } + + if (null == storeName) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "StoreName")); + } + + if (null != binaryRef && null != certificatePath) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "BinaryRef", "CertificatePath", certificatePath)); + } + else if (null == binaryRef && null == certificatePath) + { + this.Messaging.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, element.Name.LocalName, "BinaryRef", "CertificatePath")); + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + // Reference InstallCertificates and UninstallCertificates since nothing will happen without them + this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4InstallHttpCertificates", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4UninstallHttpCertificates", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + this.ParseHelper.EnsureTable(section, sourceLineNumbers, "Wix4HttpSslCertificateHash"); // Certificate CustomActions require the CertificateHash table + + if (!this.Messaging.EncounteredError) + { + section.AddSymbol(new WixHttpCertificateSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Name = name, + StoreLocation = storeLocation, + StoreName = storeName, + Attributes = attributes, + BinaryRef = binaryRef, + CertificatePath = certificatePath, + PfxPassword = pfxPassword, + }); + } + } + /// /// Parses a UrlReservation element. /// @@ -378,5 +719,57 @@ private void ParseUrlAceElement(Intermediate intermediate, IntermediateSection s }); } } + + /// + /// Parses a CertificateRef extension element. + /// + /// Element to parse. + /// Identifier for parent Ssl binding. + private string ParseCertificateRefElement(Intermediate intermediate, IntermediateSection section, XElement element, string bindingId) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, HttpSymbolDefinitions.WixHttpCertificate, id.Id); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + + if (!this.Messaging.EncounteredError) + { + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, HttpSymbolDefinitions.WixHttpCertificate, id.Id); + + section.AddSymbol(new WixHttpSslBindingCertificateSymbol(sourceLineNumbers) + { + BindingRef = bindingId, + CertificateRef = id.Id, + }); + } + + return id?.Id; + } } } diff --git a/src/ext/Http/wixext/HttpErrors.cs b/src/ext/Http/wixext/HttpErrors.cs index e87adf544..07967e79e 100644 --- a/src/ext/Http/wixext/HttpErrors.cs +++ b/src/ext/Http/wixext/HttpErrors.cs @@ -23,9 +23,15 @@ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, Resour return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args); } + public static Message IllegalElementWithoutComponent(SourceLineNumber sourceLineNumbers, string elementName) + { + return Message(sourceLineNumbers, Ids.IllegalElementWithoutComponent, "The {0} element cannot be specified unless the element has a Component as an ancestor. A {0} that does not have a Component ancestor is not installed.", elementName); + } + public enum Ids { NoSecuritySpecified = 6701, + IllegalElementWithoutComponent = 6721, } } } diff --git a/src/ext/Http/wixext/HttpTableDefinitions.cs b/src/ext/Http/wixext/HttpTableDefinitions.cs index ea08337ff..fd23caee1 100644 --- a/src/ext/Http/wixext/HttpTableDefinitions.cs +++ b/src/ext/Http/wixext/HttpTableDefinitions.cs @@ -11,18 +11,75 @@ public static class HttpTableDefinitions HttpSymbolDefinitions.WixHttpSniSslCert, new[] { - new ColumnDefinition("Wix4HttpSniSslCert", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "The non-localized primary key for the table.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("WixHttpSniSslCert", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "The non-localized primary key for the table.", modularizeType: ColumnModularizeType.Column), new ColumnDefinition("Host", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Host for the SNI SSL certificate.", modularizeType: ColumnModularizeType.Property), new ColumnDefinition("Port", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Port for the SNI SSL certificate.", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("Thumbprint", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "humbprint of the SNI SSL certificate to find.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Thumbprint", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Thumbprint of the SNI SSL certificate to find.", modularizeType: ColumnModularizeType.Property), new ColumnDefinition("AppId", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Optional application id for the SNI SSL certificate.", modularizeType: ColumnModularizeType.Property), - new ColumnDefinition("Store", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Optional application id for the SNI SSL certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Store", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Certificate store containing the SNI SSL certificate.", modularizeType: ColumnModularizeType.Property), new ColumnDefinition("HandleExisting", ColumnType.Number, 4, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: 0, maxValue: 2, description: "The behavior when trying to install a SNI SSL certificate and it already exists."), new ColumnDefinition("Component_", ColumnType.String, 72, primaryKey: false, nullable: false, ColumnCategory.Identifier, keyTable: "Component", keyColumn: 1, description: "Foreign key into the Component table referencing the component that controls the URL reservation.", modularizeType: ColumnModularizeType.Column), }, symbolIdIsPrimaryKey: true ); + public static readonly TableDefinition WixHttpSslBinding = new TableDefinition( + "Wix4HttpSslBinding", + HttpSymbolDefinitions.WixHttpSslBinding, + new[] + { + new ColumnDefinition("WixHttpSslBinding", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "The non-localized primary key for the table.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("Host", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Host for the SSL certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Port", ColumnType.String, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Port for the SSL certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Thumbprint", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Thumbprint of the SSL certificate to find.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("AppId", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Optional application id for the SSL certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("Store", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Certificate store containing the SSL certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("HandleExisting", ColumnType.Number, 4, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: 0, maxValue: 2, description: "The behavior when trying to install a SSL certificate and it already exists."), + new ColumnDefinition("Component_", ColumnType.String, 72, primaryKey: false, nullable: false, ColumnCategory.Identifier, keyTable: "Component", keyColumn: 1, description: "Foreign key into the Component table referencing the component that controls the URL reservation.", modularizeType: ColumnModularizeType.Column), + }, + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition WixHttpSslBindingCertificates = new TableDefinition( + "Wix4HttpSslBindingCertificates", + HttpSymbolDefinitions.WixHttpSslBindingCertificates, + new[] + { + new ColumnDefinition("Binding_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyTable: "Wix4HttpSslBinding", keyColumn: 1, description: "The index into the SslBinding table.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("Certificate_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Text, keyTable: "Wix4HttpSslCertificate", keyColumn: 1, description: "The index into the Certificate table.", modularizeType: ColumnModularizeType.Column), + }, + symbolIdIsPrimaryKey: false + ); + + public static readonly TableDefinition WixHttpSslCertificate = new TableDefinition( + "Wix4HttpSslCertificate", + HttpSymbolDefinitions.WixHttpCertificate, + new[] + { + new ColumnDefinition("Certificate", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyColumn: 1, description: "Identifier for the certificate in the package.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("Component_", ColumnType.String, 72, primaryKey: false, nullable: false, ColumnCategory.Identifier, description: "Foreign key into the Component table used to determine install state", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("Name", ColumnType.String, 255, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Name to be used for the Certificate."), + new ColumnDefinition("StoreLocation", ColumnType.Number, 2, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: 1, maxValue: 2, description: "Location of the target certificate store (CurrentUser == 1, LocalMachine == 2)"), + new ColumnDefinition("StoreName", ColumnType.String, 64, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "Name of the target certificate store"), + new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: false, ColumnCategory.Unknown, minValue: 0, maxValue: 2147483647, description: "Attributes of the certificate"), + new ColumnDefinition("Binary_", ColumnType.String, 72, primaryKey: false, nullable: true, ColumnCategory.Identifier, keyTable: "Binary", keyColumn: 1, description: "Identifier to Binary table containing certificate.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("CertificatePath", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Property to path of certificate.", modularizeType: ColumnModularizeType.Property), + new ColumnDefinition("PFXPassword", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Hidden property to a pfx password", modularizeType: ColumnModularizeType.Property), + }, + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition WixHttpSslCertificateHash = new TableDefinition( + "Wix4HttpSslCertificateHash", + HttpSymbolDefinitions.WixHttpCertificateHash, + new[] + { + new ColumnDefinition("Certificate_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyColumn: 1, description: "Foreign key to certificate in Certificate table.", modularizeType: ColumnModularizeType.Column), + new ColumnDefinition("Hash", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Text, description: "Base64 encoded SHA1 hash of certificate populated at run-time."), + }, + symbolIdIsPrimaryKey: false + ); + public static readonly TableDefinition WixHttpUrlReservation = new TableDefinition( "Wix4HttpUrlReservation", HttpSymbolDefinitions.WixHttpUrlReservation, @@ -55,6 +112,10 @@ public static class HttpTableDefinitions WixHttpSniSslCert, WixHttpUrlReservation, WixHttpUrlAce, + WixHttpSslCertificate, + WixHttpSslBinding, + WixHttpSslBindingCertificates, + WixHttpSslCertificateHash }; } } diff --git a/src/ext/Http/wixext/Symbols/HttpSymbolDefinitions.cs b/src/ext/Http/wixext/Symbols/HttpSymbolDefinitions.cs index 2aa034688..4e38adbc4 100644 --- a/src/ext/Http/wixext/Symbols/HttpSymbolDefinitions.cs +++ b/src/ext/Http/wixext/Symbols/HttpSymbolDefinitions.cs @@ -10,6 +10,10 @@ public enum HttpSymbolDefinitionType WixHttpSniSslCert, WixHttpUrlAce, WixHttpUrlReservation, + WixHttpSslBinding, + WixHttpCertificate, + WixHttpSslBindingCertificates, + WixHttpCertificateHash } public static partial class HttpSymbolDefinitions @@ -33,12 +37,23 @@ public static IntermediateSymbolDefinition ByType(HttpSymbolDefinitionType type) case HttpSymbolDefinitionType.WixHttpSniSslCert: return HttpSymbolDefinitions.WixHttpSniSslCert; + case HttpSymbolDefinitionType.WixHttpSslBinding: + return HttpSymbolDefinitions.WixHttpSslBinding; + case HttpSymbolDefinitionType.WixHttpUrlAce: return HttpSymbolDefinitions.WixHttpUrlAce; case HttpSymbolDefinitionType.WixHttpUrlReservation: return HttpSymbolDefinitions.WixHttpUrlReservation; + case HttpSymbolDefinitionType.WixHttpCertificate: + return HttpSymbolDefinitions.WixHttpCertificate; + + case HttpSymbolDefinitionType.WixHttpSslBindingCertificates: + return HttpSymbolDefinitions.WixHttpSslBindingCertificates; + + case HttpSymbolDefinitionType.WixHttpCertificateHash: + return HttpSymbolDefinitions.WixHttpCertificateHash; default: throw new ArgumentOutOfRangeException(nameof(type)); } diff --git a/src/ext/Http/wixext/Symbols/WixHttpCertificateHash.cs b/src/ext/Http/wixext/Symbols/WixHttpCertificateHash.cs new file mode 100644 index 000000000..6668d4cda --- /dev/null +++ b/src/ext/Http/wixext/Symbols/WixHttpCertificateHash.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Http +{ + using WixToolset.Data; + using WixToolset.Http.Symbols; + + public static partial class HttpSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixHttpCertificateHash = new IntermediateSymbolDefinition( + HttpSymbolDefinitionType.WixHttpCertificateHash.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(WixHttpCertificateHashSymbolFields.CertificateRef), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpCertificateHashSymbolFields.Hash), IntermediateFieldType.String), + }, + typeof(WixHttpCertificateHashSymbol)); + } +} + +namespace WixToolset.Http.Symbols +{ + using WixToolset.Data; + + public enum WixHttpCertificateHashSymbolFields + { + CertificateRef, + Hash, + } + + public class WixHttpCertificateHashSymbol : IntermediateSymbol + { + public WixHttpCertificateHashSymbol() : base(HttpSymbolDefinitions.WixHttpCertificateHash, null, null) + { + } + + public WixHttpCertificateHashSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(HttpSymbolDefinitions.WixHttpCertificateHash, sourceLineNumber, id) + { + } + + public IntermediateField this[WixHttpCertificateHashSymbolFields index] => this.Fields[(int)index]; + + public string CertificateRef + { + get => this.Fields[(int)WixHttpCertificateHashSymbolFields.CertificateRef].AsString(); + set => this.Set((int)WixHttpCertificateHashSymbolFields.CertificateRef, value); + } + + public string Hash + { + get => this.Fields[(int)WixHttpCertificateHashSymbolFields.Hash].AsString(); + set => this.Set((int)WixHttpCertificateHashSymbolFields.Hash, value); + } + } +} diff --git a/src/ext/Http/wixext/Symbols/WixHttpCertificateSymbol.cs b/src/ext/Http/wixext/Symbols/WixHttpCertificateSymbol.cs new file mode 100644 index 000000000..ecabdb0ef --- /dev/null +++ b/src/ext/Http/wixext/Symbols/WixHttpCertificateSymbol.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Http +{ + using WixToolset.Data; + using WixToolset.Http.Symbols; + + public static partial class HttpSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixHttpCertificate = new IntermediateSymbolDefinition( + HttpSymbolDefinitionType.WixHttpCertificate.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.ComponentRef), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.Name), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.StoreLocation), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.StoreName), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.Attributes), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.BinaryRef), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.CertificatePath), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpCertificateSymbolFields.PfxPassword), IntermediateFieldType.String), + }, + typeof(WixHttpCertificateSymbol)); + } +} + +namespace WixToolset.Http.Symbols +{ + using WixToolset.Data; + using WixToolset.Http; + + public enum HttpCertificateSymbolFields + { + ComponentRef, + Name, + StoreLocation, + StoreName, + Attributes, + BinaryRef, + CertificatePath, + PfxPassword, + } + + public class WixHttpCertificateSymbol : IntermediateSymbol + { + public WixHttpCertificateSymbol() : base(HttpSymbolDefinitions.WixHttpCertificate, null, null) + { + } + + public WixHttpCertificateSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(HttpSymbolDefinitions.WixHttpCertificate, sourceLineNumber, id) + { + } + + public IntermediateField this[HttpCertificateSymbolFields index] => this.Fields[(int)index]; + + public string ComponentRef + { + get => this.Fields[(int)HttpCertificateSymbolFields.ComponentRef].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.ComponentRef, value); + } + + public string Name + { + get => this.Fields[(int)HttpCertificateSymbolFields.Name].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.Name, value); + } + + public int StoreLocation + { + get => this.Fields[(int)HttpCertificateSymbolFields.StoreLocation].AsNumber(); + set => this.Set((int)HttpCertificateSymbolFields.StoreLocation, value); + } + + public string StoreName + { + get => this.Fields[(int)HttpCertificateSymbolFields.StoreName].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.StoreName, value); + } + + public int Attributes + { + get => this.Fields[(int)HttpCertificateSymbolFields.Attributes].AsNumber(); + set => this.Set((int)HttpCertificateSymbolFields.Attributes, value); + } + + public string BinaryRef + { + get => this.Fields[(int)HttpCertificateSymbolFields.BinaryRef].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.BinaryRef, value); + } + + public string CertificatePath + { + get => this.Fields[(int)HttpCertificateSymbolFields.CertificatePath].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.CertificatePath, value); + } + + public string PfxPassword + { + get => this.Fields[(int)HttpCertificateSymbolFields.PfxPassword].AsString(); + set => this.Set((int)HttpCertificateSymbolFields.PfxPassword, value); + } + } +} diff --git a/src/ext/Http/wixext/Symbols/WixHttpSslBindingCertificateSymbol.cs b/src/ext/Http/wixext/Symbols/WixHttpSslBindingCertificateSymbol.cs new file mode 100644 index 000000000..6fab5a5b3 --- /dev/null +++ b/src/ext/Http/wixext/Symbols/WixHttpSslBindingCertificateSymbol.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Http +{ + using WixToolset.Data; + using WixToolset.Http.Symbols; + + public static partial class HttpSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixHttpSslBindingCertificates = new IntermediateSymbolDefinition( + HttpSymbolDefinitionType.WixHttpSslBindingCertificates.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(HttpSslBindingCertificatesSymbolFields.BindingRef), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(HttpSslBindingCertificatesSymbolFields.CertificateRef), IntermediateFieldType.String), + }, + typeof(WixHttpSslBindingCertificateSymbol)); + } +} + +namespace WixToolset.Http.Symbols +{ + using WixToolset.Data; + + public enum HttpSslBindingCertificatesSymbolFields + { + BindingRef, + CertificateRef, + } + + public class WixHttpSslBindingCertificateSymbol : IntermediateSymbol + { + public WixHttpSslBindingCertificateSymbol() : base(HttpSymbolDefinitions.WixHttpSslBindingCertificates, null, null) + { + } + + public WixHttpSslBindingCertificateSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(HttpSymbolDefinitions.WixHttpSslBindingCertificates, sourceLineNumber, id) + { + } + + public IntermediateField this[HttpSslBindingCertificatesSymbolFields index] => this.Fields[(int)index]; + + public string BindingRef + { + get => this.Fields[(int)HttpSslBindingCertificatesSymbolFields.BindingRef].AsString(); + set => this.Set((int)HttpSslBindingCertificatesSymbolFields.BindingRef, value); + } + + public string CertificateRef + { + get => this.Fields[(int)HttpSslBindingCertificatesSymbolFields.CertificateRef].AsString(); + set => this.Set((int)HttpSslBindingCertificatesSymbolFields.CertificateRef, value); + } + } +} diff --git a/src/ext/Http/wixext/Symbols/WixHttpSslBindingSymbol.cs b/src/ext/Http/wixext/Symbols/WixHttpSslBindingSymbol.cs new file mode 100644 index 000000000..a0f7f0d58 --- /dev/null +++ b/src/ext/Http/wixext/Symbols/WixHttpSslBindingSymbol.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Http +{ + using WixToolset.Data; + using WixToolset.Http.Symbols; + + public static partial class HttpSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition WixHttpSslBinding = new IntermediateSymbolDefinition( + HttpSymbolDefinitionType.WixHttpSslBinding.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.Host), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.Port), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.Thumbprint), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.AppId), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.Store), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.HandleExisting), IntermediateFieldType.Number), + new IntermediateFieldDefinition(nameof(WixHttpSslBindingSymbolFields.ComponentRef), IntermediateFieldType.String), + }, + typeof(WixHttpSslBindingSymbol)); + } +} + +namespace WixToolset.Http.Symbols +{ + using WixToolset.Data; + + public enum WixHttpSslBindingSymbolFields + { + Host, + Port, + Thumbprint, + AppId, + Store, + HandleExisting, + ComponentRef, + } + + public class WixHttpSslBindingSymbol : IntermediateSymbol + { + public WixHttpSslBindingSymbol() : base(HttpSymbolDefinitions.WixHttpSslBinding, null, null) + { + } + + public WixHttpSslBindingSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(HttpSymbolDefinitions.WixHttpSslBinding, sourceLineNumber, id) + { + } + + public IntermediateField this[WixHttpSslBindingSymbolFields index] => this.Fields[(int)index]; + + public string Host + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.Host].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.Host, value); + } + + public string Port + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.Port].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.Port, value); + } + + public string Thumbprint + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.Thumbprint].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.Thumbprint, value); + } + + public string AppId + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.AppId].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.AppId, value); + } + + public string Store + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.Store].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.Store, value); + } + + public HandleExisting HandleExisting + { + get => (HandleExisting)this.Fields[(int)WixHttpSslBindingSymbolFields.HandleExisting].AsNumber(); + set => this.Set((int)WixHttpSslBindingSymbolFields.HandleExisting, (int)value); + } + + public string ComponentRef + { + get => this.Fields[(int)WixHttpSslBindingSymbolFields.ComponentRef].AsString(); + set => this.Set((int)WixHttpSslBindingSymbolFields.ComponentRef, value); + } + } +} diff --git a/src/ext/Http/wixlib/HttpExtension_Platform.wxi b/src/ext/Http/wixlib/HttpExtension_Platform.wxi index 49d39c021..5b7688016 100644 --- a/src/ext/Http/wixlib/HttpExtension_Platform.wxi +++ b/src/ext/Http/wixlib/HttpExtension_Platform.wxi @@ -1,4 +1,4 @@ - + @@ -58,6 +58,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ext/Http/wixlib/en-us.wxl b/src/ext/Http/wixlib/en-us.wxl index de3e93cd7..b959ddbee 100644 --- a/src/ext/Http/wixlib/en-us.wxl +++ b/src/ext/Http/wixlib/en-us.wxl @@ -1,4 +1,4 @@ - + @@ -14,4 +14,11 @@ + + + + + + + diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index cea1def80..733623457 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -37,6 +37,7 @@ +