ExtAuth.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. * Simple shared-key based authentication plugin
  3. * Each node (firebird server) contains same key which is used to authenticate cross-server
  4. * connections. Each connection coming from one node to another has on target same
  5. * login as it was on source node.
  6. *
  7. * The contents of this file are subject to the Initial
  8. * Developer's Public License Version 1.0 (the "License");
  9. * you may not use this file except in compliance with the
  10. * License. You may obtain a copy of the License at
  11. * https://www.firebirdsql.org/en/initial-developer-s-public-license-version-1-0/
  12. *
  13. * Software distributed under the License is distributed AS IS,
  14. * WITHOUT WARRANTY OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing rights
  16. * and limitations under the License.
  17. *
  18. * The Original Code was created by Alexander Peshkoff
  19. * for the Firebird Open Source RDBMS project.
  20. *
  21. * Copyright (c) 2020 Alexander Peshkoff <[email protected]>
  22. * and all contributors signed below.
  23. *
  24. * All Rights Reserved.
  25. * Contributor(s): ______________________________________.
  26. */
  27. #include <memory>
  28. #include <atomic>
  29. #include "TcWrapper.h"
  30. #define HANDSHAKE_DEBUG(A)
  31. const unsigned LOGINSIZE = 128u;
  32. const unsigned RANDSIZE = 32u;
  33. const unsigned SALTLEN = 8u;
  34. typedef unsigned int ULong;
  35. using namespace std;
  36. namespace {
  37. IMaster* master = NULL;
  38. class PluginModule : public IPluginModuleImpl<PluginModule, ThrowStatusWrapper>
  39. {
  40. public:
  41. PluginModule()
  42. : pluginManager(NULL)
  43. { }
  44. ~PluginModule()
  45. {
  46. if (pluginManager)
  47. {
  48. pluginManager->unregisterModule(this);
  49. doClean();
  50. }
  51. }
  52. void registerMe(IPluginManager* m)
  53. {
  54. pluginManager = m;
  55. pluginManager->registerModule(this);
  56. }
  57. void doClean()
  58. {
  59. pluginManager = NULL;
  60. }
  61. void threadDetach()
  62. { }
  63. private:
  64. IPluginManager* pluginManager;
  65. };
  66. template <class P>
  67. class Factory : public IPluginFactoryImpl<Factory<P>, ThrowStatusWrapper>
  68. {
  69. public:
  70. // IPluginFactory implementation
  71. IPluginBase* createPlugin(ThrowStatusWrapper* status, IPluginConfig* factoryParameter)
  72. {
  73. IPluginBase* p = new P(status, factoryParameter);
  74. p->addRef();
  75. return p;
  76. }
  77. };
  78. //
  79. // Common RSA helper
  80. //
  81. class PluginData
  82. {
  83. public:
  84. PluginData(ThrowStatusWrapper* status, IPluginConfig* cnf)
  85. : refCounter(0), owner(NULL), iniLvl(0)
  86. {
  87. hash.init(status);
  88. iniLvl = 1;
  89. pseudoRand.init(status);
  90. iniLvl = 2;
  91. AutoRelease<IConfig> conf(cnf->getDefaultConfig(status));
  92. if (!conf)
  93. return;
  94. AutoRelease<IConfigEntry> ce(conf->find(status, "Key"));
  95. if (!ce)
  96. return;
  97. // import a key
  98. unsigned char key[4096];
  99. unsigned keySize = readHexKey(status, ce->getValue(), key, sizeof(key));
  100. check(status, rsa_import(key, keySize, &privateKey),
  101. "ExtAuth plugin failed to initialize - error importing private RSA key");
  102. iniLvl = 3;
  103. }
  104. ~PluginData()
  105. {
  106. if (iniLvl >= 3)
  107. rsa_free(&privateKey);
  108. if (iniLvl >= 2)
  109. pseudoRand.fini();
  110. if (iniLvl >= 1)
  111. hash.fini();
  112. }
  113. protected:
  114. atomic<int> refCounter;
  115. IReferenceCounted* owner;
  116. PseudoRandom pseudoRand;
  117. HashSha256 hash;
  118. rsa_key privateKey;
  119. int iniLvl;
  120. };
  121. //
  122. // Client plugin
  123. //
  124. class ExtAuthClient : public IClientImpl<ExtAuthClient, ThrowStatusWrapper>, public PluginData
  125. {
  126. public:
  127. ExtAuthClient(ThrowStatusWrapper* status, IPluginConfig* cnf)
  128. : PluginData(status, cnf),
  129. ignorePassword(false),
  130. ignoreLogin(false)
  131. {
  132. AutoRelease<IConfig> conf(cnf->getDefaultConfig(status));
  133. if (conf)
  134. {
  135. AutoRelease<IConfigEntry> igPass(conf->find(status, "IgnorePassword"));
  136. if (igPass)
  137. ignorePassword = igPass->getBoolValue();
  138. AutoRelease<IConfigEntry> igLgn(conf->find(status, "IgnoreLogin"));
  139. if (igLgn)
  140. ignoreLogin = igLgn->getBoolValue();
  141. }
  142. }
  143. // IClient implementation
  144. int authenticate(ThrowStatusWrapper* status, IClientBlock* cBlock);
  145. int release()
  146. {
  147. if (--refCounter == 0)
  148. {
  149. delete this;
  150. return 0;
  151. }
  152. return 1;
  153. }
  154. void addRef()
  155. {
  156. ++refCounter;
  157. }
  158. void setOwner(IReferenceCounted* o)
  159. {
  160. owner = o;
  161. }
  162. IReferenceCounted* getOwner()
  163. {
  164. return owner;
  165. }
  166. private:
  167. bool ignorePassword, ignoreLogin;
  168. };
  169. int ExtAuthClient::authenticate(ThrowStatusWrapper* status, IClientBlock* cBlock)
  170. {
  171. try
  172. {
  173. // did we initialize correctly?
  174. if (iniLvl < 3)
  175. return AUTH_CONTINUE;
  176. // check for missing login from the user
  177. if ((!ignoreLogin) && cBlock->getLogin())
  178. return AUTH_CONTINUE;
  179. // check for missing password from the user
  180. if ((!ignorePassword) && cBlock->getPassword())
  181. return AUTH_CONTINUE;
  182. // check for presence of authenticatiion block
  183. IAuthBlock* authBlock = cBlock->getAuthBlock(status);
  184. if (!authBlock)
  185. return AUTH_CONTINUE;
  186. if (!authBlock->first(status))
  187. return AUTH_CONTINUE;
  188. // and for presence of user name in that authenticatiion block
  189. const char* login = NULL;
  190. do
  191. {
  192. const char* type = authBlock->getType();
  193. if (type && (strcmp(type, "USER") == 0))
  194. {
  195. login = authBlock->getName();
  196. if (login)
  197. break;
  198. }
  199. } while(authBlock->next(status));
  200. if (!login)
  201. return AUTH_CONTINUE;
  202. // check if server started to talk to us
  203. unsigned dl = 0;
  204. const unsigned char* data = cBlock->getData(&dl);
  205. if (dl == 0 || !data)
  206. return AUTH_MORE_DATA;
  207. // decrypt message
  208. unsigned char bytes[RANDSIZE + LOGINSIZE + 1];
  209. unsigned long outlen = RANDSIZE;
  210. int result = 0;
  211. check(status, rsa_decrypt_key(data, dl, bytes, &outlen, NULL, 0, hash.index, &result, &privateKey),
  212. "Error decrypting message");
  213. if (outlen < RANDSIZE)
  214. error(status, "Malformed data from server - missing random block");
  215. // next append login to random block
  216. unsigned len = strlen(login);
  217. if (len > LOGINSIZE)
  218. len = LOGINSIZE;
  219. memcpy(&bytes[RANDSIZE], login, len);
  220. // calc hash for whole block
  221. hash_state state;
  222. sha256_init(&state);
  223. check(status, sha256_process(&state, bytes, RANDSIZE + len), "Error hashing message");
  224. unsigned char digest[256 / 8];
  225. check(status, sha256_done(&state, digest), "Error extracting hash");
  226. // build message
  227. unsigned char msg[4096];
  228. // put login to it
  229. memcpy(msg, login, len);
  230. msg[len++] = 0;
  231. // append sign of hash to it
  232. unsigned long signLen = sizeof(msg) - len;
  233. unsigned char* sign = &msg[len];
  234. check(status, rsa_sign_hash(digest, sizeof digest, sign, &signLen, &pseudoRand.state,
  235. pseudoRand.index, hash.index, SALTLEN, &privateKey), "Error signing message hash");
  236. // send message
  237. cBlock->putData(status, len + signLen, msg);
  238. // output the wire crypt key
  239. ICryptKey* cKey = cBlock->newKey(status);
  240. cKey->setSymmetric(status, "Symmetric", RANDSIZE, bytes);
  241. HANDSHAKE_DEBUG( fprintf(stderr, "Key ="); for (unsigned n = 0; n < RANDSIZE; ++n)
  242. fprintf(stderr, " %02u", bytes[n]); fprintf(stderr, "\n"); )
  243. return AUTH_SUCCESS;
  244. }
  245. catch(const FbException& ex)
  246. {
  247. status->setErrors(ex.getStatus()->getErrors());
  248. return AUTH_FAILED;
  249. }
  250. }
  251. //
  252. // Server plugin
  253. //
  254. class ExtAuthServer : public IServerImpl<ExtAuthServer, ThrowStatusWrapper>, public PluginData
  255. {
  256. public:
  257. ExtAuthServer(ThrowStatusWrapper* status, IPluginConfig* cnf)
  258. : PluginData(status, cnf), sentData(false)
  259. { }
  260. // IServer implementation
  261. int authenticate(ThrowStatusWrapper* status, IServerBlock* sBlock, IWriter* writerInterface);
  262. void setDbCryptCallback(ThrowStatusWrapper* status, ICryptKeyCallback* cryptCallback)
  263. { }
  264. int release()
  265. {
  266. if (--refCounter == 0)
  267. {
  268. delete this;
  269. return 0;
  270. }
  271. return 1;
  272. }
  273. void addRef()
  274. {
  275. ++refCounter;
  276. }
  277. void setOwner(IReferenceCounted* o)
  278. {
  279. owner = o;
  280. }
  281. IReferenceCounted* getOwner()
  282. {
  283. return owner;
  284. }
  285. private:
  286. unsigned char msg[RANDSIZE + LOGINSIZE];
  287. bool sentData;
  288. };
  289. int ExtAuthServer::authenticate(ThrowStatusWrapper* status, IServerBlock* sBlock, IWriter* writerInterface)
  290. {
  291. try
  292. {
  293. // did we initialize correctly?
  294. if (iniLvl < 3)
  295. return AUTH_CONTINUE;
  296. unsigned dl = 0;
  297. const unsigned char* data = sBlock->getData(&dl);
  298. if (!sentData)
  299. {
  300. // fbassert(dl == 0 && !data);
  301. // build message: first of all get some randomness
  302. pseudoRand.getDsc()->read(msg, RANDSIZE, &pseudoRand.state);
  303. // now encrypt that random block
  304. unsigned char encrypted[4096];
  305. unsigned long encLen = sizeof encrypted;
  306. check(status, rsa_encrypt_key(msg, RANDSIZE, encrypted, &encLen, NULL, 0,
  307. &pseudoRand.state, pseudoRand.index, hash.index, &privateKey), "Error encrypting message");
  308. // send message
  309. sBlock->putData(status, encLen, encrypted);
  310. sentData = true;
  311. return AUTH_MORE_DATA;
  312. }
  313. // decompose message
  314. const char* login = reinterpret_cast<const char*>(data);
  315. unsigned len = strnlen(login, dl);
  316. if (len == dl)
  317. error(status, "Wrong data from client - no signature in a message");
  318. if (len == 0)
  319. error(status, "Wrong data from client - empty login");
  320. if (len > LOGINSIZE)
  321. error(status, "Wrong data from client - login too long");
  322. memcpy(&msg[RANDSIZE], data, len);
  323. const unsigned char* sign = &data[len + 1];
  324. unsigned long signLen = dl - (len + 1);
  325. // calc hash for message
  326. hash_state state;
  327. sha256_init(&state);
  328. check(status, sha256_process(&state, msg, RANDSIZE + len), "Error hashing message");
  329. unsigned char digest[256 / 8];
  330. check(status, sha256_done(&state, digest), "Error extracting hash");
  331. // validate signature
  332. int result = 0;
  333. int err = rsa_verify_hash(sign, signLen, digest, sizeof digest, hash.index, SALTLEN, &result, &privateKey);
  334. if (err != CRYPT_INVALID_PACKET)
  335. check(status, err, "Error verifying digital signature");
  336. else
  337. result = 0;
  338. if (!result)
  339. error(status, "Malformed data from client - invalid digital signature");
  340. // output the wire crypt key
  341. ICryptKey* cKey = sBlock->newKey(status);
  342. cKey->setSymmetric(status, "Symmetric", RANDSIZE, msg);
  343. HANDSHAKE_DEBUG( fprintf(stderr, "Key ="); for (unsigned n = 0; n < RANDSIZE; ++n)
  344. fprintf(stderr, " %02x", msg[n]); fprintf(stderr, "\n"); )
  345. // store received login name in auth block
  346. writerInterface->add(status, login);
  347. return AUTH_SUCCESS;
  348. }
  349. catch(const FbException& ex)
  350. {
  351. status->setErrors(ex.getStatus()->getErrors());
  352. }
  353. return AUTH_FAILED;
  354. }
  355. //
  356. // Static variables
  357. //
  358. PluginModule module;
  359. Factory<ExtAuthClient> clientFactory;
  360. Factory<ExtAuthServer> serverFactory;
  361. } // anonymous namespace
  362. extern "C" FB_DLL_EXPORT void FB_PLUGIN_ENTRY_POINT(IMaster* m)
  363. {
  364. master = m;
  365. IPluginManager* pluginManager = master->getPluginManager();
  366. module.registerMe(pluginManager);
  367. const char* plName = "fbSampleExtAuth";
  368. pluginManager->registerPluginFactory(IPluginManager::TYPE_AUTH_CLIENT, plName, &clientFactory);
  369. pluginManager->registerPluginFactory(IPluginManager::TYPE_AUTH_SERVER, plName, &serverFactory);
  370. }