自签名CA证书
自定义实现一个CA(证书颁发机构)并且手动管理每个客户端证书
1. 创建自定义CA
首先,我们需要创建一个自定义的CA,并使用该CA来签署客户端证书。
1.1 生成CA的私钥和证书
# 生成CA的私钥
openssl genrsa -out ca.key 2048
# 生成CA的自签名证书
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt
1.2 配置CA的配置文件
创建一个 openssl.cnf
文件,用于定义CA的配置。以下是一个简单的配置示例:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
C = US
ST = California
L = San Francisco
O = My Organization
OU = IT
CN = My CA
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
这个配置文件是用于配置 openssl
工具的 req
命令的参数,主要用于生成证书请求(Certificate Signing Request, CSR)或自签名证书。它定义了证书请求或生成证书时的默认参数和扩展属性(如 v3_ca
部分)。将 prompt
设置为 yes
,这样在生成证书请求时,openssl 会提示你输入 DN(识别名)的信息下面是对这个配置文件中各个部分的具体解释:
[ req ] 部分
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
-
default_bits
:- 定义了生成私钥时默认的密钥长度(这里是 2048 位)。
- 常见的密钥长度有 2048、4096 等。
-
prompt
:- 设置为
no
,表示生成证书请求时不会提示用户输入信息,而是直接使用配置文件中dn
部分的内容。
- 设置为
-
default_md
:- 指定默认的哈希算法,这里使用的是 SHA-256。
- 其他可选的哈希算法包括 SHA-1、SHA-512 等。
-
distinguished_name
:- 指定证书请求或证书中的 Distinguished Name(DN,识别名)的配置部分。
- 这里指向了
[ dn ]
部分。
2. 生成客户端证书
现在,我们将为每个客户端生成一个私钥和证书请求,然后使用CA来签署这些证书。
2.1 生成客户端的私钥和证书请求
# 生成客户端1的私钥
openssl genrsa -out client1.key 2048
# 生成客户端1的证书请求
openssl req -new -key client1.key -out client1.csr -config openssl.cnf
2.2 使用CA签署客户端证书
# 使用CA签署客户端1的证书
openssl x509 -req -in client1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client1.crt -days 365 -sha256
重复上述步骤,为每个客户端生成相应的证书。
3. 在服务器端加载CA证书并验证客户端证书
现在,我们回到服务器端,加载CA证书,并在客户端连接时验证其证书。
3.1 配置服务器
在服务器端,你只需要加载CA的证书(ca.crt
),而不是每个客户端的证书。这样,服务器会信任所有由该CA签发的客户端证书。
void setupServer(QSslServer *server) {
// 加载服务器证书和私钥
QFile serverCertFile("server_cert.pem");
QFile serverKeyFile("server_key.pem");
serverCertFile.open(QFile::ReadOnly);
serverKeyFile.open(QFile::ReadOnly);
QSslCertificate serverCert(&serverCertFile, QSsl::Pem);
QSslKey serverKey(&serverKeyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "your_password");
// 加载CA的证书
QFile caCertFile("ca.crt");
caCertFile.open(QFile::ReadOnly);
QSslCertificate caCert(&caCertFile, QSsl::Pem);
// 配置SSL配置
QSslConfiguration sslConfig = server->sslConfiguration();
sslConfig.setLocalCertificate(serverCert);
sslConfig.setPrivateKey(serverKey);
sslConfig.setCaCertificates({caCert}); // 只需要加载CA的证书
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
server->setSslConfiguration(sslConfig);
// 连接信号槽
connect(server, &QSslServer::newConnection, this, &YourClass::onNewConnection);
}
4. 验证客户端证书并标记客户端
在客户端连接时,验证其证书,并根据其 Common Name
进行标记。
void onNewConnection() {
QSslSocket *clientSocket = server->nextPendingConnection();
// 设置SSL配置
QSslConfiguration sslConfig = clientSocket->sslConfiguration();
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
clientSocket->setSslConfiguration(sslConfig);
// 开始SSL握手
clientSocket->startServerEncryption();
// 连接信号槽
connect(clientSocket, &QSslSocket::sslErrors, this, &YourClass::onSslErrors);
connect(clientSocket, &QSslSocket::encrypted, this, &YourClass::onSslEncrypted);
}
void onSslErrors(const QList<QSslError> &errors) {
QSslSocket *clientSocket = qobject_cast<QSslSocket *>(sender());
// 处理SSL错误
for (const QSslError &error : errors) {
qDebug() << "SSL error:" << error.errorString();
}
// 如果不接受错误,断开连接
clientSocket->abort();
}
void onSslEncrypted() {
QSslSocket *clientSocket = qobject_cast<QSslSocket *>(sender());
// 获取客户端证书
QSslCertificate clientCert = clientSocket->peerCertificate();
if (!clientCert.isNull()) {
// 获取证书的 Common Name
QString commonName = clientCert.subjectInfo(QSslCertificate::CommonName);
// 根据 Common Name 标记客户端
clientSocket->setProperty("clientTag", commonName);
qDebug() << "Client connected. CN:" << commonName;
} else {
qDebug() << "Client certificate is null.";
}
// 处理客户端连接
connect(clientSocket, &QSslSocket::readyRead, this, &YourClass::onReadyRead);
connect(clientSocket, &QSslSocket::disconnected, clientSocket, &QSslSocket::deleteLater);
}
void onReadyRead() {
QSslSocket *clientSocket = qobject_cast<QSslSocket *>(sender());
if (clientSocket) {
// 获取客户端标记
QString clientTag = clientSocket->property("clientTag").toString();
qDebug() << "Data received from" << clientTag << ":" << clientSocket->readAll();
}
}
总结
- 创建自定义CA:生成CA的私钥和自签名证书,并配置CA的配置文件。
- 生成客户端证书:为每个客户端生成私钥和证书请求,并使用CA签署这些证书。
- 在服务器端加载CA证书:服务器只需加载CA的证书,并信任所有由该CA签发的客户端证书。
- 验证客户端证书并标记客户端:在客户端连接时,验证其证书,并根据
Common Name
进行标记。