Runtime: Adições SslStream propostas para ALPN

Criado em 12 ago. 2017  ·  268Comentários  ·  Fonte: dotnet/runtime

Última Proposta

https://github.com/dotnet/corefx/issues/23177#issuecomment -332995338

`` `c #
namespace System.Net.Security
{
classe pública parcial SslStream
{
public Task AuthenticateAsServerAsync (opções de SslServerAuthenticationOptions, cancelamento de Cancelamento deoken) {}
public Task AuthenticateAsClientAsync (opções SslClientAuthenticationOptions, cancelamento de Cancelamento deoken) {}

public SslApplicationProtocol NegotiatedApplicationProtocol { get; }

}

public class SslServerAuthenticationOptions
{
public bool AllowRenegotiation {get; definir; }
public X509Certificate ServerCertificate {get; definir; }
public bool ClientCertificateRequired {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public X509RevocationMode CheckCertificateRevocation {get; definir; }
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback {get; definir; }
public EncryptionPolicy EncryptionPolicy {get; definir; }
}

public class SslClientAuthenticationOptions
{
public bool AllowRenegotiation {get; definir; }
public string TargetHost {get; definir; }
public X509CertificateCollection ClientCertificates {get; definir; }
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public X509RevocationMode CheckCertificateRevocation {get; definir; }
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback {get; definir; }
public EncryptionPolicy EncryptionPolicy {get; definir; }
}

public struct SslApplicationProtocol: IEquatable
{
public static readonly SslApplicationProtocol Http2;
public static readonly SslApplicationProtocol Http11;
// A adição de outros valores IANA é deixada com base na opinião do cliente.

public SslApplicationProtocol(byte[] protocol) { }

public SslApplicationProtocol(string protocol) { } 

public ReadOnlyMemory<byte> Protocol { get; }

public bool Equals(SslApplicationProtocol other) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public override string ToString() { } 
public static bool operator ==(SslApplicationProtocol left, SslApplicationProtocol right) { }
public static bool operator !=(SslApplicationProtocol left, SslApplicationProtocol right) { }

}
}

# Previous Proposal

Change log:
* Updated = Removed results object to keep new objects down
* Updated = Meeting notes
* Updated = Removed string overload as there is no clear way in code to tell the user that it's utf-8 and only adds a potential trap
* Updated = Put that string overload back under protest, but decision was made in a review meeting

References dotnet/runtime#23107 

## Rationale

Server Name Indication and Application Layer Protocol Negotiation are two TLS extensions that are missing currently from SslStream. They are needed urgently to support Http/2 (ALPN) and the ability to run Kestrel as an edge server (SNI). There are also many other customer applications that require this, including Clients being able to use HTTP/2 (Mananed HttpClient has an Http/2 support open issue). 

These have been outstanding for a long period of time, for a number of reasons. One major reason is that any change will cause an increase in overloading of the Authenticate methods which have become unwieldy and are brittle when adding new options. 

There will be more options coming with other TLS extensions available now (max fragment size for restricted memory clients for instance) and having a mechanism to add these without major API changes seems like a sensible change. 

## Proposed API Change

Originally I suggested overloading the Authenticate methods, however discussions in dotnet/runtime#23107 have changed my mind. Thanks to <strong i="15">@Tratcher</strong> for this concept

``` csharp
public partial class SslStream
{
   public Task AuthenticateAsServerAsync(SslServerAuthenticationOptions options, CancellationToken cancellation) { }
   public Task AuthenticateAsClientAsync(SslClientAuthenticationOptions options, CancellationToken cancellation) { }

   public SslApplicationProtocol NegotiatedApplicationProtocol {get;}
}

public class SslServerAuthenticationOptions
{
   public bool AllowRenegotiation { get; set; }
   public X509Certificate ServerCertificate { get; set; }
   public bool ClientCertificateRequired { get; set; }
   public SslProtocols EnabledSslProtocols { get; set; }
   public X509RevocationMode CheckCertificateRevocation { get; set; }
   public IList<SslApplicationProtocol> ApplicationProtocols { get; set; }
   public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
   public EncryptionPolicy EncryptionPolicy { get; set; }
}

public class SslClientAuthenticationOptions
{
   public bool AllowRenegotiation { get; set; }
   public string TargetHost { get; set; }
   public X509CertificateCollection ClientCertificates { get; set; }
   public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
   public SslProtocols EnabledSslProtocols { get; set; }
   public X509RevocationMode CheckCertificateRevocation { get; set; }
   public IList<SslApplicationProtocol> ApplicationProtocols { get; set; }
   public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
   public EncryptionPolicy EncryptionPolicy { get; set; }
}

public struct SslApplicationProtocol : IEquatable<SslApplicationProtocol>
{
    public static readonly SslApplicationProtocol Http2;
    public static readonly SslApplicationProtocol Http11;
    // Adding other IANA values, is left based on customer input.

    public SslApplicationProtocol(byte[] protocol) { }

   public SslApplicationProtocol(string protocol) { } // assumes utf-8 and public override 

    public ReadOnlyMemory<byte> Protocol { get; }

    public bool Equals(SslApplicationProtocol other) { }
    public override bool Equals(object obj) { }
    public override int GetHashCode() { }
    public override string ToString() { } // For debugger. utf-8 or a string representation of the raw bytes. E.g. "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31"
    public static bool operator ==(SslApplicationProtocol left, SslApplicationProtocol right) { }
    public static bool operator !=(SslApplicationProtocol left, SslApplicationProtocol right) { }
}

Notas de reunião 22 de setembro de 2017

  1. Adicione ToString () a SslApplicationProtocol para obter a versão da string dos bytes.
  2. Adicione string ctor a SslApplicationProtocol para usabilidade, isso assumirá que é string utf8.
  3. ReadOnlyMemorynão é imutável, precisamos copiar os bytes no ctor, então pegando um byte [] em vez de ReadOnlyMemory

Notas de reunião 5 de setembro de 2017

  1. Use a proposta de API atualizada acima com pacotes de opções nos métodos de autenticação.
  2. Introduza um tipo ApplicationProtocol para que os valores ALPN possam ser definidos corretamente como bytes brutos, tenham constantes compartilhadas e tenham operadores de igualdade.
  3. Proíba a mistura de chamadas entre os construtores antigos e os novos métodos. Apenas os construtores mínimos tomando o fluxo interno e o bool de propriedade serão suportados.
  4. Ainda há alguma discussão pendente sobre a abordagem de fábrica.

Notas Adicionais

  1. Isso significa que opções futuras podem ser adicionadas sem causar problemas de compatibilidade binária (aumentando propriedades em classes concretas em vez de alterar sobrecargas)
  2. Os métodos não assíncronos não devem ser suportados para os novos métodos, pois é uma operação assíncrona nos bastidores, portanto, ocultar os threads vai contra o pensamento da estrutura atual. Os consumidores podem envolver os métodos assíncronos com bloqueio, se assim o desejarem (consulte HttpClient moderno)
  3. Eu coloquei o fragmento máximo para o cliente, mas estou feliz por ter descartado isso, se for um obstáculo
  4. Os tokens de cancelamento existem para ambos os métodos de acordo com o pensamento da estrutura atual
  5. ValueTask não foi considerado porque haverá muito poucas vezes em que isso não causará operações assíncronas, mas se o novo padrão for usar ValueTask, tudo bem
  6. Uma lista estática de Http 1.1, Http / 2 e Http / 2 sobre TLS deve ser fornecida para evitar que os usuários tenham que procurá-la.
  7. Métodos auxiliares nos resultados de autenticação devem ser fornecidos para que os usuários não precisem pesquisar quais são as representações de string. Neste caso, adicionei um para IsHttp2.

Possíveis problemas de implementação

Existem agora vários parâmetros que podem ser modificados durante a criação de uma conexão. Um deles é o dicionário de certificados. Se o consumidor alterar isso reutilizando acidentalmente as opções de configuração para uma conexão configurada de forma diferente, você poderá ter um problema de segurança. Uma opção é ter um construtor, mas isso é desaprovado como um padrão de API e, em vez disso, é usado principalmente em bibliotecas / estruturas de nível superior (ASP.NET, por exemplo). Isso pode ser resolvido por meio de um instantâneo interno das opções, mas precisará ser considerado em qualquer modelagem de ameaça.

Exemplo de uso

var options = new ServerAuthenticationOptions()
{
    EnabledProtocols = SslProtocols.Tls12 | SslPRotocols.Tls11,
    ApplicationProtocols = new List<ApplicationProtocol>()
    {
         ApplicationProtocol.Http2,
         ApplicationProtocol.Http11
    }
};
var secureStream = new SslStream(innerStream);
await secureStream(options, token);

Referências

dotnet / runtime # 17677 SNI
dotnet / runtime # 15813 ALPN
dotnet / runtime # 23107 Proposta de API anterior
RFC 7301 ALPN
SNI RFC 3546 e fragmento máximo
Ids de protocolo IANA ALPN

[EDIT] Formatação atualizada por @karelz

api-approved area-System.Net.Security

Comentários muito úteis

@karelz Sim, eu quis dizer que a string seria convertida em bytes usando a codificação utf-8.

Essa sobrecarga de string não é uma parte crítica da API e não merece o tempo já gasto nela. Ele fornece uma ligeira melhoria na usabilidade em relação à outra sobrecarga, só isso. Mantenha-o ou exclua-o, mas de qualquer forma, é hora de seguir em frente.

Todos 268 comentários

cc @geoffkizer @ Priya91 @wfurt

@Drawaes Qual é a motivação por trás da adição de

Porque você também precisa de outra sobrecarga para SNI ou sobrecargas. Portanto, agora você tem uma matriz maior. O problema anterior referenciado dotnet / runtime # 23107 estava originalmente indo por esse caminho, no entanto, o número de sobrecargas está se tornando excessivo. Existem mais extensões que ainda não se manifestaram ou foram consideradas com o TLS 1.2 e o TLS 1.3 ao virar da esquina.

Considere os dados ZeroRtt, que devem ser apresentados como uma API que precisará de outra sobrecarga. Eu acho que a questão é quantas sobrecargas são demais?

Eu ficaria feliz se os tipos de retorno fossem descartados e talvez os argumentos do cliente e do servidor fossem mesclados (não o melhor, mas hey), mas apenas adicionar protocolIds não cobre SNI. Ele apenas chuta a lata um pouco mais adiante. Também torna difícil para os usuários finais uma lista de protocolos bruta um pouco hostil sem nenhum método auxiliar.

Lendo as RFCs, esta é a proposta que tenho,

`` `c #
namespace System.Net.Security
{
estrutura parcial pública TlsExtension
{
public TlsExtension (TlsExtensionType extensionType, SpanextensionData) {lance nulo; }
public TlsExtensionType ExtensionType {get => throw null; }
Span públicoExtensionData {obter => lançar nulo; }
}
public enum TlsExtensionType
{
ServerName = 0,
Alpn = 16
}
classe pública parcial SslStream: AuthenticatedStream
{
public virtual void AuthenticateAsClient (string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {lance nulo; }
public virtual void AuthenticateAsServer (System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {lance nulo; }
public virtual Task AuthenticateAsClientAsync (string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {lance nulo; }
public virtual Task AuthenticateAsServerAsync (System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {lance nulo; }
}
}

For the ExtensionData, we can provide structure to it by maybe having static methods on TlsExtension like,

```c#
public partial struct TlsExtension
{
    public static Span<byte> GetAlpnExtensionData(IList<string> protocols) { throw null; }
    public static Span<byte> GetServerNameExtensionData(IList<string> serverNames) { throw null; }
}

cc @geoffkizer @stephentoub @davidsh @ caesar1995 @CIPop

@CIPop Visto que você já examinou a implementação do Alpn, você tem alguma ideia sobre como devemos definir as APIs aqui.

Espere, mas só posso adicionar uma única extensão aqui? E se eu quiser ALPN e SNI (que é um caso muito comum no lado do servidor). Além disso, como faço para fornecer vários certificados? O uso normal do SNI é este

O cliente disse que estou me conectando a www.mycooldomain.com

Servidor diz que está bem espero que seja www.mycooldomain.com ou www.mylesscooldomain.com , vendo como você escolheu o certificado primeiro I deve fornecer um para você.

Você precisaria de um (xxxxxxxxxx, params TlsExtension [] extensions)

E se você fizer isso, agora você evitará quaisquer sobrecargas futuras e terá problemas. E ainda não fornece o mapeamento ServerName -> Certificate.

Além disso, isso pode se tornar uma coisa, dotnet / runtime # 22507 se houvesse um pacote de opções, ele poderia ser adicionado facilmente, com uma sobrecarga, seria bagunçado.

Estou um pouco preocupado com o fato de que a abordagem do saco de opções está transformando um acréscimo de recurso relativamente restrito (ALPN) em um negócio maior (corrigir a API SslStream).

Espere, mas só posso adicionar uma única extensão aqui?

As sobrecargas podem ser feitas para usar um IListpara esse caso.

Além disso, como faço para fornecer vários certificados?

Não tenho certeza de como resolver isso com o design atual, deixe-me pensar mais a respeito. Terei que investigar como o openssl / schannel fornece extensões Tls de suporte e ver se podemos emular esse design. Concordo que, com a adição de mais extensões, devemos projetar uma API que seja extensível.

Em vez de agrupar todas as opções em um novo pacote de opções, não seria melhor identificar o conjunto específico de configurações necessárias para cada extensão com suporte de Sslstream e adicionar uma propriedade para ela na estrutura TlsExtension? Portanto, o TlsExtension conterá todas as configurações necessárias para executar essa extensão no handshake.

Então você quer um OptionsBag, mas deixe as opções atuais fora dele. Claro que é uma possibilidade. Se você quiser entender SChannel e OpenSsl para SNI no lado do servidor, existem duas abordagens diferentes (é claro que são diferentes: P)

OpenSsl Você fornece um retorno de chamada. Quando o SNI é detectado, ele chama o retorno de chamada. Com seu objeto SSL, você pode ler o SNI dos parâmetros de retorno de chamada e "trocar" o SSL_CTX (contexto) ao qual ele está anexado (portanto, você normalmente cria e agrupa os contextos com todos os certificados iniciais, o que, a propósito, é algo que deveria ser feito de qualquer maneira, mas isso é um assunto diferente para outro dia).

