Qt6开发自签名证书的https代理服务器

时间:2024-12-17 19:06:28

目标:制作一个具备类似Fiddler、Burpsuit、Wireshark的https协议代理抓包功能,但是集成到自己的app内,这样无需修改系统代理设置,使用QWebengineview通过自建的代理服务器,即可实现https包的实时监测、注入等自定义功能。

实现:

一、https代理服务器

1.使用QSslSocket类收发https包;使用多线程,提升代理服务器的性能。

ProxyClientThread.h

#ifndef PROXYCLIENTTHREAD_H
#define PROXYCLIENTTHREAD_H

#include <QObject>
#include <QTcpSocket>
#include <QNetworkProxy>
#include <QThread>
#include <QDebug>
#include <QSslSocket>
#include <QSslConfiguration>
#include <QFile>
#include <QSslKey>
#include <QByteArray>
#include <QtZlib/zlib.h>
#include <QRegularExpression>

struct HTTPHDR{
    QString host;
    quint16 port;
    bool newReq;
};

struct HTTPHDR2{
    quint8 CMD;
    QString CMDi;
    QString HOST;
    quint16 PORT;
    bool status;
};

enum ClientConnectionState {
    InitialRequest,
    TlsHandshake,
    DataTransfer
};

class ProxyClientThread : public QThread
{
    Q_OBJECT

public:
    ProxyClientThread(qintptr  sockDesc, QObject *parent = 0);
    ~ProxyClientThread();
    void run();
    QByteArray LastResquest;


private:
    QSslSocket clientSocket;
    QSslSocket serverSocket;
    QSslConfiguration sslConfig;
    int m_client_state=0;
    bool m_serverSocketConnected=false;

    QByteArray cNewReqData;
    QByteArray clientSockData;
    QByteArray serverSockData;


    void processClient();
    HTTPHDR2 processHeader(QByteArray hdr);
    bool loadCertificateAndKey();

    //HTTPHDR getHostInfo(QByteArray httpHeaderPartial);
    int pid;

    bool targetFound=false;//是否找到要注入的目标
    bool istargetHeader=true;//是否头部
    bool finishInject=false;//已完成注入
    QString cachedStr="";//缓存的内容;

private slots:
    void clientSockReadyRead();
    void serverSockConnected();
    void clientSockDisconnected();
    void serverSockDisconnected();
    void serverSockReadyRead();
    void clientTlsHandOk();
    void serverSockError(QAbstractSocket::SocketError errorMsg);
    void clientSockError(QAbstractSocket::SocketError errorMsg);

signals:
    void complete();
};

#endif // PROXYCLIENTTHREAD_H

ProxyClientThread.cpp部分代码

#include "proxyclientthread.h"

//#define DEBUG 1
QString keyFile="9291.0d30ab5b.js";
QString keyStr="}else e=await V.ImSdk.sendMessage({text:r,textExtra:a,referenceMessage:eQ";
QString injectStr=",window.MySendMsg=e";
ProxyClientThread::ProxyClientThread(qintptr sockDesc, QObject *parent) : QThread(parent)
{
    this->pid = sockDesc;

    //服务端连接
    connect (&this->serverSocket,SIGNAL(disconnected()),this,SLOT(serverSockDisconnected()));
    connect (&this->serverSocket,SIGNAL(readyRead()),this,SLOT(serverSockReadyRead()));
    connect (&this->serverSocket,SIGNAL(errorOccurred(QAbstractSocket::SocketError)),this,SLOT(serverSockError(QAbstractSocket::SocketError)));
    connect (&this->serverSocket,SIGNAL(connected()),this,SLOT(serverSockConnected()));
    this->serverSocket.setProxy(QNetworkProxy::NoProxy);

    //客户端
    m_client_state=InitialRequest;//客户端状态为初始化状态
    this->clientSocket.setSocketDescriptor(sockDesc);

    connect(&this->clientSocket, SIGNAL(disconnected()),this,SLOT(clientSockDisconnected()));
    connect(&this->clientSocket, SIGNAL(readyRead()),this,SLOT(clientSockReadyRead()),Qt::DirectConnection);
    connect(&this->clientSocket, SIGNAL(encrypted()), this, SLOT(clientTlsHandOk()));
    connect(&this->clientSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(clientSockError(QAbstractSocket::SocketError)));
}


