Add support for signature checking updates

Both the manifest and updater.exe must now be signed before they will be used. Uses a hard coded RSA public key.
master
Richard Stanway 2015-08-20 16:43:53 +02:00
parent e0d843ae26
commit 2e43118248
4 changed files with 277 additions and 7 deletions

View File

@ -120,7 +120,7 @@
</ClCompile>
<Link>
<AdditionalOptions>/ignore:4049 /ignore:4217 /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
<AdditionalDependencies>Avrt.lib;dwmapi.lib;comctl32.lib;dxgi.lib;dxguid.lib;d3d10_1.lib;d3dx10.lib;ws2_32.lib;Iphlpapi.lib;Winmm.lib;librtmp.lib;libmp3lame-static.lib;libfaac.lib;dsound.lib;obsapi.lib;shell32.lib;gdiplus.lib;mfplat.lib;Mfuuid.lib;Winhttp.lib;libx264.lib;UxTheme.lib;Xinput9_1_0.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>crypt32.lib;Avrt.lib;dwmapi.lib;comctl32.lib;dxgi.lib;dxguid.lib;d3d10_1.lib;d3dx10.lib;ws2_32.lib;Iphlpapi.lib;Winmm.lib;librtmp.lib;libmp3lame-static.lib;libfaac.lib;dsound.lib;obsapi.lib;shell32.lib;gdiplus.lib;mfplat.lib;Mfuuid.lib;Winhttp.lib;libx264.lib;UxTheme.lib;Xinput9_1_0.lib;%(AdditionalDependencies)</AdditionalDependencies>
<Version>
</Version>
<AdditionalLibraryDirectories>OBSApi/Debug;x264/libs/32bit;librtmp/debug;lame/output/32bit;libfaac/debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
@ -197,7 +197,7 @@
</ClCompile>
<Link>
<AdditionalOptions>/ignore:4049 /ignore:4217 %(AdditionalOptions)</AdditionalOptions>
<AdditionalDependencies>Avrt.lib;dwmapi.lib;comctl32.lib;dxgi.lib;dxguid.lib;d3d10_1.lib;d3dx10.lib;ws2_32.lib;Iphlpapi.lib;Winmm.lib;librtmp.lib;libmp3lame-static.lib;libfaac.lib;dsound.lib;obsapi.lib;shell32.lib;gdiplus.lib;mfplat.lib;Mfuuid.lib;Winhttp.lib;libx264.lib;UxTheme.lib;Xinput9_1_0.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>crypt32.lib;Avrt.lib;dwmapi.lib;comctl32.lib;dxgi.lib;dxguid.lib;d3d10_1.lib;d3dx10.lib;ws2_32.lib;Iphlpapi.lib;Winmm.lib;librtmp.lib;libmp3lame-static.lib;libfaac.lib;dsound.lib;obsapi.lib;shell32.lib;gdiplus.lib;mfplat.lib;Mfuuid.lib;Winhttp.lib;libx264.lib;UxTheme.lib;Xinput9_1_0.lib;%(AdditionalDependencies)</AdditionalDependencies>
<Version>
</Version>
<AdditionalLibraryDirectories>OBSApi/Release;x264/libs/32bit;librtmp/release;lame/output/32bit;libfaac/release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

View File

@ -20,7 +20,7 @@
#include "Main.h"
#include <winhttp.h>
BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode)
BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode, TCHAR *sigOut, DWORD *sigOutLen)
{
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
@ -83,6 +83,24 @@ BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *response
*responseCode = wcstoul(statusCode, NULL, 10);
if (sigOut)
{
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CUSTOM, TEXT("X-Signature"), sigOut, sigOutLen, WINHTTP_NO_HEADER_INDEX))
{
if (GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND)
*sigOutLen = 0;
else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
*sigOutLen = 0;
else
goto failure;
}
else
{
// We need character count, not byte count.
*sigOutLen /= 2;
}
}
if (bResults && *responseCode == 200)
{
BYTE buffer[16384];
@ -127,6 +145,9 @@ failure:
if (hRequest)
WinHttpCloseHandle(hRequest);
if (sigOutLen && !ret)
*sigOutLen = 0;
return ret;
}

View File

@ -19,7 +19,7 @@
#pragma once
BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode);
BOOL HTTPGetFile(CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode, TCHAR *sigOut, DWORD *sigOutLen);
String HTTPGetString (CTSTR url, CTSTR extraHeaders, int *responseCode);

