Delphi XE10.x实现Android下Https双向认证

我有一个https云服务器,在手机端的app发出请求后,获得服务端返回的内容,服务端存放了自制ca以及sever证书。

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

};

这样的配置,就需要手机端安装client证书

但我并不想让使用者都来安装client证书,这样就把私钥泄漏出去了,能不能把*.pfx证书打包,然后以代码去加载证书呢?可以的,下面是实现的步骤:

1、在\Embarcadero\Studio\21.0\source\rtl\net下找到System.Net.HttpClient.Android.pas文件 ,复制一份到你的工程文件夹下,然后重命名。

2、把implementation下的所有类定义,移动到interface下

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、找到TAndroidHTTPClient = class(THTTPClient),增加两个私有成员

private
    FMyTrustManagerFactory : JTrustManagerFactory;
    FMyKeyManagerFactory: JKeyManagerFactory;

 再添加两个procedure

procedure TAndroidHTTPClient.SetTrustManagerFactory(const ATmf: JTrustManagerFactory);
begin
   FMyTrustManagerFactory := ATmf;
end;

procedure TAndroidHTTPClient.setKeyManagerFactory(const AKMF: JKeyManagerFactory);
begin
   FMyKeyManagerFactory := AKMF;
end;

同样的,找到TAndroidHTTPRequest = class(THTTPRequest),增加这两个私有成员

4、找到procedure TAndroidHTTPRequest.DoPrepare,做修改,替换原来的处理方式

// 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、找到function TAndroidHTTPClient.DoGetHTTPRequestInstance,把两个前面定义的私有成员传递给Request

function TAndroidHTTPClient.DoGetHTTPRequestInstance(const AClient: THTTPClient; const ARequestMethod: string;
  const AURI: TURI): IHTTPRequest;
begin
  Result := TAndroidHTTPRequest.Create(TAndroidHTTPClient(AClient), ARequestMethod, AURI);

  //把两个工厂实例传递给创建的HttpRequest
  (Result  as TAndroidHTTPRequest).setTrustManagerFactory(FMyTrustManagerFactory);
  (Result  as TAndroidHTTPRequest).setKeyManagerFactory(FMyKeyManagerFactory);
end;

 6、找到procedure TX509TrustManager.checkServerTrusted,做修改

// 检查是否是权威CA,对于自制证书,无法通过
    //FJOrigOldTrustManager.checkServerTrusted(chain, authType);
    if not isServerTrusted(FRequest.FServerCertificate) then
       raise ECertificateException.Create('无效服务端证书!');

把原生的checkServerTrusted(chain, authType);注释掉,以自定义的函数isServerTrusted

替换,这个函数很简单,判断CA证书和Server证书的序列号

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、下面看看这两个工厂实例是如何初始化的

 

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)));


  // 實例化密鑰庫
   LJAlgorithm := TJKeyManagerFactory.JavaClass.getDefaultAlgorithm;
   s := JStringToString(LJAlgorithm);
   kmf := TJKeyManagerFactory.JavaClass.getInstance(LJAlgorithm);
   // 獲得密鑰庫
   key_store_type := TJKeyStore.JavaClass.getDefaultType;
   s := JStringToString(key_store_type);
   LJKS_PK := TJKeyStore.JavaClass.getInstance(StringToJString('AndroidKeyStore'));
   //只能是此方式,源码显示不接收非空参数
   LJKS_PK.load(nil, nil);
   LJKS_PK.setKeyEntry(StringToJString('mykey'), TJKey.Wrap(LJKey), nil, LJCertChain);
   kmf.init(LJKS_PK, StringToJString('XF@dM1n').toCharArray);

  //证书管理工厂
  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);

  //设置自己的两个工厂
  FClient.SetTrustManagerFactory(tmf);
  FClient.setKeyManagerFactory(kmf);

 这里面用到SecureBlackBox的两个商业控件:KeyManager: TsbxCryptoKeyManager;
    X509Cert: TElX509Certificate;这套控件是跨平台的,很贵,但很方便。也可以用免费的Bouncy Castle来替换,但要对导出的JNI做大量修改,很费神,还要导出一些大D没提供的JNI。

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ocv.core_c 是 OpenCV 中的一个 C 语言接口,提供了一些基本的数据结构和函数,包括矩阵、图像、像素访问等。如果您想在 Delphi XE 中使用 ocv.core_c,您需要使用 Delphi XE 的 C 语言接口,将 C 语言的头文件和库文件引入 Delphi XE 中,并使用相应的函数和数据结构。以下是一个使用 Delphi XE 和 ocv.core_c 的示例代码,展示了如何使用 ocv.core_c 进行图像处理: ```delphi uses ocv.core_c, ocv.highgui_c; procedure ProcessImage(); var img: pIplImage; begin // 加载图像 img := cvLoadImage('image.jpg', CV_LOAD_IMAGE_COLOR); if img = nil then begin ShowMessage('Failed to load image!'); Exit; end; // 创建一个名为 'Input Image' 的窗口,并在其中显示图像 cvNamedWindow('Input Image', CV_WINDOW_AUTOSIZE); cvShowImage('Input Image', img); // 转换图像到灰度空间 cvCvtColor(img, img, CV_RGB2GRAY); // 创建一个名为 'Processed Image' 的窗口,并在其中显示处理后的图像 cvNamedWindow('Processed Image', CV_WINDOW_AUTOSIZE); cvShowImage('Processed Image', img); // 等待用户按下任意键 cvWaitKey(0); // 释放图像和窗口 cvReleaseImage(img); cvDestroyAllWindows(); end; ``` 这个示例代码加载一张名为 'image.jpg' 的图像,并在名为 'Input Image' 的窗口中显示它。然后,它将图像转换到灰度空间,并在名为 'Processed Image' 的窗口中显示处理后的图像。最后,它等待用户按下任意键,释放图像和窗口。注意,使用 ocv.core_c 需要注意数据类型的转换和内存管理,建议使用 Delphi XE 提供的 OpenCV 接口进行图像处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值