Skip to content

Commit f47f093

Browse files
committed
feat(ssl): add mTLS server API (SetCACertificate / SetVerifyPeer)
1 parent 90c4603 commit f47f093

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

Net/Net.CrossSslSocket.Base.pas

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,30 @@ TCrossSslSocketBase = class(TCrossSocket, ICrossSslSocket)
173173
procedure SetPrivateKey(const APKeyStr: string); overload; virtual;
174174
procedure SetPrivateKeyFile(const APKeyFile: string); virtual;
175175

176+
{ ── MTLS-1: CA certificate loading for client-certificate verification ──
177+
Loads a CA certificate that the server will use to verify presented
178+
client certificates during the TLS handshake (mutual TLS). Mirrors
179+
the SetCertificate overload family above so consumers have the same
180+
file/string/bytes/buffer surface they're already used to.
181+
182+
Concrete implementation in TCrossOpenSslSocket calls
183+
SSL_CTX_add_client_CA + X509_STORE_add_cert to register the cert in
184+
both the CertificateRequest CA list (sent to clients during the
185+
handshake) and the trust store used to verify the presented chain. }
186+
procedure SetCACertificate(const ACACertBuf: Pointer; const ACACertBufSize: Integer); overload; virtual; abstract;
187+
procedure SetCACertificate(const ACACertBytes: TBytes); overload; virtual;
188+
procedure SetCACertificate(const ACACertStr: string); overload; virtual;
189+
procedure SetCACertificateFile(const ACACertFile: string); virtual;
190+
191+
{ ── MTLS-2: enable / disable peer (client) certificate verification ──
192+
When AVerify=True the server sets SSL_VERIFY_PEER |
193+
SSL_VERIFY_FAIL_IF_NO_PEER_CERT — the handshake fails if the client
194+
does not present a certificate signed by one of the CAs registered
195+
above. When False, reverts to SSL_VERIFY_NONE (the default).
196+
Must be called AFTER SetCACertificate so the trust store is
197+
populated before verify mode is enabled. }
198+
procedure SetVerifyPeer(const AVerify: Boolean); virtual; abstract;
199+
176200
property Ssl: Boolean read GetSsl;
177201
property SslMaxPendingWriteBytes: Int64 read GetSslMaxPendingWriteBytes write SetSslMaxPendingWriteBytes;
178202
end;
@@ -236,6 +260,23 @@ procedure TCrossSslSocketBase.SetPrivateKeyFile(const APKeyFile: string);
236260
SetPrivateKey(TFileUtils.ReadAllBytes(APKeyFile));
237261
end;
238262

263+
{ ── MTLS-1: SetCACertificate overload chain (file → string → bytes → buffer) ── }
264+
265+
procedure TCrossSslSocketBase.SetCACertificate(const ACACertBytes: TBytes);
266+
begin
267+
SetCACertificate(Pointer(ACACertBytes), Length(ACACertBytes));
268+
end;
269+
270+
procedure TCrossSslSocketBase.SetCACertificate(const ACACertStr: string);
271+
begin
272+
SetCACertificate(TEncoding.ANSI.GetBytes(ACACertStr));
273+
end;
274+
275+
procedure TCrossSslSocketBase.SetCACertificateFile(const ACACertFile: string);
276+
begin
277+
SetCACertificate(TFileUtils.ReadAllBytes(ACACertFile));
278+
end;
279+
239280
{ TCrossSslConnectionBase }
240281

241282
function TCrossSslConnectionBase.GetSsl: Boolean;

Net/Net.CrossSslSocket.OpenSSL.pas

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,26 @@
1919
2020
传输层安全协议:
2121
https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E5%8D%94%E8%AD%B0
22+
23+
── mTLS additions ───────────────────────────────────────────────────────────
24+
[MTLS-1] SetCACertificate(Pointer, Integer) override added.
25+
Loads a CA certificate from a memory buffer and registers it with
26+
the SSL context for client-certificate verification (mTLS server
27+
mode). Uses BIO_new_mem_buf + PEM_read_bio_X509 to parse the PEM
28+
data, then calls SSL_CTX_add_client_CA (populates the CA list sent
29+
to clients in the CertificateRequest handshake message) and
30+
X509_STORE_add_cert (adds the cert to the trust store used to
31+
verify the presented certificate chain).
32+
Companion overloads (TBytes / string / file) inherited from the
33+
base class (Net.CrossSslSocket.Base) mirror the SetCertificate
34+
overload family.
35+
36+
[MTLS-2] SetVerifyPeer(Boolean) override added.
37+
When AVerify=True, sets SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
38+
so the server demands a valid client certificate.
39+
When AVerify=False, reverts to SSL_VERIFY_NONE (default).
40+
Must be called AFTER SetCACertificate — the trust store must be
41+
populated before verify mode is enabled.
2242
}
2343