Para SChannel, você fornece uma lista de certificados ao criar o objeto de autenticação, em vez de um, e ele seleciona com base no SNI.

Para ALPN, você fornece a extensão para Schannel como um token buffer extra. Você basicamente escreve por si mesmo. Depois, há uma propriedade que você pode inspecionar para encontrar a que foi combinada.

Para OpenSsl, você define os protocolos mais uma vez e, no lado do servidor, obtém novamente um retorno de chamada.

Portanto, não tenho certeza se a implementação realmente nos ajuda aqui, basicamente para cada nova extensão, ela é codificada manualmente em OpenSsl e eles adicionam métodos / callbacks específicos para eles.

@geoffkizer em sua resposta, você ignorou o SNI, o que é muito importante, por uma série de razões (eu tenho algumas internas), mas @Tratcher também dará a você algumas, tenho certeza.

Além disso, como uma coisa final, SslStream não tem um token de cancelamento, o que é um pouco lixo, apertos de mão podem ser facilmente travados por um cliente e hoje não há maneira de pará-lo a não ser matar o fluxo subjacente, então o aplicativo superior precisa acesso ao fluxo da rede.

Atualizado para descartar resultados.

Portanto, não tenho certeza se a implementação realmente nos ajuda aqui, basicamente para cada nova extensão, ela é codificada manualmente em OpenSsl e eles adicionam métodos / callbacks específicos para eles.

O SslStream usa openssl e schannel, para quaisquer APIs que expormos, precisamos ser capazes de conectá-lo a essas camadas de amigos. Portanto, vale a pena pensar sobre a implementação aqui e obter ideias de frameworks já usados

Claro que concordo, estava apenas apontando que ambos são diametralmente opostos, infelizmente. A vida seria muito fácil de outra forma :)

Para sua informação, uma investigação mais aprofundada parece que você pode fornecer um retorno de chamada para SNI no SChannel. O que significa que, se um retorno de chamada surgir no SslStream, o cenário de algo como Let's Encrypt fornecendo um certificado dinâmico é possível para SslStream e OpenSsl. Portanto, uma lista ou um retorno de chamada seria o ideal; se apenas um pudesse ser fornecido, o retorno de chamada seria preferível.

parece que você pode fornecer um retorno de chamada para SNI no SChannel

Você pode fornecer um link para a API para isso?

@Drawaes Não estou ignorando SNI ou cancelamento. Estou simplesmente observando que o único recurso obrigatório aqui para o 2.1 é o ALPN.

Dito isso, @stephentoub e eu conversamos um pouco hoje e concordamos que a abordagem do saco de opções aqui parece a certa, apesar do trabalho adicional e dos riscos associados a ela.

Acho que seria útil dividir esta proposta em algumas partes diferentes:

(1) Novas APIs AuthenticateAsClient / Server que aceitam pacotes de opções e um token de cancelamento, bem como a definição dos pacotes de opções que correspondem às APIs existentes. Devemos ser claros sobre os princípios usados ​​aqui. Acredito que planejamos pegar args do construtor e args do método e colocá-los no pacote de opções. Isso é correto?
(2) Proposta de extensão deste modelo para ALPN
(3) Proposta de extensão deste modelo para SNI

editado para adicionar:
(4) Proposta para estender este modelo para manuseio de MaxFragment

   public string ServerIndicationName {get;}
   public string ProtocolId {get;}

Eu sugeriria:
"ServerNameIndication" (ou mesmo apenas "ServerName")
"ApplicationProtocolName"

   public X509Certificate ServerCertificate { get; set; }
   public IDictionary<string, X509Certificate> ServerCertificates { get; set; }

Com base na discussão acima, parecia que estávamos nos movendo em direção a um modelo de retorno de chamada aqui. Isso é correto? O que isso parece?

    public SslProtocols? EnabledSslProtocols { get; set; }
    public EncryptionPolicy? EncryptionPolicy { get; set; }

Eles não devem ser anuláveis; eles devem apenas ter padrões apropriados.

Uma lista estática de Http 1.1, Http / 2 e Http / 2 sobre TLS deve ser fornecida para evitar que os usuários tenham que procurá-la

Devemos defini-los explicitamente como parte da proposta ALPN.

Precisamos realmente de http2 sem TLS? Existem outros nomes de protocolo definidos que devemos incluir?

Uma opção é ter um construtor, mas isso é desaprovado como um padrão de API e, em vez disso, é usado principalmente em bibliotecas / estruturas de nível superior (ASP.NET, por exemplo). Isso pode ser resolvido por meio de um instantâneo interno das opções, mas precisará ser considerado em qualquer modelagem de ameaça.

Aqui está minha sugestão: faça com que esses objetos congelem ao usar.

Para opções como RemoteCertificateValidationCallback, que posso especificar hoje no construtor, o que acontece se eu passar algo para o construtor, mas deixar esse valor nulo no pacote de opções? Usamos o valor do saco de opções (nulo) ou usamos o valor do construtor? Da mesma forma para outros argumentos do construtor.

Ok, estou bem em dividir SNI e ALPN para ser separado para a troca de bolsa de opções. Vou lançar duas questões secundárias para eles e responder às perguntas específicas lá para eles e discutir apenas o conceito de sacola de opções aqui.

Como o manifesto de congelamento ao usar? Existe um sinalizador interno e se você tentar definir algo depois disso ele joga? Em caso afirmativo, qual é a exceção apropriada aqui? E se os certificados acabarem sendo uma lista ou dicionário? Temos que congelar a lista / dicionário também ... como isso é feito? (Eu gosto da ideia, mas não tenho certeza de como ela é implementada).

A outra opção é ter um hashcode ou comparação que verifica as configurações + os certificados etc (objetos filho) e verifica um cache interno. Se não existir, duplique e coloque-o lá. Já existe um cache de credenciais interno.

Você poderia ref contar simplesmente para remover itens. Então, em um cliente não seria muito trabalhoso e no servidor raramente cairia para zero, a menos que seu servidor não esteja fazendo muito, caso em que uma alocação de classe ou duas não importa?

Pensamentos? Prefiro esclarecer alguns detalhes antes de atualizar a proposta novamente.

Por que é um problema ter o pacote de opções mutável? Não é diferente de qualquer outra coisa: você dá isso para a API, e a API espera que esteja em um estado consistente durante a operação ... se você mudar enquanto a operação está em andamento, isso é um bug, não diferente de qualquer outro caso onde você altera algo enquanto um método chamado depende disso (por exemplo, os buffers passados ​​para leitura / gravação). Se quiser tratá-lo como imutável e nunca alterá-lo depois de criado, você pode. Se você quiser tratá-lo como mutável, de forma que possa modificá-lo entre os usos, você pode.

Como o manifesto de congelamento ao usar? Existe um sinalizador interno e se você tentar definir algo depois disso ele joga? Em caso afirmativo, qual é a exceção apropriada aqui?

Sim. InvalidOperationException. Isso é o que HttpClient / HttpClientHandler faz para suas configurações. Consulte https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L589

E se os certificados acabarem sendo uma lista ou dicionário? Temos que congelar a lista / dicionário também ... como isso é feito? (Eu gosto da ideia, mas não tenho certeza de como ela é implementada).

Poderíamos fazer uma lista / dicionário congelável, se necessário.

Eu estou bem em apenas permitir que seja mutável também. Parece ser a abordagem mais fácil.

Para o construtor vs parâmetros de bolsa de opção. Uma exceção deve ser lançada se eles forem fornecidos no construtor e as opções forem usadas. Ou jogue apenas se forem fornecidos no construtor e diferentes, ou pegue o mais restritivo (em seu exemplo, use o retorno de chamada, não o nulo).

No entanto, estou inclinado a jogar ... puramente do lado "é segurança". Menos ambigüidade e menos permutações e adivinhação de regras costumam ser mais seguras. Novamente, que exceção é apropriada aqui?

Estou pensando muito sobre como você precisará usar as opções ... no momento, não consigo ver um problema imediato além do desempenho interno (você pode fazer um único ctx e reutilizar continuamente no openssl), mas eu acho que pode ser tratado internamente. Então, por enquanto, vamos deixar o congelamento em banho-maria. É mutante :)

Presumo que apenas adicionar coisas como o nome do servidor negociado de protocolo etc. como propriedades está bom em vez de um objeto de resultados. (Prefiro as propriedades mas quero verificar).

Esse é o padrão hoje, certo? Eu estou bem com isso.

Pacote de opções parece bom, e ter esse pacote de opções específico para TlsExtension torna a nova API menos confusa sobre o que pretende fazer. Por enquanto, mantenha o pacote de opções com suporte apenas para as configurações necessárias para alpn e sni, como e quando novas extensões tls precisarem ser suportadas, esse pacote pode ser estendido para adicionar novas propriedades. Se houver uma solicitação real do usuário para estender o pacote de opções para as configurações antigas, então certamente podemos estender o apis.

`` `c #
namespace System.Net.Security
{
estrutura parcial pública TlsExtensionConfig
{
public TlsExtension (TlsExtensionType extensionType) {throw null; }
public TlsExtensionType ExtensionType {get => throw null; }
// suporta sni do lado do servidor
public IDictionaryServerCertificates {get; definir; }
// suporta sni do lado do cliente - isso é necessário, uma vez que targetHost já é um parâmetro em
// autenticar APIs?
string pública ServerName {get; definir; }
// suporte alpn
IList públicoProtocolos de aplicativos {get; definir; }
}

[Flags] // need to maintain mapping to rfc values.
public enum TlsExtensionType
{
    ServerName = 1,
    Alpn = 2
}

public partial class SslStream : AuthenticatedStream
{
    // alpn protocol chosen during handshake
    public string ApplicationProtocol { get ; }
    // On client - targetHost, on server - current servername requested by client.
    public string ServerName { get; }

    // Authenticate APIs - without cancellation (separate issue).
    public virtual void AuthenticateAsClient(string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, TlsExtensionConfig tlsExtension) { throw null; }
    public virtual void AuthenticateAsServer(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, TlsExtensionConfig tlsExtension) { throw null; }
    public virtual Task AuthenticateAsClientAsync(string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, TlsExtensionConfig tlsExtension) { throw null; }
    public virtual Task AuthenticateAsServerAsync(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, TlsExtensionConfig tlsExtension) { throw null; }
}

}
`` `

  1. É feio
  2. Está adicionando mais bagunça a algo que deveria ter sido um saco de opções um tempo atrás
  3. Adicionar um pacote de opções para algumas, mas não todas as opções, é confuso
  4. Se as outras opções não forem movidas agora ... elas nunca serão

Se eu aprendi alguma coisa se você mudar os conceitos e dar esse salto, é melhor limpar todos os pedaços de uma vez, ou você sempre terá algumas opções lá e outras em outro lugar. Quando há mais 5 ou seis opções de configuração em SslStream e disponíveis, agora você tem 1/2 sua configuração em algum lugar e 1/2 em outro lugar.

Além disso, se você torná-lo específico para TlsExtension, e se adicionar uma opção que não seja um TlsExtension. Digamos que tenhamos a capacidade de especificar as cifras (um recurso frequentemente solicitado) e não seja uma extensão. Agora temos a discussão "vamos sobrecarregá-lo ou adicioná-lo a um pacote de opções para extensões, mas não é uma extensão".

Eu realmente gosto da ideia de todas as opções no pacote de opções, os métodos de autenticação para que SÓ suportam assíncrono e todos tendo cancelamento. Não é nada confuso, porque se você estiver usando o "jeito antigo" e não quiser ou precisar de coisas novas, você simplesmente não vai conseguir, apenas continue fazendo o que sempre fez.

Após uma discussão offline com @ericstj , temos as diretrizes de design do framework, que recomendam tornar virtual apenas a sobrecarga mais longa, claramente as sobrecargas Sslstream atuais já violam isso. Afinal, foi observado que adicionar sobrecargas seria um projeto pobre. Além disso, o design original segue o padrão criar-definir-chamar, que é um padrão amplamente usado na estrutura. @Drawaes Estarei trabalhando nisso, pois há uma necessidade urgente de fazer isso. Estaremos criando um protótipo com as APIs propostas, acho que podemos ter um problema com a implementação do Unix, onde precisaremos dessas opções durante a criação do objeto SslStream, durante quando o sslcontext é instanciado, e definiremos os callbacks então. Ficará claro se precisamos dessas opções durante o novo SslStream ou podemos adiá-lo mais tarde durante a criação do protótipo. Vou atualizar o tópico quando tiver mais informações.

Posso responder a sua pergunta sobre o OpenSsl agora. Você não cria o contexto (portanto, precisa de retornos de chamada, etc.) até que acesse o método privado AcquireServer / Client Credentials no SecureChannel. Você não acerta até que chame a primeira Criação de Token, e não acerta até que você comece o aperto de mão. Você não pode criar o objeto SslCtx sem o certificado de qualquer maneira, que é fornecido na operação AuthenticateAsClient / Server.

Não tenho certeza de como mover todas as configurações para um objeto de configurações viola o padrão de chamada create set, na verdade, eu sugeriria que isso o reforça movendo as configurações atuais para fora do construtor no "novo" caso. Quanto a tornar as sobrecargas virtuais, acho que isso nunca fez parte do design?

https://github.com/Drawaes/corefx/commit/de8fc6a997035067079b0938db6a292f50dc363a

Algo assim para o pacote de opções seria um bom começo ... para ALPN eu usaria um enum, para os protocolos e buffers fixos nos bastidores, eles são definidos pela IANA e exigem uma boa barganha para serem alterados. Um enum que é [Sinalizadores] permitirá que vários sejam fornecidos, e então as strings "mágicas" podem ser codificadas para que não sejam cometidos erros

Algo assim

[Flags]
public enum ApplicationLayerProtocolType
    {
        None = 0,
        Http1_1 = 1,
        Spdy1 = 2,
        Spdy2 = 4,
        Spdy3 = 8,
        Turn = 16,
        Stun = 32,
        Http2_Tls = 64,
        Http2_Tcp = 128,
        WebRtc = 256,
        Confidential_WebRtc = 512,
        Ftp = 1024
    }

Você provavelmente deseja cortar a lista de coisas que não precisa de suporte (speedy, por exemplo), mas apenas para ter uma ideia de como seria.

Não tenho certeza de como mover todas as configurações para um objeto de configurações viola o padrão de chamada de criação de conjunto

