Menu

authenticode signature check fails

2022-07-12
2023-08-09
  • Felix Reichmann

    Felix Reichmann - 2022-07-12

    Hi,
    I am currently working on providing a detailed guide on how to compile the VeraCrypt Code on Windows.
    I am able to compile the code without errors and the executables are generated when I run the script src/Signing/sign_test.bat
    Unfortunately I get the error "This distribution package is damaged.", when I try to run the VeraCrypt Setup on a Windows 10 and above machine. On Windows 7 the package works fine, so it does not seem to be a compiling problem. The signing certificates of the resulting executable are trusted, so it should not be a certificate issue either.

    After having a look into the code, I can see that the issue arises from the function "VerifyModuleSignature" within the file "Dlgcode.c". The function calculates a hash of a provided certificate and compares it against a hard coded value, in case the operating system is Windows 10 or higher. This should make sure, that the correct

    For me, it looks like no certificate is provided, since the variable fileInfo is set to {0}. Therefore the comparison between the generated hash and the hard coded hash fails.
    Can someone confirm this?

    Regards
    Felix


    static unsigned char gpbSha256CodeSignCertFingerprint[64] = {
        0x9C, 0xA0, 0x21, 0xD3, 0x7C, 0x90, 0x61, 0x88, 0xEF, 0x5F, 0x99, 0x3D,
        0x54, 0x9F, 0xB8, 0xCE, 0x72, 0x32, 0x4F, 0x57, 0x4F, 0x19, 0xD2, 0xA4,
        0xDC, 0x84, 0xFF, 0xE2, 0x84, 0x2B, 0xD4, 0x30, 0xAB, 0xA7, 0xE4, 0x63,
        0x18, 0xD1, 0xD8, 0x32, 0x0E, 0xA4, 0x81, 0x3C, 0x19, 0xBF, 0x13, 0x11,
        0xA4, 0x37, 0xD6, 0xDB, 0x26, 0xBA, 0xDC, 0x8F, 0x86, 0x96, 0x55, 0x96,
        0xDB, 0x6F, 0xC0, 0x62
    };
    
    static unsigned char gpbSha256MSCodeSignCertFingerprint[64] = {
        0x9C, 0x96, 0x81, 0x3B, 0x88, 0x54, 0xCB, 0x81, 0xB5, 0x94, 0x40, 0x4E,
        0x15, 0x81, 0x20, 0xA1, 0x19, 0x00, 0x4E, 0x49, 0x8A, 0xA8, 0x98, 0x13,
        0x9D, 0xE2, 0x86, 0x6A, 0xC1, 0xFA, 0xD3, 0x00, 0x0D, 0xAC, 0xE9, 0xE3,
        0x3B, 0xFC, 0x6B, 0x26, 0xCE, 0xC8, 0xE2, 0x36, 0x3B, 0x60, 0x9C, 0x8E,
        0x0A, 0x2A, 0x74, 0x20, 0xD7, 0x4E, 0x0F, 0xEE, 0x2E, 0x79, 0xE2, 0xAF,
        0x1C, 0x90, 0x0B, 0x9C
    };
    
    BOOL VerifyModuleSignature (const wchar_t* path)
    {
    #if defined(NDEBUG) && !defined (VC_SKIP_OS_DRIVER_REQ_CHECK)
        BOOL bResult = FALSE;
        HRESULT hResult;
        GUID gActionID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
        WINTRUST_FILE_INFO  fileInfo = {0};
        WINTRUST_DATA      WVTData = {0};
        wchar_t filePath [TC_MAX_PATH + 1024];
    
        // we check our own authenticode signature only starting from Windows 10 since this is
        // the minimal supported OS apart from XP where we can't verify SHA256 signatures
        if (!IsOSAtLeast (WIN_10))
            return TRUE;
    
        // Strip quotation marks (if any)
        if (path [0] == L'"')
        {
            StringCbCopyW (filePath, sizeof(filePath), path + 1);
        }
        else
        {
            StringCbCopyW (filePath, sizeof(filePath), path);
        }
    
        // Strip quotation marks (if any)
        if (filePath [wcslen (filePath) - 1] == L'"')
            filePath [wcslen (filePath) - 1] = 0;
    
        if (!InitializeWintrust ())
            return FALSE;
    
        fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
        fileInfo.pcwszFilePath = filePath;
        fileInfo.hFile = NULL;
    
        WVTData.cbStruct            = sizeof(WINTRUST_DATA);
        WVTData.dwUIChoice          = WTD_UI_NONE;
        WVTData.fdwRevocationChecks = WTD_REVOKE_NONE;
        WVTData.dwUnionChoice       = WTD_CHOICE_FILE;
        WVTData.pFile               = &fileInfo;
        WVTData.dwStateAction       = WTD_STATEACTION_VERIFY;
        WVTData.dwProvFlags         = WTD_REVOCATION_CHECK_NONE | WTD_CACHE_ONLY_URL_RETRIEVAL;
    
        hResult = WinVerifyTrustFn(0, &gActionID, &WVTData);
        if (0 == hResult)
        {
            PCRYPT_PROVIDER_DATA pProviderData = WTHelperProvDataFromStateDataFn (WVTData.hWVTStateData);
            if (pProviderData)
            {
                PCRYPT_PROVIDER_SGNR pProviderSigner = WTHelperGetProvSignerFromChainFn (pProviderData, 0, FALSE, 0);
                if (pProviderSigner)
                {
                    PCRYPT_PROVIDER_CERT pProviderCert = WTHelperGetProvCertFromChainFn (pProviderSigner, 0);
                    if (pProviderCert && (pProviderCert->pCert))
                    {
                        BYTE hashVal[64];
                        sha512 (hashVal, pProviderCert->pCert->pbCertEncoded, pProviderCert->pCert->cbCertEncoded);
    
                        if (    (0 ==  memcmp (hashVal, gpbSha256CodeSignCertFingerprint, 64))
                            ||  (0 ==  memcmp (hashVal, gpbSha256MSCodeSignCertFingerprint, 64))
                            )
                        {
                            bResult = TRUE;
                        }
                    }
                }
            }
        }
    
        WVTData.dwUIChoice = WTD_UI_NONE;
        WVTData.dwStateAction = WTD_STATEACTION_CLOSE;
        WinVerifyTrustFn(0, &gActionID, &WVTData);
    
        FinalizeWintrust ();
    
        return bResult;
    #else
        return TRUE;
    #endif
    }
    
     
  • Mounir IDRASSI

    Mounir IDRASSI - 2023-08-09

    Hi Felix,

    Thank you for your comprehensive analysis.

    Regarding the fileInfo variable: The fileInfo structure is intended to provide details about the file being verified. While it's initialized to {0}, it is later populated with the path of the file that you're checking. Specifically, these lines of code ensure it's populated:

    fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
    fileInfo.pcwszFilePath = filePath;
    fileInfo.hFile = NULL;
    

    So, it's not an issue with the variable initialization. The fileInfo correctly points to the file path, as you mentioned.

    Root Certificate Installation: One of the common reasons for WinVerifyTrust to fail, even if the certificate is valid, is if the root certificate (or intermediate certificates, if any) are not trusted by the system. To be specific, the root certificate used to sign the module needs to be present in the "Local Machine Trusted Root Certification Authorities" store on the machine where you're trying to run the setup. So, one needs to ensure that it is indeed installed in the correct store.

    SHA512 Fingerprint: As per the code, the function computes a SHA512 hash of the certificate and compares it against two hard-coded SHA512 fingerprints (gpbSha256CodeSignCertFingerprint and gpbSha256MSCodeSignCertFingerprint). The name of the variable is misleading since it's a SHA512 hash and not SHA256 (this was fixed in this commit and now the variables names are gpbSha512CodeSignCertFingerprint and gpbSha512MSCodeSignCertFingerprint). Since you are using a test certificate for signing, you will need to compute the SHA512 hash of this certificate and update the value of the gpbSha256CodeSignCertFingerprint array with this new hash. That's because the hard-coded values are specific to the original VeraCrypt certificates, and won't match your custom test certificate.

    So to summarize:

    • Double-check the installation of the root certificate in the "Local Machine Trusted Root Certification Authorities" store.
    • Compute its SHA512 fingerprint of the test certificate and update the gpbSha256CodeSignCertFingerprint array in the code accordingly.

    I hope this clarifies the situation

     

    Last edit: Mounir IDRASSI 2023-08-09

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.