From c8ef6233aaa3d9f87e523fe233eb5ae77bc94116 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 1 Feb 2020 20:46:18 +0000 Subject: [PATCH 1/7] Add CNG implementation of crypto functions --- openrct2.common.props | 8 +- src/openrct2/core/Crypt.CNG.cpp | 575 ++++++++++++++++++++++++++++ src/openrct2/core/Crypt.OpenSSL.cpp | 2 +- 3 files changed, 580 insertions(+), 5 deletions(-) create mode 100644 src/openrct2/core/Crypt.CNG.cpp diff --git a/openrct2.common.props b/openrct2.common.props index 1cccd581e7..d11b4e02f2 100644 --- a/openrct2.common.props +++ b/openrct2.common.props @@ -53,7 +53,7 @@ C4549: 'operator': operator before comma has no effect; did you intend 'operator'? C4555: expression has no effect; expected expression with side-effect --> - __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) + __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;__USE_CNG__;%(PreprocessorDefinitions) MultiThreaded MultiThreadedDLL true @@ -61,7 +61,7 @@ /utf-8 /std:c++17 /permissive- /Zc:externConstexpr - wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;%(AdditionalDependencies) + wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;ncrypt.lib;%(AdditionalDependencies) /OPT:NOLBR /ignore:4099 %(AdditionalOptions) @@ -76,7 +76,7 @@ DebugFull - benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libcrypto.lib;libcurl-d.lib;libpng16d.lib;libspeexdsp.lib;libssl.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) + benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libcurl-d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) @@ -94,7 +94,7 @@ DebugFull true true - benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libcrypto.lib;libcurl.lib;libpng16.lib;libspeexdsp.lib;libssl.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) + benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libcurl.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp new file mode 100644 index 0000000000..6002a8dc58 --- /dev/null +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -0,0 +1,575 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 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. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#ifdef __USE_CNG__ + +#include "Crypt.h" +#include "../platform/Platform2.h" +#include "IStream.hpp" +#include +#include +#include +#include + +// clang-format off +// CNG: Cryptography API: Next Generation (CNG) +// available in Windows Vista onwards. +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +// clang-format on + +using namespace Crypt; + +static void CngThrowOnBadStatus(const std::string_view& name, NTSTATUS status) +{ + if (!NT_SUCCESS(status)) + { + throw std::runtime_error(std::string(name) + " failed: " + std::to_string(status)); + } +} + +static void ThrowBadAllocOnNull(const void * ptr) +{ + if (ptr == nullptr) + { + throw std::bad_alloc(); + } +} + +template +class CngHashAlgorithm final : public TBase +{ +private: + const wchar_t * _algName; + BCRYPT_ALG_HANDLE _hAlg{}; + BCRYPT_HASH_HANDLE _hHash{}; + PBYTE _pbHashObject{}; + bool _reusable{}; + +public: + CngHashAlgorithm(const wchar_t * algName) + { + // BCRYPT_HASH_REUSABLE_FLAG only available from Windows 8 + _algName = algName; + _reusable = Platform::IsOSVersionAtLeast(6, 2, 0); + Initialise(); + } + + ~CngHashAlgorithm() + { + Dispose(); + } + + TBase * Clear() override + { + if (_reusable) + { + // Finishing the current digest clears the state ready for a new digest + Finish(); + } + else + { + Dispose(); + Initialise(); + } + return this; + } + + TBase * Update(const void * data, size_t dataLen) override + { + auto status = BCryptHashData(_hHash, (PBYTE)data, (ULONG)dataLen, 0); + CngThrowOnBadStatus("BCryptHashData", status); + return this; + } + + typename TBase::Result Finish() override + { + typename TBase::Result result; + auto status = BCryptFinishHash(_hHash, result.data(), (ULONG)result.size(), 0); + CngThrowOnBadStatus("BCryptFinishHash", status); + return result; + } + +private: + void Initialise() + { + auto flags = _reusable ? BCRYPT_HASH_REUSABLE_FLAG : 0; + auto status = BCryptOpenAlgorithmProvider(&_hAlg, _algName, nullptr, flags); + CngThrowOnBadStatus("BCryptOpenAlgorithmProvider", status); + + // Calculate the size of the buffer to hold the hash object + DWORD cbHashObject{}; + DWORD cbData{}; + status = BCryptGetProperty(_hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0); + CngThrowOnBadStatus("BCryptGetProperty", status); + + // Create a hash + _pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHashObject); + ThrowBadAllocOnNull(_pbHashObject); + status = BCryptCreateHash(_hAlg, &_hHash, _pbHashObject, cbHashObject, nullptr, 0, 0); + CngThrowOnBadStatus("BCryptCreateHash", status); + } + + void Dispose() + { + BCryptCloseAlgorithmProvider(_hAlg, 0); + BCryptDestroyHash(_hHash); + HeapFree(GetProcessHeap(), 0, _pbHashObject); + + _hAlg = {}; + _hHash = {}; + _pbHashObject = {}; + } +}; + +class DerReader +{ +private: + ivstream _stream; + + template + T Read(std::istream& stream) + { + T value; + stream.read((char*)&value, sizeof(T)); + return value; + } + + template + std::vector Read(std::istream& stream, size_t count) + { + std::vector values(count); + stream.read((char*)values.data(), sizeof(T) * count); + return values; + } + + int ReadTag(std::istream& stream) + { + auto a = Read(stream); + // auto tagClass = a >> 6; + // auto tagConstructed = ((a & 0x20) != 0); + auto tagNumber = a & 0x1F; + if (tagNumber == 0x1F) + { + throw std::runtime_error("Unsupported DER tag"); + } + return tagNumber; + } + + int ReadLength(std::istream& stream) + { + auto a = Read(stream); + auto len = a & 0x7F; + if (len == a) + { + return len; + } + if (len > 6) + { + throw std::runtime_error("Length over 48 bits not supported at this position"); + } + if (len == 0) + { + throw std::runtime_error("Unknown length"); + } + auto result = 0; + for (auto i = 0; i < len; i++) + { + result = (result << 8) + Read(stream); + } + return result; + } + +public: + DerReader(const std::vector& data) + : _stream(data) + { + } + + void ReadSequenceHeader() + { + auto a = Read(_stream); + if (a == 0x8130) + { + Read(_stream); + } + else if (a == 0x8230) + { + Read(_stream); + } + else + { + throw std::runtime_error("Invalid DER code"); + } + } + + std::vector ReadInteger() + { + auto t = ReadTag(_stream); + if (t != 2) + { + throw std::runtime_error("Expected INTEGER"); + } + auto len = ReadLength(_stream); + auto result = Read(_stream, len); + + auto v = result[0]; + auto neg = (v > 127); + auto pad = neg ? 255 : 0; + for (size_t i = 0; i < result.size(); i++) + { + if (result[i] != pad) + { + result.erase(result.begin(), result.begin() + i); + break; + } + } + return result; + } +}; + +class DerWriter +{ +private: + std::vector _buffer; + +public: + void WriteSequenceHeader() + { + _buffer.push_back(0x30); + _buffer.push_back(0x81); + _buffer.push_back(0x89); + } + + void WriteInteger(const std::vector& data) + { + if (data.size() < 128) + { + _buffer.push_back((uint8_t)data.size()); + } + else if (data.size() <= std::numeric_limits().max()) + { + _buffer.push_back(0b10000001); + _buffer.push_back((uint8_t)data.size()); + } + else if (data.size() <= std::numeric_limits().max()) + { + _buffer.push_back(0b10000010); + _buffer.push_back((data.size() >> 8) & 0xFF); + _buffer.push_back(data.size() & 0xFF); + } + _buffer.insert(_buffer.end(), data.begin(), data.end()); + } + + std::vector&& Complete() + { + return std::move(_buffer); + } +}; + +class CngRsaKey final : public RsaKey +{ +private: + struct RsaKeyParams + { + std::vector Modulus; + std::vector Exponent; + std::vector Prime1; + std::vector Prime2; + }; + +public: + NCRYPT_KEY_HANDLE GetKeyHandle() const { return _hKey; } + + ~CngRsaKey() + { + NCryptFreeObject(_hKey); + } + + void SetPrivate(const std::string_view& pem) override + { + auto der = ReadPEM(pem, SZ_PRIVATE_BEGIN_TOKEN, SZ_PRIVATE_END_TOKEN); + DerReader derReader(der); + RsaKeyParams params; + derReader.ReadSequenceHeader(); + derReader.ReadInteger(); + params.Modulus = derReader.ReadInteger(); + params.Exponent = derReader.ReadInteger(); + derReader.ReadInteger(); + params.Prime1 = derReader.ReadInteger(); + params.Prime2 = derReader.ReadInteger(); + _hKey = ImportKey(params); + } + + void SetPublic(const std::string_view& pem) override + { + auto der = ReadPEM(pem, SZ_PUBLIC_BEGIN_TOKEN, SZ_PUBLIC_END_TOKEN); + DerReader derReader(der); + RsaKeyParams params; + derReader.ReadSequenceHeader(); + params.Modulus = derReader.ReadInteger(); + params.Exponent = derReader.ReadInteger(); + _hKey = ImportKey(params); + } + + std::string GetPrivate() override + { + return ""; + } + + std::string GetPublic() override + { + auto params = ExportKey(true); + DerWriter derWriter; + derWriter.WriteSequenceHeader(); + derWriter.WriteInteger(params.Modulus); + derWriter.WriteInteger(params.Exponent); + auto derBytes = derWriter.Complete(); + auto b64 = EncodeBase64(derBytes); + + std::ostringstream sb; + sb << std::string(SZ_PUBLIC_BEGIN_TOKEN) << std::endl; + sb << b64 << std::endl; + sb << std::string(SZ_PUBLIC_END_TOKEN) << std::endl; + return sb.str(); + } + + void Generate() override + { + } + +private: + static constexpr std::string_view SZ_PUBLIC_BEGIN_TOKEN = "-----BEGIN RSA PUBLIC KEY-----"; + static constexpr std::string_view SZ_PUBLIC_END_TOKEN = "-----END RSA PUBLIC KEY-----"; + static constexpr std::string_view SZ_PRIVATE_BEGIN_TOKEN = "-----BEGIN RSA PRIVATE KEY-----"; + static constexpr std::string_view SZ_PRIVATE_END_TOKEN = "-----END RSA PRIVATE KEY-----"; + + NCRYPT_KEY_HANDLE _hKey{}; + + static std::vector ReadPEM(const std::string_view& pem, const std::string_view& beginToken, const std::string_view& endToken) + { + auto beginPos = pem.find(beginToken); + auto endPos = pem.find(endToken); + if (beginPos != std::string::npos && endPos != std::string::npos) + { + beginPos += beginToken.size(); + auto code = Trim(pem.substr(beginPos, endPos - beginPos)); + return DecodeBase64(code); + } + throw std::runtime_error("Invalid PEM file"); + } + + static std::string_view Trim(std::string_view input) + { + for (size_t i = 0; i < input.size(); i++) + { + if (input[i] >= '!') + { + input.remove_prefix(i); + break; + } + } + for (size_t i = input.size() - 1; i >= 0; i--) + { + if (input[i] >= '!') + { + input = input.substr(0, i + 1); + break; + } + } + return input; + } + + static std::string EncodeBase64(const std::vector& input) + { + DWORD chString; + if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &chString)) + { + throw std::runtime_error("CryptBinaryToStringA failed"); + } + std::string result(chString, 0); + if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, result.data(), &chString)) + { + throw std::runtime_error("CryptBinaryToStringA failed"); + } + return result; + } + + static std::vector DecodeBase64(const std::string_view& input) + { + DWORD cbBinary{}; + if (!CryptStringToBinaryA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64, NULL, &cbBinary, NULL, NULL)) + { + throw std::runtime_error("CryptStringToBinaryA failed"); + } + std::vector result(cbBinary); + if (!CryptStringToBinaryA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64, result.data(), &cbBinary, NULL, NULL)) + { + throw std::runtime_error("CryptStringToBinaryA failed"); + } + return result; + } + + static NCRYPT_KEY_HANDLE ImportKey(const RsaKeyParams& params) + { + bool isPublic = params.Prime1.size() == 0; + auto blobType = isPublic ? BCRYPT_RSAPUBLIC_BLOB : BCRYPT_RSAPRIVATE_BLOB; + + BCRYPT_RSAKEY_BLOB header{}; + header.Magic = isPublic ? BCRYPT_RSAPUBLIC_MAGIC : BCRYPT_RSAPRIVATE_MAGIC; + header.BitLength = (ULONG)(params.Modulus.size() * 8); + header.cbPublicExp = (ULONG)params.Exponent.size(); + header.cbModulus = (ULONG)params.Modulus.size(); + header.cbPrime1 = (ULONG)params.Prime1.size(); + header.cbPrime2 = (ULONG)params.Prime2.size(); + + std::vector blob; + blob.insert(blob.end(), (uint8_t*)&header, (uint8_t*)(&header + 1)); + blob.insert(blob.end(), params.Exponent.begin(), params.Exponent.end()); + blob.insert(blob.end(), params.Modulus.begin(), params.Modulus.end()); + blob.insert(blob.end(), params.Prime1.begin(), params.Prime1.end()); + blob.insert(blob.end(), params.Prime2.begin(), params.Prime2.end()); + + NCRYPT_PROV_HANDLE hProv{}; + NCRYPT_KEY_HANDLE hKey{}; + auto status = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0); + CngThrowOnBadStatus("NCryptOpenStorageProvider", status); + status = NCryptImportKey(hProv, NULL, blobType, NULL, &hKey, (PBYTE)blob.data(), (DWORD)blob.size(), 0); + NCryptFreeObject(hProv); + CngThrowOnBadStatus("NCryptImportKey", status); + return hKey; + } + + RsaKeyParams ExportKey(bool onlyPublic) + { + auto blobType = onlyPublic ? BCRYPT_RSAPUBLIC_BLOB : BCRYPT_RSAPRIVATE_BLOB; + + std::vector output; + NCRYPT_PROV_HANDLE hProv{}; + try + { + auto status = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0); + CngThrowOnBadStatus("NCryptOpenStorageProvider", status); + DWORD cbOutput{}; + status = NCryptExportKey(_hKey, NULL, blobType, NULL, NULL, 0, &cbOutput, 0); + CngThrowOnBadStatus("NCryptExportKey", status); + output = std::vector(cbOutput); + status = NCryptExportKey(_hKey, NULL, blobType, NULL, output.data(), cbOutput, &cbOutput, 0); + CngThrowOnBadStatus("NCryptExportKey", status); + NCryptFreeObject(hProv); + } + catch (const std::exception&) + { + NCryptFreeObject(hProv); + } + + RsaKeyParams params; + const auto& header = *((BCRYPT_RSAKEY_BLOB*)output.data()); + size_t i = sizeof(BCRYPT_RSAKEY_BLOB); + params.Modulus.insert(params.Modulus.end(), output.begin() + i, output.begin() + i + header.cbModulus); + i += header.cbModulus; + params.Exponent.insert(params.Exponent.end(), output.begin() + i, output.begin() + i + header.cbPublicExp); + return params; + } +}; + +class CngRsaAlgorithm final : public RsaAlgorithm +{ +public: + std::vector SignData(const RsaKey& key, const void * data, size_t dataLen) override + { + auto hKey = static_cast(key).GetKeyHandle(); + auto [cbHash, pbHash] = HashData(data, dataLen); + auto [cbSignature, pbSignature] = std::tuple(); + try + { + BCRYPT_PKCS1_PADDING_INFO paddingInfo{ BCRYPT_SHA256_ALGORITHM }; + auto status = NCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, NULL, 0, &cbSignature, BCRYPT_PAD_PKCS1); + CngThrowOnBadStatus("NCryptSignHash", status); + pbSignature = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbSignature); + ThrowBadAllocOnNull(pbSignature); + status = NCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, BCRYPT_PAD_PKCS1); + CngThrowOnBadStatus("NCryptSignHash", status); + + auto result = std::vector(pbSignature, pbSignature + cbSignature); + HeapFree(GetProcessHeap(), 0, pbSignature); + return result; + } + catch (std::exception&) + { + HeapFree(GetProcessHeap(), 0, pbHash); + HeapFree(GetProcessHeap(), 0, pbSignature); + throw; + } + } + + bool VerifyData(const RsaKey& key, const void * data, size_t dataLen, const void * sig, size_t sigLen) override + { + auto hKey = static_cast(key).GetKeyHandle(); + auto [cbHash, pbHash] = HashData(data, dataLen); + auto [cbSignature, pbSignature] = ToHeap(sig, sigLen); + + BCRYPT_PKCS1_PADDING_INFO paddingInfo { BCRYPT_SHA256_ALGORITHM }; + auto status = NCryptVerifySignature(hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, BCRYPT_PAD_PKCS1); + HeapFree(GetProcessHeap(), 0, pbSignature); + return status == ERROR_SUCCESS; + } + +private: + static std::tuple HashData(const void * data, size_t dataLen) + { + auto hash = Crypt::SHA256(data, dataLen); + return ToHeap(hash.data(), hash.size()); + } + + static std::tuple ToHeap(const void * data, size_t dataLen) + { + auto cbHash = (DWORD)dataLen; + auto pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, dataLen); + ThrowBadAllocOnNull(pbHash); + std::memcpy(pbHash, data, dataLen); + return std::make_tuple(cbHash, pbHash); + } +}; + +namespace Crypt +{ + std::unique_ptr CreateSHA1() + { + return std::make_unique>(BCRYPT_SHA1_ALGORITHM); + } + + std::unique_ptr CreateSHA256() + { + return std::make_unique>(BCRYPT_SHA256_ALGORITHM); + } + + std::unique_ptr CreateRSA() + { + return std::make_unique(); + } + + std::unique_ptr CreateRSAKey() + { + return std::make_unique(); + } +} + +#endif diff --git a/src/openrct2/core/Crypt.OpenSSL.cpp b/src/openrct2/core/Crypt.OpenSSL.cpp index ff950f9c1a..bf09363460 100644 --- a/src/openrct2/core/Crypt.OpenSSL.cpp +++ b/src/openrct2/core/Crypt.OpenSSL.cpp @@ -7,7 +7,7 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#ifndef DISABLE_NETWORK +#if defined(DISABLE_NETWORK) && !defined(__USE_CNG__) # include "Crypt.h" From e2a541bff415c39783bdb9af0d553a83fecb9c52 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 00:36:41 +0000 Subject: [PATCH 2/7] Get import / export of public key working --- src/openrct2/core/Crypt.CNG.cpp | 89 ++++++++++++++----- ...fb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey | 6 +- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 6002a8dc58..9763d5e8c5 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -230,17 +230,17 @@ public: auto len = ReadLength(_stream); auto result = Read(_stream, len); - auto v = result[0]; - auto neg = (v > 127); - auto pad = neg ? 255 : 0; - for (size_t i = 0; i < result.size(); i++) - { - if (result[i] != pad) - { - result.erase(result.begin(), result.begin() + i); - break; - } - } + // auto v = result[0]; + // auto neg = (v > 127); + // auto pad = neg ? 255 : 0; + // for (size_t i = 0; i < result.size(); i++) + // { + // if (result[i] != pad) + // { + // result.erase(result.begin(), result.begin() + i); + // break; + // } + // } return result; } }; @@ -260,6 +260,7 @@ public: void WriteInteger(const std::vector& data) { + _buffer.push_back(0x02); if (data.size() < 128) { _buffer.push_back((uint8_t)data.size()); @@ -293,6 +294,10 @@ private: std::vector Exponent; std::vector Prime1; std::vector Prime2; + std::vector Exponent1; + std::vector Exponent2; + std::vector Coefficient; + std::vector PrivateExponent; }; public: @@ -331,7 +336,26 @@ public: std::string GetPrivate() override { - return ""; + auto params = ExportKey(false); + DerWriter derWriter; + derWriter.WriteSequenceHeader(); + derWriter.WriteInteger({}); + derWriter.WriteInteger(params.Modulus); + derWriter.WriteInteger(params.Exponent); + derWriter.WriteInteger(params.PrivateExponent); + derWriter.WriteInteger(params.Prime1); + derWriter.WriteInteger(params.Prime2); + derWriter.WriteInteger(params.Exponent1); + derWriter.WriteInteger(params.Exponent2); + derWriter.WriteInteger(params.Coefficient); + auto derBytes = derWriter.Complete(); + auto b64 = EncodeBase64(derBytes); + + std::ostringstream sb; + sb << std::string(SZ_PRIVATE_BEGIN_TOKEN) << std::endl; + sb << b64; + sb << std::string(SZ_PRIVATE_END_TOKEN) << std::endl; + return sb.str(); } std::string GetPublic() override @@ -346,7 +370,7 @@ public: std::ostringstream sb; sb << std::string(SZ_PUBLIC_BEGIN_TOKEN) << std::endl; - sb << b64 << std::endl; + sb << b64; sb << std::string(SZ_PUBLIC_END_TOKEN) << std::endl; return sb.str(); } @@ -386,7 +410,7 @@ private: break; } } - for (size_t i = input.size() - 1; i >= 0; i--) + for (size_t i = input.size() - 1; i > 0; i--) { if (input[i] >= '!') { @@ -399,16 +423,21 @@ private: static std::string EncodeBase64(const std::vector& input) { - DWORD chString; - if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &chString)) + DWORD flags = CRYPT_STRING_BASE64 | CRYPT_STRING_NOCR; + DWORD chString{}; + if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), flags, NULL, &chString)) { throw std::runtime_error("CryptBinaryToStringA failed"); } std::string result(chString, 0); - if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, result.data(), &chString)) + if (!CryptBinaryToStringA(input.data(), (DWORD)input.size(), flags, result.data(), &chString)) { throw std::runtime_error("CryptBinaryToStringA failed"); } + + // CryptBinaryToStringA returns length that includes null terminator + result.resize(result.size() - 1); + return result; } @@ -478,16 +507,34 @@ private: catch (const std::exception&) { NCryptFreeObject(hProv); + throw; } + size_t offset{}; RsaKeyParams params; const auto& header = *((BCRYPT_RSAKEY_BLOB*)output.data()); - size_t i = sizeof(BCRYPT_RSAKEY_BLOB); - params.Modulus.insert(params.Modulus.end(), output.begin() + i, output.begin() + i + header.cbModulus); - i += header.cbModulus; - params.Exponent.insert(params.Exponent.end(), output.begin() + i, output.begin() + i + header.cbPublicExp); + ReadBytes(output, offset, sizeof(BCRYPT_RSAKEY_BLOB)); + params.Exponent = ReadBytes(output, offset, header.cbPublicExp); + params.Modulus = ReadBytes(output, offset, header.cbModulus); + params.Prime1 = ReadBytes(output, offset, header.cbPrime1); + params.Prime2 = ReadBytes(output, offset, header.cbPrime2); + if (!onlyPublic) + { + params.Exponent1 = ReadBytes(output, offset, header.cbPrime1); + params.Exponent2 = ReadBytes(output, offset, header.cbPrime2); + params.Coefficient = ReadBytes(output, offset, header.cbPrime1); + params.PrivateExponent = ReadBytes(output, offset, header.cbModulus); + } return params; } + + static std::vector ReadBytes(std::vector& src, size_t& offset, size_t length) + { + std::vector result; + result.insert(result.end(), src.begin() + offset, src.begin() + offset + length); + offset += length; + return result; + } }; class CngRsaAlgorithm final : public RsaAlgorithm diff --git a/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey b/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey index 06a9004fac..3f9e4296aa 100644 --- a/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey +++ b/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey @@ -1,5 +1,5 @@ -----BEGIN RSA PUBLIC KEY----- -MIGJAoGBAORGPJHpScRUYrjSu8Y5SCQW1UOefRUgQLDemcYD/DrMFBWnLYTMQmyW -QaJSt5zlacQucbfDV+tdxbQBO9eE1S+wxRVnSJpa40R9Ye7YTRsGUhwRyB0MwRBx -sxXKksWNDjsh3UujqW+Tq2Hhz4ohRr3K5fEkMS8Cgzs2TmiNgj1zAgMBAAE= +MIGJAoGA5EY8kelJxFRiuNK7xjlIJBbVQ559FSBAsN6ZxgP8OswUFacthMxCbJZB +olK3nOVpxC5xt8NX613FtAE714TVL7DFFWdImlrjRH1h7thNGwZSHBHIHQzBEHGz +FcqSxY0OOyHdS6Opb5OrYeHPiiFGvcrl8SQxLwKDOzZOaI2CPXMCAwEAAQ== -----END RSA PUBLIC KEY----- From 57a758b9c0506bd7d87e590e4c82bf4394a92888 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 02:09:19 +0000 Subject: [PATCH 3/7] Get all crypto tests passing --- src/openrct2/core/Crypt.CNG.cpp | 384 +++++++++++------- ...fb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey | 6 +- 2 files changed, 233 insertions(+), 157 deletions(-) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 9763d5e8c5..339aff0da3 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -1,28 +1,29 @@ -#pragma region Copyright (c) 2018 OpenRCT2 Developers +#pragma region Copyright(c) 2018 OpenRCT2 Developers /***************************************************************************** -* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. -* -* OpenRCT2 is the work of many authors, a full list can be found in contributors.md -* For more information, visit https://github.com/OpenRCT2/OpenRCT2 -* -* OpenRCT2 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. -* -* A full copy of the GNU General Public License can be found in licence.txt -*****************************************************************************/ + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ #pragma endregion #ifdef __USE_CNG__ -#include "Crypt.h" -#include "../platform/Platform2.h" -#include "IStream.hpp" -#include -#include -#include -#include +# include "../platform/Platform2.h" +# include "Crypt.h" +# include "IStream.hpp" + +# include +# include +# include +# include // clang-format off // CNG: Cryptography API: Next Generation (CNG) @@ -45,7 +46,7 @@ static void CngThrowOnBadStatus(const std::string_view& name, NTSTATUS status) } } -static void ThrowBadAllocOnNull(const void * ptr) +static void ThrowBadAllocOnNull(const void* ptr) { if (ptr == nullptr) { @@ -53,18 +54,17 @@ static void ThrowBadAllocOnNull(const void * ptr) } } -template -class CngHashAlgorithm final : public TBase +template class CngHashAlgorithm final : public TBase { private: - const wchar_t * _algName; + const wchar_t* _algName; BCRYPT_ALG_HANDLE _hAlg{}; BCRYPT_HASH_HANDLE _hHash{}; PBYTE _pbHashObject{}; bool _reusable{}; public: - CngHashAlgorithm(const wchar_t * algName) + CngHashAlgorithm(const wchar_t* algName) { // BCRYPT_HASH_REUSABLE_FLAG only available from Windows 8 _algName = algName; @@ -77,7 +77,7 @@ public: Dispose(); } - TBase * Clear() override + TBase* Clear() override { if (_reusable) { @@ -92,7 +92,7 @@ public: return this; } - TBase * Update(const void * data, size_t dataLen) override + TBase* Update(const void* data, size_t dataLen) override { auto status = BCryptHashData(_hHash, (PBYTE)data, (ULONG)dataLen, 0); CngThrowOnBadStatus("BCryptHashData", status); @@ -144,16 +144,14 @@ class DerReader private: ivstream _stream; - template - T Read(std::istream& stream) + template T Read(std::istream& stream) { T value; stream.read((char*)&value, sizeof(T)); return value; } - template - std::vector Read(std::istream& stream, size_t count) + template std::vector Read(std::istream& stream, size_t count) { std::vector values(count); stream.read((char*)values.data(), sizeof(T) * count); @@ -230,17 +228,17 @@ public: auto len = ReadLength(_stream); auto result = Read(_stream, len); - // auto v = result[0]; - // auto neg = (v > 127); - // auto pad = neg ? 255 : 0; - // for (size_t i = 0; i < result.size(); i++) - // { - // if (result[i] != pad) - // { - // result.erase(result.begin(), result.begin() + i); - // break; - // } - // } + auto v = result[0]; + auto neg = (v > 127); + auto pad = neg ? 255 : 0; + for (size_t i = 0; i < result.size(); i++) + { + if (result[i] != pad) + { + result.erase(result.begin(), result.begin() + i); + break; + } + } return result; } }; @@ -251,36 +249,55 @@ private: std::vector _buffer; public: - void WriteSequenceHeader() + void WriteSequenceHeader(size_t len) { _buffer.push_back(0x30); - _buffer.push_back(0x81); - _buffer.push_back(0x89); + WriteCompressedNumber(len); } void WriteInteger(const std::vector& data) { _buffer.push_back(0x02); - if (data.size() < 128) + + size_t dataLen = data.size(); + if (dataLen > 0 && data[0] > 127) { - _buffer.push_back((uint8_t)data.size()); + // Prepend a zero to number so it isn't treated as negative + WriteCompressedNumber(dataLen + 1); + _buffer.push_back(0); + _buffer.insert(_buffer.end(), data.begin(), data.end()); } - else if (data.size() <= std::numeric_limits().max()) + else + { + WriteCompressedNumber(dataLen); + _buffer.insert(_buffer.end(), data.begin(), data.end()); + } + } + + void WriteCompressedNumber(uint64_t value) + { + if (value < 128) + { + _buffer.push_back((uint8_t)value); + } + else if (value <= std::numeric_limits().max()) { _buffer.push_back(0b10000001); - _buffer.push_back((uint8_t)data.size()); + _buffer.push_back((uint8_t)value); } - else if (data.size() <= std::numeric_limits().max()) + else if (value <= std::numeric_limits().max()) { _buffer.push_back(0b10000010); - _buffer.push_back((data.size() >> 8) & 0xFF); - _buffer.push_back(data.size() & 0xFF); + _buffer.push_back((value >> 8) & 0xFF); + _buffer.push_back(value & 0xFF); } - _buffer.insert(_buffer.end(), data.begin(), data.end()); } std::vector&& Complete() { + auto oldBuffer = std::move(_buffer); + WriteSequenceHeader(oldBuffer.size()); + _buffer.insert(_buffer.end(), oldBuffer.begin(), oldBuffer.end()); return std::move(_buffer); } }; @@ -298,14 +315,108 @@ private: std::vector Exponent2; std::vector Coefficient; std::vector PrivateExponent; + + ULONG GetMagic() const + { + ULONG magic = BCRYPT_RSAPUBLIC_MAGIC; + if (!Prime1.empty() || !Prime2.empty()) + magic = BCRYPT_RSAPRIVATE_MAGIC; + if (!Exponent1.empty()) + magic = BCRYPT_RSAFULLPRIVATE_MAGIC; + return magic; + } + + size_t GetTotalSize() + { + return Modulus.size() + Exponent.size() + Prime1.size() + Prime2.size() + Exponent1.size() + Exponent2.size() + + Coefficient.size() + PrivateExponent.size(); + } + + static RsaKeyParams FromBlob(const std::vector& blob) + { + RsaKeyParams result; + const auto& header = *((BCRYPT_RSAKEY_BLOB*)blob.data()); + size_t offset = sizeof(BCRYPT_RSAKEY_BLOB); + result.Exponent = ReadBytes(blob, offset, header.cbPublicExp); + result.Modulus = ReadBytes(blob, offset, header.cbModulus); + if (header.Magic == BCRYPT_RSAPRIVATE_MAGIC || header.Magic == BCRYPT_RSAFULLPRIVATE_MAGIC) + { + result.Prime1 = ReadBytes(blob, offset, header.cbPrime1); + result.Prime2 = ReadBytes(blob, offset, header.cbPrime2); + } + if (header.Magic == BCRYPT_RSAFULLPRIVATE_MAGIC) + { + result.Exponent1 = ReadBytes(blob, offset, header.cbPrime1); + result.Exponent2 = ReadBytes(blob, offset, header.cbPrime2); + result.Coefficient = ReadBytes(blob, offset, header.cbPrime1); + result.PrivateExponent = ReadBytes(blob, offset, header.cbModulus); + } + return result; + } + + std::vector ToBlob() const + { + auto magic = GetMagic(); + std::vector blob(sizeof(BCRYPT_RSAKEY_BLOB)); + auto& header = *((BCRYPT_RSAKEY_BLOB*)blob.data()); + header.Magic = magic; + header.BitLength = (ULONG)(Modulus.size() * 8); + header.cbPublicExp = (ULONG)Exponent.size(); + header.cbModulus = (ULONG)Modulus.size(); + header.cbPrime1 = (ULONG)Prime1.size(); + header.cbPrime2 = (ULONG)Prime2.size(); + + WriteBytes(blob, Exponent); + WriteBytes(blob, Modulus); + if (magic == BCRYPT_RSAPRIVATE_MAGIC || magic == BCRYPT_RSAFULLPRIVATE_MAGIC) + { + WriteBytes(blob, Prime1); + WriteBytes(blob, Prime2); + } + if (magic == BCRYPT_RSAFULLPRIVATE_MAGIC) + { + WriteBytes(blob, Exponent1); + WriteBytes(blob, Exponent2); + WriteBytes(blob, Coefficient); + WriteBytes(blob, PrivateExponent); + } + return blob; + } + + private: + static std::vector ReadBytes(const std::vector& src, size_t& offset, size_t length) + { + std::vector result; + result.insert(result.end(), src.begin() + offset, src.begin() + offset + length); + offset += length; + return result; + } + + static void WriteBytes(std::vector& dst, const std::vector& src) + { + dst.insert(dst.end(), src.begin(), src.end()); + } }; public: - NCRYPT_KEY_HANDLE GetKeyHandle() const { return _hKey; } + BCRYPT_KEY_HANDLE GetKeyHandle() const + { + return _hKey; + } + + CngRsaKey() + { + auto status = BCryptOpenAlgorithmProvider(&_hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0); + CngThrowOnBadStatus("BCryptOpenAlgorithmProvider", status); + } ~CngRsaKey() { - NCryptFreeObject(_hKey); + BCryptDestroyKey(_hKey); + BCryptCloseAlgorithmProvider(_hAlg, 0); + + _hKey = {}; + _hAlg = {}; } void SetPrivate(const std::string_view& pem) override @@ -317,10 +428,13 @@ public: derReader.ReadInteger(); params.Modulus = derReader.ReadInteger(); params.Exponent = derReader.ReadInteger(); - derReader.ReadInteger(); + params.PrivateExponent = derReader.ReadInteger(); params.Prime1 = derReader.ReadInteger(); params.Prime2 = derReader.ReadInteger(); - _hKey = ImportKey(params); + params.Exponent1 = derReader.ReadInteger(); + params.Exponent2 = derReader.ReadInteger(); + params.Coefficient = derReader.ReadInteger(); + ImportKey(params); } void SetPublic(const std::string_view& pem) override @@ -331,15 +445,14 @@ public: derReader.ReadSequenceHeader(); params.Modulus = derReader.ReadInteger(); params.Exponent = derReader.ReadInteger(); - _hKey = ImportKey(params); + ImportKey(params); } std::string GetPrivate() override { - auto params = ExportKey(false); + auto params = ExportKey(); DerWriter derWriter; - derWriter.WriteSequenceHeader(); - derWriter.WriteInteger({}); + derWriter.WriteInteger({ 0 }); derWriter.WriteInteger(params.Modulus); derWriter.WriteInteger(params.Exponent); derWriter.WriteInteger(params.PrivateExponent); @@ -360,9 +473,8 @@ public: std::string GetPublic() override { - auto params = ExportKey(true); + auto params = ExportKey(); DerWriter derWriter; - derWriter.WriteSequenceHeader(); derWriter.WriteInteger(params.Modulus); derWriter.WriteInteger(params.Exponent); auto derBytes = derWriter.Complete(); @@ -377,6 +489,20 @@ public: void Generate() override { + Reset(); + try + { + auto status = BCryptGenerateKeyPair(_hAlg, &_hKey, 1024, 0); + CngThrowOnBadStatus("BCryptGenerateKeyPair", status); + status = BCryptFinalizeKeyPair(_hKey, 0); + CngThrowOnBadStatus("BCryptFinalizeKeyPair", status); + _keyBlobType = BCRYPT_RSAFULLPRIVATE_BLOB; + } + catch (const std::exception&) + { + Reset(); + throw; + } } private: @@ -385,9 +511,38 @@ private: static constexpr std::string_view SZ_PRIVATE_BEGIN_TOKEN = "-----BEGIN RSA PRIVATE KEY-----"; static constexpr std::string_view SZ_PRIVATE_END_TOKEN = "-----END RSA PRIVATE KEY-----"; - NCRYPT_KEY_HANDLE _hKey{}; + BCRYPT_KEY_HANDLE _hKey{}; + BCRYPT_KEY_HANDLE _hAlg{}; + LPCWSTR _keyBlobType{}; - static std::vector ReadPEM(const std::string_view& pem, const std::string_view& beginToken, const std::string_view& endToken) + void Reset() + { + BCryptDestroyKey(_hKey); + _hKey = {}; + } + + void ImportKey(const RsaKeyParams& params) + { + Reset(); + auto blob = params.ToBlob(); + _keyBlobType = params.GetMagic() == BCRYPT_RSAFULLPRIVATE_MAGIC ? BCRYPT_RSAFULLPRIVATE_BLOB : BCRYPT_RSAPUBLIC_BLOB; + auto status = BCryptImportKeyPair(_hAlg, NULL, _keyBlobType, &_hKey, blob.data(), (ULONG)blob.size(), 0); + CngThrowOnBadStatus("BCryptImportKeyPair", status); + } + + RsaKeyParams ExportKey() + { + ULONG cbOutput{}; + auto status = BCryptExportKey(_hKey, NULL, _keyBlobType, NULL, 0, &cbOutput, 0); + CngThrowOnBadStatus("BCryptExportKey", status); + std::vector blob(cbOutput); + status = BCryptExportKey(_hKey, NULL, _keyBlobType, blob.data(), cbOutput, &cbOutput, 0); + CngThrowOnBadStatus("BCryptExportKey", status); + return RsaKeyParams::FromBlob(blob); + } + + static std::vector ReadPEM( + const std::string_view& pem, const std::string_view& beginToken, const std::string_view& endToken) { auto beginPos = pem.find(beginToken); auto endPos = pem.find(endToken); @@ -455,92 +610,12 @@ private: } return result; } - - static NCRYPT_KEY_HANDLE ImportKey(const RsaKeyParams& params) - { - bool isPublic = params.Prime1.size() == 0; - auto blobType = isPublic ? BCRYPT_RSAPUBLIC_BLOB : BCRYPT_RSAPRIVATE_BLOB; - - BCRYPT_RSAKEY_BLOB header{}; - header.Magic = isPublic ? BCRYPT_RSAPUBLIC_MAGIC : BCRYPT_RSAPRIVATE_MAGIC; - header.BitLength = (ULONG)(params.Modulus.size() * 8); - header.cbPublicExp = (ULONG)params.Exponent.size(); - header.cbModulus = (ULONG)params.Modulus.size(); - header.cbPrime1 = (ULONG)params.Prime1.size(); - header.cbPrime2 = (ULONG)params.Prime2.size(); - - std::vector blob; - blob.insert(blob.end(), (uint8_t*)&header, (uint8_t*)(&header + 1)); - blob.insert(blob.end(), params.Exponent.begin(), params.Exponent.end()); - blob.insert(blob.end(), params.Modulus.begin(), params.Modulus.end()); - blob.insert(blob.end(), params.Prime1.begin(), params.Prime1.end()); - blob.insert(blob.end(), params.Prime2.begin(), params.Prime2.end()); - - NCRYPT_PROV_HANDLE hProv{}; - NCRYPT_KEY_HANDLE hKey{}; - auto status = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0); - CngThrowOnBadStatus("NCryptOpenStorageProvider", status); - status = NCryptImportKey(hProv, NULL, blobType, NULL, &hKey, (PBYTE)blob.data(), (DWORD)blob.size(), 0); - NCryptFreeObject(hProv); - CngThrowOnBadStatus("NCryptImportKey", status); - return hKey; - } - - RsaKeyParams ExportKey(bool onlyPublic) - { - auto blobType = onlyPublic ? BCRYPT_RSAPUBLIC_BLOB : BCRYPT_RSAPRIVATE_BLOB; - - std::vector output; - NCRYPT_PROV_HANDLE hProv{}; - try - { - auto status = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0); - CngThrowOnBadStatus("NCryptOpenStorageProvider", status); - DWORD cbOutput{}; - status = NCryptExportKey(_hKey, NULL, blobType, NULL, NULL, 0, &cbOutput, 0); - CngThrowOnBadStatus("NCryptExportKey", status); - output = std::vector(cbOutput); - status = NCryptExportKey(_hKey, NULL, blobType, NULL, output.data(), cbOutput, &cbOutput, 0); - CngThrowOnBadStatus("NCryptExportKey", status); - NCryptFreeObject(hProv); - } - catch (const std::exception&) - { - NCryptFreeObject(hProv); - throw; - } - - size_t offset{}; - RsaKeyParams params; - const auto& header = *((BCRYPT_RSAKEY_BLOB*)output.data()); - ReadBytes(output, offset, sizeof(BCRYPT_RSAKEY_BLOB)); - params.Exponent = ReadBytes(output, offset, header.cbPublicExp); - params.Modulus = ReadBytes(output, offset, header.cbModulus); - params.Prime1 = ReadBytes(output, offset, header.cbPrime1); - params.Prime2 = ReadBytes(output, offset, header.cbPrime2); - if (!onlyPublic) - { - params.Exponent1 = ReadBytes(output, offset, header.cbPrime1); - params.Exponent2 = ReadBytes(output, offset, header.cbPrime2); - params.Coefficient = ReadBytes(output, offset, header.cbPrime1); - params.PrivateExponent = ReadBytes(output, offset, header.cbModulus); - } - return params; - } - - static std::vector ReadBytes(std::vector& src, size_t& offset, size_t length) - { - std::vector result; - result.insert(result.end(), src.begin() + offset, src.begin() + offset + length); - offset += length; - return result; - } }; class CngRsaAlgorithm final : public RsaAlgorithm { public: - std::vector SignData(const RsaKey& key, const void * data, size_t dataLen) override + std::vector SignData(const RsaKey& key, const void* data, size_t dataLen) override { auto hKey = static_cast(key).GetKeyHandle(); auto [cbHash, pbHash] = HashData(data, dataLen); @@ -548,11 +623,12 @@ public: try { BCRYPT_PKCS1_PADDING_INFO paddingInfo{ BCRYPT_SHA256_ALGORITHM }; - auto status = NCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, NULL, 0, &cbSignature, BCRYPT_PAD_PKCS1); + auto status = BCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, NULL, 0, &cbSignature, BCRYPT_PAD_PKCS1); CngThrowOnBadStatus("NCryptSignHash", status); pbSignature = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbSignature); ThrowBadAllocOnNull(pbSignature); - status = NCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, BCRYPT_PAD_PKCS1); + status = BCryptSignHash( + hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, BCRYPT_PAD_PKCS1); CngThrowOnBadStatus("NCryptSignHash", status); auto result = std::vector(pbSignature, pbSignature + cbSignature); @@ -567,26 +643,26 @@ public: } } - bool VerifyData(const RsaKey& key, const void * data, size_t dataLen, const void * sig, size_t sigLen) override + bool VerifyData(const RsaKey& key, const void* data, size_t dataLen, const void* sig, size_t sigLen) override { auto hKey = static_cast(key).GetKeyHandle(); auto [cbHash, pbHash] = HashData(data, dataLen); auto [cbSignature, pbSignature] = ToHeap(sig, sigLen); - BCRYPT_PKCS1_PADDING_INFO paddingInfo { BCRYPT_SHA256_ALGORITHM }; - auto status = NCryptVerifySignature(hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, BCRYPT_PAD_PKCS1); + BCRYPT_PKCS1_PADDING_INFO paddingInfo{ BCRYPT_SHA256_ALGORITHM }; + auto status = BCryptVerifySignature(hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, BCRYPT_PAD_PKCS1); HeapFree(GetProcessHeap(), 0, pbSignature); return status == ERROR_SUCCESS; } private: - static std::tuple HashData(const void * data, size_t dataLen) + static std::tuple HashData(const void* data, size_t dataLen) { auto hash = Crypt::SHA256(data, dataLen); return ToHeap(hash.data(), hash.size()); } - static std::tuple ToHeap(const void * data, size_t dataLen) + static std::tuple ToHeap(const void* data, size_t dataLen) { auto cbHash = (DWORD)dataLen; auto pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, dataLen); @@ -617,6 +693,6 @@ namespace Crypt { return std::make_unique(); } -} +} // namespace Crypt #endif diff --git a/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey b/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey index 3f9e4296aa..06a9004fac 100644 --- a/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey +++ b/test/tests/testdata/keys/Player-56f4afb74622a23bd2539ee701fe1b2c13d7e6ba.pubkey @@ -1,5 +1,5 @@ -----BEGIN RSA PUBLIC KEY----- -MIGJAoGA5EY8kelJxFRiuNK7xjlIJBbVQ559FSBAsN6ZxgP8OswUFacthMxCbJZB -olK3nOVpxC5xt8NX613FtAE714TVL7DFFWdImlrjRH1h7thNGwZSHBHIHQzBEHGz -FcqSxY0OOyHdS6Opb5OrYeHPiiFGvcrl8SQxLwKDOzZOaI2CPXMCAwEAAQ== +MIGJAoGBAORGPJHpScRUYrjSu8Y5SCQW1UOefRUgQLDemcYD/DrMFBWnLYTMQmyW +QaJSt5zlacQucbfDV+tdxbQBO9eE1S+wxRVnSJpa40R9Ye7YTRsGUhwRyB0MwRBx +sxXKksWNDjsh3UujqW+Tq2Hhz4ohRr3K5fEkMS8Cgzs2TmiNgj1zAgMBAAE= -----END RSA PUBLIC KEY----- From 0cc02f397e32579c4baa08b68e04c15edf3e865a Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 08:04:05 +0000 Subject: [PATCH 4/7] Fix guards on crypto implementations --- src/openrct2/core/Crypt.CNG.cpp | 17 +++++------------ src/openrct2/core/Crypt.OpenSSL.cpp | 4 ++-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 339aff0da3..72d9cdfe19 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -1,20 +1,13 @@ -#pragma region Copyright(c) 2018 OpenRCT2 Developers /***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * Copyright (c) 2014-2020 OpenRCT2 developers * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * - * OpenRCT2 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. - * - * A full copy of the GNU General Public License can be found in licence.txt + * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#pragma endregion -#ifdef __USE_CNG__ +#if !defined(DISABLE_NETWORK) && defined(_WIN32) # include "../platform/Platform2.h" # include "Crypt.h" diff --git a/src/openrct2/core/Crypt.OpenSSL.cpp b/src/openrct2/core/Crypt.OpenSSL.cpp index bf09363460..7fde32b916 100644 --- a/src/openrct2/core/Crypt.OpenSSL.cpp +++ b/src/openrct2/core/Crypt.OpenSSL.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers + * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -7,7 +7,7 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#if defined(DISABLE_NETWORK) && !defined(__USE_CNG__) +#if !defined(DISABLE_NETWORK) && !defined(_WIN32) # include "Crypt.h" From d6da7187171e5060ca9d3945e2ede4ccd82f49d8 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 21:15:39 +0000 Subject: [PATCH 5/7] Link to bcrypt instead of ncrypt --- openrct2.common.props | 4 ++-- src/openrct2/core/Crypt.CNG.cpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openrct2.common.props b/openrct2.common.props index d11b4e02f2..6c38bd49b4 100644 --- a/openrct2.common.props +++ b/openrct2.common.props @@ -53,7 +53,7 @@ C4549: 'operator': operator before comma has no effect; did you intend 'operator'? C4555: expression has no effect; expected expression with side-effect --> - __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;__USE_CNG__;%(PreprocessorDefinitions) + __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) MultiThreaded MultiThreadedDLL true @@ -61,7 +61,7 @@ /utf-8 /std:c++17 /permissive- /Zc:externConstexpr - wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;ncrypt.lib;%(AdditionalDependencies) + wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;bcrypt.lib;%(AdditionalDependencies) /OPT:NOLBR /ignore:4099 %(AdditionalOptions) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 72d9cdfe19..9803759d09 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) // clang-format on @@ -617,12 +616,12 @@ public: { BCRYPT_PKCS1_PADDING_INFO paddingInfo{ BCRYPT_SHA256_ALGORITHM }; auto status = BCryptSignHash(hKey, &paddingInfo, pbHash, cbHash, NULL, 0, &cbSignature, BCRYPT_PAD_PKCS1); - CngThrowOnBadStatus("NCryptSignHash", status); + CngThrowOnBadStatus("BCryptSignHash", status); pbSignature = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbSignature); ThrowBadAllocOnNull(pbSignature); status = BCryptSignHash( hKey, &paddingInfo, pbHash, cbHash, pbSignature, cbSignature, &cbSignature, BCRYPT_PAD_PKCS1); - CngThrowOnBadStatus("NCryptSignHash", status); + CngThrowOnBadStatus("BCryptSignHash", status); auto result = std::vector(pbSignature, pbSignature + cbSignature); HeapFree(GetProcessHeap(), 0, pbSignature); From d4025802014d0c30ab9f197f4f6f1daf1d3b3bfe Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 21:28:59 +0000 Subject: [PATCH 6/7] Update cmake config --- src/openrct2/CMakeLists.txt | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/openrct2/CMakeLists.txt b/src/openrct2/CMakeLists.txt index 1a47f528d3..5353df0d5f 100644 --- a/src/openrct2/CMakeLists.txt +++ b/src/openrct2/CMakeLists.txt @@ -25,19 +25,23 @@ if (NOT MINGW) endif() if (NOT DISABLE_NETWORK OR NOT DISABLE_HTTP) - if (APPLE) - # Needed for linking with non-broken OpenSSL on Apple platforms - set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl/lib/pkgconfig") - endif () - - find_package(OpenSSL 1.0.0 REQUIRED) - - target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR}) - - if(STATIC) - target_link_libraries(${PROJECT_NAME} ${SSL_STATIC_LIBRARIES}) + if (WIN32) + target_link_libraries(${PROJECT_NAME} bcrypt) else () - target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES}) + if (APPLE) + # Needed for linking with non-broken OpenSSL on Apple platforms + set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl/lib/pkgconfig") + endif () + + find_package(OpenSSL 1.0.0 REQUIRED) + + target_include_directories(${PROJECT_NAME} PUBLIC ${OPENSSL_INCLUDE_DIR}) + + if(STATIC) + target_link_libraries(${PROJECT_NAME} ${SSL_STATIC_LIBRARIES}) + else () + target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES}) + endif() endif() endif () From b76092eda39a6668b6949bc522693841c6434f0c Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 5 Feb 2020 22:39:22 +0000 Subject: [PATCH 7/7] Fix mingw --- src/openrct2/core/Crypt.CNG.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 9803759d09..6e1f2dcb37 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -498,10 +498,10 @@ public: } private: - static constexpr std::string_view SZ_PUBLIC_BEGIN_TOKEN = "-----BEGIN RSA PUBLIC KEY-----"; - static constexpr std::string_view SZ_PUBLIC_END_TOKEN = "-----END RSA PUBLIC KEY-----"; - static constexpr std::string_view SZ_PRIVATE_BEGIN_TOKEN = "-----BEGIN RSA PRIVATE KEY-----"; - static constexpr std::string_view SZ_PRIVATE_END_TOKEN = "-----END RSA PRIVATE KEY-----"; + static constexpr const char* SZ_PUBLIC_BEGIN_TOKEN = "-----BEGIN RSA PUBLIC KEY-----"; + static constexpr const char* SZ_PUBLIC_END_TOKEN = "-----END RSA PUBLIC KEY-----"; + static constexpr const char* SZ_PRIVATE_BEGIN_TOKEN = "-----BEGIN RSA PRIVATE KEY-----"; + static constexpr const char* SZ_PRIVATE_END_TOKEN = "-----END RSA PRIVATE KEY-----"; BCRYPT_KEY_HANDLE _hKey{}; BCRYPT_KEY_HANDLE _hAlg{};