OpenRCT2/src/openrct2/network/NetworkKey.cpp

450 lines
12 KiB
C++
Raw Normal View History

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
2016-05-05 21:46:13 +02:00
/*****************************************************************************
* 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
2016-05-20 11:51:31 +02:00
#ifndef DISABLE_NETWORK
2017-02-08 13:53:00 +01:00
#include <vector>
2016-05-05 21:46:13 +02:00
#include <openssl/evp.h>
#include <openssl/pem.h>
2017-02-08 13:53:00 +01:00
#include <openssl/rsa.h>
#include "../core/IStream.hpp"
2017-12-12 14:52:57 +01:00
#include "../Diagnostic.h"
2017-02-08 13:53:00 +01:00
#include "NetworkKey.h"
2016-05-05 21:46:13 +02:00
#define KEY_TYPE EVP_PKEY_RSA
2017-01-04 22:17:08 +01:00
constexpr sint32 KEY_LENGTH_BITS = 2048;
2016-05-25 10:18:26 +02:00
2016-05-05 21:46:13 +02:00
NetworkKey::NetworkKey()
{
2016-05-25 10:18:26 +02:00
_ctx = EVP_PKEY_CTX_new_id(KEY_TYPE, nullptr);
if (_ctx == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Failed to create OpenSSL context");
}
}
NetworkKey::~NetworkKey()
{
Unload();
2016-05-25 10:18:26 +02:00
if (_ctx != nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-25 10:18:26 +02:00
EVP_PKEY_CTX_free(_ctx);
_ctx = nullptr;
2016-05-05 21:46:13 +02:00
}
}
void NetworkKey::Unload()
{
2016-05-25 10:18:26 +02:00
if (_key != nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-25 10:18:26 +02:00
EVP_PKEY_free(_key);
_key = nullptr;
2016-05-05 21:46:13 +02:00
}
}
bool NetworkKey::Generate()
{
2016-05-25 10:18:26 +02:00
if (_ctx == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Invalid OpenSSL context");
return false;
}
#if KEY_TYPE == EVP_PKEY_RSA
2016-05-25 10:18:26 +02:00
if (!EVP_PKEY_CTX_set_rsa_keygen_bits(_ctx, KEY_LENGTH_BITS))
{
2016-05-05 21:46:13 +02:00
log_error("Failed to set keygen params");
return false;
}
#else
#error Only RSA is supported!
#endif
2016-05-25 10:18:26 +02:00
if (EVP_PKEY_keygen_init(_ctx) <= 0)
2016-05-21 00:00:23 +02:00
{
2016-05-19 10:23:42 +02:00
log_error("Failed to initialise keygen algorithm");
return false;
}
2016-05-25 10:18:26 +02:00
if (EVP_PKEY_keygen(_ctx, &_key) <= 0)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Failed to generate new key!");
return false;
2016-05-21 00:00:23 +02:00
}
else
{
2016-05-25 10:18:26 +02:00
log_verbose("Key successfully generated");
2016-05-05 21:46:13 +02:00
}
log_verbose("New key of type %d, length %d generated successfully.", KEY_TYPE, KEY_LENGTH_BITS);
return true;
}
2017-02-08 13:53:00 +01:00
bool NetworkKey::LoadPrivate(IStream * stream)
2016-05-05 21:46:13 +02:00
{
2017-02-08 13:53:00 +01:00
Guard::ArgumentNotNull(stream);
size_t size = (size_t)stream->GetLength();
2016-05-25 10:18:26 +02:00
if (size == (size_t)-1)
{
2016-05-05 21:46:13 +02:00
log_error("unknown size, refusing to load key");
return false;
2016-05-21 00:00:23 +02:00
}
else if (size > 4 * 1024 * 1024)
{
2016-05-05 21:46:13 +02:00
log_error("Key file suspiciously large, refusing to load it");
return false;
}
2016-05-21 00:00:23 +02:00
char * priv_key = new char[size];
2017-02-08 13:53:00 +01:00
stream->Read(priv_key, size);
2017-01-04 22:17:08 +01:00
BIO * bio = BIO_new_mem_buf(priv_key, (sint32)size);
2016-05-21 00:00:23 +02:00
if (bio == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to initialise OpenSSL's BIO!");
2016-05-19 10:23:42 +02:00
delete [] priv_key;
2016-05-05 21:46:13 +02:00
return false;
}
2016-05-21 00:00:23 +02:00
RSA * rsa;
2016-05-19 10:23:42 +02:00
rsa = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr);
if (rsa == nullptr || !RSA_check_key(rsa))
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Loaded RSA key is invalid");
BIO_free_all(bio);
2016-05-19 10:23:42 +02:00
delete [] priv_key;
2016-05-05 21:46:13 +02:00
return false;
}
2016-05-25 10:18:26 +02:00
if (_key != nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-25 10:18:26 +02:00
EVP_PKEY_free(_key);
2016-05-05 21:46:13 +02:00
}
2016-05-25 10:18:26 +02:00
_key = EVP_PKEY_new();
EVP_PKEY_set1_RSA(_key, rsa);
2016-05-05 21:46:13 +02:00
BIO_free_all(bio);
RSA_free(rsa);
2016-05-19 10:23:42 +02:00
delete [] priv_key;
2016-05-05 21:46:13 +02:00
return true;
}
2017-02-08 13:53:00 +01:00
bool NetworkKey::LoadPublic(IStream * stream)
2016-05-05 21:46:13 +02:00
{
2017-02-08 13:53:00 +01:00
Guard::ArgumentNotNull(stream);
size_t size = (size_t)stream->GetLength();
2016-05-21 00:05:11 +02:00
if (size == (size_t)-1)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("unknown size, refusing to load key");
return false;
2016-05-21 00:00:23 +02:00
}
else if (size > 4 * 1024 * 1024)
{
2016-05-05 21:46:13 +02:00
log_error("Key file suspiciously large, refusing to load it");
return false;
}
2016-05-21 00:00:23 +02:00
char * pub_key = new char[size];
2017-02-08 13:53:00 +01:00
stream->Read(pub_key, size);
2017-01-04 22:17:08 +01:00
BIO * bio = BIO_new_mem_buf(pub_key, (sint32)size);
2016-05-25 10:18:26 +02:00
if (bio == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to initialise OpenSSL's BIO!");
2016-05-19 10:23:42 +02:00
delete [] pub_key;
2016-05-05 21:46:13 +02:00
return false;
}
2016-05-21 00:00:23 +02:00
RSA * rsa;
2016-05-19 10:23:42 +02:00
rsa = PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr);
2016-05-25 10:18:26 +02:00
if (_key != nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-25 10:18:26 +02:00
EVP_PKEY_free(_key);
2016-05-05 21:46:13 +02:00
}
2016-05-25 10:18:26 +02:00
_key = EVP_PKEY_new();
EVP_PKEY_set1_RSA(_key, rsa);
2016-05-05 21:46:13 +02:00
BIO_free_all(bio);
RSA_free(rsa);
2016-05-19 10:23:42 +02:00
delete [] pub_key;
2016-05-05 21:46:13 +02:00
return true;
}
2017-02-08 13:53:00 +01:00
bool NetworkKey::SavePrivate(IStream * stream)
2016-05-05 21:46:13 +02:00
{
2016-05-25 10:18:26 +02:00
if (_key == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("No key loaded");
return false;
}
#if KEY_TYPE == EVP_PKEY_RSA
2016-05-25 10:18:26 +02:00
RSA * rsa = EVP_PKEY_get1_RSA(_key);
2016-05-21 00:00:23 +02:00
if (rsa == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to get RSA key handle!");
return false;
}
2016-05-25 10:18:26 +02:00
if (!RSA_check_key(rsa))
{
2016-05-05 21:46:13 +02:00
log_error("Loaded RSA key is invalid");
return false;
}
2016-05-21 00:00:23 +02:00
BIO * bio = BIO_new(BIO_s_mem());
if (bio == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to initialise OpenSSL's BIO!");
return false;
}
2017-01-04 22:17:08 +01:00
sint32 result = PEM_write_bio_RSAPrivateKey(bio, rsa, nullptr, nullptr, 0, nullptr, nullptr);
2016-05-21 00:00:23 +02:00
if (result != 1)
{
2016-05-05 21:46:13 +02:00
log_error("failed to write private key!");
BIO_free_all(bio);
return false;
}
RSA_free(rsa);
2017-01-04 22:17:08 +01:00
sint32 keylen = BIO_pending(bio);
2016-05-21 00:00:23 +02:00
char * pem_key = new char[keylen];
2016-05-05 21:46:13 +02:00
BIO_read(bio, pem_key, keylen);
2017-02-08 13:53:00 +01:00
stream->Write(pem_key, keylen);
2016-05-19 10:23:42 +02:00
log_verbose("saving key of length %u", keylen);
2016-05-05 21:46:13 +02:00
BIO_free_all(bio);
delete [] pem_key;
#else
#error Only RSA is supported!
#endif
return true;
}
2017-02-08 13:53:00 +01:00
bool NetworkKey::SavePublic(IStream * stream)
2016-05-05 21:46:13 +02:00
{
2016-05-25 10:18:26 +02:00
if (_key == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("No key loaded");
return false;
}
2016-05-25 10:18:26 +02:00
RSA * rsa = EVP_PKEY_get1_RSA(_key);
2016-05-21 00:00:23 +02:00
if (rsa == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to get RSA key handle!");
return false;
}
2016-05-21 00:00:23 +02:00
BIO * bio = BIO_new(BIO_s_mem());
if (bio == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to initialise OpenSSL's BIO!");
return false;
}
2017-01-04 22:17:08 +01:00
sint32 result = PEM_write_bio_RSAPublicKey(bio, rsa);
2016-05-21 00:00:23 +02:00
if (result != 1)
{
2016-05-05 21:46:13 +02:00
log_error("failed to write private key!");
BIO_free_all(bio);
return false;
}
RSA_free(rsa);
2017-01-04 22:17:08 +01:00
sint32 keylen = BIO_pending(bio);
2016-05-21 00:00:23 +02:00
char * pem_key = new char[keylen];
2016-05-05 21:46:13 +02:00
BIO_read(bio, pem_key, keylen);
2017-02-08 13:53:00 +01:00
stream->Write(pem_key, keylen);
2016-05-05 21:46:13 +02:00
BIO_free_all(bio);
delete [] pem_key;
return true;
}
2016-05-19 10:23:42 +02:00
std::string NetworkKey::PublicKeyString()
2016-05-05 21:46:13 +02:00
{
2016-05-25 10:18:26 +02:00
if (_key == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("No key loaded");
return nullptr;
}
2016-05-25 10:18:26 +02:00
RSA * rsa = EVP_PKEY_get1_RSA(_key);
if (rsa == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to get RSA key handle!");
return nullptr;
}
2016-05-21 00:00:23 +02:00
BIO * bio = BIO_new(BIO_s_mem());
if (bio == nullptr)
{
2016-05-05 21:46:13 +02:00
log_error("Failed to initialise OpenSSL's BIO!");
return nullptr;
}
2017-01-04 22:17:08 +01:00
sint32 result = PEM_write_bio_RSAPublicKey(bio, rsa);
2016-05-21 00:00:23 +02:00
if (result != 1)
{
2016-05-05 21:46:13 +02:00
log_error("failed to write private key!");
BIO_free_all(bio);
return nullptr;
}
RSA_free(rsa);
2017-01-04 22:17:08 +01:00
sint32 keylen = BIO_pending(bio);
2016-05-21 00:00:23 +02:00
char * pem_key = new char[keylen + 1];
2016-05-05 21:46:13 +02:00
BIO_read(bio, pem_key, keylen);
BIO_free_all(bio);
pem_key[keylen] = '\0';
2016-05-19 10:23:42 +02:00
std::string pem_key_out(pem_key);
2016-05-21 00:00:23 +02:00
delete [] pem_key;
2016-05-19 10:23:42 +02:00
return pem_key_out;
}
2016-05-05 21:46:13 +02:00
2016-05-19 10:23:42 +02:00
/**
* @brief NetworkKey::PublicKeyHash
* Computes a short, human-readable (e.g. asciif-ied hex) hash for a given
* public key. Serves a purpose of easy identification keys in multiplayer
* overview, multiplayer settings.
*
* In particular, any of digest functions applied to a standardised key
2016-05-19 10:23:42 +02:00
* representation, like PEM, will be sufficient.
*
* @return returns a string containing key hash.
*/
std::string NetworkKey::PublicKeyHash()
{
std::string key = PublicKeyString();
2016-05-25 10:18:26 +02:00
if (key.empty())
{
2016-05-19 10:23:42 +02:00
log_error("No key found");
return nullptr;
}
2016-05-21 00:00:23 +02:00
EVP_MD_CTX * ctx = EVP_MD_CTX_create();
if (EVP_DigestInit_ex(ctx, EVP_sha1(), nullptr) <= 0)
{
2016-05-19 10:23:42 +02:00
log_error("Failed to initialise digest context");
EVP_MD_CTX_destroy(ctx);
return nullptr;
}
2016-05-21 00:00:23 +02:00
if (EVP_DigestUpdate(ctx, key.c_str(), key.size()) <= 0)
{
2016-05-19 10:23:42 +02:00
log_error("Failed to update digset");
EVP_MD_CTX_destroy(ctx);
return nullptr;
}
2017-01-04 22:17:08 +01:00
uint32 digest_size = EVP_MAX_MD_SIZE;
std::vector<uint8> digest(EVP_MAX_MD_SIZE);
2016-05-19 10:23:42 +02:00
// Cleans up `ctx` automatically.
EVP_DigestFinal(ctx, digest.data(), &digest_size);
std::string digest_out;
digest_out.reserve(EVP_MAX_MD_SIZE * 2 + 1);
2017-01-04 22:17:08 +01:00
for (uint32 i = 0; i < digest_size; i++)
2016-05-21 00:00:23 +02:00
{
2016-05-19 10:23:42 +02:00
char buf[3];
snprintf(buf, 3, "%02x", digest[i]);
2016-05-19 10:23:42 +02:00
digest_out.append(buf);
}
return digest_out;
2016-05-05 21:46:13 +02:00
}
bool NetworkKey::Sign(const uint8 * md, const size_t len, char ** signature, size_t * out_size)
2016-05-05 21:46:13 +02:00
{
2016-05-21 00:00:23 +02:00
EVP_MD_CTX * mdctx = nullptr;
2016-05-05 21:46:13 +02:00
*signature = nullptr;
/* Create the Message Digest Context */
2017-08-15 10:07:44 +02:00
if ((mdctx = EVP_MD_CTX_create()) == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Failed to create MD context");
return false;
}
/* Initialise the DigestSign operation - SHA-256 has been selected as the message digest function in this example */
2016-05-25 10:18:26 +02:00
if (1 != EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, _key))
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Failed to init digest sign");
EVP_MD_CTX_destroy(mdctx);
return false;
}
/* Call update with the message */
2016-05-21 00:00:23 +02:00
if (1 != EVP_DigestSignUpdate(mdctx, md, len))
{
2016-05-05 21:46:13 +02:00
log_error("Failed to goto update digest");
EVP_MD_CTX_destroy(mdctx);
return false;
}
/* Finalise the DigestSign operation */
2016-05-25 10:18:26 +02:00
/* First call EVP_DigestSignFinal with a nullptr sig parameter to obtain the length of the
2016-05-05 21:46:13 +02:00
* signature. Length is returned in slen */
2016-05-25 10:18:26 +02:00
if (1 != EVP_DigestSignFinal(mdctx, nullptr, out_size))
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("failed to finalise signature");
EVP_MD_CTX_destroy(mdctx);
return false;
}
2017-01-04 22:17:08 +01:00
uint8 * sig;
2016-05-05 21:46:13 +02:00
/* Allocate memory for the signature based on size in slen */
2017-08-15 10:07:44 +02:00
if ((sig = (unsigned char*)malloc((sint32)(sizeof(unsigned char) * (*out_size)))) == nullptr)
2016-05-21 00:00:23 +02:00
{
log_error("Failed to crypto-allocate space for signature");
2016-05-05 21:46:13 +02:00
EVP_MD_CTX_destroy(mdctx);
return false;
}
/* Obtain the signature */
if (1 != EVP_DigestSignFinal(mdctx, sig, out_size)) {
log_error("Failed to finalise signature");
EVP_MD_CTX_destroy(mdctx);
free(sig);
2016-05-05 21:46:13 +02:00
return false;
}
*signature = new char[*out_size];
memcpy(*signature, sig, *out_size);
free(sig);
2016-05-05 21:46:13 +02:00
EVP_MD_CTX_destroy(mdctx);
return true;
}
bool NetworkKey::Verify(const uint8 * md, const size_t len, const char * sig, const size_t siglen)
2016-05-05 21:46:13 +02:00
{
2016-05-25 10:18:26 +02:00
EVP_MD_CTX * mdctx = nullptr;
2016-05-05 21:46:13 +02:00
/* Create the Message Digest Context */
2017-08-15 10:07:44 +02:00
if ((mdctx = EVP_MD_CTX_create()) == nullptr)
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
log_error("Failed to create MD context");
return false;
}
2016-05-25 10:18:26 +02:00
if (1 != EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, _key))
2016-05-21 00:00:23 +02:00
{
log_error("Failed to initialise verification routine");
2016-05-05 21:46:13 +02:00
EVP_MD_CTX_destroy(mdctx);
return false;
}
/* Initialize `key` with a public key */
2016-05-21 00:00:23 +02:00
if (1 != EVP_DigestVerifyUpdate(mdctx, md, len))
{
2016-05-05 21:46:13 +02:00
log_error("Failed to update verification");
EVP_MD_CTX_destroy(mdctx);
return false;
}
2017-01-04 22:17:08 +01:00
if (1 == EVP_DigestVerifyFinal(mdctx, (uint8 *)sig, siglen))
2016-05-21 00:00:23 +02:00
{
2016-05-05 21:46:13 +02:00
EVP_MD_CTX_destroy(mdctx);
log_verbose("Successfully verified signature");
2016-05-05 21:46:13 +02:00
return true;
2016-05-21 00:00:23 +02:00
}
else
{
2016-05-05 21:46:13 +02:00
EVP_MD_CTX_destroy(mdctx);
log_error("Signature is invalid");
2016-05-19 10:23:42 +02:00
return false;
2016-05-05 21:46:13 +02:00
}
}
2016-05-20 11:51:31 +02:00
#endif // DISABLE_NETWORK