用TLS实现安全TCP传输及配置和访问https的web服务
2008-05-29 11:47
1091 查看
tls相关
大致原理
为了让两个之间实现安全传输,(我们把服务端统一叫做TcpServer,客户端统一叫做TcpClient),TcpServer在listen完了accept之后要用一个证书来声明自己是谁,而TcpClient在connect之后要问TcpServer是否具有自己想要的一个证书(确认服务端身份),如果是自己指定的那个证书,就说明是自己要连接的那个TcpServer,这时候连接就会建立成功,以后发给TcpServer的数据,都会用TcpServer声明的证书里面的公钥来加密后再发送给TcpServer,TcpServer会用自己机器里的私钥对数据解密。以上保证了TcpClient不会连接到非法的TcpServer,第三方截取TcpClient发送的数据也解不开。
然后TcpServer也可以(可选)要求TcpClient必须提供证书,用以证明是传入的TcpClient自己信任的TcpClient。TcpClient会把证书(从某个CA申请的)进行签名后发送给TcpServer,TcpServer会确认TcpClient提供的证书发行者是否是自己信任的发行者(就是是否装有这个发行者的根证书),如果信任就会建立连接成功。
以上保证了TcpServer和TcpClient之间实现了双向的身份验证,建立了信任连接。这时候TcpServer和TcpClient会各自用一定的算法生成一个密钥做为会话密钥,并通知给对方,之后相互传输数据就用这个会话密钥进行对称加密后传给对方,这样做是因为对称加密比非对称加密性能要好一个数量级。(密钥交换一般是服务端用一段随机数传给客户端,客户端用一段随机数传给服务端,这个传输是安全传输,然后双方用两个随机数合并起来的一个数做为会话密钥)
其中的用sha1做为消息完整性算法,3des做为消息机密性算法,rsa做为密钥交换算法(在安全策略里打开fips选项)。
证书相关操作
申请服务端证书
先找一台机器装上证书颁发服务(在添加/删除程序-添加删除windows组件里),然后再另一台机器通过浏览器申请证书,该网址类似如下(其中ms-onlytiancai为证书服务器机器名):选择高级申请证书,如下图,识别信息要填全,否则客户端验证的时候会出现证书链(我测的时候确实如此,但证书链的概念是根CA和多级的中级CA之间信任关系的一个链,具体看相关链接)失败。d
证书类型要选择“服务器身份验证证书(有些CA可能没有单独的这样的模板,那就选择“计算机副本”证书,该证书可以用来验证服务器身份和客户端身份)”,其它选项不要动,最后点申请,然后再在CA服务器的【管理工具】-【证书颁发机构】的“挂起的证书”里把刚申请的证书颁发一下,最后还在证书申请网页上点“查看挂起的证书申请的状态”,通过向导,安装证书,提示你是否信任该CA时,点是,这时候会自动把该证书安装到“个人”区域,并把该CA证书安装到“受信任的根证书颁发机构”。这一步是必须的,这样做服务端就会信任该CA发行的所有证书(如果这一步没有安装根证书,后面可以从控制台里把CA证书单独导入到本地计算机的受信任颁发机构里,CA证书不是申请的,是直接在证书申请页面的下方的链接下载的),后面申请客户端证书的时候也从这个CA来申请,服务端才会信任这个客户端。
最后申请下来的证书,大致如上图所示,必须是服务器身份认证证书(至少证书目的里由“保证远程计算机身份”),而且必须得有一个该证书对应的私钥,这个私钥应该是在DPAPI(CAPI)存储器里存着呢,按照上面的操作应该会得到这样的一个证书。用makecert-r-pe-n"CN=TestCert"-e01/01/2036-srlocalMachinec:"TestCert.cer生成的证书不会有对应的私钥,这样的证书是不能用的,到时候客户端验证的时候会抛一个异常,提示你没有私钥(需要一个关联私钥的证书)。
查看本机已安装的证书,在IE的选项-内容-证书选项里(在控制台里添加/删除管理单元里添加“证书管理单元”也可以管理)。
申请服务端证书,姓名字段一定要写成服务器的名称,netbios名或者dns名称。
申请客户端证书
过程和申请服务端证书一样,只是证书类型选择“客户端身份验证证书”。导出证书
在证书管理控制台里把服务端证书和客户端证书全部导出到本地,导出选项全部为默认。服务器为:C:"certs"huhao.pxe(带私钥)
客户端为:C:"certs"client.cer
如果服务端为windows服务,需要把服务端证书导入到本地计算机里,默认服务器证书只安装到了当前用户的个人区域,需要从当前用户的个人区域导出来,再导入到本地计算机的个人区域,导出的时候记着选上导出私钥,否则导入到本地计算机里的证书就不会关联私钥了。
应用相关
服务端代码
类库
usingSystem;
usingSystem.Net.Sockets;
usingSystem.Net;
usingSystem.Net.Security;
usingSystem.Security.Cryptography.X509Certificates;
usingSystem.Text;
usingSystem.Security.Authentication;
usingSystem.Threading;
classTCPServer_SSL
{
privateTcpListener_listener=null;
privateIPAddress_address=IPAddress.Parse("127.0.0.1");
privateint_port=55555;
CTORs#regionCTORs
publicTCPServer_SSL()
{
}
publicTCPServer_SSL(stringaddress,stringport)
{
_port=Convert.ToInt32(port);
_address=IPAddress.Parse(address);
}
#endregion//CTORs
Properties#regionProperties
publicIPAddressAddress
{
get
{return_address;}
set
{_address=value;}
}
publicintPort
{
get
{return_port;}
set
{_port=value;}
}
#endregion
publicvoidListen()
{
try
{
_listener=newTcpListener(_address,_port);
//Fireuptheserver.
_listener.Start();
//Enterthelisteningloop.
while(true)
{
Console.Write("Lookingforsomeonetotalkto…");
//Waitforconnection.
TcpClientnewClient=_listener.AcceptTcpClient();
Console.WriteLine("Connectedtonewclient");
//Spinathreadtotakecareoftheclient.
ThreadPool.QueueUserWorkItem(newWaitCallback(ProcessClient),
newClient);
}
}
catch(SocketExceptione)
{
Console.WriteLine("SocketException:{0}",e);
}
finally
{
//Shutitdown.
_listener.Stop();
}
Console.WriteLine("/nHitanykey(whereisANYKEY?)tocontinue…");
Console.Read();
}
privatevoidProcessClient(objectclient)
{
using(TcpClientnewClient=(TcpClient)client)
{
//Bufferforreadingdata.
byte[]bytes=newbyte[1024];
stringclientData=null;
//第三个参数是验证客户端证书的回调,最后一个参数是用来指定多个证明自己的证书的回调,这里为空
//前两个参数分别是一个流和是否在关闭sslstrem关闭内部流的选项
using(SslStreamsslStream=newSslStream(newClient.GetStream(),false,ValidateClientCertificate,null))
{
try
{
//该方法的第一个参数是用于服务端身份验证的证书,第二个参数指定是否需要验证客户端的身份
//第三个参数是指定安全传输的协议,最后一个参数指定是否检查吊销证书
sslStream.AuthenticateAsServer(GetServerCert("117f32ff000000000007"),true,SslProtocols.Tls,false);
}
catch(Exceptionex)
{
Console.WriteLine(ex);
}
//Looptoreceiveallthedatasentbytheclient.
intbytesRead=0;
while((bytesRead=sslStream.Read(bytes,0,bytes.Length))!=0)
{
//TranslatedatabytestoanASCIIstring.
clientData=Encoding.ASCII.GetString(bytes,0,bytesRead);
Console.WriteLine("Clientsays:{0}",clientData);
//Thankthemfortheirinput.
bytes=Encoding.ASCII.GetBytes("Thankscallagain!");
//Sendbackaresponse.
sslStream.Write(bytes,0,bytes.Length);
}
}
}
}
publicstaticboolValidateClientCertificate(objectsender,X509Certificatecertificate,X509Chainchain,SslPolicyErrorssslPolicyErrors)
{
Console.WriteLine("ValidateClientCertificate-certificate.Subject:/r/n{0}",certificate.Subject);
if(sslPolicyErrors!=SslPolicyErrors.None&&sslPolicyErrors!=SslPolicyErrors.RemoteCertificateChainErrors)
returnfalse;
if(sslPolicyErrors!=SslPolicyErrors.RemoteCertificateChainErrors)
{
//不判断吊销证书
foreach(X509ChainStatussinchain.ChainStatus)
{
Console.WriteLine("ValidateClientCertificate-chain.ChainStatus:/r/n{0}-{1}",s.Status,s.StatusInformation);
if(s.Status!=X509ChainStatusFlags.OfflineRevocation&&s.Status!=X509ChainStatusFlags.RevocationStatusUnknown)
{
returnfalse;
}
}
}
returntrue;
}
//该方法从本地计算机的个人证书区域按证书序列号查找指定的证书用于服务器身份验证
//以及打印出证书的详细信息
privatestaticX509CertificateGetServerCert(stringserialNumber)
{
X509Storestore=newX509Store(StoreName.My,StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collectioncertificate=
store.Certificates.Find(X509FindType.FindBySerialNumber,
serialNumber,true);
X509Certificate2x509=certificate[0];
byte[]rawdata=x509.RawData;
Console.WriteLine("509count:{0}",store.Certificates.Count);
Console.WriteLine("ContentType:{0}{1}",X509Certificate2.GetCertContentType(rawdata),Environment.NewLine);
Console.WriteLine("FriendlyName:{0}{1}",x509.FriendlyName,Environment.NewLine);
Console.WriteLine("CertificateVerified?:{0}{1}",x509.Verify(),Environment.NewLine);
Console.WriteLine("SimpleName:{0}{1}",x509.GetNameInfo(X509NameType.SimpleName,true),Environment.NewLine);
Console.WriteLine("SignatureAlgorithm:{0}{1}",x509.SignatureAlgorithm.FriendlyName,Environment.NewLine);
Console.WriteLine("PrivateKey:{0}{1}",x509.PrivateKey.ToXmlString(false),Environment.NewLine);
Console.WriteLine("PublicKey:{0}{1}",x509.PublicKey.Key.ToXmlString(false),Environment.NewLine);
Console.WriteLine("CertificateArchived?:{0}{1}",x509.Archived,Environment.NewLine);
Console.WriteLine("LengthofRawData:{0}{1}",x509.RawData.Length,Environment.NewLine);
Console.WriteLine("x509.SerialNumber:{0}{1}",x509.SerialNumber,Environment.NewLine);
if(certificate.Count>0)
return(certificate[0]);
else
return(null);
}
catch(Exceptionex)
{
Console.WriteLine(ex);
returnnull;
}
finally
{
store.Close();
}
}
}
控制台代码