2444
interface
@@ -158,6 +178,19 @@ TCrossOpenSslSocket = class(TCrossSslSocketBase)
158178

159179
procedure SetCertificate(const ACertBuf: Pointer; const ACertBufSize: Integer); overload; override;
160180
procedure SetPrivateKey(const APKeyBuf: Pointer; const APKeyBufSize: Integer); overload; override;
181+
182+
{ ── MTLS-1: load a CA certificate (PEM buffer) for client-cert verification.
183+
Calls SSL_CTX_add_client_CA to register the CA name in the
184+
CertificateRequest list the server sends to clients during the
185+
handshake, and X509_STORE_add_cert to populate the trust store used to
186+
verify the certificate chain presented by the client. }
187+
procedure SetCACertificate(const ACACertBuf: Pointer; const ACACertBufSize: Integer); overload; override;
188+
189+
{ ── MTLS-2: enable / disable client-certificate verification.
190+
AVerify=True → SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
191+
(handshake fails without a valid client cert)
192+
AVerify=False → SSL_VERIFY_NONE (default) }
193+
procedure SetVerifyPeer(const AVerify: Boolean); override;
161194
end;
162195

163196
implementation
@@ -888,6 +921,70 @@ procedure TCrossOpenSslSocket.SetPrivateKey(const APKeyBuf: Pointer;
888921
TSSLTools.SetPrivateKey(FSslCtx, APKeyBuf, APKeyBufSize);
889922
end;
890923

924+
{ ── MTLS-1: load a CA certificate (PEM buffer) into the SSL context ──────────
925+
Registers the certificate with both the CertificateRequest CA list (sent to
926+
clients in the TLS handshake to indicate which CAs the server trusts) and
927+
the X509 trust store (used to verify the certificate chain presented by the
928+
client). Must be called before SetVerifyPeer(True). }
929+
procedure TCrossOpenSslSocket.SetCACertificate(const ACACertBuf: Pointer;
930+
const ACACertBufSize: Integer);
931+
var
932+
LBio: PBIO;
933+
LCACert: PX509;
934+
LStore: PX509_STORE;
935+
begin
936+
if not Ssl or (FSslCtx = nil) then Exit;
937+
938+
// Wrap the caller's buffer in a read-only memory BIO — no copy is made.
939+
LBio := BIO_new_mem_buf(ACACertBuf, ACACertBufSize);
940+
if LBio = nil then
941+
raise ESsl.Create('SetCACertificate: BIO_new_mem_buf failed');
942+
try
943+
// Parse the PEM-encoded X.509 certificate.
944+
LCACert := PEM_read_bio_X509(LBio, nil, nil, nil);
945+
if LCACert = nil then
946+
raise ESsl.Create(
947+
'SetCACertificate: PEM_read_bio_X509 failed — ' +
948+
'ensure the buffer contains a valid PEM certificate');
949+
try
950+
// Register the CA name in the CertificateRequest list.
951+
SSL_CTX_add_client_CA(FSslCtx, LCACert);
952+
953+
// Add the cert to the trust store for chain verification.
954+
LStore := SSL_CTX_get_cert_store(FSslCtx);
955+
if Assigned(LStore) then
956+
X509_STORE_add_cert(LStore, LCACert);
957+
// X509_STORE_add_cert returns 0 if the cert is already in the store
958+
// (duplicate) — this is benign, so we do not raise on <= 0 here.
959+
finally
960+
// Decrement our local ref-count. The context and store hold their own.
961+
X509_free(LCACert);
962+
end;
963+
finally
964+
BIO_free(LBio);
965+
end;
966+
end;
967+
968+
{ ── MTLS-2: enable or disable mandatory client-certificate verification ──────
969+
SSL_VERIFY_NONE — no client certificate is requested (default).
970+
SSL_VERIFY_PEER — request and verify, but allow connections
971+
without a cert (or with an invalid cert).
972+
SSL_VERIFY_FAIL_IF_NO_PEER_CERT — combined with PEER, requires the client
973+
to present a valid cert; handshake fails
974+
otherwise.
975+
This implementation uses PEER | FAIL_IF_NO_PEER_CERT for AVerify=True so
976+
the server demands a valid mTLS handshake. }
977+
procedure TCrossOpenSslSocket.SetVerifyPeer(const AVerify: Boolean);
978+
begin
979+
if not Ssl or (FSslCtx = nil) then Exit;
980+
981+
if AVerify then
982+
SSL_CTX_set_verify(FSslCtx,
983+
SSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nil)
984+
else
985+
SSL_CTX_set_verify(FSslCtx, SSL_VERIFY_NONE, nil);
986+
end;
987+
891988
procedure TCrossOpenSslSocket.TriggerConnected(const AConnection: ICrossConnection);
892989
var
893990
LConnection: TCrossOpenSslConnection;

0 commit comments

Comments
 (0)