Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Add ALPN support for SslStream.#24389

Merged
Priya91 merged 12 commits intodotnet:masterfrom
Priya91:tlsextension
Oct 19, 2017
Merged

Add ALPN support for SslStream.#24389
Priya91 merged 12 commits intodotnet:masterfrom
Priya91:tlsextension

Conversation

@Priya91
Copy link
Contributor

@Priya91 Priya91 commented Oct 2, 2017

cc @stephentoub @bartonjs @geoffkizer @wfurt @karelz @Drawaes @Tratcher

fixes #23177

In this PR, only ALPN support is added. This PR does not include:

  1. CancellationToken implementation
  2. AllowRenegotiation
  3. SNI

I'll file issues separately for followup on above items at the time of merging this PR.

{
GCHandle protocols = GCHandle.FromIntPtr(arg);
byte[] server = (byte[])protocols.Target;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential for Debug.Assert if protocols.Target isn't an allocated GC handle or Target is null? Maybe actually just a null check as throwing inside call back triggered native code is never pleasant.

internal const int OPENSSL_NPN_NEGOTIATED = 1;
internal const int SSL_TLSEXT_ERR_OK = 0;
internal const int SSL_TLSEXT_ERR_NOACK = 3;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to seen native asserts for these, should there not be something like this

static_assert(PAL_X509_V_ERR_CRL_SIGNATURE_FAILURE == X509_V_ERR_CRL_SIGNATURE_FAILURE, "");

To ensure that the native and managed code never get out of sync?

int len;
SslGetAlpnSelected(ssl, out protocol, out len);

return len == 0 ? null : Marshal.PtrToStringAnsi(protocol, len);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problem, ANSI != UTF8 and if the ALPN Protocol struct is doing UTF8 default conversion you can't round trip the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code doesn't exist, you're probably looking at an older commit.

}

return buffer;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has similar encoding issues, we are now using ASCII encoding, but Protocols struct uses UTF8, and above we are decoding with PtrToAnsi?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code doesn't exist, you're probably looking at an older commit.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct you updated it after the comment so GH marks it outdated so all good now 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I had initially submitted the PR with this code, this code existed in the first commit, I had then changed this code after the api-proposal to use Readonlymemory in the later commits. You must have looked at this commit wise, instead of the file-changed wise.

internal static byte[] AlpnStringListToByteArray(IList<string> protocols)
{
int protocolSize = 0;
foreach (string protocol in protocols)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are strings now being used as the backing class? This means a conversion every time ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code doesn't exist, you're probably looking at an older commit.

@Drawaes
Copy link

Drawaes commented Oct 3, 2017

I will take a further look at work :) Good to see ALPN getting closer

internal static partial class OpenSsl
{
private static Ssl.SslCtxSetVerifyCallback s_verifyClientCertificate = VerifyClientCertificate;
private static Ssl.SslCtxSetAplnCallback s_alpnServerCallback = AplnServerSelectCallback;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly for this and the above delegates?

return OpenSslSuccess;
}

private static unsafe int AplnServerSelectCallback(IntPtr ssl, out IntPtr outp, out byte outlen, IntPtr inp, uint inlen, IntPtr arg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search/replace Apln => Alpn


return buffer;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of allocation happening in these conversion routines: enumerators, byte arrays, etc. Not to block this PR, but it'd be good subsequently to see what kind of impact that has and whether there are ways to reduce it.

byte[] result = new byte[ProtocolIdSize];
Array.Copy(ProtocolId, result, ProtocolIdSize);

return result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we allocating and copying to a new array? Is the issue that ProtocolIdSize may actually be < ProtocolId.Length? If so, how about using spans?

[MarshalAs(...)]
private byte[] _protocolId;

public Span<byte> ProtocolId => new Span<byte>(_protocolId, 0, ProtocolIdSize);

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the size can be less than 255, changed to span.

public ApplicationProtocolNegotiationExt NegotiationExtension;
public byte ProtocolIdSize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_PROTOCOL_ID_SIZE)]
public byte[] ProtocolId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about doing this as a fixed buffer instead of marshaling it as a byvalarray and forcing the extra allocation?

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class SecPkgContext_ApplicationProtocol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

