| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- /*
- * Simple shared-key based authentication plugin
- * Each node (firebird server) contains same key which is used to authenticate cross-server
- * connections. Each connection coming from one node to another has on target same
- * login as it was on source node.
- *
- * The contents of this file are subject to the Initial
- * Developer's Public License Version 1.0 (the "License");
- * you may not use this file except in compliance with the
- * License. You may obtain a copy of the License at
- * https://www.firebirdsql.org/en/initial-developer-s-public-license-version-1-0/
- *
- * Software distributed under the License is distributed AS IS,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied.
- * See the License for the specific language governing rights
- * and limitations under the License.
- *
- * The Original Code was created by Alexander Peshkoff
- * for the Firebird Open Source RDBMS project.
- *
- * Copyright (c) 2020 Alexander Peshkoff <[email protected]>
- * and all contributors signed below.
- *
- * All Rights Reserved.
- * Contributor(s): ______________________________________.
- */
- #include <memory>
- #include <atomic>
- #include "TcWrapper.h"
- #define HANDSHAKE_DEBUG(A)
- const unsigned LOGINSIZE = 128u;
- const unsigned RANDSIZE = 32u;
- const unsigned SALTLEN = 8u;
- typedef unsigned int ULong;
- using namespace std;
- namespace {
- IMaster* master = NULL;
- class PluginModule : public IPluginModuleImpl<PluginModule, ThrowStatusWrapper>
- {
- public:
- PluginModule()
- : pluginManager(NULL)
- { }
- ~PluginModule()
- {
- if (pluginManager)
- {
- pluginManager->unregisterModule(this);
- doClean();
- }
- }
- void registerMe(IPluginManager* m)
- {
- pluginManager = m;
- pluginManager->registerModule(this);
- }
- void doClean()
- {
- pluginManager = NULL;
- }
- void threadDetach()
- { }
- private:
- IPluginManager* pluginManager;
- };
- template <class P>
- class Factory : public IPluginFactoryImpl<Factory<P>, ThrowStatusWrapper>
- {
- public:
- // IPluginFactory implementation
- IPluginBase* createPlugin(ThrowStatusWrapper* status, IPluginConfig* factoryParameter)
- {
- IPluginBase* p = new P(status, factoryParameter);
- p->addRef();
- return p;
- }
- };
- //
- // Common RSA helper
- //
- class PluginData
- {
- public:
- PluginData(ThrowStatusWrapper* status, IPluginConfig* cnf)
- : refCounter(0), owner(NULL), iniLvl(0)
- {
- hash.init(status);
- iniLvl = 1;
- pseudoRand.init(status);
- iniLvl = 2;
- AutoRelease<IConfig> conf(cnf->getDefaultConfig(status));
- if (!conf)
- return;
- AutoRelease<IConfigEntry> ce(conf->find(status, "Key"));
- if (!ce)
- return;
- // import a key
- unsigned char key[4096];
- unsigned keySize = readHexKey(status, ce->getValue(), key, sizeof(key));
- check(status, rsa_import(key, keySize, &privateKey),
- "ExtAuth plugin failed to initialize - error importing private RSA key");
- iniLvl = 3;
- }
- ~PluginData()
- {
- if (iniLvl >= 3)
- rsa_free(&privateKey);
- if (iniLvl >= 2)
- pseudoRand.fini();
- if (iniLvl >= 1)
- hash.fini();
- }
- protected:
- atomic<int> refCounter;
- IReferenceCounted* owner;
- PseudoRandom pseudoRand;
- HashSha256 hash;
- rsa_key privateKey;
- int iniLvl;
- };
- //
- // Client plugin
- //
- class ExtAuthClient : public IClientImpl<ExtAuthClient, ThrowStatusWrapper>, public PluginData
- {
- public:
- ExtAuthClient(ThrowStatusWrapper* status, IPluginConfig* cnf)
- : PluginData(status, cnf),
- ignorePassword(false),
- ignoreLogin(false)
- {
- AutoRelease<IConfig> conf(cnf->getDefaultConfig(status));
- if (conf)
- {
- AutoRelease<IConfigEntry> igPass(conf->find(status, "IgnorePassword"));
- if (igPass)
- ignorePassword = igPass->getBoolValue();
- AutoRelease<IConfigEntry> igLgn(conf->find(status, "IgnoreLogin"));
- if (igLgn)
- ignoreLogin = igLgn->getBoolValue();
- }
- }
- // IClient implementation
- int authenticate(ThrowStatusWrapper* status, IClientBlock* cBlock);
- int release()
- {
- if (--refCounter == 0)
- {
- delete this;
- return 0;
- }
- return 1;
- }
- void addRef()
- {
- ++refCounter;
- }
- void setOwner(IReferenceCounted* o)
- {
- owner = o;
- }
- IReferenceCounted* getOwner()
- {
- return owner;
- }
- private:
- bool ignorePassword, ignoreLogin;
- };
- int ExtAuthClient::authenticate(ThrowStatusWrapper* status, IClientBlock* cBlock)
- {
- try
- {
- // did we initialize correctly?
- if (iniLvl < 3)
- return AUTH_CONTINUE;
- // check for missing login from the user
- if ((!ignoreLogin) && cBlock->getLogin())
- return AUTH_CONTINUE;
- // check for missing password from the user
- if ((!ignorePassword) && cBlock->getPassword())
- return AUTH_CONTINUE;
- // check for presence of authenticatiion block
- IAuthBlock* authBlock = cBlock->getAuthBlock(status);
- if (!authBlock)
- return AUTH_CONTINUE;
- if (!authBlock->first(status))
- return AUTH_CONTINUE;
- // and for presence of user name in that authenticatiion block
- const char* login = NULL;
- do
- {
- const char* type = authBlock->getType();
- if (type && (strcmp(type, "USER") == 0))
- {
- login = authBlock->getName();
- if (login)
- break;
- }
- } while(authBlock->next(status));
- if (!login)
- return AUTH_CONTINUE;
- // check if server started to talk to us
- unsigned dl = 0;
- const unsigned char* data = cBlock->getData(&dl);
- if (dl == 0 || !data)
- return AUTH_MORE_DATA;
- // decrypt message
- unsigned char bytes[RANDSIZE + LOGINSIZE + 1];
- unsigned long outlen = RANDSIZE;
- int result = 0;
- check(status, rsa_decrypt_key(data, dl, bytes, &outlen, NULL, 0, hash.index, &result, &privateKey),
- "Error decrypting message");
- if (outlen < RANDSIZE)
- error(status, "Malformed data from server - missing random block");
- // next append login to random block
- unsigned len = strlen(login);
- if (len > LOGINSIZE)
- len = LOGINSIZE;
- memcpy(&bytes[RANDSIZE], login, len);
- // calc hash for whole block
- hash_state state;
- sha256_init(&state);
- check(status, sha256_process(&state, bytes, RANDSIZE + len), "Error hashing message");
- unsigned char digest[256 / 8];
- check(status, sha256_done(&state, digest), "Error extracting hash");
- // build message
- unsigned char msg[4096];
- // put login to it
- memcpy(msg, login, len);
- msg[len++] = 0;
- // append sign of hash to it
- unsigned long signLen = sizeof(msg) - len;
- unsigned char* sign = &msg[len];
- check(status, rsa_sign_hash(digest, sizeof digest, sign, &signLen, &pseudoRand.state,
- pseudoRand.index, hash.index, SALTLEN, &privateKey), "Error signing message hash");
- // send message
- cBlock->putData(status, len + signLen, msg);
- // output the wire crypt key
- ICryptKey* cKey = cBlock->newKey(status);
- cKey->setSymmetric(status, "Symmetric", RANDSIZE, bytes);
- HANDSHAKE_DEBUG( fprintf(stderr, "Key ="); for (unsigned n = 0; n < RANDSIZE; ++n)
- fprintf(stderr, " %02u", bytes[n]); fprintf(stderr, "\n"); )
- return AUTH_SUCCESS;
- }
- catch(const FbException& ex)
- {
- status->setErrors(ex.getStatus()->getErrors());
- return AUTH_FAILED;
- }
- }
- //
- // Server plugin
- //
- class ExtAuthServer : public IServerImpl<ExtAuthServer, ThrowStatusWrapper>, public PluginData
- {
- public:
- ExtAuthServer(ThrowStatusWrapper* status, IPluginConfig* cnf)
- : PluginData(status, cnf), sentData(false)
- { }
- // IServer implementation
- int authenticate(ThrowStatusWrapper* status, IServerBlock* sBlock, IWriter* writerInterface);
- void setDbCryptCallback(ThrowStatusWrapper* status, ICryptKeyCallback* cryptCallback)
- { }
- int release()
- {
- if (--refCounter == 0)
- {
- delete this;
- return 0;
- }
- return 1;
- }
- void addRef()
- {
- ++refCounter;
- }
- void setOwner(IReferenceCounted* o)
- {
- owner = o;
- }
- IReferenceCounted* getOwner()
- {
- return owner;
- }
- private:
- unsigned char msg[RANDSIZE + LOGINSIZE];
- bool sentData;
- };
- int ExtAuthServer::authenticate(ThrowStatusWrapper* status, IServerBlock* sBlock, IWriter* writerInterface)
- {
- try
- {
- // did we initialize correctly?
- if (iniLvl < 3)
- return AUTH_CONTINUE;
- unsigned dl = 0;
- const unsigned char* data = sBlock->getData(&dl);
- if (!sentData)
- {
- // fbassert(dl == 0 && !data);
- // build message: first of all get some randomness
- pseudoRand.getDsc()->read(msg, RANDSIZE, &pseudoRand.state);
- // now encrypt that random block
- unsigned char encrypted[4096];
- unsigned long encLen = sizeof encrypted;
- check(status, rsa_encrypt_key(msg, RANDSIZE, encrypted, &encLen, NULL, 0,
- &pseudoRand.state, pseudoRand.index, hash.index, &privateKey), "Error encrypting message");
- // send message
- sBlock->putData(status, encLen, encrypted);
- sentData = true;
- return AUTH_MORE_DATA;
- }
- // decompose message
- const char* login = reinterpret_cast<const char*>(data);
- unsigned len = strnlen(login, dl);
- if (len == dl)
- error(status, "Wrong data from client - no signature in a message");
- if (len == 0)
- error(status, "Wrong data from client - empty login");
- if (len > LOGINSIZE)
- error(status, "Wrong data from client - login too long");
- memcpy(&msg[RANDSIZE], data, len);
- const unsigned char* sign = &data[len + 1];
- unsigned long signLen = dl - (len + 1);
- // calc hash for message
- hash_state state;
- sha256_init(&state);
- check(status, sha256_process(&state, msg, RANDSIZE + len), "Error hashing message");
- unsigned char digest[256 / 8];
- check(status, sha256_done(&state, digest), "Error extracting hash");
- // validate signature
- int result = 0;
- int err = rsa_verify_hash(sign, signLen, digest, sizeof digest, hash.index, SALTLEN, &result, &privateKey);
- if (err != CRYPT_INVALID_PACKET)
- check(status, err, "Error verifying digital signature");
- else
- result = 0;
- if (!result)
- error(status, "Malformed data from client - invalid digital signature");
- // output the wire crypt key
- ICryptKey* cKey = sBlock->newKey(status);
- cKey->setSymmetric(status, "Symmetric", RANDSIZE, msg);
- HANDSHAKE_DEBUG( fprintf(stderr, "Key ="); for (unsigned n = 0; n < RANDSIZE; ++n)
- fprintf(stderr, " %02x", msg[n]); fprintf(stderr, "\n"); )
- // store received login name in auth block
- writerInterface->add(status, login);
- return AUTH_SUCCESS;
- }
- catch(const FbException& ex)
- {
- status->setErrors(ex.getStatus()->getErrors());
- }
- return AUTH_FAILED;
- }
- //
- // Static variables
- //
- PluginModule module;
- Factory<ExtAuthClient> clientFactory;
- Factory<ExtAuthServer> serverFactory;
- } // anonymous namespace
- extern "C" FB_DLL_EXPORT void FB_PLUGIN_ENTRY_POINT(IMaster* m)
- {
- master = m;
- IPluginManager* pluginManager = master->getPluginManager();
- module.registerMe(pluginManager);
- const char* plName = "fbSampleExtAuth";
- pluginManager->registerPluginFactory(IPluginManager::TYPE_AUTH_CLIENT, plName, &clientFactory);
- pluginManager->registerPluginFactory(IPluginManager::TYPE_AUTH_SERVER, plName, &serverFactory);
- }
|