将客户端证书添加到.net核心Httpclient

时间:2016-10-13 06:44:36

标签: c# .net asp.net-core .net-core dotnet-httpclient

我正在玩.Net Core并构建一个利用支付API的API。需要将客户端证书添加到双向SSL身份验证请求中。 如何使用HttpClient在.Net Core中实现这一目标?

我查看了各种文章,发现HttpClientHandler没有提供添加客户端证书的任何选项。

请告知

7 个答案:

答案 0 :(得分:26)

我按照以下步骤为我的平台(Linux Mint 17.3)运行了全新安装:https://www.microsoft.com/net/core。我创建了一个针对netcoreapp1.0框架的新控制台应用程序,能够提交客户端证书;但是,我确实收到了" SSL连接错误" (CURLE_SSL_CONNECT_ERROR 35)在测试时,即使我使用了有效的证书。我的错误可能是我的libcurl特有的。

更新:我在Windows 7上运行完全相同的操作,它完全按照需要运行。

// using System.Net.Http;
// using System.Security.Authentication;
// using System.Security.Cryptography.X509Certificates;

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);
var result = client.GetAsync("https://apitest.startssl.com").GetAwaiter().GetResult();

答案 1 :(得分:7)

我有一个类似的项目,我在服务之间以及移动和桌面之间通过服务进行通信。

我们使用EXE中的Authenticode证书来确保它是我们执行请求的二进制文件。

在请求方(简化为帖子)。

Module m = Assembly.GetEntryAssembly().GetModules()[0];
using (var cert = m.GetSignerCertificate())
using (var cert2 = new X509Certificate2(cert))
{
   var _clientHandler = new HttpClientHandler();
   _clientHandler.ClientCertificates.Add(cert2);
   _clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
   var myModel = new Dictionary<string, string>
   {
       { "property1","value" },
       { "property2","value" },
   };
   using (var content = new FormUrlEncodedContent(myModel))
   using (var _client = new HttpClient(_clientHandler))
   using (HttpResponseMessage response = _client.PostAsync($"{url}/{controler}/{action}", content).Result)
   {
       response.EnsureSuccessStatusCode();
       string jsonString = response.Content.ReadAsStringAsync().Result;
       var myClass = JsonConvert.DeserializeObject<MyClass>(jsonString);
    }
}

然后我在获取请求的操作上使用以下代码

X509Certificate2 clientCertInRequest = Request.HttpContext.Connection.ClientCertificate;
if (!clientCertInRequest.Verify() || !AllowedCerialNumbers(clientCertInRequest.SerialNumber))
{
    Response.StatusCode = 404;
    return null;
}

我们宁愿提供404而不是500,因为我们喜欢那些正在尝试获取错误请求的人,而不是让他们知道他们是在正确的轨道上#34;

答案 2 :(得分:2)

我没有为我的客户端使用.Net,但是服务器端可以通过在IIS后面部署我的Asp.Net Core网站,为IIS +客户端证书配置IIS来简单地通过IIS进行配置:

iis客户端证书设置:

然后你可以在代码中得到它:

        var clientCertificate = await HttpContext.Connection.GetClientCertificateAsync();

        if(clientCertificate!=null)
            return new ContentResult() { Content = clientCertificate.Subject };

它对我来说很好,但我使用curl或chrome作为客户,而不是.Net的。在HTTPS握手期间,客户端从服务器获取请求以提供证书并将其发送到服务器。

事情是,如果您使用的是.Net Core客户端,它不具备特定于平台的代码,如果它无法将自身连接到任何特定于操作系统的证书存储区,则将其提取出来是有意义的。并将其发送到服务器。如果您正在编译.Net 4.5.x,那么这似乎很容易:

https://pfelix.wordpress.com/2012/12/16/using-httpclient-with-ssltls/

就像编译curl一样,如果你想将它连接到Windows证书库,你必须针对某个特定的Windows库进行编译。

答案 3 :(得分:1)

我认为为此提供了最好的答案here

通过使用X-ARR-ClientCert标头,您可以提供证书信息。

适用的解决方案在这里:

X509Certificate2 certificate;
var handler = new HttpClientHandler {
    ClientCertificateOptions = ClientCertificateOption.Manual,
    SslProtocols = SslProtocols.Tls12
};
handler.ClientCertificates.Add(certificate);
handler.CheckCertificateRevocationList = false;
// this is required to get around self-signed certs
handler.ServerCertificateCustomValidationCallback =
    (httpRequestMessage, cert, cetChain, policyErrors) => {
        return true;
    };
var client = new HttpClient(handler);
requestMessage.Headers.Add("X-ARR-ClientCert", certificate.GetRawCertDataString());
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
var response = await client.SendAsync(requestMessage);

if (response.IsSuccessStatusCode)
{
    var responseContent = await response.Content.ReadAsStringAsync();
    var keyResponse = JsonConvert.DeserializeObject<KeyResponse>(responseContent);

    return keyResponse;
}

在.net核心服务器的启动例程中:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddCertificateForwarding(options => {
        options.CertificateHeader = "X-ARR-ClientCert";
        options.HeaderConverter = (headerValue) => {
            X509Certificate2 clientCertificate = null;
            try
            {
                if (!string.IsNullOrWhiteSpace(headerValue))
                {
                    var bytes = ConvertHexToBytes(headerValue);
                    clientCertificate = new X509Certificate2(bytes);
                }
            }
            catch (Exception)
            {
                // invalid certificate
            }

            return clientCertificate;
        };
    });
}

答案 4 :(得分:0)

如果您查看.NET Standard reference for the HttpClientHandler class,则会发现 ClientCertificates 属性存在,但由于使用了EditorBrowsableState.Never而被隐藏了。这样可以防止IntelliSense显示它,但仍可以在使用它的代码中使用。

[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; }

答案 5 :(得分:0)

可用于.NET Core 2.0 <和.NET Framework 4.5 <:

var handler = new HttpClientHandler();
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);

对于.NET Framework 4.5,您还可以像这样使用System.Net.HttpSystem.Net.Http.WebRequest

var handler = new WebRequestHandler();
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);

答案 6 :(得分:0)

像这样在Main上进行所有配置:

public static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
            var logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
            string env="", sbj="", crtf = "";

            try
            {
                var whb = WebHost.CreateDefaultBuilder(args).UseContentRoot(Directory.GetCurrentDirectory());

                var environment = env = whb.GetSetting("environment");
                var subjectName = sbj = CertificateHelper.GetCertificateSubjectNameBasedOnEnvironment(environment);
                var certificate = CertificateHelper.GetServiceCertificate(subjectName);

                crtf = certificate != null ? certificate.Subject : "It will after the certification";

                if (certificate == null) // present apies even without server certificate but dont give permission on authorization
                {
                    var host = whb
                        .ConfigureKestrel(_ => { })
                        .UseContentRoot(Directory.GetCurrentDirectory())
                        .UseIISIntegration()
                        .UseStartup<Startup>()
                        .UseConfiguration(configuration)
                        .UseSerilog((context, config) =>
                        {
                            config.ReadFrom.Configuration(context.Configuration);
                        })
                        .Build();
                    host.Run();
                }
                else
                {
                    var host = whb
                        .ConfigureKestrel(options =>
                        {
                            options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions =>
                            {
                                var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions()
                                {
                                    ClientCertificateMode = ClientCertificateMode.AllowCertificate,
                                    SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
                                    ServerCertificate = certificate
                                };
                                listenOptions.UseHttps(httpsConnectionAdapterOptions);
                            });
                        })
                        .UseContentRoot(Directory.GetCurrentDirectory())
                        .UseIISIntegration()
                        .UseUrls("https://*:443")
                        .UseStartup<Startup>()
                        .UseConfiguration(configuration)
                        .UseSerilog((context, config) =>
                        {
                            config.ReadFrom.Configuration(context.Configuration);
                        })
                        .Build();
                    host.Run();
                }



                Log.Logger.Information("Information: Environment = " + env +
                    " Subject = " + sbj +
                    " Certificate Subject = " + crtf);

            }
            catch(Exception ex)
            {
                Log.Logger.Error("Main handled an exception: Environment = " + env +
                    " Subject = " + sbj +
                    " Certificate Subject = " + crtf +
                    " Exception Detail = " + ex.Message);
            }
        }

像这样配置startup.cs

        #region 2way SSL settings
        services.AddMvc();
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCertificateAuthentication(certOptions =>
        {
            var certificateAndRoles = new List<CertficateAuthenticationOptions.CertificateAndRoles>();
            Configuration.GetSection("AuthorizedCertficatesAndRoles:CertificateAndRoles").Bind(certificateAndRoles);
            certOptions.CertificatesAndRoles = certificateAndRoles.ToArray();
        });

        services.AddAuthorization(options =>
        {
            options.AddPolicy("CanAccessAdminMethods", policy => policy.RequireRole("Admin"));
            options.AddPolicy("CanAccessUserMethods", policy => policy.RequireRole("User"));
        });
        #endregion

证书助手

public class CertificateHelper
{
    protected internal static X509Certificate2 GetServiceCertificate(string subjectName)
    {
        using (var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
        {
            certStore.Open(OpenFlags.ReadOnly);
            var certCollection = certStore.Certificates.Find(
                                       X509FindType.FindBySubjectDistinguishedName, subjectName, true);
            X509Certificate2 certificate = null;
            if (certCollection.Count > 0)
            {
                certificate = certCollection[0];
            }
            return certificate;
        }
    }

    protected internal static string GetCertificateSubjectNameBasedOnEnvironment(string environment)
    {
        var builder = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile($"appsettings.{environment}.json", optional: false);

        var configuration = builder.Build();
        return configuration["ServerCertificateSubject"];
    }
}