背景
在用HttpWebRequest访问一个https的网站时,程序在执行到下面这一句代码时会出异常:
HttpWebResponse response = capRequest.GetResponse() as HttpWebResponse
异常信息是: 基础连接已被关闭,发送时出错。
这个异常信息与经常碰到的"基础连接被关闭,未能建立TLS/SSL安全通道"不同,况且我的程序里面也加了一直信任证书的回调函数。google上面的答案都是加下一下代码:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
但是加上之后,异常照旧。继续google查资料,偶尔看到有人说到要把底层安全协议换成tls1.1或者tls1.2版本试试,但是在.net2.0里面没有原生支持tls1.2:
SecurityProtocolType //这个枚举类型只有ssl和tls,没有tls1.1和tls1.2。
.net4.5里面才支持tls1.1和1.2版本。于是把项目的目标平台切换成.net4.5之后,SecurityProtocolType枚举类型里面果然有tls12的值,并且httpwebrequest也成功的访问到了网站内容。
但是.net4.5不支持xp系统,最低支持vista系统。软件又必须支持xp系统。。。
解决方案
又是一番检索,大方向就是用Tls1.2 Client + TCP来发送请求和接受响应结果。最终找到了BouncyCastle.Crypto这个开源库,他支持.net2.0,并且自己实现了诸多加密算法,其中就包括Tls1.2协议。可是这个开源库没有文档,不知道怎么调用,于是继续检索,终于找到了一个有用的链接:
http://stackoverflow.com/questions/16727862/bouncy-castle-tls-api-usage
2013年的帖子,用的也不是最新版的库,看库的更新日志,是最新版本才支持的tls1.2。结合库的源代码,参考如下帖子
http://stackoverflow.com/questions/18065170/how-do-i-do-tls-with-bouncycastle
最终,调用成功。从开始碰到问题到解决历时一天一夜。
示例代码如下:
// Need class with TlsClient in inheritance chain
class MyTlsClient : DefaultTlsClient
{
public override TlsAuthentication GetAuthentication()
{
return new MyTlsAuthentication();
}
}
// Need class to handle certificate auth
class MyTlsAuthentication : TlsAuthentication
{
public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
{
// return client certificate
return null;
}
public void NotifyServerCertificate(Certificate serverCertificate)
{
// validate server certificate
}
}
private void StopBtn_Click(object sender, EventArgs e)
{
string host = "www.baidu.com";
int port = 443;
string request = "GET / HTTP/1.1\r\nHost: " + host + "\r\n\r\n";
TcpClient tcpClient = new TcpClient(host, port);
//TlsProtocolHandler protocol = new TlsProtocolHandler(tcpClient.GetStream(), new Org.BouncyCastle.Security.SecureRandom());
TlsClientProtocol protocol = new TlsClientProtocol(tcpClient.GetStream(), new Org.BouncyCastle.Security.SecureRandom());
MyTlsClient client = new MyTlsClient();
protocol.Connect(client);
//发送http请求头
protocol.Stream.Write(Encoding.UTF8.GetBytes(request), 0, request.Length);
protocol.Stream.Flush();
//读取返回内容
MemoryStream ms = new MemoryStream();
byte[] buffer = new byte[1024];
int actual = 0;
//先保存到内存流MemoryStream中
while ((actual = protocol.Stream.Read(buffer, 0, 1024)) > 0)
{
ms.Write(buffer, 0, actual);
}
ms.Position = 0;
byte[] bImageBytes = ms.ToArray();
string strResult = Encoding.UTF8.GetString(bImageBytes);
}
BouncyCastle.Crypto的网站是http://www.bouncycastle.org/csharp/。