Commit f8a8c6ca authored by Christian Kandeler's avatar Christian Kandeler

SSH: Do not tie key exchange methods to specific host key algorithms.

Contrary to what I believed, all key exchange methods are compatible
with all types of host key (at least for everything we support).

Task-number: QTCREATORBUG-14621
Change-Id: Iacae3d36170b9841cceb3705780a64aeb15e10b9
Reviewed-by: default avatarBenjamin Zeller <benjamin.zeller@canonical.com>
Reviewed-by: default avatarChristian Kandeler <christian.kandeler@theqtcompany.com>
parent 3c1b1496
......@@ -118,11 +118,16 @@ inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName)
inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
{
Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1
|| rfcAlgoName == SshCapabilities::HMacSha256);
if (rfcAlgoName == SshCapabilities::HMacSha1)
return 20;
return 32;
if (rfcAlgoName == SshCapabilities::HMacSha256)
return 32;
if (rfcAlgoName == SshCapabilities::HMacSha384)
return 48;
if (rfcAlgoName == SshCapabilities::HMacSha512)
return 64;
throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
.arg(QString::fromLatin1(rfcAlgoName)));
}
} // namespace Internal
......
......@@ -30,6 +30,7 @@
#include "sshincomingpacket_p.h"
#include "ssh_global.h"
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
......@@ -169,7 +170,32 @@ SshKeyExchangeInit SshIncomingPacket::extractKeyExchangeInitData() const
return exchangeData;
}
SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const
static void getHostKeySpecificReplyData(SshKeyExchangeReply &replyData,
const QByteArray &hostKeyAlgo, const QByteArray &input)
{
quint32 offset = 0;
if (hostKeyAlgo == SshCapabilities::PubKeyDss || hostKeyAlgo == SshCapabilities::PubKeyRsa) {
// DSS: p and q, RSA: e and n
replyData.parameters << SshPacketParser::asBigInt(input, &offset);
replyData.parameters << SshPacketParser::asBigInt(input, &offset);
// g and y
if (hostKeyAlgo == SshCapabilities::PubKeyDss) {
replyData.parameters << SshPacketParser::asBigInt(input, &offset);
replyData.parameters << SshPacketParser::asBigInt(input, &offset);
}
} else {
QSSH_ASSERT_AND_RETURN(hostKeyAlgo == SshCapabilities::PubKeyEcdsa);
if (SshPacketParser::asString(input, &offset)
!= hostKeyAlgo.mid(11)) { // Without "ecdsa-sha2-" prefix.
throw SshPacketParseException();
}
replyData.q = SshPacketParser::asString(input, &offset);
}
}
SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &kexAlgo,
const QByteArray &hostKeyAlgo) const
{
Q_ASSERT(isComplete());
Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY);
......@@ -179,35 +205,23 @@ SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray
quint32 topLevelOffset = TypeOffset + 1;
replyData.k_s = SshPacketParser::asString(m_data, &topLevelOffset);
quint32 k_sOffset = 0;
if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != pubKeyAlgo)
if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != hostKeyAlgo)
throw SshPacketParseException();
getHostKeySpecificReplyData(replyData, hostKeyAlgo, replyData.k_s.mid(k_sOffset));
if (pubKeyAlgo == SshCapabilities::PubKeyDss || pubKeyAlgo == SshCapabilities::PubKeyRsa) {
// DSS: p and q, RSA: e and n
replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset);
replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset);
// g and y
if (pubKeyAlgo == SshCapabilities::PubKeyDss) {
replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset);
replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset);
}
if (kexAlgo == SshCapabilities::DiffieHellmanGroup1Sha1) {
replyData.f = SshPacketParser::asBigInt(m_data, &topLevelOffset);
} else {
Q_ASSERT(pubKeyAlgo == SshCapabilities::PubKeyEcdsa);
if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != pubKeyAlgo.mid(11)) // Without "ecdsa-sha2-" prefix.
throw SshPacketParseException();
replyData.q = SshPacketParser::asString(replyData.k_s, &k_sOffset);
QSSH_ASSERT_AND_RETURN_VALUE(kexAlgo.startsWith(SshCapabilities::EcdhKexNamePrefix),
SshKeyExchangeReply());
replyData.q_s = SshPacketParser::asString(m_data, &topLevelOffset);
}
const QByteArray fullSignature = SshPacketParser::asString(m_data, &topLevelOffset);
quint32 sigOffset = 0;
if (SshPacketParser::asString(fullSignature, &sigOffset) != pubKeyAlgo)
if (SshPacketParser::asString(fullSignature, &sigOffset) != hostKeyAlgo)
throw SshPacketParseException();
replyData.signatureBlob = SshPacketParser::asString(fullSignature, &sigOffset);
if (pubKeyAlgo == SshCapabilities::PubKeyEcdsa) {
if (hostKeyAlgo == SshCapabilities::PubKeyEcdsa) {
// Botan's PK_Verifier wants the signature in this format.
quint32 blobOffset = 0;
const Botan::BigInt r = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset);
......
......@@ -164,7 +164,8 @@ public:
void reset();
SshKeyExchangeInit extractKeyExchangeInitData() const;
SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const;
SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &kexAlgo,
const QByteArray &hostKeyAlgo) const;
SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const;
SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
......
......@@ -30,6 +30,7 @@
#include "sshkeyexchange_p.h"
#include "ssh_global.h"
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
#include "sshsendfacility_p.h"
......@@ -115,28 +116,8 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
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)));
}
m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
kexInitParams.serverHostKeyAlgorithms.names);
determineHashingAlgorithm(kexInitParams, true);
determineHashingAlgorithm(kexInitParams, false);
......@@ -152,7 +133,7 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
kexInitParams.compressionAlgorithmsServerToClient.names);
AutoSeeded_RNG rng;
if (ecdh) {
if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value()));
} else {
......@@ -169,7 +150,7 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
{
const SshKeyExchangeReply &reply
= dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo);
= dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo);
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.");
......@@ -187,6 +168,7 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
DH_KA_Operation dhOp(*m_dhKey);
SecureVector<byte> encodedF = BigInt::encode(reply.f);
encodedK = dhOp.agree(encodedF, encodedF.size());
m_dhKey.reset(nullptr);
} else {
Q_ASSERT(m_ecdhKey);
concatenatedData // Q_C.
......@@ -194,7 +176,9 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
concatenatedData += AbstractSshPacket::encodeString(reply.q_s);
ECDH_KA_Operation ecdhOp(*m_ecdhKey);
encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count());
m_ecdhKey.reset(nullptr);
}
const BigInt k = BigInt::decode(encodedK);
m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently.
concatenatedData += m_k;
......@@ -228,13 +212,13 @@ 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) {
} else {
QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo == SshCapabilities::PubKeyEcdsa);
const EC_Group domain("secp256r1");
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);
domain.get_curve());
ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point);
sigKey.reset(ecdsaKey);
} else {
Q_ASSERT(!"Impossible: Neither DSS nor RSA nor ECDSA!");
}
const byte * const botanH = convertByteArray(m_h);
......@@ -248,8 +232,6 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
checkHostKey(reply.k_s);
m_sendFacility.sendNewKeysPacket();
m_dhKey.reset(nullptr);
m_ecdhKey.reset(nullptr);
}
QByteArray SshKeyExchange::hashAlgoForKexAlgo() const
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment