How to implement uKey-based mutual (two-way) SSL/TLS authentication with Lazarus's TFPHttpClient component?
Assuming the digital certificate driver is already installed on the local machine and the USBKey containing the digital certificate is inserted, when accessing
https://192.168.1.1:8080/login.jsp via Chrome browser, the browser will prompt the user with a dialog to input the USBKey's PIN/password for certificate selection and authentication.
However, when using Lazarus's TFPHttpClient to access the same URL, such a PIN/password prompt dialog does not appear, and mutual authentication fails.
In Delphi XE11, the TNetHttpClient component supports an OnNeedClientCertificate event, where one can invoke ShowSelectCertificateDialog (or similar UI) to prompt the user to select a client certificate and enter the USBKey PIN—thus achieving mutual SSL/TLS authentication with uKey.
Question: Can Lazarus’s TFPHttpClient be extended—by drawing inspiration from this event mechanism—to support triggering a certificate selection and PIN input dialog (e.g., via PKCS#11 integration or custom callbacks) during HTTPS handshake?
Specifically, referring to the following resources—and also considering documentation on JavaScript-based SM2/SM3/SM4 (Chinese national cryptography standards) signing plugins—how can we enhance TFPHttpClient in Lazarus to:
Load and interface with the PKCS#11 engine (e.g., via libeay32, ssleay32, or OpenSSL with PKCS#11 module like pkcs11-engine);
Register appropriate OpenSSL callbacks (e.g., SSL_CTX_set_client_cert_cb, SSL_CTX_set_default_passwd_cb, SSL_set_psk_client_callback, or PKCS#11-specific PIN callback);
During SSL/TLS handshake, automatically detect the need for client certificate authentication and display a UI dialog to:
List available certificates from the inserted USBKey/token,
Prompt user to enter the USBKey PIN/password,
Select and load the correct certificate + private key (via PKCS#11 URIs or handles);
Enable successful mutual-authenticated HTTPS connections to endpoints requiring uKey-based client certificates (especially for intranet or government systems complying with Chinese cryptographic regulations, e.g., GM/T 0024-2014 SSL VPN specs).
Additional context:
The USBKey typically exposes its cryptographic functionality via a PKCS#11 DLL (e.g., FeitianPKCS11.dll, SDF_PKCS11.dll, gmssl_pkcs11.so).
OpenSSL (including forks like GmSSL or BabaSSL) supports dynamic engine loading (e.g., dynamic, pkcs11, gost, sm2) to access hardware tokens.
Lazarus/FPC’s TFPHttpClient uses TSSLSocket (via fphttpclient, sslsockets, and openssl units), which internally binds to OpenSSL; thus, low-level customization is possible by accessing the underlying PSSL_CTX / PSSL pointers.
Request: Please provide a concrete solution—code snippets, steps, and design considerations—for extending TFPHttpClient (or its SSL context initialization) in Lazarus (FPC) to support interactive uKey-based client certificate authentication, possibly via:
Custom OpenSSL engine/PKCS#11 integration,
PIN/password callback registration,
Certificate selection UI (e.g., using Lazarus InputQuery, TForm, or system-native dialogs),
Compatibility with national cryptography (e.g., SM2 certificate + SM3 signature + SM4 cipher suites).
Let me know if you'd like a full implementation example (e.g., subclassing TFPHTTPClient, overriding SSL initialization, linking libp11/pkcs11-helper, etc.).
//弹出数字证书usbkey对话框
class procedure TNetHTTPClientVio.OnNeedClientCertificate(const Sender: TObject;
const ARequest: TURLRequest; const ACertificateList: TCertificateList;
var AnIndex: Integer);
const
RSSelectClientCertificate = '选择此网站请求的客户端证书。';
Caption='数字证书';
{$IFDEF MSWINDOWS}
var
LCert: TCertificate;
I: Integer;
{$ENDIF}
begin
{$IFDEF MSWINDOWS} //System.Net.HttpClient.Win
if __ACertificateIndex <= -1 then //如果没有选中证书
begin
if ShowSelectCertificateDialog(GetForegroundWindow(), Caption, RSSelectClientCertificate, LCert) then
for I := 0 to ACertificateList.Count - 1 do
if LCert.SerialNum = ACertificateList
.SerialNum then
begin
AnIndex := I;
__ACertificateIndex:= I;//选中证书
Exit;
end;
end else //如果已选中证书,则不再重复选中,默认选中这个
AnIndex := __ACertificateIndex;
{$ENDIF}
end;
function ShowSelectCertificateDialog(AParentWnd: UIntPtr;
const ATitle, ADisplayString: string; var ACertificate): Boolean;
var
Context: PCERT_CONTEXT;
hStore: HCERTSTORE;
begin
Result := False;
{ Could add support for other Stores }
hStore := CertOpenSystemStore(0, PChar('MY'));
if hStore <> nil then
begin
try
Context := CryptUIDlgSelectCertificateFromStore(hStore,
AParentWnd,
PChar(ATitle),
PChar(ADisplayString),
CRYPTUI_SELECT_LOCATION_COLUMN or
CRYPTUI_SELECT_EXPIRATION_COLUMN,
0,
nil);
if Context <> nil then
begin
CryptCertToTCertificate(Context, TCertificate(ACertificate));
CertFreeCertificateContext(Context);
Result := True;
end;
finally
CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
end;
end;
end;