了解QSsh —— qt版的ssh实现 (六) 密钥认证方式登录

      修改sshd_config,

PasswordAuthentication no

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile      %h/.ssh/authorized_keys

KeyRegenerationInterval 3600
ServerKeyBits 1024

      在服务器上,要登录的用户的主目录下,mkdir .ssh,chmod 700 .ssh,.ssh下创建文件authorized_keys,chmod 600 authorized_keys

      SecureCRT创建密钥对,这里对于passphrase,为空,不使用单独的密文来登录。同时选择openssh格式。将.pub的内容复制到authorized_keys。SecureCRT设置好后可直接登录。

      这样操作一下就清楚了,客户端创建密钥对,服务器得到.pub(在登录时会进行比较)。

      在前面分析的时候也看到有处代码会createkey...什么的。


      shell代码修改,

par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
      运行,没有任何反应。

      发现代码里没有错误信息获取,添加

connect(con,&QSsh::SshConnection::error,
            [this](QSsh::SshError errMsg){
                qDebug("err code: %d", errMsg);
                qDebug() << "err: "<<con->errorString();});
      可提示

err code: 5

err: "No private key file given."

      回过去看之前的分析,可以看到在connectToHost会判断

void SshConnectionPrivate::connectToHost()
{
    。。。
 
 
    try {
        if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
            createPrivateKey();
    } catch (const SshClientException &ex) {
        m_error = ex.error;
        m_errorString = ex.errorString;
        emit error(m_error);
        return;
    }
    。。。
}
void SshConnectionPrivate::createPrivateKey()
{
    if (m_connParams.privateKeyFile.isEmpty())
        throw SshClientException(SshKeyFileError, tr("No private key file given."));
    QFile keyFile(m_connParams.privateKeyFile);
    if (!keyFile.open(QIODevice::ReadOnly)) {
        throw SshClientException(SshKeyFileError,
            tr("Private key file error: %1").arg(keyFile.errorString()));
    }
    m_sendFacility.createAuthenticationKey(keyFile.readAll());
}
      在初始化参数时需要设置 privateKeyFile 私钥文件,设置一下,开始时用SecureCRT了,就用它。

      却打印出

err: "Decoding of private key file failed: Format not understood."

void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
{
    if (privKeyFileContents == m_cachedPrivKeyContents)
        return;
 
 
#ifdef CREATOR_SSH_DEBUG
    qDebug("%s: Key not cached, reading", Q_FUNC_INFO);
#endif
    QList<BigInt> pubKeyParams;
    QList<BigInt> allKeyParams;
    QString error1;
    QString error2;
    if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams, error1)
            && !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
                error2)) {
#ifdef CREATOR_SSH_DEBUG
        qDebug("%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
#endif
        throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
            "Format not understood."));
    }
 
 
    foreach (const BigInt &b, allKeyParams) {
        if (b.is_zero()) {
            throw SshClientException(SshKeyFileError,
                SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
        }
    }
 
 
    m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
    foreach (const BigInt &b, pubKeyParams)
        m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
    m_cachedPrivKeyContents = privKeyFileContents;
}

      可以看到PKCS8、OpenSSL,先搜索一下openssl和pkcs8之间的关系。

      openssl pem格式,某些语言需要pkcs8格式,将pem转为pkcs8的,pem格式是大众格式,SecureCRT生成的openssh格式就是pem格式。

      我选择的是openssh的,那用SecureCRT使用标准的去生成,再试下。还是一样,标准的也不是这两个?那就先看下这两个函数。

bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
    QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
{
    try {
        Pipe pipe;
        pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
        Private_Key * const key = PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever());
        if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) {
            m_authKeyAlgoName = SshCapabilities::PubKeyDss;
            m_authKey.reset(dsaKey);
            pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
                         << dsaKey->group_g() << dsaKey->get_y();
            allKeyParams << pubKeyParams << dsaKey->get_x();
        } else if (RSA_PrivateKey * const rsaKey = dynamic_cast<RSA_PrivateKey *>(key)) {
            m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
            m_authKey.reset(rsaKey);
            pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
            allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
                         << rsaKey->get_d();
        } else {
            qWarning("%s: Unexpected code flow, expected success or exception.", Q_FUNC_INFO);
            return false;
        }
    } catch (const Botan::Exception &ex) {
        error = QLatin1String(ex.what());
        return false;
    } catch (const Botan::Decoding_Error &ex) {
        error = QLatin1String(ex.what());
        return false;
    }
 
 
    return true;
}
 
 
bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
    QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
{
    try {
        bool syntaxOk = true;
        QList<QByteArray> lines = privKeyFileContents.split('\n');
        while (lines.last().isEmpty())
            lines.removeLast();
        if (lines.count() < 3) {
            syntaxOk = false;
        } else if (lines.first() == PrivKeyFileStartLineRsa) {
            if (lines.last() != PrivKeyFileEndLineRsa)
                syntaxOk = false;
            else
                m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
        } else if (lines.first() == PrivKeyFileStartLineDsa) {
            if (lines.last() != PrivKeyFileEndLineDsa)
                syntaxOk = false;
            else
                m_authKeyAlgoName = SshCapabilities::PubKeyDss;
        } else {
            syntaxOk = false;
        }
        if (!syntaxOk) {
            error = SSH_TR("Unexpected format.");
            return false;
        }
 
 
        QByteArray privateKeyBlob;
        for (int i = 1; i < lines.size() - 1; ++i)
            privateKeyBlob += lines.at(i);
        privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
 
 
        BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
        BER_Decoder sequence = decoder.start_cons(SEQUENCE);
        size_t version;
        sequence.decode (version);
        if (version != 0) {
            error = SSH_TR("Key encoding has version %1, expected 0.").arg(version);
            return false;
        }
 
 
        if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
            BigInt p, q, g, y, x;
            sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
            DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
            m_authKey.reset(dsaKey);
            pubKeyParams << p << q << g << y;
            allKeyParams << pubKeyParams << x;
        } else {
            BigInt p, q, e, d, n;
            sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
            RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(m_rng, p, q, e, d, n);
            m_authKey.reset(rsaKey);
            pubKeyParams << e << n;
            allKeyParams << pubKeyParams << p << q << d;
        }
 
 
        sequence.discard_remaining();
        sequence.verify_end();
    } catch (const Botan::Exception &ex) {
        error = QLatin1String(ex.what());
        return false;
    } catch (const Botan::Decoding_Error &ex) {
        error = QLatin1String(ex.what());
        return false;
    }
    return true;
}
      网上搜了下,好像有好几种数据格式,但推荐openssh2的。既然 SecureCRT可以用,那就只是qt这边解析问题了。大不了自己写一个。

     看函数要得到什么东西,哎,看到openssl,和自己生成的格式比较近,那就基于它来修改。自己的私钥格式:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDEBXrFsWakn2timFhS1t5Pv7lWN3wYmjjCc0H+y8wb0AsnwvUX
fZDxsDSD7RIUdRxN5ok7sQOJNTdOd6JEfwOFU0GzVpqe3TuOmD9plAYZFaAJJVO3
FsEnGRn0mgMAiSI3WkIcNWEJZnB4i3z7tW4lOHbt6UsqFaRaM8N6EvncowIDAQAB
AoGAPqApi0OUMYAlRMi7Xyv6tqvgQKVVZl7PR4CfUxI1UaV0Cu4Ec87QIgdTn9p7
7kFEG7dNnN5+7LUtyEn8c7nIm54m0SG4Ae84TP0emVnjcIQsKWged8Rn9UF7jt93
slBVd679liAEdSG8IOz7RSdfEM1gwR95iKI27T5Fp4VEB4ECQQD2V4gILpDERC9L
tT4JPjrXz8prflQCJuMtTkVLOBNAefzgWozou5WflRL4SNNx9WqiD6ltX+KJywrw
xeyhnZRhAkEAy7Tk/zqXMPn2NNmDyZ2br0rPMRGRT4m9Nyy8KFhqXH3HDT5hFnm3
SiFsKe44OT9cEhfDoztQNK3D43cpGa1PgwJASClwAeWSJsxKhoT7PT3rgtit1TtW
ZKL7nYP2LIiqqxKRDy+3Y13AANVsjyrWNeXMbNjlqtKRzAeclteD7/v3QQJBALk9
inA+C3bSF33/vsP8urIwyTDJ1OYLTzF5ULEvlaj+B6CXdRpHHNIgioemxSHp6APb
8F/jdBAzY8rZdVqQK5kCQGGkFCjuNFbellXPeaLJ+cp4tqhR7d4r4qmL3F7J/e6e
3QgyWHAE8KQ62hlY6iInYnpwvpwdKdpHL85KIv1hpIs=
-----END RSA PRIVATE KEY-----

应该要注意:1. 头 2. 尾

     而代码中正好有

    static const QByteArray PrivKeyFileStartLineRsa;
    static const QByteArray PrivKeyFileStartLineDsa;
    static const QByteArray PrivKeyFileEndLineRsa;
    static const QByteArray PrivKeyFileEndLineDsa;
     只要修改对就可以了。

PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");
-----BEGIN RSA PRIVATE KEY-----

-----BEGIN RSA PRIVATE KEY-----

      复制下来一比较,哎,一模一样的啊。那需要调试QSsh了,看看哪里出错,返回false

if (lines.count() < 3) {
            syntaxOk = false;
        } else if (lines.first() == PrivKeyFileStartLineRsa) {
            qDebug("+++ rsa file +++");
            if (lines.last() != PrivKeyFileEndLineRsa) {
                qDebug("+++ no rsa end +++");
                syntaxOk = false;
            } else
                m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
        } else if (lines.first() == PrivKeyFileStartLineDsa) {
            qDebug("+++ dsa file +++");
            if (lines.last() != PrivKeyFileEndLineDsa) {
                qDebug("+++ no dsa end +++");
                syntaxOk = false;
            } else
                m_authKeyAlgoName = SshCapabilities::PubKeyDss;
        } else {
            qDebug("+++ other +++");
            syntaxOk = false;
        }
        if (!syntaxOk) {
            qDebug("syntaxOk = false");
            error = SSH_TR("Unexpected format.");
            return false;
        }