Eu não disse que viola, na verdade disse que o design original desta proposta (no início da edição) é um bom ponto de partida.

Sinto falta então entendido :) então você está fazendo um protótipo então eu não devo atualizar a proposta da API até isso?

então você está fazendo um protótipo então eu não devo atualizar a proposta da API até isso?

Eu gostaria de implementar e testar o design primeiro, antes de levá-lo para a revisão da API. Acho que tenho dados suficientes para prosseguir, uma vez que eu publicar minhas alterações, você pode cavar para propor melhorias. Soa bem?

Perfeito ! Boa codificação.

para ALPN, eu usaria um enum

Por que não usar apenas strings? Isso tem a vantagem de que, se um novo protocolo for adicionado, você mesmo poderá especificar a string.

BTW, de onde você tirou essa lista? Não sei onde mora a lista "oficial" da IANA.

Além disso, não vamos parar de trabalhar na proposta da API. Devemos pelo menos escrever onde estamos atualmente e listar as questões em aberto restantes. Podemos fazer progressos em alguns deles, independentemente do trabalho de prototipagem de Priya.

https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn -protocol-ids

Esse é o oficial (como no RFC 7301 diz que é o registro). Em seguida, há uma eficiência em ser capaz de manter as strings estaticamente, pois você precisa de bytes ASCII (ou talvez seu utf8, mas tenho certeza de que é ASCII / ANSI qualquer) e, em seguida, adicioná-los a um buffer de lista, eles raramente mudarão a partir daqui, um sinalizador é uma mudança fácil e os usuários cometerão erros.

Quem vai se lembrar que HTTP / 1.1 é a string "http / 1.1" mas
http / 2 é h2c e http / 2 sobre tls é h2?

Por exemplo, você não sabia onde encontrar a lista, que esperança tem um desenvolvedor sentado em sua mesa corporativa que precisa se conectar a um servidor com http1?

Colocando desta forma, eu sou bom com strings para mim, diabos eu preferiria usar bytes :) Mas eu li as especificações para frente e para trás e não tenho certeza se outros o fizeram.

Eu concordo, não espero que os usuários se lembrem das strings. Só estou dizendo que devemos ter uma lista estática de strings em vez de um enum.

Algo como:

public static class ApplicationLayerProtocolNames 
{
  public const string Http11 = "http/1.1";
  public const string Http2 = "h2";
  public const string Http2Clear = "h2c";
  // whatever else should go here
};

Sim, não use um enum para alpn. Este é um conjunto de dados aberto e um enum impediria o suporte de valores adicionais sem uma atualização da estrutura, quanto mais valores personalizados. O valor também não afeta a implementação SSL, portanto, não há razão para restringi-lo. Um erro semelhante foi cometido no passado com os códigos de status http, mas pelo menos lá você poderia lançar para contorná-lo.

A especificação também diz que a ordem da lista é importante e deve ser personalizável. Usar um enum de sinalizadores evita isso.

No entanto, as constantes conhecidas são boas.

Muito justo, constantes conhecidas são boas para mim.

Suponho que se realmente quiséssemos estar dentro das especificações usaríamos ReadOnlySpan<byte> como o tipo de dados alpn. Isso evitaria problemas como codificação e revestimento e seria mais eficiente para serializar para o fio. As constantes conhecidas tornariam isso utilizável.

Não podemos ter uma lista de span.

para strings estáticas ou buffers ou o que eu diria
Http1.1
Http / 2 sobre SSL
WebRTC
WebRTC confidencial
Pop3
IMAP
FTP

deve ser o suficiente por agora. Http / 2 sem tls parece bobo porque você já está se conectando a um ponto de extremidade TLS.

Eu diria que o comportamento do ALPN deve ser ...

Se você forneceu protocolos e o outro lado não participou (o cliente nunca enviou ou o servidor não respondeu), um protocolo nulo foi negociado. Se não houve correspondência, mas ambos os lados foram enviados, é uma exceção com falha de autenticação. E o resto é igual.

Com base em discussões separadas, acho que nosso pacote de opções também precisará de um sinalizador para oferecer suporte à desativação da renegociação, pois isso parece ser necessário para HTTP2.

Para sua informação, o ALPN era a necessidade urgente, mas

A implementação de TLS DEVE suportar a extensão de Indicação de Nome de Servidor (SNI) [TLS-EXT] para TLS. Os clientes HTTP / 2 DEVEM indicar o nome do domínio de destino ao negociar o TLS.

O receio de SNI também é OBRIGATÓRIO ...

Já suportamos SNI no lado do cliente, o que eu acho que é tudo o que é necessário para ser compatível com HTTP2.

Bom ponto, há uma clara OBRIGAÇÃO, no entanto, a lacuna é que você não precisa responder com nada, mesmo que seja compatível ... :) de volta ao segundo plano para SNI. (Presumindo que o cliente o envie, não verifiquei, mas aceitarei sua palavra nisso :))

Hmmm talvez devêssemos investigar isso ... a suposição pode estar errada de que o sni está funcionando no lado do cliente

https://github.com/dotnet/corefx/issues/23362

HttpClient no .NET Core 2.0 não usa SslStream.

Sério ... então ele usa curl e qualquer API mágica no windows ... mas o novo gerenciado vai certo?

Sério ... então ele usa curl e qualquer API mágica no windows ... mas o novo gerenciado vai certo?

HttpClientHandler usa libcurl no Unix e WinHttp no Windows. A nova implementação gerenciada de HttpClientHandler usa SslStream:
https://github.com/dotnet/corefx/blob/2efb83a3170ec14cafe6a14ea37edb037eb99836/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionHandler.cs#L57

Legal e sabemos que SSLStream envia SNI com certeza ... ou devo quebrar o WireChark?

Legal e sabemos que SSLStream envia SNI com certeza ... ou devo quebrar o WireChark?

Se você for voluntário, a validação é sempre boa. :)

Pelo menos no Windows
image

Status: Eu tenho um protótipo para Unix, mas os alpn apis são suportados apenas a partir da versão openssl 1.0.2. Atualmente estou trabalhando em patchear minha máquina para ter esta versão para que minha implementação seja construída.

Qual versão é o "min" para o núcleo .net atualmente? (Excluindo macos)

Estou confuso com as menções a h2c - quando faria sentido discuti-lo no contexto de SslStream? Um cliente e servidor nunca devem negociar h2c usando ALPN, apenas h2.

Desculpe se perdi algo na discussão (meio que folheei).

:) então concordamos como eu disse

deve ser o suficiente por agora. Http / 2 sem tls parece bobo porque você já está se conectando a um ponto de extremidade TLS.

Então, de experimentar com o protótipo, esta é a estrutura de API que eu tenho,

SslAuthenticationOptions -> pacote de opções que contém configurações comuns para cliente e servidor, definidas ao inicializar o construtor. Isso garante que não estejamos decidindo qual valor escolher, tornando essas opções disponíveis apenas por meio do construtor, e não por meio de qualquer um dos métodos authenticateas *.

SslClientAuthenticationOptions, SslServerAuthenticationOptions -> este pacote contém opções específicas para o cliente ou servidor e definido por meio de um dos métodos AuthenticateAs *, durante o qual a identidade do SslStream é encontrada, seja como cliente ou servidor.

`` `C #
public class SslAuthenticationOptions
{
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback UserCertificateValidationCallback {get; definir; }
public LocalCertificateSelectionCallback UserCertificateSelectionCallback {get; definir; }
public EncryptionPolicy EncryptionOption {get; definir; }
}

public class SslClientAuthenticationOptions
{
public string TargetHost {get; definir; }
public X509CertificateColllection ClientCertificates {get; definir; }
public bool CheckCertificateRevocation {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
}

public class SslServerAuthenticationOptions
{
public X509Certificate DefaultServerCertificate {get; definir; }
dicionário públicoServerCertificates {get; definir; }
public bool ClientCertificateRequired {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public bool CheckCertificateRevocation {get; definir; }
}

public class SslStream: AuthenticatedStream
{
public SslStream (Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) {}
public SslStream (Stream innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions sslAuthenticationOptions) {}
public virtual Task AuthenticateAsClientAsync (SslClientAuthenticationOptions sslClientAuthenticationOptions, CancelamentoToken cancellationToken) {}
public virtual Task AuthenticateAsServerAsync (SslServerAuthenticationOptions sslServerAuthenticationOptions, CancelamentoToken cancellationToken) {}

public string NegotiatedApplicationProtocol { get; }

}
`` `

Existe uma propriedade em SSLStream para descobrir o nome do host que foi indicado? Diferente do que parece bom. Espero atualizar o problema com o design esta noite. Eu gosto da ideia de divisão de construtor / autenticação.

Legal, então todos os construtores antigos se tornam invólucros para o novo? E o mesmo para os métodos AuthenticateAsClient / Server?

Isso também significa que o APLN funciona para todas as APIs AuthenticateAsClient / Server antigas, não apenas para as novas.

Há uma implicação de que SslAuthenticationOptions são mutáveis ​​entre a chamada para o construtor e a chamada para Authenticate ... Se isso for verdade, então você não precisa dos novos construtores. por exemplo

var sslStream = new SslStream(inner);
sslStream.AuthenticationOptions.ApplicationProtocols = new[] { HTTP2, HTTP11 };
await sslStream.AuthenticateAsServerAsync(...);

EnabledSslProtocols pode ser anulado para que possa flutuar com os padrões do sistema? Ou Nenhum funciona para isso?

Não estou tão interessado nisso ... Porque alocações você deseja armazenar em cache esse objeto e usá-lo novamente e novamente. Também pode haver alguma inteligência que possamos fazer com o hashcode e um cache ...?

Se as opções forem apresentadas como uma propriedade, então sua mutabilidade precisa ser mais explícita / imposta.

@Tratcher
A maneira correta será,

c# SslStreamAuthenticationOptions options = new SslStreamAuthenticationOptions(); options.ApplicationProtocols = new [] {}; var sslStream = new SslStream(inner, options);

As opções não podem ser alteradas após a criação do objeto, pois isso não será refletido no contexto de segurança, que é criado após a primeira autenticação. O que significa que o contexto de segurança não pode ser alterado depois de criado, ele precisa ser eliminado primeiro, para inicializá-lo com novos valores. Portanto, tornar essa propriedade mutável é confuso, pois as alterações não terão nenhum significado depois que o contexto de segurança for estabelecido.

E sim, todos os construtores e métodos de autenticação antigos serão invólucros para essas novas APIs.

se sslStream.AuthenticationOptions.ApplicationProtocols = new[] { HTTP2, HTTP11 }; não funcionar, deve haver um erro do compilador ou do tempo de execução quando eu tentar.

O objeto options é apenas um getter, mas a lista de protocolos do aplicativo é mutável no design acima.

Agora que penso sobre isso, talvez não seja tão importante pensar sobre o cenário de mudança de opções fora do construtor, uma vez que o SslStream não permitiria que você fizesse vários Authenticates, e é apenas durante a fase de handshake que essas propriedades são necessárias . Então eu acho que a forma acima também é aceitável, as opções podem ser alteradas quantas vezes antes do handshake, depois do handshake, mesmo que as opções sejam alteradas, isso não afeta as funções de criptografar / descriptografar na gravação / leitura.

:) Eu gosto dessa opção ...

consideramos as alocações na string -> ascii e as pesquisas alpn? Pode haver algumas pessoas chateadas se adicionarmos mais alocações no mix. Existe alguma maneira de ajustar a API para ajudar com isso?

Se você tiver a sobrecarga de public SslStream(Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) { } , poderá deixar que o chamador decida se deseja reutilizar ou modificar, desde que você tenha certeza de que se trata da instância original e não de uma cópia.

Eu gosto dessa abordagem. Acho que colocar a sacola de opções no construtor faz muito sentido.

Não acho que precisamos expor a propriedade AuthenticationOptions. Isso não parece acrescentar nada e é um pouco confuso. Não expomos os argumentos do construtor existentes em SslStream hoje (por exemplo, RemoteCertificateValidationCallback).

Uma coisa que não tenho certeza é: essa proposta efetivamente tem dois "pacotes de opções" diferentes, um que é usado para o construtor e outro que é usado para os métodos AuthenticateAs. (Na verdade, três, já que há um para AuthenticateAsClient e outro para AuthenticateAsServer).

Isso significa que cada consumidor terá que construir duas bolsas de opções (ou usar as APIs existentes).

Se estamos mudando para bolsas de opções, eu me pergunto se seria melhor simplesmente ter uma que seja usada no construtor, com sabores Cliente e Servidor. Isso permite que o consumidor construa apenas uma sacola de opções.

Então eu gosto disso, porque se você fizesse isso, seria apenas AuthenticateAsync ()?

Além disso, tenho coçado a cabeça para saber qual seria a utilidade de expor o pacote de opções no SslStream e cheguei à mesma conclusão, nada de bom. Eu diria para removê-lo para maior clareza.

Não acho que precisamos expor a propriedade AuthenticationOptions. Isso não parece acrescentar nada e é um pouco confuso. Não expomos os argumentos do construtor existentes em SslStream hoje (por exemplo, RemoteCertificateValidationCallback).

Mesmo se não expuséssemos isso, o mesmo problema existe, ainda posso alterar o valor da opção. Estou mais preocupado com este tipo de cenário,

c# SslAuthenticationOption options = new SslAuthenticationOptions(); options.ApplicationProtocols = something; SslStream stream = new SslStream(inner, options); Task t = stream.AuthenticateAsServerAsync(...); options.ApplicationProtocols = somethingelse; await t;

o AuthenticateAsServer vai registrar algo ou algo diferente? Por causa disso agora, sou contra as opções do construtor. O melhor será ter uma bolsa de opções do cliente, uma bolsa de opções do servidor exposta através dos métodos de autenticação e, em seguida, fazer com que essas opções não incluam as opções existentes disponibilizadas através do construtor SslStream atual. Pensamentos?

Como isso é diferente de

Task t = stream.AuthenticateAsServerAsync(someOptions);
someOptions.ApplicationProtocols = somethingElse;
await t;

?

O problema que você tem em fazer dessa forma (e não sou contra isso, só preciso pensar sobre isso no momento). Voltaremos ao "e se o usuário definir algo no construtor e no pacote de opções?"

