2017-10-29 15:17:24 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2017 Toni Spets <toni.spets@iki.fi>
|
|
|
|
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 2 or (at your option)
|
|
|
|
|
* version 3 of the License.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "OpenSSHKey.h"
|
2018-10-01 14:26:24 +00:00
|
|
|
|
|
|
|
|
#include "core/Tools.h"
|
2018-03-31 20:01:30 +00:00
|
|
|
#include "crypto/SymmetricCipher.h"
|
2018-10-01 14:26:24 +00:00
|
|
|
#include "crypto/ssh/ASN1Key.h"
|
|
|
|
|
#include "crypto/ssh/BinaryStream.h"
|
|
|
|
|
|
2018-03-31 20:01:30 +00:00
|
|
|
#include <QCryptographicHash>
|
2017-10-29 15:17:24 +00:00
|
|
|
#include <QRegularExpression>
|
|
|
|
|
#include <QStringList>
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
#include <gcrypt.h>
|
|
|
|
|
|
|
|
|
|
const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA PRIVATE KEY";
|
|
|
|
|
const QString OpenSSHKey::TYPE_RSA_PRIVATE = "RSA PRIVATE KEY";
|
|
|
|
|
const QString OpenSSHKey::TYPE_RSA_PUBLIC = "RSA PUBLIC KEY";
|
|
|
|
|
const QString OpenSSHKey::TYPE_OPENSSH_PRIVATE = "OPENSSH PRIVATE KEY";
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
2019-01-20 14:50:20 +00:00
|
|
|
QPair<QString, QList<QByteArray>> binaryDeserialize(const QByteArray& serialized)
|
|
|
|
|
{
|
|
|
|
|
if (serialized.isEmpty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
QBuffer buffer;
|
|
|
|
|
buffer.setData(serialized);
|
|
|
|
|
buffer.open(QBuffer::ReadOnly);
|
|
|
|
|
BinaryStream stream(&buffer);
|
|
|
|
|
QString type;
|
|
|
|
|
stream.readString(type);
|
|
|
|
|
QByteArray temp;
|
|
|
|
|
QList<QByteArray> data;
|
|
|
|
|
while (stream.readString(temp)) {
|
|
|
|
|
data << temp;
|
|
|
|
|
}
|
|
|
|
|
return ::qMakePair(type, data);
|
2018-12-19 03:28:56 +00:00
|
|
|
}
|
2018-10-01 14:26:24 +00:00
|
|
|
|
2019-01-20 14:50:20 +00:00
|
|
|
QByteArray binarySerialize(const QString& type, const QList<QByteArray>& data)
|
|
|
|
|
{
|
|
|
|
|
if (type.isEmpty() && data.isEmpty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
QByteArray buffer;
|
|
|
|
|
BinaryStream stream(&buffer);
|
|
|
|
|
stream.writeString(type);
|
|
|
|
|
for (const QByteArray& part : data) {
|
|
|
|
|
stream.writeString(part);
|
|
|
|
|
}
|
|
|
|
|
return buffer;
|
2018-12-19 03:28:56 +00:00
|
|
|
}
|
2019-01-20 14:50:20 +00:00
|
|
|
} // namespace
|
2018-02-01 17:00:35 +00:00
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
// bcrypt_pbkdf.cpp
|
|
|
|
|
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
OpenSSHKey OpenSSHKey::generate(bool secure)
|
|
|
|
|
{
|
|
|
|
|
enum Index
|
|
|
|
|
{
|
|
|
|
|
Params,
|
|
|
|
|
CombinedKey,
|
|
|
|
|
PrivateKey,
|
|
|
|
|
PublicKey,
|
|
|
|
|
|
|
|
|
|
Private_N,
|
|
|
|
|
Private_E,
|
|
|
|
|
Private_D,
|
|
|
|
|
Private_P,
|
|
|
|
|
Private_Q,
|
|
|
|
|
Private_U, // private key
|
|
|
|
|
Public_N,
|
|
|
|
|
Public_E,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
|
|
|
|
|
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
|
|
|
|
|
gcry_error_t rc = GPG_ERR_NO_ERROR;
|
2019-01-20 14:50:20 +00:00
|
|
|
rc = gcry_sexp_build(&sexp[Params],
|
|
|
|
|
NULL,
|
|
|
|
|
secure ? "(genkey (rsa (nbits 4:2048)))" : "(genkey (rsa (transient-key) (nbits 4:2048)))");
|
2018-10-01 14:26:24 +00:00
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = gcry_pk_genkey(&sexp[CombinedKey], sexp[Params]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not create ssh key" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sexp[PrivateKey] = gcry_sexp_find_token(sexp[CombinedKey], "private-key", 0);
|
|
|
|
|
sexp[PublicKey] = gcry_sexp_find_token(sexp[CombinedKey], "public-key", 0);
|
|
|
|
|
|
|
|
|
|
sexp[Private_N] = gcry_sexp_find_token(sexp[PrivateKey], "n", 1);
|
|
|
|
|
mpi[Private_N] = gcry_sexp_nth_mpi(sexp[Private_N], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Private_E] = gcry_sexp_find_token(sexp[PrivateKey], "e", 1);
|
|
|
|
|
mpi[Private_E] = gcry_sexp_nth_mpi(sexp[Private_E], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Private_D] = gcry_sexp_find_token(sexp[PrivateKey], "d", 1);
|
|
|
|
|
mpi[Private_D] = gcry_sexp_nth_mpi(sexp[Private_D], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Private_Q] = gcry_sexp_find_token(sexp[PrivateKey], "q", 1);
|
|
|
|
|
mpi[Private_Q] = gcry_sexp_nth_mpi(sexp[Private_Q], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Private_P] = gcry_sexp_find_token(sexp[PrivateKey], "p", 1);
|
|
|
|
|
mpi[Private_P] = gcry_sexp_nth_mpi(sexp[Private_P], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Private_U] = gcry_sexp_find_token(sexp[PrivateKey], "u", 1);
|
|
|
|
|
mpi[Private_U] = gcry_sexp_nth_mpi(sexp[Private_U], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
|
|
|
|
|
sexp[Public_N] = gcry_sexp_find_token(sexp[PublicKey], "n", 1);
|
|
|
|
|
mpi[Public_N] = gcry_sexp_nth_mpi(sexp[Public_N], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
sexp[Public_E] = gcry_sexp_find_token(sexp[PublicKey], "e", 1);
|
|
|
|
|
mpi[Public_E] = gcry_sexp_nth_mpi(sexp[Public_E], 1, GCRYMPI_FMT_USG);
|
|
|
|
|
|
|
|
|
|
QList<QByteArray> publicParts;
|
|
|
|
|
QList<QByteArray> privateParts;
|
|
|
|
|
Tools::Buffer buffer;
|
|
|
|
|
gcry_mpi_format format = GCRYMPI_FMT_USG;
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_N]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_E]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_D]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_U]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_P]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_Q]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract private key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
privateParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_E]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
publicParts << buffer.content();
|
|
|
|
|
|
|
|
|
|
buffer.clear();
|
|
|
|
|
rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_N]);
|
|
|
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
|
|
|
qWarning() << "Could not extract public key part" << gcry_err_code(rc);
|
|
|
|
|
return OpenSSHKey();
|
|
|
|
|
}
|
|
|
|
|
publicParts << buffer.content();
|
|
|
|
|
OpenSSHKey key;
|
|
|
|
|
key.m_rawType = OpenSSHKey::TYPE_RSA_PRIVATE;
|
|
|
|
|
key.setType("ssh-rsa");
|
|
|
|
|
key.setPublicData(publicParts);
|
|
|
|
|
key.setPrivateData(privateParts);
|
|
|
|
|
key.setComment("");
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-31 20:01:30 +00:00
|
|
|
OpenSSHKey::OpenSSHKey(QObject* parent)
|
2019-01-20 14:50:20 +00:00
|
|
|
: QObject(parent)
|
|
|
|
|
, m_type(QString())
|
|
|
|
|
, m_cipherName(QString("none"))
|
|
|
|
|
, m_kdfName(QString("none"))
|
|
|
|
|
, m_kdfOptions(QByteArray())
|
|
|
|
|
, m_rawType(QString())
|
|
|
|
|
, m_rawData(QByteArray())
|
|
|
|
|
, m_rawPublicData(QList<QByteArray>())
|
|
|
|
|
, m_rawPrivateData(QList<QByteArray>())
|
|
|
|
|
, m_comment(QString())
|
|
|
|
|
, m_error(QString())
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
|
2019-01-20 14:50:20 +00:00
|
|
|
: QObject(nullptr)
|
|
|
|
|
, m_type(other.m_type)
|
|
|
|
|
, m_cipherName(other.m_cipherName)
|
|
|
|
|
, m_kdfName(other.m_kdfName)
|
|
|
|
|
, m_kdfOptions(other.m_kdfOptions)
|
|
|
|
|
, m_rawType(other.m_rawType)
|
|
|
|
|
, m_rawData(other.m_rawData)
|
|
|
|
|
, m_rawPublicData(other.m_rawPublicData)
|
|
|
|
|
, m_rawPrivateData(other.m_rawPrivateData)
|
|
|
|
|
, m_comment(other.m_comment)
|
|
|
|
|
, m_error(other.m_error)
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::operator==(const OpenSSHKey& other) const
|
|
|
|
|
{
|
|
|
|
|
// close enough for now
|
|
|
|
|
return (fingerprint() == other.fingerprint());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString OpenSSHKey::cipherName() const
|
|
|
|
|
{
|
|
|
|
|
return m_cipherName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString OpenSSHKey::type() const
|
|
|
|
|
{
|
|
|
|
|
return m_type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int OpenSSHKey::keyLength() const
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_type == "ssh-dss" && m_rawPublicData.length() == 4) {
|
|
|
|
|
return (m_rawPublicData[0].length() - 1) * 8;
|
|
|
|
|
} else if (m_type == "ssh-rsa" && m_rawPublicData.length() == 2) {
|
|
|
|
|
return (m_rawPublicData[1].length() - 1) * 8;
|
|
|
|
|
} else if (m_type.startsWith("ecdsa-sha2-") && m_rawPublicData.length() == 2) {
|
|
|
|
|
return (m_rawPublicData[1].length() - 1) * 4;
|
|
|
|
|
} else if (m_type == "ssh-ed25519" && m_rawPublicData.length() == 1) {
|
|
|
|
|
return m_rawPublicData[0].length() * 8;
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-14 12:42:28 +00:00
|
|
|
const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawPublicData.isEmpty()) {
|
2018-03-01 16:27:53 +00:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
QByteArray publicKey;
|
|
|
|
|
BinaryStream stream(&publicKey);
|
|
|
|
|
|
|
|
|
|
stream.writeString(m_type);
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
for (const QByteArray& ba : m_rawPublicData) {
|
2017-10-29 15:17:24 +00:00
|
|
|
stream.writeString(ba);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-14 12:42:28 +00:00
|
|
|
QByteArray rawHash = QCryptographicHash::hash(publicKey, algo);
|
|
|
|
|
|
|
|
|
|
if (algo == QCryptographicHash::Md5) {
|
|
|
|
|
QString md5Hash = QString::fromLatin1(rawHash.toHex());
|
|
|
|
|
QStringList md5HashParts;
|
|
|
|
|
for (int i = 0; i < md5Hash.length(); i += 2) {
|
|
|
|
|
md5HashParts.append(md5Hash.mid(i, 2));
|
|
|
|
|
}
|
|
|
|
|
return "MD5:" + md5HashParts.join(':');
|
|
|
|
|
} else if (algo == QCryptographicHash::Sha256) {
|
|
|
|
|
return "SHA256:" + QString::fromLatin1(rawHash.toBase64(QByteArray::OmitTrailingEquals));
|
|
|
|
|
}
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-07-14 12:42:28 +00:00
|
|
|
return "HASH:" + QString::fromLatin1(rawHash.toHex());
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString OpenSSHKey::comment() const
|
|
|
|
|
{
|
|
|
|
|
return m_comment;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
const QString OpenSSHKey::privateKey() const
|
|
|
|
|
{
|
|
|
|
|
if (m_rawPrivateData.isEmpty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray privateKey;
|
|
|
|
|
BinaryStream stream(&privateKey);
|
|
|
|
|
|
|
|
|
|
stream.writeString(m_type);
|
|
|
|
|
|
|
|
|
|
for (QByteArray ba : m_rawPrivateData) {
|
|
|
|
|
stream.writeString(ba);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m_type + " " + QString::fromLatin1(privateKey.toBase64()) + " " + m_comment;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
const QString OpenSSHKey::publicKey() const
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawPublicData.isEmpty()) {
|
2018-03-01 16:27:53 +00:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
QByteArray publicKey;
|
|
|
|
|
BinaryStream stream(&publicKey);
|
|
|
|
|
|
|
|
|
|
stream.writeString(m_type);
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
for (QByteArray ba : m_rawPublicData) {
|
2017-10-29 15:17:24 +00:00
|
|
|
stream.writeString(ba);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m_type + " " + QString::fromLatin1(publicKey.toBase64()) + " " + m_comment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString OpenSSHKey::errorString() const
|
|
|
|
|
{
|
|
|
|
|
return m_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenSSHKey::setType(const QString& type)
|
|
|
|
|
{
|
|
|
|
|
m_type = type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenSSHKey::setPublicData(const QList<QByteArray>& data)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPublicData = data;
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenSSHKey::setPrivateData(const QList<QByteArray>& data)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPrivateData = data;
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenSSHKey::setComment(const QString& comment)
|
|
|
|
|
{
|
|
|
|
|
m_comment = comment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenSSHKey::clearPrivate()
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawData.clear();
|
2017-10-29 15:17:24 +00:00
|
|
|
m_rawPrivateData.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
bool OpenSSHKey::extractPEM(const QByteArray& in, QByteArray& out)
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
|
|
|
|
QString pem = QString::fromLatin1(in);
|
|
|
|
|
QStringList rows = pem.split(QRegularExpression("(?:\r?\n|\r)"), QString::SkipEmptyParts);
|
|
|
|
|
|
|
|
|
|
if (rows.length() < 3) {
|
|
|
|
|
m_error = tr("Invalid key file, expecting an OpenSSH key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString begin = rows.first();
|
|
|
|
|
QString end = rows.last();
|
|
|
|
|
|
|
|
|
|
QRegularExpressionMatch beginMatch = QRegularExpression("^-----BEGIN ([^\\-]+)-----$").match(begin);
|
|
|
|
|
QRegularExpressionMatch endMatch = QRegularExpression("^-----END ([^\\-]+)-----$").match(end);
|
|
|
|
|
|
|
|
|
|
if (!beginMatch.hasMatch() || !endMatch.hasMatch()) {
|
|
|
|
|
m_error = tr("Invalid key file, expecting an OpenSSH key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (beginMatch.captured(1) != endMatch.captured(1)) {
|
|
|
|
|
m_error = tr("PEM boundary mismatch");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawType = beginMatch.captured(1);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
|
|
|
|
rows.removeFirst();
|
|
|
|
|
rows.removeLast();
|
|
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
QRegularExpression keyValueExpr = QRegularExpression("^([A-Za-z0-9-]+): (.+)$");
|
|
|
|
|
QMap<QString, QString> pemOptions;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
QRegularExpressionMatch keyValueMatch = keyValueExpr.match(rows.first());
|
|
|
|
|
|
|
|
|
|
if (!keyValueMatch.hasMatch()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pemOptions.insert(keyValueMatch.captured(1), keyValueMatch.captured(2));
|
|
|
|
|
|
|
|
|
|
rows.removeFirst();
|
|
|
|
|
} while (!rows.isEmpty());
|
|
|
|
|
|
2018-02-06 17:23:03 +00:00
|
|
|
if (pemOptions.value("Proc-Type").compare("4,encrypted", Qt::CaseInsensitive) == 0) {
|
|
|
|
|
m_kdfName = "md5";
|
|
|
|
|
m_cipherName = pemOptions.value("DEK-Info").section(",", 0, 0);
|
|
|
|
|
m_cipherIV = QByteArray::fromHex(pemOptions.value("DEK-Info").section(",", 1, 1).toLatin1());
|
2018-02-01 17:00:35 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
out = QByteArray::fromBase64(rows.join("").toLatin1());
|
|
|
|
|
|
|
|
|
|
if (out.isEmpty()) {
|
|
|
|
|
m_error = tr("Base64 decoding failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
|
|
|
|
QByteArray data;
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
if (!extractPEM(in, data)) {
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawType == TYPE_DSA_PRIVATE || m_rawType == TYPE_RSA_PRIVATE || m_rawType == TYPE_RSA_PUBLIC) {
|
|
|
|
|
m_rawData = data;
|
|
|
|
|
} else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
|
2018-02-01 17:00:35 +00:00
|
|
|
BinaryStream stream(&data);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
QByteArray magic;
|
|
|
|
|
magic.resize(15);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
if (!stream.read(magic)) {
|
|
|
|
|
m_error = tr("Key file way too small.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
if (QString::fromLatin1(magic) != "openssh-key-v1") {
|
|
|
|
|
m_error = tr("Key file magic header id invalid");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
stream.readString(m_cipherName);
|
|
|
|
|
stream.readString(m_kdfName);
|
|
|
|
|
stream.readString(m_kdfOptions);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
quint32 numberOfKeys;
|
|
|
|
|
stream.read(numberOfKeys);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
if (numberOfKeys == 0) {
|
|
|
|
|
m_error = tr("Found zero keys");
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
for (quint32 i = 0; i < numberOfKeys; ++i) {
|
|
|
|
|
QByteArray publicKey;
|
|
|
|
|
if (!stream.readString(publicKey)) {
|
|
|
|
|
m_error = tr("Failed to read public key.");
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-02-01 17:00:35 +00:00
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
BinaryStream publicStream(&publicKey);
|
|
|
|
|
if (!readPublic(publicStream)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
// padded list of keys
|
2018-10-01 14:26:24 +00:00
|
|
|
if (!stream.readString(m_rawData)) {
|
2018-02-01 17:00:35 +00:00
|
|
|
m_error = tr("Corrupted key file, reading private key failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2018-10-01 14:26:24 +00:00
|
|
|
m_error = tr("Unsupported key type: %1").arg(m_rawType);
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// load private if no encryption
|
|
|
|
|
if (!encrypted()) {
|
2018-10-01 14:26:24 +00:00
|
|
|
return openKey();
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::encrypted() const
|
|
|
|
|
{
|
|
|
|
|
return (m_cipherName != "none");
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
bool OpenSSHKey::openKey(const QString& passphrase)
|
2017-10-29 15:17:24 +00:00
|
|
|
{
|
|
|
|
|
QScopedPointer<SymmetricCipher> cipher;
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
if (!m_rawPrivateData.isEmpty()) {
|
2017-10-29 15:17:24 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawData.isEmpty()) {
|
2017-10-29 15:17:24 +00:00
|
|
|
m_error = tr("No private key payload to decrypt");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-06 17:23:03 +00:00
|
|
|
if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) {
|
|
|
|
|
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
2018-04-05 01:58:34 +00:00
|
|
|
} else if (m_cipherName == "aes256-cbc" || m_cipherName.compare("aes-256-cbc", Qt::CaseInsensitive) == 0) {
|
2017-10-29 15:17:24 +00:00
|
|
|
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
2018-04-05 01:58:34 +00:00
|
|
|
} else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) {
|
2017-10-29 15:17:24 +00:00
|
|
|
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
|
|
|
|
|
} else if (m_cipherName != "none") {
|
2018-02-06 17:22:21 +00:00
|
|
|
m_error = tr("Unknown cipher: %1").arg(m_cipherName);
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_kdfName == "bcrypt") {
|
|
|
|
|
if (!cipher) {
|
|
|
|
|
m_error = tr("Trying to run KDF without cipher");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-01 16:27:53 +00:00
|
|
|
if (passphrase.isEmpty()) {
|
2017-10-29 15:17:24 +00:00
|
|
|
m_error = tr("Passphrase is required to decrypt this key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BinaryStream optionStream(&m_kdfOptions);
|
|
|
|
|
|
|
|
|
|
QByteArray salt;
|
|
|
|
|
quint32 rounds;
|
|
|
|
|
|
|
|
|
|
optionStream.readString(salt);
|
|
|
|
|
optionStream.read(rounds);
|
|
|
|
|
|
|
|
|
|
QByteArray decryptKey;
|
|
|
|
|
decryptKey.fill(0, cipher->keySize() + cipher->blockSize());
|
|
|
|
|
|
2018-07-13 14:41:47 +00:00
|
|
|
QByteArray phraseData = passphrase.toUtf8();
|
2017-10-29 15:17:24 +00:00
|
|
|
if (bcrypt_pbkdf(phraseData, salt, decryptKey, rounds) < 0) {
|
|
|
|
|
m_error = tr("Key derivation failed, key file corrupted?");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray keyData, ivData;
|
|
|
|
|
keyData.setRawData(decryptKey.data(), cipher->keySize());
|
|
|
|
|
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
|
|
|
|
|
|
|
|
|
|
cipher->init(keyData, ivData);
|
2018-02-01 17:00:35 +00:00
|
|
|
|
|
|
|
|
if (!cipher->init(keyData, ivData)) {
|
|
|
|
|
m_error = cipher->errorString();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-02-06 17:23:03 +00:00
|
|
|
} else if (m_kdfName == "md5") {
|
|
|
|
|
if (m_cipherIV.length() < 8) {
|
|
|
|
|
m_error = tr("Cipher IV is too short for MD5 kdf");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-05 01:58:34 +00:00
|
|
|
QByteArray keyData;
|
|
|
|
|
QByteArray mdBuf;
|
|
|
|
|
do {
|
|
|
|
|
QCryptographicHash hash(QCryptographicHash::Md5);
|
|
|
|
|
hash.addData(mdBuf);
|
|
|
|
|
hash.addData(passphrase.toUtf8());
|
|
|
|
|
hash.addData(m_cipherIV.data(), 8);
|
|
|
|
|
mdBuf = hash.result();
|
|
|
|
|
keyData.append(mdBuf);
|
2018-10-01 14:26:24 +00:00
|
|
|
} while (keyData.size() < cipher->keySize());
|
2018-04-05 01:58:34 +00:00
|
|
|
|
|
|
|
|
if (keyData.size() > cipher->keySize()) {
|
|
|
|
|
// If our key size isn't a multiple of 16 (e.g. AES-192 or something),
|
|
|
|
|
// then we will need to truncate it.
|
|
|
|
|
keyData.resize(cipher->keySize());
|
|
|
|
|
}
|
2018-02-06 17:23:03 +00:00
|
|
|
|
|
|
|
|
if (!cipher->init(keyData, m_cipherIV)) {
|
|
|
|
|
m_error = cipher->errorString();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-10-29 15:17:24 +00:00
|
|
|
} else if (m_kdfName != "none") {
|
2018-02-06 17:22:21 +00:00
|
|
|
m_error = tr("Unknown KDF: %1").arg(m_kdfName);
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
QByteArray rawData = m_rawData;
|
2017-10-29 15:17:24 +00:00
|
|
|
|
|
|
|
|
if (cipher && cipher->isInitalized()) {
|
|
|
|
|
bool ok = false;
|
2018-10-01 14:26:24 +00:00
|
|
|
rawData = cipher->process(rawData, &ok);
|
2017-10-29 15:17:24 +00:00
|
|
|
if (!ok) {
|
|
|
|
|
m_error = tr("Decryption failed, wrong passphrase?");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawType == TYPE_DSA_PRIVATE) {
|
|
|
|
|
if (!ASN1Key::parseDSA(rawData, *this)) {
|
2018-02-06 17:23:03 +00:00
|
|
|
m_error = tr("Decryption failed, wrong passphrase?");
|
2018-02-01 17:00:35 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2018-10-01 14:26:24 +00:00
|
|
|
} else if (m_rawType == TYPE_RSA_PRIVATE) {
|
|
|
|
|
if (!ASN1Key::parsePrivateRSA(rawData, *this)) {
|
2018-02-06 17:23:03 +00:00
|
|
|
m_error = tr("Decryption failed, wrong passphrase?");
|
2018-02-01 17:00:35 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2018-10-01 14:26:24 +00:00
|
|
|
} else if (m_rawType == TYPE_RSA_PUBLIC) {
|
|
|
|
|
if (!ASN1Key::parsePublicRSA(rawData, *this)) {
|
|
|
|
|
m_error = tr("Decryption failed, wrong passphrase?");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
|
|
|
|
|
BinaryStream keyStream(&rawData);
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
quint32 checkInt1;
|
|
|
|
|
quint32 checkInt2;
|
2017-10-29 15:17:24 +00:00
|
|
|
|
2018-02-01 17:00:35 +00:00
|
|
|
keyStream.read(checkInt1);
|
|
|
|
|
keyStream.read(checkInt2);
|
|
|
|
|
|
|
|
|
|
if (checkInt1 != checkInt2) {
|
|
|
|
|
m_error = tr("Decryption failed, wrong passphrase?");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return readPrivate(keyStream);
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
m_error = tr("Unsupported key type: %1").arg(m_rawType);
|
2018-02-01 17:00:35 +00:00
|
|
|
return false;
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::readPublic(BinaryStream& stream)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPublicData.clear();
|
2017-10-29 15:17:24 +00:00
|
|
|
|
|
|
|
|
if (!stream.readString(m_type)) {
|
|
|
|
|
m_error = tr("Unexpected EOF while reading public key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int keyParts;
|
|
|
|
|
if (m_type == "ssh-dss") {
|
|
|
|
|
keyParts = 4;
|
|
|
|
|
} else if (m_type == "ssh-rsa") {
|
|
|
|
|
keyParts = 2;
|
|
|
|
|
} else if (m_type.startsWith("ecdsa-sha2-")) {
|
|
|
|
|
keyParts = 2;
|
|
|
|
|
} else if (m_type == "ssh-ed25519") {
|
|
|
|
|
keyParts = 1;
|
|
|
|
|
} else {
|
2018-02-06 17:22:21 +00:00
|
|
|
m_error = tr("Unknown key type: %1").arg(m_type);
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < keyParts; ++i) {
|
|
|
|
|
QByteArray t;
|
|
|
|
|
|
|
|
|
|
if (!stream.readString(t)) {
|
|
|
|
|
m_error = tr("Unexpected EOF while reading public key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPublicData.append(t);
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::readPrivate(BinaryStream& stream)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPrivateData.clear();
|
2017-10-29 15:17:24 +00:00
|
|
|
|
|
|
|
|
if (!stream.readString(m_type)) {
|
|
|
|
|
m_error = tr("Unexpected EOF while reading private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int keyParts;
|
|
|
|
|
if (m_type == "ssh-dss") {
|
|
|
|
|
keyParts = 5;
|
|
|
|
|
} else if (m_type == "ssh-rsa") {
|
|
|
|
|
keyParts = 6;
|
|
|
|
|
} else if (m_type.startsWith("ecdsa-sha2-")) {
|
|
|
|
|
keyParts = 3;
|
|
|
|
|
} else if (m_type == "ssh-ed25519") {
|
|
|
|
|
keyParts = 2;
|
|
|
|
|
} else {
|
2018-02-06 17:22:21 +00:00
|
|
|
m_error = tr("Unknown key type: %1").arg(m_type);
|
2017-10-29 15:17:24 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < keyParts; ++i) {
|
|
|
|
|
QByteArray t;
|
|
|
|
|
|
|
|
|
|
if (!stream.readString(t)) {
|
|
|
|
|
m_error = tr("Unexpected EOF while reading private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
m_rawPrivateData.append(t);
|
2017-10-29 15:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!stream.readString(m_comment)) {
|
|
|
|
|
m_error = tr("Unexpected EOF while reading private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::writePublic(BinaryStream& stream)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawPublicData.isEmpty()) {
|
2017-10-29 15:17:24 +00:00
|
|
|
m_error = tr("Can't write public key as it is empty");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!stream.writeString(m_type)) {
|
|
|
|
|
m_error = tr("Unexpected EOF when writing public key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
for (QByteArray t : m_rawPublicData) {
|
2017-10-29 15:17:24 +00:00
|
|
|
if (!stream.writeString(t)) {
|
|
|
|
|
m_error = tr("Unexpected EOF when writing public key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenSSHKey::writePrivate(BinaryStream& stream)
|
|
|
|
|
{
|
2018-10-01 14:26:24 +00:00
|
|
|
if (m_rawPrivateData.isEmpty()) {
|
2017-10-29 15:17:24 +00:00
|
|
|
m_error = tr("Can't write private key as it is empty");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!stream.writeString(m_type)) {
|
|
|
|
|
m_error = tr("Unexpected EOF when writing private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
for (QByteArray t : m_rawPrivateData) {
|
2017-10-29 15:17:24 +00:00
|
|
|
if (!stream.writeString(t)) {
|
|
|
|
|
m_error = tr("Unexpected EOF when writing private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!stream.writeString(m_comment)) {
|
|
|
|
|
m_error = tr("Unexpected EOF when writing private key");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-01 14:26:24 +00:00
|
|
|
QList<QByteArray> OpenSSHKey::publicParts() const
|
|
|
|
|
{
|
|
|
|
|
return m_rawPublicData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<QByteArray> OpenSSHKey::privateParts() const
|
|
|
|
|
{
|
|
|
|
|
return m_rawPrivateData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString& OpenSSHKey::privateType() const
|
|
|
|
|
{
|
|
|
|
|
return m_rawType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenSSHKey OpenSSHKey::restoreFromBinary(Type type, const QByteArray& serialized)
|
|
|
|
|
{
|
|
|
|
|
OpenSSHKey key;
|
|
|
|
|
auto data = binaryDeserialize(serialized);
|
|
|
|
|
key.setType(data.first);
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Public:
|
|
|
|
|
key.setPublicData(data.second);
|
|
|
|
|
break;
|
|
|
|
|
case Private:
|
|
|
|
|
key.setPrivateData(data.second);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray OpenSSHKey::serializeToBinary(Type type, const OpenSSHKey& key)
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(!key.encrypted());
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Public:
|
|
|
|
|
return binarySerialize(key.type(), key.publicParts());
|
|
|
|
|
case Private:
|
|
|
|
|
return binarySerialize(key.type(), key.privateParts());
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-29 15:17:24 +00:00
|
|
|
uint qHash(const OpenSSHKey& key)
|
|
|
|
|
{
|
|
|
|
|
return qHash(key.fingerprint());
|
|
|
|
|
}
|