65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Phone 8/8.1 上的证书锁定

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.33/5 (3投票s)

2014年12月4日

CPOL

4分钟阅读

viewsIcon

23746

证书固定确保客户端将服务器证书与该证书的已知副本进行比对

引言

这篇博文主要讲述了我们在 Windows Phone 8 和 8.1 上实现 证书锁定 时遇到的挑战。执行证书锁定的客户端为正常的 TLS 协议或 SSL 协议增加了一个额外的安全步骤。

我们可以说,证书锁定是为了确保客户端根据已知的证书副本检查服务器的证书。证书锁定提供了传输层安全性,并验证客户端和服务器之间的请求。它通过将服务器证书的公钥与客户端证书的公钥进行匹配来实现。它通过阻止无效请求来增强移动设备的安全性。

背景

在实现证书锁定之前,由于 Microsoft/MSDN 站点上缺少文档、博客或任何其他信息,我们确实在 WP 8 和 WP8.1 上面临了很多挑战。我们尝试从以下几个 URL 获取帮助,但由于信息不足,无法就证书锁定得出结论。

流套接字http://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj150599.aspx

使用 HttpClient 的证书锁定https://social.msdn.microsoft.com/Forums/windowsapps/en-US/fd71fd56-22d8-462f-af78-99bb0a0abc0d/how-to-utilize-the-info-from-windowssecuritycryptographycertificatesgetcertificateblob?forum=winappswithnativecode

通常,通过验证签名层次结构(服务器证书-中间证书-根证书)来信任站点的证书,因为根证书将存在于证书存储区(受信任的根证书)中。证书锁定是针对特定证书或由该证书颁发机构 (CA) 签名的证书进行验证。

Using the Code

Windows Phone 8.0 方法 - Windows Phone 8 中的低级证书锁定可以使用第三方库(如 Secureblackbox)来实现。

我们尝试了流套接字,但 Windows Phone 8 中的流套接字有 3 种类型

  • 普通套接字
  • SSL(带加密)
  • SSL(不带加密)

可以使用带加密的 SSL 选项以安全的方式进行通信,但它不支持获取认证信息或验证特定站点的公钥的 API。因此,流套接字不能用于证书锁定。

Windows Phone 8.1 方法 - 可以使用原生流套接字对象在 Windows Phone 8.1 中实现低级证书锁定,因为服务器证书信息在套接字中是可读的。虽然在 WP8.1 中使用 HTTP 客户端进行安全/高级证书锁定是可能的,但为此,我们将不得不使用 HTTPClient 从通过 URL 访问的证书对象中提取公钥。

超文本传输协议安全 (HTTPS) 是超文本传输协议 (http) 的安全版本。HTTPS 允许安全的电子商务交易,例如当用户通过 HTTPS 连接到网站时,该网站会使用数字证书加密会话。

///Source code for creation of HTTPClient Object and 
///extracting Certificate object from given URL using PostAsync()</p>

Uri serverUri = new Uri(URLName.Text);
HttpClient httpClient = new HttpClient();
string responseData = string.Empty;
HttpResponseMessage response = new HttpResponseMessage();
string data = "test=something";
response = await httpClient.PostAsync(serverUri, 
new HttpStringContent(data, Windows.Storage.Streams.UnicodeEncoding.Utf8, 
"application/json"));                              
//response = await httpClient.GetAsync
(serverUri, HttpCompletionOption.ResponseContentRead);
responseData = await response.Content.ReadAsStringAsync(); 

这将帮助您使用 URL 证书对象(例如根证书、中间证书和子证书)从服务器中提取所有可用证书。一个服务器可以有多个中间证书。

此外,我们在以下源代码中使用的 OID 对于 RSA 算法是全局的,不确定它是否适用于所有 URL,但我们仍然检查了近十个具有不同根证书的不同站点,并且它按预期工作。

