From b8d37548ed5f20ca2a7d9a2fe1b79a1d3731226f Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 26 May 2018 18:56:18 +0100 Subject: [PATCH] Implement PEM writing for CNG implementation --- src/openrct2/core/Crypt.CNG.cpp | 120 ++++++++++++++++++++++++++-- src/openrct2/core/Crypt.OpenSSL.cpp | 5 ++ src/openrct2/core/File.cpp | 13 ++- src/openrct2/core/File.h | 1 + test/tests/CryptTests.cpp | 9 +++ 5 files changed, 139 insertions(+), 9 deletions(-) diff --git a/src/openrct2/core/Crypt.CNG.cpp b/src/openrct2/core/Crypt.CNG.cpp index 3a2cf45423..d476d652e6 100644 --- a/src/openrct2/core/Crypt.CNG.cpp +++ b/src/openrct2/core/Crypt.CNG.cpp @@ -24,11 +24,13 @@ #include "../platform/Platform2.h" #include "IStream.hpp" #include +#include #include #include // CNG: Cryptography API: Next Generation (CNG) // available in Windows Vista onwards. +#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include @@ -244,6 +246,45 @@ public: } }; +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: @@ -258,6 +299,11 @@ private: 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); @@ -284,9 +330,27 @@ public: _hKey = ImportKey(params); } - std::string GetPrivate() override { return GetKey(true); } + std::string GetPrivate() override + { + return ""; + } - std::string GetPublic() override { return GetKey(false); } + 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(); + } private: static constexpr std::string_view SZ_PUBLIC_BEGIN_TOKEN = "-----BEGIN RSA PUBLIC KEY-----"; @@ -296,11 +360,6 @@ private: NCRYPT_KEY_HANDLE _hKey{}; - std::string GetKey(bool isPrivate) - { - throw std::runtime_error("Not implemented"); - } - static std::vector ReadPEM(const std::string_view& pem, const std::string_view& beginToken, const std::string_view& endToken) { auto beginPos = pem.find(beginToken); @@ -335,6 +394,21 @@ private: 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; @@ -379,6 +453,38 @@ private: 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(hProv, _hKey, blobType, NULL, NULL, 0, &cbOutput, 0); + CngThrowOnBadStatus("NCryptExportKey", status); + output = std::vector(cbOutput); + status = NCryptExportKey(hProv, _hKey, blobType, NULL, output.data(), cbOutput, NULL, 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 diff --git a/src/openrct2/core/Crypt.OpenSSL.cpp b/src/openrct2/core/Crypt.OpenSSL.cpp index 9561a3c393..4ab9c80162 100644 --- a/src/openrct2/core/Crypt.OpenSSL.cpp +++ b/src/openrct2/core/Crypt.OpenSSL.cpp @@ -112,6 +112,11 @@ class OpenSSLRsaKey final : public RsaKey public: EVP_PKEY * GetEvpKey() const { return _evpKey; } + ~OpenSSLRsaKey() + { + EVP_PKEY_free(_evpKey); + } + void SetPrivate(const std::string_view& pem) override { SetKey(pem, true); diff --git a/src/openrct2/core/File.cpp b/src/openrct2/core/File.cpp index 3822f7df7c..0ef70771ed 100644 --- a/src/openrct2/core/File.cpp +++ b/src/openrct2/core/File.cpp @@ -56,10 +56,10 @@ namespace File std::vector result; #if defined(_WIN32) && !defined(__MINGW32__) - auto pathW = String::ToUtf16(path.data()); + auto pathW = String::ToUtf16(std::string(path)); std::ifstream fs(pathW, std::ios::in | std::ios::binary); #else - std::ifstream fs(path.data(), std::ios::in | std::ios::binary); + std::ifstream fs(std::string(path), std::ios::in | std::ios::binary); #endif if (!fs.is_open()) { @@ -83,6 +83,15 @@ namespace File return result; } + std::string ReadAllText(const std::string_view& path) + { + auto bytes = ReadAllBytes(path); + // TODO skip BOM + std::string result(bytes.size(), 0); + std::copy(bytes.begin(), bytes.end(), result.begin()); + return result; + } + void WriteAllBytes(const std::string &path, const void * buffer, size_t length) { auto fs = FileStream(path, FILE_MODE_WRITE); diff --git a/src/openrct2/core/File.h b/src/openrct2/core/File.h index 40c0ad451d..c54962cbcb 100644 --- a/src/openrct2/core/File.h +++ b/src/openrct2/core/File.h @@ -28,6 +28,7 @@ namespace File bool Delete(const std::string &path); bool Move(const std::string &srcPath, const std::string &dstPath); std::vector ReadAllBytes(const std::string_view& path); + std::string ReadAllText(const std::string_view& path); void WriteAllBytes(const std::string &path, const void * buffer, size_t length); std::vector ReadAllLines(const std::string &path); uint64 GetLastModified(const std::string &path); diff --git a/test/tests/CryptTests.cpp b/test/tests/CryptTests.cpp index c541158bfd..f5b3318adb 100644 --- a/test/tests/CryptTests.cpp +++ b/test/tests/CryptTests.cpp @@ -130,3 +130,12 @@ TEST_F(CryptTests, RSA_VerifyWithPublic) bool verified = rsa->VerifyData(*publicKey, data.data(), data.size(), signature.data(), signature.size()); ASSERT_TRUE(verified); } + +TEST_F(CryptTests, RSAKey_GetPublic) +{ + auto inPem = File::ReadAllText("C:/Users/Ted/Documents/OpenRCT2/keys/Ted-b298a310905df8865788bdc864560c3d4c3ba562.pubkey"); + auto publicKey = Hash::CreateRSAKey(); + publicKey->SetPublic(inPem); + auto outPem = publicKey->GetPublic(); + ASSERT_EQ(inPem, outPem); +}