Acho que @Tratcher perguntou em um estágio e pode valer a pena revisitar, quão diferentes são realmente as opções de cliente e servidor? Vale a pena mesclar e apenas lançar se uma opção inválida foi definida. Pode não valer a pena apenas pensar.

FYI a razão para não expor as opções ...

var newStream = new SslStream(options);
await newStream.Authenitcate(blabla)

// pass the stream to other code... and it modifies your options you are using to make more connections.

Estou menos preocupado com a modificação do usuário entre a criação e a autenticação porque isso normalmente é controlado pela pessoa / código que faz a conexão.

passe o fluxo para outro código ... e modifica as opções que você está usando para fazer mais conexões.

Significado? será uma nova conexão agora e executará o handshake novamente com as opções especificadas.

eu queria dizer isso

Um servidor faz uma conexão TCP e, em seguida, um fluxo SSL. O sslStream é passado ao código do usuário para leitura / gravação. Mas agora o código do usuário pode alterar as configurações das opções, que se você reutilizar para fazer novas conexões, ele as modificará.

Mas agora o código do usuário pode alterar as configurações das opções

Essas alterações são ineficazes, uma vez que não executará o handshake novamente no sslstream. Depois que um stream é autenticado, o sslstream não pode ser reutilizado. Tem que ser eliminado.

Certo, mas diga (eu sei que há framebuffers etc, mas vamos supor)
Kestrel faz uma bolsa de opções e deseja armazená-la em cache e usá-la nas próximas 100 conexões.
Ele faz uma conexão com o pacote de opções e então passa o sslstream para o código de usuário.

Agora é verdade que você não pode alterar a conexão atual do código de usuário (por acidente ou de outra forma), mas você pode alterar as configurações para todas as conexões futuras.

@geoffkizer Esta proposta corresponde ao seu pensamento?

`` `c #
public class SslAuthenticationOptions
{
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback UserCertificateValidationCallback {get; definir; }
public LocalCertificateSelectionCallback UserCertificateSelectionCallback {get; definir; }
public EncryptionPolicy EncryptionOption {get; definir; }
}

public class SslClientAuthenticationOptions: SslAuthenticationOptions
{
public string TargetHost {get; definir; }
public X509CertificateColllection ClientCertificates {get; definir; }
public bool CheckCertificateRevocation {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
}

public class SslServerAuthenticationOptions: SslAuthenticationOptions
{
public X509Certificate DefaultServerCertificate {get; definir; }
dicionário públicoServerCertificates {get; definir; }
public bool ClientCertificateRequired {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public bool CheckCertificateRevocation {get; definir; }
}

public class SslStream: AuthenticatedStream
{
public SslStream (Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) {}
public SslStream (Stream innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions sslAuthenticationOptions) {}
public virtual Task AuthenticateAsync (bool isServer, CancelamentoToken cancellationToken) {}

public string NegotiatedApplicationProtocol { get; }

}
`` `

:) Eu gosto, não tenho certeza sobre todo mundo ... o que você acha?

Agora é verdade que você não pode alterar a conexão atual do código de usuário (por acidente ou de outra forma), mas você pode alterar as configurações para todas as conexões futuras.

Acho que é preocupação do usuário desenvolver o código como achar melhor, não tenho certeza se devemos nos proteger para isso aqui, apenas que as opções devem ser imutáveis ​​ou deixá-lo mutável e garantir que o objeto de opções não seja modificado por sslstream internamente para armazenar qualquer informação de estado sslstream.

Esse modelo começa a prejudicar a descoberta de opções e torna mais fácil deixar de fora os parâmetros necessários, como DefaultServerCertificate.

No mínimo:

public class SslAuthenticationOptions
{
    public IList<string> ApplicationProtocols { get; set; }
    public RemoteCertificateValidationCallback UserCertificateValidationCallback { get; set; }
    public LocalCertificateSelectionCallback UserCertificateSelectionCallback { get; set; }
    public EncryptionPolicy EncryptionOption { get; set; }
}

public class SslClientAuthenticationOptions : SslAuthenticationOptions
{
    public string TargetHost { get; set; }
    public X509CertificateColllection ClientCertificates { get; set; }
    public bool CheckCertificateRevocation { get; set; }
    public SslProtocols EnabledSslProtocols { get; set; }
}

public class SslServerAuthenticationOptions : SslAuthenticationOptions
{
    public X509Certificate DefaultServerCertificate { get; set; }
    public Dictionary<string, X509Certificate> ServerCertificates { get; set; }
    public bool ClientCertificateRequired { get; set; }
    public SslProtocols EnabledSslProtocols { get; set; }
    public bool CheckCertificateRevocation { get; set; }
}

public class SslStream : AuthenticatedStream
{
    public SslStream(Stream innerStream, SslClientAuthenticationOptions sslAuthenticationOptions) { }
    public SslStream(Stream innerStream, SslServerAuthenticationOptions sslAuthenticationOptions) { }
    public SslStream(Stream innerStream, bool leaveInnerStreamOpen, SslClientAuthenticationOptions sslAuthenticationOptions) { }
    public SslStream(Stream innerStream, bool leaveInnerStreamOpen, SslServerAuthenticationOptions sslAuthenticationOptions) { }
    public virtual Task AuthenticateAsClientAsync(CancellationToken cancellationToken) { }
    public virtual Task AuthenticateAsServerAsync(CancellationToken cancellationToken) { }

    public string NegotiatedApplicationProtocol { get; }
}

@ Priya91 e @Tratcher : Sim, parecem bons para mim. Alguns pontos menores:

  • Não precisamos de isServer em AuthenicateAsync e não precisamos de sobrecargas separadas para AuthenticateAsClient / Server. Podemos saber a partir das opções especificadas para o construtor.
  • Não tenho certeza se é necessário ter o (s) método (s) de autenticação virtual (is). Eu sei que tudo isso é hoje, mas não tenho certeza de qual valor isso está realmente agregando.
  • Eu acho que ter sobrecargas explícitas de ctor para cliente vs servidor faz sentido. Você não pode usar a classe base aqui, então não faz sentido ter um ctor que usa a classe base.
  • Eu me pergunto se devemos trocar a ordenação de parâmetros por leaveInnerStreamOpen. Ou seja, (innerStream, options, leaveInnerStreamOpen). Também me pergunto se este deve ser apenas um parâmetro opcional com default = false. Não tenho certeza de qual é o pensamento atual sobre parâmetros opcionais ...

Algo um pouco mais radical a considerar:

Pelo que posso ver, a única razão pela qual separamos o método Authenticate do ctor é que o método Authenticate é assíncrono.

Em vez disso, poderíamos considerar mover o método Authenticate para o pacote de opções em si e fazer com que ele faça a criação e a autenticação. Isso reduz o processo a uma única etapa e evita o estado um pouco estranho de "fluxo criado, mas não autenticado".

Seria mais ou menos assim:

var options = new SslClientOptions() { ... }
SslStream sslStream = await options.AuthenticateAsync(innerStream);

Nesse modelo, pode fazer sentido ajustar um pouco a nomenclatura - SslClientOptions => SslClientFactory e AuthenticateAsync => CreateAsync ou algo parecido.

Pensamentos?

Um outro pensamento menor:

Devemos adicionar um bool como "AllowRenegotiation" ao pacote de opções básicas, uma vez que parece que precisamos disso para suporte HTTP2. Pode muito bem incluir isso aqui.

Vou divulgar isso, embora já tenha sido discutido antes, mas visto que estamos quase indo nessa direção, qualquer coisa ... (e @geoffkizer chamou de fábrica, então eu o culpo)

Hoje o esquema para usar pelo menos OpenSsl está errado, o objeto SSL_CTX é alocado para um SslStream e então o objeto SSL é alocado. Por que você faria ambos para uma conexão? Por que eles não fizeram um? Por que você não cria um novo objeto de certificado X509 para cada conexão (normalmente) e, em vez disso, leva um ref para um (com contagem de ref).

A resposta é simples: o uso de SSL_CTX por conexão está errado. Ele foi projetado para que você tenha um único SSL_CTX. Isso pode fazer uma diferença significativa com o OpenSSL capaz de compartilhar vários recursos, como buffers de reciclagem. Mas hoje isso é quase impossível por causa do número de configurações, que podem mudar entre fazer duas transmissões.

No entanto, se pudéssemos ter um "objeto" que efetivamente poderia ser um rastreamento ou fábrica ou qualquer objeto (é realmente por isso que a questão mutável / imutável / Build () é importante para mim), poderíamos fazer um SSL_CTX para isso e reutilizar isto. Também há muitos bloqueios para fazer um SSL_CTX já que ele tem um bloqueio global ... consulte minha discussão sobre bloqueios do OpenSsl, pois muitas conexões rápidas colocam pressão sobre eles.

De qualquer forma, é apenas uma ideia, que se isso pudesse ser projetado, poderia facilitar o caminho para um fluxo SSL mais rápido, pelo menos para o Linux. O que seria bom porque no modelo há muitos saltos circulares porque ele foi projetado em torno do SChannel, o que é compreensível, mas isso pode ser um pequeno aceno de design que poderia ser dado?

Eu mantive AuthenticateAsClient / Server para consistência / descoberta.

Mover AuthenticateAsync de SslStream para opções leva você a um padrão de uso completamente diferente. Se estivéssemos começando do zero, eu diria que sim, mas não gostaria de fazer um fork da API existente dessa forma, isso tornaria a adoção de novos recursos no código existente mais difícil. Corefx está pronto para isso?

Mover AuthenticateAsync de SslStream para opções leva você a um padrão de uso completamente diferente.

Eu concordo. Mas eu sinto que já estamos no meio do caminho do "padrão de uso diferente". A proposta mais recente requer que os usuários alterem a chamada do construtor e a chamada Authenticate de maneiras não triviais. Uma vez que percorremos esse caminho, não está claro para mim onde devemos parar.

Alternativamente, se quisermos minimizar o impacto no padrão de uso, poderíamos apenas adicionar algumas novas sobrecargas de ctor.

Aqui está o espectro, a meu ver:

(1) Basta adicionar novas sobrecargas de ctor que levam "IListapplicationProtocols "e" bool supportRenegotiation "
=> Menor mudança, menor impacto no modelo de uso atual
(2) Adicionar novo ctor que leva um OptionBag, com parâmetros de ctor existentes mais os dois novos acima.
=> Impacta um pouco o modelo de uso atual, mas apenas para o construtor
=> Um pouco estranho porque o ctor é baseado em OptionBag, mas os métodos de autenticação não são
=> Pode ser estendido no futuro para se parecer mais com (3) ou (4) - mas se pensarmos que queremos fazer isso eventualmente, provavelmente faz sentido fazê-los agora, quando estamos fazendo alterações no modelo de uso, ao invés em seguida, driblar as alterações do modelo de uso ao longo do tempo e acabar com muitas maneiras diferentes de realizar a mesma coisa
(3) Adicionar novos ctors que levam ClientOptionsBag / ServerOptionsBag mais novos métodos de autenticação com parâmetros mínimos
=> Maior mudança no modelo de uso
=> Mais consistente do que (2), em que todas as opções são através de um único saco de opções
(4) Modelo de fábrica
=> Grande mudança no modelo de uso
=> Se estivéssemos começando do zero, provavelmente faríamos isso

Não tenho certeza de onde pousar aqui. (2) é o que menos gosto.

Sim, e embora esse seja o cenário "ideal" que eu sinto (de acordo com uma lib anterior que hackeei em conjunto para pipelines no início), poderíamos fazer algo mais como o X509 aqui, onde é basicamente imutável por natureza e você apenas conta para então se o usuário descartar você não se importa.

Para sua informação, meu projeto original era mais baseado em soquetes TCP, onde eles têm um Listener que fornece um soquete para cada conexão, e este era um "SecureListener" que recebe um fluxo e fornece um fluxo seguro.

Isso também significa que podemos evitar alocações / trabalhar para, digamos, a lista ALPN, porque da maneira que vejo acima, teremos que converter para strings ansi, gerar um buffer com um cabeçalho e, em seguida, colocar cada um com um cabeçalho, onde como isso poderia / deveria ser feito uma vez.

Outro pensamento.

(1) e (4) não são mutuamente exclusivos. Ou seja, poderíamos apenas fazer (1) agora com a intenção de adicionar um modelo de fábrica (4) no topo no futuro. Acho que poderíamos construir (4) sem quaisquer alterações no SslStream adequado.

Se 4. fosse uma grande possibilidade, então eu diria fazer 1 agora, porque se você fizer uma grande mudança parcialmente, uma mudança realmente grande será impossível mais tarde :) ou pelo menos é o que aprendi trabalhando para super grandes empresas ;)

se você fizer parcialmente uma grande mudança, uma mudança realmente grande será impossível mais tarde :)

Sim, concordou.

Deixando de lado a nomenclatura / estilo geral, eu tinha algo como

using (var serverContext = new SslSecurityContext(isServer : true))
{
    //Set all your settings and things
    using(var sslStream = serverContext.CreateSecurePipeline(innerPipeline);
    {
        await sslStream.PerformHandshakeAsync();
    }
}

Isso permitiu muitas otimizações em torno de contextos, buffers, buffers ALPN que você nomeie. Se uma grande reformulação estivesse acontecendo, é isso que eu pressionaria. Eu também entendo as restrições de tempo Http / 2 ALPN, então eu diria que é uma sobrecarga ... e o segundo que for feito, inicie a discussão sobre um redesenho :)

Em um servidor real, é claro, você faria vários sslStreams de "context / factory / [insira o nome]"

Sim, e isso me faz querer generalizar ainda mais, além do SslStream, e me faz pensar como isso se relaciona com o alicerce do projeto.

Não faça (1), os construtores já excederam um número razoável de parâmetros e isso não nos deixa mais perto de abordar outros cenários como SNI. (2) e (3) são diferenças amplamente estéticas. (4) é uma mudança de uso fundamental que será mais difícil de consumir. Por exemplo, ele combina o código de criação e conexão que pode estar ocorrendo atualmente em locais diferentes em meu aplicativo.

ele combina o código de criação e conexão que pode estar ocorrendo atualmente em diferentes lugares em meu aplicativo.

Isso não se aplica a (3) também?

@tractcher meu projeto acima não cria o contexto apenas cria o fluxo que você ainda pode chamar o aperto de mão, mas não consegue configurá-lo.

