Skip to content

TLS server API: mutual TLS, encrypted private keys, cipher-list control#194

Closed
freitasjca wants to merge 1 commit into
winddriver:masterfrom
freitasjca:feat/ssl-mtls-tlsopt
Closed

TLS server API: mutual TLS, encrypted private keys, cipher-list control#194
freitasjca wants to merge 1 commit into
winddriver:masterfrom
freitasjca:feat/ssl-mtls-tlsopt

Conversation

@freitasjca

Copy link
Copy Markdown

TLS server API: mutual TLS (client-cert verification), encrypted private keys, and cipher-list control

Into: winddriver/Delphi-Cross-Socket:master


Summary

Extends the OpenSSL server-side TLS API with four commonly-needed capabilities the
library currently lacks. All are additive and mirror the shape of the existing
SetCertificate / SetPrivateKey family — no existing method is renamed, removed, or
re-signed.

Tag Capability Files
MTLS-1 SetCACertificate overload chain (file → string → bytes → buffer) — load the CA used to verify client certificates Net.CrossSslSocket.Base.pas, Net.CrossSslSocket.OpenSSL.pas
MTLS-2 SetVerifyPeer(Boolean) — require & verify a client certificate (mutual TLS) both
TLSOPT-1 SetPrivateKeyPassword — load a password-protected private key both
TLSOPT-2 SetCipherList — override the TLS 1.2 cipher list both

Motivation: the current API can present a server certificate but cannot (a) require and
verify client certificates (mTLS), (b) load an encrypted private key, or (c)
constrain the cipher suites — all standard requirements for hardened/enterprise
deployments. These were implemented while adding mTLS support to a Horse web-framework
provider built on this library.


MTLS-1 — SetCACertificate overload chain

Mirrors the existing SetCertificate* family so the CA (used to verify client certs) can
be supplied from a file, a PEM string, raw bytes, or a buffer:

// TCrossSslSocketBase (Net.CrossSslSocket.Base.pas)
procedure SetCACertificateFile(const ACACertFile: string); virtual;
procedure SetCACertificate(const ACACertStr: string); overload; virtual;
procedure SetCACertificate(const ACACertBytes: TBytes); overload; virtual;
procedure SetCACertificate(const ACACertBuf: Pointer; const ACACertBufSize: Integer); overload; virtual; abstract;

The file/string/bytes overloads funnel to the buffer overload (identical to how
SetCertificate already works). OpenSSL implementation (Net.CrossSslSocket.OpenSSL.pas):
parses the PEM with BIO_new_mem_buf + PEM_read_bio_X509, then

  • SSL_CTX_add_client_CA — populates the CA-name list the server advertises in the TLS
    handshake (tells the client which client-certs are acceptable), and
  • X509_STORE_add_cert (into SSL_CTX_get_cert_store) — adds the CA to the trust store
    used to actually verify the presented client certificate.

MTLS-2 — SetVerifyPeer(Boolean)

procedure SetVerifyPeer(const AVerify: Boolean); virtual; abstract;   // Base
procedure SetVerifyPeer(const AVerify: Boolean); override;            // OpenSSL
  • TrueSSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT (require + verify a client cert)
  • FalseSSL_VERIFY_NONE (the existing default — no behaviour change when unused)

Must be called after SetCACertificate so the trust store is populated first.

TLSOPT-1 — SetPrivateKeyPassword (encrypted private keys)

procedure SetPrivateKeyPassword(const APassword: string); virtual; abstract;   // Base
procedure SetPrivateKeyPassword(const APassword: string); override;            // OpenSSL

Stores the passphrase; the OpenSSL private-key load path then reads the key with
PEM_read_bio_PrivateKey + a password callback and installs it via
SSL_CTX_use_PrivateKey. Without this, a password-protected key file cannot be loaded.
When no password is set, the existing (unencrypted) key path is unchanged.

TLSOPT-2 — SetCipherList

procedure SetCipherList(const ACipherList: string); virtual; abstract;   // Base
procedure SetCipherList(const ACipherList: string); override;            // OpenSSL

Calls SSL_CTX_set_cipher_list to override the TLS 1.2 cipher list — lets operators
restrict to a hardened suite set.


Compatibility / risk

  • Purely additive to the public API. No existing method changed; default behaviour is
    identical when the new methods are not called (SetVerifyPeer(False) = current default).
  • The overloads follow the established SetCertificate pattern, so they read naturally
    against the existing surface.
  • One review consideration: the new methods are declared virtual; abstract on
    TCrossSslSocketBase and overridden in TCrossOpenSslSocket (the only SSL
    implementation in the tree). If you would rather not add abstract methods to the base
    (in case of out-of-tree TLS backends), I'm happy to change them to virtual with a
    default raise ENotSupported body instead — your call.

Testing

Exercised end-to-end with a TLS/mTLS integration suite: server presenting a certificate,
requiring and verifying a client certificate against a supplied CA, loading a
password-protected private key, and negotiating a restricted cipher list — validated with
both an OpenSSL/TCrossHttpClient mTLS client and external tooling.


These additions originate from a downstream fork (freitasjca/Delphi-Cross-Socket) used
as the transport for a Horse web-framework provider; offered upstream so the main library
gains first-class mTLS/cipher control. Can be split into an mTLS PR (MTLS-1/2) and a
key/cipher PR (TLSOPT-1/2) if preferred.

@winddriver

Copy link
Copy Markdown
Owner

Thanks for the feedback, I implemented the relevant functions in a way that I think is more appropriate.

@winddriver winddriver closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants