CityGame Posted March 7 Posted March 7 (edited) Hello everyone, I'm new to the forum, but have been using AutoIt for years. It's quite an easy but very powerful tool to create programs, thank you My home language is spanish, so I'm sorry if I make a few mistakes while writing in english. I'm currently trying to create an updater for an already done program. The updater must be secure and not blindly download and run a new version installer, of course, so as first step I'm downloading a manifest file (that contains information about the current version update) and a signature file for that manifest created using a private key (generated by OpenSSL). Using the public key embedded in the updater program, I would then verify that the signature "matches" the manifest file. It's the first time I use those cryptography Windows functions and I have always avoided as much as I could using DllCall (because mistakes can just crash the program) so I'm not really experienced with it, but this time there is no way around and I'm "a bit" confused, I have been trying to debug this for a few days without success, and would like to ask for help here. The actual code of the first part of the updater is the following. I've translated all comments and error/warning/debug messages to english, but variable names remain in spanish. If that's a problem, please tell me. I've been using for years another naming convention for the type of variables different than the one used on the forums but I think is easy to guess, but again, if it's a problem, I would change it. expandcollapse popup#include <FileConstants.au3> #include <MsgBoxConstants.au3> #include <StringConstants.au3> AutoItSetOption("MustDeclareVars", 1) Global $ERROR_AU3_DLLCALL_FAILED = 1 Global $str_ProgramaVersion = 2026.3 Global $str_ActualizarManifestURL = "https://www.examplesite.es/temp.ini" Global $str_ActualizarFirmaURL = "https://www.examplesite.es/temp.sig" Global $str_ActualizarClavePublicaDERBase64 = "MIIBo...AAE=" ;============================================================================== ; The main function does some more things, but I have left only the the update related lines. Local $int_ResultadoActualizacion = 0 $int_ResultadoActualizacion = Actualizacion() Exit $int_ResultadoActualizacion ;============================================================================== Func Actualizacion() ; Step 01 of 14: Local variables of the update function. Local Const $PROV_RSA_AES = 24 Local Const $CRYPT_VERIFYCONTEXT = 0xF0000000 Local Const $CALG_SHA_256 = 0x0000800C Local Const $HP_HASHVAL = 0x0002 Local Const $X509_ASN_ENCODING = 0x00000001 Local Const $PKCS_7_ASN_ENCODING = 0x00010000 Local $array_CryptStringToBinaryW Local $array_CryptAcquireContextW Local $array_CryptCreateHash Local $array_CryptHashData Local $array_CryptGetHashParam Local $array_CryptImportPublicKeyInfo Local $array_CryptDecodeObjectEx Local $array_CryptVerifySignatureW Local $bin_ActualizarManifestDatos Local $bin_ActualizarFirmaDatos Local $bin_ActualizarClavePublicaDER Local $bin_ActualizarManifestHash Local $dllstruct_ActualizarClavePublicaDER Local $dllstruct_ActualizarFirmaDatos Local $dllstruct_ActualizarManifestDatos Local $dllstruct_CryptDecodeObjectEx Local $dword_LongitudBufferCryptDecodeObjectEx Local $dword_LongitudBufferCryptGetHashParam Local $handle_ProveedorServiciosCriptograficos = 0 Local $handle_Hash = 0 Local $handle_ActualizarClavePublica = 0 Local $int_RetornoFileWrite = 0 Local $int_RetornoShellExecute = 0 Local $int_ResultadoMsgBox = 0 Local $object_PeticionActualizarManifest Local $object_PeticionActualizarFirma ; Step 02 of 14: Create objects to download manifest and its signature. If ((StringLeft(StringLower($str_ActualizarManifestURL), 8) <> "https://") Or (StringLeft(StringLower($str_ActualizarFirmaURL), 8) <> "https://")) Then MsgBox(16, "Error", "Download URLs no using HTTPS.", 0) Return EndIf $object_PeticionActualizarManifest = ObjCreate("WinHttp.WinHttpRequest.5.1") If @error Or Not IsObj($object_PeticionActualizarManifest) Then MsgBox(16, "Error", "Could not create WinHTTP object to download manifest.", 0) Return Else $object_PeticionActualizarManifest.Option(6) = False ; No seguir redirecciones automáticamente: WinHttpRequestOption_EnableRedirects = 6 $object_PeticionActualizarManifest.Open("GET", $str_ActualizarManifestURL, False) $object_PeticionActualizarManifest.Send() If $object_PeticionActualizarManifest.Status <> 200 Then MsgBox(48, "Warning", "Could not download manifest file." & @CRLF & @CRLF & "Server reply: " & $object_PeticionActualizarManifest.Status, 0) Return EndIf $bin_ActualizarManifestDatos = $object_PeticionActualizarManifest.ResponseBody EndIf $object_PeticionActualizarFirma = ObjCreate("WinHttp.WinHttpRequest.5.1") If @error Or Not IsObj($object_PeticionActualizarFirma) Then MsgBox(16, "Error", "Could not create WinHTTP object to download manifest signature.", 0) Return Else $object_PeticionActualizarFirma.Option(6) = False ; No seguir redirecciones automáticamente: WinHttpRequestOption_EnableRedirects = 6 $object_PeticionActualizarFirma.Open("GET", $str_ActualizarFirmaURL, False) $object_PeticionActualizarFirma.Send() If $object_PeticionActualizarFirma.Status <> 200 Then MsgBox(48, "Warning", "Could not download manifest signature file." & @CRLF & @CRLF & "Server reply: " & $object_PeticionActualizarManifest.Status, 0) Return EndIf $bin_ActualizarFirmaDatos = $object_PeticionActualizarFirma.ResponseBody EndIf ; Step 03.1 of 14: Get public key ready to use: convert DER from base64 to binary. $str_ActualizarClavePublicaDERBase64 = StringStripWS($str_ActualizarClavePublicaDERBase64, 3) If $str_ActualizarClavePublicaDERBase64 = "" Then MsgBox(16, "Error", "Embedded public key is damaged or has unexpected format.", 0) Return EndIf $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $str_ActualizarClavePublicaDERBase64, "dword", 0, "dword", 1, "ptr", 0, "dword*", 0, "ptr", 0, "ptr", 0) If @error Or Not $array_CryptStringToBinaryW[0] Then MsgBox(16, "Error", "Could not convert public key from DER Base64 to binary (step 1).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf Local $pointer_CryptStringToBinaryWTamano = $array_CryptStringToBinaryW[5] Local $dllstruct_CryptStringToBinaryWResultado = DllStructCreate("byte[" & $pointer_CryptStringToBinaryWTamano & "]") $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $str_ActualizarClavePublicaDERBase64, "dword", 0, "dword", 1, "ptr", DllStructGetPtr($dllstruct_CryptStringToBinaryWResultado), "dword*", $pointer_CryptStringToBinaryWTamano, "ptr", 0, "ptr", 0) If @error Or Not $array_CryptStringToBinaryW[0] Then MsgBox(16, "Error", "Could not convert public key from DER Base64 to binary (step 2).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $bin_ActualizarClavePublicaDER = DllStructGetData($dllstruct_CryptStringToBinaryWResultado, 1) ; Step 03.2 of 14: Get public key ready to use: decode DER. $dllstruct_ActualizarClavePublicaDER = DllStructCreate("byte[" & BinaryLen($bin_ActualizarClavePublicaDER) & "]") DllStructSetData($dllstruct_ActualizarClavePublicaDER, 1, $bin_ActualizarClavePublicaDER) $dword_LongitudBufferCryptDecodeObjectEx = 0 ; Wrong?: $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", $X509_ASN_ENCODING, "str", "X509_PUBLIC_KEY_INFO", "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", 8, "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Or $dword_LongitudBufferCryptDecodeObjectEx = 0 Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 1: getting size of the result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $dllstruct_CryptDecodeObjectEx = DllStructCreate("byte[" & $dword_LongitudBufferCryptDecodeObjectEx & "]") $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", 8, "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", DllStructGetPtr($dllstruct_CryptDecodeObjectEx), "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 2: getting the actual result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf MsgBox(64, "Debug", "About to call CryptAcquireContextW") ; Step 03.3 of 14: Get public key ready to use: Adquire context and import it. $array_CryptAcquireContextW = DllCall("advapi32.dll", "bool", "CryptAcquireContextW", "ptr*", 0, "ptr", 0, "ptr", 0, "dword", $PROV_RSA_AES, "dword", $CRYPT_VERIFYCONTEXT) If @error Or Not $array_CryptAcquireContextW[0] Then MsgBox(16, "Error", "Adquiring context failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $handle_ProveedorServiciosCriptograficos = $array_CryptAcquireContextW[1] MsgBox(64, "Debug", "CryptAcquireContextW checked ok") MsgBox(64, "Debug", "About to call CryptImportPublicKeyInfo") $array_CryptImportPublicKeyInfo = DllCall("crypt32.dll", "bool", "CryptImportPublicKeyInfo", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", DllStructGetPtr($dllstruct_CryptDecodeObjectEx), "ptr*", $handle_ActualizarClavePublica) If @error Or Not $array_CryptImportPublicKeyInfo[0] Then DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Importing public key failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf ;DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) ; Reuse context? $handle_ActualizarClavePublica = $array_CryptImportPublicKeyInfo[4] MsgBox(64, "Debug", "CryptImportPublicKeyInfo checked ok") ; Step 04.1 of 14: Compute hash of manifest: Create hash object. ; Reuse context? ;$array_CryptAcquireContextW = DllCall("advapi32.dll", "bool", "CryptAcquireContextW", "ptr*", 0, "ptr", 0, "ptr", 0, "dword", $PROV_RSA_AES, "dword", $CRYPT_VERIFYCONTEXT) ;If @error Or Not $array_CryptAcquireContextW[0] Then ; MsgBox(16, "Error", "Se ha producido un error al adquirir el contexto del proveedor de servicios criptográficos para calcular el hash de la información de actualización descargada.", 0) ; Return $ERROR_AU3_DLLCALL_FAILED ;EndIf ;$handle_ProveedorServiciosCriptograficos = $array_CryptAcquireContextW[1] $array_CryptCreateHash = DllCall("advapi32.dll", "bool", "CryptCreateHash", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", $CALG_SHA_256, "ptr", 0, "dword", 0, "ptr*", 0) If @error Or Not $array_CryptCreateHash[0] Then DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Creating hash object failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $handle_Hash = $array_CryptCreateHash[6] ; Step 04.2 of 14: Compute hash of manifest: Do compute hash. $dllstruct_ActualizarManifestDatos = DllStructCreate("byte[" & BinaryLen($bin_ActualizarManifestDatos) & "]") DllStructSetData($dllstruct_ActualizarManifestDatos, 1, $bin_ActualizarManifestDatos) $array_CryptHashData = DllCall("advapi32.dll", "bool", "CryptHashData", "ptr", $handle_Hash, "ptr", DllStructGetPtr($dllstruct_ActualizarManifestDatos), "dword", BinaryLen($bin_ActualizarManifestDatos), "dword", 0) If @error Or Not $array_CryptHashData[0] Then DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Computing hash failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf ; Step 04.3 of 14: Compute hash of manifest: Obtain hash (not needed?, I use the object directly instead of this obtainted data). $dword_LongitudBufferCryptGetHashParam = 0 $array_CryptGetHashParam = DllCall("advapi32.dll", "bool", "CryptGetHashParam", "ptr", $handle_Hash, "dword", $HP_HASHVAL, "ptr", 0, "dword*", $dword_LongitudBufferCryptGetHashParam, "dword", 0) If @error Or Not $array_CryptGetHashParam[0] Or $dword_LongitudBufferCryptGetHashParam <= 0 Then DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Obtaining computed hash failed (step 1).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf Local $dllstruct_CryptGetHashParamResultado = DllStructCreate("byte[" & $dword_LongitudBufferCryptGetHashParam & "]") $array_CryptGetHashParam = DllCall("advapi32.dll", "bool", "CryptGetHashParam", "ptr", $handle_Hash, "dword", $HP_HASHVAL, "ptr", DllStructGetPtr($dllstruct_CryptGetHashParamResultado), "dword*", $dword_LongitudBufferCryptGetHashParam, "dword", 0) If @error Or Not $array_CryptGetHashParam[0] Then DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Obtaining computed hash failed (step 2).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf ;DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) ; Should not release this before verifying the signature? ;DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) ; Should not release this before verifying the signature? $bin_ActualizarManifestHash = DllStructGetData($dllstruct_CryptGetHashParamResultado, 1) ; Step 05 of 14: Verify signature using public key. $dllstruct_ActualizarFirmaDatos = DllStructCreate("byte[" & BinaryLen($bin_ActualizarFirmaDatos) & "]") DllStructSetData($dllstruct_ActualizarFirmaDatos, 1, $bin_ActualizarFirmaDatos) $array_CryptVerifySignatureW = DllCall("advapi32.dll", "bool", "CryptVerifySignatureW", "ptr", $handle_Hash, "ptr", DllStructGetPtr($dllstruct_ActualizarFirmaDatos), "dword", BinaryLen($bin_ActualizarFirmaDatos), "ptr", $handle_ActualizarClavePublica, "ptr", 0, "dword", 0) If @error Then DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Could not verify signature.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf DllCall("advapi32.dll", "bool", "CryptDestroyHash", "ptr", $handle_Hash) DllCall("advapi32.dll", "bool", "CryptDestroyKey", "ptr", $handle_ActualizarClavePublica) DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) If $array_CryptVerifySignatureW[0] = 0 Then MsgBox(48, "Warning", "Signature is not valid. Manifest file may be corrupted...", 0) Return EndIf MsgBox(48, "Debug", "Manifest is OK. Next we parse the file and so on...", 0) EndFunc I've tried to follow the documentation on Microsoft about all these functions, but I think I made a mistake, as the CryptDecodeObjectEx function doesn't work as I expected. When I call it to get the size of the result, I get a 0 size, so something must be wrong on my call, but I can't guess where is the mistake. Later, that causes the CryptImportPublicKeyInfo call to crash, so I suppose I must fix something on the CryptDecodeObjectEx call first. Can you please help me to find the problem? If some more information is needed, please just tell me. Thank you very much Edited March 11 by CityGame
Nine Posted March 7 Posted March 7 I am not gonna run a program that I do not fully understand. And yours is a quite long for me to follow all the steps and understand what it does. I strongly suggest that you eliminate all unnecessary parts to surround the part that doesn't work. In other words, make a minimalist snippet with CryptDecodeObjectEx that shows the issue. Thanks. “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
CityGame Posted March 7 Author Posted March 7 (edited) Hello, and thanks for replying The part that fails is where the public key (the long string on the $str_ActualizarClavePublicaDERBase64 variable) is converted from Base64 to binary (step 03.1; that seems to work), then decoded (step 03.2; that's the part that I think that fails), then imported to the cryptographic context to be used (step 03.3; that's the part where it crashes, probably because the previous one did fail somehow). If we are just checking that, I suppose I can remove any following steps (steps 04 and 05) and the previous download of the files that would be checked (step 02)... I have not removed unused variables at the beginning. I bet I will have to add those parts again once this one works to ask for help with them Just in case, that "import" of the public key doesn't mean the public key is imported to the certificate store of the computer, it's imported to the context of this program so the Windows functions that verify the files can use it. The program is safe. I'm trying to create a really secure updater that won't be fooled to run an untrusted update even if something like the recent Notepad++ hack happens. Here is the smaller version: expandcollapse popup#include <FileConstants.au3> #include <MsgBoxConstants.au3> #include <StringConstants.au3> AutoItSetOption("MustDeclareVars", 1) Global $ERROR_AU3_DLLCALL_FAILED = 1 Global $str_ActualizarClavePublicaDERBase64 = "MIIBojAN...MBAAE=" ;============================================================================== ; The main function does some more things, but I have left only the the update related lines. Local $int_ResultadoActualizacion = 0 $int_ResultadoActualizacion = Actualizacion() Exit $int_ResultadoActualizacion ;============================================================================== Func Actualizacion() ; Step 01 of 14: Local variables of the update function. Local Const $PROV_RSA_AES = 24 Local Const $CRYPT_VERIFYCONTEXT = 0xF0000000 Local Const $CALG_SHA_256 = 0x0000800C Local Const $HP_HASHVAL = 0x0002 Local Const $X509_ASN_ENCODING = 0x00000001 Local Const $PKCS_7_ASN_ENCODING = 0x00010000 Local $array_CryptStringToBinaryW Local $array_CryptAcquireContextW Local $array_CryptCreateHash Local $array_CryptHashData Local $array_CryptGetHashParam Local $array_CryptImportPublicKeyInfo Local $array_CryptDecodeObjectEx Local $array_CryptVerifySignatureW Local $bin_ActualizarManifestDatos Local $bin_ActualizarFirmaDatos Local $bin_ActualizarClavePublicaDER Local $bin_ActualizarManifestHash Local $dllstruct_ActualizarClavePublicaDER Local $dllstruct_ActualizarFirmaDatos Local $dllstruct_ActualizarManifestDatos Local $dllstruct_CryptDecodeObjectEx Local $dword_LongitudBufferCryptDecodeObjectEx Local $dword_LongitudBufferCryptGetHashParam Local $handle_ProveedorServiciosCriptograficos = 0 Local $handle_Hash = 0 Local $handle_ActualizarClavePublica = 0 Local $int_RetornoFileWrite = 0 Local $int_RetornoShellExecute = 0 Local $int_ResultadoMsgBox = 0 Local $object_PeticionActualizarManifest Local $object_PeticionActualizarFirma ; Step 03.1 of 14: Get public key ready to use: convert DER from base64 to binary. $str_ActualizarClavePublicaDERBase64 = StringStripWS($str_ActualizarClavePublicaDERBase64, 3) If $str_ActualizarClavePublicaDERBase64 = "" Then MsgBox(16, "Error", "Embedded public key is damaged or has unexpected format.", 0) Return EndIf $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $str_ActualizarClavePublicaDERBase64, "dword", 0, "dword", 1, "ptr", 0, "dword*", 0, "ptr", 0, "ptr", 0) If @error Or Not $array_CryptStringToBinaryW[0] Then MsgBox(16, "Error", "Could not convert public key from DER Base64 to binary (step 1).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf Local $pointer_CryptStringToBinaryWTamano = $array_CryptStringToBinaryW[5] Local $dllstruct_CryptStringToBinaryWResultado = DllStructCreate("byte[" & $pointer_CryptStringToBinaryWTamano & "]") $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $str_ActualizarClavePublicaDERBase64, "dword", 0, "dword", 1, "ptr", DllStructGetPtr($dllstruct_CryptStringToBinaryWResultado), "dword*", $pointer_CryptStringToBinaryWTamano, "ptr", 0, "ptr", 0) If @error Or Not $array_CryptStringToBinaryW[0] Then MsgBox(16, "Error", "Could not convert public key from DER Base64 to binary (step 2).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $bin_ActualizarClavePublicaDER = DllStructGetData($dllstruct_CryptStringToBinaryWResultado, 1) ; Step 03.2 of 14: Get public key ready to use: decode DER. $dllstruct_ActualizarClavePublicaDER = DllStructCreate("byte[" & BinaryLen($bin_ActualizarClavePublicaDER) & "]") DllStructSetData($dllstruct_ActualizarClavePublicaDER, 1, $bin_ActualizarClavePublicaDER) $dword_LongitudBufferCryptDecodeObjectEx = 0 ; Wrong?: $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", $X509_ASN_ENCODING, "str", "X509_PUBLIC_KEY_INFO", "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", 8, "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Or $dword_LongitudBufferCryptDecodeObjectEx = 0 Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 1: getting size of the result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $dllstruct_CryptDecodeObjectEx = DllStructCreate("byte[" & $dword_LongitudBufferCryptDecodeObjectEx & "]") $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", 8, "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", DllStructGetPtr($dllstruct_CryptDecodeObjectEx), "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 2: getting the actual result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf MsgBox(64, "Debug", "About to call CryptAcquireContextW") ; Step 03.3 of 14: Get public key ready to use: Adquire context and import it. $array_CryptAcquireContextW = DllCall("advapi32.dll", "bool", "CryptAcquireContextW", "ptr*", 0, "ptr", 0, "ptr", 0, "dword", $PROV_RSA_AES, "dword", $CRYPT_VERIFYCONTEXT) If @error Or Not $array_CryptAcquireContextW[0] Then MsgBox(16, "Error", "Adquiring context failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $handle_ProveedorServiciosCriptograficos = $array_CryptAcquireContextW[1] MsgBox(64, "Debug", "CryptAcquireContextW checked ok") MsgBox(64, "Debug", "About to call CryptImportPublicKeyInfo") $array_CryptImportPublicKeyInfo = DllCall("crypt32.dll", "bool", "CryptImportPublicKeyInfo", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", DllStructGetPtr($dllstruct_CryptDecodeObjectEx), "ptr*", $handle_ActualizarClavePublica) If @error Or Not $array_CryptImportPublicKeyInfo[0] Then DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) MsgBox(16, "Error", "Importing public key failed.", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf ;DllCall("advapi32.dll", "bool", "CryptReleaseContext", "ptr", $handle_ProveedorServiciosCriptograficos, "dword", 0) ; Reuse context? $handle_ActualizarClavePublica = $array_CryptImportPublicKeyInfo[4] MsgBox(64, "Debug", "CryptImportPublicKeyInfo checked ok") EndFunc Hope everything is ok now, but if some more explanation is needed, just tell me. Thank you Edited March 10 by CityGame
Nine Posted March 8 Posted March 8 Although I am not familiar with those API and not much more about cryptography, I will try to help. The issue is (as you already noticed) coming from the call to CryptDecodeObjectEx. Based on MSDN, the first parameter seem alright, second might be replaced with this : $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", Ptr(8), _ Ptr(8) implies Quote X509_PUBLIC_KEY_INFO (LPCSTR) 8 The pvStructInfo parameter is a pointer to a CERT_PUBLIC_KEY_INFO structure. So this is the third parameter, a pointer to this structure. Quote typedef struct _CERT_PUBLIC_KEY_INFO { CRYPT_ALGORITHM_IDENTIFIER Algorithm; CRYPT_BIT_BLOB PublicKey; } CERT_PUBLIC_KEY_INFO, *PCERT_PUBLIC_KEY_INFO; The algorithm is again a structure : Quote typedef struct _CRYPT_ALGORITHM_IDENTIFIER { LPSTR pszObjId; CRYPT_OBJID_BLOB Parameters; } CRYPT_ALGORITHM_IDENTIFIER, *PCRYPT_ALGORITHM_IDENTIFIER; Where pszObjId is a string constant found here. Now my questions are what string should reflect your situation ? And what could be the parameters (if any) ? “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
CityGame Posted March 8 Author Posted March 8 Quote Although I am not familiar with those API and not much more about cryptography, I will try to help. Thanks a lot. I'm not familiar with it either I've changed the second parameter to Ptr(8), as you said. As you point, it seems my third parameter is wrong... I'm passing only a structure with the public key, but the algorithm identifier structure is missing... Thanks for finding this, I didn't know about that page with the structure and the algorithms... There are a ton of algorithms... I created the signature as RSA using OpenSSL (Command: openssl genrsa -out private-key.pem 3072) and the hashes used are SHA-256 (Command to sign the ini: openssl dgst -sha256 -sign private-key.pem -out temp.sig temp.ini), so I suppose the algorithm is RSA, but I'm sorry, I don't really know which of the different RSA choices should I choose... Maybe "szOID_RSA_HASH" or "szOID_RSA_SHA256RSA" (it seems none of them use parameters, so if I'm not mistaken, the parameters should be "0") I've tried to create the new DllStruct, but I'm doing something wrong. The CryptDecodeObjectEx function fails (Result: 0 / GetLastError: 2148086027): Local $dllstruct_ActualizarClavePublicaDER_Algorithm = DllStructCreate("ptr;int") If @error Then MsgBox(64, "Debug", "1 DllStructCreateError: " & @error) DllStructSetData($dllstruct_ActualizarClavePublicaDER_Algorithm, 1, Ptr("1.2.840.113549.1.1.11")) DllStructSetData($dllstruct_ActualizarClavePublicaDER_Algorithm, 2, 0) $dllstruct_ActualizarClavePublicaDER = DllStructCreate("ptr;byte[" & BinaryLen($bin_ActualizarClavePublicaDER) & "]") If @error Then MsgBox(64, "Debug", "2 DllStructCreateError: " & @error) DllStructSetData($dllstruct_ActualizarClavePublicaDER, 1, DllStructGetPtr($dllstruct_ActualizarClavePublicaDER_Algorithm)) DllStructSetData($dllstruct_ActualizarClavePublicaDER, 2, $bin_ActualizarClavePublicaDER) $dword_LongitudBufferCryptDecodeObjectEx = 0 $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", ptr(8), "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Or $dword_LongitudBufferCryptDecodeObjectEx = 0 Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 1: getting size of the result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf $dllstruct_CryptDecodeObjectEx = DllStructCreate("byte[" & $dword_LongitudBufferCryptDecodeObjectEx & "]") $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", ptr(8), "ptr", DllStructGetPtr($dllstruct_ActualizarClavePublicaDER), "dword", BinaryLen($bin_ActualizarClavePublicaDER), "dword", 0, "ptr", 0, "ptr", DllStructGetPtr($dllstruct_CryptDecodeObjectEx), "dword*", $dword_LongitudBufferCryptDecodeObjectEx) If @error Or Not $array_CryptDecodeObjectEx[0] Then Local $array_GetLastError = DllCall("kernel32.dll", "dword", "GetLastError") MsgBox(64, "Debug", "CryptDecodeObjectEx result (1 OK / 0 Fail): " & $array_CryptDecodeObjectEx[0] & " - GetLastError: " & $array_GetLastError[0]) MsgBox(64, "Debug", BinaryLen($bin_ActualizarClavePublicaDER) & " " & $dword_LongitudBufferCryptDecodeObjectEx) MsgBox(16, "Error", "Could not decode public key (Step 2: getting the actual result.).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf
Solution Nine Posted March 9 Solution Posted March 9 I believe it should be something like this, but it is not working for me either. Local $sAlgo = "1.2.840.113549.1.1.11" ; CRYPT_ALGORITHM_IDENTIFIER structure Local $tAlgorithm = DllStructCreate("ptr str;ptr param") Local $tString = DllStructCreate("char str[" & StringLen($sAlgo) + 1 & "]") $tString.str = $sAlgo ConsoleWrite($tString.str & @CRLF) $tAlgorithm.str = DllStructGetPtr($tString) $tAlgorithm.param = 0 ; CERT_PUBLIC_KEY_INFO Local $tPubliKey = DllStructCreate("ptr algo;ptr pkey") $tPubliKey.algo = DllStructGetPtr($tAlgorithm) $tPubliKey.pkey = 0 ; certainly wrong - should be BLOBHEADER ? ; X509_PUBLIC_KEY_INFO = 8 $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", Ptr(8), _ "ptr", DllStructGetPtr($tPubliKey), "dword", DllStructGetSize($tPubliKey), "dword", 0, "ptr", 0, "ptr*", 0, "dword*", 0) _ArrayDisplay($array_CryptDecodeObjectEx) CityGame 1 “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
CityGame Posted March 10 Author Posted March 10 Hello again, Thank you for correcting my structure for DllCall... It was a bit more complex than I though... The function doesn't work for me too. But with that help from you, there is an improvement: Right now GetLastError returns 0x8009310B, and that seems to be "CRYPT_E_ASN1_BADTAG: ASN.1 bad tag value met" (https://learn.microsoft.com/en-us/windows/win32/seccrypto/asn-1-encoding-decoding-return-values). So, to me, it seems the certificate is not in the format it expects... (And/Or the algorithm I chose is not the correct one...) While searching about the certificate format, it seems that a DER certificate not only contains the public key (in this case): it already contains information about the algorithm, and it has a format (X509_ASN_ENCODING) (the same format as the structure?) the CryptDecodeObjectEx function expects. So it's ok to pass the DER (once decoded from Base64) to the CryptDecodeObjectEx function. It seems there are TWO options: 1) you can pass the binary DER to the function and it will "automagically" get the algorithm from the DER, or 2) you can create the structure, manually specify the algorithm and pass the "raw" public key in the structure. If you're using the option 2 and you have all the correct data, it should work. But because OpenSSL already gives you the DER file, after using CryptStringToBinaryW to convert it from Base64 to binary, you can use option 1). That was a lot to understand to me with no previous experience with this Windows Cryptography API... I hope I didn't make any mistakes understanding+explaining that... So it seems your code to call the CryptDecodeObjectEx function *is correct* if we were using the option 2), but because we already have a DER file, we have to use option 1) Local $sPubliKeyDERBase64 = "MII.......AgMBAAE=" $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $sPubliKeyDERBase64, "dword", 0, "dword", 1, "ptr", 0, "dword*", 0, "ptr", 0, "ptr", 0) Local $pCryptStringToBinaryWSize = $array_CryptStringToBinaryW[5] Local $tCryptStringToBinaryWResult = DllStructCreate("byte[" & $pCryptStringToBinaryWSize & "]") $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $sPubliKeyDERBase64, "dword", 0, "dword", 1, "ptr", DllStructGetPtr($tCryptStringToBinaryWResult), "dword*", $pCryptStringToBinaryWSize, "ptr", 0, "ptr", 0) $bPubliKeyDERBinary = DllStructGetData($tCryptStringToBinaryWResult, 1) $tPubliKeyDERBinary = DllStructCreate("byte[" & BinaryLen($bPubliKeyDERBinary) & "]") DllStructSetData($tPubliKeyDERBinary, 1, $bPubliKeyDERBinary) $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", $X509_ASN_ENCODING, "ptr", Ptr(8), "ptr", DllStructGetPtr($tPubliKeyDERBinary), "dword", BinaryLen(($tPubliKeyDERBinary), "dword", 0, "ptr", 0, "ptr", 0, "dword*", $dword_LongitudBufferCryptDecodeObjectEx) Then, a mistake *I* made on the code on my first post: I was checking for errors after DllCall with CryptDecodeObjectEx by checking @error, checking the result on $array_CryptDecodeObjectEx[0], and checking if ($dword_LongitudBufferCryptDecodeObjectEx = 0). That last one was always 0, because I shouldn't have checked that, but $array_CryptDecodeObjectEx[8], that contains the correct value modified by the function! And now *this step* is working Thank you very much, your help pointed me in the right direction. Now the program got to the step 5 of the code on the original post (with the modifications made to correct my mistakes found on these posts) and it says the signature is not valid... 🙄
Nine Posted March 10 Posted March 10 (edited) @CityGame Glad it is working. Thanks for your explanation but I am not sure I'm fully understanding all of it BTW, you do not need to recreate the $tPubliKeyDERBinary. Save you time and space : Local $pointer_CryptStringToBinaryWTamano = $array_CryptStringToBinaryW[5] Local $dllstruct_CryptStringToBinaryWResultado = DllStructCreate("byte[" & $pointer_CryptStringToBinaryWTamano & "]") $array_CryptStringToBinaryW = DllCall("crypt32.dll", "bool", "CryptStringToBinaryW", "wstr", $str_ActualizarClavePublicaDERBase64, "dword", 0, "dword", 1, _ "ptr", DllStructGetPtr($dllstruct_CryptStringToBinaryWResultado), "dword*", $pointer_CryptStringToBinaryWTamano, "ptr", 0, "ptr", 0) If @error Or Not $array_CryptStringToBinaryW[0] Then MsgBox(16, "Error", "Could not convert public key from DER Base64 to binary (step 2).", 0) Return $ERROR_AU3_DLLCALL_FAILED EndIf ; X509_PUBLIC_KEY_INFO = 8 $array_CryptDecodeObjectEx = DllCall("crypt32.dll", "bool", "CryptDecodeObjectEx", "dword", BitOR($X509_ASN_ENCODING, $PKCS_7_ASN_ENCODING), "ptr", Ptr(8), _ "ptr", DllStructGetPtr($dllstruct_CryptStringToBinaryWResultado), "dword", DllStructGetSize($dllstruct_CryptStringToBinaryWResultado), "dword", 0, "ptr", 0, "ptr*", 0, "dword*", 0) ps. I would strongly suggest you follow the variable naming convention found here, it would make your script much more easy to follow, especially when asking for help. I am not a big fan of very long variable name, but it might just be me... Edited March 10 by Nine “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Debug Messages Monitor UDF Screen Scraping Round Corner GUI UDF Multi-Threading Made Easy Interface Object based on Tag
CityGame Posted March 10 Author Posted March 10 Thanks to you. Your messages and improvements prompted me to read the docs about that functions a bit more, so the problems were solved. Just in case anyone was curious: the problem that I said yesterday that "step 5" of the original code was failing as if the signature was not valid, was solved today too. It seems the function CryptVerifySignatureW requires the data to verify using little-endian byte order, so the byte order of all the data has to be swapped before calling that function (why that function requires to swap byte order is beyond me anyway), but the program works. Thanks for pointing that improvement about the $tPubliKeyDERBinary structure. Now I understand, I was extracting the data from the structure on the previous step, only to put it in an exactly equal structure I'll try to follow the variable name convention when posting here in the future. Using that long variable names and the different type naming is something I prefer (it's easier to me to understand what a variable was used for if the name is more "explanatory", I dislike it when a variable or function name it's a really short abbreviation that I may not remember or understand once enough time has passed since the code was written), but it's just a "fixation" of me on my code. I'll try to convert it to the standard name convention when posting again. Thanks Danyfirex and Nine 2
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now