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