DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Apache Spark 3 to Apache Spark 4 Migration: What Breaks, What Improves, What's Mandatory
  • Zero Trust, Build High Scale TLS Termination Layer
  • Model Context Protocol Vs Agent2Agent: Practical Integration with Enterprise Data
  • Securing HTTPS From the Inside Out: Preventing Client-Side Interception Attacks

Trending

  • From AI Chaos to Control: Building Enterprise-Grade LLM Gateways With MuleSoft Anypoint
  • Dear Micromanager: Your Distrust Has a Job; It’s Just Not the One You’re Doing
  • The Hidden Cost of Overprivileged Tokens: Designing Messaging Platforms That Assume Compromise
  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds
  1. DZone
  2. Coding
  3. Languages
  4. Implementing HTTPS Two-Way Authentication in Android Using Delphi XE10.x

Implementing HTTPS Two-Way Authentication in Android Using Delphi XE10.x

This guide shows how to bundle .pfx client certs into app's AndroidKeyStore for mTLS to self-signed servers.

By 
sona dorje user avatar
sona dorje
·
Jan. 27, 26 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
907 Views

Join the DZone community and get the full member experience.

Join For Free

I have an HTTPS cloud server. After a mobile app sends a request, it receives the content returned by the server. The server stores a self-made CA and a server certificate.

Vue.js Component
 
const https = require('https');
var fs = require('fs');
var options = {
	key: fs.readFileSync("./myserver.key"),
    cert: fs.readFileSync('./myserver.crt'),
	ca: fs.readFileSync('./MyCARoot.crt'), 
    requestCert: true,
	rejectUnauthorized:true
 
};


This configuration requires the client certificate to be installed on the mobile device. 


However, I don't want all users to install client certificates, as doing so would expose their private keys. Is it possible to package the *.pfx certificate and then load it via code? Yes, it is possible. Here are the steps:

1. Locate the System .Net.HttpClient.Android.pas file under \Embarcadero\Studio\21.0\source\rtl\netCopy it to your project folder and then rename it.

2. Move all class definitions under 'implementation' to 'interface'.

Pascal
 
TAndroidHTTPRequest = class;
  TAliasCallback = class(TJavaLocal, JKeyChainAliasCallback)
  protected
    [Weak] FRequest: TAndroidHTTPRequest;
  public
    procedure alias(alias: JString); cdecl;
    constructor Create(const ARequest: TAndroidHTTPRequest);
  end;
 
  TJHostnameVerifier = class(TJavaLocal, JHostnameVerifier)
  public
    function verify(hostname: JString; session: JSSLSession): Boolean; cdecl;
  end;


3. Locate TAndroidHTTPClient = class(THTTPClient) and add two private members. 

Pascal
 
private
    FMyTrustManagerFactory : JTrustManagerFactory;
    FMyKeyManagerFactory: JKeyManagerFactory;


Add two more procedures. 

Pascal
 
procedure TAndroidHTTPClient.SetTrustManagerFactory(const ATmf: JTrustManagerFactory);
begin
   FMyTrustManagerFactory := ATmf;
end;
 
procedure TAndroidHTTPClient.setKeyManagerFactory(const AKMF: JKeyManagerFactory);
begin
   FMyKeyManagerFactory := AKMF;
end;


Similarly, find TAndroidHTTPRequest = class(THTTPRequest) and add these two private members.

4. Locate procedure TAndroidHTTPRequest.DoPrepare, modify it, and replace the original processing method.

Pascal
 
// TrustManager
    //LJTrustManagers := FMyTrustManagerFactory.getTrustManagers;
    LJOldTrustManager := TJX509TrustManager.Wrap(FMyTrustManagerFactory.getTrustManagers[0]); // Get Current Trust Manager.
    FJTrustManager := TX509TrustManager.Create(LJOldTrustManager, Self);
    LJTrustManagers := TJavaObjectArray<JTrustManager>.Create(1);
    LJTrustManagers.Items[0] := TJTrustManager.Wrap(FJTrustManager);
    LJCerts := FJTrustManager.getAcceptedIssuers;
 
    FJTrustManager.checkClientTrusted(LJCerts, StringToJString('RSA'));
    FJTrustManager.checkServerTrusted(LJCerts, StringToJString('RSA'));
 
    // KeyManager
    LJOldKeyManager := TJX509KeyManager.Wrap(FMyKeyManagerFactory.getKeyManagers[0]); // Get Current Key Manager.
    FJKeyManager := TX509KeyManager.Create(LJOldKeyManager, Self);
    LJKeyManagers := TJavaObjectArray<JKeyManager>.Create(1);
    LJKeyManagers.Items[0] := TJKeyManager.Wrap(FJKeyManager);


5. Locate function TAndroidHTTPClient.DoGetHTTPRequestInstance and pass the two previously defined private members to Request. 

Pascal
 
function TAndroidHTTPClient.DoGetHTTPRequestInstance(const AClient: THTTPClient; const ARequestMethod: string;
  const AURI: TURI): IHTTPRequest;
begin
  Result := TAndroidHTTPRequest.Create(TAndroidHTTPClient(AClient), ARequestMethod, AURI);
 
  //Pass two factory instances to the created HttpRequest
  (Result  as TAndroidHTTPRequest).setTrustManagerFactory(FMyTrustManagerFactory);
  (Result  as TAndroidHTTPRequest).setKeyManagerFactory(FMyKeyManagerFactory);
end;


6. Find procedure TX509TrustManager.checkServerTrusted, and modify:

