我们开发了一个Java应用程序,通过TCP为请求提供服务。我们还为应用程序开发了客户端库,其中每个库支持不同的语言/平台(Java、. NET等)。
直到最近,TCP流量还被限制在安全的网络中。为了支持在不安全的网络中使用,我们使用java-plain-and-tls-socket-examples中的配方实现了TLS。这里有服务器和客户端的配方,以及生成X.509证书的脚本。下面是使用仅服务器身份验证的TLS配方的摘要:
- 创建一个自签名且仅包含根证书的X.509证书。
- 使用包含标识数据以及公钥和私钥的 * keystore * 文件配置服务器。
- 使用 * trust store * 文件配置客户端,该文件包含相同的标识数据,但仅包含公钥。
- 连接时,客户端通过将证书的标识数据与客户端信任存储区中的相应数据进行比较来验证从服务器接收到的证书。这看起来像证书固定。
现在我们假设这种方法对于保护TCP流量是有效的,由证书颁发机构签名似乎是不必要的,因为我们同时控制着服务器和客户端。
初步测试表明,该实现在Java服务器和Java客户端中工作正常:
- 客户端接受与客户端信任存储区中的数据匹配的服务器证书。
- 客户端拒绝不匹配的服务器证书。
- tcpdump捕获的TCP数据包包含加密数据。
. NET客户端
我们使用SslStream来加密TCP流量,正如文档所建议的,我们不指定TLS版本;相反,如果版本低于1.2,则抛出异常。
我们对如何正确使用X509Chain.ChainPolicy.CustomTrustStore没有信心,因为文档省略了诸如此类型的用例以及X509KeyStorageFlags
和X509VerificationFlags
等选项类型的信息。
下面的代码旨在模仿上面概述的方法,即配置客户端在验证服务器证书时使用的信任存储数据结构。这种方法似乎相当于将证书导入操作系统的信任存储。
// Import the trust store.
private X509Certificate2Collection GetCertificates(string storePath, string storePassword)
{
byte[] bytes = File.ReadAllBytes(storePath);
var result = new X509Certificate2Collection();
result.Import(bytes, storePassword, X509KeyStorageFlags.EphemeralKeySet);
return result;
}
// Callback function to validate a certificate received from the server.
// fCertificates stores the result of function GetCertificates.
private bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// Do not allow this client to communicate with unauthenticated servers.
//
// With a self-signed certficate, sslPolicyErrors should be always equal to
// SslPolicyErrors.RemoteCertificateChainErrors.
var result = (SslPolicyErrors.RemoteCertificateChainErrors == sslPolicyErrors);
if (result)
{
// The values below are default values: set them to be explicit.
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.AddRange(fCertificates);
result = chain.Build((X509Certificate2)certificate);
}
return result;
}
// Initialize SslStream.
private SslStream GetStream(TcpClient tcpClient, string targetHost)
{
SslStream sslStream = new SslStream(
tcpClient.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient(targetHost);
// require TLS 1.2 or higher
if (sslStream.SslProtocol < SslProtocols.Tls12)
{
throw new AuthenticationException($"The SSL protocol ({sslStream.SslProtocol}) must be {SslProtocols.Tls12} or higher.");
}
}
catch (AuthenticationException caught)
{
sslStream.Dispose();
throw caught;
}
return sslStream;
}
初始测试产生的结果因操作系统而异:
- 当部署到WSL2上的Ubuntu时,此代码:
- 接受有效的服务器证书。
- 拒绝无效的服务器证书。
- 加密TCP数据包。
- 自动使用TLS 1.3。
- 给定有效的服务器证书,回调函数参数
sslPolicyErrors
等于SslPolicyErrors.RemoteCertificateChainErrors
(预期值)。 - 部署到MacOS时:
- 此代码自动使用TLS 1.2。
- 如果服务器证书有效,回调函数参数
sslPolicyErrors
包括以下值: SslPolicyErrors.RemoteCertificateNameMismatch
(意外)。SslPolicyErrors.RemoteCertificateChainErrors
(预期值)。
问题
1.在使用应用程序级信任存储区进行证书固定时,安全性会受到哪些影响?
1.此. NET代码会以哪些方式危及安全性?
1.在回顾了关于"证书名称不匹配"的讨论后(参见上面的SslPolicyErrors.RemoteCertificateNameMismatch
),我们的服务器证书似乎应该包含一个subjectAltName
字段来指定允许的DNS名称。在验证服务器证书时忽略sslPolicyErrors
是否有必要,或者是否合理,因为我们正在使用证书固定?
1条答案
按热度按时间h6my8fg21#
我无法回答您的具体问题,但我有一些想法:
你还没有提到服务器如何认证客户端。所以你可能会考虑实现一些类似客户端证书的东西。如果你控制了两者,你可能需要一些方法来确保随机攻击者无法连接。
你可以考虑建立一个威胁模型,在很多情况下,你没有想到的事情会导致问题。