求助delphi实现ssl验证客户端证书

edhn2006 2009-09-05 07:02:01
最近在用delphi实现CS模式下的SSL通信开发。
项目使用weblogic架设ssl,即生成jks文件在weblogic上配置加载,可成功启动ssl。
但weblogic要求必须配置为验证客户端证书,此种情况下,客户端不提交证书是不能访问任何服务器资源的。
这个证书存在于客户的usbkey中,将usb插入电脑,使用浏览器访问https的资源,浏览器会自动提示选择证书(是IE6时,使用ie8时只要求输入pin码),之后浏览器自动完成提交证书操作。

这里由于项目的客户端使用的是delphi,并且交互接口是通过webservice实现的,也就是说要实现带ssl的webservice。
这里使用indy10完成,主要代码如下。
function TForm1.getHTTPRIO(): TIdHTTPRIO;
var
SoapClient: TIdHTTPRIO;
SSLRequired: Boolean;
begin
SSLRequired := True;
SoapClient:= TIdHTTPRIO.Create(nil);
with SoapClient.HTTPWebNode.HttpClient do
begin
SoapClient.WSDLLocation := 'e:\MyProgram\Delphi\Demos\SSL_HTTPRio\demo\interface.wsdl';
SoapClient.Service := 'ServiceInterfaceImplService';
SoapClient.Port := 'ServiceInterface';

AllowCookies:= True;
HandleRedirects:= False;
ProtocolVersion:= pv1_1;
// {$IFNDEF INDY10}
// MaxLineLength:= 16384;
// RecvBufferSize:= 32768;
// {$ENDIF}
if not SSLRequired then
begin // HTTP
{$IFDEF INDY10}
CreateIOHandler(nil);
{$ELSE}
IOHandler.Free;
IOHandler:= nil;
{$ENDIF}
end
else
begin // HTTPS
{$IFDEF INDY10}
IOHandler:= TIdSSLIOHandlerSocketOpenSSL.Create(
SoapClient.HTTPWebNode.HttpClient);
{$ELSE}
IOHandler:= TIdSSLIOHandlerSocket.Create(SoapClient.HTTPWebNode.HttpClient);
{$ENDIF}
with
{$IFDEF INDY10}
TIdSSLIOHandlerSocketOpenSSL
{$ELSE}
TIdSSLIOHandlerSocket
{$ENDIF}
(IOHandler), SSLOptions do
begin
Method := sslvSSLv23; // select SSL method
// 3个文件!
CertFile := 'd:\bea\ca\temp\cert1.pem'; // 'my.crt'; // assign certificate
KeyFile := 'e:\WorkDoc\ngg\CA\CA\myCA\key.pem'; // 'my.key'; // assign private key
RootCertFile := 'd:\bea\ca\temp\cert2.pem'; // 'root.crt';

// 证书密码
OnGetPassword := GetPassword;

Mode := sslmClient;

// 验证深度
VerifyDepth := 2;

VerifyMode := [sslvrfPeer, sslvrfFailIfNoPeerCert, sslvrfClientOnce];

OnStatusInfo := DoOnStatusInfo;
OnVerifyPeer:= DoOnVerifyPeer;
end;
end;
end;
// end;
SoapClient.HTTPWebNode.OnLog:= SoapClientOnLog; // custom logging
// set correct ReadTimeout
TIdTCPClient(SoapClient.HTTPWebNode.HttpClient).OnConnected:= HttpClientOnConnected;
Result := SoapClient;
end;


上述代码已经可成功与服务器交互。
上面的3个文件是从weblogic发布ssl使用的jsk文件中转换出来,没有使用usbkey中的证书,这样等于没有用上usbkey。

如何使用usbkey的证书和密钥,是我没弄明白的问题。‘
usbkey插入电脑后,会自动将证书注册到系统证书区,可以查看到证书,可以将证书导出,但导出的格式可用的只有3种,base64,der,pkcs7。其中都是只有证书没有密钥的。可以包含密钥的pfx格式在导出界面为灰色无法选择。
那么我如何得到usbkey中证书的密钥文件,从而复制给indy呢?
另外indy这个组件要求必须有证书文件,这个策略是不是也不够安全,目前不知道有什么其他更好用的 可以在delphi下实现ssl的组件,如果有人知道请告知。

总结一下,一句话说这个问题就是delphi下实现验证客户端证书的ssl通信,请做过这种项目的高手告知,不胜感谢,嗷嗷感谢,不胜嗷嗷的感谢啊!!!!!!!!
...全文
1932 8 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
edhn2006 2009-09-17
  • 打赏
  • 举报
回复
该问题已经解决。
证书操作使用crypt32.dll
ssl通信使用wininet.dll

function TMyHTTPReqResp.Send2(const S: string): Integer;
const
INTERNET_OPTION_CLIENT_CERT_CONTEXT = 84;
var
Request: HINTERNET;
RetVal, Flags: DWord;
P: Pointer;
AcceptTypes: array of PChar;
ActionHeader: string;
WireData: string;
ContentHeader: string;
ErrCode: Cardinal;
SendResult: Boolean;
dwFlags, dwBuffLen: DWORD;
const
ContentHeaderUTF8 = 'Content-Type: text/xml charset="utf-8"';
ContentHeaderNoUTF8 = 'Content-Type: text/xml';
begin
//…………省略原来send方法的处理……
Flags := INTERNET_FLAG_KEEP_CONNECTION or INTERNET_FLAG_NO_CACHE_WRITE;
// 访问https地址,flag必须包含INTERNET_FLAG_SECURE
if FURLScheme = INTERNET_SCHEME_HTTPS then
Flags := Flags or INTERNET_FLAG_SECURE
or SECURITY_INTERNET_MASK;

Request := HttpOpenRequest(FInetConnect, 'POST', PChar(FURLSite), 'HTTP/1.1',
nil, Pointer(AcceptTypes), Flags, Integer(Self));

//…………省略原来send方法的处理……
while True do
begin
SendResult := HttpSendRequest(Request, nil, 0, @WireData[1], Length(WireData));
ErrCode := GetLastError;

// 访问https的地址.当服务器证书存在安全问题时,返回ERROR_INTERNET_INVALID_CA 此时设置忽略选项
// 特殊说明:这些选项都是必须在出错后设置并重新调用HttpSendRequest
if (FURLScheme = INTERNET_SCHEME_HTTPS)
and (ErrCode = ERROR_INTERNET_INVALID_CA) then
begin
dwBuffLen := sizeof(dwFlags);
if InternetQueryOption (Request, INTERNET_OPTION_SECURITY_FLAGS,
@dwFlags, dwBuffLen) then
begin
// 设置忽略安全警告
dwFlags := dwFlags or SECURITY_FLAG_IGNORE_UNKNOWN_CA
or SECURITY_FLAG_IGNORE_CERT_CN_INVALID
or SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
InternetSetOption (Request, INTERNET_OPTION_SECURITY_FLAGS,
@dwFlags, sizeof (dwFlags) );
end;
RetVal := ERROR_INTERNET_FORCE_RETRY; // 将再次发送HttpSendRequest
end
// 访问https地址.当服务器要求客户端提供证书时,返回ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
else if (FURLScheme = INTERNET_SCHEME_HTTPS)
and (ErrCode = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) then
begin
if FCert = nil then
raise Exception.Create('请插入证书!');
// 使用CertEnumCertificatesInStore 和 证书关键字 查找到证书Context指针,传入FCert
dwBuffLen := SizeOf(FCert^); // 取Sizeof(CERT_CONTEXT)而非PCERT_CONTEXT
// 设置证书
if not InternetSetOption (Request, INTERNET_OPTION_CLIENT_CERT_CONTEXT,
@(FCert^), dwBuffLen )then
begin
raise Exception.Create('尝试将证书设置到请求中失败.ErrorCode='
+IntToStr(GetLastError));
end;
RetVal := ERROR_INTERNET_FORCE_RETRY;
end
// 其他情况 或 一般的http请求处理。检查错误,在有错误的时候显示dlg
else
begin
Check(not SendResult);
RetVal := InternetErrorDlg(GetDesktopWindow(), Request, GetLastError,
FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, P);
end;
case RetVal of
ERROR_SUCCESS: break;
ERROR_CANCELLED: SysUtils.Abort;
ERROR_INTERNET_FORCE_RETRY: { Retry the operation };
end;
end;
Result := Integer(Request);
end;
wintergoes 2009-09-07
  • 打赏
  • 举报
回复
用indy使用证书文件那种方式吧
没见过其他调用ssl的方式
edhn2006 2009-09-06
  • 打赏
  • 举报
回复
http://www.abc188.com/info/html/chengxusheji/delphi/20080411/65621.html
edhn2006 2009-09-06
  • 打赏
  • 举报
回复
另外关于indy对ssl的支持,这里有一篇英文文档有指导作用

This document explains how to use certificate authentication when connecting to a site that requires certificate authentication. We are using Indy components on the client side and some server (MS IIS, Apache, …) on the server side. In the example we will be using http protocol, cause it is very easy to set such an environment.
First of all we must get certificates and private keys for the client. Let''''s suppose that we got some private key/certificate pair from some Certificate Authority (like Verisign) and we have this listed in MS IE in Personal Certificates Store.

Task 1. Convert the certificate from MS format to PEM format used by OpenSSL

First we have to export the certificate, I don''''t write down but it is assumed that also private key is exported, to the PFX file (personal exchange format). We can protect this file with some password, but for let''''s not for the sake of example.
When we have this file, in our case is test_b.pfx, we have to convert it to PEM format. With IndySSL dll''''s we distribute also the precompiled openssl.exe utility that can be used to do the conversion.

