[fido] add additional Authenticode validation before running the script

* This basically means that the script is validate *TWICE*, using two
  completely independent signatures, before it is allowed to run, which
  should add another mitigation layer against TOCTOU (which we already
  friggin' mitigated against anyway) and other potential vectors of
  attack.
* Also remove -DisableFirstRunCustomize option and the associated cookie
  prompt monitoring, which the latest version of Fido no longer requires.
* Also update WDK version for signtool and flesh out PKI error messages.
This commit is contained in:
Pete Batard 2021-05-27 00:18:05 +01:00
parent 3083324a0e
commit f26fd2fbe3
No known key found for this signature in database
GPG Key ID: 38E0CF5E69EDD671
8 changed files with 63 additions and 49 deletions

View File

@ -1,3 +1,3 @@
@echo off
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\signtool" sign /v /sha1 9ce9a71ccab3b38a74781b975f1c228222cf7d3b /fd SHA256 /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp %1
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool" sign /v /sha1 9ce9a71ccab3b38a74781b975f1c228222cf7d3b /fd SHA256 /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp %1
exit

View File

@ -11,7 +11,7 @@
<Identity
Name="19453.net.Rufus"
Publisher="CN=7AC86D13-3E5A-491A-ADD5-80095C212740"
Version="3.14.1793.0" />
Version="3.14.1794.0" />
<Properties>
<DisplayName>Rufus</DisplayName>

View File

@ -52,7 +52,7 @@ HANDLE update_check_thread = NULL;
extern loc_cmd* selected_locale;
extern HANDLE dialog_handle;
extern BOOL is_x86_32, close_fido_cookie_prompts;
extern BOOL is_x86_32;
static DWORD error_code, fido_len = 0;
static BOOL force_update_check = FALSE;
static const char* request_headers = "Accept-Encoding: gzip, deflate";
@ -918,7 +918,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param)
static_sprintf(sig_url, "%s.sig", fido_url);
dwSize = (DWORD)DownloadToFileOrBuffer(sig_url, NULL, &sig, NULL, FALSE);
if ((dwSize != RSA_SIGNATURE_SIZE) || (!ValidateOpensslSignature(compressed, dwCompressedSize, sig, dwSize))) {
uprintf("FATAL: Signature is invalid ✗");
uprintf("FATAL: Download signature is invalid ✗");
FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_BAD_SIGNATURE);
SendMessage(hProgress, PBM_SETSTATE, (WPARAM)PBST_ERROR, 0);
SetTaskbarProgressState(TASKBAR_ERROR);
@ -927,7 +927,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param)
goto out;
}
free(sig);
uprintf("Signature is valid ✓");
uprintf("Download signature is valid ✓");
uncompressed_size = *((uint64_t*)&compressed[5]);
if ((uncompressed_size < 1 * MB) && (bled_init(_uprintf, NULL, NULL, NULL, NULL, &FormatStatus) >= 0)) {
fido_script = malloc((size_t)uncompressed_size);
@ -983,13 +983,22 @@ static DWORD WINAPI DownloadISOThread(LPVOID param)
}
static_sprintf(cmdline, "\"%s\" -NonInteractive -Sta -NoProfile ExecutionPolicy Bypass "
"-File \"%s\" -DisableFirstRunCustomize -PipeName %s -LocData \"%s\" -Icon \"%s\" -AppTitle \"%s\"",
"-File \"%s\" -PipeName %s -LocData \"%s\" -Icon \"%s\" -AppTitle \"%s\"",
powershell_path, script_path, &pipe[9], locale_str, icon_path, lmprintf(MSG_149));
// Signal our Windows alert hook that it should close the IE cookie prompts from Fido
close_fido_cookie_prompts = TRUE;
// For extra security, even after we validated that the LZMA download is properly
// signed, we also validate the Authenticode signature of the local script.
if (ValidateSignature(INVALID_HANDLE_VALUE, script_path) != NO_ERROR) {
uprintf("FATAL: Script signature is invalid ✗");
FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_BAD_SIGNATURE);
SendMessage(hProgress, PBM_SETSTATE, (WPARAM)PBST_ERROR, 0);
SetTaskbarProgressState(TASKBAR_ERROR);
goto out;
}
uprintf("Script signature is valid ✓");
dwExitCode = RunCommand(cmdline, app_dir, TRUE);
uprintf("Exited download script with code: %d", dwExitCode);
close_fido_cookie_prompts = FALSE;
if ((dwExitCode == 0) && PeekNamedPipe(hPipe, NULL, dwPipeSize, NULL, &dwAvail, NULL) && (dwAvail != 0)) {
url = malloc(dwAvail + 1);
dwSize = 0;

View File

@ -192,12 +192,24 @@ const char* WinPKIErrorString(void)
return "None of the signers of the cryptographic message or certificate trust list is trusted.";
case CERT_E_UNTRUSTEDROOT:
return "The root certificate is not trusted.";
case TRUST_E_SYSTEM_ERROR:
return "A system-level error occurred while verifying trust.";
case TRUST_E_NO_SIGNER_CERT:
return "The certificate for the signer of the message is invalid or not found.";
case TRUST_E_COUNTER_SIGNER:
return "One of the counter signatures was invalid.";
case TRUST_E_CERT_SIGNATURE:
return "The signature of the certificate cannot be verified.";
case TRUST_E_TIME_STAMP:
return "The timestamp could not be verified.";
case TRUST_E_BAD_DIGEST:
return "The file content has been altered.";
case TRUST_E_BASIC_CONSTRAINTS:
return "A certificate's basic constraint extension has not been observed.";
case TRUST_E_NOSIGNATURE:
return "Not digitally signed.";
case TRUST_E_EXPLICIT_DISTRUST:
return "One of the certificates used was marked as untrusted by the user.";
case TRUST_E_TIME_STAMP:
return "The timestamp could not be verified.";
default:
static_sprintf(error_string, "Unknown PKI error 0x%08lX", error_code);
return error_string;
@ -205,7 +217,7 @@ const char* WinPKIErrorString(void)
}
// Mostly from https://support.microsoft.com/en-us/kb/323809
char* GetSignatureName(const char* path, const char* country_code)
char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent)
{
static char szSubjectName[128];
char szCountry[3] = "__";
@ -308,9 +320,9 @@ char* GetSignatureName(const char* path, const char* country_code)
}
if (szCountry[0] == '_')
uprintf("Binary executable is signed by '%s'", szSubjectName);
suprintf("Binary executable is signed by '%s'", szSubjectName);
else
uprintf("Binary executable is signed by '%s' (%s)", szSubjectName, szCountry);
suprintf("Binary executable is signed by '%s' (%s)", szSubjectName, szCountry);
p = szSubjectName;
out:
@ -572,10 +584,11 @@ LONG ValidateSignature(HWND hDlg, const char* path)
// Check the signature name. Make it specific enough (i.e. don't simply check for "Akeo")
// so that, besides hacking our server, it'll place an extra hurdle on any malicious entity
// into also fooling a C.A. to issue a certificate that passes our test.
signature_name = GetSignatureName(path, cert_country);
signature_name = GetSignatureName(path, cert_country, (hDlg == INVALID_HANDLE_VALUE));
if (signature_name == NULL) {
uprintf("PKI: Could not get signature name");
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
if (hDlg != INVALID_HANDLE_VALUE)
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
return TRUST_E_NOSIGNATURE;
}
for (i = 0; i < ARRAYSIZE(cert_name); i++) {
@ -584,8 +597,9 @@ LONG ValidateSignature(HWND hDlg, const char* path)
}
if (i >= ARRAYSIZE(cert_name)) {
uprintf("PKI: Signature '%s' is unexpected...", signature_name);
if (MessageBoxExU(hDlg, lmprintf(MSG_285, signature_name), lmprintf(MSG_283),
MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES)
if ((hDlg == INVALID_HANDLE_VALUE) || (MessageBoxExU(hDlg,
lmprintf(MSG_285, signature_name), lmprintf(MSG_283),
MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES))
return TRUST_E_EXPLICIT_DISTRUST;
}
@ -615,6 +629,9 @@ LONG ValidateSignature(HWND hDlg, const char* path)
safe_free(trust_file.pcwszFilePath);
switch (r) {
case ERROR_SUCCESS:
// hDlg = INVALID_HANDLE_VALUE is used when validating the Fido PS1 script
if (hDlg == INVALID_HANDLE_VALUE)
break;
// Verify that the timestamp of the downloaded update is in the future of our current one.
// This is done to prevent the use of an officially signed, but older binary, as potential attack vector.
current_ts = GetSignatureTimeStamp(NULL);
@ -634,11 +651,13 @@ LONG ValidateSignature(HWND hDlg, const char* path)
case TRUST_E_NOSIGNATURE:
// Should already have been reported, but since we have a custom message for it...
uprintf("PKI: File does not appear to be signed: %s", WinPKIErrorString());
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
if (hDlg != INVALID_HANDLE_VALUE)
MessageBoxExU(hDlg, lmprintf(MSG_284), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
break;
default:
uprintf("PKI: Failed to validate signature: %s", WinPKIErrorString());
MessageBoxExU(hDlg, lmprintf(MSG_240), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
if (hDlg != INVALID_HANDLE_VALUE)
MessageBoxExU(hDlg, lmprintf(MSG_240), lmprintf(MSG_283), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid);
break;
}

View File

@ -3335,7 +3335,7 @@ skip_args_processing:
static_sprintf(ini_path, "%s\\rufus.ini", app_dir);
fd = fopenU(ini_path, ini_flags); // Will create the file if portable mode is requested
// Using the string directly in safe_strcmp() would call GetSignatureName() twice
tmp = GetSignatureName(NULL, NULL);
tmp = GetSignatureName(NULL, NULL, FALSE);
vc |= (safe_strcmp(tmp, cert_name[0]) == 0);
if (fd != NULL) {
ini_file = ini_path;

View File

@ -572,7 +572,7 @@ extern uint8_t IsBootableImage(const char* path);
extern BOOL AppendVHDFooter(const char* vhd_path);
extern int SetWinToGoIndex(void);
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 char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent);
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);

View File

@ -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.14.1793"
CAPTION "Rufus 3.14.1794"
FONT 9, "Segoe UI Symbol", 400, 0, 0x0
BEGIN
LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP
@ -395,8 +395,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,14,1793,0
PRODUCTVERSION 3,14,1793,0
FILEVERSION 3,14,1794,0
PRODUCTVERSION 3,14,1794,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -414,13 +414,13 @@ BEGIN
VALUE "Comments", "https://rufus.ie"
VALUE "CompanyName", "Akeo Consulting"
VALUE "FileDescription", "Rufus"
VALUE "FileVersion", "3.14.1793"
VALUE "FileVersion", "3.14.1794"
VALUE "InternalName", "Rufus"
VALUE "LegalCopyright", "© 2011-2021 Pete Batard (GPL v3)"
VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html"
VALUE "OriginalFilename", "rufus-3.14.exe"
VALUE "ProductName", "Rufus"
VALUE "ProductVersion", "3.14.1793"
VALUE "ProductVersion", "3.14.1794"
END
END
BLOCK "VarFileInfo"

View File

@ -59,9 +59,8 @@ static const notification_info* notification_more_info;
static const char* notification_dont_display_setting;
static WNDPROC update_original_proc = NULL;
static HWINEVENTHOOK ap_weh = NULL;
static char title_str[3][128], button_str[128];
static char title_str[2][128], button_str[128];
HWND hFidoDlg = NULL;
BOOL close_fido_cookie_prompts = FALSE;
static int update_settings_reposition_ids[] = {
IDI_ICON,
@ -1989,14 +1988,13 @@ INT_PTR MyDialogBox(HINSTANCE hInstance, int Dialog_ID, HWND hWndParent, DLGPROC
/*
* The following function calls are used to automatically detect and close the native
* Windows format prompt "You must format the disk in drive X:" as well as the cookies
* alert being popped by Windows when running our Download script. To do that, we use
* Windows format prompt "You must format the disk in drive X:". To do that, we use
* an event hook that gets triggered whenever a window is placed in the foreground.
* In that hook, we look for a dialog that has style WS_POPUPWINDOW and has the relevant
* title. However, in case of the Format prompt, because the title in itself is too
* generic (the expectation is that it will be "Microsoft Windows") we also enumerate
* all the child controls from that prompt, using another callback, until we find one
* that contains the text we expect for the "Format disk" button.
* title. However, because the title in itself is too generic (the expectation is that
* it will be "Microsoft Windows") we also enumerate all the child controls from that
* prompt, using another callback, until we find one that contains the text we expect
* for the "Format disk" button.
* Oh, and since all of these strings are localized, we must first pick them up from
* the relevant mui's.
*/
@ -2028,9 +2026,7 @@ static void CALLBACK AlertPromptHook(HWINEVENTHOOK hWinEventHook, DWORD Event, H
SendMessage(hWnd, WM_COMMAND, (WPARAM)IDCANCEL, (LPARAM)0);
uprintf("Closed Windows format prompt");
}
} else if (close_fido_cookie_prompts && strcmp(str, title_str[1]) == 0) {
SendMessage(hWnd, WM_COMMAND, (WPARAM)IDCANCEL, (LPARAM)0);
} else if ((strcmp(str, title_str[2]) == 0) && (hWnd != hFidoDlg)) {
} else if ((strcmp(str, title_str[1]) == 0) && (hWnd != hFidoDlg)) {
// A wild Fido dialog appeared! => Keep track of its handle and center it
hFidoDlg = hWnd;
CenterDialog(hWnd, hMainDialog);
@ -2062,17 +2058,7 @@ void SetAlertPromptMessages(void)
}
FreeLibrary(mui_lib);
}
static_sprintf(mui_path, "%s\\%s\\urlmon.dll.mui", sysnative_dir, GetCurrentMUI());
mui_lib = LoadLibraryExU(mui_path, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
if (mui_lib != NULL) {
// 2070 = "Windows Security Warning" (yes, that's what MS uses for a stupid cookie!)
if (LoadStringU(mui_lib, 2070, title_str[1], sizeof(title_str[1])) <= 0) {
static_strcpy(title_str[1], "Windows Security Warning");
uprintf("Warning: Could not locate localized cookie prompt title string in '%s': %s", mui_path, WindowsErrorString());
}
FreeLibrary(mui_lib);
}
static_strcpy(title_str[2], lmprintf(MSG_149));
static_strcpy(title_str[1], lmprintf(MSG_149));
}
BOOL SetAlertPromptHook(void)