超详细的Asp.net使用SSL双向认证,一篇就够了

传统:项目和项目之间https仅通过SSL单向认证后进行数据传输;
本文:项目和项目之间做到SSL双向认证,防止攻击者恶意破坏;

关于单向认证和双向认证的区别:https://cloud.tencent.com/developer/article/1819018

**本文一共分为4章节
1章节:简述了服务器的网站在SSL双向认证中的角色分类;
2章节:叙述了第三方在访问我方Server API时,我方需要做的配置和校验其携带的客户端证书;
3章节:叙述了我方服务器上的网站作为客户端访问第三方Server API时需要做的配置和本地证书的配置与获取;
4章节:简述了参考来源;
5章节:维护内容;
**


1 叙述

当前系统关于SSL双向认证包括两种不同的角色:
1、当前系统作为服务器(第三方请求我方服务时,需要携带颁发给他们的客户端证书)
2、当前系统作为客户端(我方请求第三方服务时,需要携带颁发给我方的客户端证书)
3、(二者可以组合使用)
后面章节分别对上述(1)(2)部分进行详细的描述;

2 当前系统作为服务器

2.1 IIS的配置

在这里插入图片描述

(1)当只有部分API需要Client Certificate时,勾选“接受”;(本文)
在这里插入图片描述

(2)当整个Service都必须Client Certificate时,勾选“要求SSL”–>“必须”;
在这里插入图片描述

2.2 相关代码

2.2.1 代码验证片段

下面代码所在的类继承了特性,重写了基类方法;

var reClient = context.Request.GetClientCertificate();

if (reClient == null)
{
    _logService.Error($"Client Certificate is null.");
    context.ErrorResult = new AuthenticationFailureResult($"Client Certificate is require.", context.Request, HttpStatusCode.BadRequest);
    return;
}

var x509 = new X509Certificate2(reClient);
var chain = new X509Chain(true);

chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;//使用在线证书吊销列表(CRL)进行吊销检查

if (chain.Build(x509))
{
    context.ErrorResult = new AuthenticationFailureResult($"Invalid Client Certificate.", context.Request, HttpStatusCode.BadRequest);
    _logService.Error("Invalid Client Certificate. chain: " + chain.ToString());
    return;
}
                
List<string> x509IssuerList = WC_X509_IssuerListString.Replace(" ","").Split(';').ToList();//去除空格,WC_X509_IssuerListString变量为Appsetting.config文件中配置的允许哪些CN的证书,分号作为分隔符,可配置多个CN

if (!x509IssuerList.Contains(x509.Issuer.Replace(" ", "")))
{
    context.ErrorResult = new AuthenticationFailureResult($"", context.Request, HttpStatusCode.BadRequest);//Not return any message.
    _logService.Error("Appsetting.config not exit x509Issuer: " + x509.Issuer.ToString());
    return;
}

2.2.2 AppSetting.config设置

Value用于配置允许哪些CN进行连接;多个CN之间通过分号隔开(自定义);
在这里插入图片描述
在这里插入图片描述

2.3 计算机管理控制台MMC的配置

Win+R --> mmc,打开控制台;如图所示,点击“文件”–>“添加/删除管理单元”;
在这里插入图片描述

左侧项目中选择“证书”,点击中间“添加”按钮,如下图所示,选择“我的用户账户”,其他的全部默认,点击完成;同样的方法选择“计算机账户”;
(1)对于用户来说,用户个人计算机需要安装“客户端证书”时,只需要添加“我的用户账户”项;
(2)对于服务器来说,只需要添加“计算机账户”;
在这里插入图片描述 . 在这里插入图片描述
在这里插入图片描述
如下图所示,分别为配置“客户端证书”和“服务器证书”的主界面;
在这里插入图片描述

2.3.1 配置客户端证书

2.3.1.1 添加客户端证书

展开“证书 - 当前用户”节点,在“个人”–>“证书”右键,选择“所有任务”–>“导入”,点击“下一步”,选择“客户端.pfx”文件,点击“下一步”–> 输入文件的“密码”后,默认点击“下一步”,直到完成;此时客户端证书已经添加完毕;

2.3.1.2添加客户端证书CA签发中心

在“证书 - 当前用户”节点下,点击“受信任的根证书颁发机构”–>“证书”,右键“所有任务”–>“导入”,点击下一步,选择“.cer”文件,全部默认点击下一步,直到完成;此时客户端证书的CA签发中心已经添加完毕;
注意:如果客户端证书和服务器证书的CA签发中心不一致,需要将服务器证书的CA签发文件(即.cer文件)上传到此处;

2.3.2 配置服务器证书

2.3.2.1 添加服务器证书