The proper parameters are:
openssl.exe pkcs12 –in test_b.pfx –out test_b.pem

we will be asked to provide the password, first to unlock the pfx file (we didn''''t specify it) and then password for locking the private key part in pem file. We can specify this password that will be latter used to unlock the private key in the demo. Le''''t suppose that we use ?aaaa? for the password (four letters a).
If we look at the PEM file we will notice that we have two parts in it. The private key file and the certificate (public key) part and some informational statements. We should divide those two parts in separate file, cause we need them separated in Indy SSL clients.

So, let''''s create a first file called test_b_key.pem and copy/paste every thing between
-----BEGIN RSA PRIVATE KEY----- and
-----END RSA PRIVATE KEY-----
and those two lines included in this new file and save it.
Create also the certificate file called test_b_crt.pem and copy/paste every thing between
-----BEGIN CERTIFICATE----- and
-----END CERTIFICATE-----
and those two lines included in this new file and save it.

Now we need also the Certificate Authority certificate file. This can be obtained from the MSIE in Trusted Root Certificate Authority. Select the Authority that issued your certificate and export it in Base64 (cer) format. This format is also the same like PEM format so you can easily rename the file test_b_ca.crt to test_b_ca.pem and you have the proper file.

We have now all the files that we require so we can start coding in Delphi.

Let''''s create a new application.

Put IdHTTP component and IdSSLIOHandlerSocket on it and save the project.
Now we will specify those certificate files in the IdSSLIOHandlerSocket component.

Set the property:
- CertFile to test_b_crt.pem,
- KeyFile to test_b_key.pem,
- RootCertFile to test_b_ca.pem.

Set the property Method to sslvSSLv23 so the ssl protocol will negotiate the proper mode (SSL ver2 or SSL ver3) automatically.
Set the property VerifyDepth to 2, this means that we accept the server certificate (that we connect to), up to 2 levels of Certificate Chain (CA1 -> CA2 -> Server certificate). In our case we have only one level so value 2 will be fine.
Now we have to connect the components IdHTTP to IdSSLIOHandlerSocket. This is done by choosing the IdSSLIOHandlerSocket1 in IOHandler property of IdHTTP1 component.
Set the Port of IdHTTP1 to 443, that is the HTTPS protocol port.

Create a OnGetPassword event, that will be fired when the client will need to access the private key. In this event handler you specify the password for unlocking the key.

procedure TForm1.IdSSLIOHandlerSocket1GetPassword(var Password: String);
begin
Password := ''''aaaa'''';
end;

Now, add a button on a form that will trigger the read of http address, and a memo box that will show the results. We used something like this:

procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Clear;
Memo1.Lines.Text := IdHTTP1.Get(''''https://rotel/test/'''');
end;

Now you can set the verify options like sslvrfPeer, will force checking if the other side has a proper valid certificate, sslvrfFailNoPeerCert, will check if the other side has the certificate (used in server applications mostly), sslvrfClientOnce, will check the certificate only once in the ssl session - not all requests will be checked.

If you specify the OnVerifyPeer event, you can additionally check the properties of other side certificate, for example a valid user certificate properties that match your user database, role of user or something like this.
Note that you have to set the property VerifyMode to verify Peer if you want to event OnVerifyPeer get triggered.

Sample of verification code:

function TForm1.IdSSLIOHandlerSocket1VerifyPeer(Certificate: TIdX509): Boolean;
begin
if Pos(''''INTELICOM'''', UpperCase(Certificate.Subject.OneLine)) > 0 then
Result := True
Else
Result := False;
end;

If you return the True value then the client will be authorized, elsewhere it will not. Either way the other side certificate must be valid. The purpose of this event is to narrow the group of users with valid certificates.

http://www.abc188.com/info/html/ ... 20080411/65621.html
edhn2006 2009-09-06
  • 打赏
  • 举报
回复
这篇文章我看过 网上的绝大多数都看过了 不过还是感谢楼上提供这篇文章
这个文章给出了一般用法,但不是验证客户端证书的实现。
另外困扰我的现在也不是仅是验证客户端证书,而是客户端证书可不可以不使用文件的方式。
刚才去138soft论坛逛了一下,发了个帖子过去
de410 2009-09-06
  • 打赏
  • 举报
回复
edhn2006 2009-09-06
  • 打赏
  • 举报
回复
这篇文章曾经有点用处。。。在ssl搭建的时候也看过。
不过后面都省略了!
“四.3.1、 服务器端调用流程
四.3.2、 客户段调用流程”
gyk120 2009-09-05
  • 打赏
  • 举报
回复

5,927

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