struct instead of class? Or in current usage is it always going to be boxed anyway?

public delegate bool RemoteCertificateValidationCallback(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors);
public class SslServerAuthenticationOptions
{
public bool AllowRenegotiation { get { throw null; } set { } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why include AllowRenegotiation now if you're not going to implement it yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is part of the approved api.

{
if (Interop.Ssl.SslCtxSetAlpnProtos(innerContext, sslAuthenticationOptions.ApplicationProtocols) != 0)
{
throw CreateSslException(SR.net_alpn_notsupported);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design was to fail silently when ALPN wasn't supported, correct? E.g. return a null result and let the caller decide what to do. This avoids error-prone platform light-up detection in application code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This situation doesn't seem to be "isn't supported", but rather "should be supported, but broke". So it seems exceptional to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the error message is misleading?

public struct SslApplicationProtocol : IEquatable<SslApplicationProtocol>
{
private readonly byte[] _protocol;
private readonly ReadOnlyMemory<byte> _readOnlyProtocol;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why store both representations? Is ReadOnlyMemory<byte> so hard to work with that you can't use it for Equals, GetHashCode, ToString, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Equals on readonlymemory checks if the underlying array instance is the same, that's not the equality check that's needed here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking why you can't implement Equals over top of ReadOnlyMemory<byte> rather than byte[]. I don't see why you need the byte[] field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware how I can do that, I have to compare the values of the underlying byte[] for which i need to get the byte[] from readonlymemory, and looking at coreclr code, toarray() is expensive than just storing the byte[] here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should be a SequenceEqual extension ReadOnlyMemory; meanwhile you can do

public bool Equals(SslApplicationProtocol other)
{
    if (_readOnlyProtocol.Equals(other._readOnlyProtocol))
        return true;

    return _readOnlyProtocol.Span.SequenceEqual(other._protocol.Span);

    /*
    // Or instead of SequenceEqual
    ReadOnlySpan<byte> protocol = _readOnlyProtocol.Span; 
    ReadOnlySpan<byte> otherProtocol = other._readOnlyProtocol.Span; 
    
    if (protocol.Length != otherProtocol.Length)
        return false;
    
    for (int i = 0; i < protocol.Length; i++)
    {
        if (protocol[i] != other._protocol[i])
            return false;
    }

    return true;
    */
}

Copy link

@Drawaes Drawaes Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

if(_readonlyProtocol.Span.SequenceEquals(other._readonlyProtocol.Span))

you could drop the readonly if you didn't need the array around as well.

thus

if(_protocol.Span.SequenceEquals(other._protocol.Span))

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both work, the bonus is you get to drop all the null checking because they are structs.....

private readonly ReadOnlyMemory<byte> _readOnlyProtocol;
private static readonly Encoding s_utf8 = Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);

// h2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@Priya91 Priya91 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't include IANA links in code. Can reference the RFC number.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Priya91 Maybe I've missed some memo somewhere, but I don't understand why the iana.org URI isn't a thing to include in comments. That does happen to (AFAIK) be the authoritative list. The RFCs all have things like "requesting IANA to register blah blah blah"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include IANA information in comments. This makes the source code more understandable and maintainable.

_readOnlyProtocol = new ReadOnlyMemory<byte>(_protocol);
}

public SslApplicationProtocol(byte[] protocol) : this(protocol, true) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named parameters when using constants?


if (sslClientAuthenticationOptions.TargetHost.Length == 0)
{
sslClientAuthenticationOptions.TargetHost = "?" + Interlocked.Increment(ref s_uniqueNameInteger).ToString(NumberFormatInfo.InvariantInfo);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea why this is here? I know it was in the old version, bit it's pretty strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. @davidsh ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. @davidsh ?

I don't know. The code is very old. It's trying to prevent an empty string from being used as TargetHost, so it makes one up.

public override int GetHashCode()
{
int hash = 0;
for (int i = 0; i < _protocol.Length; i++)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing null check

AuthenticateAsClient(options, CancellationToken.None);
}

private void AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this take a CancellationToken? And why does this method exist if it's private?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancellationtoken is not required, but this method takes in options bag and all other methods call this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one caller, correct? Why not collapse this method into the one above?


internal static byte[] GetNegotiatedApplicationProtocol(SafeDeleteContext context)
{
// OSX SecureTransport does not export APIs to support ALPN, no-op.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's available on newer versions, correct? Will there be a way to light that up?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to just P/Invoke into the function; though appropriate guards would need to be put in place for detecting suitability. I'd prefer it was shimmed, but sometimes needs must.

@Tratcher
Copy link
Member

Tratcher commented Oct 3, 2017

@halter73 @CesarBS @davidfowl

@Priya91
Copy link
Contributor Author

Priya91 commented Oct 3, 2017

I don't get the native linux build failure locally, and the alpn tests pass as well.

@Drawaes
Copy link

Drawaes commented Oct 3, 2017

Might be worth checking the build is using the correct version of OpenSSL 1.02 because 1.01 is missing those methods that it is complaining about

@Priya91
Copy link
Contributor Author

Priya91 commented Oct 3, 2017

The alpn tests failing with cert error could be related to this issue #23341 I tried @bartonjs workaround to use EphemeralKeySet, I get same invalid cert error. @bartonjs Any ideas how to fix this?

@bartonjs
Copy link
Member

bartonjs commented Oct 4, 2017

I tried @bartonjs workaround to use EphemeralKeySet, I get same invalid cert error.

Do you get the same intermittent error, or a now-always error? If it's "always" it's possible that something in the SChannel stack doesn't work well with ephemeral private keys. If that's the case, the remaining option would be to strip the key names out of the PFX files and resubmit them, then the keys will always be named based on a GUID created at the time of import.

If it's an intermittent problem even with ephemeral keys, then it's not key collision, but must be something else going on with SChannel.

if (remoteCertRequired)
if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired)
{
Debug.Assert(isServer, "isServer flag should be true");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean if RemoteCertRequired is true and IsServer is false? The assert here took care of identifying that as an invalid state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This state will not be hit with the current implementation. Previously, these internal APIs were passing the RemoteCertRequired=false for client and RemoteCertRequired=true for server. This is in direct contradiction with external meaning of RemoteCertRequired, where for client it is always true, and server is got from RemoteCertRequired parameter on the authenticate methods. Since we don't flip these values internally anymore, it doesn't make sense to have this assert.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I get it. You've changed the client to be true for this instead of false. Really the client is N/A, and that's what the assert was guarding (that it made no sense for a client to change that value during the handshake).

This seems fine, now that I understand the change. There's a mild perf gain (ditching the &&) for having the client use false, but it's not something worth adding confusion over.

{
if (Interop.Ssl.SslCtxSetAlpnProtos(innerContext, sslAuthenticationOptions.ApplicationProtocols) != 0)
{
throw CreateSslException(SR.net_alpn_notsupported);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This situation doesn't seem to be "isn't supported", but rather "should be supported, but broke". So it seems exceptional to me.

foreach (SslApplicationProtocol protocol in applicationProtocols)
{
buffer[offset++] = (byte)(protocol.Protocol.Length);
Array.Copy(protocol.Protocol.ToArray(), 0, buffer, offset, protocol.Protocol.Length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ToArray()? Using the Span property avoids the extra allocation and extra extra copy.

protocol.Protocol.Span.CopyTo(buffer.Slice(offset));

BadBinding = unchecked((int)0x80090346),
DowngradeDetected = unchecked((int)0x80090350)
DowngradeDetected = unchecked((int)0x80090350),
ApplicationProtocolMismatch = unchecked((int)0x80090367)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we have a rule for (or, hopefully not, against) it, but I always end my enums with a , so that new members add one line of diff, not two.

return SSL_CTX_set_alpn_protos(ctx, protos, protos_len);
}

extern "C" void CryptoNative_SslGet0AlpnSelected(SSL* ssl, const unsigned char** protocol, unsigned int* len)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions all need to use the correct type identifiers in their signatures.

  • char* is only used for string. byte[] => uint8_t*.
  • int (C#) => int32_t

So they might be

// If the void*'s here can't be void* then whatever type you think they are should be encoded.  I'm guessing uint8_t.
extern "C" int32_t CryptoNative_SslSelectNextProto(void** out, uint8_t* outlen, const uint8_t* server, uint32_t server_len, const void* client, uint32_t client_len);

extern "C" int32_t CryptoNative_SslCtxSetAlpnProtos(SSL_CTX* ctx, const uint8_t* protos, uint32_t protos_len);

extern "C" void CryptoNative_SslGet0AlpnSelected(SSL* ssl, const uint8_t** protocol, uint32_t* len);

if (remoteCertRequired)
if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired)
{
Debug.Assert(isServer, "remoteCertRequired should not be set for client-side handshakes");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked this question in the OpenSSL version, but I'll ask it again: why are we removing the assert? If RemoteCertRequired can only be set when IsServer is true, then the assert is good (because we can remove the && operation). If there's a state where RemoteCertRequired can be true and IsServer is false, then what do we want that behavior to be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As explained previously.

context,
Interop.SspiCli.ContextAttribute.SECPKG_ATTR_APPLICATION_PROTOCOL) as Interop.SecPkgContext_ApplicationProtocol;

if (alpnContext == null || alpnContext.NegotiationExtension != Interop.ApplicationProtocolNegotiationExt.ALPN || alpnContext.NegotiationStatus != Interop.ApplicationProtocolNegotiationStatus.Success)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where NegotiationExtension isn't ALPN that isn't considered exceptional?

If I ask SChannel for the ALPN data and it gives me back a certificate, I'm certainly upset.

But maybe this is for the ALPN vs NPN thing? If so, a comment seems warranted.

using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
{
SslApplicationProtocol LongLength = new SslApplicationProtocol(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new string('a', 256) seems clearer. (Or whatever number is accurate)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol yes

Assert.True(left == right);
Assert.False(left != right);

if (!left.Protocol.IsEmpty && !right.Protocol.IsEmpty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if doesn't belong. if == is true then GetHashCode() should match. And GetHashCode() should never throw.

Assert.True(left != right);
Assert.False(left == right);

if (!left.Protocol.IsEmpty && !right.Protocol.IsEmpty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if doesn't belong.

@Priya91
Copy link
Contributor Author

Priya91 commented Oct 4, 2017

Do you get the same intermittent error, or a now-always error? If it's "always" it's possible that something in the SChannel stack doesn't work well with ephemeral private keys. If that's the case, the remaining option would be to strip the key names out of the PFX files and resubmit them, then the keys will always be named based on a GUID created at the time of import.

Repro status of this error:

  1. Windows local box - no repro
  2. Windows CI box - always repro only for this test method that has Theory on it.
  3. Linux Azure box - always repro
  4. Linux local box - repro only if all tests run together, running a single alpn test - no repro.

serverOptions.ServerCertificate = certificate;
serverOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;

Assert.ThrowsAsync<InvalidOperationException>(() => { return client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None); });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is common in CoreFX tests, but shouldn't you assert the exception message to make sure this is actually failing for the reason it is expected to fail?


[Fact]
[PlatformSpecific(~TestPlatforms.OSX)]
public void SslStream_StreamToStream_Alpn_Fail()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail for what reason? That should be clearer in the test name.

{
public class SslAuthenticationOptionsTests
{
private SslClientAuthenticationOptions _clientOptions = new SslClientAuthenticationOptions();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly?

@Priya91
Copy link
Contributor Author

Priya91 commented Oct 6, 2017

Update: I'm investigating the test failures, and working on updating the CI images to openssl 1.0.2. Please bear with no activity here till I sort those issues. Thanks!

@Priya91 Priya91 changed the title Add ALPN support for SslStream. [WIP] Add ALPN support for SslStream. Oct 13, 2017
@Priya91 Priya91 changed the title [WIP] Add ALPN support for SslStream. Add ALPN support for SslStream. Oct 18, 2017
{
get => _enabledSslProtocols;
set => _enabledSslProtocols = value;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServerCertificate and EnabledSslProtocols could both be auto-properties now, if you wanted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabledsslprotocols has a default, left that as if, changed the rest to auto properties.

alpnContext.ProtoNegoExt != Interop.ApplicationProtocolNegotiationExt.ALPN ||
alpnContext.ProtoNegoStatus != Interop.ApplicationProtocolNegotiationStatus.Success)
{
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the situation where we expect ProtoNegoExt to not be ALPN, or ProtoNegoStatus to not be success? And should those cases be exceptions? (Comment seems to have been lost due to formatting fixes)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add comment, NPN is an earlier version of alpn. Here, the code is querying the context for information about the alpn protocol negotiated, and the win32 api should return object with structure SecPkgContext_ApplicationProtocol, this is just checking if the object returned is indeed of this type and the context of the alpn operation was successful and of type alpn. If there was some internal error that resulted in a wrong object or object with incorrect context returned we'll just return null.

{
fixed (byte* p = ProtocolId)
{
return new Span<byte>(p, ProtocolIdSize).ToArray();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this using fixed instead of just new Span<byte>(ProtocolId, 0, ProtocolIdSize).ToArray()?

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class SecPkgContext_ApplicationProtocol
[StructLayout(LayoutKind.Sequential)]
internal unsafe class SecPkgContext_ApplicationProtocol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need unsafe at the class scope? A local unsafe block, or member-level, seems more appropriate...

int index = 0;
for (int i = 0; i < applicationProtocols.Count; i++)
{
pBuffer[index++] = (byte)applicationProtocols[i].Protocol.Length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug.Assert(applicationProtocols[i].Protocol.Length < 255)? (or <=, if appropriate)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is done, in the code just before in the same function, so a debug assert is not required here.

{
return SSL_select_next_proto(out, outlen, server, server_len, client, client_len);
}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the else keyword and else braces are redundant, since the method either #defines out or hits an if+return.

But this is fine if you like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the code pattern used elsewhere in the crypto shim.

for (int i = 0; i < _readOnlyProtocol.Length; i++)
{
hash = ((hash << 5) + hash) ^ _protocol[i];
hash1 = ((hash1 << 5) + hash1) ^ _readOnlyProtocol.Span[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're reading this property a lot. Should it be saved outside the loop?

public ApplicationProtocolNegotiationExt ProtocolExtenstionType;
public short ProtocolListSize;

public static unsafe byte[] ToByteArray(List<SslApplicationProtocol> applicationProtocols)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub, should we also have a method that takes in a span and puts the bytes into it, i.e. TryCopyTo(Span buffer)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is internal. Is the equivalent exposed publicly somewhere?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's exposed, but: a) maybe we should expose it, b) maybe the internal code that uses it would be written with less copies/allocations if we had such internal API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For b), at that point it's an implementation detail and we should do whatever's most efficient, assuming it matters.

For a), what would it be used for?

protocols.ProtocolExtenstionType = ApplicationProtocolNegotiationExt.ALPN;
protocols.ProtocolListSize = (short)protocolListSize;

Span<byte> pBuffer = new byte[protocolListSize];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: pBuffer kind of implies that the variable is a pointer. it might be better to call it protocolsBuffer.

@Priya91 Priya91 merged commit 57b4664 into dotnet:master Oct 19, 2017
@Priya91 Priya91 deleted the tlsextension branch October 19, 2017 18:08
Ssl.SslCtxSetVerify(innerContext, s_verifyClientCertificate);

//update the client CA list
UpdateCAListFromRootStore(innerContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method call does not match the behavior on Windows.
Why does it exist in this manner? See also: https://github.com/dotnet/corefx/issues/23938

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ayende This code is not related to the work done in this PR. Please file separate issue if there's a bug.

@Perksey
Copy link
Member

Perksey commented Jul 15, 2018

Documentation? (dotnet/docs#6469)

@karelz
Copy link
Member

karelz commented Jul 15, 2018

@DylanFPS yeah, we should add docs - in the meantime, check out the API shape in #23177, it is fairly straightforward - use new SslStream AuthenticateAsClient(Server)Async to pass SslClient(Server)AuthenticationOptions and provide ApplicationProtocols. SslStream.NegotiatedApplicationProtocol is the result of negotiation.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposed SslStream additions for ALPN