我为我的平台(Linux Mint 17.3)进行了全新安装,按照以下步骤操作:.NET教程 - 5分钟内完成Hello World。我创建了一个针对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();
我有一个类似的项目,其中我通过服务与移动设备和桌面设备之间进行通信。
我们使用 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错误页面,因为我们希望那些尝试访问错误URL的人得到错误请求的提示,而不是让他们知道他们已经"走上了正轨"。
在.NET Core中,获取证书的方式不再通过Module。现代化的方法可能适合您:
private static X509Certificate2? Signer()
{
using var cert = X509Certificate2.CreateFromSignedFile(Assembly.GetExecutingAssembly().Location);
if (cert is null)
return null;
return new X509Certificate2(cert);
}
handler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
- Michael在处理这个问题时,经过大量测试后,我最终得到了以下解决方案。
SSL
,创建一个pfx
文件,包含证书和密钥。HttpClient
:_httpClient = new(new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = SslProtocols.Tls12,
ClientCertificates = { new X509Certificate2(@"C:\kambiDev.pfx") }
});
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进行编译,则似乎很容易:
使用带有基于SSL / TLS的客户端身份验证的HttpClient
这就像编译curl一样。如果您想能够将其连接到Windows证书存储,则必须针对某些特定的Windows库进行编译。
可用于 .NET Core 2.0< 和 .NET Framework 4.7.1<:
最初的回答:
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(new X509Certificate2("cert.crt"));
var client = new HttpClient(handler);
.Net Framework 4.5
上无法运行,但是在.Net Framework 4.7.1
上可以正常工作。我已经在本地测试过,并且文档也有说明。我已经更新了答案。 - Ogglaspublic 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"];
}
}
我认为这里提供的最佳答案。
通过利用 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 core 服务器的启动例程中:
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;
};
});
}
如果您查看.NET Standard HttpClientHandler类的参考文档, 您会发现ClientCertificates属性是存在的,但由于使用了EditorBrowsableState.Never而被隐藏。这可以防止IntelliSense显示它,但仍然可以在使用它的代码中正常工作。
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get; }