+++ file lines 15 +++

+++ other +++

syntaxOk = false

      竟然提示other,那就打印一下lines.first()是什么东西

lines.first: -----BEGIN RSA PRIVATE KEY-----

, len 32

StartLineRsa: -----BEGIN RSA PRIVATE KEY-----, len 31


      果然是'\n'的问题。这样可以说是QSsh的一个BUG了

      看源码有sshkeycreationdialog.cpp,待会用这个去生成看下,估计也是一样的吧。哎,不对呀,这是从QTCreator提取出来的呀,难道被修改过?


      先加'\n'看下

PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----\n");
PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----\n");
PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----\n");
PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----\n");

+++ file lines 15 +++

lines.first: -----BEGIN RSA PRIVATE KEY-----

, len 32

StartLineRsa: -----BEGIN RSA PRIVATE KEY-----

, len 32

lines.last: -----END RSA PRIVATE KEY-----

, len 30

EndLineRsa: -----END RSA PRIVATE KEY-----

, len 30

+++ other +++

syntaxOk = false

      还是出错,真是对QByteArray的==无语了


    QByteArray str = "1234567890\n\n123123\n";
    const QByteArray test("1234567890");


    QList<QByteArray> lines = str.split('\n');
    while (lines.last().isEmpty())
        lines.removeLast();


    qDebug("+++ file lines %d +++", lines.count());
    qDebug("lines.first: %s, len %d", lines.first().constData(), lines.first().length());
    qDebug("test: %s, len %d", test.constData(), test.length());


    if(lines.first() == test) {
        qDebug(" eq ");
    } else {
        qDebug(" not eq ");
    }


+++ file lines 3 +++

lines.first: 1234567890,len 10

test: 1234567890,len 10

 eq


      看来是对QByteArray的split无语了。为什么这个就不含'\n',前面那个就会有'\n'

      后来冷静下来一想\n,应该是windows的\r\n和linux的\n问题。

果然“

 

QT下解决换行符、回车符与Windows不一致的问题

在Linux下敲一下回车键,系统加入一个"\n",换行符,ASCII值为10(0xA)

在Winows下敲一下回车键,系统加入一个"\n"和一个"\r",换行符、回车符,ASCII值为10(0xA)、13(0xd)

Qt秉承了Linux的风格,只加入了一个"\n"。

我使用Qt Creator在Winows写了一个串口调试程序,在调试AT指令时有些指令要求“AT+<Enter>”,把我难住了!后经过“toHex()”才发现上述问题。

在对QByteArray 求长度时换行符、回车符都占一个字符长度。如:对“A”+“<Enter>”Linux下2个字符长度,Winows下是3个字符长度。

所以解决方法如下:判断字符串最后一个字符是否“\n”,如果是就再加上一个“\r”。代码如下

QByteArray ba=ui->TextEdit->document()->toPlainText().toAscii();    //取出TextEdit的内容并转换为QByteArray 
int bl=ba.size();                                                                                    //取QByteArray 长度
if(ba[bl-1]=='\n')                                                                                    //判断最后一个字符是否为换行符“\n”
{
        ba.resize(bl+1);                                                                            //将QByteArray 长度加1
        ba[bl]='\r';                                                                                     //加上一个回车符“\r”
}

      这个问题就简单了,只要将生成的文件用UE转DOS到UNIX就可以了\r\n(0xd 0xa)就会变为\n(0xa)

      再测试,

state = 2, remote data size = 41

void QSsh::Internal::SshConnectionPrivate::handleServerId(): incoming data size = 41, incoming data = 'SSH-2.0-OpenSSH_6.9p1 Ubuntu-2ubuntu0.1

'

state = 2, remote data size = 952

state = 2, remote data size = 848

state = 3, remote data size = 52

state = 4, remote data size = 52

err code: 6

err: "Server rejected key."

      至少private key文件可以了。

      回过去看,发觉createPrivateKey()这函数名和功能不一致啊,应该改为

createAuthenticationKey()

      接下来继续调试QSsh,打印public key的内容,看生成了什么,和SecureCRT生成的是否一致

      竟然是空的。打印,文件读取没问题

      代码中看待fromBase64,所以每一行的内容其实是做base64转换。调试了半天,突然看到代码,是我把参数的userName给注释掉了。



      这样就简单了,

      如果是密码登录

    par.port = 22;
    par.userName = "ghost";
    par.password = "ghost";
    par.timeout = 500;
    par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
      如果是密钥认证

 par.port = 22;
 par.userName = "ghost";
 par.timeout = 500;
 par.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePublicKey;
    par.privateKeyFile = path;

你可能感兴趣的:(了解QSsh —— qt版的ssh实现 (六) 密钥认证方式登录)