diff options
author | Christian Kandeler <[email protected]> | 2015-05-26 10:25:56 +0200 |
---|---|---|
committer | Christian Kandeler <[email protected]> | 2015-05-28 09:07:24 +0000 |
commit | 3b8ea3fc1ea5a3be3e537fc6a6390886eaaff62e (patch) | |
tree | 9b79452d230296d9f41fd277c1f47b2a9d4d0111 /src/libs/ssh/sshkeyexchange.cpp | |
parent | 244cdb7804e7e45f235836d4656644d5bf9135ee (diff) |
SSH: Support ECDH key exchange.
As per RFC 5656.
Task-number: QTCREATORBUG-14025
Change-Id: I623c9f0808967f140cdfb40e11234c2e523484e6
Reviewed-by: Joerg Bornemann <[email protected]>
Reviewed-by: Christian Kandeler <[email protected]>
Diffstat (limited to 'src/libs/ssh/sshkeyexchange.cpp')
-rw-r--r-- | src/libs/ssh/sshkeyexchange.cpp | 138 |
1 files changed, 107 insertions, 31 deletions
diff --git a/src/libs/ssh/sshkeyexchange.cpp b/src/libs/ssh/sshkeyexchange.cpp index 1f90417c1f1..528a117908e 100644 --- a/src/libs/ssh/sshkeyexchange.cpp +++ b/src/libs/ssh/sshkeyexchange.cpp @@ -113,44 +113,64 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit) qDebug("First packet follows: %d", kexInitParams.firstKexPacketFollows); #endif - const QByteArray &keyAlgo - = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods, - kexInitParams.keyAlgorithms.names); - m_serverHostKeyAlgo - = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms, - kexInitParams.serverHostKeyAlgorithms.names); + m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods, + kexInitParams.keyAlgorithms.names); + const QList<QByteArray> &commonHostKeyAlgos + = SshCapabilities::commonCapabilities(SshCapabilities::PublicKeyAlgorithms, + kexInitParams.serverHostKeyAlgorithms.names); + const bool ecdh = m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix); + foreach (const QByteArray &possibleHostKeyAlgo, commonHostKeyAlgos) { + if (ecdh && possibleHostKeyAlgo == SshCapabilities::PubKeyEcdsa) { + m_serverHostKeyAlgo = possibleHostKeyAlgo; + break; + } + if (!ecdh && (possibleHostKeyAlgo == SshCapabilities::PubKeyDss + || possibleHostKeyAlgo == SshCapabilities::PubKeyRsa)) { + m_serverHostKeyAlgo = possibleHostKeyAlgo; + break; + } + } + if (m_serverHostKeyAlgo.isEmpty()) { + throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Invalid combination of key exchange and host key algorithms.", + QCoreApplication::translate("SshConnection", + "No matching host key algorithm available for key exchange algorithm '%1'.") + .arg(QString::fromLatin1(m_kexAlgoName))); + } + determineHashingAlgorithm(kexInitParams, true); + determineHashingAlgorithm(kexInitParams, false); + m_encryptionAlgo = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, kexInitParams.encryptionAlgorithmsClientToServer.names); m_decryptionAlgo = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, kexInitParams.encryptionAlgorithmsServerToClient.names); - m_c2sHMacAlgo - = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, - kexInitParams.macAlgorithmsClientToServer.names); - m_s2cHMacAlgo - = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, - kexInitParams.macAlgorithmsServerToClient.names); SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, kexInitParams.compressionAlgorithmsClientToServer.names); SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, kexInitParams.compressionAlgorithmsServerToClient.names); AutoSeeded_RNG rng; - m_dhKey.reset(new DH_PrivateKey(rng, - DL_Group(botanKeyExchangeAlgoName(keyAlgo)))); + if (ecdh) { + m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName)))); + m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value())); + } else { + m_dhKey.reset(new DH_PrivateKey(rng, DL_Group(botanKeyExchangeAlgoName(m_kexAlgoName)))); + m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y()); + } m_serverKexInitPayload = serverKexInit.payLoad(); - m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y()); return kexInitParams.firstKexPacketFollows; } void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, const QByteArray &clientId) { + const SshKeyExchangeReply &reply = dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo); - if (reply.f <= 0 || reply.f >= m_dhKey->group_p()) { + if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, "Server sent invalid f."); } @@ -160,19 +180,28 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload); concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload); concatenatedData += reply.k_s; - concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y()); - concatenatedData += AbstractSshPacket::encodeMpInt(reply.f); - DH_KA_Operation dhOp(*m_dhKey); - SecureVector<byte> encodedF = BigInt::encode(reply.f); - SecureVector<byte> encodedK = dhOp.agree(encodedF, encodedF.size()); + SecureVector<byte> encodedK; + if (m_dhKey) { + concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y()); + concatenatedData += AbstractSshPacket::encodeMpInt(reply.f); + DH_KA_Operation dhOp(*m_dhKey); + SecureVector<byte> encodedF = BigInt::encode(reply.f); + encodedK = dhOp.agree(encodedF, encodedF.size()); + } else { + Q_ASSERT(m_ecdhKey); + concatenatedData // Q_C. + += AbstractSshPacket::encodeString(convertByteArray(m_ecdhKey->public_value())); + concatenatedData += AbstractSshPacket::encodeString(reply.q_s); + ECDH_KA_Operation ecdhOp(*m_ecdhKey); + encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count()); + } const BigInt k = BigInt::decode(encodedK); m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently. concatenatedData += m_k; - m_hash.reset(get_hash(botanSha1Name())); - const SecureVector<byte> &hashResult - = m_hash->process(convertByteArray(concatenatedData), - concatenatedData.size()); + m_hash.reset(get_hash(botanHMacAlgoName(hashAlgoForKexAlgo()))); + const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData), + concatenatedData.size()); m_h = convertByteArray(hashResult); #ifdef CREATOR_SSH_DEBUG @@ -199,22 +228,69 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, RSA_PublicKey * const rsaKey = new RSA_PublicKey(reply.parameters.at(1), reply.parameters.at(0)); sigKey.reset(rsaKey); + } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyEcdsa) { + const PointGFp point = OS2ECP(convertByteArray(reply.q), reply.q.count(), + m_ecdhKey->domain().get_curve()); + ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(m_ecdhKey->domain(), point); + sigKey.reset(ecdsaKey); } else { - Q_ASSERT(!"Impossible: Neither DSS nor RSA!"); + Q_ASSERT(!"Impossible: Neither DSS nor RSA nor ECDSA!"); } + const byte * const botanH = convertByteArray(m_h); - const Botan::byte * const botanSig - = convertByteArray(reply.signatureBlob); + const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob); PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo)); - if (!verifier.verify_message(botanH, m_h.size(), botanSig, - reply.signatureBlob.size())) { + if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - "Invalid signature in SSH_MSG_KEXDH_REPLY packet."); + "Invalid signature in key exchange reply packet."); } checkHostKey(reply.k_s); m_sendFacility.sendNewKeysPacket(); + m_dhKey.reset(nullptr); + m_ecdhKey.reset(nullptr); +} + +QByteArray SshKeyExchange::hashAlgoForKexAlgo() const +{ + if (m_kexAlgoName == SshCapabilities::EcdhNistp256) + return SshCapabilities::HMacSha256; + if (m_kexAlgoName == SshCapabilities::EcdhNistp384) + return SshCapabilities::HMacSha384; + if (m_kexAlgoName == SshCapabilities::EcdhNistp521) + return SshCapabilities::HMacSha512; + return SshCapabilities::HMacSha1; +} + +void SshKeyExchange::determineHashingAlgorithm(const SshKeyExchangeInit &kexInit, + bool serverToClient) +{ + QByteArray * const algo = serverToClient ? &m_s2cHMacAlgo : &m_c2sHMacAlgo; + const QList<QByteArray> &serverCapabilities = serverToClient + ? kexInit.macAlgorithmsServerToClient.names + : kexInit.macAlgorithmsClientToServer.names; + const QList<QByteArray> commonAlgos = SshCapabilities::commonCapabilities( + SshCapabilities::MacAlgorithms, serverCapabilities); + const QByteArray hashAlgo = hashAlgoForKexAlgo(); + foreach (const QByteArray &potentialAlgo, commonAlgos) { + if (potentialAlgo == hashAlgo + || !m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) { + *algo = potentialAlgo; + break; + } + } + + if (algo->isEmpty()) { + throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Invalid combination of key exchange and hashing algorithms.", + QCoreApplication::translate("SshConnection", + "Server requested invalid combination of key exchange and hashing algorithms. " + "Key exchange algorithm list was: %1.\nHashing algorithm list was %2.") + .arg(QString::fromLocal8Bit(kexInit.keyAlgorithms.names.join(", "))) + .arg(QString::fromLocal8Bit(serverCapabilities.join(", ")))); + + } } void SshKeyExchange::checkHostKey(const QByteArray &hostKey) |