我想在我的 xamarin 应用程序中实现客户端证书身份验证。 最重要的是,我使用的是自定义证书颁发机构 (CA) 和 TLS 1.2。
到目前为止,我设法使用 android、UWP 和 WPF 让它运行起来。唯一缺少的平台是 ios。
这是我的 NSUrlSessionDelegate:
public class SSLSessionDelegate : NSUrlSessionDelegate, INSUrlSessionDelegate
{
private NSUrlCredential Credential { get; set; }
private SecIdentity identity = null;
private X509Certificate2 ClientCertificate = null;
private readonly SecCertificate CACertificate = null;
public SSLSessionDelegate(byte[] caCert) : base()
{
if (caCert != null)
{
CACertificate = new SecCertificate(new X509Certificate2(caCert));
}
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
if (pkcs12 != null)
{
ClientCertificate = new X509Certificate2(pkcs12, new string(password));
identity = SecIdentity.Import(ClientCertificate);
SecCertificate certificate = new SecCertificate(ClientCertificate);
SecCertificate[] certificates = { certificate };
Credential = NSUrlCredential.FromIdentityCertificatesPersistance(identity, certificates, NSUrlCredentialPersistence.ForSession);
}
else
{
ClientCertificate = null;
identity = null;
Credential = null;
}
}
public override void DidReceiveChallenge(NSUrlSession session, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodClientCertificate)
{
NSUrlCredential c = Credential;
if (c != null)
{
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.UseCredential, c);
return;
}
}
if (challenge.ProtectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodServerTrust)
{
SecTrust secTrust = challenge.ProtectionSpace.ServerSecTrust;
secTrust.SetAnchorCertificates(new SecCertificate[] {
CACertificate
});
secTrust.SetAnchorCertificatesOnly(true);
}
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
}
}
如果没有配置客户端证书,此方法有效 DidReceiveChallenge
使用 AuthenticationMethodServerTrust
调用一次并接受自定义 CA。
但是一旦配置了客户端证书,DidReceiveChallenge
就会被调用 4 次(每个 AuthenticationMethod
调用两次),我得到 NSURLErrorDomain (-1200)
错误。
有人知道我做错了什么吗?
更新
SSLSessionDelegate
的用法如下:
public class HttpsServer : AbstractRemoteServer, IRemoteServer
{
private static readonly Logger LOG = LogManager.GetLogger();
private SSLSessionDelegate sSLSessionDelegate;
private NSUrlSession session;
private NSUrl baseAddress;
public HttpsServer()
{
sSLSessionDelegate = new SSLSessionDelegate(SSLSupport.GetTruststoreRaw());
NSUrlSessionConfiguration configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;
configuration.HttpShouldSetCookies = true;
configuration.TimeoutIntervalForRequest = 30;
configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_2;
configuration.TimeoutIntervalForResource = 30;
NSMutableDictionary requestHeaders;
if (configuration.HttpAdditionalHeaders != null)
{
requestHeaders = (NSMutableDictionary)configuration.HttpAdditionalHeaders.MutableCopy();
}
else
{
requestHeaders = new NSMutableDictionary();
}
AppendHeaders(requestHeaders, SSLSupport.GetDefaultHeaders());
configuration.HttpAdditionalHeaders = requestHeaders;
session = NSUrlSession.FromConfiguration(configuration, (INSUrlSessionDelegate)sSLSessionDelegate, NSOperationQueue.MainQueue);
baseAddress = NSUrl.FromString(SSLSupport.GetBaseAddress());
}
public void SetClientCertificate(byte[] pkcs12, char[] password)
{
sSLSessionDelegate.SetClientCertificate(pkcs12, password);
}
public override async Task<string> GetString(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSData responseContent = await GetRaw(url, headers, cancellationToken);
return NSString.FromData(responseContent, NSStringEncoding.UTF8).ToString();
}
private async Task<NSData> GetRaw(string url, Dictionary<string, string> headers, CancellationToken cancellationToken)
{
NSMutableUrlRequest request = GetRequest(url);
request.HttpMethod = "GET";
request.Headers = AppendHeaders(request.Headers, headers);
Task<NSUrlSessionDataTaskRequest> taskRequest = session.CreateDataTaskAsync(request, out NSUrlSessionDataTask task);
cancellationToken.Register(() =>
{
if (task != null)
{
task.Cancel();
}
});
try
{
task.Resume();
NSUrlSessionDataTaskRequest taskResponse = await taskRequest;
if (taskResponse == null || taskResponse.Response == null)
{
throw new Exception(task.Error.Description);
}
else
{
NSHttpUrlResponse httpResponse = (NSHttpUrlResponse)taskResponse.Response;
if (httpResponse.StatusCode == 303)
{
if (!httpResponse.AllHeaderFields.TryGetValue(new NSString("Location"), out NSObject locationValue))
{
throw new Exception("redirect received without Location-header!");
}
return await GetRaw(locationValue.ToString(), headers, cancellationToken);
}
if (httpResponse.StatusCode != 200)
{
throw new Exception("unsupported statuscode: " + httpResponse.Description);
}
return taskResponse.Data;
}
}
catch (Exception ex)
{
throw new Exception("communication exception: " + ex.Message);
}
}
}
这是我的 Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>XXXXXXXXXX</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
更新2
我既没有找到解决方案,也没有人给我提示,所以我最终暂时放弃了客户端证书。我切换到 OAuth2 进行授权,并使用我自己的证书颁发机构(无自签名证书)进行服务器身份验证,效果很好。
但我仍然对这个问题很感兴趣,并且对如何让它发挥作用的每一个想法都很高兴。
最佳答案
我建议使用 ModernHttpClient。它支持 Android 和 iOS 的 ClientCertificates。 它是开源的,因此如果您想完成自己的实现,您可以随时查看他们的 github 以供引用。
关于c# - xamarin 中带有 NSUrlSessionDelegate 的客户端证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51087853/