Um problema que vejo com 4, conforme proposto, é que ele está mudando a funcionalidade básica compreendida de SslStream, agora você faz o handshake nas opções e, então, qual é o propósito da autenticação como * no sslstream. Essas APIs serão redundantes agora, e ainda as teremos expostas, e isso resulta em várias maneiras de fazer a mesma coisa. Eu gosto do trecho de código @Drawaes acima, onde o handshake ainda é mantido no sslstream e tem as opções de ser uma fábrica para produzir objetos sslstream com várias configurações de entrada. Acho que podemos prosseguir com este modelo mesmo agora, colocar os botões mínimos necessários nesta fábrica de opções por enquanto e estender mais tarde para outras extensões tls.

Eu gosto do trecho de código @Drawaes acima, onde o handshake ainda é mantido no sslstream e tem as opções de ser uma fábrica para produzir objetos sslstream com várias configurações de entrada

Eu senti falta disso originalmente.

Em uma abordagem de estilo "fábrica", não tenho certeza de qual é o objetivo de ter um método Authenticate separado (PerformHandshakeAsync no snippet @drawaes ). Quando você faria qualquer coisa diferente de Create imediatamente seguido por PerformHandshakeAsync? E os métodos Authenticate * ainda são redundantes.

No caso dos pipelines, era uma questão de

//Some part of the connect handler
var secureStream = Listen.OnConnect( stream => context.CreateSecureStream(stream));
SomeServerLoop(secureStream);

//The actual handling loop
public async Task SomeServerLoop(secureStream)
{
    await secureStream.Authenticate();
    do
    {
         //Some logic
    } while(true);
}

Portanto, mover a criação e a autenticação ajudou aqui. Mas pode ou não ser útil. Acho que esse era o padrão que @Tratcher estava sugerindo acima.

A outra coisa é, se você retornar uma tarefa de lá porque o handshake é inerentemente assíncrono, o código pode ser parecido com isto (o que é feio)

using(var mySslStream = await context.CreateSecureConnection(innerStream))
{

} 

Então você sempre acaba tendo que pegar aquele objeto da tarefa ... não é a coisa mais bonita do mundo.

Não entendo por que isso é feio. Existem muitas APIs que funcionam assim hoje.

Toda essa discussão está me empurrando para uma abordagem minimalista aqui. Fazer uma mudança mais envolvente parece abrir um monte de novos problemas, e eu prefiro adiá-los e apenas fazer o ALPN etc. funcionar.

Problema lateral: SNI. Existe alguma razão pela qual não podemos simplesmente usar o argumento "targetHost" para LocalCertificateSelectionCallback para fazer isso funcionar no servidor? No servidor, isso é sempre definido como string.Empty hoje.

@geoffkizer Estou tentado a propor fazer SNI assim, mas não foi confirmado se podemos fazer o SNI baseado em callback ou não no schannel. No entanto, a maioria dos outros parâmetros não faria sentido.

Estou feliz em abandonar isso de qualquer maneira. Quer dizer, não consigo pensar em muitos exemplos que tenham um ID descartável dentro da Tarefa e é "surpreendente" para a maioria dos programadores (talvez não no MS, mas fora :)).

Quanto a largá-lo para fazer, como eu disse acima, eu ficaria bem com isso, mas não há bolsa de opções, basta adicionar um parâmetro e sobrecarga e está feito por agora e pode ser pensado, tenho uma pequena vantagem que tenho pensado nisso há um bom tempo :)

No SNI existe uma maneira de usar um callback para trocar certificados no SChannel, eu estava lutando para encontrar um, mas a fonte está fechada e a documentação tem alguns anos, você pode ter mais recursos e contatos para ajudar nisso.

Por favor, não volte para a opção 1 ... por favor ...

A vantagem da opção 1 é que é tããão feia que forçará uma mudança rapidamente após: P

🤕

@Tratcher Sim, todo o mecanismo LocalCertificateSelectionCallback é um pouco estranho, e eu não entendo muito bem como ele deve funcionar, ou quais coisas funcionam no cliente versus servidor.

LocalCertificateSelectionCallback é apenas para clientes. targetHost e localCertificates vêm de AuthenticateAsClientAsync, e remoteCertificate e relevantIssuers vêm do servidor. O cliente deve escolher um certificado que corresponda a um dos emissores aceitáveis.

Para SNI, o cliente envia um único nome de host e o servidor tem a opção de escolher seu certificado. O problema é que, pelo que posso ver, no SChannel, você tem que fornecer essa lista antecipadamente e o SChannel escolherá uma delas para você, você não pode fornecê-la "de fora". Mas pode haver uma maneira que estou perdendo

LocalCertificateSelectionCallback é apenas para clientes.

Isso faz sentido. No entanto, ele também é chamado no caso do servidor. Aparentemente, todos os parâmetros são definidos como nulos / vazios, exceto para a coleção de certificados - construímos uma coleção temporária com apenas o certificado do servidor nela. E então usamos qualquer certificado que você retornar deste retorno de chamada.

Por que fazemos isso, não tenho ideia.

O código legado é uma coisa maravilhosa e misteriosa ..

@ Priya91 @geoffkizer Foi tomada uma decisão? A maneira como eu vejo é

  1. Saco de opções agora, meio que meio caminho para o que o "ideal" seria
  2. Ganhe rapidamente o ALPN agora e comece as discussões da API em um "alvo futuro", como uma fábrica ou um redesenho total

Pessoalmente, com as restrições de tempo para 2.1 Http / 2, eu diria que vá com 2, pegue o ALPN e comece a discussão maior com mais tempo no bolso e levando em consideração coisas como a base do projeto e pipelines.

Enfim .. pensamentos?

Vá com a bolsa opcional. O design atual é insustentável e não prevejo um redesenho mais dramático em breve. A bolsa de opções também não é mais demorada para implementar.

@Tratcher Qual proposta de bolsa opcional? (2) acima? (3) acima? Algo mais?

(2) com o saco de opções para o construtor é o menos impactante ao abordar os requisitos. Isso pode lidar facilmente com o ALPN e a opção de renegociação. Nada aqui requer alterações nos métodos de autenticação.

Pensando no SNI, isso ainda pode ser feito com (2) se você tiver opções e sobrecargas específicas de cliente / servidor. Ou você introduz pacotes de opções de cliente / servidor separados para os métodos de autenticação. Hesito em ir até (3), onde tudo se move para as opções do construtor, pois isso exigiria mais rotatividade para o implementador e os consumidores, mas acho que é uma decisão melhor avaliada pelo implementador. Espero que qualquer uma dessas opções atenda aos requisitos do ASP.NET Core.

Ok, bem, embora @Tratcher esteja pensando como eu estava no início, tive esperanças de uma correção real futura e agora não tenho certeza se é a direção correta.

No entanto, vou me afastar do comportamento geral da Internet e capitular, pois parece haver um suporte sólido para esse design. Então aqui está o que eu penso para mover as coisas

@ Priya91 se você concordar, pois parece que está fazendo a implementação / investigação e se concordar com @Tratcher, podemos colocar um design em torno disso aqui, se @geoffkizer também concordar com o design, irei atualizar o topo e nós pode seguir em frente ...

(Um comentário para a posteridade, estaremos de volta aqui em um futuro próximo: P)

Eu também comecei a trabalhar com alpn para Linux e usei um pacote de opções básicas para a área de superfície da API, posso mudar isso depois que a API for aprovada. Enquanto isso, aqui está o commit com as mudanças alpn no Linux, o teste alpn funciona, mas alguns outros testes estão falhando, investigando esses.

Ok, @Tratcher, se você estiver satisfeito com a API geral desse commit, posso destilar e atualizar o primeiro comentário do problema e podemos seguir em frente?

Por todos os meios

Não acredito que devamos fazer (2). É, na melhor das hipóteses, meio passo em direção a uma abordagem de bolsa cheia de opções.

Eu tenho apoiado a exploração da abordagem do saco de opções, mas faz literalmente semanas que estamos discutindo sobre isso e não há convergência. Neste ponto, não acho que vale a pena continuar a perseguir.

Acho que devemos simplesmente fazer 1, adicionar uma sobrecarga de ctor.

@Tratcher , você

Aqui está a redação completa para a abordagem (1). Se houver algo incorreto aqui, por favor me avise.

ALPN:

`` `c #
public class SslStream: AuthenticatedStream
{
public SslStream (
Stream innerStream,
bool leaveInnerStreamOpen,
RemoteCertificateValidationCallback userCertificateValidationCallback,
LocalCertificateSelectionCallback userCertificateSelectionCallback,
EncryptionPolicy encryptionPolicy,
IListapplicationProtocols);

public string NegotiatedApplicationProtocol { get; }

}

Support for disabling renegotiation:

```c#
public class SslStream : AuthenticatedStream
{
    public SslStream( 
        Stream innerStream,
        bool leaveInnerStreamOpen,
        RemoteCertificateValidationCallback userCertificateValidationCallback,
        LocalCertificateSelectionCallback userCertificateSelectionCallback,
        EncryptionPolicy encryptionPolicy,
        IList<string> applicationProtocols,
        bool allowRenegotiation);
}

Vamos terminar isso offline.

@geoffkizer @Tratcher Temos um consenso sobre o design?

Não há consenso neste ponto.

Falei com @terrajobst hoje e usaremos o slot de design regular das terças 11h para discutir isso. O convite deve sair em breve.

@Tratcher , encaminhe o convite conforme apropriado.

@ Priya91 @karelz @stephentoub , FYI - espero que você possa fazer esta reunião.

@Drawaes seria bom tê-lo também, mas não sei como isso se encaixa a sua agenda ...

Não relacionado à proposta da API, mas o SecureTransport não tem APIs públicas para oferecer suporte a ALPN, portanto, lançaremos o PNSE no OSX.

Huh. Isso é lamentável.

SecureTransport não tem APIs públicas para suportar ALPN

Isso também é parte do motivo pelo qual a libcurl fornecida com o macOS não oferece suporte a HTTP / 2:
https://daniel.haxx.se/blog/2016/08/01/curl-and-h2-on-mac/

11h onde? Por exemplo, fuso horário?

desculpe - PST (Seattle)

Oferecemos suporte a OpenSSL como uma opção no OSX?

@bartonjs pode confirmar, mas não para TLS, apenas para algumas funções ausentes, eu acredito.

Eu encontrei este bug de radar aberto pedindo suporte ALPN para SecureTransport. Um dos comentários mencionou uma nova API SSLSetALPNProtocols a ser lançada no macOS 10.13, que atualmente está em beta. Podemos aumentar o suporte ALPN no macOS se esta API estiver disponível?

19h O ldn deve ficar bem, contanto que meu Pen Testing na próxima semana não vá mal :)

Podemos aumentar o suporte ALPN no macOS se esta API estiver disponível?

Eu suponho que sim. @ Priya91?

Podemos aumentar o suporte ALPN no macOS se esta API estiver disponível?

Não facilmente. No momento, construímos no 10.12 e só temos a API 10.12 disponível. Teríamos que fazer coisas complicadas para fazer qualquer tipo de iluminação funcionar com este shim, ou apenas obter a aprovação para tornar o 10.13 o sistema operacional mínimo para "2.1".

Provavelmente ainda seria complicado, mas estou me perguntando se a instalação de novos XCode e cabeçalhos no 10.12 seria suficiente. O SecureTransport.h reside em /Applications/Xcode.app/Contents/Developer/Platforms/*

Provavelmente não devemos contar com isso neste momento e tomá-lo como um bônus quando possível.

na realidade:

+/*
+ * Set the ALPN protocols to be passed in the ALPN negotiation.
+ * This is the list of supported application-layer protocols supported.
+ *
+ * The protocols parameter must be an array of CFStringRef values
+ * with ASCII-encoded reprensetations of the supported protocols, e.g., "http/1.1".
+ *
+ * See RFC 7301 for more information.
+ */
+OSStatus
+SSLSetALPNProtocols         (SSLContextRef      context,
+                             CFArrayRef         protocols)
+    __OSX_AVAILABLE_STARTING(__MAC_10_12_4, __IPHONE_9_3);
+
+/*
+ * Get the ALPN protocols associated with this SSL context.
+ * This is the list of supported application-layer protocols supported.
+ *
+ * The resultant protocols array will contain CFStringRef values containing
+ * ASCII-encoded representations of the supported protocols, e.g., "http/1.1".
+ *
+ * See RFC 7301 for more information.
+ *
+ * Note: The `protocols` pointer must be NULL, otherwise the copy will fail.
+ * This function will allocate memory for the CFArrayRef container
+ * if there is data to provide. Otherwise, the pointer will remain NULL.
+ */
+OSStatus
+SSLCopyALPNProtocols        (SSLContextRef      context,
+                             CFArrayRef         __nullable * __nonnull protocols)           /* RETURNED */
+    __OSX_AVAILABLE_STARTING(__MAC_10_12_4, __IPHONE_9_3);

Oooh. Que faz vida ter um pouco mais fácil. (10.12.4 parece um mínimo mais fácil de declarar do que 10.13)

Posso colocá-lo na lista TODO para investigar. E sim, concordo que seria muito mais viável.
A questão em aberto seria o comportamento em versões mais antigas. Seria bom IMHO se tudo funcionar, apenas ALPN lançaria exceção para plataforma não suportada.

@wfurt como o consumidor de SslStream saberia se ele poderia habilitar ALPN além de tentar fazer isso e lançar?

Que tal um modo de fallback normal em que o ApplicationProtocal negociado retorna nulo e o aplicativo decide se retorna para um padrão (HTTP / 1.1) ou falha? O aplicativo já precisa lidar com esse cenário quando a parte remota não oferece suporte a ALPN.

Acho que o carregador da biblioteca se recusará a carregar o shim (uma vez que não conseguiu resolver a importação), o que significa que qualquer coisa que tocasse na criptografia faria com que o aplicativo não carregasse. (A menos que façamos muito trabalho assustador)

@bartonjs , lembre-me por que no shim não podemos apenas usar dlopen / dlsym para encontrar a função que nos interessa e usá-la se existir?

Em geral, PlatformNotSupportedExceptions são minas terrestres do consumidor, o ASP.NET prefere ter lightup / no-op para recursos opcionais como ALPN.

