Skip to content

Commit

Permalink
[System]: Epic: Client Certificate Support - Part Two.
Browse files Browse the repository at this point in the history
This is the second and final part to bring Client Certificate support.
It needs to be landed on top of #8753 and #8756.

* `Mono.Security.Interface.IMonoSslStream`: Add `CanRenegotiate` and `RenegotiateAsync()`.

* `Mono.Security.Interface.MonoTlsSettings`: Add `DisallowUnauthenticatedCertificateRequest`.

* `AppleTlsContext`: fully support renegotiation.
  - we may now receive `SslStatus.PeerAuthCompleted` and `SslStatus.PeerClientCertRequested`
    during `Read()`.  It should in theory not happen during `Write()`, but I added it there
    as well just to be on the safe side.
  - `SetSessionOption()` may only be called before the initial handshake.

* `MobileAuthenticatedStream`: this is the major part of the work and the most complex one.
  - added a new `Operation` enum to keep track of what is going on and detect invalid state.
  - a renegotion may only be triggered while we're idle - that is no handshake, read or write
    operation is currently active.
  - `InternalWrite()` may now be called from `SSLRead()`, the new `Operation` tells us what
    is currently happening.
  - `ProcessHandshake()` now takes a `bool renegotiate` argument.
  - added sanity checks to `ProcessRead()` and `ProcessWrite()`.

* `MobileTlsContext.SelectClientCertificate()`: check for
  `MonoTlsSettings.DisallowUnauthenticatedCertificateRequest`

* `MonoTlsProviderFactory.InternalVersion`: bump the internal version number.

Tests have already been added to `web-tests/master`, they will auto-enable themselves when
using a Mono runtime that contains this code.
  • Loading branch information
Martin Baulig committed May 24, 2018
1 parent 0c2e513 commit 0a43e1c
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 19 deletions.
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ MONO_VERSION_BUILD=`echo $VERSION | cut -d . -f 3`
# This can be reset to 0 when Mono's version number is bumped
# since it's part of the corlib version (the prefix '1' in the full
# version number is to ensure the number isn't treated as octal in C)
MONO_CORLIB_COUNTER=6
MONO_CORLIB_COUNTER=7
MONO_CORLIB_VERSION=`printf "1%02d%02d%02d%03d" $MONO_VERSION_MAJOR $MONO_VERSION_MINOR 0 $MONO_CORLIB_COUNTER`

AC_DEFINE_UNQUOTED(MONO_CORLIB_VERSION,$MONO_CORLIB_VERSION,[Version of the corlib-runtime interface])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ public interface IMonoSslStream : IDisposable


MonoTlsConnectionInfo GetConnectionInfo ();

bool CanRenegotiate {
get;
}

Task RenegotiateAsync (CancellationToken cancellationToken);
}

interface IMonoSslStream2 : IMonoSslStream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,10 @@ public static IMonoSslStream GetMonoSslStream (HttpListenerContext context)
*
* - 1: everything up until May 2018
* - 2: the new ServicePointScheduler changes have landed
* - 3: full support for Client Certificates
*
*/
internal const int InternalVersion = 2;
internal const int InternalVersion = 3;

#endregion
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ public sealed class MonoTlsSettings
get; set;
}

public bool DisallowUnauthenticatedCertificateRequest {
get; set;
}

/*
* If you set this here, then it will override 'ServicePointManager.SecurityProtocol'.
*/
Expand Down Expand Up @@ -189,6 +193,7 @@ public MonoTlsSettings Clone ()
CertificateValidationTime = other.CertificateValidationTime;
SendCloseNotify = other.SendCloseNotify;
ClientCertificateIssuers = other.ClientCertificateIssuers;
DisallowUnauthenticatedCertificateRequest = other.DisallowUnauthenticatedCertificateRequest;
if (other.TrustAnchors != null)
TrustAnchors = new X509CertificateCollection (other.TrustAnchors);
if (other.CertificateSearchPaths != null) {
Expand Down
54 changes: 49 additions & 5 deletions mcs/class/System/Mono.AppleTls/AppleTlsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AppleTlsContext : MobileTlsContext
MonoTlsConnectionInfo connectionInfo;
bool isAuthenticated;
bool handshakeFinished;
// bool renegotiating;
bool renegotiating;
int handshakeStarted;

bool closed;
Expand Down Expand Up @@ -145,6 +145,24 @@ public override void StartHandshake ()

InitializeConnection ();

/*
* SecureTransport is bugged OS X 10.5.8+ - renegotiation after
* calling SetCertificate() will not work.
*
* We also cannot change options after the handshake has started,
* so if you want to request a client certificate, it will happen
* both during the initial handshake and during renegotiation.
*
* You may check 'SslStream.IsAuthenticated' (which will be false
* during the initial handshake) from within your
* 'LocalCertificateSelectionCallback' and return null to have the
* callback invoked again during renegotiation.
*
* However, the first time your selection callback returns a client
* certificate, that certificate will be used for the rest of the
* session.
*/

SetSessionOption (SslSessionOption.BreakOnCertRequested, true);
SetSessionOption (SslSessionOption.BreakOnClientAuth, true);
SetSessionOption (SslSessionOption.BreakOnServerAuth, true);
Expand Down Expand Up @@ -174,7 +192,7 @@ public override void Flush ()

public override bool ProcessHandshake ()
{
if (handshakeFinished)
if (handshakeFinished && !renegotiating)
throw new NotSupportedException ("Handshake already finished.");

while (true) {
Expand All @@ -191,7 +209,9 @@ public override bool ProcessHandshake ()
} else if (status == SslStatus.WouldBlock) {
return false;
} else if (status == SslStatus.Success) {
Debug ("Handshake complete!");
handshakeFinished = true;
renegotiating = false;
return true;
}
}
Expand Down Expand Up @@ -320,6 +340,9 @@ void InitializeConnection ()
!IPAddress.TryParse (TargetHost, out address)) {
PeerDomainName = ServerName;
}

