2016-08-23 13:20:38 +00:00
|
|
|
|
//
|
|
|
|
|
// MobileTlsContext.cs
|
|
|
|
|
//
|
|
|
|
|
// Author:
|
|
|
|
|
// Martin Baulig <martin.baulig@xamarin.com>
|
|
|
|
|
//
|
|
|
|
|
// Copyright (c) 2015 Xamarin, Inc.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#if SECURITY_DEP
|
|
|
|
|
#if MONO_SECURITY_ALIAS
|
|
|
|
|
extern alias MonoSecurity;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if MONO_SECURITY_ALIAS
|
|
|
|
|
using MonoSecurity::Mono.Security.Interface;
|
|
|
|
|
#else
|
|
|
|
|
using Mono.Security.Interface;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using SD = System.Diagnostics;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Security.Authentication;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
|
|
|
|
|
namespace Mono.Net.Security
|
|
|
|
|
{
|
|
|
|
|
abstract class MobileTlsContext : IDisposable
|
|
|
|
|
{
|
|
|
|
|
ICertificateValidator2 certificateValidator;
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
protected MobileTlsContext (MobileAuthenticatedStream parent, MonoSslAuthenticationOptions options)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2018-08-07 15:19:03 +00:00
|
|
|
|
Parent = parent;
|
|
|
|
|
Options = options;
|
|
|
|
|
IsServer = options.ServerMode;
|
|
|
|
|
EnabledProtocols = options.EnabledSslProtocols;
|
|
|
|
|
|
|
|
|
|
if (options.ServerMode) {
|
|
|
|
|
LocalServerCertificate = options.ServerCertificate;
|
|
|
|
|
AskForClientCertificate = options.ClientCertificateRequired;
|
|
|
|
|
} else {
|
|
|
|
|
ClientCertificates = options.ClientCertificates;
|
|
|
|
|
TargetHost = options.TargetHost;
|
|
|
|
|
ServerName = options.TargetHost;
|
|
|
|
|
if (!string.IsNullOrEmpty (ServerName)) {
|
|
|
|
|
var pos = ServerName.IndexOf (':');
|
|
|
|
|
if (pos > 0)
|
|
|
|
|
ServerName = ServerName.Substring (0, pos);
|
|
|
|
|
}
|
2016-11-16 13:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
certificateValidator = (ICertificateValidator2)ChainValidationHelper.GetInternalValidator (
|
|
|
|
|
parent.SslStream, parent.Provider, parent.Settings);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
internal MonoSslAuthenticationOptions Options {
|
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
internal MobileAuthenticatedStream Parent {
|
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
public MonoTlsSettings Settings => Parent.Settings;
|
|
|
|
|
|
|
|
|
|
public MonoTlsProvider Provider => Parent.Provider;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-10-19 20:04:20 +00:00
|
|
|
|
[SD.Conditional ("MONO_TLS_DEBUG")]
|
2016-08-23 13:20:38 +00:00
|
|
|
|
protected void Debug (string message, params object[] args)
|
|
|
|
|
{
|
2018-08-07 15:19:03 +00:00
|
|
|
|
Parent.Debug ("{0}: {1}", GetType ().Name, string.Format (message, args));
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract bool HasContext {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract bool IsAuthenticated {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsServer {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
internal string TargetHost {
|
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-16 13:31:47 +00:00
|
|
|
|
protected string ServerName {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
2016-11-16 13:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-23 13:20:38 +00:00
|
|
|
|
protected bool AskForClientCertificate {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected SslProtocols EnabledProtocols {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected X509CertificateCollection ClientCertificates {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool AllowRenegotiation {
|
|
|
|
|
get { return false; }
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
protected void GetProtocolVersions (out TlsProtocolCode? min, out TlsProtocolCode? max)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2018-08-07 15:19:03 +00:00
|
|
|
|
if ((EnabledProtocols & SslProtocols.Tls) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
min = TlsProtocolCode.Tls10;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else if ((EnabledProtocols & SslProtocols.Tls11) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
min = TlsProtocolCode.Tls11;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else if ((EnabledProtocols & SslProtocols.Tls12) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
min = TlsProtocolCode.Tls12;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else
|
|
|
|
|
min = null;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
if ((EnabledProtocols & SslProtocols.Tls12) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
max = TlsProtocolCode.Tls12;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else if ((EnabledProtocols & SslProtocols.Tls11) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
max = TlsProtocolCode.Tls11;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else if ((EnabledProtocols & SslProtocols.Tls) != 0)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
max = TlsProtocolCode.Tls10;
|
2018-08-07 15:19:03 +00:00
|
|
|
|
else
|
|
|
|
|
max = null;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract void StartHandshake ();
|
|
|
|
|
|
|
|
|
|
public abstract bool ProcessHandshake ();
|
|
|
|
|
|
|
|
|
|
public abstract void FinishHandshake ();
|
|
|
|
|
|
|
|
|
|
public abstract MonoTlsConnectionInfo ConnectionInfo {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal X509Certificate LocalServerCertificate {
|
2018-08-07 15:19:03 +00:00
|
|
|
|
get;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal abstract bool IsRemoteCertificateAvailable {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal abstract X509Certificate LocalClientCertificate {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract X509Certificate RemoteCertificate {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract TlsProtocols NegotiatedProtocol {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract void Flush ();
|
|
|
|
|
|
2017-10-19 20:04:20 +00:00
|
|
|
|
public abstract (int ret, bool wantMore) Read (byte[] buffer, int offset, int count);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-10-19 20:04:20 +00:00
|
|
|
|
public abstract (int ret, bool wantMore) Write (byte[] buffer, int offset, int count);
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2017-08-21 15:34:15 +00:00
|
|
|
|
public abstract void Shutdown ();
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
public abstract bool PendingRenegotiation ();
|
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
|
protected bool ValidateCertificate (X509Certificate leaf, X509Chain chain)
|
2016-08-23 13:20:38 +00:00
|
|
|
|
{
|
2016-11-10 13:04:39 +00:00
|
|
|
|
var result = certificateValidator.ValidateCertificate (TargetHost, IsServer, leaf, chain);
|
|
|
|
|
return result != null && result.Trusted && !result.UserDenied;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
|
protected bool ValidateCertificate (X509CertificateCollection certificates)
|
|
|
|
|
{
|
|
|
|
|
var result = certificateValidator.ValidateCertificate (TargetHost, IsServer, certificates);
|
|
|
|
|
return result != null && result.Trusted && !result.UserDenied;
|
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
protected X509Certificate SelectClientCertificate (string[] acceptableIssuers)
|
2016-11-10 13:04:39 +00:00
|
|
|
|
{
|
2018-08-07 15:19:03 +00:00
|
|
|
|
if (Settings.DisallowUnauthenticatedCertificateRequest && !IsAuthenticated)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
if (RemoteCertificate == null)
|
|
|
|
|
throw new TlsException (AlertDescription.InternalError, "Cannot request client certificate before receiving one from the server.");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We need to pass null to the user selection callback during the initial handshake, to allow the callback to distinguish
|
|
|
|
|
* between an authenticated and unauthenticated session.
|
|
|
|
|
*/
|
2016-11-10 13:04:39 +00:00
|
|
|
|
X509Certificate certificate;
|
|
|
|
|
var selected = certificateValidator.SelectClientCertificate (
|
2018-08-07 15:19:03 +00:00
|
|
|
|
TargetHost, ClientCertificates, IsAuthenticated ? RemoteCertificate : null, acceptableIssuers, out certificate);
|
2016-11-10 13:04:39 +00:00
|
|
|
|
if (selected)
|
|
|
|
|
return certificate;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
if (ClientCertificates == null || ClientCertificates.Count == 0)
|
2016-11-10 13:04:39 +00:00
|
|
|
|
return null;
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
/*
|
|
|
|
|
* .NET actually scans the entire collection to ensure the selected certificate has a private key in it.
|
|
|
|
|
*
|
|
|
|
|
* However, since we do not support private key retrieval from the key store, we require all certificates
|
|
|
|
|
* to have a private key in them (explicitly or implicitly via OS X keychain lookup).
|
|
|
|
|
*/
|
|
|
|
|
if (acceptableIssuers == null || acceptableIssuers.Length == 0)
|
|
|
|
|
return ClientCertificates [0];
|
|
|
|
|
|
|
|
|
|
// Copied from the referencesource implementation in referencesource/System/net/System/Net/_SecureChannel.cs.
|
|
|
|
|
for (int i = 0; i < ClientCertificates.Count; i++) {
|
|
|
|
|
var certificate2 = ClientCertificates[i] as X509Certificate2;
|
|
|
|
|
if (certificate2 == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
X509Chain chain = null;
|
|
|
|
|
try {
|
|
|
|
|
chain = new X509Chain ();
|
|
|
|
|
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
|
|
|
|
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreInvalidName;
|
|
|
|
|
chain.Build (certificate2);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// We ignore any errors happened with chain.
|
|
|
|
|
// Consider: try to locate the "best" client cert that has no errors and the lognest validity internal
|
|
|
|
|
//
|
|
|
|
|
if (chain.ChainElements.Count == 0)
|
|
|
|
|
continue;
|
|
|
|
|
for (int ii=0; ii< chain.ChainElements.Count; ++ii) {
|
|
|
|
|
var issuer = chain.ChainElements[ii].Certificate.Issuer;
|
|
|
|
|
if (Array.IndexOf (acceptableIssuers, issuer) != -1)
|
|
|
|
|
return certificate2;
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
; // ignore errors
|
|
|
|
|
} finally {
|
|
|
|
|
if (chain != null)
|
|
|
|
|
chain.Reset ();
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-10 13:04:39 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
// No certificate matches.
|
|
|
|
|
return null;
|
2016-11-10 13:04:39 +00:00
|
|
|
|
}
|
2016-08-23 13:20:38 +00:00
|
|
|
|
|
2018-08-07 15:19:03 +00:00
|
|
|
|
public abstract bool CanRenegotiate {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract void Renegotiate ();
|
|
|
|
|
|
2016-08-23 13:20:38 +00:00
|
|
|
|
public void Dispose ()
|
|
|
|
|
{
|
|
|
|
|
Dispose (true);
|
|
|
|
|
GC.SuppressFinalize (this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose (bool disposing)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~MobileTlsContext ()
|
|
|
|
|
{
|
|
|
|
|
Dispose (false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|