diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-08-15 15:48:19 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-08-15 15:50:32 -0300 |
| commit | 8082c228c5d618e6f865f76b4d0518c2fde573ec (patch) | |
| tree | 59e61abe98c3060fb1eb3677b9dbbb744c92174a | |
| parent | e4b8bf807b76f6f446d9617d9a3957dc77dd6e90 (diff) | |
| download | game-8082c228c5d618e6f865f76b4d0518c2fde573ec.tar.gz game-8082c228c5d618e6f865f76b4d0518c2fde573ec.zip | |
wrapping up for a public release
| -rw-r--r-- | LICENSE.txt | 24 | ||||
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | README.md | 104 | ||||
| -rw-r--r-- | TODO.md | 36 | ||||
| -rw-r--r-- | build.bat | 26 | ||||
| -rw-r--r-- | src/common.hh | 1 | ||||
| -rw-r--r-- | src/communication.cc | 2 | ||||
| -rw-r--r-- | src/cract.cc | 4 | ||||
| -rw-r--r-- | src/houses.cc | 2 | ||||
| -rw-r--r-- | src/map.cc | 10 | ||||
| -rw-r--r-- | src/moveuse.cc | 4 | ||||
| -rw-r--r-- | src/operate.cc | 12 | ||||
| -rw-r--r-- | src/query.cc | 7 | ||||
| -rw-r--r-- | tibia-game.service | 26 | ||||
| -rw-r--r-- | tools/genpem.go | 63 | ||||
| -rw-r--r-- | tools/pubkey.go | 34 |
16 files changed, 254 insertions, 105 deletions
diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c32dd18 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org/>
\ No newline at end of file @@ -3,12 +3,12 @@ BUILDDIR = build OUTPUTEXE = game CC = g++ -CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -std=c++11 -pthread -DOS_LINUX=1 -DARCH_X64=1 +CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-deprecated-declarations -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) - CFLAGS += -g -O0 + CFLAGS += -g -Og else CFLAGS += -O2 endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..347d139 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Tibia 7.7 Game Server +This is the result of manually decompiling the leaked Tibia 7.7 server binary into a readable version of the original source code. With the exception of a few [changes](#changes), expected typos, and translation errors, everything should be as close as possible to the original. I do expect problems so any issues should be submitted to the issue tracker with the appropriate description. + +## License and Legal Issues +The legal status of decompiled and rewritten code is a gray area. While the project contains no original assets or code, CipSoft may still view this as a violation of their claimed intellectual property rights, due to the original binary not being willfully released. As the author, I release this code into the public domain, relinquishing all rights and claims to it (see `LICENSE.txt`). + +## Changes +- The most important change was fixing thread interations. The original binary relied on some old Linux threading library, most likely LinuxThreads, which assigned different process ids to different threads, among other quirks. The fix is small and contained in commit `d359c0a`. + +- Custom RSA routines were replaced with OpenSSL's libcrypto. I didn't bother to translate the decompiled routines because it would be annoying to get write and could end up with security issues. This introduces the **ONLY** dependency required to compile but is fairly simple to get it installed on most Linux distros. For example, on Debian you can use `apt install libssl-dev`. + +## Future +I had a small *TODO* list with a few things to attempt after the server was up and running but I think that trying to modernize it further or get it to run on Windows wouldn't really add any value, plus it would probably require a lot of extra time. My commitment is to fix all reported bugs and perhaps string handling/building which is kind of a disaster. Exceptions are already too engraved into the codebase, to the point it would make more sense to rewrite everything without them rather than attempting to remove them. + +## Compiling +The server uses a few Linux specific features so it will **ONLY** compile on Linux. It's possible to port to Windows but it would require a few design changes. Originally it would have no dependencies but with the RSA routines change it now requires OpenSSL's `libcrypto` as its **ONLY** dependency. The makefile is very simple and should work as long as libcrypto is installed. You can add the `-j N` switch to make it compile across N processes. +``` +make # build in release mode +make DEBUG=1 # build in debug mode +make clean # remove `build` directory +make clean && make # full rebuild in release mode (recommended) +make clean && make DEBUG=1 # full rebuild in debug mode (recommended) +``` + +## Running +This repository contains only the source code for the game server. After the first decompilation pass, it was clear the server would need a few supporting services. They're fairly simple but each one will have a separate *README* file with a short description on how to compile and run them. +- [Query Manager](https://github.com/fusion32/tibia-querymanager) +- [Login Server](https://github.com/fusion32/tibia-login) +- [Web Server](https://github.com/fusion32/tibia-web) + +The game server won't boot up if it's not able to connect to the query manager which makes it the only real hard dependency. The login server will handle character list, and the web server will handle basic account management. + +It is recommended that the server is setup as a service. There is a *systemd* configuration file (`tibia-game.service`) in the repository that may be used along with the intructions below to set everything up. The steps may change depending on whether your system uses *systemd* or not. + +> It is also a good idea to configure a firewall and generate a new RSA private key for **security** reasons, but those aren't covered here. + +## Playing +Playing will require a copy of the original Tibia 7.7 client and some IP-Changer application. Since I can't trust anything out there, I also wrote a small [IP-Changer](https://github.com/fusion32/tibia-ipchanger) command line tool which should be very simple to use. As for the client itself, there is no way except to look around the internet for the old installer and hope that it doesn't contain viruses. It is always a good idea to check it with [Virus Total](https://www.virustotal.com). + +### Stale data +The leaked tarball contains a lot of stale data that you may want to cleanup ahead of time: +- `.tibia` contains the base config and should be configured appropriately +- `dat/owners.dat` contains house owner information and will cause query manager errors the first time the server is run because there is no character data in the database +- `log` contains old log data +- `map` contains world data which is **PERSISTENT** and should probably be replaced with a fresh copy of `origmap` +- `map.bak` contains map backup data (generated from `map` by some external tool) +- `usr` contains old character data +- `usr.bak` contains character backup data (generated from `usr` by some external tool) +- `save/game.pid` is used to prevent multiple instances of the game server from running at the same time and will linger if the process doesn't exit gracefully -- should be removed whenever **needed** to allow the server to properly startup + +> The server won't create sub-directories like `usr/01` which will result in saving/loading errors. You'll need to recreate them manually with `mkdir -p {00..99}`. + +### Prepare game files +``` +tar -xzf ./tibia-game.tarball.tar.gz ./tibia-game # extract game files +cp game ./tibia-game/bin/game # replace old game binary with newly compiled one +cp tibia.pem ./tibia-game/tibia.pem # copy RSA private key +``` + +### Setup service user and game files +The default service config will assume game files are in `/opt/tibia/game`. +``` +useradd -r -M tibia-game # create user that will run the service +mkdir -p /opt/tibia/game # create directory that will hold game files +cp -r ./tibia-game/* /opt/tibia/game # copy game files into /opt/tibia/game +chown -R tibia-game:tibia-game /opt/tibia/game # change game files ownership to newly created user +``` + +### Install service +``` +cp tibia-game.service /etc/systemd/system # copy service configuration into place +systemctl daemon-reload # reload configuration files +systemctl enable tibia-game.service # enable service +systemctl start tibia-game.service # start service +``` + +### Check service +``` +systemctl status tibia-game.service # show service status +journalctl -n 100 -f -u tibia-game.service # show last 100 log lines +``` + +### Ownership and permission problems +Unexpected errors may arise from invalid file ownership and permissions. You should always make sure that game files have the appropriate read-write-execute permissions and that are owned by the user running the game binary. I won't discuss this topic here but you may find plenty of information either in the manual (`man chmod`, `man chown`) or the internet. + +> Extra Security: Files with sensitive data like `tibia.pem` should have 0600 permissions to make sure it is only accessed by the assigned user. + +## Comparison with OpenTibia +### Performance +The application is well designed and I'd expect the main thread to have comparable performance to OpenTibia distros. The problem is that each connection will spawn a communication thread, each with its own stack (hardcoded to 64KB) and a few extra system resources for bookkeeping. They should have negligible runtime due to constant I/O but if we consider a high volume, CPU performance would surely be impacted with the constant context switching, nevermind the constant instruction and data cache thrashing. + +Making connection management asynchronous would probably allow a higher number of connections with better performance but would also require some design changes as the communication threads will also handle authentication which involves reaching out to the query manager which also performs I/O. + +### Customizability +If we're talking about the executable itself, then the imagination is the limit. If we're talking about external files/scripts, then you'll find that changes are strictly limited to existing game mechanics. The level of customizability of OpenTibia servers are a lot higher with custom Lua scripts, etc... + +Modifying original files shouldn't be too difficult with a text editor but having a specialized tool like a map editor will make a huge difference when trying to modify the map particularly. + +## Why +I've been thinking about different server designs lately, and at the start of the year, I had setup a decompilation project for the leaked binary to see if would give me any insights. To my surprise it had debug symbols which made all the difference and kinda got me hooked. I knew I'd want to fully decompile it at some point but mostly for introspection. + +I still visit OTLand every once in a while, and around May of this year, there was a thread advertising a server using some modified version of the 7.7 leak, which quickly turned into a shit show. It got me wondering why hadn't anyone released anything related to this leak **AT ALL**, and as the depressing as it may sound, there is only one answer: the OpenTibia **community** is pretty much dead. People gate-keeping or selling features/bug-fixes, abusing known bugs to extort server owners. Entitled users that can't get anything working without help while also extorting players with their low quality servers. A pathetic server list that requires advertisement fees to even display servers past a certain amount of players. It is a dread scenario all across. + +To break out of this shitty ass trend, I knew from the beginning that I'd release it all with no strings attached. It may not be in the best possible shape but it will give an idea on how earlier Tibia servers worked and may even serve as inspiration for future server designs. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index a7a2f84..0000000 --- a/TODO.md +++ /dev/null @@ -1,36 +0,0 @@ -## TODO - -## Synchronization -I'm not sure whether synchronization is done properly. The implementation of the first few `crplayer.cc` functions left me with a taste of race conditions although integer loads on x86 are generally atomic. - -## TODO (LEFTOVER) -- Include `x == 0xFFFF` inside `CheckVisibility`? -- The map container uses type id zero but it seems to also be used as a "no type" - id which makes me want to add some `isNone()`, `isNull()`, `isVoid()` check to - `ObjectType` to be used as an alias when pertinent. -- Make some `TNonplayer` random step function? One version that does a random step - and another that does a random step keeping some distance from a certain position. - -- I realized that `Change` with an `ObjectType` parameter was being called where - the version with `INSTANCEATTRIBUTE` parameter was expected due to `ObjectType` - having an implicit conversion constructor from `int`. I think it is best to make - all converting constructors explicit to avoid these types of errors, although - there aren't that many function overloads. - -- Define a few constants like `MAX_NAME_LENGTH`, `MAX_SKILLS`, and `MAX_OPEN_CONTAINERS` - instead of relying on `NARRAY`. - -## TODO AFTER FIRST PASS -- Remove VTABLE comments. -- Split NPC, Monster, and Player into separate header files. -- Review dividing comments. I feel like the current "//========" blends in too easily, making it hard to see. -- Trim rough edges. -- Replace unsafe libc functions like `strcpy`, `strncpy`, `strcat`, `sprintf` etc... -- Handle connections inline with `poll`/`epoll` (probably?). -- Remove exceptions. - - Would be desirable but I feel it could change too much of the original code flow while also - littering functions with early return checks like `if(err != NOERROR) { return err }` although - we could also have some macros to simplify this since we know we only use `RESULT` for errors. - But then would we be in a better place? -- Review signal usage for timing (SIGALRM, etc...). -- Support Windows. diff --git a/build.bat b/build.bat deleted file mode 100644 index a0e501f..0000000 --- a/build.bat +++ /dev/null @@ -1,26 +0,0 @@ -@echo off -setlocal - -pushd %~dp0 -del /q .\build\* -mkdir .\build -pushd .\build - -set OUTPUTEXE=out.exe -set OUTPUTPDB=out.pdb -set INSTALLDIR=..\bin - -@REM set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" "../src/skill.cc" -set SRC="../src/map.cc" "../src/script.cc" "../src/util.cc" -cl -W3 -WX -Zi -we4244 -we4456 -we4457 -wd4996 -std:c++14 -EHsc -utf-8 -MP8 -DBUILD_DEBUG=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 -Fe:"%OUTPUTEXE%" -Fd:"%OUTPUTPDB%" %* %SRC% /link -subsystem:console -incremental:no -opt:ref -dynamicbase "ws2_32.lib" - -if errorlevel 1 exit /b errorlevel - -mkdir "%INSTALLDIR%" -copy /Y "%OUTPUTEXE%" "%INSTALLDIR%" -copy /Y "%OUTPUTPDB%" "%INSTALLDIR%" - -popd -popd - -endlocal diff --git a/src/common.hh b/src/common.hh index 971dca2..8e0cac7 100644 --- a/src/common.hh +++ b/src/common.hh @@ -245,7 +245,6 @@ struct TWriteStream { struct TWriteBuffer: TWriteStream { TWriteBuffer(uint8 *Data, int Size); - void reset(void) { this->Position = 0; } // VIRTUAL FUNCTIONS // ================= diff --git a/src/communication.cc b/src/communication.cc index b981d1e..f9a1753 100644 --- a/src/communication.cc +++ b/src/communication.cc @@ -1232,7 +1232,7 @@ void IncrementActiveConnections(void){ void DecrementActiveConnections(void){ CommunicationThreadMutex.down(); - ActiveConnections += 1; + ActiveConnections -= 1; CommunicationThreadMutex.up(); } diff --git a/src/cract.cc b/src/cract.cc index 41bbcca..35ca92a 100644 --- a/src/cract.cc +++ b/src/cract.cc @@ -1434,9 +1434,7 @@ void TCreature::NotifyGo(void){ } } - // TODO(fusion): This is probably an inlined function to get the first object - // with some specific flag on a field. - int Waypoints; + int Waypoints = 0; Object Bank = GetFirstObject(DestX, DestY, DestZ); while(Bank != NONE){ ObjectType BankType = Bank.getObjectType(); diff --git a/src/houses.cc b/src/houses.cc index a26ffbf..84c9634 100644 --- a/src/houses.cc +++ b/src/houses.cc @@ -36,7 +36,7 @@ THouse::THouse(void) : Subowner(0, 4, 5), Guest(0, 9, 10) { this->CenterY = 0; this->CenterZ = 0; this->OwnerID = 0; - this->OwnerName[30] = 0; + this->OwnerName[0] = 0; this->LastTransition = 0; this->PaidUntil = 0; this->Subowners = 0; @@ -995,7 +995,7 @@ void LoadSector(const char *FileName, int SectorX, int SectorY, int SectorZ){ AccessObject(LoadingSector->MapCon[OffsetX][OffsetY])->Attributes[3] |= 0x400; }else if(strcmp(Identifier, "content") == 0){ Script.readSymbol('='); - HelpBuffer.reset(); + HelpBuffer.Position = 0; LoadObjects(&Script, &HelpBuffer, false); TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); LoadObjects(&ReadBuffer, LoadingSector->MapCon[OffsetX][OffsetY]); @@ -1254,7 +1254,7 @@ void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ Script.writeText(", "); } Script.writeText("Content="); - HelpBuffer.reset(); + HelpBuffer.Position = 0; SaveObjects(First, &HelpBuffer, false); TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); SaveObjects(&ReadBuffer, &Script); @@ -1459,7 +1459,7 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector, } }else if(strcmp(Identifier, "content") == 0){ Script->readSymbol('='); - HelpBuffer.reset(); + HelpBuffer.Position = 0; if(!House || !SaveHouses){ LoadObjects(Script, &HelpBuffer, false); TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); @@ -1615,7 +1615,7 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector, } }else if(strcmp(Identifier, "content") == 0){ IN.readSymbol('='); - HelpBuffer.reset(); + HelpBuffer.Position = 0; LoadObjects(&IN, &HelpBuffer, false); if(!FieldPatched[OffsetX][OffsetY]){ if(AttrCount > 0){ @@ -1680,7 +1680,7 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector, OUT.writeText(", "); } OUT.writeText("Content="); - HelpBuffer.reset(); + HelpBuffer.Position = 0; SaveObjects(First, &HelpBuffer, false); TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); SaveObjects(&ReadBuffer, &OUT); diff --git a/src/moveuse.cc b/src/moveuse.cc index e4d76db..396207d 100644 --- a/src/moveuse.cc +++ b/src/moveuse.cc @@ -2977,7 +2977,9 @@ void LoadDataBase(void){ continue; } - int EventType; + // NOTE(fusion): `TReadScriptFile::error` will always throw but the + // compiler might not be able to detect that and issue a warning. + int EventType = 0; if(strcmp(Identifier, "use") == 0){ EventType = MOVEUSE_EVENT_USE; }else if(strcmp(Identifier, "multiuse") == 0){ diff --git a/src/operate.cc b/src/operate.cc index 92ba83e..b0c81e2 100644 --- a/src/operate.cc +++ b/src/operate.cc @@ -2892,10 +2892,10 @@ void RefreshMap(void){ continue; } - bool Refreshable = false; int OffsetX = -1; int OffsetY = -1; - HelpBuffer.reset(); + bool Refreshable = false; + HelpBuffer.Position = 0; try{ TReadScriptFile Script; Script.open(FileName); @@ -2914,13 +2914,9 @@ void RefreshMap(void){ uint8 *SectorOffset = Script.getBytesequence(); OffsetX = (int)SectorOffset[0]; OffsetY = (int)SectorOffset[1]; + Refreshable = false; Script.readSymbol(':'); - continue; - } - - // TODO(fusion): We don't enforce the token to be an identifier - // here as we do when loading any sector file in `map.cc`. - if(Script.Token == IDENTIFIER){ + }else if(Script.Token == IDENTIFIER){ if(OffsetX == -1 || OffsetY == -1){ Script.error("coordinate expected"); } diff --git a/src/query.cc b/src/query.cc index c9b47aa..3b62ecd 100644 --- a/src/query.cc +++ b/src/query.cc @@ -312,7 +312,8 @@ int TQueryManagerConnection::executeQuery(int Timeout, bool AutoReconnect){ return QUERY_STATUS_FAILED; } - for(int Attempt = 0; true; Attempt += 1){ + const int MaxAttempts = 2; + for(int Attempt = 1; true; Attempt += 1){ if(!this->isConnected()){ if(!AutoReconnect){ return QUERY_STATUS_FAILED; @@ -335,7 +336,7 @@ int TQueryManagerConnection::executeQuery(int Timeout, bool AutoReconnect){ if(this->write(this->Buffer, PacketSize) != PacketSize){ this->disconnect(); - if(Attempt > 0){ + if(Attempt >= MaxAttempts){ error("TQueryManagerConnection::executeQuery: Fehler beim Abschicken der Anfrage.\n"); return QUERY_STATUS_FAILED; } @@ -346,7 +347,7 @@ int TQueryManagerConnection::executeQuery(int Timeout, bool AutoReconnect){ int BytesRead = this->read(Help, 2, Timeout); if(BytesRead != 2){ this->disconnect(); - if(BytesRead == -2 || Attempt > 0){ + if(BytesRead == -2 || Attempt >= MaxAttempts){ return QUERY_STATUS_FAILED; } continue; diff --git a/tibia-game.service b/tibia-game.service new file mode 100644 index 0000000..e158b21 --- /dev/null +++ b/tibia-game.service @@ -0,0 +1,26 @@ +# Basic SYSTEMD service file for the Tibia Game Server + +[Unit] +Description=Tibia Game Server +After=network.target +Requires=tibia-querymanager.service + +[Install] +WantedBy=multi-user.target + +[Service] +Type=simple +User=tibia-game +Group=tibia-game +ExecStart=/opt/tibia/game/bin/game +WorkingDirectory=/opt/tibia/game +Restart=always +RestartSec=10 +# After a SIGTERM, the server will issue shutdown warnings for 5 minutes and +# take another minute or two to save everything and exit. Not waiting for this +# process to complete would cause data to not be properly saved. +TimeoutStopSec=600 +LimitCORE=infinity +StandardOutput=journal +StandardError=journal +SyslogIdentifier=%n diff --git a/tools/genpem.go b/tools/genpem.go index 542fd9a..182c630 100644 --- a/tools/genpem.go +++ b/tools/genpem.go @@ -1,42 +1,45 @@ package main import ( + "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" "math/big" "os" + "slices" ) -const ( - P = "1201758001370723323398753778257470257713354828752713123415294815" + - "0506251412291888866940292054989907714155267326586216043845592229" + - "084368540020196135619327879" - - Q = "1189892136861686835188050824611210139447876026576932541274639840" + - "5473436969889506919017477758618276066588858607419440134394668095" + - "105156501566867770737187273" - - E = "65537" -) - -func main() { +func GenerateDefaultKey() (*rsa.PrivateKey, error) { // 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. + const ( + P = "1201758001370723323398753778257470257713354828752713123415294815" + + "0506251412291888866940292054989907714155267326586216043845592229" + + "084368540020196135619327879" + + Q = "1189892136861686835188050824611210139447876026576932541274639840" + + "5473436969889506919017477758618276066588858607419440134394668095" + + "105156501566867770737187273" + + E = "65537" + ) + p, ok := new(big.Int).SetString(P, 10) if !ok || !p.ProbablyPrime(4) { - panic("invalid P") + return nil, errors.New("invalid default P prime") } q, ok := new(big.Int).SetString(Q, 10) if !ok || !q.ProbablyPrime(4) { - panic("invalid Q") + return nil, errors.New("invalid default Q prime") } e, ok := new(big.Int).SetString(E, 10) if !ok || !e.IsInt64() || !e.ProbablyPrime(4) { - panic("invalid E") + return nil, errors.New("invalid default E prime") } pMinus1 := new(big.Int).Sub(p, big.NewInt(1)) @@ -56,16 +59,40 @@ func main() { Primes: []*big.Int{p, q}, } + return &privateKey, nil +} + +func GenerateKey() (*rsa.PrivateKey, error) { + return rsa.GenerateKey(rand.Reader, 1024) +} + +func main() { + var ( + privateKey *rsa.PrivateKey + err error + ) + + if len(os.Args) > 1 && slices.Contains(os.Args[1:], "-default") { + privateKey, err = GenerateDefaultKey() + } else { + privateKey, err = GenerateKey() + } + + if err != nil { + panic("failed to generate key: " + err.Error()) + } + // 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 { + err = privateKey.Validate() + if 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), + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), } pem.Encode(os.Stdout, &block) } diff --git a/tools/pubkey.go b/tools/pubkey.go new file mode 100644 index 0000000..4649664 --- /dev/null +++ b/tools/pubkey.go @@ -0,0 +1,34 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "log" + "os" +) + +func main() { + keyFile := "key.pem" + if len(os.Args) > 1 { + keyFile = os.Args[1] + } + + keyData, err := os.ReadFile(keyFile) + if err != nil { + log.Panicf("failed to read key \"%v\": %v", keyFile, err) + } + + keyBlock, _ := pem.Decode(keyData) + if keyBlock == nil { + log.Panicf("key \"%v\" is empty") + } + + privateKey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + if err != nil { + log.Panic("failed to read PKCS1 RSA private key: %v", err) + } + + fmt.Println("N", privateKey.N) + fmt.Println("E", privateKey.E) +} |
