diff --git a/UpdCheck/main.cpp b/UpdCheck/main.cpp index bcd7337..73b7c42 100644 --- a/UpdCheck/main.cpp +++ b/UpdCheck/main.cpp @@ -21,72 +21,6 @@ #pragma comment(lib, "Crypt32.lib") -/// -/// Reads package information from a stream -/// -/// \param[in] stream Input stream -/// \param[out] pkg Package information -/// -/// \returns The stream \p stream -/// -inline std::istream& operator >>(_In_ std::istream& stream, _Out_ Updater::package_info &pkg) -{ - // Read binary version. - stream.read((char*)&pkg.ver, sizeof(pkg.ver)); - if (!stream.good()) return stream; - - // Read human readable description (length prefixed). - unsigned __int32 count; - stream.read((char*)&count, sizeof(count)); - if (!stream.good()) return stream; - pkg.desc.resize(count); - stream.read((char*)pkg.desc.data(), sizeof(wchar_t)*count); - - // Read package language (length prefixed). - stream.read((char*)&count, sizeof(count)); - if (!stream.good()) return stream; - pkg.lang.resize(count); - stream.read((char*)pkg.lang.data(), sizeof(char)*count); - - // Read package download URL (length prefixed). - stream.read((char*)&count, sizeof(count)); - if (!stream.good()) return stream; - pkg.url.resize(count); - stream.read((char*)pkg.url.data(), sizeof(char)*count); - - return stream; -} - - -/// -/// Reads signature from a stream -/// -/// \param[in] stream Input stream -/// \param[out] sig Signature -/// -/// \returns The stream \p stream -/// -inline std::istream& operator >>(_In_ std::istream& stream, _Out_ Updater::signature &sig) -{ - // Read signature (length prefixed). - unsigned __int32 count; - stream.read((char*)&count, sizeof(count)); - if (!stream.good()) return stream; - - // Read, and reverse signature byte order (to be OpenSSL compatible). - std::vector sig_swap(count); - stream.read((char*)sig_swap.data(), sizeof(unsigned char)*count); - if (!stream.good()) return stream; - sig.resize(count); - for (unsigned __int32 i = 0, j = count - 1; i < count; i++, j--) - sig[i] = sig_swap[j]; - - return stream; -} - - - - /// /// Main function /// @@ -99,96 +33,91 @@ int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In if (!initializer) return -1; - // Open data file. - std::ifstream dat(_T("..\\..\\output\\test.bin"), std::ios_base::in | std::ios_base::binary); - if (!dat.good()) { + wxXmlDocument doc; + + // Load repository database. + wxHTTP http; + //wxDateTime timestamp_last_checked; + //http.SetHeader(wxS("If-Modified-Since"), timestamp_last_checked.Format(wxS("%a, %d %b %y %T %z"))); + if (!http.Connect(wxS(UPDATER_HTTP_SERVER), UPDATER_HTTP_PORT)) { + wxFAIL_MSG(wxT("Error resolving server name.")); + return 1; + } + wxInputStream *httpStream = http.GetInputStream(wxS(UPDATER_HTTP_PATH)); + if (!doc.Load(*httpStream, "UTF-8", wxXMLDOC_KEEP_WHITESPACE_NODES)) { wxFAIL_MSG(wxT("Error reading data file.")); return 1; } + wxDELETE(httpStream); + http.Close(); // Find the signature first. - if (stdex::idrec::find(dat, Updater::signature_rec::id)) { - // Signature found. Remember file position. - Updater::recordsize_t - sig_pos = (Updater::recordsize_t)dat.tellg(), // Position to return to to read the signature. - sig_start = sig_pos - sizeof(Updater::recordid_t); // Beginning of the signature block + wxXmlNode *document = doc.GetDocumentNode(); + std::vector sig; - // Create cryptographic context. - HCRYPTPROV cp = NULL; - wxVERIFY(::CryptAcquireContext(&cp, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)); + for (wxXmlNode *prolog = document->GetChildren(); prolog; prolog = prolog->GetNext()) { + if (prolog->GetType() == wxXML_COMMENT_NODE) { + wxString content = prolog->GetContent(); + size_t content_len = content.length(); + if (content_len >= _countof(wxS(UPDATER_SIGNATURE_MARK)) - 1 && + memcmp((const wxStringCharType*)content, wxS(UPDATER_SIGNATURE_MARK), sizeof(wxStringCharType)*(_countof(wxS(UPDATER_SIGNATURE_MARK)) - 1)) == 0) + { + size_t signature_len = content_len - (_countof(wxS(UPDATER_SIGNATURE_MARK)) - 1); + sig.resize(wxBase64DecodedSize(signature_len)); + size_t res = wxBase64Decode(sig.data(), sig.capacity(), content.Right(signature_len), wxBase64DecodeMode_SkipWS); + if (res != wxCONV_FAILED) + sig.resize(res); + else + sig.clear(); - // Hash the file up to signature block start. - HCRYPTHASH ch = NULL; - wxVERIFY(::CryptCreateHash(cp, CALG_SHA1, 0, 0, &ch)); - { - dat.seekg(0); - std::vector buf(8192); - for (Updater::recordsize_t data_left = sig_start; dat.good() && data_left;) { - dat.read((char*)buf.data(), std::min(buf.size(), data_left)); - Updater::recordsize_t count = dat.gcount(); - wxVERIFY(::CryptHashData(ch, buf.data(), count, 0)); - data_left -= count; + // Remove signature for check. + document->RemoveChild(prolog); + break; } } + } - // Read the signature. - dat.seekg(sig_pos); - Updater::signature sig; - dat >> Updater::signature_rec(sig); - if (dat.good()) { - // Import public key. - HCRYPTKEY ck = NULL; - { - HRSRC res = ::FindResource(NULL, MAKEINTRESOURCE(IDR_KEY_PUBLIC), RT_RCDATA); - wxASSERT_MSG(res, wxT("public key not found")); - HGLOBAL res_handle = ::LoadResource(NULL, res); - wxASSERT_MSG(res_handle, wxT("loading resource failed")); - - CERT_PUBLIC_KEY_INFO *keyinfo_data = NULL; - DWORD keyinfo_size = 0; - wxVERIFY(::CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, (const BYTE*)::LockResource(res_handle), ::SizeofResource(NULL, res), CRYPT_DECODE_ALLOC_FLAG, NULL, &keyinfo_data, &keyinfo_size)); - - wxVERIFY(::CryptImportPublicKeyInfo(cp, X509_ASN_ENCODING, keyinfo_data, &ck)); - ::LocalFree(keyinfo_data); - } - - // We have the hash, we have the signature, we have the public key. Now verify. - if (::CryptVerifySignature(ch, (BYTE*)sig.data(), sig.size(), ck, NULL, 0)) { - // The signature is correct. Now parse the file. - dat.seekg(0); - if (stdex::idrec::find(dat, UPDATER_DB_ID, sizeof(Updater::recordid_t))) { - Updater::recordsize_t size; - dat.read((char*)&size, sizeof(Updater::recordsize_t)); - if (dat.good()) { - if (size > sig_start) { - // Limit the size up to the file signature. Should have not get here. - wxFAIL_MSG(wxT("Updater file wrong record size.")); - size = sig_start; - } - if (stdex::idrec::find(dat, Updater::package_info_rec::id, size)) { - // Read package information. - Updater::package_info pi; - dat >> Updater::package_info_rec(pi); - if (dat.good()) { - - } else - wxFAIL_MSG(wxT("Error reading package information from Updater file.")); - } else - wxFAIL_MSG(wxT("Updater file has no package information.")); - } else - wxFAIL_MSG(wxT("Updater file read error.")); - } - } else - wxFAIL_MSG(wxT("Updater file signature does not match file content.")); - - wxVERIFY(::CryptDestroyKey(ck)); - } else - wxFAIL_MSG(wxT("Error reading signature from the Updater file.")); - - wxVERIFY(::CryptDestroyHash(ch)); - wxVERIFY(::CryptReleaseContext(cp, 0)); - } else + if (sig.empty()) wxFAIL_MSG(wxT("Signature not found in the Updater file.")); + // Reverse byte order, for consistent OpenSSL experience. + for (std::vector::size_type i = 0, j = sig.size() - 1; i < j; i++, j--) + std::swap(sig[i], sig[j]); + + // Create cryptographic context. + HCRYPTPROV cp = NULL; + wxVERIFY(::CryptAcquireContext(&cp, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)); + + // Hash the content. + HCRYPTHASH ch = NULL; + wxVERIFY(::CryptCreateHash(cp, CALG_SHA1, 0, 0, &ch)); + ::wxXmlHashNode(ch, document); + + // Import public key. + HCRYPTKEY ck = NULL; + { + HRSRC res = ::FindResource(NULL, MAKEINTRESOURCE(IDR_KEY_PUBLIC), RT_RCDATA); + wxASSERT_MSG(res, wxT("public key not found")); + HGLOBAL res_handle = ::LoadResource(NULL, res); + wxASSERT_MSG(res_handle, wxT("loading resource failed")); + + CERT_PUBLIC_KEY_INFO *keyinfo_data = NULL; + DWORD keyinfo_size = 0; + wxVERIFY(::CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, (const BYTE*)::LockResource(res_handle), ::SizeofResource(NULL, res), CRYPT_DECODE_ALLOC_FLAG, NULL, &keyinfo_data, &keyinfo_size)); + + wxVERIFY(::CryptImportPublicKeyInfo(cp, X509_ASN_ENCODING, keyinfo_data, &ck)); + ::LocalFree(keyinfo_data); + } + + // We have the hash, we have the signature, we have the public key. Now verify. + if (::CryptVerifySignature(ch, sig.data(), sig.size(), ck, NULL, 0)) { + // The signature is correct. Now parse the file. + } else + wxFAIL_MSG(wxT("Updater file signature does not match file content.")); + + wxVERIFY(::CryptDestroyKey(ck)); + wxVERIFY(::CryptDestroyHash(ch)); + wxVERIFY(::CryptReleaseContext(cp, 0)); + return 0; } diff --git a/UpdCheck/stdafx.h b/UpdCheck/stdafx.h index ad9ffcb..74ce2ba 100644 --- a/UpdCheck/stdafx.h +++ b/UpdCheck/stdafx.h @@ -19,13 +19,19 @@ #pragma once +#include "../../include/UpdaterCfg.h" + #include "UpdCheck.h" #include "../include/Updater.h" #include +#include +#include +#include +#include #include +#include -#include - -#include +#include +#include diff --git a/UpdMkDesc/main.cpp b/UpdMkDesc/main.cpp deleted file mode 100644 index 5066062..0000000 --- a/UpdMkDesc/main.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - Copyright 2016 Amebis - - This file is part of Updater. - - Updater is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Updater is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Updater. If not, see . -*/ - -#include "stdafx.h" -#pragma comment(lib, "Crypt32.lib") - - -/// -/// Writes package information to a stream -/// -/// \param[in] stream Output stream -/// \param[in] pkg Package information -/// -/// \returns The stream \p stream -/// -inline std::ostream& operator <<(_In_ std::ostream& stream, _In_ const Updater::package_info &pkg) -{ - // Write binary version. - if (stream.fail()) return stream; - stream.write((const char*)&pkg.ver, sizeof(pkg.ver)); - - // Write human readable description (length prefixed). - std::wstring::size_type char_count = pkg.desc.length(); -#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) - // 4G check - if (char_count > 0xffffffff) { - stream.setstate(std::ios_base::failbit); - return stream; - } -#endif - if (stream.fail()) return stream; - unsigned __int32 count = (unsigned __int32)char_count; - stream.write((const char*)&count, sizeof(count)); - if (stream.fail()) return stream; - stream.write((const char*)pkg.desc.c_str(), sizeof(wchar_t)*count); - - // Write package language (length prefixed) - char_count = pkg.lang.length(); -#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) - // 4G check - if (char_count > 0xffffffff) { - stream.setstate(std::ios_base::failbit); - return stream; - } -#endif - if (stream.fail()) return stream; - count = (unsigned __int32)char_count; - stream.write((const char*)&count, sizeof(count)); - if (stream.fail()) return stream; - stream.write(pkg.lang.c_str(), sizeof(char)*count); - - // Write package download URL (length prefixed) - char_count = pkg.url.length(); -#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) - // 4G check - if (char_count > 0xffffffff) { - stream.setstate(std::ios_base::failbit); - return stream; - } -#endif - if (stream.fail()) return stream; - count = (unsigned __int32)char_count; - stream.write((const char*)&count, sizeof(count)); - if (stream.fail()) return stream; - stream.write(pkg.url.c_str(), sizeof(char)*count); - - return stream; -} - - -/// -/// Writes signature to a stream -/// -/// \param[in] stream Output stream -/// \param[in] sig Signature -/// -/// \returns The stream \p stream -/// -inline std::ostream& operator <<(_In_ std::ostream& stream, _In_ const Updater::signature &sig) -{ - // Write signature (length prefixed). - Updater::signature::size_type sig_count = sig.size(); -#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) - // 4G check - if (sig_count > 0xffffffff) { - stream.setstate(std::ios_base::failbit); - return stream; - } -#endif - if (stream.fail()) return stream; - unsigned __int32 count = (unsigned __int32)sig_count; - stream.write((const char*)&count, sizeof(count)); - - // Reverse signature byte order (to make OpenSSL compatible), and write. - if (stream.fail()) return stream; - std::vector sig_swap(count); - for (unsigned __int32 i = 0, j = count - 1; i < count; i++, j--) - sig_swap[j] = sig[i]; - stream.write((const char*)sig_swap.data(), sizeof(unsigned char)*count); - - return stream; -} - - -/// -/// Main function -/// -int _tmain(int argc, _TCHAR *argv[]) -{ - wxApp::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "program"); - - // Inizialize wxWidgets. - wxInitializer initializer; - if (!initializer) { - _ftprintf(stderr, wxT("Failed to initialize the wxWidgets library, aborting.\n")); - return -1; - } - - // Parse command line. - static const wxCmdLineEntryDesc cmdLineDesc[] = - { - { wxCMD_LINE_SWITCH, "h" , "help" , _("Show this help message" ), wxCMD_LINE_VAL_NONE , wxCMD_LINE_OPTION_HELP }, - { wxCMD_LINE_OPTION, "x" , "ver-hex", _("Hexadecimal version of the product"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, - { wxCMD_LINE_OPTION, "s" , "ver-str", _("String version of the product" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, - { wxCMD_LINE_OPTION, "l" , "lang" , _("Package language" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, - { wxCMD_LINE_OPTION, "u" , "url" , _("Package download URL" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, - { wxCMD_LINE_PARAM , NULL, NULL , _("output file" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, - - { wxCMD_LINE_NONE } - }; - wxCmdLineParser parser(cmdLineDesc, argc, argv); - switch (parser.Parse()) { - case -1: - // Help was given, terminating. - return 0; - - case 0: - // everything is ok; proceed - break; - - default: - wxLogMessage(wxT("Syntax error detected, aborting.")); - return -1; - } - - const wxString& filenameOut = parser.GetParam(0); - std::fstream dst((LPCTSTR)filenameOut, std::ios_base::out | std::ios_base::in | std::ios_base::trunc | std::ios_base::binary); - if (dst.fail()) { - _ftprintf(stderr, wxT("%s: error UMD0001: Error opening output file.\n"), filenameOut.fn_str()); - return 1; - } - - bool has_errors = false; - - // Open file ID. - std::streamoff dst_start = stdex::idrec::open(dst, UPDATER_DB_ID); - - Updater::package_info pi; - wxString val; - - pi.ver = parser.Found(wxT("x"), &val) ? _tcstoul(val, NULL, 16) : 0; - - if (parser.Found(wxT("s"), &val)) - pi.desc = val; - - if (parser.Found(wxT("l"), &val)) - pi.lang = val.ToAscii(); - - if (parser.Found(wxT("u"), &val)) - pi.url = val.ToUTF8(); - - dst << Updater::package_info_rec(pi); - if (dst.fail()) { - _ftprintf(stderr, wxT("%s: error UMD0002: Writing to output file failed.\n"), filenameOut.fn_str()); - has_errors = true; - } - - stdex::idrec::close(dst, dst_start); - - // Create cryptographic context. - HCRYPTPROV cp = NULL; - wxVERIFY(::CryptAcquireContext(&cp, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)); - - // Import private key. - { - HRSRC res = ::FindResource(NULL, MAKEINTRESOURCE(IDR_KEY_PRIVATE), RT_RCDATA); - wxASSERT_MSG(res, wxT("private key not found")); - HGLOBAL res_handle = ::LoadResource(NULL, res); - wxASSERT_MSG(res_handle, wxT("loading resource failed")); - - PUBLICKEYSTRUC *key_data = NULL; - DWORD key_size = 0; - wxVERIFY(::CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (const BYTE*)::LockResource(res_handle), ::SizeofResource(NULL, res), CRYPT_DECODE_ALLOC_FLAG, NULL, &key_data, &key_size)); - // See: http://pumka.net/2009/12/16/rsa-encryption-cplusplus-delphi-cryptoapi-php-openssl-2/comment-page-1/#comment-429 - key_data->aiKeyAlg = CALG_RSA_SIGN; - - HCRYPTKEY ck = NULL; - wxVERIFY(::CryptImportKey(cp, (const BYTE*)key_data, key_size, NULL, 0, &ck)); - wxVERIFY(::CryptDestroyKey(ck)); - ::LocalFree(key_data); - } - - // Hash the file. - HCRYPTHASH ch = NULL; - wxVERIFY(::CryptCreateHash(cp, CALG_SHA1, 0, 0, &ch)); - { - std::vector buf(8192); - for (dst.seekg(0); !dst.eof();) { - dst.read((char*)buf.data(), buf.size()); - wxVERIFY(::CryptHashData(ch, buf.data(), dst.gcount(), 0)); - } - dst.clear(); - } - - // Sign the hash. - DWORD sig_size = 0; - if (!::CryptSignHash(ch, AT_SIGNATURE, NULL, 0, NULL, &sig_size)) { - _ftprintf(stderr, wxT("%s: error UMD0003: Signing output file failed (%i).\n"), filenameOut.fn_str(), ::GetLastError()); - has_errors = true; - } - Updater::signature sig(sig_size); - if (!::CryptSignHash(ch, AT_SIGNATURE, NULL, 0, sig.data(), &sig_size)) { - _ftprintf(stderr, wxT("%s: error UMD0003: Signing output file failed (%i).\n"), filenameOut.fn_str(), ::GetLastError()); - has_errors = true; - } - wxVERIFY(::CryptDestroyHash(ch)); - wxVERIFY(::CryptReleaseContext(cp, 0)); - - // Append to the end of the file. - dst << Updater::signature_rec(sig); - - if (has_errors) { - dst.close(); - wxRemoveFile(filenameOut); - return 1; - } else - return 0; -} diff --git a/UpdMkDesc/UpdMkDesc.h b/UpdSignXML/UpdSignXML.h similarity index 100% rename from UpdMkDesc/UpdMkDesc.h rename to UpdSignXML/UpdSignXML.h diff --git a/UpdMkDesc/UpdMkDesc.props b/UpdSignXML/UpdSignXML.props similarity index 100% rename from UpdMkDesc/UpdMkDesc.props rename to UpdSignXML/UpdSignXML.props diff --git a/UpdMkDesc/UpdMkDesc.rc b/UpdSignXML/UpdSignXML.rc similarity index 95% rename from UpdMkDesc/UpdMkDesc.rc rename to UpdSignXML/UpdSignXML.rc index fe544cd..7b7ad3c 100644 Binary files a/UpdMkDesc/UpdMkDesc.rc and b/UpdSignXML/UpdSignXML.rc differ diff --git a/UpdMkDesc/UpdMkDesc.vcxproj b/UpdSignXML/UpdSignXML.vcxproj similarity index 91% rename from UpdMkDesc/UpdMkDesc.vcxproj rename to UpdSignXML/UpdSignXML.vcxproj index 132dbed..b9e4619 100644 --- a/UpdMkDesc/UpdMkDesc.vcxproj +++ b/UpdSignXML/UpdSignXML.vcxproj @@ -19,8 +19,8 @@ - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB} - UpdMkDesc + {2A403460-7834-41B8-9823-199E8FE36FA8} + UpdSignXML @@ -56,25 +56,25 @@ - + - + - + - + @@ -88,7 +88,7 @@ - + @@ -96,7 +96,7 @@ - + diff --git a/UpdMkDesc/UpdMkDesc.vcxproj.filters b/UpdSignXML/UpdSignXML.vcxproj.filters similarity index 92% rename from UpdMkDesc/UpdMkDesc.vcxproj.filters rename to UpdSignXML/UpdSignXML.vcxproj.filters index de7c64b..218e55e 100644 --- a/UpdMkDesc/UpdMkDesc.vcxproj.filters +++ b/UpdSignXML/UpdSignXML.vcxproj.filters @@ -30,12 +30,12 @@ Header Files - + Header Files - + Resource Files diff --git a/UpdMkDesc/locale/.gitignore b/UpdSignXML/locale/.gitignore similarity index 100% rename from UpdMkDesc/locale/.gitignore rename to UpdSignXML/locale/.gitignore diff --git a/UpdMkDesc/locale/sl_SI.po b/UpdSignXML/locale/sl_SI.po similarity index 97% rename from UpdMkDesc/locale/sl_SI.po rename to UpdSignXML/locale/sl_SI.po index b18e162..068cdf8 100644 --- a/UpdMkDesc/locale/sl_SI.po +++ b/UpdSignXML/locale/sl_SI.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"Project-Id-Version: UpdMkDesc\n" +"Project-Id-Version: UpdSignXML\n" "POT-Creation-Date: 2016-03-15 20:09+0100\n" "PO-Revision-Date: 2016-03-15 20:10+0100\n" "Last-Translator: Simon Rozman \n" diff --git a/UpdSignXML/main.cpp b/UpdSignXML/main.cpp new file mode 100644 index 0000000..28d7e43 --- /dev/null +++ b/UpdSignXML/main.cpp @@ -0,0 +1,149 @@ +/* + Copyright 2016 Amebis + + This file is part of Updater. + + Updater is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Updater is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Updater. If not, see . +*/ + +#include "stdafx.h" +#pragma comment(lib, "Crypt32.lib") + + +/// +/// Main function +/// +int _tmain(int argc, _TCHAR *argv[]) +{ + wxApp::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, "program"); + + // Inizialize wxWidgets. + wxInitializer initializer; + if (!initializer) { + _ftprintf(stderr, wxT("Failed to initialize the wxWidgets library, aborting.\n")); + return -1; + } + + // Parse command line. + static const wxCmdLineEntryDesc cmdLineDesc[] = + { + { wxCMD_LINE_SWITCH, "h" , "help", _("Show this help message"), wxCMD_LINE_VAL_NONE , wxCMD_LINE_OPTION_HELP }, + { wxCMD_LINE_PARAM , NULL, NULL , _("input file" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, + { wxCMD_LINE_PARAM , NULL, NULL , _("output file" ), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY }, + + { wxCMD_LINE_NONE } + }; + wxCmdLineParser parser(cmdLineDesc, argc, argv); + switch (parser.Parse()) { + case -1: + // Help was given, terminating. + return 0; + + case 0: + // everything is ok; proceed + break; + + default: + wxLogMessage(wxT("Syntax error detected, aborting.")); + return -1; + } + + const wxString& filenameIn = parser.GetParam(0); + wxXmlDocument doc; + if (!doc.Load(filenameIn, "UTF-8", wxXMLDOC_KEEP_WHITESPACE_NODES)) { + _ftprintf(stderr, wxT("%s: error UMD0001: Error opening input file.\n"), filenameIn.fn_str()); + return 1; + } + + bool has_errors = false; + + wxXmlNode *document = doc.GetDocumentNode(); + + // Examine prologue if the document is already signed. + for (wxXmlNode *prolog = document->GetChildren(); prolog; prolog = prolog->GetNext()) { + if (prolog->GetType() == wxXML_COMMENT_NODE) { + wxString content = prolog->GetContent(); + if (content.length() >= _countof(wxS(UPDATER_SIGNATURE_MARK)) - 1 && + memcmp((const wxStringCharType*)content, wxS(UPDATER_SIGNATURE_MARK), sizeof(wxStringCharType)*(_countof(wxS(UPDATER_SIGNATURE_MARK)) - 1)) == 0) + { + // Previous signature found. Remove it. + document->RemoveChild(prolog); + } + } + } + + // Create cryptographic context. + HCRYPTPROV cp = NULL; + wxVERIFY(::CryptAcquireContext(&cp, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)); + + // Import private key. + { + HRSRC res = ::FindResource(NULL, MAKEINTRESOURCE(IDR_KEY_PRIVATE), RT_RCDATA); + wxASSERT_MSG(res, wxT("private key not found")); + HGLOBAL res_handle = ::LoadResource(NULL, res); + wxASSERT_MSG(res_handle, wxT("loading resource failed")); + + PUBLICKEYSTRUC *key_data = NULL; + DWORD key_size = 0; + wxVERIFY(::CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, (const BYTE*)::LockResource(res_handle), ::SizeofResource(NULL, res), CRYPT_DECODE_ALLOC_FLAG, NULL, &key_data, &key_size)); + // See: http://pumka.net/2009/12/16/rsa-encryption-cplusplus-delphi-cryptoapi-php-openssl-2/comment-page-1/#comment-429 + key_data->aiKeyAlg = CALG_RSA_SIGN; + + HCRYPTKEY ck = NULL; + wxVERIFY(::CryptImportKey(cp, (const BYTE*)key_data, key_size, NULL, 0, &ck)); + wxVERIFY(::CryptDestroyKey(ck)); + ::LocalFree(key_data); + } + + // Hash the content. + HCRYPTHASH ch = NULL; + wxVERIFY(::CryptCreateHash(cp, CALG_SHA1, 0, 0, &ch)); + ::wxXmlHashNode(ch, document); + + // Sign the hash. + DWORD sig_size = 0; + if (!::CryptSignHash(ch, AT_SIGNATURE, NULL, 0, NULL, &sig_size)) { + _ftprintf(stderr, wxT("%s: error UMD0003: Signing input file failed (%i).\n"), filenameIn.fn_str(), ::GetLastError()); + has_errors = true; + } + std::vector sig(sig_size); + if (!::CryptSignHash(ch, AT_SIGNATURE, NULL, 0, sig.data(), &sig_size)) { + _ftprintf(stderr, wxT("%s: error UMD0003: Signing input file failed (%i).\n"), filenameIn.fn_str(), ::GetLastError()); + has_errors = true; + } + wxVERIFY(::CryptDestroyHash(ch)); + wxVERIFY(::CryptReleaseContext(cp, 0)); + + // Reverse byte order, for consistent OpenSSL experience. + for (std::vector::size_type i = 0, j = sig.size() - 1; i < j; i++, j--) + std::swap(sig[i], sig[j]); + + // Encode signature (Base64) and append to the document prolog. + wxString signature; + signature += wxS(UPDATER_SIGNATURE_MARK); + signature += wxBase64Encode(sig.data(), sig.size()); + document->AddChild(new wxXmlNode(wxXML_COMMENT_NODE, wxS(""), signature)); + + const wxString& filenameOut = parser.GetParam(1); + if (!doc.Save(filenameOut, wxXML_NO_INDENTATION)) { + _ftprintf(stderr, wxT("%s: error UMD0001: Error writing output file.\n"), filenameOut.fn_str()); + has_errors = true; + } + + if (has_errors) { + wxRemoveFile(filenameOut); + return 1; + } else + return 0; +} diff --git a/UpdMkDesc/stdafx.cpp b/UpdSignXML/stdafx.cpp similarity index 100% rename from UpdMkDesc/stdafx.cpp rename to UpdSignXML/stdafx.cpp diff --git a/UpdMkDesc/stdafx.h b/UpdSignXML/stdafx.h similarity index 87% rename from UpdMkDesc/stdafx.h rename to UpdSignXML/stdafx.h index 4aba235..02d521e 100644 --- a/UpdMkDesc/stdafx.h +++ b/UpdSignXML/stdafx.h @@ -19,18 +19,16 @@ #pragma once -#include "UpdMkDesc.h" +#include "UpdSignXML.h" #include "../include/Updater.h" #include +#include #include +#include #include - -#include +#include #include -#include #include - -#include diff --git a/Updater.sln b/Updater.sln index a401120..ac9cc43 100644 --- a/Updater.sln +++ b/Updater.sln @@ -8,14 +8,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution include\Updater.h = include\Updater.h EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UpdMkDesc", "UpdMkDesc\UpdMkDesc.vcxproj", "{516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UpdCheck", "UpdCheck\UpdCheck.vcxproj", "{0B2B7A10-3FA0-46CD-AD89-6D02DA2C5FC3}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wxExtend", "..\lib\wxExtend\build\wxExtend.vcxproj", "{A3A36689-AC35-4026-93DA-A3BA0C0E767C}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stdex", "..\lib\stdex\build\stdex.vcxproj", "{518777CC-0A59-4415-A12A-82751ED75343}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UpdSignXML", "UpdSignXML\UpdSignXML.vcxproj", "{2A403460-7834-41B8-9823-199E8FE36FA8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -24,14 +24,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Debug|Win32.ActiveCfg = Debug|Win32 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Debug|Win32.Build.0 = Debug|Win32 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Debug|x64.ActiveCfg = Debug|x64 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Debug|x64.Build.0 = Debug|x64 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Release|Win32.ActiveCfg = Release|Win32 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Release|Win32.Build.0 = Release|Win32 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Release|x64.ActiveCfg = Release|x64 - {516AFFF6-F1E7-4806-B2E2-5CD9911ED2FB}.Release|x64.Build.0 = Release|x64 {0B2B7A10-3FA0-46CD-AD89-6D02DA2C5FC3}.Debug|Win32.ActiveCfg = Debug|Win32 {0B2B7A10-3FA0-46CD-AD89-6D02DA2C5FC3}.Debug|Win32.Build.0 = Debug|Win32 {0B2B7A10-3FA0-46CD-AD89-6D02DA2C5FC3}.Debug|x64.ActiveCfg = Debug|x64 @@ -56,6 +48,14 @@ Global {518777CC-0A59-4415-A12A-82751ED75343}.Release|Win32.Build.0 = Release|Win32 {518777CC-0A59-4415-A12A-82751ED75343}.Release|x64.ActiveCfg = Release|x64 {518777CC-0A59-4415-A12A-82751ED75343}.Release|x64.Build.0 = Release|x64 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Debug|Win32.Build.0 = Debug|Win32 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Debug|x64.ActiveCfg = Debug|x64 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Debug|x64.Build.0 = Debug|x64 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Release|Win32.ActiveCfg = Release|Win32 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Release|Win32.Build.0 = Release|Win32 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Release|x64.ActiveCfg = Release|x64 + {2A403460-7834-41B8-9823-199E8FE36FA8}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/include/Updater.h b/include/Updater.h index a6aabc6..79b8d76 100644 --- a/include/Updater.h +++ b/include/Updater.h @@ -39,58 +39,12 @@ // // Human readable product version and build year for UI // -#define UPDATER_VERSION_STR "2.0-alpha" +#define UPDATER_VERSION_STR "1.0" #define UPDATER_BUILD_YEAR_STR "2016" #if !defined(RC_INVOKED) && !defined(MIDL_PASS) -#include - -#include -#include - - -/// -/// Data records alignment -/// -#define UPDATER_RECORD_ALIGN 1 - - -/// -/// Database IDs -/// -#define UPDATER_DB_ID (*(Updater::recordid_t*)"UPD") - - -namespace Updater { - typedef unsigned __int32 recordid_t; - typedef unsigned __int32 recordsize_t; - - - /// - /// Package information - /// - struct package_info { - unsigned __int32 ver; ///< Binary Version - std::wstring desc; ///< Human Readable Description (i. e. "1.2") - std::string lang; ///< Package Language (ISO 639-1 language code followed by underscore and ISO 3166 country code) - std::string url; ///< Package download URL (UTF-8 encoded) - }; - - typedef stdex::idrec::record package_info_rec; - - - /// - /// Signature - /// - typedef std::vector signature; - - typedef stdex::idrec::record signature_rec; -}; - - -const Updater::recordid_t stdex::idrec::record::id = *(Updater::recordid_t*)"PKG"; -const Updater::recordid_t stdex::idrec::record::id = *(Updater::recordid_t*)"SGN"; +#define UPDATER_SIGNATURE_MARK "SHA1SIGN:" #endif // !defined(RC_INVOKED) && !defined(MIDL_PASS)