@stephentoub "Trabalho assustador" :). Embora fazê-lo para um par de funções possa ser muito mais independente do que se fizéssemos tudo como fizemos com o OpenSSL para o esforço portátil.

Eu tenderia a concordar com @Tratcher. A desvantagem é arrastar novos requisitos de sistema operacional para algo que talvez seja marginal para a maioria dos usuários. Requerer 10.12.4 em vez de 10.12 não parece tão ruim.

Eu colocaria isso em espera por enquanto e posso compartilhar mais informações quando eu fizer alguns testes.

De todas as propostas, @ s cometer parece o mais simples tanto para consumir e implementar. Vou resumir:
`` `c #
classe pública parcial SslStream
{
public SslStream (Stream innerStream, opções SslAuthenticationOptions) {}
public SslStream (Stream innerStream, bool leaveInnerStreamOpen, opções SslAuthenticationOptions) {}

public string ServerIndicationName {get; }
public string ApplicationProtocol {get; }
}

public class SslAuthenticationOptions
{
public bool AllowRenegotiation {get; definir; }
IList públicoProtocolos de aplicativos {get; }
public EncryptionPolicy? EncryptionPolicy {get; definir; }
public IDictionaryServerCertificates {get; definir; }
public LocalCertificateSelectionCallback UserCertificateSelectionCallback {get; definir; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback {get; definir; }
}
`` `

Um retorno de chamada ainda teria preferência em vez de IDictionary<string, X509Certificate> para certificados de servidor, mas recebi relatórios mistos sobre se isso é compatível com Windows.

Eu gosto que a classe de opções forneça um local para colar uma coleção de certificados para fazer a fixação de raiz (assim que adicionarmos essa opção ao X509Chain).

Vai funcionar, há algumas coisas que ainda gostaria de descobrir

  1. Como fazer um Ssl_Ctx comum para OpenSsl
  2. Podemos fazer um retorno de chamada para o certificado do servidor para SNI?
  3. Tokens de cancelamento para os métodos de autenticação
  4. Reduzir as alocações envolvidas na implementação ALPN atual

Preciso sentar e pensar se 1,3,4 pode ser resolvido. Quanto a 2, eu diria que espere o SNI se não for crítico neste ponto até que alguém possa fazer alguma investigação no lado do SChannel (o OpenSsl dá suporte com certeza).

@Drawaes 1,3,4 não afetará o problema da proposta da API aqui. 1 e 4 são melhorias de desempenho, 2 e 3 são solicitações de API diferentes. Eu não consideraria esses bloqueadores para a proposta ALPN.

Claro, mas a API pinta a implementação um pouco complicada. Estou tentando pensar em uma saída e pode ser possível, mas vale a pena considerar.

Desempenho e usabilidade não precisam ser mutuamente exclusivos.

Deixe-me explicar de outra forma. A proposta da API não torna isso significativamente mais difícil, mas também não ajuda. O conceito de fábrica / construtor / ouvinte ajudou muito.

Um pensamento adicional que é ortogonal à abordagem geral da API.

Precisamos definir as cadeias de caracteres do protocolo ALPN para uso geral. Algo como:

class SslStream
{
    public const string Http11 = "http/1.1";
    public const string Http2 = "h2";
    // etc
}

Essas strings podem viver:
(1) No próprio SslStream.
(2) Em alguma outra classe de nível superior (presumivelmente estática), por exemplo, SslApplicationProtocols.
(3) Em uma classe aninhada (estática) dentro de SslStream. Assim, você os acessaria fazendo algo como "SslStream.ApplicationProtocols.Http11". Um pouco mais detectável do que (2), mas mais detalhado.

(2) parece ser a maneira como expomos coisas como HttpRequestHeaders. Dito isso, estou feliz com qualquer um deles.

Qual é a história de amanhã?

O que você quer dizer? A reunião é às 11h PST.

(2) com sua própria classe estática. Os outros causam ruído API.

Atualizei a proposta com as notas da reunião e uma API ajustada. Me avise se eu perdi alguma coisa.

Você precisa da capacidade de desabilitar a renegociação e remover o tamanho máximo de fragmentos por enquanto.

A renegociação desabilitada é um requisito http / 2

@Drawaes Renegociação adicionada.
Você já removeu o tamanho máximo do fragmento, certo?

Não, estava do lado do cliente, mas agora tenho :)

@stephentoub teve um bom argumento, logo após a reunião e acho que vou explorá-lo um pouco mais. Também pode deixar @ halter73 feliz. Que com a abordagem da bolsa de opções uma "fábrica" ​​poderia ser adicionada depois e a bolsa de opções poderia realmente ajudar. Portanto, seria uma cortesia. Para fazê-lo funcionar, seria necessário algum acesso interno ao SslStream para poder fornecer valores armazenados em cache diretamente, portanto, pode ser necessário estar no assembly.

Para ser claro, isso não afeta de forma alguma o trabalho do optionsbag ou esta proposta de API.

@karelz pediu referências a outros frameworks usando uma "abordagem de fábrica", encontrei meus refs (lá nos comentários 23 dias atrás;))

Referências a outras estruturas
GO - Bolsa de Opções
NodeJs - Bolsa de opções com uma fábrica / fornecedor
Jetty - ContextFactory

A API proposta no início parece boa, tenho algumas perguntas,

  1. Por que EncryptionPolicy e SslProtocols ser tipos anuláveis, eles podem assumir o padrão como Nenhum, certo?
  2. O ApplicaitonProtocol deve implementar IEquatableinterface
  3. Todos os pacotes de opções e protocolo de aplicativo devem estar dentro de SslStream ou ter o prefixo Ssl em seus nomes, uma vez que são aplicáveis ​​apenas para TLS, e também temos NegotiateStream no mesmo namespace.
  1. EncryptionPolicy e SslProtocols devem ser anuláveis ​​para que possam flutuar com o padrão do sistema. Em outras palavras, você pode dizer se o usuário os definiu propositalmente ou não.
  2. Sem opinião. Verificações de igualdade diretas são tudo que espero precisar.
  3. Dentro? Você quer dizer classes aninhadas? Por favor não. A adjacência com um prefixo 'Ssl' é adequada.

Sim mesmo 2 eu não me importo. 3 prefixo de classe Ssl é bom

Uma pergunta: o buffer de protocolo é apenas o conteúdo de bytes opacos para esse protocolo ou os bytes opacos + o prefixo de tamanho?

Também coloco o prefixo Ssl nas classes no topo

@Drawaes Eu esperaria que eles correspondessem exatamente aos valores da IANA, sem prefixo.

Legal, eu não tenho uma preferência, desde que esteja claro :)

Eu sugeriria remover as sni apis daqui, uma vez que não estamos planejando implementá-las por enquanto. Mantendo apenas os valores alpn e allowrenegotiation. Para sni, pode haver uma proposta de API separada. Atualizado o principal problema com isso, e também implementado iequatable em applnprotocol.

@Tratcher, eu não entendo isso,

EncryptionPolicy e SslProtocols devem ser anuláveis ​​para que possam flutuar com o padrão do sistema. Em outras palavras, você pode dizer se o usuário os definiu propositalmente ou não.

Se o usuário não os definir, os padrões podem ser Nenhum, nulo será Nenhum internamente. Não entendo a vantagem que o anulável oferece em relação a Nenhum.

Há um novo SslProtocol a cada poucos anos. Queremos atualizar os padrões, mas também queremos respeitar as configurações explícitas dos desenvolvedores. Se SslProtocol é um parâmetro obrigatório, é difícil dizer se o desenvolvedor o definiu porque era isso que ele queria ou apenas porque era o que estava disponível no momento. Se ele puder ser deixado nulo, então está claro que o desenvolvedor optou por usar os padrões do sistema, mesmo se esses padrões mudarem.

Ok, deixe-me colocar desta forma, se eu não definir a propriedade EncryptionPolicy, ela usará qualquer valor padrão que o estado atual de segurança SSL exija. Se o usuário alterar explicitamente o padrão, use o valor de usuários.

@Tratcher SslProtocols.None já foi cooptado para significar isso no modelo atual. Embora null seja um valor melhor ("Não sei, você deve fazer a coisa certa") em geral, não acho que queremos duas maneiras diferentes de expressá-lo. Portanto, eu defenderia a reutilização do valor sobrecarregado de None .

Eu concordo. Se fosse greenfield null é muito mais claro na intenção, mas é onde está. Para ser honesto, é completamente ao contrário de como você gostaria de qualquer maneira. Em vez de escolher quais protocolos você deseja, deve-se escolher quais protocolos você desabilita.

Então, você diria que não quero ssl3 ou tls1, mas me dê o que mais você puder suportar. Que foi meu projeto de 12 meses atrás, mas provavelmente estamos muito longe nessa linha para mudar isso.

Com relação a SslServerAuthenticationOptions , seria possível também adicionar suporte para a seleção de emissores confiáveis ​​aqui?
Veja: https://github.com/dotnet/corefx/issues/23938

@ayende Por favor, arquive um problema separado para isso.

Ele tem e vinculou a isso aqui por minha sugestão. Pode valer a pena abrir um problema SNI, visto que o deixamos cair a partir daqui.

SNI E quais CAs você permite precisarão estar cientes um do outro.

   public Task AuthenticateAsServerAsync(ServerAuthenticationOptions options, CancellationToken cancellation) { }
   public Task AuthenticateAsClientAsync(ClientAuthenticationOptions options, CancellationToken cancellation) { }

Eles devem ser Ssl [Server | Client] AuthenticationOptions.

   public bool ClientCertificateRequired { get; set; }

Ainda me pergunto se devemos renomear isso para "ClientCertificateRequested" ou similar. Do MSDN, parece uma descrição mais precisa do que isso faz (a menos que eu esteja mal-entendido ...)

    public static readonly SslApplicationProtocol Http2;
    public static readonly SslApplicationProtocol H2;

Precisamos realmente de duas maneiras de especificar Http2? Eu iria apenas com Http2 e soltar H2.

public class SslApplicationProtocol : IEquatable<SslApplicationProtocol>

Houve alguma discussão sobre como tornar isso uma estrutura. Nós fechamos isso?

Eu removi o H2 duplicado.

Alterado para struct, não houve desacordo quanto ao uso de struct. Vamos com ReadOnlyMemory? Que vantagem isso oferece em relação ao byte []

Já que precisamos copiar de qualquer maneira, acho que byte [] está bom.

Re: ClientCertificateRequ {ired | ested}: Se definido como verdadeiro sem retorno de chamada personalizado, é obrigatório. Se definido como verdadeiro com um retorno de chamada personalizado, é tudo o que o retorno de chamada personalizado decidir.

O que realmente se resume (para terminologia RFC) é "enviar solicitação de certificado [cliente]" https://tools.ietf.org/html/rfc5246#section -7.4.4

Então RequestClientCertificate , talvez?

ReadOnlyMemory era mais importante antes de SslApplicationProtocol, quando estávamos adicionando IList diretamentepara as opções. Agora não importa.

RE: certificado do cliente, realmente queremos renomear APIs existentes? Isso vai causar tanta confusão quanto economizar.

Renomeando, eu não me sinto fortemente de qualquer maneira. Temos aqui a oportunidade de esclarecer essas coisas que provavelmente não teremos novamente por muito tempo. Mas, como você disse, não tenho certeza se vale a pena.

RE: memória somente leitura ... vamos colocar de outra forma ... Por que você não usaria? Oferece muito mais flexibilidade do que uma matriz. Você não pode por ref comparar de qualquer maneira, porque como você diz, você está copiando.

Memória somente leitura alterada para byte []. Acho que todas as questões pendentes foram resolvidas. @karelz @KrzysztofCwalina @bartonjs @terrajobst @weshaggard Você pode fazer uma revisão final e marcar a API como aprovada. Obrigado!

Espere, por que a memória somente leitura está sendo descartada? As pessoas estão gastando tempo adicionando ativamente o conceito de "memória" em toda a estrutura, byte [] é conversível em memória somente leitura, mas não o contrário. Também concordamos que os dados sejam copiados e, portanto, você não pode fazer uma igualdade de referência de qualquer maneira, que seria a única vantagem. Finalmente, se a memória precisar ser fixada para ser transferida para a API nativa, você poderá fixá-la uma vez e obter o alfinete pelo custo de uma contagem de referência.

@Tratcher Acabou de mencionar que inicialmente optamos por readonlymemory para tornar os campos http1.1, http2 somente leitura estáticos imutáveis. Revertendo isso.

Atualização: devemos fazer com que a configuração checkCertificateRevocation no pacote de opções seja um enum. Como não é extensível para design futuro. Digamos, decidimos adicionar um novo enum de revogação de certificado, agora o usuário pode definir valores conflitantes por meio dessas configurações diferentes e teremos que tornar obsoleto o valor bool. Portanto, faz sentido adicionar o enum agora na sacola de opções.

Mude isso,

`` `c #
public bool CheckCertificateRevocation {get; definir; }

to,

```c#
public System.Security.Cryptography.X509Certificates.X509RevocationMode CheckCertificateRevocation {get; set;}

Atualizado com public SslApplicationProtocol(string protocol) { } // assumes utf-8 e public override string ToString() { } // For debugger. utf-8 or a string representation of the raw bytes. E.g. "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31"

Parece razoável. Vale a pena verificar se não há caracteres multibyte utf8 na string e lançar se houver? Você ainda pode defini-los por meio de bytes de leitura e isso poderia impedir os problemas de cópia e colagem de caracteres ocultos (como acontece com impressões digitais copiadas de mmc nas quais desperdicei horas da minha vida)

@Drawaes algum exemplo?

O exemplo é se você copiar e colar uma impressão digital do plug-in de certificado para mmc, você acaba com um caractere Unicode oculto. Se você tentar usar isso para fazer a correspondência com a impressão digital no armazenamento de certificados no código, não conseguirá encontrá-lo. Em seguida, você passa 2 horas verificando tudo apenas para descobrir que em vs na string o número da coluna salta entre o final da impressão digital e o ".

Não é crítico, mas é mais um poço de sucesso, é muito improvável que você realmente queira um char Unicode transformado em utf8 com um codepoint. Se você fizer isso, você saberá e poderá usar o byte somente leitura.

De qualquer forma, não é um quebra-negócio, apenas algo que quando você acerta, nunca esquece.

Eu quis dizer um exemplo de sequência de caracteres / bytes. O espaço (0x20) era o caractere incorreto?

Está no início, desculpe .. É 0x200e e é a marca da esquerda para a direita. Há uma trilha de sonhos desfeitos no stackoverflow;)

https://stackoverflow.com/questions/8448147/problems-with-x509store-certificates-find-findbythumbprint

Então, minha sugestão é esta. Se alguém enviar uma string que resulte no conjunto de bits mais significativo em qualquer um dos bytes, descarte-a como uma exceção. Isso interromperá precocemente problemas estranhos de correspondência de tempo de execução e há uma maneira de contornar isso, se você realmente quisesse.

Em parte também é porque não há como dizer "vamos interpretar isso como utf8" quase como se precisássemos de uma string utf8;)

Ah, entendido. Isso é mais um problema de editor de código do que um problema de API. Dependendo da codificação do arquivo, esses bytes extras podem simplesmente ser descartados. Adivinhar os valores está muito além do alcance desta API.

Certo, eu pensei mais sobre isso, a sobrecarga de cordas deve ser totalmente descartada. Existem consts para o caso comum, para qualquer outra coisa é "avançado" e o usuário deve controlar a string que é inserida.

Eles podem converter para UTF8 se quiserem, mas não há nenhum tipo de string utf8, então não está claro o que o envio de uma string fará. Não há nada nas especificações sobre UTF8, apenas bytes opacos, a sobrecarga ajuda muito pouco e apenas adiciona uma armadilha potencial.

A armadilha comum seria uma chave / valor Redis binário que tem uma conversão implícita em string (sem alargamento ou estreitamento)

Portanto, usar um valor binário do Redis seria interpretado como Utf8 e obteria todos os outros bytes consumidos ou produziria uma interpretação inválida de Utf8.

A especificação fornece utf-8 como uma sugestão para definir valores de especificação, e todos os atuais usaram essa codificação.

Sequência de identificação: o conjunto preciso de valores de octeto que
identifica o protocolo. Esta pode ser a codificação UTF-8
[RFC3629] do nome do protocolo.

Certo e todos os atuais que são relevantes têm um const. Essas também são as considerações da IANA para alocá-los. A própria especificação é

Os protocolos são nomeados por sequências de bytes não vazias, opacas e registradas na IANA, conforme descrito a seguir
na Seção 6 ("Considerações IANA") deste documento.

E o poderia ser é tão fraco quanto uma especificação IETF fica, não é nem mesmo um "deve" minúsculo: P

Eu acho que a verdadeira questão é. Qual é o caso de uso para passar uma string que é facilitada e garante os problemas potenciais?

A especificação fornece utf-8 como uma sugestão para definir valores de especificação, e todos os atuais usaram essa codificação.

Isso é diferente de converter automaticamente a entrada para a função de representação de 2 bytes por caractere ( string ) para 1 byte por representação de caractere ( utf8 ) para que possa corresponder à representação on-wire.

Quando, por exemplo, se viesse de uma chave redis, provavelmente estaria na representação on wire, então você descartaria todos os outros bytes na conversão automática de string para utf8 . Ou pode estar em um formato de string.

O problema que tenho é com a conversão automática de string de 16 bytes para formato binário opaco de 8 bytes

^^^ Isso. O desconforto que eu tenho é que não há nada dizendo "Será considerado UTF8" na definição da API além de um comentário. Você poderia, por exemplo, ter um
(ignorando forma e nomenclatura)

CreateUTF8ApplicationLayerProtocolName(string inputString);

Onde estava claro o que iria acontecer, mas ter um construtor sobrecarregado automático é problemático.

Alguns de seus exemplos são copiar e colar e alguns tratam a entrada como dinâmica. Copiar e colar parece muito mais provável, pois você não adiciona novos protocolos com muita frequência e, certamente, não em tempo real a partir do redis.

O cenário principal para o qual vejo o construtor de string sendo usado é ao copiar e colar novos valores da lista da IANA, que incentiva explicitamente as codificações utf-8.

Disseram-me que várias plataformas comparáveis ​​estão apenas expondo a API de string utf-8, nenhuma API de byte []. Eu ficaria curioso para ver referências reais embora.

Claro, go leva uma "string", mas uma string em Go é bytes opacos de qualquer maneira, não utf16 como uma string ac #. O problema não é o UTF8. Se houvesse uma string utf8, sem problemas.

De GO

É importante afirmar desde o início que uma string contém bytes arbitrários. Não é necessário conter texto Unicode, texto UTF-8 ou qualquer outro formato predefinido. No que diz respeito ao conteúdo de uma string, é exatamente equivalente a uma fatia de bytes.

O uso atual sem sobrecarga para uma string seria este?

var myProtocol = new SslApplicationProtocol(Encoding.UTF8.GetBytes("MyProtocol"))

@Drawaes Você pode reverter as alterações feitas no comentário do problema principal? A última atualização da proposta foi feita após uma reunião de revisão da API durante a qual foi recomendado adicionar um construtor de string. Não queremos que o comentário principal seja alterado sem uma revisão da API.

cc @karelz

Eu apenas removi a sobrecarga de string que não foi discutida na reunião de revisão da API e foi adicionada apenas 4 dias atrás, a menos que houvesse uma reunião da qual eu não soubesse.

Atualizado com public SslApplicationProtocol (string protocol) {} // assume utf-8 e public override string ToString () {} // Para depurador. utf-8 ou uma representação de string dos bytes brutos. Por exemplo, "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x3

Esse foi esse comentário
Comente onde foi adicionado

Se eu estiver enganado, ele poderia ser adicionado de volta, mas isso foi adicionado, pelo que eu posso dizer, 4 dias atrás e a reunião de revisão apenas discutiu o construtor com memória somente leitura e as consts para tipos bem conhecidos?

@Drawaes Sim, houve outra reunião para acompanhar as questões abertas da revisão inicial. E as notas da reunião de 22 set-2017 foi o resultado disso.

Olhando de outra maneira

byte[] binaryProtocol // = ...;

var myProtocol = new SslApplicationProtocol(binaryProtocol); 
var otherProtocol = new SslApplicationProtocol(myProtocol.ToString());

Console.WriteLine(myProtocol == otherProtocol)

Teria potencialmente a saída false ?

Enquanto que

var myProtocol = new SslApplicationProtocol(binaryProtocol); 
var otherProtocol = new SslApplicationProtocol(myProtocol.Protocol);

Console.WriteLine(myProtocol == otherProtocol)

Retornaria true ?

Exceto ... ele precisa de um ReadOnlyMemory<byte> .ctor; já que você não pode fazer isso atualmente ☹️

Protocol é ReadOnlyMemory<byte> ; e há apenas um byte[] .ctor.

Portanto, a simetria de objeto do construtor <-> para SslApplicationProtocol não funciona atualmente. Você não pode pegar as propriedades de saída de um SslApplicationProtocol e criar outro que seja igual.

  • ToString() saída string .ctor, pois .ctor o interpreta como utf8 e ToString pode gerar códigos hexadecimais.
  • Protocol que não faz interpretação não tem .ctor para dar; então você precisa alocar uma nova matriz de bytes e, em seguida, iterar sobre Protocol definindo cada byte individualmente e, em seguida, fornecer o byte[] recém-alocado para o .ctor

Concordo que a API para o protocolo é assimétrica, o que é problemático para qualquer API.

@ Priya91 Um problema que eu tenho com a reunião, não é que ela aconteceu, tudo bem se você precisar resolver as coisas. Mas isso não foi mencionado a não ser pela atualização do meu principal comentário, apenas que houve uma mudança na API. Agora é parcialmente uma falha do GH em que você não é notificado sobre alterações em um comentário, apenas em novos comentários, então felizmente não sabia e não conseguia entender por que a API tinha sido alterada.

O ponto acima ainda permanece, pegar um byte [] e trazer à tona uma memória parece assimétrico. Bem como as dúvidas na corda.

Você não pode pegar as propriedades de saída de um SslApplicationProtocol e criar outro que seja igual.

O seguinte não funcionaria?
`` `c #
var myProtocol = new SslApplicationProtocol (binaryProtocol);
var otherProtocol = new SslApplicationProtocol (myProtocol.Protocol.ToArray ());

Console.WriteLine (myProtocol == otherProtocol)
`` `

Acho que a questão era que você tinha que copiá-lo para um array quando ele acabasse sendo memória somente leitura no final novamente. Por que não incluir memória somente leitura?

É um pouco estranho usar tipos diferentes para o construtor e a propriedade. Em alguma versão da proposta, o construtor pegou ReadOnlyMemory<byte> . Não tenho certeza de onde isso se perdeu.

Por que não incluir memória somente leitura?
c# byte[] bytes = ... ReadOnlyMemory<byte> memory = bytes; var myProtocol = new SslApplicationProtocol(memory); bytes[0] = 20;
A questão é que temos que copiar no ctor, não importa o que levemos. E então retornamos ReadOnlyMemory e não precisamos copiar.

Ok, obviamente estou faltando alguma coisa (vou acreditar em você nisso). Mas, para meu próprio entendimento, por que você tem que copiar tudo o que tem no construtor de qualquer maneira?

Do jeito que eu entendi ... novamente, pode estar faltando algo que você tem x número de estruturas ALPN com os bytes opacos.

Quando você vem para enviar isso ao cliente, você precisa preparar uma lista (agora eu não usaria o linq, mas seu código de exemplo, você poderia usar um equiv de array ou qualquer outro)

var totalSize = listOfAlpn.Sum(i => i.Length + 1) + 2;
var outputBuffer = new byte[totalSize];
var span = new Span<byte>(outputBuffer);
span.WriteUshort(totalSize-2);
span = span.slice(2);
foreach(var alpn in listOfAlpn)
{
    span.WriteByte(alpn.Length);
    //slice again (this is why spans need a write FYI)
    alpn.ReadonlyMemory.Span.CopyTo(span);
   //one last slice
}

//done you have an array with the buffer to put on the wire

Presumi que queremos que SslApplicationProtocol seja imutável. Tipos imutáveis ​​são melhores para representar constantes lógicas.

Tipos imutáveis ​​não podem armazenar dados, sejam mutáveis ​​e compartilhados. A matriz de bytes passada ao ctor é mutável e compartilhada. Para que não seja compartilhado, o ctor precisa fazer uma cópia privada.

Em seu exemplo de código, o loop pode produzir dados diferentes no intervalo de resultados sempre que o loop for executado, mesmo que a coleção de itens alpn nunca seja alterada. Isso ocorre porque os itens na coleção podem ser alterados por meio da matriz de bytes "porta dos fundos". E não queremos isso, queremos?

E não queremos isso, queremos?

Pode ser divertido :) Embora provavelmente seja uma causa de bugs.

Independentemente disso, deve ser um .ctor

public SslApplicationProtocol(ReadOnlyMemory<byte> protocol) { }

Ou talvez

public SslApplicationProtocol(ReadOnlySpan<byte> protocol) { }

Como eles transmitem corretamente a intenção de usar o parâmetro protocol ; bem como aceitar outras fontes além de apenas um bcl byte[]

@KrzysztofCwalina claro que não, no entanto, eu disse muito cedo que a mutabilidade era um problema para todas as opções. Aqui estamos dizendo que o ALPN é especial em comparação, digamos, a lista do ALPN, o certificado ou qualquer outra coisa que possa ser alterada na sacola de opções? Aqui está a citação de @stephentoub quando eu disse que deveríamos bloquear a bolsa de opções (que, para sua informação, me convenceu de que não deveria ser imutável, então não tenho certeza de por que agora o ALPN precisa ser).

Por que é um problema ter o pacote de opções mutável? Não é diferente de qualquer outra coisa: você dá isso para a API, e a API espera que esteja em um estado consistente durante a operação ... se você mudar enquanto a operação está em andamento, isso é um bug, não diferente de qualquer outro caso onde você altera algo enquanto um método chamado depende disso (por exemplo, os buffers passados ​​para leitura / gravação). Se quiser tratá-lo como imutável e nunca alterá-lo depois de criado, você pode. Se você quiser tratá-lo como mutável, de forma que possa modificá-lo entre os usos, você pode.

@Drawaes A propriedade options bag IList<SslApplicationProtocol> pode ser alterada, mas o valor do protocolo da estrutura SslApplicaitonProtocol é imutável. Por enquanto, estamos assumindo que o uso de SslApplicationProtocol reside apenas no pacote de opções, mas esse tipo pode ser usado fora do pacote de opções em outros cenários no código do usuário.

@ Priya91 Eu

De qualquer forma, se é o design que todos vocês desejam, que assim seja.

@Drawaes , não vamos expor sacolas de opções constantes / pré-cozidas. Queremos ter SslApplicationProtocols constantes expostos.

@benaadams , concordo que ReadOnlySpanseria mais flexível. Não tenho certeza se precisamos dessa flexibilidade. Isso tornará as APIs mais difíceis de usar (Span é menos conhecido que byte [], Encoding.UTF8.GetBytes retorna array, etc.).

Dito isso, não estou pressionando por nenhum design aqui (na verdade, acho que é um pouco complicado e devemos apenas deixar o desenvolvedor responsável por esse recurso, ou seja, @ Priya91 , fazer a ligação). Eu estava apenas comentando que o ctor deve copiar independentemente do que passamos, e então o argumento de que devemos usar ReadOnlyMemory porque é somente leitura não parece ser válido.

e temos uma API assimétrica que expõe um ReadOnlyMemory na propriedade do protocolo, mas nenhuma maneira de usar isso para fazer um novo?

Não entendo esta declaração. A memória somente leitura pode ser usada para criar um novo objeto SslApplicationProtocol, novo SslApplicationProtocol (readonlymemory.toarray ()).

A memória somente leitura pode ser usada para criar um novo objeto SslApplicationProtocol, novo SslApplicationProtocol (readonlymemory.toarray ()).

Pode. No entanto, você está pegando um item imutável ReadOnlyMemory<byte> alocando para convertê-lo em um item mutável byte[] e, em seguida, passando o item mutável para o construtor, para que ele possa copiá-lo novamente e transformá-lo em um ReadOnlyMemory<byte> imutável

O que estou sugerindo é tomar o parâmetro como ReadOnlyMemory<byte> transmite a intenção da função corretamente de uma nova maneira que não podíamos fazer anteriormente com matrizes. Ele diz que a função que leva isso não tem a intenção de modificá-lo. Considerando que uma função que assume uma matriz não sugere tal garantia; o parâmetro pode ser alterado para o receptor após a chamada.

Não estou sugerindo remover o byte[] .ctor; já que isso provavelmente apenas confundiria as pessoas; apenas para adicionar um .ctor adicional que pode tomar diretamente a propriedade que o tipo expõe e criar outra, em vez de ter que convertê-la em outra coisa; ou traduza primeiro.

Além disso, você provavelmente nunca vai realmente fazer
new SslApplicationProtocol(proto.Protocol));
ou
new SslApplicationProtocol(proto.Protocol.ToArray()));
Como se você tivesse o protocolo, por que criaria um idêntico; então, principalmente, é que a API não é autoconsistente que me incomoda. No entanto, é um ponto menor.

No entanto, acho que interpretar uma entrada string como UFT8 é problemático e, da mesma forma, sem um string .ctor, você poderia facilmente fazer

new SslApplicationProtocol(Encoding.ASCII.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.UTF8.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.BigEndianUnicode.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.Unicode.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.UTF7.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.UTF32.GetBytes("MyProtocol"));
new SslApplicationProtocol(Encoding.GetEncoding("iso-8859-5").GetBytes("MyProtocol"));

Onde é explícito qual é a codificação, já que a string no .NET é um formato de 16 bits; que também pode armazenar facilmente dados binários e não um formato UFT8.

Suponho que da mesma maneira que você poderia fazer

byte[] protocol = new byte[str.Length * 2];
int i = 0;
foreach (char ch in str)
{
    protocol[i] = (byte)(ch & 0xFF);
    protocol[i+1] = (byte)((ch >> 8) & 0xFF);
    i += 2;
}
new SslApplicationProtocol(protocol);

Para manter o formato natural de string - só acho que uma API que recebe uma string e assume automaticamente uma codificação de saída diferente está pedindo problemas - está preparando uma suposição de conversão na api.

O problema com o .NET apis conforme demonstrado pelo .NET Standard 2.0 (e é incrível) - é que eles nunca podem ser alterados uma vez que estão ativos.

Uma sobrecarga de string sempre pode ser adicionada posteriormente se houver demanda; entretanto, uma vez lá, ele nunca pode ser retirado ou seu comportamento alterado.

Com um ReadOnlyMemory .ctor + https://github.com/dotnet/corefx/issues/24293 , você seria capaz de evitar o loop foreach char com o no-aloc

new SslApplicationProtocol(new ReadOnlyMemory<char>(str).AsBytes())

(ignorando a cópia defensiva em .ctor)

@benaadams Quais são os casos de uso para alguém fazer isso?

Não estou sugerindo remover o byte [] .ctor; já que isso provavelmente apenas confundiria as pessoas; apenas para adicionar um .ctor adicional que pode tomar diretamente a propriedade que o tipo expõe e criar outra, em vez de ter que convertê-la em outra coisa; ou traduza primeiro.

Seja byte [] ou memória somente leituraele deve ser copiado no construtor, e uma memória somente leitura é apenas um byte [] que não expõe meios para modificar o byte subjacente []. Portanto, não vejo nenhum valor extra na memória somente leitura no construtor. Eu também não entendo o ponto API assimétrico, quais cenários são afetados por isso?

Com relação à sobrecarga do construtor de string, ela é fornecida para usabilidade e conveniência de usar novos valores alpn que não são constantes no momento ou para novos valores pré-acordados personalizados que os clientes e servidores podem usar. Não é nosso objetivo fornecer igualdade para um objeto sslaplicationprotocol criado com outro tostring () para corresponder, esse cenário não é um caso de uso para esta api. É semelhante a como as apis de codificação não correspondem a getstring e getbytes para valores de codificação incompatíveis.

Devemos convergir a discussão sobre este assunto e chegar a um consenso, está se arrastando por muito tempo :(.

@Tratcher public SslApplicationProtocol(string protocol) { } // assumes utf-8

O que significa "utf-8"?
Temos codificação UTF-16 de strings, então é isso que vamos receber. Teremos que convertê-lo internamente para a string UTF-8 e armazenar no "byte []" que estamos transmitindo.
ToString interpretará o "byte []" subjacente como string UTF-8. Se isso falhar, será uma representação de string dos bytes brutos, por exemplo, "0x68 0x74 0x74 0x70 0x70 0x2f 0x31 0x2e 0x31".

Esta é a última proposta da ALPN a partir da discussão neste tópico. Postando isso aqui para a equipe de revisão da API.

`` `c #
namespace System.Net.Security
{
classe pública parcial SslStream
{
public Task AuthenticateAsServerAsync (opções de SslServerAuthenticationOptions, cancelamento de Cancelamento deoken) {}
public Task AuthenticateAsClientAsync (opções SslClientAuthenticationOptions, cancelamento de Cancelamento deoken) {}

public SslApplicationProtocol NegotiatedApplicationProtocol {get; }
}

public class SslServerAuthenticationOptions
{
public bool AllowRenegotiation {get; definir; }
public X509Certificate ServerCertificate {get; definir; }
public bool ClientCertificateRequired {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public X509RevocationMode CheckCertificateRevocation {get; definir; }
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback {get; definir; }
public EncryptionPolicy EncryptionPolicy {get; definir; }
}

public class SslClientAuthenticationOptions
{
public bool AllowRenegotiation {get; definir; }
public string TargetHost {get; definir; }
public X509CertificateCollection ClientCertificates {get; definir; }
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback {get; definir; }
public SslProtocols EnabledSslProtocols {get; definir; }
public X509RevocationMode CheckCertificateRevocation {get; definir; }
IList públicoProtocolos de aplicativos {get; definir; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback {get; definir; }
public EncryptionPolicy EncryptionPolicy {get; definir; }
}

public struct SslApplicationProtocol: IEquatable
{
public static readonly SslApplicationProtocol Http2;
public static readonly SslApplicationProtocol Http11;
// A adição de outros valores IANA é deixada com base na opinião do cliente.

public SslApplicationProtocol(byte[] protocol) { }

public SslApplicationProtocol(string protocol) { } 

public ReadOnlyMemory<byte> Protocol { get; }

public bool Equals(SslApplicationProtocol other) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public override string ToString() { } 
public static bool operator ==(SslApplicationProtocol left, SslApplicationProtocol right) { }
public static bool operator !=(SslApplicationProtocol left, SslApplicationProtocol right) { }

}
}
`` `

Temos codificação UTF-16 de strings, então é isso que vamos receber. Teremos que convertê-lo internamente para a string UTF-8 e armazenar no "byte []" que estamos transmitindo.

E não é transmitido na api que este é um cozido em autoconversão, por exemplo

public SslApplicationProtocol(string protocol)
  : this(Encoding.UTF8.GetBytes(protocol))
{ }

Na melhor das hipóteses, é um comentário lateral nesta edição.

A especificação não diz que deve ser uma interpretação de string UTF-8 - diz que pode ser

Sequência de identificação: o conjunto preciso de valores de octeto que identifica o protocolo. Esta poderia ser a codificação UTF-8 [RFC3629] do nome do protocolo.

Será comum que os usuários façam seus próprios protocolos e precisem de um construtor de conveniência que faça uma conversão opinativa de string em bytes UTF-8?

Qual subconjunto desses usuários não compreenderá a apis de codificação, portanto, precisamos de ajuda para fazer

var protocol0 = new SslApplicationProtocol(Encoding.UTF8.GetBytes(protocol))

Da mesma forma, qualquer um que se desvie para o não-ascii primitivo com sotaque; eles irão para o byte extra que UTF8 cria para

// 13 bytes in UTF-8, 12 in Windows-1250/ISO-8859-2
var protocol0 = new SslApplicationProtocol("můj protokol");
// 22 bytes in UTF-8, 21 in Windows-1252/ ISO 8859-1
var protocol1 = new SslApplicationProtocol("doppelgängerprotokoll");
// 23 bytes in UTF-8, 12 in Windows-1251/ ISO 8859-5
var protocol2 = new SslApplicationProtocol("мой протокол");

Ou eles preferem usar as codificações Latin 2 ou Windows 1250/1251/1252 de System.Text.Encoding.CodePages que salva um byte e cada caractere ainda tem 1 byte; enquanto em UTF8 seus 2 bytes?

Não acho que a sobrecarga de string seja boa - é um problema, especialmente com a dificuldade em usar utf8 como uma representação de string compacta de pessoas que usam conjuntos de caracteres latinos, pois usam seu formato de 8 bytes localizado https://github.com / dotnet / coreclr / issues / 7083

Todos os IDs de protocolo IANA ALPN atuais são ASCII, portanto, não há exemplos reais ou precedentes de UTF-8 em uso real:

"http/1.1"
"spdy/1"
"spdy/2"
"spdy/3"
"stun.turn"
"stun.nat-discovery"
"h2"
"h2c"
"webrtc"
"c-webrtc"
"ftp"
"imap"
"pop3"
"managesieve"

@karelz Sim, eu quis dizer que a string seria convertida em bytes usando a codificação utf-8.

Essa sobrecarga de string não é uma parte crítica da API e não merece o tempo já gasto nela. Ele fornece uma ligeira melhoria na usabilidade em relação à outra sobrecarga, só isso. Mantenha-o ou exclua-o, mas de qualquer forma, é hora de seguir em frente.

Quer seja byte [] ou Readonlymemory, deve ser copiado no construtor, e uma readonlymemory é apenas um byte [] que não expõe meios para modificar o byte subjacente []. Portanto, não vejo nenhum valor extra na memória somente leitura no construtor.

É um bom ponto. Como você disse, ele precisa ser copiado de qualquer maneira, então ReadOnlySpan<byte> teria mais valor do que ReadOnlyMemory<byte> , pois também pode ser memória de pilha - e você sempre pode obter um de um ReadOnlyMemory<byte>

Ok, cabe ao desenvolvedor implementá-lo e ao MS que deve dar suporte no final. Principalmente, eu queria uma bolsa de opções e estava buscando um construtor, então estou muito feliz por ter conseguido o que queria. Vou fazer um último comentário sobre o construtor, então alguém na MS pode tomar uma decisão e eles são bem-vindos para atualizar o problema principal.

Então, minha palavra final ... como repetido frequentemente, o design da API é para sempre. Por que pegar um barbante e usá-lo? As strings UTF8 podem estar ao virar da esquina de qualquer maneira. Qual é a probabilidade de alguém realmente precisar da sobrecarga agora? Ele pode ser adicionado mais tarde, se necessário, você sempre pode adicionar itens que não podem ser retirados.

Segunda memória somente leitura .... matrizes implicitamente convertidas na memória de qualquer maneira, certo? Portanto, torne-a memória somente leitura e adicione uma matriz se precisar. Se a preocupação for familiaridade, se você estiver fazendo seu próprio protocolo ALPN, espero um certo nível de conhecimento profundo de qualquer maneira.

De qualquer forma, deixe-me saber o que você deseja acima ...

Caso não esteja claro, apenas um construtor de memória somente leitura é minha recomendação.

@benaadams A especificação não diz que deve ser uma interpretação de string UTF-8 - diz que pode ser

Correto. A especificação é intencionalmente obscura por motivos desconhecidos, acreditamos que o UTF-8 é fortemente sugerido. É a nossa interpretação das especificações.
BTW: Discutimos também ASCII. Se alguém precisar de UTF-16 ou outras codificações, pode usar diretamente a sobrecarga de byte [].
Praticamente, não achamos que alguém usará nada diferente das strings ASCII / UTF-8 predefinidas da IANA.

Quão comum será que os usuários façam seus próprios protocolos

Os usuários podem usá-lo para todos os protocolos da IANA que não incluímos como constantes convenientes. Também é o resultado de uma longa discussão interna sobre o uso apenas de string como dados subjacentes em vez de byte [] (que é mais uma prova de recurso). Adicionar sobrecarga de string é conveniente para usabilidade.

Essa sobrecarga de string não é uma parte crítica da API e não merece o tempo já gasto nela. ... de qualquer forma, é hora de seguir em frente.

Concordo 💯!

Strings @Drawaes UTF8 podem estar ao virar da esquina de qualquer maneira.

Não acho uma boa ideia tornar as APIs dependentes umas das outras. Não sabemos se / quando as strings UTF-8 entrarão na plataforma.
Eu vejo a API de string de conveniência como uma forma de passar string ASCII (ou UTF-8), de acordo com a orientação das especificações.

BTW: vou atualizar a postagem principal com cópia e link para a versão mais recente da API de @ Priya91.

A especificação é intencionalmente obscura por motivos desconhecidos, acreditamos que o UTF-8 é fortemente sugerido. É a nossa interpretação das especificações.

Não é, não usa um termo RFC2119 (ao qual a especificação também faz referência)

  1. Linguagem de Requisitos

    As palavras-chave "DEVE", "NÃO DEVE", "REQUERIDO", "DEVE", "NÃO DEVE",
    "DEVE", "NÃO DEVE", "RECOMENDADO", "PODE" e "OPCIONAL" neste
    documento deve ser interpretado conforme descrito em [RFC2119].

Diz "poderia", que não é um desses termos; em vez de "RECOMENDADO" ou mesmo "PODE" - é mais um exemplo de uso - para esclarecer que, embora seja um campo binário, o texto é provavelmente uma opção sensata.

Se você deseja assar esta conversão em; Eu sugeriria ser explícito na API de alguma forma, por exemplo

new SslApplicationProtocol(string uf8Protocol)

Acho que as pessoas que especificam seus próprios protocolos podem lidar com a etapa extra onerosa de usar um codificador explícito para a string em vez de ter um .ctor de sobrecarga de string de convencimento ambíguo para torná-lo mais fácil.

Qualquer forma; Eu levantei minhas objeções, fiz sugestões - não me oponho à decisão final.

Revisão da API: aprovado como proposta mais recente. Discutimos as alternativas sugeridas acima, mas achamos que não deveríamos aceitá-las.

Ok legal, estou ansioso para estar no corefx

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

EgorBo picture EgorBo  ·  3Comentários

bencz picture bencz  ·  3Comentários

btecu picture btecu  ·  3Comentários

jzabroski picture jzabroski  ·  3Comentários

matty-hall picture matty-hall  ·  3Comentários