mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-24 08:12:53 +01:00
Implement PEM writing for CNG implementation
This commit is contained in:
@@ -24,11 +24,13 @@
|
||||
#include "../platform/Platform2.h"
|
||||
#include "IStream.hpp"
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
// CNG: Cryptography API: Next Generation (CNG)
|
||||
// available in Windows Vista onwards.
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
@@ -244,6 +246,45 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DerWriter
|
||||
{
|
||||
private:
|
||||
std::vector<uint8_t> _buffer;
|
||||
|
||||
public:
|
||||
void WriteSequenceHeader()
|
||||
{
|
||||
_buffer.push_back(0x30);
|
||||
_buffer.push_back(0x81);
|
||||
_buffer.push_back(0x89);
|
||||
}
|
||||
|
||||
void WriteInteger(const std::vector<uint8_t>& data)
|
||||
{
|
||||
if (data.size() < 128)
|
||||
{
|
||||
_buffer.push_back((uint8_t)data.size());
|
||||
}
|
||||
else if (data.size() <= std::numeric_limits<uint8_t>().max())
|
||||
{
|
||||
_buffer.push_back(0b10000001);
|
||||
_buffer.push_back((uint8_t)data.size());
|
||||
}
|
||||
else if (data.size() <= std::numeric_limits<uint16_t>().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<uint8_t>&& 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<uint8_t> 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<uint8_t>& 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<uint8_t> 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<uint8_t> 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<uint8_t>(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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -56,10 +56,10 @@ namespace File
|
||||
std::vector<uint8> 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);
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace File
|
||||
bool Delete(const std::string &path);
|
||||
bool Move(const std::string &srcPath, const std::string &dstPath);
|
||||
std::vector<uint8> 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<std::string> ReadAllLines(const std::string &path);
|
||||
uint64 GetLastModified(const std::string &path);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user