1 /*
2  *  Copyright (C) 2017 Toni Spets <toni.spets@iki.fi>
3  *  Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 2 or (at your option)
8  *  version 3 of the License.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "OpenSSHKey.h"
20 
21 #include "core/Tools.h"
22 #include "crypto/SymmetricCipher.h"
23 #include "crypto/ssh/ASN1Key.h"
24 #include "crypto/ssh/BinaryStream.h"
25 
26 #include <QCryptographicHash>
27 #include <QRegularExpression>
28 #include <QStringList>
29 
30 #include <gcrypt.h>
31 
32 const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA PRIVATE KEY";
33 const QString OpenSSHKey::TYPE_RSA_PRIVATE = "RSA PRIVATE KEY";
34 const QString OpenSSHKey::TYPE_RSA_PUBLIC = "RSA PUBLIC KEY";
35 const QString OpenSSHKey::TYPE_OPENSSH_PRIVATE = "OPENSSH PRIVATE KEY";
36 
37 namespace
38 {
binaryDeserialize(const QByteArray & serialized)39     QPair<QString, QList<QByteArray>> binaryDeserialize(const QByteArray& serialized)
40     {
41         if (serialized.isEmpty()) {
42             return {};
43         }
44         QBuffer buffer;
45         buffer.setData(serialized);
46         buffer.open(QBuffer::ReadOnly);
47         BinaryStream stream(&buffer);
48         QString type;
49         stream.readString(type);
50         QByteArray temp;
51         QList<QByteArray> data;
52         while (stream.readString(temp)) {
53             data << temp;
54         }
55         return ::qMakePair(type, data);
56     }
57 
binarySerialize(const QString & type,const QList<QByteArray> & data)58     QByteArray binarySerialize(const QString& type, const QList<QByteArray>& data)
59     {
60         if (type.isEmpty() && data.isEmpty()) {
61             return {};
62         }
63         QByteArray buffer;
64         BinaryStream stream(&buffer);
65         stream.writeString(type);
66         for (const QByteArray& part : data) {
67             stream.writeString(part);
68         }
69         return buffer;
70     }
71 } // namespace
72 
73 // bcrypt_pbkdf.cpp
74 int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
75 
generate(bool secure)76 OpenSSHKey OpenSSHKey::generate(bool secure)
77 {
78     enum Index
79     {
80         Params,
81         CombinedKey,
82         PrivateKey,
83         PublicKey,
84 
85         Private_N,
86         Private_E,
87         Private_D,
88         Private_P,
89         Private_Q,
90         Private_U, // private key
91         Public_N,
92         Public_E,
93     };
94 
95     Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
96     Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
97     gcry_error_t rc = GPG_ERR_NO_ERROR;
98     rc = gcry_sexp_build(&sexp[Params],
99                          NULL,
100                          secure ? "(genkey (rsa (nbits 4:2048)))" : "(genkey (rsa (transient-key) (nbits 4:2048)))");
101     if (rc != GPG_ERR_NO_ERROR) {
102         qWarning() << "Could not create ssh key" << gcry_err_code(rc);
103         return OpenSSHKey();
104     }
105 
106     rc = gcry_pk_genkey(&sexp[CombinedKey], sexp[Params]);
107     if (rc != GPG_ERR_NO_ERROR) {
108         qWarning() << "Could not create ssh key" << gcry_err_code(rc);
109         return OpenSSHKey();
110     }
111 
112     sexp[PrivateKey] = gcry_sexp_find_token(sexp[CombinedKey], "private-key", 0);
113     sexp[PublicKey] = gcry_sexp_find_token(sexp[CombinedKey], "public-key", 0);
114 
115     sexp[Private_N] = gcry_sexp_find_token(sexp[PrivateKey], "n", 1);
116     mpi[Private_N] = gcry_sexp_nth_mpi(sexp[Private_N], 1, GCRYMPI_FMT_USG);
117     sexp[Private_E] = gcry_sexp_find_token(sexp[PrivateKey], "e", 1);
118     mpi[Private_E] = gcry_sexp_nth_mpi(sexp[Private_E], 1, GCRYMPI_FMT_USG);
119     sexp[Private_D] = gcry_sexp_find_token(sexp[PrivateKey], "d", 1);
120     mpi[Private_D] = gcry_sexp_nth_mpi(sexp[Private_D], 1, GCRYMPI_FMT_USG);
121     sexp[Private_Q] = gcry_sexp_find_token(sexp[PrivateKey], "q", 1);
122     mpi[Private_Q] = gcry_sexp_nth_mpi(sexp[Private_Q], 1, GCRYMPI_FMT_USG);
123     sexp[Private_P] = gcry_sexp_find_token(sexp[PrivateKey], "p", 1);
124     mpi[Private_P] = gcry_sexp_nth_mpi(sexp[Private_P], 1, GCRYMPI_FMT_USG);
125     sexp[Private_U] = gcry_sexp_find_token(sexp[PrivateKey], "u", 1);
126     mpi[Private_U] = gcry_sexp_nth_mpi(sexp[Private_U], 1, GCRYMPI_FMT_USG);
127 
128     sexp[Public_N] = gcry_sexp_find_token(sexp[PublicKey], "n", 1);
129     mpi[Public_N] = gcry_sexp_nth_mpi(sexp[Public_N], 1, GCRYMPI_FMT_USG);
130     sexp[Public_E] = gcry_sexp_find_token(sexp[PublicKey], "e", 1);
131     mpi[Public_E] = gcry_sexp_nth_mpi(sexp[Public_E], 1, GCRYMPI_FMT_USG);
132 
133     QList<QByteArray> publicParts;
134     QList<QByteArray> privateParts;
135     Tools::Buffer buffer;
136     gcry_mpi_format format = GCRYMPI_FMT_USG;
137     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_N]);
138     if (rc != GPG_ERR_NO_ERROR) {
139         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
140         return OpenSSHKey();
141     }
142     privateParts << buffer.content();
143 
144     buffer.clear();
145     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_E]);
146     if (rc != GPG_ERR_NO_ERROR) {
147         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
148         return OpenSSHKey();
149     }
150     privateParts << buffer.content();
151 
152     buffer.clear();
153     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_D]);
154     if (rc != GPG_ERR_NO_ERROR) {
155         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
156         return OpenSSHKey();
157     }
158     privateParts << buffer.content();
159 
160     buffer.clear();
161     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_U]);
162     if (rc != GPG_ERR_NO_ERROR) {
163         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
164         return OpenSSHKey();
165     }
166     privateParts << buffer.content();
167 
168     buffer.clear();
169     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_P]);
170     if (rc != GPG_ERR_NO_ERROR) {
171         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
172         return OpenSSHKey();
173     }
174     privateParts << buffer.content();
175 
176     buffer.clear();
177     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_Q]);
178     if (rc != GPG_ERR_NO_ERROR) {
179         qWarning() << "Could not extract private key part" << gcry_err_code(rc);
180         return OpenSSHKey();
181     }
182     privateParts << buffer.content();
183 
184     buffer.clear();
185     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_E]);
186     if (rc != GPG_ERR_NO_ERROR) {
187         qWarning() << "Could not extract public key part" << gcry_err_code(rc);
188         return OpenSSHKey();
189     }
190     publicParts << buffer.content();
191 
192     buffer.clear();
193     rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_N]);
194     if (rc != GPG_ERR_NO_ERROR) {
195         qWarning() << "Could not extract public key part" << gcry_err_code(rc);
196         return OpenSSHKey();
197     }
198     publicParts << buffer.content();
199     OpenSSHKey key;
200     key.m_rawType = OpenSSHKey::TYPE_RSA_PRIVATE;
201     key.setType("ssh-rsa");
202     key.setPublicData(publicParts);
203     key.setPrivateData(privateParts);
204     key.setComment("");
205     return key;
206 }
207 
OpenSSHKey(QObject * parent)208 OpenSSHKey::OpenSSHKey(QObject* parent)
209     : QObject(parent)
210     , m_type(QString())
211     , m_cipherName(QString("none"))
212     , m_kdfName(QString("none"))
213     , m_kdfOptions(QByteArray())
214     , m_rawType(QString())
215     , m_rawData(QByteArray())
216     , m_rawPublicData(QList<QByteArray>())
217     , m_rawPrivateData(QList<QByteArray>())
218     , m_comment(QString())
219     , m_error(QString())
220 {
221 }
222 
OpenSSHKey(const OpenSSHKey & other)223 OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
224     : QObject(nullptr)
225     , m_type(other.m_type)
226     , m_cipherName(other.m_cipherName)
227     , m_kdfName(other.m_kdfName)
228     , m_kdfOptions(other.m_kdfOptions)
229     , m_rawType(other.m_rawType)
230     , m_rawData(other.m_rawData)
231     , m_rawPublicData(other.m_rawPublicData)
232     , m_rawPrivateData(other.m_rawPrivateData)
233     , m_comment(other.m_comment)
234     , m_error(other.m_error)
235 {
236 }
237 
operator ==(const OpenSSHKey & other) const238 bool OpenSSHKey::operator==(const OpenSSHKey& other) const
239 {
240     // close enough for now
241     return (fingerprint() == other.fingerprint());
242 }
243 
cipherName() const244 const QString OpenSSHKey::cipherName() const
245 {
246     return m_cipherName;
247 }
248 
type() const249 const QString OpenSSHKey::type() const
250 {
251     return m_type;
252 }
253 
keyLength() const254 int OpenSSHKey::keyLength() const
255 {
256     if (m_type == "ssh-dss" && m_rawPublicData.length() == 4) {
257         return (m_rawPublicData[0].length() - 1) * 8;
258     } else if (m_type == "ssh-rsa" && m_rawPublicData.length() == 2) {
259         return (m_rawPublicData[1].length() - 1) * 8;
260     } else if (m_type.startsWith("ecdsa-sha2-") && m_rawPublicData.length() == 2) {
261         return (m_rawPublicData[1].length() - 1) * 4;
262     } else if (m_type == "ssh-ed25519" && m_rawPublicData.length() == 1) {
263         return m_rawPublicData[0].length() * 8;
264     }
265     return 0;
266 }
267 
fingerprint(QCryptographicHash::Algorithm algo) const268 const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
269 {
270     if (m_rawPublicData.isEmpty()) {
271         return {};
272     }
273 
274     QByteArray publicKey;
275     BinaryStream stream(&publicKey);
276 
277     stream.writeString(m_type);
278 
279     for (const QByteArray& ba : m_rawPublicData) {
280         stream.writeString(ba);
281     }
282 
283     QByteArray rawHash = QCryptographicHash::hash(publicKey, algo);
284 
285     if (algo == QCryptographicHash::Md5) {
286         QString md5Hash = QString::fromLatin1(rawHash.toHex());
287         QStringList md5HashParts;
288         for (int i = 0; i < md5Hash.length(); i += 2) {
289             md5HashParts.append(md5Hash.mid(i, 2));
290         }
291         return "MD5:" + md5HashParts.join(':');
292     } else if (algo == QCryptographicHash::Sha256) {
293         return "SHA256:" + QString::fromLatin1(rawHash.toBase64(QByteArray::OmitTrailingEquals));
294     }
295 
296     return "HASH:" + QString::fromLatin1(rawHash.toHex());
297 }
298 
comment() const299 const QString OpenSSHKey::comment() const
300 {
301     return m_comment;
302 }
303 
privateKey() const304 const QString OpenSSHKey::privateKey() const
305 {
306     if (m_rawPrivateData.isEmpty()) {
307         return {};
308     }
309 
310     QByteArray privateKey;
311     BinaryStream stream(&privateKey);
312 
313     stream.writeString(m_type);
314 
315     for (QByteArray ba : m_rawPrivateData) {
316         stream.writeString(ba);
317     }
318 
319     return m_type + " " + QString::fromLatin1(privateKey.toBase64()) + " " + m_comment;
320 }
321 
publicKey() const322 const QString OpenSSHKey::publicKey() const
323 {
324     if (m_rawPublicData.isEmpty()) {
325         return {};
326     }
327 
328     QByteArray publicKey;
329     BinaryStream stream(&publicKey);
330 
331     stream.writeString(m_type);
332 
333     for (QByteArray ba : m_rawPublicData) {
334         stream.writeString(ba);
335     }
336 
337     return m_type + " " + QString::fromLatin1(publicKey.toBase64()) + " " + m_comment;
338 }
339 
errorString() const340 const QString OpenSSHKey::errorString() const
341 {
342     return m_error;
343 }
344 
setType(const QString & type)345 void OpenSSHKey::setType(const QString& type)
346 {
347     m_type = type;
348 }
349 
setPublicData(const QList<QByteArray> & data)350 void OpenSSHKey::setPublicData(const QList<QByteArray>& data)
351 {
352     m_rawPublicData = data;
353 }
354 
setPrivateData(const QList<QByteArray> & data)355 void OpenSSHKey::setPrivateData(const QList<QByteArray>& data)
356 {
357     m_rawPrivateData = data;
358 }
359 
setComment(const QString & comment)360 void OpenSSHKey::setComment(const QString& comment)
361 {
362     m_comment = comment;
363 }
364 
clearPrivate()365 void OpenSSHKey::clearPrivate()
366 {
367     m_rawData.clear();
368     m_rawPrivateData.clear();
369 }
370 
extractPEM(const QByteArray & in,QByteArray & out)371 bool OpenSSHKey::extractPEM(const QByteArray& in, QByteArray& out)
372 {
373     QString pem = QString::fromLatin1(in);
374     QStringList rows = pem.split(QRegularExpression("(?:\r?\n|\r)"), QString::SkipEmptyParts);
375 
376     if (rows.length() < 3) {
377         m_error = tr("Invalid key file, expecting an OpenSSH key");
378         return false;
379     }
380 
381     QString begin = rows.first();
382     QString end = rows.last();
383 
384     QRegularExpressionMatch beginMatch = QRegularExpression("^-----BEGIN ([^\\-]+)-----$").match(begin);
385     QRegularExpressionMatch endMatch = QRegularExpression("^-----END ([^\\-]+)-----$").match(end);
386 
387     if (!beginMatch.hasMatch() || !endMatch.hasMatch()) {
388         m_error = tr("Invalid key file, expecting an OpenSSH key");
389         return false;
390     }
391 
392     if (beginMatch.captured(1) != endMatch.captured(1)) {
393         m_error = tr("PEM boundary mismatch");
394         return false;
395     }
396 
397     m_rawType = beginMatch.captured(1);
398 
399     rows.removeFirst();
400     rows.removeLast();
401 
402     QRegularExpression keyValueExpr = QRegularExpression("^([A-Za-z0-9-]+): (.+)$");
403     QMap<QString, QString> pemOptions;
404 
405     do {
406         QRegularExpressionMatch keyValueMatch = keyValueExpr.match(rows.first());
407 
408         if (!keyValueMatch.hasMatch()) {
409             break;
410         }
411 
412         pemOptions.insert(keyValueMatch.captured(1), keyValueMatch.captured(2));
413 
414         rows.removeFirst();
415     } while (!rows.isEmpty());
416 
417     if (pemOptions.value("Proc-Type").compare("4,encrypted", Qt::CaseInsensitive) == 0) {
418         m_kdfName = "md5";
419         m_cipherName = pemOptions.value("DEK-Info").section(",", 0, 0);
420         m_cipherIV = QByteArray::fromHex(pemOptions.value("DEK-Info").section(",", 1, 1).toLatin1());
421     }
422 
423     out = QByteArray::fromBase64(rows.join("").toLatin1());
424 
425     if (out.isEmpty()) {
426         m_error = tr("Base64 decoding failed");
427         return false;
428     }
429 
430     return true;
431 }
432 
parsePKCS1PEM(const QByteArray & in)433 bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
434 {
435     QByteArray data;
436 
437     if (!extractPEM(in, data)) {
438         return false;
439     }
440 
441     if (m_rawType == TYPE_DSA_PRIVATE || m_rawType == TYPE_RSA_PRIVATE || m_rawType == TYPE_RSA_PUBLIC) {
442         m_rawData = data;
443     } else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
444         BinaryStream stream(&data);
445 
446         QByteArray magic;
447         magic.resize(15);
448 
449         if (!stream.read(magic)) {
450             m_error = tr("Key file way too small.");
451             return false;
452         }
453 
454         if (QString::fromLatin1(magic) != "openssh-key-v1") {
455             m_error = tr("Key file magic header id invalid");
456             return false;
457         }
458 
459         stream.readString(m_cipherName);
460         stream.readString(m_kdfName);
461         stream.readString(m_kdfOptions);
462 
463         quint32 numberOfKeys;
464         stream.read(numberOfKeys);
465 
466         if (numberOfKeys == 0) {
467             m_error = tr("Found zero keys");
468             return false;
469         }
470 
471         for (quint32 i = 0; i < numberOfKeys; ++i) {
472             QByteArray publicKey;
473             if (!stream.readString(publicKey)) {
474                 m_error = tr("Failed to read public key.");
475                 return false;
476             }
477 
478             if (i == 0) {
479                 BinaryStream publicStream(&publicKey);
480                 if (!readPublic(publicStream)) {
481                     return false;
482                 }
483             }
484         }
485 
486         // padded list of keys
487         if (!stream.readString(m_rawData)) {
488             m_error = tr("Corrupted key file, reading private key failed");
489             return false;
490         }
491     } else {
492         m_error = tr("Unsupported key type: %1").arg(m_rawType);
493         return false;
494     }
495 
496     // load private if no encryption
497     if (!encrypted()) {
498         return openKey();
499     }
500 
501     return true;
502 }
503 
encrypted() const504 bool OpenSSHKey::encrypted() const
505 {
506     return (m_cipherName != "none");
507 }
508 
openKey(const QString & passphrase)509 bool OpenSSHKey::openKey(const QString& passphrase)
510 {
511     QScopedPointer<SymmetricCipher> cipher;
512 
513     if (!m_rawPrivateData.isEmpty()) {
514         return true;
515     }
516 
517     if (m_rawData.isEmpty()) {
518         m_error = tr("No private key payload to decrypt");
519         return false;
520     }
521 
522     if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) {
523         cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
524     } else if (m_cipherName == "aes256-cbc" || m_cipherName.compare("aes-256-cbc", Qt::CaseInsensitive) == 0) {
525         cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
526     } else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) {
527         cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
528     } else if (m_cipherName != "none") {
529         m_error = tr("Unknown cipher: %1").arg(m_cipherName);
530         return false;
531     }
532 
533     if (m_kdfName == "bcrypt") {
534         if (!cipher) {
535             m_error = tr("Trying to run KDF without cipher");
536             return false;
537         }
538 
539         if (passphrase.isEmpty()) {
540             m_error = tr("Passphrase is required to decrypt this key");
541             return false;
542         }
543 
544         BinaryStream optionStream(&m_kdfOptions);
545 
546         QByteArray salt;
547         quint32 rounds;
548 
549         optionStream.readString(salt);
550         optionStream.read(rounds);
551 
552         QByteArray decryptKey;
553         decryptKey.fill(0, cipher->keySize() + cipher->blockSize());
554 
555         QByteArray phraseData = passphrase.toUtf8();
556         if (bcrypt_pbkdf(phraseData, salt, decryptKey, rounds) < 0) {
557             m_error = tr("Key derivation failed, key file corrupted?");
558             return false;
559         }
560 
561         QByteArray keyData, ivData;
562         keyData.setRawData(decryptKey.data(), cipher->keySize());
563         ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
564 
565         cipher->init(keyData, ivData);
566 
567         if (!cipher->init(keyData, ivData)) {
568             m_error = cipher->errorString();
569             return false;
570         }
571     } else if (m_kdfName == "md5") {
572         if (m_cipherIV.length() < 8) {
573             m_error = tr("Cipher IV is too short for MD5 kdf");
574             return false;
575         }
576 
577         QByteArray keyData;
578         QByteArray mdBuf;
579         do {
580             QCryptographicHash hash(QCryptographicHash::Md5);
581             hash.addData(mdBuf);
582             hash.addData(passphrase.toUtf8());
583             hash.addData(m_cipherIV.data(), 8);
584             mdBuf = hash.result();
585             keyData.append(mdBuf);
586         } while (keyData.size() < cipher->keySize());
587 
588         if (keyData.size() > cipher->keySize()) {
589             // If our key size isn't a multiple of 16 (e.g. AES-192 or something),
590             // then we will need to truncate it.
591             keyData.resize(cipher->keySize());
592         }
593 
594         if (!cipher->init(keyData, m_cipherIV)) {
595             m_error = cipher->errorString();
596             return false;
597         }
598     } else if (m_kdfName != "none") {
599         m_error = tr("Unknown KDF: %1").arg(m_kdfName);
600         return false;
601     }
602 
603     QByteArray rawData = m_rawData;
604 
605     if (cipher && cipher->isInitalized()) {
606         bool ok = false;
607         rawData = cipher->process(rawData, &ok);
608         if (!ok) {
609             m_error = tr("Decryption failed, wrong passphrase?");
610             return false;
611         }
612     }
613 
614     if (m_rawType == TYPE_DSA_PRIVATE) {
615         if (!ASN1Key::parseDSA(rawData, *this)) {
616             m_error = tr("Decryption failed, wrong passphrase?");
617             return false;
618         }
619 
620         return true;
621     } else if (m_rawType == TYPE_RSA_PRIVATE) {
622         if (!ASN1Key::parsePrivateRSA(rawData, *this)) {
623             m_error = tr("Decryption failed, wrong passphrase?");
624             return false;
625         }
626         return true;
627     } else if (m_rawType == TYPE_RSA_PUBLIC) {
628         if (!ASN1Key::parsePublicRSA(rawData, *this)) {
629             m_error = tr("Decryption failed, wrong passphrase?");
630             return false;
631         }
632         return true;
633     } else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
634         BinaryStream keyStream(&rawData);
635 
636         quint32 checkInt1;
637         quint32 checkInt2;
638 
639         keyStream.read(checkInt1);
640         keyStream.read(checkInt2);
641 
642         if (checkInt1 != checkInt2) {
643             m_error = tr("Decryption failed, wrong passphrase?");
644             return false;
645         }
646 
647         return readPrivate(keyStream);
648     }
649 
650     m_error = tr("Unsupported key type: %1").arg(m_rawType);
651     return false;
652 }
653 
readPublic(BinaryStream & stream)654 bool OpenSSHKey::readPublic(BinaryStream& stream)
655 {
656     m_rawPublicData.clear();
657 
658     if (!stream.readString(m_type)) {
659         m_error = tr("Unexpected EOF while reading public key");
660         return false;
661     }
662 
663     int keyParts;
664     if (m_type == "ssh-dss") {
665         keyParts = 4;
666     } else if (m_type == "ssh-rsa") {
667         keyParts = 2;
668     } else if (m_type.startsWith("ecdsa-sha2-")) {
669         keyParts = 2;
670     } else if (m_type == "ssh-ed25519") {
671         keyParts = 1;
672     } else {
673         m_error = tr("Unknown key type: %1").arg(m_type);
674         return false;
675     }
676 
677     for (int i = 0; i < keyParts; ++i) {
678         QByteArray t;
679 
680         if (!stream.readString(t)) {
681             m_error = tr("Unexpected EOF while reading public key");
682             return false;
683         }
684 
685         m_rawPublicData.append(t);
686     }
687 
688     return true;
689 }
690 
readPrivate(BinaryStream & stream)691 bool OpenSSHKey::readPrivate(BinaryStream& stream)
692 {
693     m_rawPrivateData.clear();
694 
695     if (!stream.readString(m_type)) {
696         m_error = tr("Unexpected EOF while reading private key");
697         return false;
698     }
699 
700     int keyParts;
701     if (m_type == "ssh-dss") {
702         keyParts = 5;
703     } else if (m_type == "ssh-rsa") {
704         keyParts = 6;
705     } else if (m_type.startsWith("ecdsa-sha2-")) {
706         keyParts = 3;
707     } else if (m_type == "ssh-ed25519") {
708         keyParts = 2;
709     } else {
710         m_error = tr("Unknown key type: %1").arg(m_type);
711         return false;
712     }
713 
714     for (int i = 0; i < keyParts; ++i) {
715         QByteArray t;
716 
717         if (!stream.readString(t)) {
718             m_error = tr("Unexpected EOF while reading private key");
719             return false;
720         }
721 
722         m_rawPrivateData.append(t);
723     }
724 
725     if (!stream.readString(m_comment)) {
726         m_error = tr("Unexpected EOF while reading private key");
727         return false;
728     }
729 
730     return true;
731 }
732 
writePublic(BinaryStream & stream)733 bool OpenSSHKey::writePublic(BinaryStream& stream)
734 {
735     if (m_rawPublicData.isEmpty()) {
736         m_error = tr("Can't write public key as it is empty");
737         return false;
738     }
739 
740     if (!stream.writeString(m_type)) {
741         m_error = tr("Unexpected EOF when writing public key");
742         return false;
743     }
744 
745     for (QByteArray t : m_rawPublicData) {
746         if (!stream.writeString(t)) {
747             m_error = tr("Unexpected EOF when writing public key");
748             return false;
749         }
750     }
751 
752     return true;
753 }
754 
writePrivate(BinaryStream & stream)755 bool OpenSSHKey::writePrivate(BinaryStream& stream)
756 {
757     if (m_rawPrivateData.isEmpty()) {
758         m_error = tr("Can't write private key as it is empty");
759         return false;
760     }
761 
762     if (!stream.writeString(m_type)) {
763         m_error = tr("Unexpected EOF when writing private key");
764         return false;
765     }
766 
767     for (QByteArray t : m_rawPrivateData) {
768         if (!stream.writeString(t)) {
769             m_error = tr("Unexpected EOF when writing private key");
770             return false;
771         }
772     }
773 
774     if (!stream.writeString(m_comment)) {
775         m_error = tr("Unexpected EOF when writing private key");
776         return false;
777     }
778 
779     return true;
780 }
781 
publicParts() const782 QList<QByteArray> OpenSSHKey::publicParts() const
783 {
784     return m_rawPublicData;
785 }
786 
privateParts() const787 QList<QByteArray> OpenSSHKey::privateParts() const
788 {
789     return m_rawPrivateData;
790 }
791 
privateType() const792 const QString& OpenSSHKey::privateType() const
793 {
794     return m_rawType;
795 }
796 
restoreFromBinary(Type type,const QByteArray & serialized)797 OpenSSHKey OpenSSHKey::restoreFromBinary(Type type, const QByteArray& serialized)
798 {
799     OpenSSHKey key;
800     auto data = binaryDeserialize(serialized);
801     key.setType(data.first);
802     switch (type) {
803     case Public:
804         key.setPublicData(data.second);
805         break;
806     case Private:
807         key.setPrivateData(data.second);
808         break;
809     }
810     return key;
811 }
812 
serializeToBinary(Type type,const OpenSSHKey & key)813 QByteArray OpenSSHKey::serializeToBinary(Type type, const OpenSSHKey& key)
814 {
815     Q_ASSERT(!key.encrypted());
816     switch (type) {
817     case Public:
818         return binarySerialize(key.type(), key.publicParts());
819     case Private:
820         return binarySerialize(key.type(), key.privateParts());
821     }
822     return {};
823 }
824 
qHash(const OpenSSHKey & key)825 uint qHash(const OpenSSHKey& key)
826 {
827     return qHash(key.fingerprint());
828 }
829