for(int i = 0; 
i <  response.RequestMessage.TransportInformation.ServerIntermediateCertificates.Count; i++)
{
    Certificate aCertificate = 
    response.RequestMessage.TransportInformation.ServerIntermediateCertificates[i];

    IBuffer buffer = aCertificate.GetCertificateBlob();
    byte[] bCert = buffer.ToArray();
    string scert = BitConverter.ToString(bCert);
    ////Global OID - 1.2.840.113549.1.1.1
    byte[] rsaOID = EncodeOID("1.2.840.113549.1.1.1"); //
    string sOID = BitConverter.ToString(rsaOID);
    int length;
    int index = FindX509PubKeyIndex(bCert, rsaOID, out length);
    // Found X509PublicKey in certificate so copy it.
    if (index > -1)
    {
        byte[] X509PublicKey = new byte[length];
        Array.Copy(bCert, index, X509PublicKey, 0, length);
        URLCertPublicKey = BitConverter.ToString(X509PublicKey);
        txtServerPK.Text = URLCertPublicKey;
    }

虽然我们参考了从 MSDN 博客使用 Base64String 提取给定 OID 的公钥的源代码,请参考相同的代码,但该博客上可用的代码已针对以下例程进行了修改

if (index > -1)
{
    // Find outer Sequence
    while (index > 0 && Reference[index] != 0x30) index++;
    //index++;
    while (index > 0 && Reference[index] != 0x30) index++;
}

以下函数将帮助您从设备上可用的证书中获取公钥。

private async Task<string> GetClientPublicKey()
        {
            string DevCertPublicKey = null;
            string CertFileName = "GeoTrustGlobalCArootcert.cer";
            try
            {         
                StorageFolder folder = ApplicationData.Current.LocalFolder;
                if (folder != null)
                {
                    StorageFile file = await folder.GetFileAsync(CertFileName);
                    IBuffer CertBlob = await FileIO.ReadBufferAsync(file);
                    Certificate rootCert =new Certificate(CertBlob);
                    IBuffer buffer1 = rootCert.GetCertificateBlob();
                    byte[] bCert1 = buffer1.ToArray();
                    string scert1 = BitConverter.ToString(bCert1);
                    byte[] rsaOID1 = EncodeOID("1.2.840.113549.1.1.1"); //
                    string sOID1 = BitConverter.ToString(rsaOID1);
                    int length1;
                    int index1 = FindX509PubKeyIndex(bCert1, rsaOID1, out length1);
                    // Found X509PublicKey in certificate so copy it.                   
                    if (index1 > -1)
                    {
                        byte[] X509PublicKey1 = new byte[length1];
                        Array.Copy(bCert1, index1, X509PublicKey1, 0, length1);
                        DevCertPublicKey = BitConverter.ToString(X509PublicKey1);
                        txtClientPK.Text = DevCertPublicKey;
                    }                 
                    return DevCertPublicKey;
                }
                return DevCertPublicKey;
            }
            catch (Exception ex)
            {
                return ex.Message.ToString();
            }
        }          

一旦用户同时拥有公钥,即来自客户端证书和服务器证书,比较它们将给出证书锁定是成功还是失败的结果。我们能够成功地在 WebView 上扩展证书锁定。在 webview 中,在 Start_Navigation 事件中,将能够在重定向到目标 URL 之前验证每个证书。

证书锁定问题

  1. 可能由于证书锁定而导致应用程序性能问题。
  2. 嵌入在您的应用程序中的证书最终会过期。您将不得不计划应用程序更新,其中包含更新的证书,或者编写一种方法让应用程序下载新证书。

关注点

我们确实花费了大量的努力来实现这一成功,但最终我们实现了我们想要的,而那种感觉本身就帮助我们忘记了我们面临的所有问题和困难......真的这是一次很棒的经历和团队合作!

来自我的团队 Mukesh Gupta、Preeti Arora、Atanu Ray、Santhiya Raman 以及所有其他与我一起实现这一成功的人。

© . All rights reserved.