Skip to content

Commit

Permalink
CertificateRef support
Browse files Browse the repository at this point in the history
  • Loading branch information
solidstore committed Jul 19, 2023
1 parent 2f42efa commit dad178d
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 29 deletions.
139 changes: 127 additions & 12 deletions src/ext/Http/ca/sslcert.cpp
Expand Up @@ -21,6 +21,7 @@ static HRESULT WriteSslCert(
__in int iPort,
__in int iHandleExisting,
__in_z LPCWSTR wzCertificateThumbprint,
__in_z LPCWSTR wzCertificateRef,
__in_z LPCWSTR wzAppId,
__in_z_opt LPCWSTR wzCertificateStore,
__inout_z LPWSTR* psczCustomActionData
Expand Down Expand Up @@ -57,12 +58,20 @@ static HRESULT SetSslCertSetKey(
__in_z LPWSTR wzHost,
__in int iPort
);

static HRESULT FindExistingCertificate(
__in LPCWSTR wzName,
__in DWORD dwStoreLocation,
__in LPCWSTR wzStore,
__out BYTE** prgbCertificate,
__out DWORD* pcbCertificate
);

LPCWSTR vcsWixHttpSslCertQuery =
L"SELECT `WixHttpSslCert`, `Host`, `Port`, `Thumbprint`, `AppId`, `Store`, `HandleExisting`, `Component_` "
L"SELECT `WixHttpSslCert`, `Host`, `Port`, `Thumbprint`, `Certificate_`, `AppId`, `Store`, `HandleExisting`, `Component_` "
L"FROM `Wix4HttpSslCert`";
enum eWixHttpSslCertQuery { hurqId = 1, hurqHost, hurqPort, hurqCertificateThumbprint, hurqAppId, hurqCertificateStore, hurqHandleExisting, hurqComponent };
enum eWixHttpSslCertQuery { hurqId = 1, hurqHost, hurqPort, hurqCertificateThumbprint, hurqCertificateRef, hurqAppId, hurqCertificateStore, hurqHandleExisting, hurqComponent };

#define msierrCERTFailedOpen 26351

/******************************************************************
SchedWixHttpSslCertsInstall - immediate custom action entry
Expand Down Expand Up @@ -123,6 +132,7 @@ extern "C" UINT __stdcall ExecHttpSslCerts(
int iPort = 0;
eHandleExisting handleExisting = heIgnore;
LPWSTR sczCertificateThumbprint = NULL;
LPWSTR sczCertificateRef = NULL;
LPWSTR sczAppId = NULL;
LPWSTR sczCertificateStore = NULL;

Expand Down Expand Up @@ -170,6 +180,9 @@ extern "C" UINT __stdcall ExecHttpSslCerts(
hr = WcaReadStringFromCaData(&wz, &sczCertificateThumbprint);
ExitOnFailure(hr, "Failed to read CertificateThumbprint from custom action data");

hr = WcaReadStringFromCaData(&wz, &sczCertificateRef);
ExitOnFailure(hr, "Failed to read CertificateRef from custom action data");

hr = WcaReadStringFromCaData(&wz, &sczAppId);
ExitOnFailure(hr, "Failed to read AppId from custom action data");

Expand All @@ -181,13 +194,13 @@ extern "C" UINT __stdcall ExecHttpSslCerts(
case WCA_TODO_INSTALL:
case WCA_TODO_REINSTALL:
fRemove = heReplace == handleExisting || fRollback;
fAdd = !fRollback || *sczCertificateThumbprint;
fAdd = !fRollback || (*sczCertificateThumbprint || *sczCertificateRef);
fFailOnExisting = heFail == handleExisting && !fRollback;
break;

case WCA_TODO_UNINSTALL:
fRemove = !fRollback;
fAdd = fRollback && *sczCertificateThumbprint;
fAdd = fRollback && (*sczCertificateThumbprint || *sczCertificateRef);
fFailOnExisting = FALSE;
break;
}
Expand Down Expand Up @@ -216,8 +229,26 @@ extern "C" UINT __stdcall ExecHttpSslCerts(
{
WcaLog(LOGMSG_STANDARD, "Adding SSL certificate '%ls' for hostname: %ls:%d", sczId, sczHost, iPort);

hr = StrAllocHexDecode(sczCertificateThumbprint, &pbCertificateThumbprint, &cbCertificateThumbprint);
ExitOnFailure(hr, "Failed to convert thumbprint to bytes for SSL certificate '%ls' for hostname: %ls:%d", sczId, sczHost, iPort);
// if we have been provided a thumbprint, then use that
if (*sczCertificateThumbprint)
{
hr = StrAllocHexDecode(sczCertificateThumbprint, &pbCertificateThumbprint, &cbCertificateThumbprint);
ExitOnFailure(hr, "Failed to convert thumbprint to bytes for SSL certificate '%ls' for hostname: %ls:%d", sczId, sczHost, iPort);
}

// if we have been provided with a cerificate ref, use that to find an existing certificate
if (*sczCertificateRef)
{
hr = FindExistingCertificate(sczCertificateRef, CERT_SYSTEM_STORE_LOCAL_MACHINE, sczCertificateStore, &pbCertificateThumbprint, &cbCertificateThumbprint);
ExitOnFailure(hr, "Failed to convert thumbprint to bytes for referenced SSL certificate '%ls'", sczCertificateRef);
if (S_FALSE == hr)
{
ExitOnFailure(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), "Failed to find referenced SSL certificate '%ls'", sczCertificateRef);
}

hr = StrAllocHexEncode(pbCertificateThumbprint, cbCertificateThumbprint, &sczCertificateThumbprint);
ExitOnFailure(hr, "Failed to convert thumbprint for referenced SSL certificate '%ls'", sczCertificateRef);
}

hr = ::IIDFromString(sczAppId, &guidAppId);
ExitOnFailure(hr, "Failed to convert AppId '%ls' back to GUID for SSL certificate '%ls' for hostname: %ls:%d", sczAppId, sczId, sczHost, iPort);
Expand Down Expand Up @@ -288,6 +319,7 @@ static UINT SchedHttpSslCerts(
LPWSTR sczHost = NULL;
int iPort = 0;
LPWSTR sczCertificateThumbprint = NULL;
LPWSTR sczCertificateRef = NULL;
LPWSTR sczAppId = NULL;
LPWSTR sczCertificateStore = NULL;
int iHandleExisting = 0;
Expand Down Expand Up @@ -337,6 +369,9 @@ static UINT SchedHttpSslCerts(
hr = WcaGetRecordFormattedString(hRec, hurqCertificateThumbprint, &sczCertificateThumbprint);
ExitOnFailure(hr, "Failed to get Wix4HttpSslCert.CertificateThumbprint");

hr = WcaGetRecordString(hRec, hurqCertificateRef, &sczCertificateRef);
ExitOnFailure(hr, "Failed to get Wix4HttpSslCert.CertificateRef");

if (!sczHost || !*sczHost)
{
hr = E_INVALIDARG;
Expand All @@ -349,11 +384,11 @@ static UINT SchedHttpSslCerts(
ExitOnFailure(hr, "Require a Port value for Wix4HttpSslCert '%ls'", sczId);
}

if (!sczCertificateThumbprint || !*sczCertificateThumbprint)
/*if (!sczCertificateThumbprint || !*sczCertificateThumbprint)
{
hr = E_INVALIDARG;
ExitOnFailure(hr, "Require a CertificateThumbprint value for Wix4HttpSslCert '%ls'", sczId);
}
}*/

hr = WcaGetRecordFormattedString(hRec, hurqAppId, &sczAppId);
ExitOnFailure(hr, "Failed to get AppId for Wix4HttpSslCert '%ls'", sczId);
Expand All @@ -373,7 +408,7 @@ static UINT SchedHttpSslCerts(
hr = WriteExistingSslCert(todoComponent, sczId, sczHost, iPort, iHandleExisting, pExistingSslSet, &sczRollbackCustomActionData);
ExitOnFailure(hr, "Failed to write rollback custom action data for Wix4HttpSslCert '%ls'", sczId);

hr = WriteSslCert(todoComponent, sczId, sczHost, iPort, iHandleExisting, sczCertificateThumbprint, sczAppId, sczCertificateStore, &sczCustomActionData);
hr = WriteSslCert(todoComponent, sczId, sczHost, iPort, iHandleExisting, sczCertificateThumbprint, sczCertificateRef, sczAppId, sczCertificateStore, &sczCustomActionData);
ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslCert '%ls'", sczId);
++cCertificates;

Expand Down Expand Up @@ -458,7 +493,7 @@ static HRESULT WriteExistingSslCert(
wzCertificateStore = pSslSet->ParamDesc.pSslCertStoreName;
}

hr = WriteSslCert(action, wzId, wzHost, iPort, iHandleExisting, sczCertificateThumbprint ? sczCertificateThumbprint : L"", sczAppId ? sczAppId : L"", wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData);
hr = WriteSslCert(action, wzId, wzHost, iPort, iHandleExisting, sczCertificateThumbprint ? sczCertificateThumbprint : L"", NULL, sczAppId ? sczAppId : L"", wzCertificateStore ? wzCertificateStore : L"", psczCustomActionData);
ExitOnFailure(hr, "Failed to write custom action data for Wix4HttpSslCert '%ls'", wzId);

LExit:
Expand All @@ -475,6 +510,7 @@ static HRESULT WriteSslCert(
__in int iPort,
__in int iHandleExisting,
__in_z LPCWSTR wzCertificateThumbprint,
__in_z LPCWSTR wzCertificateRef,
__in_z LPCWSTR wzAppId,
__in_z_opt LPCWSTR wzCertificateStore,
__inout_z LPWSTR* psczCustomActionData
Expand All @@ -497,9 +533,12 @@ static HRESULT WriteSslCert(
hr = WcaWriteIntegerToCaData(iHandleExisting, psczCustomActionData);
ExitOnFailure(hr, "Failed to write HandleExisting to custom action data");

hr = WcaWriteStringToCaData(wzCertificateThumbprint, psczCustomActionData);
hr = WcaWriteStringToCaData(wzCertificateThumbprint ? wzCertificateThumbprint : L"", psczCustomActionData);
ExitOnFailure(hr, "Failed to write CertificateThumbprint to custom action data");

hr = WcaWriteStringToCaData(wzCertificateRef ? wzCertificateRef : L"", psczCustomActionData);
ExitOnFailure(hr, "Failed to write CertificateRef to custom action data");

hr = WcaWriteStringToCaData(wzAppId, psczCustomActionData);
ExitOnFailure(hr, "Failed to write AppId to custom action data");

Expand Down Expand Up @@ -691,3 +730,79 @@ static HRESULT SetSslCertSetKey(
HRESULT hr = HRESULT_FROM_WIN32(er);
return hr;
}

static HRESULT FindExistingCertificate(
__in LPCWSTR wzName,
__in DWORD dwStoreLocation,
__in LPCWSTR wzStore,
__out BYTE** ppbCertificateThumbprint,
__out DWORD* pcbCertificateThumbprint
)
{
HRESULT hr = S_FALSE;
HCERTSTORE hCertStore = NULL;
PCCERT_CONTEXT pCertContext = NULL;
BYTE* pbCertificateThumbprint = NULL;
DWORD cbCertificateThumbprint = 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<BYTE*>(wzFriendlyName), &cbFriendlyName))
{
LPCWSTR wzFound = wcsistr(wzFriendlyName, wzName);
if (wzFound && wzFound == wzFriendlyName)
{
// If the certificate with matching friendly name is valid, let's use that.
long lVerify = ::CertVerifyTimeValidity(NULL, pCertContext->pCertInfo);
if (0 == lVerify)
{
byte thumb[64] = { 0 };
cbCertificateThumbprint = sizeof(thumb);
if (!CertGetCertificateContextProperty(pCertContext, CERT_HASH_PROP_ID, thumb, &cbCertificateThumbprint))
{
ExitFunctionWithLastError(hr);
}

pbCertificateThumbprint = static_cast<BYTE*>(MemAlloc(cbCertificateThumbprint, FALSE));
ExitOnNull(pbCertificateThumbprint, hr, E_OUTOFMEMORY, "Failed to allocate memory to copy out exist certificate thumbprint.");

CopyMemory(pbCertificateThumbprint, thumb, cbCertificateThumbprint);
hr = S_OK;
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;
}

*ppbCertificateThumbprint = pbCertificateThumbprint;
*pcbCertificateThumbprint = cbCertificateThumbprint;
pbCertificateThumbprint = NULL;

LExit:
ReleaseMem(pbCertificateThumbprint);

if (pCertContext)
{
::CertFreeCertificateContext(pCertContext);
}

if (hCertStore)
{
::CertCloseStore(hCertStore, 0);
}

return hr;
}
15 changes: 8 additions & 7 deletions src/ext/Http/test/WixToolsetTest.Http/HttpExtensionFixture.cs
Expand Up @@ -37,13 +37,14 @@ public void CanBuildUsingSsl()
var results = build.BuildAndQuery(Build, "CustomAction", "Wix4HttpSslCert");
WixAssert.CompareLineByLine(new[]
{
"CustomAction:Wix4ExecHttpSslCertsInstall_X86\t3073\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4ExecHttpSslCertsUninstall_X86\t3073\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4RollbackHttpSslCertsInstall_X86\t3329\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4RollbackHttpSslCertsUninstall_X86\t3329\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4SchedHttpSslCertsInstall_X86\t1\tWix4HttpCA_X86\tSchedHttpSslCertsInstall\t",
"CustomAction:Wix4SchedHttpSslCertsUninstall_X86\t1\tWix4HttpCA_X86\tSchedHttpSslCertsUninstall\t",
"Wix4HttpSslCert:ssle0Wgg93FXwXdLjwZWqv0HKBhhKE\t0.0.0.0\t8080\t[SOME_THUMBPRINT]\t\t\t2\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo",
"CustomAction:Wix4ExecHttpSslCertsInstall_X86\t11265\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4ExecHttpSslCertsUninstall_X86\t11265\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4RollbackHttpSslCertsInstall_X86\t11521\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4RollbackHttpSslCertsUninstall_X86\t11521\tWix4HttpCA_X86\tExecHttpSslCerts\t",
"CustomAction:Wix4SchedHttpSslCertsInstall_X86\t8193\tWix4HttpCA_X86\tSchedHttpSslCertsInstall\t",
"CustomAction:Wix4SchedHttpSslCertsUninstall_X86\t8193\tWix4HttpCA_X86\tSchedHttpSslCertsUninstall\t",
"Wix4HttpSslCert:ssle0Wgg93FXwXdLjwZWqv0HKBhhKE\t0.0.0.0\t8080\t[SOME_THUMBPRINT]\t\t\t\t2\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo",
"Wix4HttpSslCert:ssltjEpdUFkxO7rNF2TrXuGLJg5NwE\t0.0.0.0\t8081\t\t1234\t\t\t2\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo",
}, results);
}

Expand Down
Expand Up @@ -6,6 +6,7 @@
<Component>
<File Source="example.txt" />
<http:SslCertificate Host="0.0.0.0" Port="8080" Thumbprint="[SOME_THUMBPRINT]" HandleExisting="fail" />
<http:SslCertificate Host="0.0.0.0" Port="8081" CertificateRef="1234" HandleExisting="fail" />
</Component>
</ComponentGroup>
</Fragment>
Expand Down
16 changes: 14 additions & 2 deletions src/ext/Http/wixext/HttpCompiler.cs
Expand Up @@ -196,6 +196,7 @@ private void ParseSslCertificateElement(Intermediate intermediate, IntermediateS
string appId = null;
string store = null;
string thumbprint = null;
string certificateRef = null;
var handleExisting = HandleExisting.Replace;

foreach (var attrib in node.Attributes())
Expand Down Expand Up @@ -240,6 +241,9 @@ private void ParseSslCertificateElement(Intermediate intermediate, IntermediateS
case "Thumbprint":
thumbprint = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "CertificateRef":
certificateRef = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
break;
default:
this.ParseHelper.UnexpectedAttribute(node, attrib);
break;
Expand Down Expand Up @@ -268,9 +272,16 @@ private void ParseSslCertificateElement(Intermediate intermediate, IntermediateS
this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Port"));
}

if (null == thumbprint)
if (null != certificateRef && null != thumbprint)
{
this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint"));
//this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint"));
this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint", "CertificateRef"));
}

if (null == thumbprint && null == certificateRef)
{
//this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "CertificateRef"));
this.Messaging.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint", "CertificateRef"));
}

// Parse unknown children.
Expand All @@ -283,6 +294,7 @@ private void ParseSslCertificateElement(Intermediate intermediate, IntermediateS
Host = host,
Port = port,
Thumbprint = thumbprint,
CertificateRef = certificateRef,
AppId = appId,
Store = store,
HandleExisting = handleExisting,
Expand Down
3 changes: 2 additions & 1 deletion src/ext/Http/wixext/HttpTableDefinitions.cs
Expand Up @@ -31,7 +31,8 @@ public static class HttpTableDefinitions
new ColumnDefinition("WixHttpSslCert", 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: false, ColumnCategory.Formatted, description: "Thumbprint of the SSL certificate to find.", 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("Certificate_", ColumnType.String, 72, primaryKey: false, nullable: true, ColumnCategory.Unknown, description: "The index into the Certificate table."),
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."),
Expand Down

0 comments on commit dad178d

Please sign in to comment.