View File

@ -25,6 +25,87 @@
HCRYPTPROV hProvider;
#pragma pack(push, r1, 1)
typedef struct {
BLOBHEADER blobheader;
RSAPUBKEY rsapubkey;
} PUBLICKEYHEADER;
#pragma pack(pop, r1)
// Hard coded 4096 bit RSA public key for obsproject.com in PEM format
const unsigned char obs_pub[] = {
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x49, 0x6a, 0x41, 0x4e, 0x42,
0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41,
0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x38, 0x41, 0x4d,
0x49, 0x49, 0x43, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x67, 0x45, 0x41, 0x70,
0x63, 0x51, 0x4a, 0x33, 0x57, 0x44, 0x62, 0x43, 0x62, 0x66, 0x6e, 0x4d,
0x75, 0x48, 0x4b, 0x50, 0x77, 0x53, 0x5a, 0x0a, 0x56, 0x36, 0x77, 0x49,
0x2f, 0x67, 0x36, 0x58, 0x50, 0x65, 0x46, 0x36, 0x62, 0x4c, 0x62, 0x72,
0x53, 0x66, 0x71, 0x39, 0x66, 0x53, 0x4e, 0x68, 0x61, 0x47, 0x50, 0x37,
0x55, 0x6e, 0x67, 0x4e, 0x2b, 0x44, 0x46, 0x66, 0x53, 0x36, 0x30, 0x62,
0x35, 0x31, 0x42, 0x2f, 0x58, 0x6e, 0x6a, 0x67, 0x37, 0x51, 0x38, 0x70,
0x35, 0x77, 0x4f, 0x57, 0x34, 0x39, 0x44, 0x46, 0x65, 0x62, 0x33, 0x73,
0x0a, 0x75, 0x65, 0x41, 0x2b, 0x49, 0x42, 0x50, 0x77, 0x6d, 0x71, 0x62,
0x32, 0x46, 0x39, 0x4a, 0x7a, 0x63, 0x71, 0x67, 0x73, 0x6a, 0x74, 0x46,
0x76, 0x63, 0x37, 0x74, 0x4b, 0x4a, 0x6c, 0x70, 0x66, 0x59, 0x6b, 0x42,
0x39, 0x71, 0x67, 0x47, 0x2b, 0x48, 0x4a, 0x54, 0x30, 0x53, 0x50, 0x69,
0x64, 0x66, 0x53, 0x69, 0x63, 0x51, 0x32, 0x6e, 0x79, 0x35, 0x67, 0x6d,
0x37, 0x69, 0x4d, 0x6f, 0x71, 0x0a, 0x35, 0x34, 0x36, 0x6a, 0x69, 0x53,
0x75, 0x41, 0x75, 0x41, 0x63, 0x49, 0x71, 0x74, 0x33, 0x39, 0x37, 0x67,
0x41, 0x47, 0x56, 0x6f, 0x45, 0x46, 0x58, 0x30, 0x58, 0x47, 0x46, 0x41,
0x4c, 0x64, 0x63, 0x38, 0x35, 0x35, 0x2f, 0x34, 0x46, 0x74, 0x61, 0x63,
0x68, 0x79, 0x39, 0x70, 0x59, 0x34, 0x77, 0x58, 0x39, 0x41, 0x59, 0x57,
0x69, 0x50, 0x42, 0x4e, 0x48, 0x52, 0x65, 0x78, 0x45, 0x51, 0x0a, 0x54,
0x46, 0x75, 0x55, 0x73, 0x69, 0x76, 0x44, 0x64, 0x51, 0x6c, 0x50, 0x64,
0x76, 0x6a, 0x79, 0x59, 0x6e, 0x72, 0x63, 0x30, 0x4c, 0x50, 0x2f, 0x4f,
0x71, 0x4a, 0x2f, 0x71, 0x2f, 0x42, 0x58, 0x7a, 0x4f, 0x4b, 0x66, 0x33,
0x69, 0x4e, 0x6b, 0x58, 0x70, 0x72, 0x63, 0x68, 0x50, 0x61, 0x67, 0x52,
0x72, 0x61, 0x43, 0x34, 0x64, 0x41, 0x74, 0x34, 0x62, 0x42, 0x4a, 0x54,
0x34, 0x43, 0x7a, 0x0a, 0x56, 0x6a, 0x66, 0x55, 0x33, 0x69, 0x53, 0x2f,
0x57, 0x74, 0x68, 0x7a, 0x54, 0x50, 0x49, 0x51, 0x34, 0x4b, 0x34, 0x4c,
0x47, 0x57, 0x35, 0x4f, 0x70, 0x4e, 0x2b, 0x46, 0x35, 0x70, 0x67, 0x6d,
0x6f, 0x69, 0x44, 0x4e, 0x30, 0x64, 0x36, 0x4a, 0x35, 0x43, 0x49, 0x47,
0x6d, 0x37, 0x62, 0x5a, 0x78, 0x50, 0x58, 0x61, 0x32, 0x6a, 0x73, 0x64,
0x52, 0x30, 0x6c, 0x31, 0x6a, 0x4d, 0x6a, 0x48, 0x0a, 0x6a, 0x53, 0x52,
0x74, 0x68, 0x73, 0x59, 0x33, 0x62, 0x33, 0x31, 0x75, 0x73, 0x5a, 0x34,
0x43, 0x64, 0x6b, 0x6a, 0x48, 0x4f, 0x68, 0x64, 0x76, 0x32, 0x67, 0x4a,
0x43, 0x4d, 0x52, 0x65, 0x71, 0x41, 0x67, 0x78, 0x4a, 0x77, 0x54, 0x39,
0x2f, 0x48, 0x6a, 0x69, 0x6c, 0x57, 0x67, 0x4b, 0x72, 0x51, 0x72, 0x42,
0x34, 0x31, 0x50, 0x46, 0x4d, 0x4e, 0x71, 0x47, 0x42, 0x66, 0x4c, 0x4b,
0x61, 0x0a, 0x32, 0x6b, 0x69, 0x50, 0x74, 0x58, 0x48, 0x30, 0x35, 0x46,
0x50, 0x52, 0x35, 0x71, 0x6f, 0x56, 0x79, 0x42, 0x6b, 0x65, 0x77, 0x63,
0x45, 0x65, 0x75, 0x41, 0x35, 0x73, 0x6b, 0x78, 0x5a, 0x31, 0x67, 0x33,
0x7a, 0x4a, 0x30, 0x67, 0x48, 0x35, 0x6f, 0x76, 0x51, 0x64, 0x42, 0x6a,
0x54, 0x34, 0x59, 0x68, 0x32, 0x4a, 0x37, 0x50, 0x39, 0x68, 0x7a, 0x76,
0x73, 0x62, 0x6a, 0x39, 0x76, 0x56, 0x0a, 0x4a, 0x75, 0x68, 0x44, 0x6e,
0x45, 0x34, 0x52, 0x38, 0x2f, 0x65, 0x38, 0x45, 0x74, 0x61, 0x66, 0x53,
0x41, 0x68, 0x44, 0x53, 0x32, 0x47, 0x74, 0x38, 0x6c, 0x77, 0x62, 0x4f,
0x52, 0x64, 0x43, 0x55, 0x48, 0x4d, 0x48, 0x59, 0x49, 0x57, 0x4e, 0x47,
0x59, 0x30, 0x46, 0x6e, 0x61, 0x4d, 0x79, 0x41, 0x74, 0x72, 0x37, 0x54,
0x75, 0x48, 0x2f, 0x53, 0x43, 0x44, 0x77, 0x4c, 0x6a, 0x6a, 0x52, 0x0a,
0x6e, 0x41, 0x54, 0x64, 0x30, 0x44, 0x43, 0x30, 0x48, 0x49, 0x74, 0x59,
0x31, 0x59, 0x32, 0x53, 0x49, 0x43, 0x7a, 0x42, 0x61, 0x41, 0x66, 0x4c,
0x41, 0x6a, 0x78, 0x79, 0x38, 0x67, 0x58, 0x61, 0x49, 0x53, 0x6c, 0x52,
0x69, 0x79, 0x6b, 0x37, 0x70, 0x4b, 0x5a, 0x62, 0x4b, 0x6e, 0x61, 0x67,
0x6b, 0x35, 0x4c, 0x49, 0x6f, 0x63, 0x52, 0x69, 0x73, 0x79, 0x36, 0x4e,
0x6e, 0x76, 0x41, 0x31, 0x0a, 0x59, 0x66, 0x56, 0x7a, 0x4f, 0x69, 0x6a,
0x46, 0x4b, 0x55, 0x72, 0x48, 0x6c, 0x76, 0x76, 0x38, 0x38, 0x59, 0x39,
0x41, 0x32, 0x43, 0x77, 0x78, 0x6d, 0x77, 0x4c, 0x78, 0x34, 0x7a, 0x78,
0x4a, 0x73, 0x4f, 0x77, 0x74, 0x68, 0x75, 0x42, 0x57, 0x79, 0x76, 0x31,
0x59, 0x64, 0x34, 0x30, 0x4d, 0x68, 0x67, 0x71, 0x54, 0x75, 0x72, 0x7a,
0x39, 0x51, 0x51, 0x6e, 0x39, 0x32, 0x6e, 0x6f, 0x72, 0x0a, 0x46, 0x46,
0x58, 0x2f, 0x58, 0x54, 0x30, 0x6e, 0x65, 0x67, 0x74, 0x4a, 0x4f, 0x56,
0x44, 0x67, 0x49, 0x41, 0x30, 0x76, 0x49, 0x61, 0x6b, 0x43, 0x41, 0x77,
0x45, 0x41, 0x41, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b,
0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
};
const unsigned int obs_pub_len = 800;
VOID HashToString (BYTE *in, TCHAR *out)
{
const char alphabet[] = "0123456789abcdef";
@ -38,6 +119,20 @@ VOID HashToString (BYTE *in, TCHAR *out)
out[40] = 0;
}
VOID HexToByteArray(TCHAR *hexStr, int hexLen, BYTE *out)
{
TCHAR ptr[3];
ptr[2] = 0;
for (int i = 0; i < hexLen; i += 2)
{
ptr[0] = hexStr[i];
ptr[1] = hexStr[i + 1];
out[i / 2] = (BYTE)wcstoul(ptr, NULL, 16);
}
}
VOID GenerateGUID(String &strGUID)
{
BYTE junk[20];
@ -93,6 +188,134 @@ BOOL CalculateFileHash (TCHAR *path, BYTE *hash)
return TRUE;
}
BOOL VerifyDigitalSignature(BYTE *buff, DWORD len, BYTE *signature, DWORD signatureLen)
{
// ASN of PEM public key
BYTE binaryKey[1024];
DWORD binaryKeyLen = sizeof(binaryKey);
// Windows X509 public key info from ASN
CERT_PUBLIC_KEY_INFO *pbPublicPBLOB = NULL;
DWORD iPBLOBSize;
// RSA BLOB info from X509 public key
PUBLICKEYHEADER *rsaPublicBLOB;
DWORD rsaPublicBLOBSize;
// Handle to public key
HCRYPTKEY keyOut = NULL;
// Handle to hash context
HCRYPTHASH hHash = NULL;
// Signature in little-endian format
BYTE *reversedSignature = NULL;
BOOL ret = FALSE;
if (!CryptStringToBinaryA((LPCSTR)obs_pub, obs_pub_len, CRYPT_STRING_BASE64HEADER, binaryKey, &binaryKeyLen, NULL, NULL))
goto cleanup;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, binaryKey, binaryKeyLen, CRYPT_ENCODE_ALLOC_FLAG, NULL, &pbPublicPBLOB, &iPBLOBSize))
goto cleanup;
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, pbPublicPBLOB->PublicKey.pbData, pbPublicPBLOB->PublicKey.cbData, CRYPT_ENCODE_ALLOC_FLAG, NULL, &rsaPublicBLOB, &rsaPublicBLOBSize))
goto cleanup;
if (!CryptImportKey(hProvider, (const BYTE *)rsaPublicBLOB, rsaPublicBLOBSize, NULL, 0, &keyOut))
goto cleanup;
if (!CryptCreateHash(hProvider, CALG_SHA_512, 0, 0, &hHash))
goto cleanup;
if (!CryptHashData(hHash, buff, len, 0))
goto cleanup;
// Windows requires signature in little-endian. Every other crypto provider is big-endian of course.
reversedSignature = (BYTE *)HeapAlloc(GetProcessHeap(), 0, signatureLen);
for (DWORD i = 0; i < signatureLen; i++)
reversedSignature[i] = signature[signatureLen - i - 1];
if (!CryptVerifySignature(hHash, reversedSignature, signatureLen, keyOut, NULL, 0))
{
DWORD i = GetLastError();
goto cleanup;
}
ret = TRUE;
cleanup:
if (keyOut != NULL)
CryptDestroyKey(keyOut);
if (hHash != NULL)
CryptDestroyHash(hHash);
if (pbPublicPBLOB)
LocalFree(pbPublicPBLOB);
if (rsaPublicBLOB)
LocalFree(rsaPublicBLOB);
if (reversedSignature)
HeapFree(GetProcessHeap(), 0, reversedSignature);
return ret;
}
BOOL CheckSignature(TCHAR *path, TCHAR *hexSignature, int signatureLength)
{
BYTE *fileContents = NULL;
BYTE *signature = NULL;
BOOL ret = FALSE;
XFile updateFile;
if (signatureLength == 0 || signatureLength > 0xFFFF || (signatureLength & 1) != 0)
{
Log(TEXT("WARNING: Missing or invalid signature for %s."), path);
goto cleanup;
}
signature = (BYTE *)Allocate(signatureLength);
// Convert TCHAR signature to byte array
HexToByteArray(hexSignature, signatureLength, signature);
signatureLength /= 2;
if (updateFile.Open(path, XFILE_READ, XFILE_OPENEXISTING))
{
DWORD fileLength = (DWORD)updateFile.GetFileSize();
fileContents = (BYTE *)Allocate(fileLength);
if (updateFile.Read(fileContents, fileLength) != fileLength)
goto cleanup;
if (!VerifyDigitalSignature(fileContents, fileLength, signature, signatureLength))
{
Log(TEXT("WARNING: Signature check failed for %s."), path);
goto cleanup;
}
updateFile.Close();
}
else
{
goto cleanup;
}
ret = TRUE;
cleanup:
if (fileContents)
Free(fileContents);
if (signature)
Free(signature);
return ret;
}
/* required defines for archive based updating:
#define MANIFEST_WITH_ARCHIVES 1
#define MANIFEST_PATH "/updates/org.catchexception.builds.xconfig"
@ -140,11 +363,24 @@ BOOL FetchUpdaterModule(String const &url, String const &hash=String())
else
extraHeaders[0] = 0;
if (HTTPGetFile(url, updateFilePath, extraHeaders, &responseCode))
TCHAR hexSignature[4096];
DWORD signatureLength = sizeof(hexSignature);
if (HTTPGetFile(url, updateFilePath, extraHeaders, &responseCode, hexSignature, &signatureLength))
{
if (responseCode != 200 && responseCode != 304)
return FALSE;
// A new file must be digitally signed.
if (responseCode == 200)
{
if (!CheckSignature(updateFilePath, hexSignature, signatureLength))
{
DeleteFile(updateFilePath);
return FALSE;
}
}
#if MANIFEST_WITH_ARCHIVES
if (!CalculateFileHash(updateFilePath, updateFileHash))
return false;
@ -384,7 +620,7 @@ DWORD WINAPI CheckUpdateThread (VOID *arg)
tsprintf_s (manifestPath, _countof(manifestPath)-1, TEXT("%s") TEXT(MANIFEST_PATH), lpAppDataPath);
if (!CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
if (!CryptAcquireContext(&hProvider, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
Log (TEXT("Updater: CryptAcquireContext failed: %08x"), GetLastError());
return 1;
@ -421,12 +657,25 @@ DWORD WINAPI CheckUpdateThread (VOID *arg)
scat(extraHeaders, strGUID);
}
if (HTTPGetFile(TEXT(MANIFEST_URL), manifestPath, extraHeaders, &responseCode))
TCHAR hexSignature[4096];
DWORD signatureLength = sizeof(hexSignature);
if (HTTPGetFile(TEXT(MANIFEST_URL), manifestPath, extraHeaders, &responseCode, hexSignature, &signatureLength))
{
if (responseCode == 200 || responseCode == 304)
{
String updateInfo;
// A new file must be digitally signed.
if (responseCode == 200)
{
if (!CheckSignature(manifestPath, hexSignature, signatureLength))
{
DeleteFile(manifestPath);
return FALSE;
}
}
updateInfo = Str("Updater.NewUpdates");
if (ParseUpdateManifest(manifestPath, &updatesAvailable, updateInfo))