1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
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.
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "sshcryptofacility_p.h"
35 #include "sshbotanconversions_p.h"
36 #include "sshcapabilities_p.h"
37 #include "sshexception_p.h"
38 #include "sshkeyexchange_p.h"
39 #include "sshpacket_p.h"
41 #include <botan/ber_dec.h>
42 #include <botan/botan.h>
43 #include <botan/cbc.h>
44 #include <botan/dsa.h>
45 #include <botan/hash.h>
46 #include <botan/hmac.h>
47 #include <botan/look_pk.h>
48 #include <botan/pipe.h>
49 #include <botan/pkcs8.h>
50 #include <botan/pubkey.h>
51 #include <botan/rsa.h>
53 #include <QtCore/QDebug>
54 #include <QtCore/QList>
58 using namespace Botan;
63 SshAbstractCryptoFacility::SshAbstractCryptoFacility()
64 : m_cipherBlockSize(0), m_macLength(0)
68 SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
70 void SshAbstractCryptoFacility::clearKeys()
72 m_cipherBlockSize = 0;
79 void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
83 if (m_sessionId.isEmpty())
84 m_sessionId = kex.h();
85 Algorithm_Factory &af = global_state().algorithm_factory();
86 const std::string &cryptAlgo = botanCryptAlgoName(cryptAlgoName(kex));
87 BlockCipher * const cipher = af.prototype_block_cipher(cryptAlgo)->clone();
89 m_cipherBlockSize = cipher->BLOCK_SIZE;
90 const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
91 const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
93 const quint32 keySize = max_keylength_of(cryptAlgo);
94 const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
95 SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
97 BlockCipherMode * const cipherMode
98 = makeCipherMode(cipher, new Null_Padding, iv, cryptKey);
99 m_pipe.reset(new Pipe(cipherMode));
101 m_macLength = botanHMacKeyLen(hMacAlgoName(kex));
102 const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength());
103 SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength());
104 const HashFunction * const hMacProto
105 = af.prototype_hash_function(botanHMacAlgoName(hMacAlgoName(kex)));
106 m_hMac.reset(new HMAC(hMacProto->clone()));
107 m_hMac->set_key(hMacKey);
110 void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset,
111 quint32 dataSize) const
113 Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size()));
116 // Session id empty => No key exchange has happened yet.
117 if (dataSize == 0 || m_sessionId.isEmpty())
120 if (dataSize % cipherBlockSize() != 0) {
121 throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
122 "Invalid packet size");
124 m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset,
126 quint32 bytesRead = m_pipe->read(reinterpret_cast<byte *>(data.data()) + offset,
127 dataSize, m_pipe->message_count() - 1); // Can't use Pipe::LAST_MESSAGE because of a VC bug.
128 Q_ASSERT(bytesRead == dataSize);
131 QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
132 quint32 dataSize) const
134 return m_sessionId.isEmpty()
136 : convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
140 QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
141 char c, quint32 length)
143 const QByteArray &k = kex.k();
144 const QByteArray &h = kex.h();
146 data.append(h).append(c).append(m_sessionId);
147 SecureVector<byte> key
148 = kex.hash()->process(convertByteArray(data), data.size());
149 while (key.size() < length) {
150 SecureVector<byte> tmpKey;
151 tmpKey.append(convertByteArray(k), k.size());
152 tmpKey.append(convertByteArray(h), h.size());
154 key.append(kex.hash()->process(tmpKey));
156 return QByteArray(reinterpret_cast<const char *>(key.begin()), length);
159 void SshAbstractCryptoFacility::checkInvariant() const
161 Q_ASSERT(m_sessionId.isEmpty() == !m_pipe);
165 const QByteArray SshEncryptionFacility::PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
166 const QByteArray SshEncryptionFacility::PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
167 const QByteArray SshEncryptionFacility::PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
168 const QByteArray SshEncryptionFacility::PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");
170 QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
172 return kex.encryptionAlgo();
175 QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
177 return kex.hMacAlgoClientToServer();
180 BlockCipherMode *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher,
181 BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
182 const SymmetricKey &key)
184 return new CBC_Encryption(cipher, paddingMethod, key, iv);
187 void SshEncryptionFacility::encrypt(QByteArray &data) const
189 convert(data, 0, data.size());
192 void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
194 if (privKeyFileContents == m_cachedPrivKeyContents)
197 #ifdef CREATOR_SSH_DEBUG
198 qDebug("%s: Key not cached, reading", Q_FUNC_INFO);
200 QList<BigInt> pubKeyParams;
201 QList<BigInt> allKeyParams;
203 createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams,
205 } catch (Botan::Exception &) {
206 createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams,
210 foreach (const BigInt &b, allKeyParams) {
212 throw SshClientException(SshKeyFileError,
213 SSH_TR("Decoding of private key file failed."));
217 m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
218 foreach (const BigInt &b, pubKeyParams)
219 m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
220 m_cachedPrivKeyContents = privKeyFileContents;
223 void SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
224 QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
227 pipe.process_msg(convertByteArray(privKeyFileContents),
228 privKeyFileContents.size());
229 Private_Key * const key = PKCS8::load_key(pipe, m_rng);
230 if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) {
231 m_authKey.reset(dsaKey);
232 pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
233 << dsaKey->group_g() << dsaKey->get_y();
234 allKeyParams << pubKeyParams << dsaKey->get_x();
235 } else if (RSA_PrivateKey * const rsaKey = dynamic_cast<RSA_PrivateKey *>(key)) {
236 m_authKey.reset(rsaKey);
237 pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
238 allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
241 throw Botan::Exception();
245 void SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
246 QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
248 bool syntaxOk = true;
249 QList<QByteArray> lines = privKeyFileContents.split('\n');
250 while (lines.last().isEmpty())
252 if (lines.count() < 3) {
254 } else if (lines.first() == PrivKeyFileStartLineRsa) {
255 if (lines.last() != PrivKeyFileEndLineRsa)
258 m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
259 } else if (lines.first() == PrivKeyFileStartLineDsa) {
260 if (lines.last() != PrivKeyFileEndLineDsa)
263 m_authKeyAlgoName = SshCapabilities::PubKeyDss;
268 throw SshClientException(SshKeyFileError,
269 SSH_TR("Private key file has unexpected format."));
272 QByteArray privateKeyBlob;
273 for (int i = 1; i < lines.size() - 1; ++i)
274 privateKeyBlob += lines.at(i);
275 privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
277 BER_Decoder decoder(convertByteArray(privateKeyBlob),
278 privateKeyBlob.size());
279 BER_Decoder sequence = decoder.start_cons(SEQUENCE);
281 sequence.decode (version);
283 throw SshClientException(SshKeyFileError,
284 SSH_TR("Private key encoding has version %1, expected 0.")
288 if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
289 BigInt p, q, g, y, x;
290 sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
291 DSA_PrivateKey * const dsaKey
292 = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
293 m_authKey.reset(dsaKey);
294 pubKeyParams << p << q << g << y;
295 allKeyParams << pubKeyParams << x;
297 BigInt p, q, e, d, n;
298 sequence.decode (n).decode (e).decode (d).decode (p).decode (q);
299 RSA_PrivateKey * const rsaKey
300 = new RSA_PrivateKey (m_rng, p, q, e, d, n);
301 m_authKey.reset(rsaKey);
302 pubKeyParams << e << n;
303 allKeyParams << pubKeyParams << p << q << d;
306 sequence.discard_remaining();
307 sequence.verify_end();
310 QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
313 return m_authKeyAlgoName;
316 QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
320 QScopedPointer<PK_Signer> signer(get_pk_signer (*m_authKey,
321 botanEmsaAlgoName(m_authKeyAlgoName)));
322 QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
324 = convertByteArray(signer->sign_message(convertByteArray(dataToSign),
325 dataToSign.size(), m_rng));
326 return AbstractSshPacket::encodeString(m_authKeyAlgoName)
327 + AbstractSshPacket::encodeString(signature);
330 QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
334 m_rng.randomize(convertByteArray(data), count);
338 SshEncryptionFacility::~SshEncryptionFacility() {}
341 QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
343 return kex.decryptionAlgo();
346 QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
348 return kex.hMacAlgoServerToClient();
351 BlockCipherMode *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher,
352 BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
353 const SymmetricKey &key)
355 return new CBC_Decryption(cipher, paddingMethod, key, iv);
358 void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
359 quint32 dataSize) const
361 convert(data, offset, dataSize);
362 #ifdef CREATOR_SSH_DEBUG
363 qDebug("Decrypted data:");
364 const char * const start = data.constData() + offset;
365 const char * const end = start + dataSize;
366 for (const char *c = start; c < end; ++c)
367 qDebug() << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
371 } // namespace Internal