Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sync SslStream.AuthenticateAs overload with SslOptions public #34638

Closed
ManickaP opened this issue Apr 7, 2020 · 13 comments · Fixed by #36221
Closed

Make sync SslStream.AuthenticateAs overload with SslOptions public #34638

ManickaP opened this issue Apr 7, 2020 · 13 comments · Fixed by #36221
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Net.Security
Milestone

Comments

@ManickaP
Copy link
Member

ManickaP commented Apr 7, 2020

Make SslStream.AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions) public.
Possibly make SslStream.AuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions) public as well to keep the API consistent. This one is not required, only suggested.

Motivation

To properly support sync implementation of HttpClient (see #32125).

Without this overload, it is not possible to pass custom certificate callbacks (neither RemoteCertificateValidationCallback nor LocalCertificateSelectionCallback). Eventually failing authentication which would otherwise pass in an async scenario.

The corresponding async method AuthenticateAsClientAsync(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken = default) already is public. And there are other sync overloads which are public. This issue is not the first one introducing sync methods on SslStream.

Existing API for AuthenticateAsClient(Async): https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Security/ref/System.Net.Security.cs#L185-L191
Existing API for AuthenticateAsServer(Async): https://github.com/dotnet/runtime/blob/master/src/libraries/System.Net.Security/ref/System.Net.Security.cs#L192-L198

Proposed API

public partial class SslStream : System.Net.Security.AuthenticatedStream
{
...
    // Required for client method:
    // existing public methods:
    public Task AuthenticateAsClientAsync(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken = default);
    // Existing synchronous overloads
    public virtual void AuthenticateAsClient(string targetHost);
    public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates, bool checkCertificateRevocation);
    public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection? clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation);
    // NEW overload (existing private method to be made public)
    public void AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions);

...
    // Optionally for server methods:
    // existing public method
    public Task AuthenticateAsServerAsync(SslServerAuthenticationOptions sslServerAuthenticationOptions, CancellationToken cancellationToken = default);
    // Existing synchronous overloads
    public virtual void AuthenticateAsServer(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate);
    public virtual void AuthenticateAsServer(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation);
    public virtual void AuthenticateAsServer(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation);
    // NEW overload (existing private method to be made public)
    public void AuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions);
...
}
@ManickaP ManickaP added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net.Http labels Apr 7, 2020
@ManickaP ManickaP self-assigned this Apr 7, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Apr 7, 2020
@ManickaP
Copy link
Member Author

ManickaP commented Apr 7, 2020

Ping: @dotnet/ncl @stephentoub

@scalablecory scalablecory added this to the 5.0 milestone Apr 7, 2020
@scalablecory scalablecory removed the untriaged New issue has not been triaged by the area owner label Apr 7, 2020
@davidfowl
Copy link
Member

The effects of making a synchronous HttpClient.Send 😢

@ManickaP
Copy link
Member Author

ManickaP commented Apr 8, 2020

I looked into meaningfulness of adding CancellationToken to the sync methods (i.e. adding a new API instead of making private methods public) and IMO it's worth a thought.
If you look at ProcessAuthentication it already passes CancellationToken to the AsyncSslIOAdapter. We could pass it the same way to SyncSslIOAdapter and check for cancellation before each Read/Write operation.

Though I'm not sure if this is OK with regard to the stream state. What if we leave some bytes in the stream because we do not finish reading due to cancellation? What about the state of the server if we stop in the middle of the handshake? Will both sides be able to recover (with the assumption that the client will not continue with this particular request)?

@wfurt @alnikola @davidsh @dotnet/ncl

@davidsh
Copy link
Contributor

davidsh commented Apr 8, 2020

Will both sides be able to recover?

Cancellation of an I/O operation is rarely recoverable. At the Socket layer, cancelling a pending I/O might leave the socket in a usable state. But higher-layer APIs like SslStream where cancellation is invoked usually result in the stream being unusable after that.