void ProxyClientThread::clientSockReadyRead()
{
    this->processClient();
    return;
}
void ProxyClientThread::processClient()
{
    HTTPHDR2 pHead;

    //recieved incoming client packet
    this->clientSockData.clear();
    this->clientSockData = this->clientSocket.readAll();
    #ifdef DEBUG
    qDebug()<<this->pid<<"**收到客户端数据"<<this->clientSockData;
    #endif

    //查找匹配文件请求
    QString reqStr=QString(clientSockData);
    if(reqStr.contains("GET") and reqStr.contains(keyFile)){
        targetFound=true;
        qDebug()<<"找到要注入的文件--------------"<<reqStr;
        //修改请求头,不压缩
        reqStr.replace("Accept-Encoding: gzip, deflate, br","Accept-Encoding: identity");
        clientSockData=reqStr.toLocal8Bit();
    }

    if (this->serverSocket.state() == QAbstractSocket::ConnectedState){
        #ifdef DEBUG
        qDebug() <<this->pid<<": 4.2.向服务器发送请求:";//<<this->clientSockData;
        #endif
        serverSocket.write(clientSockData);
        return;
    }


    //处理 header
    pHead = this->processHeader(clientSockData.mid(0,100));
    if (!pHead.status){
        this->LastResquest = this->clientSockData;
        return;
    }


    //process SSL/TLS Connection;
    if (pHead.CMD == 3){    //CONNECT类型
         if (serverSocket.state() == QAbstractSocket::UnconnectedState){
             #ifdef DEBUG
             qDebug() <<this->pid<<": 1.收到客户发起CONNECT连接" << pHead.CMD << pHead.HOST << pHead.PORT;
             #endif
             m_client_state=TlsHandshake;//握手状态
             serverSocket.connectToHostEncrypted(pHead.HOST, pHead.PORT);

             return;
         }
    }


    if (serverSocket.state() == QAbstractSocket::UnconnectedState){
        #ifdef DEBUG
        qDebug()<<"***连接服务器";
        #endif
        LastResquest=clientSockData;
        serverSocket.connectToHostEncrypted(pHead.HOST,pHead.PORT);
        return;
    }

    return;
}

void ProxyClientThread::clientTlsHandOk(){
    //clientSockData = clientSocket.readAll();//读取客户端请求
    #ifdef DEBUG
    qDebug()<<this->pid<<": 4.<-- 已经和客户端ssl握手成功:"<<LastResquest;
    #endif
    serverSocket.write(LastResquest);

}

...

}

/*
 * 加载自签名证书
*/
bool ProxyClientThread::loadCertificateAndKey() {
    QFile certFile(":/certs/server.crt");
    if (!certFile.open(QIODevice::ReadOnly)) {
        qWarning() << "Certificate file not found!";
        return false;
    }
    QSslCertificate cert(&certFile);

    QFile keyFile(":/certs/server.key");
    if (!keyFile.open(QIODevice::ReadOnly)) {
        qWarning() << "Private key file not found!";
        return false;
    }
    QSslKey key(&keyFile, QSsl::Rsa);

    sslConfig.setLocalCertificate(cert);
    sslConfig.setPrivateKey(key);
    sslConfig.setProtocol(QSsl::TlsV1_2);

    return true;
}

3.proxyserver.h

#ifndef PROXYSERVER_H
#define PROXYSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QTcpServer>
//#include <proxyclient.h>
#include <proxyclientthread.h>

class proxyServer : public QTcpServer {
    Q_OBJECT

public:
    explicit proxyServer(QObject* parent = nullptr) : QTcpServer(parent) {}

protected:
    void incomingConnection(qintptr socketDescriptor) override {
        // 创建子线程并传递 socket 描述符
        ProxyClientThread* workerThread = new ProxyClientThread(socketDescriptor, this);
        // 启动子线程
        workerThread->run();
    }
};

#endif // PROXYSERVER_H

代码的逻辑其实不难,按照代理服务器的连接过程补全相关代码就可以了。

二、QWebengineView部分

使用代理服务连接,该设置仅在app内有效,不影响其他应用。

设置QWebengineView的page忽略证书错误(因为是自签名证书),不处理的话无法访问https页面。

// 配置 QWebEngineView 使用代理
QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", 8787);
QNetworkProxy::setApplicationProxy(proxy);

//忽略证书错误
connect(webPage,SIGNAL(certificateError(QWebEngineCertificateError)),this,SLOT(on_certerror(QWebEngineCertificateError)));

void xxxx::on_certerror(QWebEngineCertificateError certerror){
    auto mutableError = const_cast<QWebEngineCertificateError&>(certerror);
    mutableError.acceptCertificate();
    qDebug()<<"忽略证书错误。";
    if(certerror.type()==QWebEngineCertificateError::CertificateAuthorityInvalid)
        {
            auto error=const_cast<QWebEngineCertificateError&>(certerror);
            qDebug()<<"忽略证书错误。";
            error.acceptCertificate();
        }
}

经过验证,这个方案可行,可以在代理服务器端修改客户端发起的请求,也可以修改服务器端返回的任何数据(已解密过的)后再返回给客户端,但是前提是要做好对应的处理工作,比如Content-length记得要修改。