选择“服务器.pfx”文件;与2.3.1.1中操作步骤一致;

2.3.2.2 添加服务器证书CA签发中心

与3.1.2中操作步骤一致;注意:如果客户端证书和服务器证书的CA签发中心不一致,需要将客户端证书的CA签发文件(即.cer文件)上传到此处;

2.4 双向认证之IIS的log记录

Log存储位置:C:\inetpub\logs\LogFiles,文件夹的最后一位编号对应IIS中应用程序的ID列,如图所示;
在这里插入图片描述

2.4.1 AWS Log记录示例

提供正确的客户端证书(IIS Log):
在这里插入图片描述

提供自签名的客户端证书(IIS Log):
在这里插入图片描述
未提供客户端证书(网站Log4net记录)
在这里插入图片描述
---->【Client certificates is null】

提供了相关证书,但是未在appsetting.config中配置该CN时(网站Log4net记录)
在这里插入图片描述

2.5 注意事项

2.5.1网站部署在局域网和远端的区别

2.5.1.1网站部署局域网时

部署在局域网内的网站,可以通过本地计算机来生成CA、服务器证书、客户端证书;
通过局域网内两台电脑进行测试的测试结果:可通过需求中的SSL双向验证要求;

2.5.1.2网站部署在远端时

(1)部署在远端时,需要使用经过第三方CA进行签发的证书(需要一定的费用),也可自行通过网上尝试搜索“申请免费的计算机证书”进行获取;
(2)自签名证书在远端不被信任,所以会被IIS拦截,可IIS的log中查看到相关拦截403记录(可参考4.1);
(3)负载均衡不会将证书传递到另一台机器;

2.5.2 Postman配置请求证书

Postman配置客户端证书如下图所示(此文件由第三方提供)
在这里插入图片描述

2.5.3 与http层面的SSL双向握手的区别

由于这里是通过程序进行客户端证书的验证,所以在请求API时,需要同时将客户端证书提交上来(Postman需要配置客户端证书和CA的PEM文件,PEM文件可通过网站进行下载);
从http层面来说并不是实际意义上的双向认证,所以通过抓包只能看见单向验证,但是从某种意义上来说是更加严格的双向认证,其原因是因为通过“代码 + appsetting.config文件”限制了哪些CN可以通过;而http层面的不会去筛选特定的CN;

3 当前系统作为客户端

3.1 必备文件

1、第三方提供的客户端证书.pfx文件;
2、第三方提供的客户端证书的秘钥(明文,不要随意暴露);

3.2 代码获取客户端证书的方式

注意:如果证书经常过期,可以选择“通过文件路径获取证书”方式(Within 1 years.),这样可以免去通过MMC进行安装等复杂的配置步骤;

3.2.1通过文件路径获取证书(非首选)

注意:如果Log文件记录提示IIS无权限,则修改文件的访问权限,右键属性修改即可。

#region 方式1:通过路径获取Client Certificate方式;
try
{
    string certificatePath = @"....\ClientCertificateFile.pfx";//使用相对路径或者绝对路径,
    string certPassword = "******"; //客户端证书对应的明文密码;

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3|SecurityProtocolType.Tls| (SecurityProtocolType)768|(SecurityProtocolType)3072|(SecurityProtocolType)0x300|(SecurityProtocolType)0xC00;//根据当前framework版本中的枚举进行设置;

    ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;//获取或设置用于验证服务器证书的回调,根据实际情况true或false;

    //clientCer对象就是请求时携带上去的客户端证书
    X509Certificate2 clientCer = new X509Certificate2(certificatePath, certPassword, X509KeyStorageFlags.MachineKeySet|X509KeyStorageFlags.PersistKeySet|X509KeyStorageFlags.Exportable);
catch (Exception ex)
{
    logger.Error($"Get Client certificate Error : {ex}.");
    return null;
}
#endregion

3.2.2获取通过MMC安装的证书(首选)

    #region 方式2:通过mmc安装Client Certificate方式

                ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; //获取或设置用于验证服务器证书的回调,根据实际情况true或false;
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | (SecurityProtocolType)768 | (SecurityProtocolType)3072 | (SecurityProtocolType)0x300 | (SecurityProtocolType)0xC00;
#if !DEBUG
                X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); //从 "本地计算机" 安装的Client Certificate中进行查找;//不能使用CurrentUser
#else
                X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);//从 "计算机当前用户" 安装的Client Certificate中进行查找;也可以使用LocalMachine
#endif
                store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
                X509Certificate2 clientCer;
                try
                {
                    //var clintCertificatesList = store.Certificates.Cast<X509Certificate2>().Select(c => c.Thumbprint).ToList();
                    //foreach (var VARIABLE in clintCertificatesList)
                    //{
                        //logger.Info($" {VARIABLE}.");//可以通过log4net打印MMC中所有证书的指纹,用于调试使用;
                    //}

                    //do not use "store.Certificates.Find()" --> 性能不好
                    //Find By certificate thumbprint.
                    //Case must be omitted;
                    clientCer = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.Thumbprint.Equals(***证书指纹***, StringComparison.OrdinalIgnoreCase)); //linq语法,根据证书指纹获取指定证书
                    if (clientCer == null)
                    {
                        logger.Error($"Client certificate is null.");
                        return null;
                    }
                }
                catch (Exception ex)
                {
                    logger.Error($"Get Client certificate Error : {ex}.");
                    return null;
                }

                #endregion

3.3 MMC及IIS的配置

3.3.1 MMC安装并配置客户端证书权限

测试与实施人员:参见本文2.3章节安装客户端证书(仅在本地计算机 --> Local Machine);
开发人员:参见本文2.3章节所有(Local Machine,Current User);

3.3.2 配置客户端证书与IIS的权限

简介:这里提供了两种方法来实现,基于网站的安全性考虑,建议通过MMC来配置客户端证书的访问权限;

3.3.2.1 通过MMC配置客户端证书的访问权限(首选)

Win+R --> mmc,打开控制台;如图所示,点击“文件”–>“添加/删除管理单元”–“证书”–“本地计算机”;如下图所示;
在这里插入图片描述

找到安装的指定的客户端证书后,“右键”–“所有任务”–“管理私钥”–>点击“添加”–>点击“高级”–>点击“立即查找”,如下图列表所示;
在这里插入图片描述

选择“IIS_IUSERS”–> 点击确定,–> 点击确定–>“应用”–>“确定”,完成
在这里插入图片描述

3.3.2.2 设置IIS程序池的角色权限(非首选)

不建议使用此方式来设置(关于内置账户的区别,在第4章节有参考链接);
步骤:打开IIS管理器,找到网站对应的程序池–>“高级设置”–“进程模型”–>“标识”–>“Local System”–>“确定”即可;
在这里插入图片描述

4 附录及参考

4.1 第三章节参考信息

1、IIS应用程序池标识的权限说明
https://blog.csdn.net/u014088408/article/details/98732583
https://www.cnblogs.com/jfzhu/p/4067297.html
https://docs.microsoft.com/zh-cn/troubleshoot/developer/webapps/iis/www-authentication-authorization/understanding-identities

2、IIS无法读取本地证书问题(见解答):Advanced Setting–>Process Model–>Identity IIS默认的是权限较低,可以改成Networkservice即可(个人觉得这里改成LocalSystem是可以的,但是由于权限过高会不安全)。
https://q.cnblogs.com/q/54675/

5 维护内容

更新日期 2022.08.31 --> 将客户端证书保存到Sqlserver中,并读取以访问第三方 Server

T_SETTING表存储证书相关内容
在这里插入图片描述

                        <label type="button" class="btn btn-primary" style="margin-right: 10px; margin-bottom: 0px;" for="xdaTanFileCertificate" data-toggle="tooltip" data-placement="bottom" data-html="true">
                            Select Certificate
                        </label>
                        <input type="file" name="Logo" accpet="application/x-pkcs12" id="xdaTanFileCertificate"  onchange="xmTanUploadCertificate(this)" />
    function xmTanUploadCertificate(obj) {
        var file = obj.files[0];

        if (file != null) {
            if (file.size < 1048576) {  //1MB
                var reader = new FileReader();

                reader.onloadstart = function (e) {
                    console.log("start....");
                }
                reader.onprogress = function (e) {
                    console.log("reading....");
                }
                reader.onabort = function (e) {
                    console.log("stop....");
                }
                reader.onerror = function (e) {
                    console.log("error....");
                }
                reader.onload = function (e) {
                    console.log("success....");
                    var clientCertficateName = document.getElementById("ClientCertficateName");
                    clientCertficateName.value = file.name;

                    var pfx = document.getElementById("ClientCertficatePFX");
                    pfx.value = e.target.result;

                }

                reader.readAsDataURL(file);//表示所读取文件的内容为 Base64 字符串
                //reader.readAsText(file);
                //https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
            }
        }
    }
//using 
                    byte[] rawData = Convert.FromBase64String("base64加密后存储在DB中的证书");
                    try
                    {
                        X509Certificate2 clientCer = new X509Certificate2(rawData, "证书密码");
                    }
                    catch (Exception ex)
                    {
                        logger.Error($"Get Client certificate Error : {ex}.");
                        throw new Exception("Get Client certificate Error.");
                    }

欢迎指正和提问;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值