sshcryptofacility.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /**************************************************************************
  2. **
  3. ** This file is part of Qt Creator
  4. **
  5. ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
  6. **
  7. ** Contact: http://www.qt-project.org/
  8. **
  9. **
  10. ** GNU Lesser General Public License Usage
  11. **
  12. ** This file may be used under the terms of the GNU Lesser General Public
  13. ** License version 2.1 as published by the Free Software Foundation and
  14. ** appearing in the file LICENSE.LGPL included in the packaging of this file.
  15. ** Please review the following information to ensure the GNU Lesser General
  16. ** Public License version 2.1 requirements will be met:
  17. ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  18. **
  19. ** In addition, as a special exception, Nokia gives you certain additional
  20. ** rights. These rights are described in the Nokia Qt LGPL Exception
  21. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  22. **
  23. ** Other Usage
  24. **
  25. ** Alternatively, this file may be used in accordance with the terms and
  26. ** conditions contained in a signed written agreement between you and Nokia.
  27. **
  28. **
  29. **************************************************************************/
  30. #include "sshcryptofacility_p.h"
  31. #include "opensshkeyfilereader_p.h"
  32. #include "sshbotanconversions_p.h"
  33. #include "sshcapabilities_p.h"
  34. #include "sshexception_p.h"
  35. #include "sshkeyexchange_p.h"
  36. #include "sshkeypasswordretriever_p.h"
  37. #include "sshpacket_p.h"
  38. #include "sshlogging_p.h"
  39. #include <botan/block_cipher.h>
  40. #include <botan/hash.h>
  41. #include <botan/pkcs8.h>
  42. #include <botan/dsa.h>
  43. #include <botan/rsa.h>
  44. #include <botan/ber_dec.h>
  45. #include <botan/pubkey.h>
  46. #include <botan/filters.h>
  47. #include <botan/ecdsa.h>
  48. #include <QDebug>
  49. #include <QList>
  50. #include <string>
  51. using namespace Botan;
  52. namespace QSsh {
  53. namespace Internal {
  54. SshAbstractCryptoFacility::SshAbstractCryptoFacility()
  55. : m_cipherBlockSize(0), m_macLength(0)
  56. {
  57. }
  58. SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
  59. void SshAbstractCryptoFacility::clearKeys()
  60. {
  61. m_cipherBlockSize = 0;
  62. m_macLength = 0;
  63. m_sessionId.clear();
  64. m_pipe.reset(nullptr);
  65. m_hMac.reset(nullptr);
  66. }
  67. SshAbstractCryptoFacility::Mode SshAbstractCryptoFacility::getMode(const QByteArray &algoName)
  68. {
  69. if (algoName.endsWith("-ctr"))
  70. return CtrMode;
  71. if (algoName.endsWith("-cbc"))
  72. return CbcMode;
  73. throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
  74. .arg(QString::fromLatin1(algoName)));
  75. }
  76. void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
  77. {
  78. checkInvariant();
  79. if (m_sessionId.isEmpty())
  80. m_sessionId = kex.h();
  81. const QByteArray &rfcCryptAlgoName = cryptAlgoName(kex);
  82. { // Don't know how else to get this with the new botan API
  83. std::unique_ptr<BlockCipher> cipher
  84. = BlockCipher::create_or_throw(botanCryptAlgoName(rfcCryptAlgoName));
  85. m_cipherBlockSize = static_cast<quint32>(cipher->block_size());
  86. }
  87. const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
  88. const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
  89. Keyed_Filter * const cipherMode
  90. = makeCipherMode(botanCipherAlgoName(rfcCryptAlgoName), getMode(rfcCryptAlgoName));
  91. const quint32 keySize = static_cast<quint32>(cipherMode->key_spec().maximum_keylength());
  92. const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
  93. SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
  94. cipherMode->set_key(cryptKey);
  95. cipherMode->set_iv(iv);
  96. m_pipe.reset(new Pipe(cipherMode));
  97. m_macLength = botanHMacKeyLen(hMacAlgoName(kex));
  98. const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength());
  99. SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength());
  100. m_hMac = MessageAuthenticationCode::create_or_throw("HMAC(" + std::string(botanHMacAlgoName(hMacAlgoName(kex))) + ")");
  101. m_hMac->set_key(hMacKey);
  102. }
  103. void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset,
  104. quint32 dataSize) const
  105. {
  106. Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size()));
  107. checkInvariant();
  108. // Session id empty => No key exchange has happened yet.
  109. if (dataSize == 0 || m_sessionId.isEmpty())
  110. return;
  111. if (dataSize % cipherBlockSize() != 0) {
  112. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  113. "Invalid packet size");
  114. }
  115. m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset,
  116. dataSize);
  117. // Can't use Pipe::LAST_MESSAGE because of a VC bug.
  118. quint32 bytesRead = static_cast<quint32>(m_pipe->read(
  119. reinterpret_cast<byte *>(data.data()) + offset, dataSize, m_pipe->message_count() - 1));
  120. if (bytesRead != dataSize) {
  121. throw SshClientException(SshInternalError,
  122. QLatin1String("Internal error: Botan::Pipe::read() returned unexpected value"));
  123. }
  124. }
  125. Keyed_Filter *SshAbstractCryptoFacility::makeCtrCipherMode(const QByteArray &cipher)
  126. {
  127. StreamCipher_Filter *filter = new StreamCipher_Filter(cipher.toStdString());
  128. return filter;
  129. }
  130. QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
  131. quint32 dataSize) const
  132. {
  133. return m_sessionId.isEmpty()
  134. ? QByteArray()
  135. : convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
  136. dataSize));
  137. }
  138. QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
  139. char c, quint32 length)
  140. {
  141. const QByteArray &k = kex.k();
  142. const QByteArray &h = kex.h();
  143. QByteArray data(k);
  144. data.append(h).append(c).append(m_sessionId);
  145. SecureVector<byte> key
  146. = kex.hash()->process(convertByteArray(data), data.size());
  147. while (key.size() < length) {
  148. secure_vector<byte> tmpKey;
  149. tmpKey += secure_vector<byte>(k.begin(), k.end());
  150. tmpKey += secure_vector<byte>(h.begin(), h.end());
  151. tmpKey += key;
  152. key += kex.hash()->process(tmpKey);
  153. }
  154. return QByteArray(reinterpret_cast<const char *>(key.data()), length);
  155. }
  156. void SshAbstractCryptoFacility::checkInvariant() const
  157. {
  158. Q_ASSERT(m_sessionId.isEmpty() == !m_pipe);
  159. }
  160. const QByteArray SshEncryptionFacility::PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
  161. const QByteArray SshEncryptionFacility::PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
  162. const QByteArray SshEncryptionFacility::PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
  163. const QByteArray SshEncryptionFacility::PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");
  164. const QByteArray SshEncryptionFacility::PrivKeyFileStartLineEcdsa("-----BEGIN EC PRIVATE KEY-----");
  165. const QByteArray SshEncryptionFacility::PrivKeyFileEndLineEcdsa("-----END EC PRIVATE KEY-----");
  166. QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
  167. {
  168. return kex.encryptionAlgo();
  169. }
  170. QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
  171. {
  172. return kex.hMacAlgoClientToServer();
  173. }
  174. Keyed_Filter *SshEncryptionFacility::makeCipherMode(const QByteArray &cipher, const Mode mode)
  175. {
  176. if (mode == CtrMode) {
  177. return new StreamCipher_Filter(cipher.toStdString());
  178. }
  179. qWarning() << "I haven't been able to test the CBC encryption modes, so if this files file a bug at https://github.com/sandsmark/QSsh";
  180. Cipher_Mode_Filter *filter = new Cipher_Mode_Filter(
  181. Cipher_Mode::create_or_throw(cipher.toStdString(), ENCRYPTION).release()); // We have to release, otherwise clang fails to link
  182. return filter;
  183. }
  184. void SshEncryptionFacility::encrypt(QByteArray &data) const
  185. {
  186. convert(data, 0, data.size());
  187. }
  188. void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
  189. {
  190. if (privKeyFileContents == m_cachedPrivKeyContents)
  191. return;
  192. m_authKeyAlgoName.clear();
  193. qCDebug(sshLog, "%s: Key not cached, reading", Q_FUNC_INFO);
  194. QList<BigInt> pubKeyParams;
  195. QList<BigInt> allKeyParams;
  196. QString error1;
  197. QString error2;
  198. OpenSshKeyFileReader openSshReader(m_rng);
  199. if (openSshReader.parseKey(privKeyFileContents)) {
  200. m_authKeyAlgoName = openSshReader.keyType();
  201. m_authKey.reset(openSshReader.privateKey().release());
  202. pubKeyParams = openSshReader.publicParameters();
  203. allKeyParams = openSshReader.allParameters();
  204. } else if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams,
  205. error1)
  206. && !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
  207. error2)) {
  208. qCDebug(sshLog, "%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
  209. throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
  210. "Format not understood."));
  211. }
  212. for (const BigInt &b : allKeyParams) {
  213. if (b.is_zero()) {
  214. throw SshClientException(SshKeyFileError,
  215. SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
  216. }
  217. }
  218. m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
  219. auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data());
  220. if (ecdsaKey) {
  221. m_authPubKeyBlob += AbstractSshPacket::encodeString(m_authKeyAlgoName.mid(11)); // Without "ecdsa-sha2-" prefix.
  222. m_authPubKeyBlob += AbstractSshPacket::encodeString(
  223. convertByteArray(ecdsaKey->public_point().encode(PointGFp::UNCOMPRESSED)));
  224. } else {
  225. for (const BigInt &b : pubKeyParams) {
  226. m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
  227. }
  228. }
  229. m_cachedPrivKeyContents = privKeyFileContents;
  230. }
  231. bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
  232. QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
  233. {
  234. try {
  235. Pipe pipe;
  236. pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
  237. m_authKey.reset(PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever::get_passphrase));
  238. if (auto * const dsaKey = dynamic_cast<DSA_PrivateKey *>(m_authKey.data())) {
  239. m_authKeyAlgoName = SshCapabilities::PubKeyDss;
  240. pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
  241. << dsaKey->group_g() << dsaKey->get_y();
  242. allKeyParams << pubKeyParams << dsaKey->get_x();
  243. } else if (auto * const rsaKey = dynamic_cast<RSA_PrivateKey *>(m_authKey.data())) {
  244. m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
  245. pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
  246. allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
  247. << rsaKey->get_d();
  248. } else if (auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data())) {
  249. const BigInt value = ecdsaKey->private_value();
  250. m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
  251. static_cast<int>(value.bytes()));
  252. pubKeyParams << ecdsaKey->public_point().get_affine_x()
  253. << ecdsaKey->public_point().get_affine_y();
  254. allKeyParams << pubKeyParams << value;
  255. } else {
  256. qCWarning(sshLog, "%s: Unexpected code flow, expected success or exception.",
  257. Q_FUNC_INFO);
  258. return false;
  259. }
  260. } catch (const std::exception &ex) {
  261. error = QLatin1String(ex.what());
  262. return false;
  263. }
  264. return true;
  265. }
  266. bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
  267. QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
  268. {
  269. try {
  270. bool syntaxOk = true;
  271. QList<QByteArray> lines = privKeyFileContents.split('\n');
  272. while (lines.last().isEmpty())
  273. lines.removeLast();
  274. if (lines.count() < 3) {
  275. syntaxOk = false;
  276. } else if (lines.first() == PrivKeyFileStartLineRsa) {
  277. if (lines.last() != PrivKeyFileEndLineRsa)
  278. syntaxOk = false;
  279. else
  280. m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
  281. } else if (lines.first() == PrivKeyFileStartLineDsa) {
  282. if (lines.last() != PrivKeyFileEndLineDsa)
  283. syntaxOk = false;
  284. else
  285. m_authKeyAlgoName = SshCapabilities::PubKeyDss;
  286. } else if (lines.first() == PrivKeyFileStartLineEcdsa) {
  287. if (lines.last() != PrivKeyFileEndLineEcdsa)
  288. syntaxOk = false;
  289. // m_authKeyAlgoName set below, as we don't know the size yet.
  290. } else {
  291. syntaxOk = false;
  292. }
  293. if (!syntaxOk) {
  294. error = SSH_TR("Unexpected format.");
  295. return false;
  296. }
  297. QByteArray privateKeyBlob;
  298. for (int i = 1; i < lines.size() - 1; ++i)
  299. privateKeyBlob += lines.at(i);
  300. privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
  301. BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
  302. BER_Decoder sequence = decoder.start_cons(SEQUENCE);
  303. size_t version;
  304. sequence.decode (version);
  305. const size_t expectedVersion = m_authKeyAlgoName.isEmpty() ? 1 : 0;
  306. if (version != expectedVersion) {
  307. error = SSH_TR("Key encoding has version %1, expected %2.")
  308. .arg(version).arg(expectedVersion);
  309. return false;
  310. }
  311. if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
  312. BigInt p, q, g, y, x;
  313. sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
  314. DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
  315. m_authKey.reset(dsaKey);
  316. pubKeyParams << p << q << g << y;
  317. allKeyParams << pubKeyParams << x;
  318. } else if (m_authKeyAlgoName == SshCapabilities::PubKeyRsa) {
  319. BigInt p, q, e, d, n;
  320. sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
  321. RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(p, q, e, d, n);
  322. m_authKey.reset(rsaKey);
  323. pubKeyParams << e << n;
  324. allKeyParams << pubKeyParams << p << q << d;
  325. } else {
  326. BigInt privKey;
  327. sequence.decode_octet_string_bigint(privKey);
  328. m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
  329. static_cast<int>(privKey.bytes()));
  330. const EC_Group group(SshCapabilities::oid(m_authKeyAlgoName));
  331. auto * const key = new ECDSA_PrivateKey(m_rng, group, privKey);
  332. m_authKey.reset(key);
  333. pubKeyParams << key->public_point().get_affine_x()
  334. << key->public_point().get_affine_y();
  335. allKeyParams << pubKeyParams << privKey;
  336. }
  337. sequence.discard_remaining();
  338. sequence.verify_end();
  339. } catch (const std::exception &ex) {
  340. error = QLatin1String(ex.what());
  341. return false;
  342. }
  343. return true;
  344. }
  345. QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
  346. {
  347. Q_ASSERT(m_authKey);
  348. return m_authKeyAlgoName;
  349. }
  350. QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
  351. {
  352. Q_ASSERT(m_authKey);
  353. QScopedPointer<PK_Signer> signer(new PK_Signer(*m_authKey,
  354. m_rng,
  355. botanEmsaAlgoName(m_authKeyAlgoName)));
  356. QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
  357. QByteArray signature
  358. = convertByteArray(signer->sign_message(convertByteArray(dataToSign),
  359. dataToSign.size(), m_rng));
  360. if (m_authKeyAlgoName.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
  361. // The Botan output is not quite in the format that SSH defines.
  362. const int halfSize = signature.count() / 2;
  363. const BigInt r = BigInt::decode(convertByteArray(signature), halfSize);
  364. const BigInt s = BigInt::decode(convertByteArray(signature.mid(halfSize)), halfSize);
  365. signature = AbstractSshPacket::encodeMpInt(r) + AbstractSshPacket::encodeMpInt(s);
  366. }
  367. return AbstractSshPacket::encodeString(m_authKeyAlgoName)
  368. + AbstractSshPacket::encodeString(signature);
  369. }
  370. QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
  371. {
  372. QByteArray data;
  373. data.resize(count);
  374. m_rng.randomize(convertByteArray(data), count);
  375. return data;
  376. }
  377. SshEncryptionFacility::~SshEncryptionFacility() {}
  378. QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
  379. {
  380. return kex.decryptionAlgo();
  381. }
  382. QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
  383. {
  384. return kex.hMacAlgoServerToClient();
  385. }
  386. Keyed_Filter *SshDecryptionFacility::makeCipherMode(const QByteArray &cipher, const Mode mode)
  387. {
  388. if (mode == CtrMode) {
  389. return new StreamCipher_Filter(cipher.toStdString());
  390. }
  391. qWarning() << "I haven't been able to test the CBC decryption modes, so if this files file a bug at https://github.com/sandsmark/QSsh";
  392. Cipher_Mode_Filter *filter = new Cipher_Mode_Filter(
  393. Cipher_Mode::create_or_throw(cipher.toStdString(), DECRYPTION).release()); // We have to release, otherwise clang fails to link
  394. return filter;
  395. }
  396. void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
  397. quint32 dataSize) const
  398. {
  399. convert(data, offset, dataSize);
  400. qCDebug(sshLog, "Decrypted data:");
  401. const char * const start = data.constData() + offset;
  402. const char * const end = start + dataSize;
  403. for (const char *c = start; c < end; ++c)
  404. qCDebug(sshLog) << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
  405. }
  406. } // namespace Internal
  407. } // namespace QSsh