implement crypto.cc with OpenSSL + fix config loading
I considered reversing the original `vlong` implementation but OpenSSL is just simpler, more secure, and most importantly, is already there. I also made it so the key is now stored in a PEM file (`tibia.pem`) so it can be easily swapped without re-compiling. This was the last piece to build and run a working executable, except that we now need to implement a query manager for the server to communicate with. I was able to fix a small problem with loading the config before hitting the problem with the query manager, which was expected. The next task should be getting a query manager up and running before starting phase two of upgrading and fixing the server itself.
This commit is contained in:
parent
e782274ade
commit
e65dc561ac
6
Makefile
6
Makefile
@ -3,8 +3,8 @@ BUILDDIR = build
|
||||
OUTPUTEXE = game
|
||||
|
||||
CC = g++
|
||||
CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -std=c++11 -DOS_LINUX=1 -DARCH_X64=1
|
||||
LFLAGS = -Wl,-t
|
||||
CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -std=c++11 -pthread -DOS_LINUX=1 -DARCH_X64=1
|
||||
LFLAGS = -Wl,-t -lcrypto
|
||||
|
||||
DEBUG ?= 0
|
||||
ifneq ($(DEBUG), 0)
|
||||
@ -16,7 +16,7 @@ endif
|
||||
HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/communication.hh $(SRCDIR)/config.hh $(SRCDIR)/connections.hh $(SRCDIR)/containers.hh $(SRCDIR)/cr.hh $(SRCDIR)/crypto.hh $(SRCDIR)/enums.hh $(SRCDIR)/houses.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/moveuse.hh $(SRCDIR)/objects.hh $(SRCDIR)/operate.hh $(SRCDIR)/query.hh $(SRCDIR)/reader.hh $(SRCDIR)/script.hh $(SRCDIR)/threads.hh $(SRCDIR)/writer.hh
|
||||
|
||||
$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/communication.obj $(BUILDDIR)/config.obj $(BUILDDIR)/connections.obj $(BUILDDIR)/cract.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crnonpl.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/crypto.obj $(BUILDDIR)/houses.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/moveuse.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/operate.obj $(BUILDDIR)/query.obj $(BUILDDIR)/reader.obj $(BUILDDIR)/receiving.obj $(BUILDDIR)/script.obj $(BUILDDIR)/sending.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/threads.obj $(BUILDDIR)/time.obj $(BUILDDIR)/utils.obj $(BUILDDIR)/writer.obj
|
||||
$(CC) $(CFLAGS) $(LFLAGS) -o $@ $^
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS)
|
||||
|
||||
$(BUILDDIR)/communication.obj: $(SRCDIR)/communication.cc $(HEADERS)
|
||||
@mkdir -p $(@D)
|
||||
|
||||
3493
reference/game.c
3493
reference/game.c
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
|
||||
struct TDatabasePoolConnection {
|
||||
TDatabaseConnectionPool *DatabaseConnectionPool;
|
||||
TDatabaseConnection *DatabaseConnection;
|
||||
bool TransactionRunning;
|
||||
};
|
||||
|
||||
struct TDatabaseConnectionPool {
|
||||
int NumberOfConnections;
|
||||
struct TDatabaseSettings DatabaseSettings;
|
||||
struct TDatabaseConnection **DatabaseConnection;
|
||||
bool *DatabaseConnectionFree;
|
||||
struct Semaphore FreeDatabaseConnections;
|
||||
struct Semaphore DatabaseConnectionMutex;
|
||||
};
|
||||
|
||||
// TODO(fusion): Opaque struct?
|
||||
struct TDatabaseConnection {};
|
||||
|
||||
struct TPreparedQuery {
|
||||
int Number;
|
||||
char *Query;
|
||||
char *DBTypes;
|
||||
char *CTypes;
|
||||
char *Name;
|
||||
};
|
||||
|
||||
struct TNameLockOrderData {
|
||||
ulong GamemasterID;
|
||||
char GamemasterName[30];
|
||||
char CharacterName[30];
|
||||
bool OldNameVisible;
|
||||
};
|
||||
|
||||
struct TNotationOrderData {
|
||||
ulong GamemasterID;
|
||||
char GamemasterName[30];
|
||||
char CharacterName[30];
|
||||
char Comment[200];
|
||||
};
|
||||
@ -23,16 +23,6 @@
|
||||
#define MAX_COMMUNICATION_THREADS 1100
|
||||
#define COMMUNICATION_THREAD_STACK_SIZE ((int)KB(64))
|
||||
|
||||
static const char RSA_PRIME_P[] =
|
||||
"1201758001370723323398753778257470257713354828752713123415294815"
|
||||
"0506251412291888866940292054989907714155267326586216043845592229"
|
||||
"084368540020196135619327879";
|
||||
|
||||
static const char RSA_PRIME_Q[] =
|
||||
"1189892136861686835188050824611210139447876026576932541274639840"
|
||||
"5473436969889506919017477758618276066588858607419440134394668095"
|
||||
"105156501566867770737187273";
|
||||
|
||||
static int TERMINALVERSION[3] = {770, 770, 770};
|
||||
static int TCPSocket;
|
||||
static ThreadHandle AcceptorThread;
|
||||
@ -1533,7 +1523,9 @@ void InitCommunication(void){
|
||||
AcceptorThreadPID = 0;
|
||||
ActiveConnections = 0;
|
||||
QueryManagerConnectionPool.init();
|
||||
PrivateKey.init(RSA_PRIME_P, RSA_PRIME_Q);
|
||||
|
||||
// TODO(fusion): This is arbitrary, should probably be set in the config.
|
||||
PrivateKey.initFromFile("tibia.pem");
|
||||
|
||||
OpenSocket();
|
||||
if(TCPSocket == -1){
|
||||
|
||||
@ -106,6 +106,7 @@ void ReadConfig(void){
|
||||
MANAGER_DATABASE.Database[0] = 0;
|
||||
|
||||
char FileName[4096];
|
||||
#if 0
|
||||
if(const char *Home = getenv("home")){
|
||||
snprintf(FileName, sizeof(FileName), "%s/.tibia", Home);
|
||||
}else if(const char *Home = getenv("HOME")){
|
||||
@ -113,6 +114,9 @@ void ReadConfig(void){
|
||||
}else{
|
||||
snprintf(FileName, sizeof(FileName), ".tibia");
|
||||
}
|
||||
#else
|
||||
snprintf(FileName, sizeof(FileName), ".tibia");
|
||||
#endif
|
||||
|
||||
if(!FileExists(FileName)){
|
||||
throw "cannot find config-file";
|
||||
@ -130,7 +134,7 @@ void ReadConfig(void){
|
||||
}
|
||||
|
||||
char Identifier[MAX_IDENT_LENGTH];
|
||||
strcpy(Identifier, Script.readIdentifier());
|
||||
strcpy(Identifier, Script.getIdentifier());
|
||||
Script.readSymbol('=');
|
||||
|
||||
// TODO(fusion): Ughh... Get rid of all `strcpy`s. A malicious configuration
|
||||
|
||||
107
src/crypto.cc
107
src/crypto.cc
@ -1,3 +1,108 @@
|
||||
#include "crypto.hh"
|
||||
|
||||
// TODO
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
static void DumpOpenSSLErrors(const char *Where, const char *What){
|
||||
error("OpenSSL error(s) while executing %s at %s:\n", What, Where);
|
||||
ERR_print_errors_cb(
|
||||
[](const char *str, usize len, void *u) -> int {
|
||||
// NOTE(fusion): These error strings already have trailing newlines,
|
||||
// for whatever reason.
|
||||
error("> %s", str);
|
||||
return 1;
|
||||
}, NULL);
|
||||
}
|
||||
|
||||
// TRSAPrivateKey
|
||||
// =============================================================================
|
||||
TRSAPrivateKey::TRSAPrivateKey(void){
|
||||
m_RSA = NULL;
|
||||
}
|
||||
|
||||
TRSAPrivateKey::~TRSAPrivateKey(void){
|
||||
if(m_RSA){
|
||||
RSA_free(m_RSA);
|
||||
m_RSA = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void TRSAPrivateKey::initFromFile(const char *FileName){
|
||||
if(m_RSA != NULL){
|
||||
error("TRSAPrivateKey::init: Key already initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *File = fopen(FileName, "rb");
|
||||
if(File == NULL){
|
||||
error("TRSAPrivateKey::initFromFile: Failed to open \"%s\".\n", FileName);
|
||||
return;
|
||||
}
|
||||
|
||||
m_RSA = PEM_read_RSAPrivateKey(File, NULL, NULL, NULL);
|
||||
fclose(File);
|
||||
|
||||
if(m_RSA == NULL){
|
||||
error("TRSAPrivateKey::initFromFile: Failed to read key from \"%s\".\n", FileName);
|
||||
DumpOpenSSLErrors("TRSAPrivateKey::initFromFile", "PEM_read_RSAPrivateKey");
|
||||
}else if(RSA_size(m_RSA) != 128){
|
||||
error("TRSAPrivateKey::initFromFile: File \"%s\" doesn't contain a 1024-bit key", FileName);
|
||||
RSA_free(m_RSA);
|
||||
m_RSA = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void TRSAPrivateKey::decrypt(uint8 *Data){
|
||||
if(m_RSA == NULL){
|
||||
error("TRSAPrivateKey::decrypt: Key not initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(fusion): Pass in the length of `Data` for checking.
|
||||
ASSERT(RSA_size(m_RSA) == 128);
|
||||
|
||||
if(RSA_private_decrypt(128, Data, Data, m_RSA, RSA_NO_PADDING) == -1){
|
||||
DumpOpenSSLErrors("TRSAPrivateKey::decrypt", "RSA_private_decrypt");
|
||||
}
|
||||
}
|
||||
|
||||
// TXTEASymmetricKey
|
||||
// =============================================================================
|
||||
void TXTEASymmetricKey::init(TReadBuffer *Buffer){
|
||||
m_SymmetricKey[0] = Buffer->readQuad();
|
||||
m_SymmetricKey[1] = Buffer->readQuad();
|
||||
m_SymmetricKey[2] = Buffer->readQuad();
|
||||
m_SymmetricKey[3] = Buffer->readQuad();
|
||||
}
|
||||
|
||||
void TXTEASymmetricKey::encrypt(uint8 *Data){
|
||||
// TODO(fusion): This assumes both data endpoints have the same byte order.
|
||||
// It's unlikely that there is anything other than little-endian but we
|
||||
// should use a few helping functions to ensure compatibility.
|
||||
uint32 Sum = 0x00000000UL;
|
||||
uint32 Delta = 0x9E3779B9UL;
|
||||
uint32 V0 = *(uint32*)(&Data[0]);
|
||||
uint32 V1 = *(uint32*)(&Data[4]);
|
||||
for(int i = 0; i < 32; i += 1){
|
||||
V0 += (((V1 << 4) ^ (V1 >> 5)) + V1) ^ (Sum + m_SymmetricKey[Sum & 3]);
|
||||
Sum += Delta;
|
||||
V1 += (((V0 << 4) ^ (V0 >> 5)) + V0) ^ (Sum + m_SymmetricKey[(Sum >> 11) & 3]);
|
||||
}
|
||||
*(uint32*)(&Data[0]) = V0;
|
||||
*(uint32*)(&Data[4]) = V1;
|
||||
}
|
||||
|
||||
void TXTEASymmetricKey::decrypt(uint8 *Data){
|
||||
// TODO(fusion): Same as above.
|
||||
uint32 Sum = 0xC6EF3720UL;
|
||||
uint32 Delta = 0x9E3779B9UL;
|
||||
uint32 V0 = *(uint32*)(&Data[0]);
|
||||
uint32 V1 = *(uint32*)(&Data[4]);
|
||||
for(int i = 0; i < 32; i += 1){
|
||||
V1 -= (((V0 << 4) ^ (V0 >> 5)) + V0) ^ (Sum + m_SymmetricKey[(Sum >> 11) & 3]);
|
||||
Sum -= Delta;
|
||||
V0 -= (((V1 << 4) ^ (V1 >> 5)) + V1) ^ (Sum + m_SymmetricKey[Sum & 3]);
|
||||
}
|
||||
*(uint32*)(&Data[0]) = V0;
|
||||
*(uint32*)(&Data[4]) = V1;
|
||||
}
|
||||
|
||||
@ -2,52 +2,20 @@
|
||||
#define TIBIA_CRYPTO_HH_ 1
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
// TODO(fusion): We might want to scrap this implementation and use OpenSSL instead.
|
||||
|
||||
struct vlong_flex_unit{
|
||||
uint32 n;
|
||||
uint32 *a;
|
||||
uint32 z;
|
||||
};
|
||||
|
||||
struct vlong_value: vlong_flex_unit {
|
||||
uint32 share;
|
||||
};
|
||||
|
||||
struct vlong{
|
||||
vlong_value *value;
|
||||
int negative;
|
||||
};
|
||||
|
||||
struct vlong_montgomery{
|
||||
vlong R;
|
||||
vlong R1;
|
||||
vlong m;
|
||||
vlong n1;
|
||||
vlong T;
|
||||
vlong k;
|
||||
uint32 N;
|
||||
};
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
struct TRSAPrivateKey{
|
||||
TRSAPrivateKey(void);
|
||||
~TRSAPrivateKey(void);
|
||||
void init(const char *PrimeP, const char *PrimeQ);
|
||||
void initFromFile(const char *FileName);
|
||||
void decrypt(uint8 *Data); // single 128 bytes block
|
||||
|
||||
// DATA
|
||||
// =================
|
||||
vlong m_PrimeP;
|
||||
vlong m_PrimeQ;
|
||||
vlong m_U;
|
||||
vlong m_DP;
|
||||
vlong m_DQ;
|
||||
RSA *m_RSA;
|
||||
};
|
||||
|
||||
struct TXTEASymmetricKey{
|
||||
TXTEASymmetricKey(void);
|
||||
~TXTEASymmetricKey(void);
|
||||
void init(TReadBuffer *Buffer);
|
||||
void encrypt(uint8 *Data); // single 8 bytes block
|
||||
void decrypt(uint8 *Data); // single 8 bytes block
|
||||
|
||||
@ -216,7 +216,7 @@ void LoadWorldConfig(void){
|
||||
static void InitAll(void){
|
||||
try{
|
||||
ReadConfig();
|
||||
//SetQueryManagerLoginData(1, WorldName);
|
||||
SetQueryManagerLoginData(1, WorldName);
|
||||
LoadWorldConfig();
|
||||
InitSHM(!BeADaemon);
|
||||
LockGame();
|
||||
|
||||
@ -187,4 +187,6 @@ struct TQueryManagerPoolConnection{
|
||||
TQueryManagerConnection *QueryManagerConnection;
|
||||
};
|
||||
|
||||
void SetQueryManagerLoginData(int Type, const char *Data);
|
||||
|
||||
#endif //TIBIA_QUERY_HH_
|
||||
|
||||
15
tibia.pem
Normal file
15
tibia.pem
Normal file
@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDLoibNahc41DeQhZUTth3FrhMNYek5xbQrlZ0O4BFr0LukdGY1
|
||||
A3xHKAXt4d6Y539cQxpKqVPb+eSo44076c1PfL31BTDNFToN4uB3eUsOw3hROPSx
|
||||
0Jkh72FAIhl6QynId0AHyrqeAgY59ZsTyXyJCSCWpFnl0WvK3Qf6nVBH/wIDAQAB
|
||||
AoGAB9DoA19sl8JRhasS70hAuUs2sP9Ol+iWQ0wBVMZV9Nj0stnC6IsDNKn9HEXc
|
||||
qOrN0SlEM5RvQxTC6ZaeX6vYNQDSJj/aigSyiNTMvw+38TD09z6EBlI4+0piGIJd
|
||||
qY1ky0w3bdVhWiY2VFyGZ50NkfwjrpZSdLmsbhExzR4b/mkCQQDldLUeePo3RKZD
|
||||
7+CVLFkTX/Yof8RLPOh1nukgftpvgaGcD1FXSwrIyKCHVAuwFnGgPfEr/2OAj60D
|
||||
7Ya6XPeHAkEA4zC3U67ukzQxfyELrLTUBnsnKfkxejySsJ/M29SKCuHV2NOWxO8W
|
||||
s9/c0RFvp0wI4uOxH00fIPYyI2h6TpxZyQJBAKQ0ZPktskKjCilMHPgkCIro/Yv2
|
||||
A0+kgubJliP/I+rwZer8u0UxGsKdcOPnrYWSSjZWnaTS2y5Bo5tP/D6aETkCQDAc
|
||||
AtZPtumpJcob/1LlP/jXX2W+BUIzTYTlcgYjLdA8HoK527V8Q7x3bVVAcfplWYRi
|
||||
XwGX3T2npNpmp2+6IDECQBZuNbXiwHhDdsH7sSt36BGTFJNtVk3GJl/Bf50VrL6N
|
||||
6NBNU3d4jQwArvSsanFYrZw+ayxS4rcJdZasO5mm+EI=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
71
tools/genpem.go
Normal file
71
tools/genpem.go
Normal file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
P = "1201758001370723323398753778257470257713354828752713123415294815" +
|
||||
"0506251412291888866940292054989907714155267326586216043845592229" +
|
||||
"084368540020196135619327879"
|
||||
|
||||
Q = "1189892136861686835188050824611210139447876026576932541274639840" +
|
||||
"5473436969889506919017477758618276066588858607419440134394668095" +
|
||||
"105156501566867770737187273"
|
||||
|
||||
E = "65537"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// NOTE(fusion): Generate key from known P, Q, and E. There isn't a helper
|
||||
// function from the standard library so we need to build the private key
|
||||
// ourselves.
|
||||
p, ok := new(big.Int).SetString(P, 10)
|
||||
if !ok || !p.ProbablyPrime(4) {
|
||||
panic("invalid P")
|
||||
}
|
||||
|
||||
q, ok := new(big.Int).SetString(Q, 10)
|
||||
if !ok || !q.ProbablyPrime(4) {
|
||||
panic("invalid Q")
|
||||
}
|
||||
|
||||
e, ok := new(big.Int).SetString(E, 10)
|
||||
if !ok || !e.IsInt64() || !e.ProbablyPrime(4) {
|
||||
panic("invalid E")
|
||||
}
|
||||
|
||||
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
|
||||
qMinus1 := new(big.Int).Sub(q, big.NewInt(1))
|
||||
gcd := new(big.Int).GCD(nil, nil, pMinus1, qMinus1)
|
||||
phi := new(big.Int).Mul(pMinus1, qMinus1)
|
||||
lambda := new(big.Int).Div(phi, gcd)
|
||||
d := new(big.Int).ModInverse(e, lambda)
|
||||
n := new(big.Int).Mul(p, q)
|
||||
|
||||
privateKey := rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: n,
|
||||
E: int(e.Int64()),
|
||||
},
|
||||
D: d,
|
||||
Primes: []*big.Int{p, q},
|
||||
}
|
||||
|
||||
// NOTE(fusion): `Validate()` will only perform minor sanity checks. To actually
|
||||
// check that the output key is valid use `openssl rsa -in KEY.PEM -check`.
|
||||
if err := privateKey.Validate(); err != nil {
|
||||
panic("invalid private key: " + err.Error())
|
||||
}
|
||||
|
||||
// NOTE(fusion): Dump private key into stdout.
|
||||
block := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(&privateKey),
|
||||
}
|
||||
pem.Encode(os.Stdout, &block)
|
||||
}
|
||||
@ -21,11 +21,13 @@ var (
|
||||
"-Wno-unused-parameter",
|
||||
"-Wno-format-truncation",
|
||||
"-std=c++11",
|
||||
"-pthread",
|
||||
"-DOS_LINUX=1",
|
||||
"-DARCH_X64=1",
|
||||
}
|
||||
linkerOptions = []string{
|
||||
"-Wl,-t",
|
||||
"-lcrypto",
|
||||
}
|
||||
)
|
||||
|
||||
@ -93,7 +95,7 @@ func main() {
|
||||
fmt.Fprintf(&output, " $(BUILDDIR)/%v", object.obj)
|
||||
}
|
||||
fmt.Fprint(&output, "\n")
|
||||
fmt.Fprint(&output, "\t$(CC) $(CFLAGS) $(LFLAGS) -o $@ $^\n\n")
|
||||
fmt.Fprint(&output, "\t$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS)\n\n")
|
||||
|
||||
// OBJECTS
|
||||
for _, object := range objectFiles {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user