Pascal
 
// Checking if it's an authoritative CA will fail for self-made certificates.
    //FJOrigOldTrustManager.checkServerTrusted(chain, authType);
    if not isServerTrusted(FRequest.FServerCertificate) then
       raise ECertificateException.Create('Invalid server certificate!');


Comment out the native checkServerTrusted(chain, authType); function and use the custom function isServerTrusted instead.

The replacement function is quite simple; it checks the serial numbers of the CA certificate and the server certificate.

Pascal
 
function TX509TrustManager.isServerTrusted(const ADCert: TCertificate):boolean;
begin
    //only check SN
   if (UpperCase(ADCert.SerialNum) = '1AFE100A09D8E894') or
      (UpperCase(ADCert.SerialNum) = '40F2768CE4B83190') then
      Result := True
   else
      Result := False;
end;


7. Let's see how these two factory instances are initialized. 

Pascal
 
fname := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'myclient.pfx';
   F := TFileStream.Create(fname, fmOpenRead);
    try
      R := X509Cert.LoadFromStreamPFX(F, 'XF@dM1n');
      if R = 0 then
      begin
        ms := TMemoryStream.Create;
        if X509Cert.PrivateKeyExists  then
        begin
           X509Cert.SaveKeyToStreamPEM(ms, 'password');
           fname := System.IOUtils.TPath.GetSharedDocumentsPath + PathDelim + 'mykey.pem';
           ms.SaveToFile(fname);
           KeyManager.ImportFromFile(fname, 3, 'RSA', '', '', 2);
           vKey := KeyManager.Key.Key;
           vSize := Length(vKey);
 
        end;
 
      end
      else
        raise ECertificateException.Create('Failed to load certificate, PFX error ' + IntToHex(R, 4));
    finally
      F.Free;
      ms.Free;
    end;
 
   SetLength(vBytes, X509Cert.CertificateSize);
   Move(X509Cert.CertificateBinary^, vBytes[0], X509Cert.CertificateSize);
   LJArray := TJavaArray<Byte>.Create(Length(vBytes));
   Move(vBytes[0], LJArray.Data^, Length(vBytes));
 
   LJStream := TJByteArrayInputStream.JavaClass.init(LJArray);
   LJArray.Free;
 
  LJClientCert := TJCertificateFactory.JavaClass.getInstance(StringToJString('X.509')).generateCertificate(LJStream);
  LJCertChain := TJavaObjectArray<JCertificate>.create(1);
  LJCertChain.Items[0] := LJClientCert;
   LJArray := TJavaArray<Byte>.Create(vSize);
   move(vKey[0], LJArray.Data^, vSize);
 
   LJkeySpec := TJPKCS8EncodedKeySpec.JavaClass.Init(LJArray);
   LJKeyFactory := TJKeyFactory.JavaClass.getInstance(StringToJString('RSA'));
   LJKey := TJRSAPrivateKey.Wrap(LJkeyFactory.generatePrivate(TJKeySpec.Wrap(LJkeySpec)));
 
 
  // Instantiate keystore
   LJAlgorithm := TJKeyManagerFactory.JavaClass.getDefaultAlgorithm;
   s := JStringToString(LJAlgorithm);
   kmf := TJKeyManagerFactory.JavaClass.getInstance(LJAlgorithm);
   // Obtain secret store
   key_store_type := TJKeyStore.JavaClass.getDefaultType;
   s := JStringToString(key_store_type);
   LJKS_PK := TJKeyStore.JavaClass.getInstance(StringToJString('AndroidKeyStore'));
   //This is the only way; the source code shows that it does not accept non-empty parameters.
   LJKS_PK.load(nil, nil);
   LJKS_PK.setKeyEntry(StringToJString('mykey'), TJKey.Wrap(LJKey), nil, LJCertChain);
   kmf.init(LJKS_PK, StringToJString('XF@dM1n').toCharArray);
 
  //Certificate Management Factory
  LJKS_Cert := TJKeyStore.JavaClass.getInstance(StringToJString('AndroidKeyStore'));
  LJKS_Cert.load(nil, nil);
  //key_Store.setCertificateEntry(StringToJString('ca'), ca);
  LJKS_Cert.setCertificateEntry(StringToJString('LJClientCert'), LJClientCert);
 
  LJAlgorithm := TJTrustManagerFactory.JavaClass.getDefaultAlgorithm;
  s := JStringToString(LJAlgorithm); //#BKS
  tmf := TJTrustManagerFactory.JavaClass.getInstance(LJAlgorithm);
  tmf.init(LJKS_Cert);
 
  //set custom two factorys
  FClient.SetTrustManagerFactory(tmf);
  FClient.setKeyManagerFactory(kmf);


This uses two commercial controls from SecureBlackBox: KeyManager: TsbxCryptoKeyManager; and X509Cert: TElX509Certificate;. This cross-platform control is expensive, but very convenient. 

You can also use the free Bouncy Castle, but that requires extensive modifications to the exported JNI, which is time-consuming and involves exporting some JNI code not provided by BigD.


HTTPS Embarcadero Delphi Delphi (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Apache Spark 3 to Apache Spark 4 Migration: What Breaks, What Improves, What's Mandatory
  • Zero Trust, Build High Scale TLS Termination Layer
  • Model Context Protocol Vs Agent2Agent: Practical Integration with Enterprise Data
  • Securing HTTPS From the Inside Out: Preventing Client-Side Interception Attacks

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook