mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
SSH Agent: Support old unencrypted DSA and RSA keys
This commit is contained in:
parent
90eea14b8c
commit
cbb70cdc7c
@ -444,6 +444,10 @@ bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.comment().isEmpty()) {
|
||||||
|
key.setComment(m_entry->username());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
186
src/sshagent/ASN1Key.cpp
Normal file
186
src/sshagent/ASN1Key.cpp
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Toni Spets <toni.spets@iki.fi>
|
||||||
|
* Copyright (C) 2018 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 "ASN1Key.h"
|
||||||
|
#include <gcrypt.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr quint8 TAG_INT = 0x02;
|
||||||
|
constexpr quint8 TAG_SEQUENCE = 0x30;
|
||||||
|
constexpr quint8 KEY_ZERO = 0x0;
|
||||||
|
|
||||||
|
bool nextTag(BinaryStream& stream, quint8& tag, quint32& len)
|
||||||
|
{
|
||||||
|
stream.read(tag);
|
||||||
|
|
||||||
|
quint8 lenByte;
|
||||||
|
stream.read(lenByte);
|
||||||
|
|
||||||
|
if (lenByte & 0x80) {
|
||||||
|
quint32 bytes = lenByte & ~0x80;
|
||||||
|
if (bytes == 1) {
|
||||||
|
stream.read(lenByte);
|
||||||
|
len = lenByte;
|
||||||
|
} else if (bytes == 2) {
|
||||||
|
quint16 lenShort;
|
||||||
|
stream.read(lenShort);
|
||||||
|
len = lenShort;
|
||||||
|
} else if (bytes == 4) {
|
||||||
|
stream.read(len);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
len = lenByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parseHeader(BinaryStream& stream, quint8 wantedType)
|
||||||
|
{
|
||||||
|
quint8 tag;
|
||||||
|
quint32 len;
|
||||||
|
|
||||||
|
nextTag(stream, tag, len);
|
||||||
|
|
||||||
|
if (tag != TAG_SEQUENCE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTag(stream, tag, len);
|
||||||
|
|
||||||
|
if (tag != TAG_INT || len != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint8 keyType;
|
||||||
|
stream.read(keyType);
|
||||||
|
|
||||||
|
return (keyType == wantedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readInt(BinaryStream& stream, QByteArray& target)
|
||||||
|
{
|
||||||
|
quint8 tag;
|
||||||
|
quint32 len;
|
||||||
|
|
||||||
|
nextTag(stream, tag, len);
|
||||||
|
|
||||||
|
if (tag != TAG_INT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.resize(len);
|
||||||
|
stream.read(target);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray calculateIqmp(QByteArray& bap, QByteArray& baq)
|
||||||
|
{
|
||||||
|
gcry_mpi_t u, p, q;
|
||||||
|
QByteArray iqmp_hex;
|
||||||
|
|
||||||
|
u = gcry_mpi_snew(bap.length() * 8);
|
||||||
|
gcry_mpi_scan(&p, GCRYMPI_FMT_HEX, bap.toHex().data(), 0, nullptr);
|
||||||
|
gcry_mpi_scan(&q, GCRYMPI_FMT_HEX, baq.toHex().data(), 0, nullptr);
|
||||||
|
|
||||||
|
mpi_invm(u, q, p);
|
||||||
|
|
||||||
|
iqmp_hex.resize((bap.length() + 1) * 2);
|
||||||
|
gcry_mpi_print(GCRYMPI_FMT_HEX, reinterpret_cast<unsigned char*>(iqmp_hex.data()), iqmp_hex.length(), nullptr, u);
|
||||||
|
|
||||||
|
gcry_mpi_release(u);
|
||||||
|
gcry_mpi_release(p);
|
||||||
|
gcry_mpi_release(q);
|
||||||
|
|
||||||
|
return QByteArray::fromHex(iqmp_hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
|
||||||
|
{
|
||||||
|
BinaryStream stream(&ba);
|
||||||
|
|
||||||
|
if (!parseHeader(stream, KEY_ZERO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray p,q,g,y,x;
|
||||||
|
readInt(stream, p);
|
||||||
|
readInt(stream, q);
|
||||||
|
readInt(stream, g);
|
||||||
|
readInt(stream, y);
|
||||||
|
readInt(stream, x);
|
||||||
|
|
||||||
|
QList<QByteArray> publicData;
|
||||||
|
publicData.append(p);
|
||||||
|
publicData.append(q);
|
||||||
|
publicData.append(g);
|
||||||
|
publicData.append(y);
|
||||||
|
|
||||||
|
QList<QByteArray> privateData;
|
||||||
|
privateData.append(p);
|
||||||
|
privateData.append(q);
|
||||||
|
privateData.append(g);
|
||||||
|
privateData.append(y);
|
||||||
|
privateData.append(x);
|
||||||
|
|
||||||
|
key.setType("ssh-dss");
|
||||||
|
key.setPublicData(publicData);
|
||||||
|
key.setPrivateData(privateData);
|
||||||
|
key.setComment("");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key)
|
||||||
|
{
|
||||||
|
BinaryStream stream(&ba);
|
||||||
|
|
||||||
|
if (!parseHeader(stream, KEY_ZERO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray n,e,d,p,q,dp,dq,qinv;
|
||||||
|
readInt(stream, n);
|
||||||
|
readInt(stream, e);
|
||||||
|
readInt(stream, d);
|
||||||
|
readInt(stream, p);
|
||||||
|
readInt(stream, q);
|
||||||
|
readInt(stream, dp);
|
||||||
|
readInt(stream, dq);
|
||||||
|
readInt(stream, qinv);
|
||||||
|
|
||||||
|
QList<QByteArray> publicData;
|
||||||
|
publicData.append(e);
|
||||||
|
publicData.append(n);
|
||||||
|
|
||||||
|
QList<QByteArray> privateData;
|
||||||
|
privateData.append(n);
|
||||||
|
privateData.append(e);
|
||||||
|
privateData.append(d);
|
||||||
|
privateData.append(calculateIqmp(p, q));
|
||||||
|
privateData.append(p);
|
||||||
|
privateData.append(q);
|
||||||
|
|
||||||
|
key.setType("ssh-rsa");
|
||||||
|
key.setPublicData(publicData);
|
||||||
|
key.setPrivateData(privateData);
|
||||||
|
key.setComment("");
|
||||||
|
return true;
|
||||||
|
}
|
31
src/sshagent/ASN1Key.h
Normal file
31
src/sshagent/ASN1Key.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Toni Spets <toni.spets@iki.fi>
|
||||||
|
* Copyright (C) 2018 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ASN1KEY_H
|
||||||
|
#define ASN1KEY_H
|
||||||
|
|
||||||
|
#include "OpenSSHKey.h"
|
||||||
|
#include <QtCore>
|
||||||
|
|
||||||
|
namespace ASN1Key
|
||||||
|
{
|
||||||
|
bool parseDSA(QByteArray& ba, OpenSSHKey& key);
|
||||||
|
bool parseRSA(QByteArray& ba, OpenSSHKey& key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ASN1KEY_H
|
@ -9,6 +9,7 @@ if(WITH_XC_SSHAGENT)
|
|||||||
BinaryStream.cpp
|
BinaryStream.cpp
|
||||||
KeeAgentSettings.cpp
|
KeeAgentSettings.cpp
|
||||||
OpenSSHKey.cpp
|
OpenSSHKey.cpp
|
||||||
|
ASN1Key.cpp
|
||||||
SSHAgent.cpp
|
SSHAgent.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,11 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "OpenSSHKey.h"
|
#include "OpenSSHKey.h"
|
||||||
|
#include "ASN1Key.h"
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include "crypto/SymmetricCipher.h"
|
#include "crypto/SymmetricCipher.h"
|
||||||
|
|
||||||
|
const QString OpenSSHKey::TYPE_DSA = "DSA PRIVATE KEY";
|
||||||
|
const QString OpenSSHKey::TYPE_RSA = "RSA PRIVATE KEY";
|
||||||
|
const QString OpenSSHKey::TYPE_OPENSSH = "OPENSSH PRIVATE KEY";
|
||||||
|
|
||||||
// bcrypt_pbkdf.cpp
|
// bcrypt_pbkdf.cpp
|
||||||
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
|
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
|
||||||
|
|
||||||
@ -34,6 +39,7 @@ OpenSSHKey::OpenSSHKey(QObject *parent)
|
|||||||
, m_rawPrivateData(QByteArray())
|
, m_rawPrivateData(QByteArray())
|
||||||
, m_publicData(QList<QByteArray>())
|
, m_publicData(QList<QByteArray>())
|
||||||
, m_privateData(QList<QByteArray>())
|
, m_privateData(QList<QByteArray>())
|
||||||
|
, m_privateType(QString())
|
||||||
, m_comment(QString())
|
, m_comment(QString())
|
||||||
, m_error(QString())
|
, m_error(QString())
|
||||||
{
|
{
|
||||||
@ -178,14 +184,31 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beginMatch.captured(1) != "OPENSSH PRIVATE KEY") {
|
m_privateType = beginMatch.captured(1);
|
||||||
m_error = tr("This is not an OpenSSH key, only modern keys are supported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rows.removeFirst();
|
rows.removeFirst();
|
||||||
rows.removeLast();
|
rows.removeLast();
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
if (pemOptions.contains("Proc-Type")) {
|
||||||
|
m_error = tr("Encrypted keys are not yet supported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
out = QByteArray::fromBase64(rows.join("").toLatin1());
|
out = QByteArray::fromBase64(rows.join("").toLatin1());
|
||||||
|
|
||||||
if (out.isEmpty()) {
|
if (out.isEmpty()) {
|
||||||
@ -204,51 +227,58 @@ bool OpenSSHKey::parse(const QByteArray& in)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryStream stream(&data);
|
if (m_privateType == TYPE_DSA || m_privateType == TYPE_RSA) {
|
||||||
|
m_rawPrivateData = data;
|
||||||
|
} else if (m_privateType == TYPE_OPENSSH) {
|
||||||
|
BinaryStream stream(&data);
|
||||||
|
|
||||||
QByteArray magic;
|
QByteArray magic;
|
||||||
magic.resize(15);
|
magic.resize(15);
|
||||||
|
|
||||||
if (!stream.read(magic)) {
|
if (!stream.read(magic)) {
|
||||||
m_error = tr("Key file way too small.");
|
m_error = tr("Key file way too small.");
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QString::fromLatin1(magic) != "openssh-key-v1") {
|
|
||||||
m_error = tr("Key file magic header id invalid");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.readString(m_cipherName);
|
|
||||||
stream.readString(m_kdfName);
|
|
||||||
stream.readString(m_kdfOptions);
|
|
||||||
|
|
||||||
quint32 numberOfKeys;
|
|
||||||
stream.read(numberOfKeys);
|
|
||||||
|
|
||||||
if (numberOfKeys == 0) {
|
|
||||||
m_error = tr("Found zero keys");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (quint32 i = 0; i < numberOfKeys; ++i) {
|
|
||||||
QByteArray publicKey;
|
|
||||||
if (!stream.readString(publicKey)) {
|
|
||||||
m_error = tr("Failed to read public key.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0) {
|
if (QString::fromLatin1(magic) != "openssh-key-v1") {
|
||||||
BinaryStream publicStream(&publicKey);
|
m_error = tr("Key file magic header id invalid");
|
||||||
if (!readPublic(publicStream)) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.readString(m_cipherName);
|
||||||
|
stream.readString(m_kdfName);
|
||||||
|
stream.readString(m_kdfOptions);
|
||||||
|
|
||||||
|
quint32 numberOfKeys;
|
||||||
|
stream.read(numberOfKeys);
|
||||||
|
|
||||||
|
if (numberOfKeys == 0) {
|
||||||
|
m_error = tr("Found zero keys");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (quint32 i = 0; i < numberOfKeys; ++i) {
|
||||||
|
QByteArray publicKey;
|
||||||
|
if (!stream.readString(publicKey)) {
|
||||||
|
m_error = tr("Failed to read public key.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// padded list of keys
|
if (i == 0) {
|
||||||
if (!stream.readString(m_rawPrivateData)) {
|
BinaryStream publicStream(&publicKey);
|
||||||
m_error = tr("Corrupted key file, reading private key failed");
|
if (!readPublic(publicStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// padded list of keys
|
||||||
|
if (!stream.readString(m_rawPrivateData)) {
|
||||||
|
m_error = tr("Corrupted key file, reading private key failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_error = tr("Unsupported key type: %s").arg(m_privateType);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +313,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
|
|||||||
} else if (m_cipherName == "aes256-ctr") {
|
} else if (m_cipherName == "aes256-ctr") {
|
||||||
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
|
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
|
||||||
} else if (m_cipherName != "none") {
|
} else if (m_cipherName != "none") {
|
||||||
m_error = tr("Unknown cipher: ") + m_cipherName;
|
m_error = tr("Unknown cipher: %s").arg(m_cipherName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,8 +350,13 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
|
|||||||
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
|
ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize());
|
||||||
|
|
||||||
cipher->init(keyData, ivData);
|
cipher->init(keyData, ivData);
|
||||||
|
|
||||||
|
if (!cipher->init(keyData, ivData)) {
|
||||||
|
m_error = cipher->errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (m_kdfName != "none") {
|
} else if (m_kdfName != "none") {
|
||||||
m_error = tr("Unknown KDF: ") + m_kdfName;
|
m_error = tr("Unknown KDF: %s").arg(m_kdfName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,20 +371,39 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryStream keyStream(&rawPrivateData);
|
if (m_privateType == TYPE_DSA) {
|
||||||
|
if (!ASN1Key::parseDSA(rawPrivateData, *this)) {
|
||||||
|
m_error = tr("Reading DSA private key failed, only unencrypted keys are supported at this time");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
quint32 checkInt1;
|
return true;
|
||||||
quint32 checkInt2;
|
} else if (m_privateType == TYPE_RSA) {
|
||||||
|
if (!ASN1Key::parseRSA(rawPrivateData, *this)) {
|
||||||
|
m_error = tr("Reading RSA private key failed, only unencrypted keys are supported at this time");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
keyStream.read(checkInt1);
|
return true;
|
||||||
keyStream.read(checkInt2);
|
} else if (m_privateType == TYPE_OPENSSH) {
|
||||||
|
BinaryStream keyStream(&rawPrivateData);
|
||||||
|
|
||||||
if (checkInt1 != checkInt2) {
|
quint32 checkInt1;
|
||||||
m_error = tr("Decryption failed, wrong passphrase?");
|
quint32 checkInt2;
|
||||||
return false;
|
|
||||||
|
keyStream.read(checkInt1);
|
||||||
|
keyStream.read(checkInt2);
|
||||||
|
|
||||||
|
if (checkInt1 != checkInt2) {
|
||||||
|
m_error = tr("Decryption failed, wrong passphrase?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return readPrivate(keyStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readPrivate(keyStream);
|
m_error = tr("Unsupported key type: %s").arg(m_privateType);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenSSHKey::readPublic(BinaryStream& stream)
|
bool OpenSSHKey::readPublic(BinaryStream& stream)
|
||||||
@ -371,7 +425,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream)
|
|||||||
} else if (m_type == "ssh-ed25519") {
|
} else if (m_type == "ssh-ed25519") {
|
||||||
keyParts = 1;
|
keyParts = 1;
|
||||||
} else {
|
} else {
|
||||||
m_error = tr("Unknown key type: ") + m_type;
|
m_error = tr("Unknown key type: %s").arg(m_type);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +462,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream)
|
|||||||
} else if (m_type == "ssh-ed25519") {
|
} else if (m_type == "ssh-ed25519") {
|
||||||
keyParts = 2;
|
keyParts = 2;
|
||||||
} else {
|
} else {
|
||||||
m_error = tr("Unknown key type: ") + m_type;
|
m_error = tr("Unknown key type: %s").arg(m_type);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,10 @@ public:
|
|||||||
bool writePrivate(BinaryStream& stream);
|
bool writePrivate(BinaryStream& stream);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static const QString TYPE_DSA;
|
||||||
|
static const QString TYPE_RSA;
|
||||||
|
static const QString TYPE_OPENSSH;
|
||||||
|
|
||||||
bool parsePEM(const QByteArray& in, QByteArray& out);
|
bool parsePEM(const QByteArray& in, QByteArray& out);
|
||||||
|
|
||||||
QString m_type;
|
QString m_type;
|
||||||
@ -64,6 +68,7 @@ private:
|
|||||||
QByteArray m_rawPrivateData;
|
QByteArray m_rawPrivateData;
|
||||||
QList<QByteArray> m_publicData;
|
QList<QByteArray> m_publicData;
|
||||||
QList<QByteArray> m_privateData;
|
QList<QByteArray> m_privateData;
|
||||||
|
QString m_privateType;
|
||||||
QString m_comment;
|
QString m_comment;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
@ -260,6 +260,10 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.comment().isEmpty()) {
|
||||||
|
key.setComment(e->username());
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.removeAtDatabaseClose()) {
|
if (settings.removeAtDatabaseClose()) {
|
||||||
removeIdentityAtLock(key, uuid);
|
removeIdentityAtLock(key, uuid);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ void TestOpenSSHKey::testParse()
|
|||||||
QCOMPARE(key.cipherName(), QString("none"));
|
QCOMPARE(key.cipherName(), QString("none"));
|
||||||
QCOMPARE(key.type(), QString("ssh-ed25519"));
|
QCOMPARE(key.type(), QString("ssh-ed25519"));
|
||||||
QCOMPARE(key.comment(), QString("opensshkey-test-parse@keepassxc"));
|
QCOMPARE(key.comment(), QString("opensshkey-test-parse@keepassxc"));
|
||||||
|
QCOMPARE(key.fingerprint(), QString("SHA256:D1fVmA15YXzaJ5sdO9dXxo5coHL/pnNaIfCvokHzTA4"));
|
||||||
|
|
||||||
QByteArray publicKey, privateKey;
|
QByteArray publicKey, privateKey;
|
||||||
BinaryStream publicStream(&publicKey), privateStream(&privateKey);
|
BinaryStream publicStream(&publicKey), privateStream(&privateKey);
|
||||||
@ -61,6 +62,77 @@ void TestOpenSSHKey::testParse()
|
|||||||
QVERIFY(privateKey.length() == 154);
|
QVERIFY(privateKey.length() == 154);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestOpenSSHKey::testParseDSA()
|
||||||
|
{
|
||||||
|
const QString keyString = QString(
|
||||||
|
"-----BEGIN DSA PRIVATE KEY-----\n"
|
||||||
|
"MIIBuwIBAAKBgQCudjbvSh8JxQOr2laCqZM1t4kNWBETVOXz5vgk9iw6Z5opB9/k\n"
|
||||||
|
"g4nFc1PVq7fdAIc8W/5WCAjugKcxPb9PIHfcwY2fimmiPWFK68/eHKLoCuIn2wxB\n"
|
||||||
|
"63ig2hAhx5U5aYG9QHkNCaT6VX7rc19nToSeZXlpja4x54/DaQaqOEWYsQIVAOer\n"
|
||||||
|
"UQWfccz7KXUu6+x7heGob6I3AoGAVDRFJIlL0DI/4nePIcgwgwbfgs2ojSu21g4w\n"
|
||||||
|
"dQoXvqU34XydPgPQ985XIIuiDkaomRw4yYd/Sh4ZapFcrP++iJ1V+WS6kLcWPHMq\n"
|
||||||
|
"poYwk8mq6GLbPFLEjr+n6HgX5ln15n3i4WAopNH7mEl0glY9L0rxmcN0XOpqw6Ux\n"
|
||||||
|
"ETGEfAwCgYAiOeYwblMkkTIGtVx5NvNsOlfrBYL4GqUP9oQMO5I+xLZLWQIf+7Jp\n"
|
||||||
|
"8t6mwxSBz0RHjNVQ11vZowNjq3587aLy57bVwf2lIm9KSvS6z9HoNbHgQimcBorR\n"
|
||||||
|
"J9l9RUrj7TnsZgiVw66j2r34nHRHRtggiO+qrMtw7MJc0Q7jiuTmzgIVAMXbk0T9\n"
|
||||||
|
"nBfSLWQz/L8RexU2GR4e\n"
|
||||||
|
"-----END DSA PRIVATE KEY-----\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
const QByteArray keyData = keyString.toLatin1();
|
||||||
|
|
||||||
|
OpenSSHKey key;
|
||||||
|
QVERIFY(key.parse(keyData));
|
||||||
|
QVERIFY(!key.encrypted());
|
||||||
|
QCOMPARE(key.cipherName(), QString("none"));
|
||||||
|
QCOMPARE(key.type(), QString("ssh-dss"));
|
||||||
|
QCOMPARE(key.comment(), QString(""));
|
||||||
|
QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOpenSSHKey::testParseRSA()
|
||||||
|
{
|
||||||
|
const QString keyString = QString(
|
||||||
|
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||||
|
"MIIEpAIBAAKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbB\n"
|
||||||
|
"y1QTy0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErN\n"
|
||||||
|
"mH8vU91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD\n"
|
||||||
|
"9T3lm2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/\n"
|
||||||
|
"VzF3MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pO\n"
|
||||||
|
"XDpy/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQABAoIBAQCnEUc1LUQxeM5K\n"
|
||||||
|
"wANNCqE+SgoIClPdeHC7fmrLh1ttqe6ib6ybBUFRS31yXs0hnfefunVEDKlaV8K2\n"
|
||||||
|
"N52UAMAsngFHQNRvGh6kEWeZPd9Xc+N98TZbNCjcT+DGKc+Om8wqH5DrodZlCq4c\n"
|
||||||
|
"GaoT4HnE4TjWtZTH2XXrWF9I66PKFWf070R44nvyVcvaZi4pC2YmURRPuGF6K1iK\n"
|
||||||
|
"dH8zM6HHG1UGu2W6hLNn+K01IulG0Lb8eWNaNYMmtQWaxyp7I2IWkkecUs3nCuiR\n"
|
||||||
|
"byFOoomCjdh8r9yZFvwxjGUhgtkALN9GCU0Mwve+s11IB2gevruN+q9/Qejbyfdm\n"
|
||||||
|
"IlgLAeTRAoGBANRcVzW9CYeobCf+U9hKJFEOur8XO+J2mTMaELA0EjWpTJFAeIT7\n"
|
||||||
|
"KeRpCRG4/vOSklxxRF6vP1EACA4Z+5BlN+FTipHHs+bSEgqkPZiiANDH7Zot5Iqv\n"
|
||||||
|
"1q0fRyldNRZNZK7DWp08BPNVWGA/EnEuKJiURxnxBaxNXbUyMCdjxvMvAoGBANRT\n"
|
||||||
|
"utbrqS/bAa/DcHKn3V6DRqBl3TDOfvCNjiKC84a67F2uXgzLIdMktr4d1NyCZVJd\n"
|
||||||
|
"7/zVgWORLIdg1eAi6rYGoOvNV39wwga7CF+m9sBY0wAaKYCELe6L26r4aQHVCX6n\n"
|
||||||
|
"rnIgUv+4o4itmU2iP0r3wlmDC9pDRQP82vfvQPlfAoGASwhleANW/quvq2HdViq8\n"
|
||||||
|
"Mje2HBalfhrRfpDTHK8JUBSFjTzuWG42GxJRtgVbb8x2ElujAKGDCaetMO5VSGu7\n"
|
||||||
|
"Fs5hw6iAFCpdXY0yhl+XUi2R8kwM2EPQ4lKO3jqkq0ClNmqn9a5jQWcCVt9yMLNS\n"
|
||||||
|
"fLbHeI8EpiCf34ngIcrLXNkCgYEAzlcEZuKkC46xB+dNew8pMTUwSKZVm53BfPKD\n"
|
||||||
|
"44QRN6imFbBjU9mAaJnwQbfp6dWKs834cGPolyM4++MeVfB42iZ88ksesgmZdUMD\n"
|
||||||
|
"szkl6O0pOJs0I+HQZVdjRbadDZvD22MHQ3+oST1dJ3FVXz3Cdo9qPuT8esMO6f4r\n"
|
||||||
|
"qfDH2s8CgYAXC/lWWHQ//PGP0pH4oiEXisx1K0X1u0xMGgrChxBRGRiKZUwNMIvJ\n"
|
||||||
|
"TqUu7IKizK19cLHF/NBvxHYHFw+m7puNjn6T1RtRCUjRZT7Dx1VHfVosL9ih5DA8\n"
|
||||||
|
"tpbZA5KGKcvHtB5DDgT0MHwzBZnb4Q//Rhovzn+HXZPsJTTgHHy3NQ==\n"
|
||||||
|
"-----END RSA PRIVATE KEY-----\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
const QByteArray keyData = keyString.toLatin1();
|
||||||
|
|
||||||
|
OpenSSHKey key;
|
||||||
|
QVERIFY(key.parse(keyData));
|
||||||
|
QVERIFY(!key.encrypted());
|
||||||
|
QCOMPARE(key.cipherName(), QString("none"));
|
||||||
|
QCOMPARE(key.type(), QString("ssh-rsa"));
|
||||||
|
QCOMPARE(key.comment(), QString(""));
|
||||||
|
QCOMPARE(key.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk"));
|
||||||
|
}
|
||||||
|
|
||||||
void TestOpenSSHKey::testDecryptAES256CBC()
|
void TestOpenSSHKey::testDecryptAES256CBC()
|
||||||
{
|
{
|
||||||
const QString keyString = QString(
|
const QString keyString = QString(
|
||||||
|
@ -29,6 +29,8 @@ class TestOpenSSHKey : public QObject
|
|||||||
private slots:
|
private slots:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
void testParse();
|
void testParse();
|
||||||
|
void testParseDSA();
|
||||||
|
void testParseRSA();
|
||||||
void testDecryptAES256CBC();
|
void testDecryptAES256CBC();
|
||||||
void testDecryptAES256CTR();
|
void testDecryptAES256CTR();
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user