1 證書生成
因為目標(biāo)是實現(xiàn)雙向認證米碰,所以需要將自己的公鑰和私鑰以及對端的私鑰加載到Qt的安全環(huán)境中。證書可借助keytool和openssl工具生成堡掏,總結(jié)幾個比較常用的生成命令如下:
# 生成jks格式密鑰庫
keytool -genkey -v -alias tomcat -keyalg RSA -keystore tomcat.keystore -validity 36500
# 從jks格式密鑰庫中導(dǎo)出證書(DER格式)
keytool -keystore tomcat.keystore -export -alias tomcat -file server.cer -storepass 123456
# 生成p12格式密鑰庫
keytool -genkey -v -alias mykey -keyalg RSA -storetype PKCS12 -keystore mykey.p12 -storepass 123456
# 將jks格式密鑰庫轉(zhuǎn)化為p12格式
keytool -importkeystore -srckeystore tomcat.keystore -destkeystore tomcat.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass 123456 -deststorepass 123456 -srcalias tomcat -destalias tomcat -srckeypass 123456 -destkeypass 123456 -noprompt
# 從p12密鑰庫中導(dǎo)出公鑰和私鑰(PEM格式)
openssl pkcs12 -clcerts -nokeys -in mykey.p12 -out cert.pem
openssl pkcs12 -nocerts -nodes -in mykey.p12 -out private.pem
# 向jks格式密鑰庫中導(dǎo)入可信任的證書
keytool -import -v -file cert.pem -keystore clients.keystore -storepass 123456
2 QSslSocket設(shè)置
首先客戶端和服務(wù)器都必須加載本地的私鑰应结、證書和信任庫。在QSslSocket中這三個設(shè)置分別對應(yīng)localCertificate, privateKey和caCertificates泉唁。同時雙向認證需要設(shè)置VerifyPeer和Depth = 1鹅龄。以客戶端為例,加載方法如下:
bool ClientSimulator::loadSslFiles()
{
bool openOk = false;
QFile certFile(QDir::currentPath() + QString("/sslCert/server.cer"));
openOk = certFile.open(QIODevice::ReadOnly);
m_certificate = QSslCertificate(certFile.readAll(), QSsl::Der);
openOk &= !m_certificate.isNull();
QFile keyFile(QDir::currentPath() + QString("/sslCert/ckey.pem"));
openOk &= keyFile.open(QIODevice::ReadOnly);
m_privateKey = QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
openOk &= !m_privateKey.isNull();
QFile peerFile(QDir::currentPath() + QString("/sslCert/cert.pem"));
openOk &= peerFile.open(QIODevice::ReadOnly);
QSslCertificate peerCert(peerFile.readAll(), QSsl::Pem);
bool peerCertValid = !peerCert.isNull();
openOk &= peerCertValid;
QList<QSslCertificate> caCerts;
caCerts << peerCert;
m_caCertificates = caCerts;
return openOk;
}
之后在客戶端連接到服務(wù)器時亭畜,設(shè)置加載好的證書和密鑰:
if(loadSslFiles())
{
m_socket->setLocalCertificate(m_certificate);
m_socket->setPrivateKey(m_privateKey);
m_socket->setCaCertificates(m_caCertificates);
m_socket->setPeerVerifyMode(QSslSocket::VerifyPeer);
m_socket->setPeerVerifyDepth(1);
m_socket->connectToHostEncrypted(ui->lineEditIP->text(), port);
}
else
{
QMessageBox::warning(this, "SSL File Error", "Load SSL Files failed.");
}
這里m_socket是QSslSocket類的實例扮休。
服務(wù)器端的設(shè)置類似,重載incommingConnection方法拴鸵,參考實現(xiàn)如下:
void SslServer::incomingConnection(qintptr socketDescriptor)
{
if(!m_client.isNull())
{
m_client->disconnectFromHost();
disconnect(m_client, SIGNAL(readyRead()), this, SLOT(onRecvFromClient()));
disconnect(m_client, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(onSslErrors(QList<QSslError>)));
delete m_client;
}
m_client = new QSslSocket(this);
m_client->setSocketDescriptor(socketDescriptor);
if(m_sslConfig != NULL)
{
m_client->setLocalCertificate(m_sslConfig->certificate());
m_client->setPrivateKey(m_sslConfig->privateKey());
m_client->setCaCertificates(m_sslConfig->caCertificates());
}
m_client->setPeerVerifyMode(QSslSocket::VerifyPeer);
m_client->setPeerVerifyDepth(1);
connect(m_client, SIGNAL(readyRead()), this, SLOT(onRecvFromClient()));
connect(m_client, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(onSslErrors(const QList<QSslError> &)));
m_client->startServerEncryption();
QTcpServer::incomingConnection(socketDescriptor);
}
注意:
- 在客戶端連接服務(wù)端時玷坠,要調(diào)用加密連接方法connectToHostEncrypted()蜗搔。如果使用普通的鏈接connectToHost()方法,會報無效套接字的錯八堡。
- 同理樟凄,在服務(wù)端連接客戶端時,需調(diào)用加密通信方法startServerEncryption()兄渺。
- 連接的IP要和信任庫中證書所提供的IP一致缝龄,否則可能會出現(xiàn)IP不匹配的告警。
- 如果ssl環(huán)境設(shè)置需要在多個地方復(fù)用溶耘,可以將設(shè)置統(tǒng)一加載到QSslConfiguration類的實例中二拐,之后通過QSslSocket的setSslConfiguration方法進行加載。QSslConfiguration提供的接口與上面范例中的比較類似凳兵,這里就不贅述了百新。
- 除上述直接載入證書和秘鑰文件的方法外,qt5.4之后還支持直接從pkcs12格式的文件解析并載入證書庐扫,調(diào)用靜態(tài)方法QSslCertificate::importPkcs12()即可饭望,范例如下:
QFile keyFile("/certs/ks.p12");
bool openOK = keyFile.open(QIODevice::ReadWrite);
QSslKey key;
QSslCertificate certs;
QList<QSslCertificate> caCerts;
QByteArray passPhrase = QString("test123").toLatin1();
openOK = QSslCertificate::importPkcs12(&keyFile, &key, &certs, &caCerts, passPhrase);
keyFile.close();