OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / libs / utils / ssh / sshcryptofacility.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
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 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "sshcryptofacility_p.h"
34
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"
40
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>
52
53 #include <QtCore/QDebug>
54 #include <QtCore/QList>
55
56 #include <string>
57
58 using namespace Botan;
59
60 namespace Utils {
61 namespace Internal {
62
63 SshAbstractCryptoFacility::SshAbstractCryptoFacility()
64     : m_cipherBlockSize(0), m_macLength(0)
65 {
66 }
67
68 SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
69
70 void SshAbstractCryptoFacility::clearKeys()
71 {
72     m_cipherBlockSize = 0;
73     m_macLength = 0;
74     m_sessionId.clear();
75     m_pipe.reset(0);
76     m_hMac.reset(0);
77 }
78
79 void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
80 {
81     checkInvariant();
82
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();
88
89     m_cipherBlockSize = cipher->BLOCK_SIZE;
90     const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
91     const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
92
93     const quint32 keySize = max_keylength_of(cryptAlgo);
94     const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
95     SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
96
97     BlockCipherMode * const cipherMode
98         = makeCipherMode(cipher, new Null_Padding, iv, cryptKey);
99     m_pipe.reset(new Pipe(cipherMode));
100
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);
108 }
109
110 void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset,
111     quint32 dataSize) const
112 {
113     Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size()));
114     checkInvariant();
115
116     // Session id empty => No key exchange has happened yet.
117     if (dataSize == 0 || m_sessionId.isEmpty())
118         return;
119
120     if (dataSize % cipherBlockSize() != 0) {
121         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
122             "Invalid packet size");
123     }
124     m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset,
125         dataSize);
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);
129 }
130
131 QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
132     quint32 dataSize) const
133 {
134     return m_sessionId.isEmpty()
135         ? QByteArray()
136         : convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
137               dataSize));
138 }
139
140 QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
141     char c, quint32 length)
142 {
143     const QByteArray &k = kex.k();
144     const QByteArray &h = kex.h();
145     QByteArray data(k);
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());
153         tmpKey.append(key);
154         key.append(kex.hash()->process(tmpKey));
155     }
156     return QByteArray(reinterpret_cast<const char *>(key.begin()), length);
157 }
158
159 void SshAbstractCryptoFacility::checkInvariant() const
160 {
161     Q_ASSERT(m_sessionId.isEmpty() == !m_pipe);
162 }
163
164
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-----");
169
170 QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
171 {
172     return kex.encryptionAlgo();
173 }
174
175 QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
176 {
177     return kex.hMacAlgoClientToServer();
178 }
179
180 BlockCipherMode *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher,
181     BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
182     const SymmetricKey &key)
183 {
184     return new CBC_Encryption(cipher, paddingMethod, key, iv);
185 }
186
187 void SshEncryptionFacility::encrypt(QByteArray &data) const
188 {
189     convert(data, 0, data.size());
190 }
191
192 void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
193 {
194     if (privKeyFileContents == m_cachedPrivKeyContents)
195         return;
196
197 #ifdef CREATOR_SSH_DEBUG
198     qDebug("%s: Key not cached, reading", Q_FUNC_INFO);
199 #endif
200     QList<BigInt> pubKeyParams;
201     QList<BigInt> allKeyParams;
202     try {
203         createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams,
204             allKeyParams);
205     } catch (Botan::Exception &) {
206         createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams,
207             allKeyParams);
208     }
209
210     foreach (const BigInt &b, allKeyParams) {
211         if (b.is_zero()) {
212             throw SshClientException(SshKeyFileError,
213                 SSH_TR("Decoding of private key file failed."));
214         }
215     }
216
217     m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
218     foreach (const BigInt &b, pubKeyParams)
219         m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
220     m_cachedPrivKeyContents = privKeyFileContents;
221 }
222
223 void SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
224     QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
225 {
226     Pipe pipe;
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()
239             << rsaKey->get_d();
240     } else {
241         throw Botan::Exception();
242     }
243 }
244
245 void SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
246     QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams)
247 {
248     bool syntaxOk = true;
249     QList<QByteArray> lines = privKeyFileContents.split('\n');
250     while (lines.last().isEmpty())
251         lines.removeLast();
252     if (lines.count() < 3) {
253         syntaxOk = false;
254     } else if (lines.first() == PrivKeyFileStartLineRsa) {
255         if (lines.last() != PrivKeyFileEndLineRsa)
256             syntaxOk =false;
257         else
258             m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
259     } else if (lines.first() == PrivKeyFileStartLineDsa) {
260         if (lines.last() != PrivKeyFileEndLineDsa)
261             syntaxOk = false;
262         else
263             m_authKeyAlgoName = SshCapabilities::PubKeyDss;
264     } else {
265         syntaxOk = false;
266     }
267     if (!syntaxOk) {
268         throw SshClientException(SshKeyFileError,
269             SSH_TR("Private key file has unexpected format."));
270     }
271
272     QByteArray privateKeyBlob;
273     for (int i = 1; i < lines.size() - 1; ++i)
274         privateKeyBlob += lines.at(i);
275     privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
276
277     BER_Decoder decoder(convertByteArray(privateKeyBlob),
278                         privateKeyBlob.size());
279     BER_Decoder sequence = decoder.start_cons(SEQUENCE);
280     quint32 version;
281     sequence.decode (version);
282     if (version != 0) {
283         throw SshClientException(SshKeyFileError,
284             SSH_TR("Private key encoding has version %1, expected 0.")
285             .arg(version));
286     }
287
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;
296     } else {
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;
304     }
305
306     sequence.discard_remaining();
307     sequence.verify_end();
308 }
309
310 QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
311 {
312     Q_ASSERT(m_authKey);
313     return m_authKeyAlgoName;
314 }
315
316 QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
317 {
318     Q_ASSERT(m_authKey);
319
320     QScopedPointer<PK_Signer> signer(get_pk_signer (*m_authKey,
321         botanEmsaAlgoName(m_authKeyAlgoName)));
322     QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
323     QByteArray signature
324         = convertByteArray(signer->sign_message(convertByteArray(dataToSign),
325               dataToSign.size(), m_rng));
326     return AbstractSshPacket::encodeString(m_authKeyAlgoName)
327         + AbstractSshPacket::encodeString(signature);
328 }
329
330 QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
331 {
332     QByteArray data;
333     data.resize(count);
334     m_rng.randomize(convertByteArray(data), count);
335     return data;
336 }
337
338 SshEncryptionFacility::~SshEncryptionFacility() {}
339
340
341 QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
342 {
343     return kex.decryptionAlgo();
344 }
345
346 QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
347 {
348     return kex.hMacAlgoServerToClient();
349 }
350
351 BlockCipherMode *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher,
352     BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv,
353     const SymmetricKey &key)
354 {
355     return new CBC_Decryption(cipher, paddingMethod, key, iv);
356 }
357
358 void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
359     quint32 dataSize) const
360 {
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) << ")";
368 #endif
369 }
370
371 } // namespace Internal
372 } // namespace Utils