if (Options.AllowRenegotiation)
SetSessionOption (SslSessionOption.AllowRenegotiation, true);
}

void InitializeSession ()
Expand Down Expand Up @@ -858,7 +881,19 @@ public override unsafe (int ret, bool wantMore) Read (byte[] buffer, int offset,
return (0, false);
}

CheckStatusAndThrow (status, SslStatus.WouldBlock, SslStatus.ClosedGraceful);
CheckStatusAndThrow (status, SslStatus.WouldBlock, SslStatus.ClosedGraceful,
SslStatus.PeerAuthCompleted, SslStatus.PeerClientCertRequested);

if (status == SslStatus.PeerAuthCompleted) {
Debug ($"Renegotiation complete: {GetSessionState ()}");
EvaluateTrust ();
return (0, true);
} else if (status == SslStatus.PeerClientCertRequested) {
Debug ($"Renegotiation asked for client certificate: {GetSessionState ()}");
ClientCertificateRequested ();
return (0, true);
}

var wantMore = status == SslStatus.WouldBlock;
return ((int)processed, wantMore);
} catch (Exception ex) {
Expand Down Expand Up @@ -890,7 +925,16 @@ public override unsafe (int ret, bool wantMore) Write (byte[] buffer, int offset

Debug ("Write done: {0} {1}", status, processed);

CheckStatusAndThrow (status, SslStatus.WouldBlock);
CheckStatusAndThrow (status, SslStatus.WouldBlock,
SslStatus.PeerAuthCompleted, SslStatus.PeerClientCertRequested);

if (status == SslStatus.PeerAuthCompleted) {
Debug ($"Renegotiation complete: {GetSessionState ()}");
EvaluateTrust ();
} else if (status == SslStatus.PeerClientCertRequested) {
Debug ($"Renegotiation asked for client certificate: {GetSessionState ()}");
ClientCertificateRequested ();
}

var wantMore = status == SslStatus.WouldBlock;
return ((int)processed, wantMore);
Expand Down Expand Up @@ -929,7 +973,7 @@ public override void Renegotiate ()

var status = SSLReHandshake (Handle);
CheckStatusAndThrow (status);
// renegotiating = true;
renegotiating = true;
#endif
}

Expand Down
3 changes: 3 additions & 0 deletions mcs/class/System/Mono.Btls/MonoBtlsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ public override void StartHandshake ()
} else {
ssl.SetServerName (ServerName);
}

if (Options.AllowRenegotiation)
ssl.SetRenegotiateMode (MonoBtlsSslRenegotiateMode.FREELY);
}

void SetPrivateCertificate (X509CertificateImplBtls privateCert)
Expand Down
14 changes: 13 additions & 1 deletion mcs/class/System/Mono.Net.Security/AsyncProtocolRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public AsyncHandshakeRequest (MobileAuthenticatedStream parent, bool sync)

protected override AsyncOperationStatus Run (AsyncOperationStatus status)
{
return Parent.ProcessHandshake (status);
return Parent.ProcessHandshake (status, false);
}
}

Expand Down Expand Up @@ -390,5 +390,17 @@ protected override AsyncOperationStatus Run (AsyncOperationStatus status)
}
}

class AsyncRenegotiateRequest : AsyncProtocolRequest
{
public AsyncRenegotiateRequest (MobileAuthenticatedStream parent)
: base (parent, false)
{
}

protected override AsyncOperationStatus Run (AsyncOperationStatus status)
{
return Parent.ProcessHandshake (status, true);
}
}
}
#endif

0 comments on commit 0a43e1c

Please sign in to comment.