Do we know if canceling the async AuthenticateAsServer/Client APIs result in the stream still being usable for a subsequent authentication attempt?

@Drawaes
Copy link
Contributor

Drawaes commented Apr 8, 2020

If you have sent any data to the otherside it is highly unlikely you can do much but re-establish the tcp socket.

@wfurt
Copy link
Member

wfurt commented Apr 8, 2020

I don't think we can recover if we abort in middle of the handshake. The benefit would be ability to stop and unblock synchronous call. Since the SyncSslIOAdapter has no way how to pass it down to underline stream, it would be possible only between reading TLS frames.

If overall timeout is desirable, it may be better to set it on NetworkStream/Socket before passed to SslStream constructor. That would be than applicable to each Read() operation instead to handshake as whole.

@scalablecory
Copy link
Contributor

scalablecory commented Apr 8, 2020

I looked into meaningfulness of adding CancellationToken to the sync methods (i.e. adding a new API instead of making private methods public) and IMO it's worth a thought.

When considering cancellation here, we should look at how it realistically interacts with how someone uses SslStream. What does cancellation look like in sync HttpClient?

Are we checking for cancellation between I/Os there too? In this case making these cancellable in the same way makes some sense. This sort of best-effort cancellation isn't the best experience when e.g. a Write() hangs, but it's better than nothing.

Are we closing the socket? In this case we already handle cancellation at a lower layer and don't need it in SslStream.

@stephentoub
Copy link
Member

stephentoub commented Apr 8, 2020

Are we closing the socket? In this case we already handle cancellation at a lower layer and don't need it in SslStream.

We don't currently. But we used to:
ec22adc
We could go back to that for sync.

@karelz
Copy link
Member

karelz commented Apr 8, 2020

Triage: (notes from yesterday)

  1. We are adding "just" new overload with SslClientAuthenticationOptions - other synchronous APIs/overloads already exist -- to be clarified for full API review - DONE
  2. Should we add CancellationToken arg? - @ManickaP to explore if it make sense -- see ongoing discussion here
  3. We decided to take the server counterpart for symmetry (even if it is not strictly needed) -- the server sync overloads already exist
  4. @wfurt to check for interactions with his in-flight SslStream proposals - does it impact this decision? (likely not, but we want to have confirmation)

This was referenced Apr 9, 2020
@ManickaP
Copy link
Member Author

  1. Should we add CancellationToken arg? - @ManickaP to explore if it make sense -- see ongoing discussion here

So there are two ways which I can be done:

  1. Pass CancellationToken and check it in between sync call on underlying stream (i.e. 'best-effort' cancellation). This would mean adding new method instead of making private one public. Also it would mean another sync method with CancellationToken.
  2. Use @stephentoub solution, i.e. registering in cancellationToken and disposing the stream when cancelled. This would already worked nicely with existing exception handling. Also it would mean no CancellationToken passed to another sync method.

Personally, I like the second option better.

What about you @dotnet/ncl ?

@scalablecory
Copy link
Contributor

I think the second option is just fine. You'll want to think about where you can translate the SocketException with OperationAborted into the proper OperationCanceledException.

@ManickaP
Copy link
Member Author

Triage:

  • Cancellation: we agreed to go with the second option, i.e. disposing the underlying stream.
  • SslStream work in-flight: confirmed that there shouldn't be any issues.

The API is ready to be reviewed as-is now.

@ManickaP ManickaP added api-ready-for-review blocking Marks issues that we want to fast track in order to unblock other important work and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Apr 23, 2020
@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review labels Apr 30, 2020
@bartonjs
Copy link
Member

Looks good as proposed:

public partial class SslStream : System.Net.Security.AuthenticatedStream
{
    public void AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions);
    public void AuthenticateAsServer(SslServerAuthenticationOptions sslServerAuthenticationOptions);
}

@bartonjs bartonjs removed the blocking Marks issues that we want to fast track in order to unblock other important work label Apr 30, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Net.Security
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants