From fdfc9ff82d162d8c96b886af070c2705a01c9d31 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 29 Jun 2018 18:19:05 +0100 Subject: [PATCH] [pki] add RSA-2048 signature validation for all server downloads * Closes #1172 * Also fix a MinGW warning in badblocks.c --- src/badblocks.c | 2 +- src/msapi_utf8.h | 16 ++++ src/net.c | 212 ++++++++++++++++++++++++++++++++--------------- src/pki.c | 146 ++++++++++++++++++++++++++++++++ src/rufus.c | 16 ++-- src/rufus.h | 6 +- src/rufus.rc | 10 +-- src/stdlg.c | 8 +- 8 files changed, 331 insertions(+), 85 deletions(-) diff --git a/src/badblocks.c b/src/badblocks.c index 57a4fe43..268cdabd 100644 --- a/src/badblocks.c +++ b/src/badblocks.c @@ -430,7 +430,7 @@ static unsigned int test_rw(HANDLE hDrive, blk_t last_block, size_t block_size, int i, pat_idx; unsigned int bb_count = 0; blk_t got, tryout, recover_block = ~0, *blk_id; - size_t id_offset; + size_t id_offset = 0; if ((pattern_type < 0) || (pattern_type >= BADLOCKS_PATTERN_TYPES)) { uprintf("%sInvalid pattern type\n", bb_prefix); diff --git a/src/msapi_utf8.h b/src/msapi_utf8.h index ecc86040..388fdfd2 100644 --- a/src/msapi_utf8.h +++ b/src/msapi_utf8.h @@ -512,6 +512,22 @@ static __inline int PathGetDriveNumberU(char* lpPath) return ret; } +// This one is tricky since we can't blindly convert a +// UTF-16 position to a UTF-8 one. So we do it manually. +static __inline const char* PathFindFileNameU(const char* szPath) +{ + size_t i; + if (szPath == NULL) + return NULL; + for (i = strlen(szPath); i != 0; i--) { + if ((szPath[i] == '/') || (szPath[i] == '\\')) { + i++; + break; + } + } + return &szPath[i]; +} + // This function differs from regular GetTextExtentPoint in that it uses a zero terminated string static __inline BOOL GetTextExtentPointU(HDC hdc, const char* lpString, LPSIZE lpSize) { diff --git a/src/net.c b/src/net.c index 3e867cf7..d28fbf17 100644 --- a/src/net.c +++ b/src/net.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "rufus.h" #include "missing.h" @@ -216,13 +217,14 @@ const char* WinInetErrorString(void) } /* - * Download a file from an URL + * Download a file or fill a buffer from an URL * Mostly taken from http://support.microsoft.com/kb/234913 + * If file is NULL, a buffer is allocated for the download (that needs to be freed by the caller) * If hProgressDialog is not NULL, this function will send INIT and EXIT messages * to the dialog in question, with WPARAM being set to nonzero for EXIT on success * and also attempt to indicate progress using an IDC_PROGRESS control */ -DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) +static DWORD DownloadToFileOrBuffer(const char* url, const char* file, BYTE** buffer, HWND hProgressDialog) { HWND hProgressBar = NULL; BOOL r = FALSE; @@ -234,8 +236,8 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) HINTERNET hSession = NULL, hConnection = NULL, hRequest = NULL; URL_COMPONENTSA UrlParts = {sizeof(URL_COMPONENTSA), NULL, 1, (INTERNET_SCHEME)0, hostname, sizeof(hostname), 0, NULL, 1, urlpath, sizeof(urlpath), NULL, 1}; - size_t last_slash; - int i; + const char* short_name; + size_t i; // Can't link with wininet.lib because of sideloading issues PF_TYPE_DECL(WINAPI, BOOL, InternetCrackUrlA, (LPCSTR, DWORD, DWORD, LPURL_COMPONENTSA)); @@ -257,7 +259,7 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) PF_INIT_OR_OUT(HttpSendRequestA, WinInet); PF_INIT_OR_OUT(HttpQueryInfoA, WinInet); - DownloadStatus = 0; + DownloadStatus = 404; if (hProgressDialog != NULL) { // Use the progress control provided, if any hProgressBar = GetDlgItem(hProgressDialog, IDC_PROGRESS); @@ -268,22 +270,18 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) SendMessage(hProgressDialog, UM_PROGRESS_INIT, 0, 0); } - if (file == NULL) + if (url == NULL) goto out; - for (last_slash = safe_strlen(file); last_slash != 0; last_slash--) { - if ((file[last_slash] == '/') || (file[last_slash] == '\\')) { - last_slash++; - break; - } - } + short_name = (file != NULL) ? PathFindFileNameU(file) : PathFindFileNameU(url); - PrintInfo(0, MSG_085, &file[last_slash]); - uprintf("Downloading '%s' from %s\n", &file[last_slash], url); + if (hProgressDialog != NULL) + PrintInfo(0, MSG_085, short_name); + uprintf("Downloading %s", url); if ( (!pfInternetCrackUrlA(url, (DWORD)safe_strlen(url), 0, &UrlParts)) || (UrlParts.lpszHostName == NULL) || (UrlParts.lpszUrlPath == NULL)) { - uprintf("Unable to decode URL: %s\n", WinInetErrorString()); + uprintf("Unable to decode URL: %s", WinInetErrorString()); goto out; } hostname[sizeof(hostname)-1] = 0; @@ -295,7 +293,7 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) if (i <= 0) { // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384702.aspx is wrong... SetLastError(ERROR_INTERNET_NOT_INITIALIZED); - uprintf("Network is unavailable: %s\n", WinInetErrorString()); + uprintf("Network is unavailable: %s", WinInetErrorString()); goto out; } static_sprintf(agent, APPLICATION_NAME "/%d.%d.%d (Windows NT %d.%d%s)", @@ -303,13 +301,13 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) nWindowsVersion>>4, nWindowsVersion&0x0F, is_x64()?"; WOW64":""); hSession = pfInternetOpenA(agent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (hSession == NULL) { - uprintf("Could not open Internet session: %s\n", WinInetErrorString()); + uprintf("Could not open Internet session: %s", WinInetErrorString()); goto out; } hConnection = pfInternetConnectA(hSession, UrlParts.lpszHostName, UrlParts.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, (DWORD_PTR)NULL); if (hConnection == NULL) { - uprintf("Could not connect to server %s:%d: %s\n", UrlParts.lpszHostName, UrlParts.nPort, WinInetErrorString()); + uprintf("Could not connect to server %s:%d: %s", UrlParts.lpszHostName, UrlParts.nPort, WinInetErrorString()); goto out; } @@ -318,35 +316,46 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI|INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_HYPERLINK| ((UrlParts.nScheme==INTERNET_SCHEME_HTTPS)?INTERNET_FLAG_SECURE:0), (DWORD_PTR)NULL); if (hRequest == NULL) { - uprintf("Could not open URL %s: %s\n", url, WinInetErrorString()); + uprintf("Could not open URL %s: %s", url, WinInetErrorString()); goto out; } if (!pfHttpSendRequestA(hRequest, NULL, 0, NULL, 0)) { - uprintf("Unable to send request: %s\n", WinInetErrorString()); + uprintf("Unable to send request: %s", WinInetErrorString()); goto out; } // Get the file size dwSize = sizeof(DownloadStatus); - DownloadStatus = 404; pfHttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_CODE|HTTP_QUERY_FLAG_NUMBER, (LPVOID)&DownloadStatus, &dwSize, NULL); if (DownloadStatus != 200) { error_code = ERROR_INTERNET_ITEM_NOT_FOUND; - uprintf("Unable to access file: %d\n", DownloadStatus); + uprintf("Unable to access file: %d", DownloadStatus); goto out; } dwSize = sizeof(dwTotalSize); if (!pfHttpQueryInfoA(hRequest, HTTP_QUERY_CONTENT_LENGTH|HTTP_QUERY_FLAG_NUMBER, (LPVOID)&dwTotalSize, &dwSize, NULL)) { - uprintf("Unable to retrieve file length: %s\n", WinInetErrorString()); + uprintf("Unable to retrieve file length: %s", WinInetErrorString()); goto out; } - uprintf("File length: %d bytes\n", dwTotalSize); + uprintf("File length: %d bytes", dwTotalSize); - hFile = CreateFileU(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { - uprintf("Unable to create file '%s': %s\n", &file[last_slash], WinInetErrorString()); - goto out; + if (file != NULL) { + hFile = CreateFileU(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + uprintf("Unable to create file '%s': %s", short_name, WinInetErrorString()); + goto out; + } + } else { + if (buffer == NULL) { + uprintf("No buffer pointer provided for download"); + goto out; + } + *buffer = malloc(dwTotalSize); + if (*buffer == NULL) { + uprintf("Could not allocate buffer for download"); + goto out; + } } // Keep checking for data until there is nothing left. @@ -354,28 +363,37 @@ DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog) while (1) { if (IS_ERROR(FormatStatus)) goto out; - if (!pfInternetReadFile(hRequest, buf, sizeof(buf), &dwDownloaded) || (dwDownloaded == 0)) break; - dwSize += dwDownloaded; - SendMessage(hProgressBar, PBM_SETPOS, (WPARAM)(MAX_PROGRESS*((1.0f*dwSize)/(1.0f*dwTotalSize))), 0); - PrintInfo(0, MSG_241, (100.0f*dwSize)/(1.0f*dwTotalSize)); - if (!WriteFile(hFile, buf, dwDownloaded, &dwWritten, NULL)) { - uprintf("Error writing file '%s': %s\n", &file[last_slash], WinInetErrorString()); - goto out; - } else if (dwDownloaded != dwWritten) { - uprintf("Error writing file '%s': Only %d/%d bytes written\n", dwWritten, dwDownloaded); - goto out; + if (hProgressDialog != NULL) { + SendMessage(hProgressBar, PBM_SETPOS, (WPARAM)(MAX_PROGRESS*((1.0f*dwSize) / (1.0f*dwTotalSize))), 0); + PrintInfo(0, MSG_241, (100.0f*dwSize) / (1.0f*dwTotalSize)); } + if (file != NULL) { + if (!WriteFile(hFile, buf, dwDownloaded, &dwWritten, NULL)) { + uprintf("Error writing file '%s': %s", short_name, WinInetErrorString()); + goto out; + } else if (dwDownloaded != dwWritten) { + uprintf("Error writing file '%s': Only %d/%d bytes written", short_name, dwWritten, dwDownloaded); + goto out; + } + } else { + memcpy(&(*buffer)[dwSize], buf, dwDownloaded); + } + dwSize += dwDownloaded; } if (dwSize != dwTotalSize) { - uprintf("Could not download complete file - read: %d bytes, expected: %d bytes\n", dwSize, dwTotalSize); + uprintf("Could not download complete file - read: %d bytes, expected: %d bytes", dwSize, dwTotalSize); FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_WRITE_FAULT; goto out; } else { r = TRUE; - uprintf("Successfully downloaded '%s'\n", &file[last_slash]); + uprintf("Successfully downloaded '%s'", short_name); + if (hProgressDialog != NULL) { + SendMessage(hProgressBar, PBM_SETPOS, (WPARAM)MAX_PROGRESS, 0); + PrintInfo(0, MSG_241, 100.0f); + } } out: @@ -406,20 +424,76 @@ out: return r?dwSize:0; } +// Download and validate a signed file. The file must have a corresponding '.sig' on the server. +DWORD DownloadSignedFile(const char* url, const char* file, HWND hProgressDialog) +{ + char* url_sig = NULL; + BYTE *buf = NULL, *sig = NULL; + DWORD buf_len = 0, sig_len = 0; + DWORD ret = 0; + HANDLE hFile = INVALID_HANDLE_VALUE; + + if (url == NULL) + goto out; + + url_sig = malloc(strlen(url) + 5); + if (url_sig == NULL) { + uprintf("Could not allocate signature URL"); + goto out; + } + strcpy(url_sig, url); + strcat(url_sig, ".sig"); + + buf_len = DownloadToFileOrBuffer(url, NULL, &buf, hProgressDialog); + if (buf_len == 0) + goto out; + sig_len = DownloadToFileOrBuffer(url_sig, NULL, &sig, NULL); + if ((sig_len != RSA_SIGNATURE_SIZE) || (!ValidateOpensslSignature(buf, buf_len, sig, sig_len))) { + uprintf("FATAL: Server signature is invalid!"); + DownloadStatus = 403; // Forbidden + goto out; + } + + uprintf("Download signature is valid"); + DownloadStatus = 206; // Partial content + hFile = CreateFileU(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + uprintf("Unable to create file '%s': %s", PathFindFileNameU(file), WinInetErrorString()); + goto out; + } + if (!WriteFile(hFile, buf, buf_len, &ret, NULL)) { + uprintf("Error writing file '%s': %s", PathFindFileNameU(file), WinInetErrorString()); + ret = 0; + goto out; + } else if (ret != buf_len) { + uprintf("Error writing file '%s': Only %d/%d bytes written", PathFindFileNameU(file), ret, buf_len); + ret = 0; + goto out; + } + DownloadStatus = 200; // Full content + +out: + safe_closehandle(hFile); + free(url_sig); + free(buf); + free(sig); + return ret; +} + /* Threaded download */ static const char *_url, *_file; static HWND _hProgressDialog; -static DWORD WINAPI _DownloadFileThread(LPVOID param) +static DWORD WINAPI _DownloadSignedFileThread(LPVOID param) { - ExitThread(DownloadFile(_url, _file, _hProgressDialog) != 0); + ExitThread(DownloadSignedFile(_url, _file, _hProgressDialog) != 0); } -HANDLE DownloadFileThreaded(const char* url, const char* file, HWND hProgressDialog) +HANDLE DownloadSignedFileThreaded(const char* url, const char* file, HWND hProgressDialog) { _url = url; _file = file; _hProgressDialog = hProgressDialog; - return CreateThread(NULL, 0, _DownloadFileThread, NULL, 0, NULL); + return CreateThread(NULL, 0, _DownloadSignedFileThread, NULL, 0, NULL); } static __inline uint64_t to_uint64_t(uint16_t x[4]) { @@ -443,8 +517,9 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) static const char* channel[] = {"release", "beta", "test"}; // release channel const char* accept_types[] = {"*/*\0", NULL}; DWORD dwFlags, dwSize, dwDownloaded, dwTotalSize, dwStatus; + BYTE *sig = NULL; char* buf = NULL; - char agent[64], hostname[64], urlpath[128], mime[32]; + char agent[64], hostname[64], urlpath[128], sigpath[256], mime[32]; OSVERSIONINFOA os_version = {sizeof(OSVERSIONINFOA), 0, 0, 0, 0, ""}; HINTERNET hSession = NULL, hConnection = NULL, hRequest = NULL; URL_COMPONENTSA UrlParts = {sizeof(URL_COMPONENTSA), NULL, 1, (INTERNET_SCHEME)0, @@ -487,7 +562,7 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) } while ((!force_update_check) && ((iso_op_in_progress || format_op_in_progress || (dialog_showing>0)))); if (!force_update_check) { if ((ReadSetting32(SETTING_UPDATE_INTERVAL) == -1)) { - vuprintf("Check for updates disabled, as per settings.\n"); + vuprintf("Check for updates disabled, as per settings."); goto out; } reg_time = ReadSetting64(SETTING_LAST_UPDATE); @@ -500,9 +575,9 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) if (!SystemTimeToFileTime(&LocalTime, &FileTime)) goto out; local_time = ((((int64_t)FileTime.dwHighDateTime)<<32) + FileTime.dwLowDateTime) / 10000000; - vvuprintf("Local time: %" PRId64 "\n", local_time); + vvuprintf("Local time: %" PRId64, local_time); if (local_time < reg_time + update_interval) { - vuprintf("Next update check in %" PRId64 " seconds.\n", reg_time + update_interval - local_time); + vuprintf("Next update check in %" PRId64 " seconds.", reg_time + update_interval - local_time); goto out; } } @@ -512,7 +587,7 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) status++; // 1 if (!GetVersionExA(&os_version)) { - uprintf("Could not read Windows version - Check for updates cancelled.\n"); + uprintf("Could not read Windows version - Check for updates cancelled."); goto out; } @@ -540,7 +615,7 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) max_channel = releases_only ? 1 : (int)ARRAYSIZE(channel) - 1; #endif for (k=0; (k___.ver" // and then remove each each of the components until we find our match. For instance, we may first @@ -548,21 +623,18 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) // This allows sunsetting OS versions (eg XP) or providing different downloads for different archs/groups. static_sprintf(urlpath, "%s%s%s_%s_%lu.%lu.ver", APPLICATION_NAME, (k==0)?"":"_", (k==0)?"":channel[k], archname[is_x64()?1:0], os_version.dwMajorVersion, os_version.dwMinorVersion); - vuprintf("Base update check: %s\n", urlpath); + vuprintf("Base update check: %s", urlpath); for (i=0, j=(int)safe_strlen(urlpath)-5; (j>0)&&(i to_uint64_t(rufus_version)) || (force_update)) && ( (os_version.dwMajorVersion > update.platform_min[0]) || ( (os_version.dwMajorVersion == update.platform_min[0]) && (os_version.dwMinorVersion >= update.platform_min[1])) ); - uprintf("N%sew %s version found%c\n", found_new_version?"":"o n", channel[k], found_new_version?'!':'.'); + uprintf("N%sew %s version found%c", found_new_version?"":"o n", channel[k], found_new_version?'!':'.'); } out: safe_free(buf); + safe_free(sig); if (hRequest) pfInternetCloseHandle(hRequest); if (hConnection) diff --git a/src/pki.c b/src/pki.c index 07cf065c..328adf50 100644 --- a/src/pki.c +++ b/src/pki.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "rufus.h" #include "resource.h" @@ -52,6 +53,63 @@ typedef struct { LPWSTR lpszMoreInfoLink; } SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO; +// https://msdn.microsoft.com/en-us/library/ee442238.aspx +typedef struct { + BLOBHEADER BlobHeader; + RSAPUBKEY RsaHeader; + BYTE Modulus[256]; // 2048 bit modulus +} RSA_2048_PUBKEY; + +// The RSA public key modulus for the private key we use to sign the files on the server. +// NOTE 1: This openssl modulus must be *REVERSED* to be usable with Microsoft APIs +// NOTE 2: Also, this modulus is 2052 bits, and not 2048, because openssl adds an extra +// 0x00 at the beginning to force an integer sign. These extra 8 bits *MUST* be removed. +static uint8_t rsa_pubkey_modulus[] = { + /* + $ openssl genrsa -aes256 -out private.pem 2048 + $ openssl rsa -in private.pem -pubout -out public.pem + $ openssl rsa -pubin -inform PEM -text -noout < public.pem + Public-Key: (2048 bit) + Modulus: + 00:b6:40:7d:d1:98:7b:81:9e:be:23:0f:32:5d:55: + 60:c6:bf:b4:41:bb:43:1b:f1:e1:e6:f9:2b:d6:dd: + 11:50:e8:b9:3f:19:97:5e:a7:8b:4a:30:c6:76:58: + 72:1c:ac:ff:a1:f8:96:6c:51:5d:13:11:e3:5b:11: + 82:f5:9a:69:e4:28:97:0f:ca:1f:02:ea:1f:7d:dc: + f9:fc:79:2f:61:ff:8e:45:60:65:ba:37:9b:de:49: + 05:6a:a8:fd:70:d0:0c:79:b6:d7:81:aa:54:c3:c6: + 4a:87:a0:45:ee:ca:d5:d5:c5:c2:ac:86:42:b3:58: + 27:d2:43:b9:37:f2:e6:75:66:17:53:d0:38:d0:c6: + 57:c2:55:36:a2:43:87:ea:24:f0:96:ec:34:dd:79: + 4d:80:54:9d:84:81:a7:cf:0c:a5:7c:d6:63:fa:7a: + 66:30:a9:50:ee:f0:e5:f8:a2:2d:ac:fc:24:21:fe: + ef:e8:d3:6f:0e:27:b0:64:22:95:3e:6d:a6:66:97: + c6:98:c2:47:b3:98:69:4d:b1:b5:d3:6f:43:f5:d7: + a5:13:5e:8c:28:4f:62:4e:01:48:0a:63:89:e7:ca: + 34:aa:7d:2f:bb:70:e0:31:bb:39:49:a3:d2:c9:2e: + a6:30:54:9a:5c:4d:58:17:d9:fc:3a:43:e6:8e:2a: + 18:e9 + Exponent: 65537 (0x10001) + */ + 0x00, 0xb6, 0x40, 0x7d, 0xd1, 0x98, 0x7b, 0x81, 0x9e, 0xbe, 0x23, 0x0f, 0x32, 0x5d, 0x55, + 0x60, 0xc6, 0xbf, 0xb4, 0x41, 0xbb, 0x43, 0x1b, 0xf1, 0xe1, 0xe6, 0xf9, 0x2b, 0xd6, 0xdd, + 0x11, 0x50, 0xe8, 0xb9, 0x3f, 0x19, 0x97, 0x5e, 0xa7, 0x8b, 0x4a, 0x30, 0xc6, 0x76, 0x58, + 0x72, 0x1c, 0xac, 0xff, 0xa1, 0xf8, 0x96, 0x6c, 0x51, 0x5d, 0x13, 0x11, 0xe3, 0x5b, 0x11, + 0x82, 0xf5, 0x9a, 0x69, 0xe4, 0x28, 0x97, 0x0f, 0xca, 0x1f, 0x02, 0xea, 0x1f, 0x7d, 0xdc, + 0xf9, 0xfc, 0x79, 0x2f, 0x61, 0xff, 0x8e, 0x45, 0x60, 0x65, 0xba, 0x37, 0x9b, 0xde, 0x49, + 0x05, 0x6a, 0xa8, 0xfd, 0x70, 0xd0, 0x0c, 0x79, 0xb6, 0xd7, 0x81, 0xaa, 0x54, 0xc3, 0xc6, + 0x4a, 0x87, 0xa0, 0x45, 0xee, 0xca, 0xd5, 0xd5, 0xc5, 0xc2, 0xac, 0x86, 0x42, 0xb3, 0x58, + 0x27, 0xd2, 0x43, 0xb9, 0x37, 0xf2, 0xe6, 0x75, 0x66, 0x17, 0x53, 0xd0, 0x38, 0xd0, 0xc6, + 0x57, 0xc2, 0x55, 0x36, 0xa2, 0x43, 0x87, 0xea, 0x24, 0xf0, 0x96, 0xec, 0x34, 0xdd, 0x79, + 0x4d, 0x80, 0x54, 0x9d, 0x84, 0x81, 0xa7, 0xcf, 0x0c, 0xa5, 0x7c, 0xd6, 0x63, 0xfa, 0x7a, + 0x66, 0x30, 0xa9, 0x50, 0xee, 0xf0, 0xe5, 0xf8, 0xa2, 0x2d, 0xac, 0xfc, 0x24, 0x21, 0xfe, + 0xef, 0xe8, 0xd3, 0x6f, 0x0e, 0x27, 0xb0, 0x64, 0x22, 0x95, 0x3e, 0x6d, 0xa6, 0x66, 0x97, + 0xc6, 0x98, 0xc2, 0x47, 0xb3, 0x98, 0x69, 0x4d, 0xb1, 0xb5, 0xd3, 0x6f, 0x43, 0xf5, 0xd7, + 0xa5, 0x13, 0x5e, 0x8c, 0x28, 0x4f, 0x62, 0x4e, 0x01, 0x48, 0x0a, 0x63, 0x89, 0xe7, 0xca, + 0x34, 0xaa, 0x7d, 0x2f, 0xbb, 0x70, 0xe0, 0x31, 0xbb, 0x39, 0x49, 0xa3, 0xd2, 0xc9, 0x2e, + 0xa6, 0x30, 0x54, 0x9a, 0x5c, 0x4d, 0x58, 0x17, 0xd9, 0xfc, 0x3a, 0x43, 0xe6, 0x8e, 0x2a, + 0x18, 0xe9 +}; /* * FormatMessage does not handle PKI errors @@ -65,8 +123,19 @@ const char* WinPKIErrorString(void) return WindowsErrorString(); switch (error_code) { + // See also https://docs.microsoft.com/en-gb/windows/desktop/com/com-error-codes-4 case NTE_BAD_UID: return "Bad UID."; + case NTE_NO_KEY: + return "Key does not exist."; + case NTE_BAD_KEYSET: + return "Keyset does not exist."; + case NTE_BAD_ALGID: + return "Invalid algorithm specified."; + case NTE_BAD_VER: + return "Bad version of provider."; + case NTE_BAD_SIGNATURE: + return "Invalid Signature."; case CRYPT_E_MSG_ERROR: return "An error occurred while performing an operation on a cryptographic message."; case CRYPT_E_UNKNOWN_ALGO: @@ -581,3 +650,80 @@ LONG ValidateSignature(HWND hDlg, const char* path) return r; } + +// Why-oh-why am I the only one on github doing this openssl vs MS signature validation?!? +// For once, I'd like to find code samples from *OTHER PEOPLE* who went through this ordeal first... +BOOL ValidateOpensslSignature(BYTE* pbBuffer, DWORD dwBufferLen, BYTE* pbSignature, DWORD dwSigLen) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + HCRYPTKEY hPubKey; + // We could load and convert an openssl PEM, but since we know what we need... + RSA_2048_PUBKEY pbMyPubKey = { + { PUBLICKEYBLOB, CUR_BLOB_VERSION, 0, CALG_RSA_KEYX }, + // $ openssl genrsa -aes256 -out private.pem 2048 + // Generating RSA private key, 2048 bit long modulus + // e is 65537 (0x010001) + // => 0x010001 below. Also 0x31415352 = "RSA1" + { 0x31415352, sizeof(pbMyPubKey.Modulus) * 8, 0x010001 }, + { 0 } // Modulus is initialized below + }; + USHORT dwMyPubKeyLen = sizeof(pbMyPubKey); + BOOL r; + BYTE t; + int i, j; + + // Get a handle to the default PROV_RSA_AES provider (AES so we get SHA-256 support). + // 2 passes in case we need to create a new container. + r = CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT); + if (!r) { + uprintf("PKI: Could not create the default key container: %s", WinPKIErrorString()); + goto out; + } + + // Reverse the modulus bytes from openssl (and also remove the extra unwanted 0x00) + assert(sizeof(rsa_pubkey_modulus) >= sizeof(pbMyPubKey.Modulus)); + for (i = 0; i < sizeof(pbMyPubKey.Modulus); i++) + pbMyPubKey.Modulus[i] = rsa_pubkey_modulus[sizeof(rsa_pubkey_modulus) -1 - i]; + + // Import our RSA public key so that the MS API can use it + r = CryptImportKey(hProv, (BYTE*)&pbMyPubKey.BlobHeader, dwMyPubKeyLen, 0, 0, &hPubKey); + if (!r) { + uprintf("Could not import public key: %s", WinPKIErrorString()); + goto out; + } + + // Create the hash object. + r = CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash); + if (!r) { + uprintf("Could not create empty hash: %s", WinPKIErrorString()); + goto out; + } + + // Compute the cryptographic hash of the buffer. + r = CryptHashData(hHash, pbBuffer, dwBufferLen, 0); + if (!r) { + uprintf("Could not hash data: %s", WinPKIErrorString()); + goto out; + } + + // Reverse the signature bytes + for (i = 0, j = dwSigLen - 1; i < j; i++, j--) { + t = pbSignature[i]; + pbSignature[i] = pbSignature[j]; + pbSignature[j] = t; + } + + // Now that we have all of the public key, hash and signature data in a + // format that Microsoft can handle, we can call CryptVerifySignature(). + r = CryptVerifySignature(hHash, pbSignature, dwSigLen, hPubKey, NULL, 0); + if (!r) + uprintf("Signature validation failed: %s", WinPKIErrorString()); + +out: + if (hHash) + CryptDestroyHash(hHash); + if (hProv) + CryptReleaseContext(hProv, 0); + return r; +} diff --git a/src/rufus.c b/src/rufus.c index 026ee9ce..a0f62e22 100644 --- a/src/rufus.c +++ b/src/rufus.c @@ -1567,7 +1567,7 @@ static BOOL BootCheck(void) IGNORE_RETVAL(_chdir(tmp)); static_sprintf(tmp, "%s/%s-%s/%s", FILES_URL, grub, img_report.grub2_version, core_img); PromptOnError = FALSE; - grub2_len = (long)DownloadFile(tmp, core_img, hMainDialog); + grub2_len = (long)DownloadSignedFile(tmp, core_img, hMainDialog); PromptOnError = TRUE; if ((grub2_len == 0) && (DownloadStatus == 404)) { // Couldn't locate the file on the server => try to download without the version extra @@ -1578,7 +1578,7 @@ static BOOL BootCheck(void) tmp2[i] = 0; static_sprintf(tmp, "%s/%s-%s/%s", FILES_URL, grub, tmp2, core_img); PromptOnError = FALSE; - grub2_len = (long)DownloadFile(tmp, core_img, hMainDialog); + grub2_len = (long)DownloadSignedFile(tmp, core_img, hMainDialog); PromptOnError = TRUE; static_sprintf(tmp, "%s/%s-%s/%s", FILES_URL, grub, img_report.grub2_version, core_img); } @@ -1624,7 +1624,7 @@ static BOOL BootCheck(void) static_sprintf(tmp, "%s-%s", syslinux, embedded_sl_version_str[0]); IGNORE_RETVAL(_mkdir(tmp)); static_sprintf(tmp, "%s/%s-%s/%s", FILES_URL, syslinux, embedded_sl_version_str[0], old_c32_name[i]); - len = DownloadFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); + len = DownloadSignedFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); if (len == 0) { uprintf("Could not download file - cancelling"); return FALSE; @@ -1672,14 +1672,14 @@ static BOOL BootCheck(void) static_sprintf(tmp, "%s/%s-%s%s/%s.%s", FILES_URL, syslinux, img_report.sl_version_str, img_report.sl_version_ext, ldlinux, ldlinux_ext[i]); PromptOnError = (*img_report.sl_version_ext == 0); - syslinux_ldlinux_len[i] = DownloadFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); + syslinux_ldlinux_len[i] = DownloadSignedFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); PromptOnError = TRUE; if ((syslinux_ldlinux_len[i] == 0) && (DownloadStatus == 404) && (*img_report.sl_version_ext != 0)) { // Couldn't locate the file on the server => try to download without the version extra uprintf("Extended version was not found, trying main version..."); static_sprintf(tmp, "%s/%s-%s/%s.%s", FILES_URL, syslinux, img_report.sl_version_str, ldlinux, ldlinux_ext[i]); - syslinux_ldlinux_len[i] = DownloadFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); + syslinux_ldlinux_len[i] = DownloadSignedFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog); if (syslinux_ldlinux_len[i] != 0) { // Duplicate the file so that the user won't be prompted to download again static_sprintf(tmp, "%s-%s\\%s.%s", syslinux, img_report.sl_version_str, ldlinux, ldlinux_ext[i]); @@ -1722,7 +1722,7 @@ static BOOL BootCheck(void) static_sprintf(tmp, "%s-%s", syslinux, embedded_sl_version_str[1]); IGNORE_RETVAL(_mkdir(tmp)); static_sprintf(tmp, "%s/%s-%s/%s.%s", FILES_URL, syslinux, embedded_sl_version_str[1], ldlinux, ldlinux_ext[2]); - if (DownloadFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog) == 0) + if (DownloadSignedFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog) == 0) return FALSE; } } @@ -1752,7 +1752,7 @@ static BOOL BootCheck(void) static_sprintf(tmp, "grub4dos-%s", GRUB4DOS_VERSION); IGNORE_RETVAL(_mkdir(tmp)); static_sprintf(tmp, "%s/grub4dos-%s/grldr", FILES_URL, GRUB4DOS_VERSION); - if (DownloadFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog) == 0) + if (DownloadSignedFile(tmp, &tmp[sizeof(FILES_URL)], hMainDialog) == 0) return FALSE; } } @@ -2978,7 +2978,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA case WM_COMMAND: #ifdef RUFUS_TEST if (LOWORD(wParam) == IDC_TEST) { - Notification(MSG_ERROR, NULL, lmprintf(MSG_042), lmprintf(MSG_043, lmprintf(MSG_055))); + DownloadSignedFile(FILES_URL "/gendb.sh", "C:\\Downloads\\gendb.sh", hProgress); break; } #endif diff --git a/src/rufus.h b/src/rufus.h index 05a538e6..3a2bb7c0 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -80,6 +80,7 @@ #define FAT32_CLUSTER_THRESHOLD 1.011f // For FAT32, cluster size changes don't occur at power of 2 boundaries but sligthly above #define DD_BUFFER_SIZE 65536 // Minimum size of the buffer we use for DD operations #define UBUFFER_SIZE 2048 +#define RSA_SIGNATURE_SIZE 256 #define CBN_SELCHANGE_INTERNAL (CBN_SELCHANGE + 256) #define RUFUS_URL "https://rufus.ie" #define DOWNLOAD_URL RUFUS_URL "/downloads" @@ -475,8 +476,8 @@ extern BOOL ResetDevice(int index); extern BOOL GetOpticalMedia(IMG_SAVE* img_save); extern BOOL SetLGP(BOOL bRestore, BOOL* bExistingKey, const char* szPath, const char* szPolicy, DWORD dwValue); extern LONG GetEntryWidth(HWND hDropDown, const char* entry); -extern DWORD DownloadFile(const char* url, const char* file, HWND hProgressDialog); -extern HANDLE DownloadFileThreaded(const char* url, const char* file, HWND hProgressDialog); +extern DWORD DownloadSignedFile(const char* url, const char* file, HWND hProgressDialog); +extern HANDLE DownloadSignedFileThreaded(const char* url, const char* file, HWND hProgressDialog); extern INT_PTR CALLBACK UpdateCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); extern BOOL SetUpdateCheck(void); extern BOOL CheckForUpdates(BOOL force); @@ -503,6 +504,7 @@ extern int IsHDD(DWORD DriveIndex, uint16_t vid, uint16_t pid, const char* strid extern char* GetSignatureName(const char* path, const char* country_code); extern uint64_t GetSignatureTimeStamp(const char* path); extern LONG ValidateSignature(HWND hDlg, const char* path); +extern BOOL ValidateOpensslSignature(BYTE* pbBuffer, DWORD dwBufferLen, BYTE* pbSignature, DWORD dwSigLen); extern BOOL IsFontAvailable(const char* font_name); extern BOOL WriteFileWithRetry(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, DWORD nNumRetries); diff --git a/src/rufus.rc b/src/rufus.rc index 62278d93..4c424577 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 3.2.1324" +CAPTION "Rufus 3.2.1325" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -389,8 +389,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,2,1324,0 - PRODUCTVERSION 3,2,1324,0 + FILEVERSION 3,2,1325,0 + PRODUCTVERSION 3,2,1325,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -407,13 +407,13 @@ BEGIN BEGIN VALUE "CompanyName", "Akeo Consulting (http://akeo.ie)" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "3.2.1324" + VALUE "FileVersion", "3.2.1325" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2018 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html" VALUE "OriginalFilename", "rufus.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "3.2.1324" + VALUE "ProductVersion", "3.2.1325" END END BLOCK "VarFileInfo" diff --git a/src/stdlg.c b/src/stdlg.c index 234074f8..2f1a0c86 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -1630,7 +1630,7 @@ INT_PTR CALLBACK NewVersionCallback(HWND hDlg, UINT message, WPARAM wParam, LPAR si.cb = sizeof(si); if (!CreateProcessU(filepath, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { PrintInfo(0, MSG_214); - uprintf("Failed to launch new application: %s\n", WindowsErrorString()); + uprintf("Failed to launch new application: %s", WindowsErrorString()); } else { PrintInfo(0, MSG_213); PostMessage(hDlg, WM_COMMAND, (WPARAM)IDCLOSE, 0); @@ -1639,19 +1639,19 @@ INT_PTR CALLBACK NewVersionCallback(HWND hDlg, UINT message, WPARAM wParam, LPAR break; default: // Download if (update.download_url == NULL) { - uprintf("Could not get download URL\n"); + uprintf("Could not get download URL"); break; } for (i=(int)strlen(update.download_url); (i>0)&&(update.download_url[i]!='/'); i--); dl_ext.filename = &update.download_url[i+1]; filepath = FileDialog(TRUE, app_dir, &dl_ext, OFN_NOCHANGEDIR); if (filepath == NULL) { - uprintf("Could not get save path\n"); + uprintf("Could not get save path"); break; } // Opening the File Dialog will make us lose tabbing focus - set it back SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDC_DOWNLOAD), TRUE); - DownloadFileThreaded(update.download_url, filepath, hDlg); + DownloadSignedFileThreaded(update.download_url, filepath, hDlg); break; } return (INT_PTR)TRUE;