Runtime: Предлагаемые дополнения SslStream для ALPN

Созданный на 12 авг. 2017  ·  268Комментарии  ·  Источник: dotnet/runtime

Последнее предложение

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

`` С #
пространство имен System.Net.Security
{
общедоступный частичный класс SslStream
{
общедоступная задача AuthenticateAsServerAsync (параметры SslServerAuthenticationOptions, отмена CancellationToken) {}
общедоступная задача AuthenticateAsClientAsync (параметры SslClientAuthenticationOptions, отмена CancellationToken) {}

public SslApplicationProtocol NegotiatedApplicationProtocol { get; }

}

открытый класс SslServerAuthenticationOptions
{
public bool AllowRenegotiation {получить; установленный; }
общедоступный X509Certificate ServerCertificate {получить; установленный; }
public bool ClientCertificateRequired {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
общедоступный X509RevocationMode CheckCertificateRevocation {получить; установленный; }
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback RemoteCertificateValidationCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionPolicy {получить; установленный; }
}

открытый класс SslClientAuthenticationOptions
{
public bool AllowRenegotiation {получить; установленный; }
общедоступная строка TargetHost {получить; установленный; }
общедоступные X509CertificateCollection ClientCertificates {получить; установленный; }
общедоступный LocalCertificateSelectionCallback LocalCertificateSelectionCallback {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
общедоступный X509RevocationMode CheckCertificateRevocation {получить; установленный; }
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback RemoteCertificateValidationCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionPolicy {получить; установленный; }
}

общедоступная структура SslApplicationProtocol: IEquatable
{
общедоступный статический только для чтения SslApplicationProtocol Http2;
общедоступный статический только для чтения SslApplicationProtocol Http11;
// Добавление других значений IANA остается на основе ввода данных пользователем.

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) { }
}

Заметки о встрече 22 сентября 2017 г.

  1. Добавьте ToString () в SslApplicationProtocol, чтобы получить строковую версию байтов.
  2. Добавьте строковый ctor в SslApplicationProtocol для удобства использования, это будет предполагать, что это строка utf8.
  3. Только для чтения памятине является неизменным, нам нужно скопировать байты в ctor, поэтому взяв byte [] вместо ReadOnlyMemory

Заметки о встрече 5 сентября 2017 г.

  1. Используйте обновленное предложение API выше с пакетами опций для методов аутентификации.
  2. Введите тип ApplicationProtocol, чтобы значения ALPN могли быть правильно определены как необработанные байты, имели общие константы и операторы равенства.
  3. Запретите смешивание вызовов между старыми конструкторами и новыми методами. Поддерживаются только минимальные конструкторы, принимающие внутренний поток и логическое значение владения.
  4. Фабричный подход все еще обсуждается.

Дальнейшие примечания

  1. Это будет означать, что будущие параметры могут быть добавлены без проблем с двоичной совместимостью (увеличение свойств конкретных классов вместо изменения перегрузок).
  2. Неасинхронные методы не должны поддерживаться для новых методов, поскольку это асинхронная операция под капотом, поэтому скрытие потоков противоречит текущему мышлению фреймворка. Потребители могут обернуть асинхронные методы блокировкой, если они того пожелают (см. Современный HttpClient).
  3. Я вставил максимальный фрагмент для клиента, но я рад, что он упал, если он является камнем преткновения.
  4. Жетоны отмены существуют для обоих методов в соответствии с текущим концептуальным подходом.
  5. ValueTask не рассматривался, потому что очень редко это не вызывает асинхронных операций, но если новый стандарт должен использовать ValueTask, это тоже нормально
  6. Должен быть предоставлен статический список Http 1.1, Http / 2 и Http / 2 через TLS, чтобы пользователям не приходилось искать его.
  7. Должны быть предоставлены вспомогательные методы для результатов аутентификации, чтобы пользователям не приходилось искать строковые представления. В этом случае я добавил один для IsHttp2.

Возможные проблемы с реализацией

Теперь существует ряд параметров, которые можно изменить во время создания соединения. Один из них - словарь сертификатов. Если потребитель изменяет их, случайно повторно используя параметры конфигурации для другого настроенного соединения, вы можете столкнуться с проблемой безопасности. Один из вариантов - иметь конструктор, но он не одобряется как шаблон API и вместо этого в основном используется в библиотеках / фреймворках более высокого уровня (например, ASP.NET). Это можно решить, сделав внутренний снимок вариантов, но его необходимо будет учитывать при любом моделировании угроз.

Пример использования

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);

использованная литература

dotnet / время выполнения # 17677 SNI
dotnet / время выполнения # 15813 ALPN
dotnet / runtime # 23107 Предыдущее предложение API
RFC 7301 ALPN
RFC 3546 SNI и максимальный фрагмент
Идентификаторы протокола IANA ALPN

[EDIT] Обновлено форматирование @karelz

api-approved area-System.Net.Security

Самый полезный комментарий

@karelz Да, я имел в виду, что строка будет преобразована в байты с использованием кодировки utf-8.

Эта перегрузка строки не является важной частью API и не заслуживает того количества времени, которое уже было потрачено на нее. Это обеспечивает небольшое улучшение удобства использования по сравнению с другими перегрузками, вот и все. Сохраните его или удалите, но в любом случае пора двигаться дальше.

Все 268 Комментарий

cc @geoffkizer @ Priya91 @wfurt

@Drawaes Какова мотивация добавления пакета опций отдельно? Это не работает с текущим дизайном API в SslStream. Вместо того, чтобы вводить так много новых типов, я думаю, что будет проще раскрыть перегрузки методов AuthenticateAs [Client | Server] Async, чтобы иметь протоколы приложений, для которых они хотели бы установить связь, и выполнить свою работу.

Потому что вам также нужна другая перегрузка для SNI или перегрузок. Итак, теперь у вас есть матрица большего размера. Упомянутая предыдущая проблема dotnet / runtime # 23107 изначально использовалась по этому пути, однако количество перегрузок становится чрезмерным. Есть и другие расширения, которые еще предстоит реализовать или рассмотреть как TLS 1.2, так и TLS 1.3 не за горами.

Рассмотрим данные ZeroRtt, которые должны быть представлены как API, которому потребуется еще одна перегрузка. Думаю, вопрос в том, сколько перегрузок - это слишком много?

Я был бы счастлив, если бы типы возвращаемых данных были отброшены и, возможно, были объединены аргументы клиента и сервера (не самое лучшее, но эй), но простое добавление идентификаторов протоколов не распространяется на SNI. Это просто немного сбивает с толку. Это также усложняет работу конечных пользователей: необработанный список протоколов без каких-либо вспомогательных методов выглядит немного недружелюбно.

Читая RFC, я вижу следующее предложение:

`` С #
пространство имен System.Net.Security
{
публичная частичная структура TlsExtension
{
общедоступный TlsExtension (TlsExtensionType extensionType, SpanextensionData) {throw null; }
общедоступный TlsExtensionType ExtensionType {get => throw null; }
общественный диапазонExtensionData {get => throw null; }
}
общедоступное перечисление TlsExtensionType
{
ServerName = 0,
Alpn = 16
}
общедоступный частичный класс SslStream: AuthenticatedStream
{
public virtual void AuthenticateAsClient (string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {бросить ноль; }
общедоступный виртуальный void AuthenticateAsServer (System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {бросить ноль; }
общедоступная виртуальная задача AuthenticateAsClientAsync (string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {бросить ноль; }
общедоступная виртуальная задача AuthenticateAsServerAsync (System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation, IListtlsExtension) {бросить ноль; }
}
}

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 Поскольку вы уже рассмотрели реализацию Alpn, есть ли у вас какие-либо мысли о том, как мы должны здесь определять API?

Подождите, а я могу добавить сюда только одно расширение? Что, если мне нужны ALPN и SNI (что является очень распространенным случаем на стороне сервера). Также как мне предоставить несколько сертификатов? Обычное использование SNI - это

Клиент говорит, что я подключаюсь к www.mycooldomain.com

Сервер говорит, что хорошо, я ожидаю либо www.mycooldomain.com, либо www.mylesscooldomain.com , поскольку вы выбрали первый, и я предоставлю вам сертификат A.

Вам понадобится (xxxxxxxxxx, params TlsExtension [] extension)

И если вы сделаете это сейчас, вы предотвратите любые будущие перегрузки и попадете в неприятности. И он по-прежнему не обеспечивает сопоставление ServerName -> Certificate.

Также это может стать проблемой, dotnet / runtime # 22507, если был пакет параметров, его можно было бы легко добавить, при перегрузке это будет беспорядочно.

Меня немного беспокоит, что подход с использованием пакета параметров превращает относительно ограниченное добавление функций (ALPN) в более крупную сделку (исправьте SslStream API).

Подождите, а я могу добавить сюда только одно расширение?

Перегрузки могут быть использованы для использования IListдля этого случая.

Также как мне предоставить несколько сертификатов?

Я не уверен, как решить эту проблему с текущим дизайном, позвольте мне подумать об этом подробнее. Мне нужно будет исследовать, как openssl / schannel обеспечивают поддержку расширений Tls, и посмотреть, сможем ли мы подражать этой конструкции. Я согласен с тем, что при добавлении дополнительных расширений мы должны разработать расширяемый api.

Вместо того, чтобы объединять все параметры в новый пакет параметров, не лучше ли определить конкретный набор параметров, необходимых для каждого расширения, поддерживаемого Sslstream, и добавить для него свойство в структуре TlsExtension? Таким образом, TlsExtension будет содержать все настройки, необходимые для выполнения этого расширения в рукопожатии.

Итак, вам нужен OptionsBag, но вы не должны использовать текущие параметры. Конечно, это возможно. Если вы хотите понять SChannel и OpenSsl для SNI на стороне сервера, есть два разных подхода (конечно, они разные: P)

OpenSsl Вы предоставляете обратный вызов. Когда обнаруживается SNI, он вызывает обратный вызов. С помощью вашего объекта SSL вы затем можете прочитать SNI из параметров обратного вызова и «переключить» SSL_CTX (контекст), к которому он подключен (следовательно, вы обычно создаете и объединяете контексты со всеми сертификатами заранее, что, кстати, что-то, что должно быть сделано в любом случае, но это другой вопрос на другой день).

Для SChannel вы предоставляете список сертификатов при создании объекта аутентификации, а не один, и он выбирает на основе SNI.

Для ALPN вы предоставляете расширение для Schannel в качестве дополнительного буфера токенов. Вы в основном пишете это сам. Затем есть недвижимость, которую вы можете осмотреть, чтобы найти согласованную.

Для OpenSsl вы снова устанавливаете протоколы, и на стороне сервера снова вы получаете обратный вызов.

Так что я не уверен, что реализация действительно дает нам здесь большую помощь, в основном для каждого нового расширения оно вручную кодируется в OpenSsl, и они добавляют для них определенные методы / обратные вызовы.

@geoffkizer в своем ответе вы проигнорировали SNI, что очень важно по ряду причин (у меня есть внутренние), но @Tratcher даст вам некоторые, я уверен.

Плюс, наконец, у SslStream нет токена отмены, что немного вздор, рукопожатия могут быть легко остановлены клиентом, и сегодня нет другого способа остановить его, кроме как убить базовый поток, поэтому верхнему приложению требуется доступ к сетевому потоку.

Обновлено, чтобы выпустить результаты.

Так что я не уверен, что реализация действительно дает нам здесь большую помощь, в основном для каждого нового расширения оно вручную кодируется в OpenSsl, и они добавляют для них определенные методы / обратные вызовы.

SslStream использует openssl и schannel, для любых API-интерфейсов, которые мы предоставляем, нам нужно иметь возможность подключать его к этим уровням друзей. Так что здесь стоит подумать о реализации и почерпнуть идеи из уже используемых фреймворков.

Конечно, я согласен, я просто указал на то, что, к сожалению, они оба диаметрально противоположны. Иначе жизнь была бы слишком легкой :)

К вашему сведению, дальнейшее расследование, похоже, вы можете предоставить обратный вызов для SNI на SChannel. Это означает, что если обратный вызов отображается в SslStream, то сценарий чего-то вроде Let's Encrypt, предоставляющего динамический сертификат, возможен как для SslStream, так и для OpenSsl. Таким образом, идеальным вариантом будет либо список, либо обратный вызов, если может быть предоставлен только один, обратный вызов будет предпочтительнее.

кажется, вы можете предоставить обратный вызов для SNI на SChannel

Вы можете предоставить для этого ссылку на API?

@Drawaes Я не игнорирую SNI или отмену. Я просто заметил, что единственная обязательная функция для 2.1 - это ALPN.

Тем не менее, @stephentoub и я сегодня немного

Думаю, было бы полезно разбить это предложение на несколько частей:

(1) Новые API AuthenticateAsClient / Server, которые принимают пакеты опций и токен отмены, а также определение пакетов опций, соответствующих существующим API. Мы должны четко понимать используемые здесь принципы. Я считаю, что мы планируем взять аргументы конструктора и аргументы метода и поместить их в пакет параметров. Это верно?
(2) Предложение расширить эту модель для ALPN.
(3) Предложение расширить эту модель для SNI.

отредактировано, чтобы добавить:
(4) Предложение расширить эту модель для обработки MaxFragment.

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

Я бы посоветовал:
"ServerNameIndication" (или даже просто "ServerName")
"ApplicationProtocolName"

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

Основываясь на приведенном выше обсуждении, похоже, что здесь мы движемся к модели обратного вызова. Это верно? Как это выглядит?

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

Они не должны допускать значения NULL; у них просто должны быть соответствующие значения по умолчанию.

Должен быть предоставлен статический список Http 1.1, Http / 2 и Http / 2 через TLS, чтобы пользователям не приходилось искать его.

Мы должны определить их явно как часть предложения ALPN.

Действительно ли нам нужен http2 без TLS? Есть ли какие-либо другие определенные имена протоколов, которые мы должны включить?

Один из вариантов - иметь конструктор, но он не одобряется как шаблон API и вместо этого в основном используется в библиотеках / фреймворках более высокого уровня (например, ASP.NET). Это можно решить, сделав внутренний снимок вариантов, но его необходимо будет учитывать при любом моделировании угроз.

Вот мое предложение: сделайте так, чтобы эти объекты зависали при использовании.

Для таких параметров, как RemoteCertificateValidationCallback, которые я могу указать сегодня в конструкторе, что произойдет, если я передам что-то конструктору, но затем оставлю это значение null в сумке параметров? Используем ли мы значение из пакета параметров (null) или значение из конструктора? Аналогично для других аргументов конструктора.

Хорошо, я хорошо разделил SNI и ALPN, чтобы они были отделены от изменения пакета опций. Я запущу для них два дополнительных вопроса и отвечу на конкретные вопросы по ним, а здесь остановлюсь только на концепции пакета опций.

Как замораживается манифест использования? Есть ли внутренний флаг и при попытке что-то выставить после этого выкидывает? Если да, то какое здесь уместное исключение? Что, если сертификаты останутся списком или словарем? Мы также должны заморозить список / словарь .... как это сделать? (Идея нравится, просто не знаю, как она реализована).

Другой вариант - иметь хэш-код или сравнение, которое проверяет настройки + сертификаты и т. Д. (Дочерние объекты) и проверяет внутренний кеш. Если его нет, скопируйте и вставьте его туда. Уже есть внутренний кеш учетных данных.

Вы можете использовать счетчик ссылок просто для удаления элементов. Тогда на клиенте это не будет большой нагрузкой, а на сервере оно редко опускается до нуля, если только ваш сервер не делает много, и в этом случае выделение классов или два не будут иметь значения?

Мысли? Я бы предпочел уточнить некоторые детали, прежде чем снова обновлять предложение.

Почему проблема с изменяемым пакетом опций? Это ничем не отличается от всего остального: вы передаете это API, и API ожидает, что он будет в согласованном состоянии на протяжении всей операции ... если вы измените его во время выполнения операции, это ошибка, нет отличается от любого другого случая, когда вы что-то мутируете, в то время как вызываемый метод зависит от этого (например, буферы, переданные для чтения / записи). Если вы хотите относиться к нему как к неизменяемому и просто никогда не изменять его после создания, вы можете это сделать. Если вы хотите рассматривать его как изменяемый, чтобы вы могли изменять его между использованиями, вы можете это сделать.

Как замораживается манифест использования? Есть ли внутренний флаг и при попытке что-то выставить после этого выкидывает? Если да, то какое здесь уместное исключение?

Ага. InvalidOperationException. Это то, что HttpClient / HttpClientHandler делает для своих настроек. См. Https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L589

Что, если сертификаты останутся списком или словарем? Мы также должны заморозить список / словарь .... как это сделать? (Идея нравится, просто не знаю, как она реализована).

При необходимости мы могли бы сделать список / словарь, который можно замораживать.

Я тоже могу позволить ему быть изменяемым. Кажется, самый простой подход.

Для конструктора vs option bag params. Либо должно быть выброшено исключение, если они указаны в конструкторе и используются параметры. Или бросайте, только если они указаны в конструкторе и разные, или возьмите наиболее ограничительный (в вашем примере используйте обратный вызов, а не нуль).

Я склоняюсь к метанию ... чисто из соображений "безопасности". Меньше двусмысленности и меньше перестановок и угадываний правил обычно более безопасным. Опять же, какое исключение здесь уместно?

Я много думаю о том, как вам нужно будет использовать эти параметры ... на данный момент я не вижу немедленной проблемы, кроме внутренней производительности (вы можете создать один ctx и повторно использовать его снова и снова в openssl), но я думаю, что с этим можно справиться изнутри. Так что пока оставим заморозку в стороне. Мутировать это :)

Я предполагаю, что просто добавляю такие вещи, как согласованное по протоколу имя сервера и т. Д. В качестве свойств, а не объекта результатов? (Я предпочитаю свойства, но хочу проверить).

Таков образец сегодня, верно? Меня это устраивает.

Пакет параметров звучит хорошо, и наличие этого пакета параметров, специфичного для TlsExtension, делает новый API менее запутанным в том, что он намеревается делать. На данный момент сохраняйте поддержку пакета параметров только для тех настроек, которые требуются для alpn и sni, поскольку и когда необходимо поддерживать новые расширения tls, этот пакет можно расширить, добавив новые свойства. Если есть реальный запрос пользователя на расширение пакета опций до старых настроек, то мы, несомненно, сможем расширить apis.

`` С #
пространство имен System.Net.Security
{
общедоступная частичная структура TlsExtensionConfig
{
общедоступный TlsExtension (TlsExtensionType extensionType) {выбросить ноль; }
общедоступный TlsExtensionType ExtensionType {get => throw null; }
// поддержка sni со стороны сервера
общедоступный IDictionaryServerCertificates {получить; установленный; }
// поддержка sni со стороны клиента - это обязательно, поскольку targetHost уже является параметром в
// аутентифицировать API?
общедоступная строка ServerName {получить; установленный; }
// поддерживаем alpn
публичный IListApplicationProtocols {получить; установленный; }
}

[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. Это ужасно
  2. Это добавляет беспорядка к тому, что некоторое время назад должно было быть сумкой опций.
  3. Добавление набора параметров для некоторых, но не для всех, сбивает с толку
  4. Если другие варианты не будут перемещены сейчас ... они никогда не будут перемещены.

Если я чему-то научился, если вы измените концепции и совершите этот прыжок, вам лучше всего очистить все биты за один раз, иначе у вас навсегда останутся некоторые варианты там, а некоторые в другом месте. Когда есть еще 5 или шесть вариантов конфигурации в SslStream и в сумке, теперь у вас есть 1/2 вашей конфигурации где-то и 1/2 где-то еще.

Также, если вы сделаете это специфичным для TlsExtension, что, если вы добавите параметр, который не является TlsExtension. Скажем, у нас есть возможность указывать шифры (часто запрашиваемая функция), и это не расширение? Теперь у нас есть обсуждение «перегружаем ли мы его или добавляем в набор опций для расширений, но это не расширение».

Мне очень нравится идея всех опций в сумке опций, методов аутентификации для ТОЛЬКО поддерживающих асинхронность, и всех с отменой. Это совсем не сбивает с толку, потому что, если вы используете «старый способ» и не хотите или не нуждаетесь в новых вещах, вы просто не получите этого, просто продолжайте делать то, что делали всегда.

После обсуждения в автономном режиме с @ericstj у нас есть рекомендации по проектированию фреймворка, которые рекомендуют делать только самую длинную перегрузку виртуальной, очевидно, что текущие перегрузки Sslstream уже нарушают это. В конце концов было замечено, что добавление перегрузок будет плохой конструкцией. Кроме того, исходный дизайн следует шаблону создания-установки-вызова, который широко используется в структуре. @Drawaes Я буду работать над этим, так как это срочно нужно сделать. Будем создавать прототип с предлагаемыми API, я думаю, мы можем столкнуться с проблемой с реализацией Unix, где нам понадобятся эти параметры во время создания объекта SslStream, во время создания экземпляра sslcontext, и тогда мы будем устанавливать обратные вызовы. Становится ясно, нужны ли нам эти параметры во время нового SslStream или можно отложить их позже при создании прототипа. Я обновлю тему, когда у меня будет больше информации.

Я могу ответить вам на вопрос OpenSsl прямо сейчас. Вы не создаете контекст (следовательно, вам нужны какие-либо обратные вызовы и т. Д.), Пока вы не нажмете закрытый метод AcquireServer / Client Credentials на SecureChannel. Вы не нажмете это, пока не вызовете первое создание токена, и это не произойдет, пока вы не начнете рукопожатие. В любом случае вы не можете создать объект SslCtx без сертификата, который предоставляется в операции AuthenticateAsClient / Server.

Я не уверен, как перемещение всех настроек в объект настроек нарушает шаблон вызова create set, на самом деле я бы посоветовал, чтобы он усиливал его, перемещая текущие настройки из конструктора в «новом» случае. Что касается создания виртуальных перегрузок, я не думаю, что это когда-либо было частью дизайна?

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

Что-то вроде этого для пакета опций было бы хорошим началом ... для ALPN я бы использовал перечисление, для протоколов и фиксированных буферов за кулисами они устанавливаются IANA и требуют изрядных усилий для изменения. Перечисление [Flags] позволит указать несколько, а затем "волшебные" строки могут быть жестко закодированы, чтобы не допустить ошибок.

Что-то вроде этого

[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
    }

Вы, вероятно, захотите урезать список для того, что вам не нужно поддерживать (например, быстро), но просто для того, чтобы получить представление о том, как это будет выглядеть.

Я не уверен, как перемещение всех настроек в объект настроек нарушает шаблон вызова create set

Я не сказал, что это нарушает, я фактически сказал, что исходный дизайн этого предложения (вверху вопроса) является хорошей отправной точкой.

Тогда я не понял :) Итак, вы делаете прототип, поэтому мне не следует обновлять предложение API до этого?

Итак, вы делаете прототип, поэтому мне не следует до этого обновлять предложение API?

Я хотел бы сначала реализовать и протестировать дизайн, прежде чем выносить его на api-review. Я думаю, что у меня достаточно информации, чтобы продолжить, как только я опубликую свои изменения, вы можете покопаться в них, чтобы предложить улучшения. Звучит отлично?

Идеально ! Удачного кодирования.

для ALPN я бы использовал перечисление

Почему бы просто не использовать струны? Это имеет то преимущество, что при добавлении нового протокола вы можете указать строку самостоятельно.

Кстати, откуда вы взяли этот список? Я не знаю, где находится «официальный» список IANA.

Кроме того, давайте не перестанем работать над предложением API. Мы должны хотя бы написать, где мы находимся в настоящее время, и перечислить оставшиеся нерешенные вопросы. Возможно, мы сможем добиться прогресса в некоторых из них независимо от работы Прии по созданию прототипа.

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

Это официальный (поскольку в RFC 7301 написано, что это реестр). Далее, эффективность в том, чтобы иметь возможность статически удерживать строки, поскольку вам нужны байты ASCII (или, может быть, его utf8, но я уверен, что это ASCII / ANSI что угодно), а затем добавить их в буфер списка, они очень редко меняются Отсюда легко изменить флаг, и пользователи будут делать ошибки.

Кто будет помнить, что HTTP / 1.1 - это строка "http / 1.1", но
http / 2 - это h2c, а http / 2 поверх tls - это h2?

Например, вы не знаете, где найти список, что надеяться на то, что разработчик, сидящий за своим корпоративным столом, должен подключиться к серверу с http1?

Скажем так, я хорошо разбираюсь в строках, черт возьми, я бы предпочел использовать байты :) Но я прочитал спецификации вперед и назад, и я не уверен, что у других есть.

Я согласен, я не ожидаю, что пользователи запомнят строки. Я просто говорю, что у нас должен быть статический список строк вместо перечисления.

Что-то вроде:

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
};

Да, не используйте enum для alpn. Это открытый набор данных, и перечисление не позволит поддерживать дополнительные значения без обновления фреймворка, не говоря уже о пользовательских значениях. Это значение также не влияет на реализацию ssl, поэтому нет причин ограничивать его. Подобная ошибка была сделана в прошлом для кодов статуса http, но, по крайней мере, вы могли использовать кастинг, чтобы обойти это.

В спецификации также говорится, что порядок списков имеет значение и должен быть настраиваемым. Использование перечисления флагов предотвращает это.

Однако известные константы - это хорошо.

Честно говоря, известные константы мне подходят.

Я полагаю, если бы мы действительно хотели соответствовать спецификации, мы бы использовали ReadOnlySpan<byte> в качестве типа данных alpn. Это позволило бы избежать таких проблем, как кодирование и корпус, и было бы более эффективно сериализовать по сети. Известные константы сделают это пригодным для использования.

У нас не может быть списка диапазона.

для статических строк или буферов или что бы я ни сказал
HTTP1.1
HTTP / 2 через SSL
WebRTC
Конфиденциально WebRTC
Pop3
IMAP
FTP

на данный момент должно быть достаточно. Http / 2 без tls кажется глупым, потому что вы уже подключаетесь к конечной точке TLS.

Я бы сказал, что поведение ALPN должно быть ...

Если вы предоставили протоколы, а другая сторона вообще не участвовала (клиент никогда не отправлял или сервер не отвечал), согласовывается нулевой протокол. Если совпадений нет, но отправлены обе стороны, то это исключение с ошибкой аутентификации. А в остальном - совпадение.

Основываясь на отдельных обсуждениях, я думаю, что нашему пакету опций также понадобится флаг для поддержки отключения повторного согласования, поскольку это, по-видимому, требуется для HTTP2.

К вашему сведению, вы упомянули выше, что ALPN была насущной необходимостью, но

Реализация TLS ДОЛЖНА поддерживать расширение [TLS-EXT] индикации имени сервера (SNI) для TLS. Клиенты HTTP / 2 ДОЛЖНЫ указывать имя целевого домена при согласовании TLS.

Боюсь, что SNI тоже ДОЛЖЕН ...

Мы уже поддерживаем SNI на стороне клиента, и я думаю, это все, что необходимо для совместимости с HTTP2.

Хороший момент, здесь ясно ОБЯЗАТЕЛЬНО, однако лазейка в том, что вам не нужно ничего отвечать, даже если он поддерживает это ... :) вернемся на задний план для SNI. (Предполагая, что клиент действительно отправил его, я не проверял, но верю вам на слово :))

Хммм, может быть, нам стоит исследовать это ... предположение может быть ошибочным, что sni работает на стороне клиента

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

HttpClient в .NET Core 2.0 не использует SslStream.

На самом деле ... так что он использует завиток и какой-то волшебный api для окон ... но новый управляемый будет правильным?

На самом деле ... так что он использует завиток и какой-то волшебный api для окон ... но новый управляемый будет правильным?

HttpClientHandler использует libcurl в Unix и WinHttp в Windows. Новая управляемая реализация HttpClientHandler действительно использует SslStream:
https://github.com/dotnet/corefx/blob/2efb83a3170ec14cafe6a14ea37edb037eb99836/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnectionHandler.cs#L57

Круто, и мы знаем, что SSLStream точно отправляет SNI ... или мне следует отключить wirehark?

Круто, и мы знаем, что SSLStream точно отправляет SNI ... или мне следует отключить wirehark?

Если вы работаете волонтером, проверка - это всегда хорошо. :)

По крайней мере, в окнах
image

Статус: у меня есть прототип для Unix, но API alpn поддерживаются только с версии openssl 1.0.2. В настоящее время работаю над исправлением моей машины, чтобы получить эту версию для сборки моей реализации.

Какая версия является "минимальной" для ядра .net в наши дни? (Исключая macos)

Меня смущают упоминания о h2c - когда имеет смысл обсуждать его в контексте SslStream? Клиент и сервер никогда не должны согласовывать h2c с помощью ALPN, только h2.

Извините, если я что-то пропустил в обсуждении (как бы бегло просмотрел).

:) тогда соглашаемся как я сказал

на данный момент должно быть достаточно. Http / 2 без tls кажется глупым, потому что вы уже подключаетесь к конечной точке TLS.

Итак, экспериментируя с прототипом, я получил следующую структуру API:

SslAuthenticationOptions -> пакет параметров, содержащий настройки, общие для клиента и сервера, установленные при инициализации конструктора. Это гарантирует, что мы не сможем решить, какое значение выбрать, сделав эти параметры доступными только через конструктор, а не через какие-либо методы Authenticateas *.

SslClientAuthenticationOptions, SslServerAuthenticationOptions -> этот пакет содержит параметры, специфичные для клиента или сервера и устанавливаемые с помощью одного из методов AuthenticateAs *, в течение которого обнаруживается идентификатор SslStream, либо как клиент, либо как сервер.

`` С #
открытый класс SslAuthenticationOptions
{
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback UserCertificateValidationCallback {получить; установленный; }
общедоступный LocalCertificateSelectionCallback UserCertificateSelectionCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionOption {получить; установленный; }
}

открытый класс SslClientAuthenticationOptions
{
общедоступная строка TargetHost {получить; установленный; }
общедоступные X509CertificateColllection ClientCertificates {получить; установленный; }
public bool CheckCertificateRevocation {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
}

открытый класс SslServerAuthenticationOptions
{
общедоступный X509Certificate DefaultServerCertificate {получить; установленный; }
общедоступный словарьServerCertificates {получить; установленный; }
public bool ClientCertificateRequired {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
public bool CheckCertificateRevocation {получить; установленный; }
}

открытый класс SslStream: AuthenticatedStream
{
общедоступный SslStream (Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) {}
public SslStream (Stream innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions sslAuthenticationOptions) {}
общедоступная виртуальная задача AuthenticateAsClientAsync (SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken) {}
общедоступная виртуальная задача AuthenticateAsServerAsync (SslServerAuthenticationOptions sslServerAuthenticationOptions, CancellationToken cancellationToken) {}

public string NegotiatedApplicationProtocol { get; }

}
`` ''

Есть ли у SSLStream свойство, чтобы узнать указанное имя хоста? В остальном это хорошо выглядит. Надеюсь, сегодня вечером я обновлю проблему с дизайном. Мне нравится идея разделения конструктора / аутентификации.

Красиво, значит, все старые конструкторы стали обертками для нового? И то же самое для методов AuthenticateAsClient / Server?

Это также означает, что APLN работает для всех старых API-интерфейсов AuthenticateAsClient / Server, а не только для новых?

Подразумевается, что SslAuthenticationOptions являются изменяемыми между вызовом конструктора и вызовом Authenticate ... Если это правда, то вам не нужны новые конструкторы. например

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

Можно ли сделать EnabledSslProtocols допускающим значение NULL, чтобы он мог плавать с системными значениями по умолчанию? Или для этого не работает None?

Не очень в этом заинтересован ... Вызывайте выделения, которые вы хотите кэшировать, чтобы этот объект использовать снова и снова. Также могут быть некоторые хитрости, которые мы можем сделать с хэш-кодом и кешем ...?

Если параметры будут отображаться как свойство, их i / изменчивость должна быть более явной / принудительной.

@Tratcher
Правильный путь будет,

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

Параметры не могут быть изменены после создания объекта, так как это не будет отражено в контексте безопасности, который создается после первой аутентификации. Это означает, что контекст безопасности не может быть изменен после создания, его необходимо сначала удалить, чтобы инициализировать его новыми значениями. Таким образом, изменение этого свойства сбивает с толку, поскольку изменения не будут иметь никакого значения после установления контекста безопасности.

И да, все старые конструкторы и методы аутентификации будут оболочками для этих новых API.

если sslStream.AuthenticationOptions.ApplicationProtocols = new[] { HTTP2, HTTP11 }; не сработает, то когда я попробую, должна возникнуть ошибка компилятора или времени выполнения.

Объект параметров является только получателем, но сам список протоколов приложения является изменяемым в вашем дизайне выше.

Теперь, когда я думаю об этом, возможно, не так важно думать о сценарии изменения параметров вне конструктора, поскольку SslStream не позволит вам выполнять несколько аутентификаций, и эти свойства требуются только во время фазы подтверждения. . Поэтому я думаю, что вышеупомянутый способ также приемлем, параметры можно изменять любое количество раз до рукопожатия, после рукопожатия, даже если параметры изменены, это не влияет на функции шифрования / дешифрования при записи / чтении.

:) Мне такой вариант нравится ...

учли ли мы выделения в строке -> ascii и поисках alpn? Некоторые люди могут расстроиться, если мы добавим больше выделений. Есть ли способ настроить API, чтобы помочь с этим?

Если у вас есть перегрузка public SslStream(Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) { } вы можете оставить ее на усмотрение вызывающей стороны, чтобы решить, хотят ли они повторно использовать или изменить, при условии, что вам было ясно, что это исходный экземпляр, а не какая-то копия.

Мне нравится такой подход. Я думаю, что размещение пакета опций на конструкторе имеет большой смысл.

Я не думаю, что нам вообще нужно раскрывать свойство AuthenticationOptions. Кажется, это ничего не добавляет и немного сбивает с толку. Сегодня мы не раскрываем существующие аргументы конструктора в SslStream (например, RemoteCertificateValidationCallback).

В одном я не уверен: в этом предложении фактически есть два разных «пакета параметров», один из которых используется для конструктора, а другой - для методов AuthenticateAs. (Фактически три, поскольку один для AuthenticateAsClient, а другой для AuthenticateAsServer).

Это означает, что каждый потребитель должен будет создать два пакета опций (или использовать существующие API).

Если мы переходим к пакетам опций, мне интересно, не лучше ли нам просто иметь тот, который используется в конструкторе, с вариантами клиента и сервера. Это позволяет потребителю создать только одну сумку с опциями.

Мне это нравится, потому что если бы вы это сделали, тогда это был бы AuthenticateAsync ()?

Также я ломал голову над тем, какой смысл было бы выставлять пакет параметров на SslStream, и пришел к тому же выводу, ничего хорошего. Я бы сказал убрать это для наглядности.

Я не думаю, что нам вообще нужно раскрывать свойство AuthenticationOptions. Кажется, это ничего не добавляет и немного сбивает с толку. Сегодня мы не раскрываем существующие аргументы конструктора в SslStream (например, RemoteCertificateValidationCallback).

Даже если мы не раскрыли это, существует та же проблема, я все равно могу изменить значение параметра. Меня больше беспокоит такой сценарий,

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

AuthenticateAsServer собирается что-то зарегистрировать или что-то еще? Из-за этого сейчас я против параметров конструктора. Лучше всего иметь пакет параметров клиента, пакет параметров сервера, открытый с помощью методов Authenticateas, а затем, чтобы этот пакет параметров не включал существующие параметры, доступные через текущий конструктор SslStream. Мысли?

Чем это отличается от

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

?

Проблема, с которой вы столкнулись с этим (и я не против, просто сейчас нужно подумать об этом). Вернемся ли мы к вопросу «что, если пользователь что-то устанавливает в конструкторе и в пакете опций?»

Я думаю, что @Tratcher спросил на

К вашему сведению, причина не выставлять варианты ...

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.

Меня меньше беспокоит изменение пользователем между созданием и аутентификацией, потому что это обычно контролируется человеком / кодом, устанавливающим соединение.

передать поток другому коду ... и он изменит ваши параметры, которые вы используете, чтобы сделать больше подключений.

Имея в виду? Теперь это будет новое соединение, и оно снова выполнит рукопожатие с указанными параметрами.

Я имею в виду это

Сервер устанавливает TCP-соединение, а затем - ssl-поток. SslStream передается пользовательскому коду для чтения / записи. Но теперь пользовательский код может изменять настройки параметров, которые, если вы повторно используете их для создания новых подключений, он изменит.

Но теперь пользовательский код может изменять настройки опций.

Эти изменения неэффективны, поскольку он не будет повторно выполнять квитирование sslstream. После аутентификации потока sslstream нельзя использовать повторно. Его нужно утилизировать.

Верно, но скажите (я знаю, что есть фреймбуферы и т. Д., Но давайте предположим)
Kestrel создает пакет параметров и хочет кэшировать и использовать его для следующих 100 подключений.
Он устанавливает соединение с пакетом опций, а затем передает sslstream в код пользователя.

Это правда, что вы не можете изменить текущее соединение с помощью кода пользователя (случайно или иным образом), но вы можете изменить настройки для всех будущих подключений.

@geoffkizer Соответствует ли это предложение вашему

`` С #
открытый класс SslAuthenticationOptions
{
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback UserCertificateValidationCallback {получить; установленный; }
общедоступный LocalCertificateSelectionCallback UserCertificateSelectionCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionOption {получить; установленный; }
}

открытый класс SslClientAuthenticationOptions: SslAuthenticationOptions
{
общедоступная строка TargetHost {получить; установленный; }
общедоступные X509CertificateColllection ClientCertificates {получить; установленный; }
public bool CheckCertificateRevocation {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
}

открытый класс SslServerAuthenticationOptions: SslAuthenticationOptions
{
общедоступный X509Certificate DefaultServerCertificate {получить; установленный; }
общедоступный словарьServerCertificates {получить; установленный; }
public bool ClientCertificateRequired {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
public bool CheckCertificateRevocation {получить; установленный; }
}

открытый класс SslStream: AuthenticatedStream
{
общедоступный SslStream (Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) {}
public SslStream (Stream innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions sslAuthenticationOptions) {}
общедоступная виртуальная задача AuthenticateAsync (bool isServer, CancellationToken cancellationToken) {}

public string NegotiatedApplicationProtocol { get; }

}
`` ''

:) Мне это нравится, в остальном не уверен ... как вы думаете?

Это правда, что вы не можете изменить текущее соединение с помощью кода пользователя (случайно или иным образом), но вы можете изменить настройки для всех будущих подключений.

Я думаю, что это забота пользователя о разработке кода по своему усмотрению, я не уверен, следует ли нам здесь остерегаться, только то, что параметры должны быть неизменными или оставить их изменяемыми и гарантировать, что объект параметров не изменяется с помощью sslstream внутри для хранения любой информации о состоянии sslstream.

Эта модель начинает мешать обнаружению опций и упрощает пропуск обязательных параметров, таких как DefaultServerCertificate.

Минимум:

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 и @Tratcher : Да, мне они

  • Нам не нужен isServer для AuthenicateAsync, и нам не нужны отдельные перегрузки для AuthenticateAsClient / Server. Мы можем узнать об этом по параметрам, указанным в конструкторе.
  • Я не уверен, что методы аутентификации должны быть виртуальными. Я знаю, что все это есть сегодня, но я не уверен, какую ценность это на самом деле добавляет.
  • Я думаю, что наличие явных перегрузок ctor для клиента и сервера имеет смысл. На самом деле вы не можете использовать здесь базовый класс, поэтому нет смысла иметь ctor, который принимает базовый класс.
  • Интересно, стоит ли поменять порядок параметров на leaveInnerStreamOpen. То есть (innerStream, options, leaveInnerStreamOpen). Мне также интересно, должно ли это быть необязательным параметром с default = false. Не уверен, что сейчас думают о необязательных параметрах ...

Следует рассмотреть кое-что более радикальное:

Насколько я понимаю, единственная причина, по которой мы отделяем метод Authenticate от ctor, заключается в том, что метод Authenticate является асинхронным.

Вместо этого мы могли бы рассмотреть возможность переноса метода Authenticate в сам пакет параметров и сделать так, чтобы он выполнял как создание, так и аутентификацию. Это сокращает процесс до одного шага и позволяет избежать немного странного состояния «поток создан, но не аутентифицирован».

Это выглядело бы примерно так:

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

В этой модели имеет смысл немного изменить именование - SslClientOptions => SslClientFactory и AuthenticateAsync => CreateAsync или что-то в этом роде.

Мысли?

Еще одна небольшая мысль:

Мы должны добавить логическое значение типа «AllowRenegotiation» в пакет базовых опций, поскольку, похоже, нам это нужно для поддержки HTTP2. Можно также включить это сюда.

Я расскажу об этом, хотя это уже обсуждалось ранее, но поскольку мы почти что-то движемся в этом направлении .... (и @geoffkizer назвал это фабрикой, поэтому я виню его)

Сегодня схема использования хотя бы OpenSsl неверна, объект SSL_CTX выделяется для SslStream, а затем выделяется объект SSL. Зачем вам делать и то, и другое для связи? Почему они его не сделали? Почему бы вам не создать новый объект сертификата X509 для каждого соединения (обычно) и вместо этого взять ссылку на единицу (с подсчетом ссылок).

Ответ прост: использование SSL_CTX для каждого соединения неверно. Он разработан, чтобы у вас был один SSL_CTX. Это может иметь большое значение, поскольку OpenSSL может совместно использовать различные ресурсы, такие как перезапуск буферов. Но сегодня это практически невозможно из-за количества настроек, которые могут меняться между созданием двух потоков.

Однако, если бы у нас мог быть «объект», который фактически мог бы быть отслеживающим, или фабричным, или каким-либо другим объектом (именно поэтому для меня важен вопрос «изменяемый / неизменяемый / Build ()»), тогда мы могли бы создать для этого SSL_CTX и повторно использовать Это. Также существует много блокировок для создания SSL_CTX, поскольку он имеет глобальную блокировку ... обратитесь к моему обсуждению блокировки OpenSsl, поэтому многие быстрые соединения оказывают давление на них.

В любом случае просто идея, что если бы это можно было спроектировать, это могло бы облегчить путь к более быстрому потоку SSL, по крайней мере, для Linux. Что было бы неплохо, потому что в модели много прыжков через обруч, потому что она была спроектирована вокруг SChannel, что понятно, но это может быть небольшой дизайнерский кивок, который можно было бы дать?

Я сохранил AuthenticateAsClient / Server для согласованности / доступности.

Перемещение AuthenticateAsync с SslStream в параметры перемещает вас к совершенно другому шаблону использования. Если бы мы начинали с нуля, я бы сказал, что да, но я бы не хотел разветвлять существующий API таким образом, это затруднило бы внедрение новых функций в существующий код. Подходит ли corefx для этого?

Перемещение AuthenticateAsync с SslStream в параметры перемещает вас к совершенно другому шаблону использования.

Я согласен. Но я чувствую, что мы уже на полпути по пути "другого шаблона использования". Самое последнее предложение требует, чтобы пользователи изменили вызов конструктора и вызов Authenticate нетривиальными способами. Когда мы идем по этому пути, мне не ясно, где мы должны остановиться.

В качестве альтернативы, если мы хотим минимизировать влияние на шаблон использования, мы могли бы просто добавить пару новых перегрузок ctor.

Вот спектр, как я его вижу:

(1) Просто добавьте новые перегрузки ctor, которые принимают "IListapplicationProtocols "и" bool supportsRenegotiation "
=> Наименьшее изменение, наименьшее влияние на текущую модель использования
(2) Добавьте новый ctor, который принимает OptionBag, с существующими параметрами ctor и двумя новыми выше.
=> Несколько влияет на текущую модель использования, но только для конструктора
=> Немного странно в том, что ctor основан на OptionBag, но методы аутентификации не
=> Может быть расширен в будущем, чтобы он выглядел больше как (3) или (4) - но если мы думаем, что хотим сделать это в конечном итоге, вероятно, имеет смысл сделать это сейчас, когда мы вносим изменения в модель использования, скорее затем вывести изменения в модели использования с течением времени и получить множество разных способов добиться того же.
(3) Добавьте новые ctors, которые принимают ClientOptionsBag / ServerOptionsBag, а также новые методы аутентификации с минимальными параметрами.
=> Большие изменения в модели использования
=> Более последовательный, чем (2), в том смысле, что все параметры находятся в одной сумке параметров
(4) Заводская модель
=> Большое изменение модели использования
=> Если бы мы начинали с нуля, мы, вероятно, сделали бы это

Я не совсем уверен, где здесь приземлиться. (2) - мой наименее любимый.

Это так, и хотя я считаю, что это «идеальный» сценарий (согласно предыдущей библиотеке, которую я вначале взломал для конвейеров), мы могли бы сделать что-то вроде X509 здесь, где он в основном неизменен по своей природе, и вы просто ссылаетесь на это, поэтому, если пользователь избавится от него, вам все равно.

К вашему сведению, мой первоначальный дизайн был больше похож на сокеты TCP, где у них есть Listener, который предоставляет вам сокет для каждого подключения, и это был «SecureListener», который принимает поток и дает вам безопасный поток.

Это также означает, что мы можем избежать выделения / работы, скажем, для списка ALPN, потому что, как я вижу выше, нам придется преобразовать в строки ansi, сгенерировать буфер с заголовком, а затем поместить каждый с заголовком, где как это можно / нужно сделать один раз.

Еще одна мысль.

(1) и (4) не исключают друг друга. То есть мы могли бы просто выполнить (1) сейчас с намерением добавить заводскую модель (4) поверх в будущем. Я думаю, что мы могли бы построить (4) без каких-либо изменений в самом SslStream.

Если бы 4. была сильной возможностью, то я бы посоветовал сделать 1 прямо сейчас, потому что если вы частично сделаете большое изменение, то действительно большое изменение будет невозможно позже :) или, по крайней мере, это то, что я узнал, работая в супер-больших компаниях. ;)

если вы частично сделаете большое изменение, то действительно большое изменение позже будет невозможно :)

Ага, согласился.

Помимо наименования / общего стиля, у меня было что-то вроде

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

Это позволило много оптимизировать контексты, буферы, буферы ALPN, как вы их называете. Если бы происходил большой редизайн, я бы настаивал на этом. Я также понимаю временные ограничения Http / 2 ALPN, поэтому я бы сказал, что вставьте их как перегрузку ... и второе, что сделано, начните обсуждение редизайна :)

На реальном сервере, конечно, вы бы сделали несколько sslStreams из "context / factory / [insert name]"

Да, и тогда это заставляет меня обобщать еще больше, помимо SslStream, и заставляет задуматься, как это связано с основополагающим материалом проекта.

Пожалуйста, не делайте (1), конструкторы уже превысили разумное количество параметров, и это не приближает нас к рассмотрению других сценариев, таких как SNI. (2) и (3) - в основном эстетические различия. (4) - это фундаментальное изменение в использовании, которое будет труднее принять. Например, он сочетает в себе код создания и подключения, который в настоящее время может иметь место в разных местах моего приложения.

он сочетает в себе код создания и подключения, который в настоящее время может иметь место в разных местах моего приложения.

Разве это не относится и к (3)?

@tractcher мой дизайн выше не контекст просто создает поток, и вы все еще можете вызвать рукопожатие, которое вы просто не можете его настроить.

Одна проблема, которую я вижу с предложением 4, заключается в том, что он меняет базовую понятную функциональность SslStream, теперь вы выполняете рукопожатие по параметрам, а затем какова цель Authenticateas * на sslstream. Эти API-интерфейсы теперь будут избыточными, и мы по-прежнему будем открывать их, и это приведет к тому, что можно сделать то же самое разными способами. Мне нравится приведенный выше фрагмент кода

Мне нравится приведенный выше фрагмент кода

Я пропустил это изначально.

В «фабричном» подходе я не уверен, в чем смысл использования отдельного метода аутентификации (PerformHandshakeAsync во фрагменте @drawaes ). Когда бы вы когда-либо делали что-либо, кроме Create, сразу за которым следует PerformHandshakeAsync? И методы Authenticate * по-прежнему избыточны.

В случае с трубопроводами речь шла о

//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);
}

Таким образом, здесь помогли перемещение создания и аутентификации. Но это может быть полезно, а может и нет. Я думаю, что это был шаблон, на который @Tratcher намекал выше.

Другое дело, если вы вернете задачу оттуда, потому что рукопожатие по своей сути асинхронное, тогда код может выглядеть примерно так (что уродливо)

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

} 

Таким образом, вам всегда приходится получать этот объект из задачи ... не самая красивая вещь в мире.

Я не понимаю, почему это уродливо. Сегодня существует множество API, которые так работают.

Вся эта дискуссия подталкивает меня к минималистскому подходу. Внесение более сложных изменений, кажется, открывает кучу новых проблем, и я бы предпочел отложить их и просто заставить работать ALPN и т. Д.

Дополнительный выпуск: SNI. Есть ли причина, по которой мы не можем просто использовать аргумент targetHost для LocalCertificateSelectionCallback, чтобы заставить эту работу работать на сервере? На сервере всегда установлено значение string.Empty today.

@geoffkizer У меня возникло искушение предложить сделать такой SNI, но не было подтверждено, можем ли мы делать SNI на основе обратного вызова или нет на schannel. Однако большинство других параметров не имеют смысла.

В любом случае я счастлив отказаться от этого. Я просто имею в виду, я не могу вспомнить много примеров, в которых IDisposable находится внутри Task, и это «удивительно» для большинства программистов (возможно, не в MS, а снаружи :)).

Что касается отбрасывания его, чтобы сделать это, как я сказал выше, я был бы в порядке с этим, но тогда нет пакета опций, просто добавьте параметр и перегрузку, и на данный момент все готово, и это можно продумать, у меня есть небольшое преимущество что я думал об этом некоторое время :)

В SNI есть способ использовать обратный вызов для переключения сертификатов на SChannel, я изо всех сил пытался его найти, но источник закрыт, а документации несколько лет, у вас может быть больше ресурсов и контактов, чтобы помочь в этом.

Пожалуйста, не возвращайтесь к варианту 1 ... пожалуйста ...

Плюс к варианту 1, это так уродливо, что он заставит быстро измениться после: P

🤕

@Tratcher Да, весь механизм LocalCertificateSelectionCallback немного странный, и я не совсем понимаю, как он должен работать или какие вещи работают на клиенте и сервере.

LocalCertificateSelectionCallback только для клиентов. targetHost и localCertificates поступают из AuthenticateAsClientAsync, а удаленные сертификаты и acceptIssuers поступают с сервера. Клиент должен выбрать сертификат, который соответствует одному из допустимых издателей.

Для SNI клиент отправляет одно имя хоста, а затем сервер может выбрать его сертификат. Проблема в том, что, насколько я понимаю, в SChannel вы должны предоставить этот список заранее, и SChannel выберет из них для вас, вы не можете предоставить его «извне». Но может быть способ, которым я скучаю

LocalCertificateSelectionCallback только для клиентов.

В этом есть смысл. Однако на самом деле он также вызывается в серверном корпусе. По-видимому, для всех параметров установлено значение null / empty, за исключением коллекции сертификатов - мы создаем временную коллекцию только с сертификатом сервера в ней. А затем мы используем тот сертификат, который вы возвращаете из этого обратного вызова.

Зачем мы это делаем, понятия не имею.

Устаревший код - замечательная и загадочная вещь ..

@ Priya91 @geoffkizer Решение принято? Как я это вижу

  1. Вариант мешок сейчас, вроде 1/2 пути к тому, что "идеал" был бы
  2. Быстро выиграйте ALPN прямо сейчас и начните обсуждение API на «будущей цели», такой как завод или полная модернизация.

Лично с учетом временных ограничений для 2.1 Http / 2 я бы сказал, что выберите 2, выпустите ALPN и начните более широкое обсуждение с большим количеством времени в кармане и с учетом таких вещей, как фундамент проекта и конвейеры.

Во всяком случае .. мысли?

Пойдите с сумкой option. Текущий дизайн неустойчив, и я не предвижу, что в ближайшее время произойдет более радикальный редизайн. Пакет опций также не требует больших затрат времени на внедрение.

@Tratcher Какой вариант предложения сумки? (2) выше? (3) выше? Что-то другое?

(2) с пакетом опций для конструктора наименее эффективен при выполнении требований. Это может легко обрабатывать ALPN и вариант повторного согласования. Здесь ничего не требует изменения методов аутентификации.

Забегая вперед к SNI, это все еще можно сделать с помощью (2), если у вас есть параметры и перегрузки, специфичные для клиента / сервера. Или вы вводите отдельные пакеты опций клиент / сервер для методов аутентификации. Я не решаюсь идти до пункта (3), где все переходит к параметрам конструктора, так как это потребует большего оттока для разработчика и потребителей, но я думаю, что это решение лучше всего оценивает разработчик. Я ожидаю, что любой из этих вариантов будет соответствовать нашим требованиям ASP.NET Core.

Хорошо, хотя @Tratcher думает, как я был в начале, я надеялся на будущее исправление и теперь не уверен, что это правильное направление.

Однако я собираюсь отойти от общего поведения в Интернете и капитулировать, поскольку, похоже, у этого дизайна есть прочная поддержка. Итак, вот что я думаю, чтобы двигаться дальше

@ Priya91, если вы согласны, поскольку похоже, что вы занимаетесь внедрением / исследованием, и если вы согласны с @Tratcher, можем ли мы разработать дизайн, который будет здесь вставлен , если обновлю верхнюю часть, и мы может подтолкнуть ...

(Комментарий для потомков, мы вернемся сюда в ближайшем будущем: P)

У меня также есть alpn, работающий для Linux, и я использовал пакет основных параметров для области поверхности api, я могу изменить это после утверждения api. А пока вот фиксация с изменениями alpn в Linux, тест alpn работает, но некоторые другие тесты не работают, исследуя их.

Хорошо, @Tratcher, если вас устраивает общий API в этом коммите, я могу выделить и обновить первый комментарий в проблеме, и мы можем двигаться дальше?

Во всех смыслах

Я не верю, что нам следует это делать (2). Это в лучшем случае полшага к подходу с полным набором опций.

Я был сторонником изучения подхода с использованием пакета опций, но мы уже буквально недели занимались этим, а он не сходился. На данный момент я не думаю, что стоит продолжать заниматься.

Я думаю, мы должны просто сделать 1, добавить перегрузку ctor.

@Tratcher , вы подразумевали, что (1) не соответствует требованиям ASP.NET. Пожалуйста, объясните это.

Вот полная рецензия на подход (1). Если здесь что-то не так, дайте мне знать.

ALPN:

`` С #
открытый класс SslStream: AuthenticatedStream
{
общедоступный SslStream (
Поток 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);
}

Давай закончим это в оффлайне.

@geoffkizer @Tratcher Есть ли у нас консенсус по поводу дизайна?

На данный момент нет единого мнения.

Я разговаривал с @terrajobst сегодня, и мы будем использовать обычный дизайнерский слот во

@Tratcher , пожалуйста, перешлите приглашение соответствующим образом.

@ Priya91 @karelz @stephentoub , к сведению - надеюсь, вы сможете провести эту встречу.

@Drawaes было бы здорово, если бы вы тоже были, но не уверен, насколько это соответствует вашему графику ...

Не связано с предложением api, но SecureTransport не имеет общедоступных API для поддержки ALPN, поэтому мы будем использовать PNSE на OSX.

Хм. Это прискорбно.

SecureTransport не имеет общедоступных API для поддержки ALPN.

Это также одна из причин, по которой libcurl, поставляемый с macOS, не поддерживает HTTP / 2:
https://daniel.haxx.se/blog/2016/08/01/curl-and-h2-on-mac/

11 утра где? Например, часовой пояс?

извините - PST (Сиэтл)

Поддерживаем ли мы OpenSSL в качестве опции в OSX?

Я считаю, что @bartonjs может подтвердить, но не для TLS, только для нескольких недостающих функций.

Я обнаружил эту открытую ошибку радара, запрашивающую поддержку ALPN для SecureTransport. В одном из комментариев упоминается новый API SSLSetALPNProtocols, который будет выпущен в macOS 10.13, которая в настоящее время находится в стадии бета-тестирования. Можем ли мы включить поддержку ALPN в macOS, если этот API доступен?

19:00 ldn должно быть в порядке, если мой тест на проникновение на следующей неделе не будет плохим :)

Можем ли мы включить поддержку ALPN в macOS, если этот API доступен?

Я так полагаю. @ Priya91?

Можем ли мы включить поддержку ALPN в macOS, если этот API доступен?

Не легко. Сейчас мы строим на 10.12, и у нас доступен только 10.12 API. Нам пришлось бы проделать хитрые вещи, чтобы заставить работать любую подсветку с этой прокладкой, или просто получить компенсацию за то, чтобы сделать 10.13 минимальной ОС для «2.1».

Это все равно будет, вероятно, сложно, но мне интересно, будет ли достаточно установки нового XCode и заголовков на 10.12. SecureTransport.h находится в /Applications/Xcode.app/Contents/Developer/Platforms/*

Вероятно, нам не следует рассчитывать на это сейчас и по возможности принимать это как бонус.

на самом деле:

+/*
+ * 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);

Ооо. Это делает жизнь немного легче. (10.12.4 кажется более легким минимумом для объявления, чем 10.13)

Я могу внести его в список TODO для расследования. И да, я согласен, это было бы намного жизнеспособнее.
Открытым вопросом будет поведение в старых версиях. Было бы неплохо, ИМХО, если бы все работало, просто ALPN выдаст исключение для неподдерживаемой платформы.

@wfurt, как потребитель SslStream узнает, может ли он включить ALPN, кроме попытки сделать это и выбросить его?

Как насчет изящного резервного режима, когда согласованный ApplicationProtocal возвращает null, и приложение может решить, вернутся ли они к значениям по умолчанию (HTTP / 1.1) или потерпят неудачу? Приложение уже должно иметь дело с этим сценарием, когда удаленная сторона не поддерживает ALPN.

Я думаю, что загрузчик библиотеки откажется загружать прокладку (поскольку он не может разрешить импорт), а это означает, что что-либо, касающееся криптографии, приведет к сбою загрузки приложения. (Если только мы не сделаем много страшной работы)

@bartonjs , напомните мне, почему в прокладке нельзя просто использовать dlopen / dlsym, чтобы найти интересующую нас функцию и использовать ее, если она существует?

В общем, PlatformNotSupportedExceptions - это наземные мины для потребителей, ASP.NET предпочел бы иметь подсветку / отключение для дополнительных функций, таких как ALPN.

@stephentoub "Страшная работа" :). Хотя выполнение этого для одной пары функций могло бы быть намного более самодостаточным, чем если бы мы сделали все это, как мы сделали с OpenSSL для переносимых усилий.

Я склонен согласиться с @Tratcher. Обратной стороной является перетаскивание новых требований к ОС для чего-то, возможно, маргинального для большинства пользователей. Требование 10.12.4 вместо 10.12 кажется неплохим.

Я бы отложил это пока и смогу поделиться дополнительной информацией, когда сделаю несколько тестов.

Из всех предложений, @ Priya91 «с фиксацией выглядит проще и потреблять и реализовать. Резюмирую:
`` С #
общедоступный частичный класс SslStream
{
общедоступный SslStream (параметры потока innerStream, SslAuthenticationOptions) {}
public SslStream (параметры потока innerStream, bool leaveInnerStreamOpen, SslAuthenticationOptions) {}

общедоступная строка ServerIndicationName {получить; }
общедоступная строка ApplicationProtocol {получить; }
}

открытый класс SslAuthenticationOptions
{
public bool AllowRenegotiation {получить; установленный; }
публичный IListApplicationProtocols {получить; }
public EncryptionPolicy? EncryptionPolicy {получить; установленный; }
общедоступный IDictionaryServerCertificates {получить; установленный; }
общедоступный LocalCertificateSelectionCallback UserCertificateSelectionCallback {получить; установленный; }
общедоступный RemoteCertificateValidationCallback RemoteCertificateValidationCallback {получить; установленный; }
}
`` ''

Обратный вызов по-прежнему предпочтительнее, чем IDictionary<string, X509Certificate> для ServerCertificates, но я получил смешанные отчеты о том, поддерживается ли это в Windows.

Мне нравится, что класс параметров предоставит место для размещения коллекции сертификатов для выполнения корневого закрепления (как только мы добавим эту опцию в X509Chain).

Это сработает, есть пара вещей, которые я бы еще хотел выяснить

  1. Как сделать общий Ssl_Ctx для OpenSsl
  2. Можем ли мы сделать обратный вызов для сертификата сервера для SNI?
  3. Жетоны отмены для методов аутентификации
  4. Уменьшите выделение ресурсов, задействованное в текущей реализации ALPN.

Мне нужно сесть и подумать, можно ли решить 1,3,4. Что касается 2, я бы сказал, отложите SNI, если это не критично на данный момент, пока кто-то не проведет какое-то исследование на стороне SChannel (OpenSsl точно поддерживает его).

@Drawaes 1,3,4 не повлияет на проблему с предложением api здесь. 1 и 4 - улучшения производительности, 2 и 3 - разные запросы api. Я бы не стал рассматривать эти блокираторы для предложения ALPN.

Конечно, но API закрашивает реализацию в какой-то угол. Я пытаюсь придумать выход из этого, и это возможно, но стоит подумать.

Производительность и удобство использования не обязательно должны быть взаимоисключающими.

Позвольте мне сказать по-другому. Предложение API не усложняет задачу, но и не помогает. Концепция фабрика / строитель / слушатель очень помогла.

Еще одна мысль, которая ортогональна общему подходу API.

Нам нужно определить строки протокола ALPN для общего использования. Что-то вроде:

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

Эти струны могли жить:
(1) На самом SslStream.
(2) В каком-то другом классе верхнего уровня (предположительно статическом), например SslApplicationProtocols.
(3) Во вложенном (статическом) классе внутри SslStream. Таким образом, вы можете получить к ним доступ, выполнив что-то вроде «SslStream.ApplicationProtocols.Http11». Чуть более заметный, чем (2), но более подробный.

(2) похоже, так мы раскрываем такие вещи, как HttpRequestHeaders. Тем не менее, я доволен любым из них.

Какая история с завтрашним днем?

Что ты имеешь в виду? Встреча состоится в 11:00 по тихоокеанскому стандартному времени.

(2) с собственным статическим классом. Остальные вызывают шум API.

Я обновил предложение, добавив заметки о встрече и доработанный API. Сообщите мне, если я что-то пропустил.

На данный момент вам нужна возможность отключить повторное обновление и удалить максимальный размер фрагмента.

Отключенное повторное согласование является требованием http / 2

@Drawaes Добавлен пересмотр.
Вы уже удалили максимальный размер фрагмента, верно?

Нет, это было на стороне клиента, но теперь у меня есть :)

@stephentoub высказал хорошее @ halter73 . Что с подходом с пакетом опций, «фабрика» может быть добавлена ​​после, и пакет опций может действительно помочь. Так что это было бы комплиментарно. Чтобы заставить его работать, потребуется некоторый внутренний доступ к SslStream, чтобы иметь возможность напрямую предоставлять кешированные значения, поэтому может потребоваться их присутствие в сборке.

Чтобы быть ясным, это никоим образом не влияет ни на работу с пакетом опций, ни на это предложение API.

@karelz попросил ссылки на другие фреймворки, использующие "фабричный подход", я нашел свои ссылки (там, в комментариях 23 дня назад;))

Ссылки на другие фреймворки
GO - Сумка с опциями
NodeJs - Пакет опций с фабрикой / поставщиком
Причал - ContextFactory

Предлагаемый API вначале выглядит хорошо, есть несколько вопросов,

  1. Почему EncryptionPolicy и SslProtocols должны быть типами, допускающими значение NULL, они могут принимать значение по умолчанию как None?
  2. Если ApplicaitonProtocol реализует IEquatableинтерфейс
  3. Либо все пакеты параметров и протокол приложения должны находиться внутри SslStream, либо иметь префикс Ssl в своих именах, поскольку они применимы только для TLS, и у нас также есть NegotiateStream в том же пространстве имен.
  1. EncryptionPolicy и SslProtocols должны допускать значение NULL, чтобы они могли плавать с системным значением по умолчанию. Другими словами, вы можете сказать, намеренно ли их установил пользователь.
  2. Нет мнения. Я ожидаю, что вам понадобится прямая проверка на равенство.
  3. Внутри? Вы имеете в виду вложенные классы? Пожалуйста, нет. Смежность с префиксом 'ssl' в порядке.

Да же 2 мне плевать. 3 Префикс класса SSL в порядке

Один вопрос: буфер протокола - это просто содержимое непрозрачных байтов для этого протокола или непрозрачные байты + префикс размера?

Также я помещаю префикс Ssl в классы вверху

@Drawaes Я ожидал, что они будут точно соответствовать значениям IANA, без префикса.

Круто, у меня нет предпочтений, раз уж ясно :)

Я бы предложил удалить отсюда sni apis, поскольку мы не планируем их внедрять на данный момент. Сохранение только значений alpn и allowrenegotiation. Для sni может быть отдельное предложение api. Обновлена ​​основная проблема с этим, а также реализована функция iequatable в приложении.

@Tratcher Я не понимаю,

EncryptionPolicy и SslProtocols должны допускать значение NULL, чтобы они могли плавать с системным значением по умолчанию. Другими словами, вы можете сказать, намеренно ли их установил пользователь.

Если пользователь не установил их, значения по умолчанию могут быть None, внутренним значением null будет None. Я не понимаю преимущества nullable по сравнению с None.

Каждые несколько лет появляется новый протокол SslProtocol. Мы хотим обновить значения по умолчанию, но мы также хотим уважать явные настройки разработчиков. Если SslProtocol является обязательным параметром, тогда трудно сказать, установил ли его разработчик потому, что он этого хотел, или просто потому, что это было доступно в то время. Если его можно оставить равным нулю, то ясно, что разработчик решил использовать системные значения по умолчанию, даже если эти значения по умолчанию изменятся.

Хорошо, позвольте мне сказать это так, если я не установлю свойство EncryptionPolicy, оно будет использовать любое значение по умолчанию, требуемое текущим состоянием безопасности ssl. Если пользователь явно изменяет значение по умолчанию, используйте значение пользователя.

@Tratcher SslProtocols.None уже используется для обозначения этого в текущей модели. Хотя null в целом является лучшим («Я не знаю, ты должен поступать правильно») значением, я не думаю, что нам нужны два разных способа его выражения. Поэтому я бы поспорил за повторное использование перегруженного значения None .

Я согласен. Если бы это было пустое поле с нуля, гораздо яснее было бы намерение, но это то, где есть. Если честно, это совершенно не так, как вам хотелось бы. Вместо того, чтобы выбирать, какие протоколы вы хотите, нужно выбрать, какие протоколы вы отключите.

Таким образом, вы бы сказали, что я не хочу ssl3 или tls1, но дайте мне то, с чем вы еще можете справиться. Это был мой дизайн 12 месяцев назад, но мы, вероятно, слишком далеко продвинулись по этой линии, чтобы это изменить.

Что касается SslServerAuthenticationOptions , можно ли добавить сюда поддержку для выбора доверенных эмитентов?
См .: https://github.com/dotnet/corefx/issues/23938

@ayende Пожалуйста, напишите об этом отдельно.

По моему предложению, он добавил сюда ссылку. Возможно, стоит открыть проблему SNI, поскольку мы ее отсюда убрали.

SNI. И разрешенные вами центры сертификации должны знать друг о друге.

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

Это должны быть Ssl [Server | Client] AuthenticationOptions.

   public bool ClientCertificateRequired { get; set; }

Мне все еще интересно, следует ли нам переименовать это в «ClientCertificateRequested» или подобное. Из MSDN это кажется более точным описанием того, что это делает (если я не неправильно понимаю ...)

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

Неужели нам действительно нужны два способа указать Http2? Я бы просто пошел с Http2 и сбросил H2.

public class SslApplicationProtocol : IEquatable<SslApplicationProtocol>

Было некоторое обсуждение создания этой структуры. Мы закрылись на этом?

Я удалил дубликат H2.

Изменил его на struct, разногласий по использованию struct не было. Собираемся ли мы использовать ReadOnlyMemory? Какое преимущество это дает перед byte []

Поскольку нам все равно нужно копировать, я думаю, что byte [] в порядке.

Re: ClientCertificateRequ {ired | ested}: если задано значение true без пользовательского обратного вызова, это обязательно. Если установлено значение true с настраиваемым обратным вызовом, это то, что решит настраиваемый обратный вызов.

На самом деле это сводится к (для терминологии RFC) «отправить запрос сертификата [клиента]» https://tools.ietf.org/html/rfc5246#section -7.4.4

Так что, может быть, RequestClientCertificate ?

ReadOnlyMemory была более важной до SslApplicationProtocol, когда мы напрямую добавляли IList.к опциям. Теперь это не важно.

RE: сертификат клиента, действительно ли мы хотим переименовать существующие API? Это вызовет столько же путаницы, сколько и спасет.

Повторное переименование я ни в коем случае не чувствую. Здесь у нас есть возможность прояснить эти вещи, которые, вероятно, у нас не будет еще долгое время. Но, как вы сказали, я не уверен, что оно того стоит.

RE: постоянная память ... скажем иначе ... Почему бы вам не использовать это? Это дает далеко не гибкость, чем массив. Вы все равно не можете сравнивать по ссылке, потому что, как вы говорите, вы ее копируете.

Память только для чтения изменена на байт []. Думаю, все нерешенные вопросы решены. @karelz @KrzysztofCwalina @bartonjs @terrajobst @weshaggard Не могли бы вы сделать окончательный обзор и отметить API как одобренный. Спасибо!

Подождите, почему выкидывают память только для чтения? Люди тратят время, активно добавляя концепцию «памяти» во фреймворк, byte [] можно преобразовать в память только для чтения, но не наоборот. Мы также договорились, что данные копируются, и поэтому вы в любом случае не можете выполнить равенство ref, что было бы единственным положительным моментом. Наконец, если необходимо закрепить память для передачи в собственный API, вы можете закрепить ее один раз и получить за нее счетчик ссылок.

@Tratcher Только что упомянул, что изначально мы использовали readonlymemory, чтобы сделать статические поля http1.1, http2 неизменяемыми. Возврат.

Обновление: мы должны сделать параметр checkCertificateRevocation в сумке параметров перечислением. Поскольку он не расширяется для будущего дизайна. Скажем, мы решили добавить новое перечисление отзыва сертификата, теперь пользователь может установить конфликтующие значения с помощью этих различных настроек, и нам придется отказаться от значения bool. Так что теперь имеет смысл добавить перечисление в сумку с опциями.

Измени это,

`` С #
public bool CheckCertificateRevocation {получить; установленный; }

to,

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

Обновлено с помощью public SslApplicationProtocol(string protocol) { } // assumes utf-8 и 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"

Кажется разумным. Стоит ли проверять, нет ли в строке многобайтовых символов utf8 и закидывать, если есть? Вы все еще можете установить их через readonlybytes, и это может остановить проблемы с копированием скрытых символов (например, происходит с отпечатками, скопированными из mmc, на которые я потратил впустую часы своей жизни)

@Drawaes какие-нибудь примеры?

Например, если вы копируете и вставляете отпечаток из плагина сертификата для mmc, вы получаете скрытый символ Unicode. Если вы затем попытаетесь использовать это для сопоставления с отпечатком в хранилище сертификатов в коде, вы не сможете его найти. Затем вы проводите 2 часа, проверяя все, и обнаруживаете, что в строке vs в строке номер столбца перескакивает между концом отпечатка пальца и знаком ".

Это не критично, а скорее успех, очень маловероятно, что вы действительно захотите, чтобы символ Unicode превратился в utf8 с помощью кодовой точки. Если вы это сделаете, вы узнаете об этом и сможете использовать байт, доступный только для чтения.

В любом случае, это не нарушение сделки, а просто то, что, когда вы попадаете в него, вы никогда не забудете.

Я имел в виду пример последовательности символов / байтов. Был ли пробел (0x20) ошибочным?

Это вначале извините .. Это 0x200e, и это знак слева направо. В stackoverflow есть след разбитых мечтаний;)

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

Итак, мое предложение таково. Если кто-то отправляет строку, которая приводит к установке самого старшего бита в любом из байтов, выбросить ее как исключение. Это предотвратит нечетные проблемы сопоставления времени выполнения на раннем этапе, и есть способ обойти это, если вы это имели в виду.

Частично это также потому, что нет способа сказать «мы будем интерпретировать это как utf8» почти так же, как нам нужна строка utf8;)

Ах, понял. Это скорее проблема редактора кода, чем проблема API. В зависимости от кодировки файла эти лишние байты могут просто пропасть. Второе предположение о значениях выходит за рамки этого API.

Правильно, я подумал об этом больше, перегрузку строки следует полностью исключить. Существуют константы для общего случая, для всего остального он «продвинутый», и пользователь должен контролировать вводимую строку.

Они могут конвертировать в UTF8, если хотят, но строкового типа utf8 нет, поэтому неясно, что будет делать отправка строки. В спецификации UTF8 нет ничего, кроме непрозрачных байтов, перегрузка очень мало помогает и просто добавляет потенциальную ловушку.

Обычная ловушка - это двоичный ключ / значение Redis с неявным преобразованием в строку (без расширения или сужения).

Таким образом, использование двоичного значения из Redis будет интерпретироваться как Utf8 и либо съедает каждый второй байт, либо дает неверную интерпретацию Utf8.

Спецификация действительно дает utf-8 в качестве предложения для определения значений спецификации, и все текущие используют эту кодировку.

Последовательность идентификации: точный набор значений октетов, которые
определяет протокол. Это может быть кодировка UTF-8.
[RFC3629] имени протокола.

Right и все актуальные актуальные имеют константу. Это также соображения IANA при их распределении. Сама спецификация

Протоколы именуются зарегистрированными IANA непрозрачными непустыми байтовыми строками, как описано ниже.
в Разделе 6 («Соображения по поводу IANA») этого документа.

И это могло быть примерно так же слабо, как указано в спецификации IETF, это даже не строчные буквы «обязательно»: P

Думаю, настоящий вопрос в том. Каков вариант использования упрощенной передачи строки, гарантирующей возможные проблемы?

Спецификация действительно дает utf-8 в качестве предложения для определения значений спецификации, и все текущие используют эту кодировку.

Это отличается от автоматического преобразования ввода в функцию из 2 байтов на символьное представление ( string ) в 1 байтовое на символьное представление ( utf8 ), чтобы он мог соответствовать представлению на проводе.

Когда, например, если он исходит из ключа redis, он, скорее всего, будет в представлении на проводе, поэтому вы отбрасываете каждый второй байт при автоматическом преобразовании из string в utf8 . Или это может быть строковый формат.

У меня проблема с автоматическим преобразованием из 16-байтовой строки в 8-байтовый непрозрачный двоичный формат.

^^^ Это. Меня беспокоит то, что в определении API нет ничего, говорящего «Предполагается, что это UTF8», кроме комментария. Например, у вас может быть
(игнорируя форму и названия)

CreateUTF8ApplicationLayerProtocolName(string inputString);

Где было ясно, что произойдет, но иметь автоматический перегруженный конструктор проблематично.

Некоторые из ваших примеров являются копипастом, а некоторые рассматривают ввод как динамический. Копипаст кажется гораздо более вероятным, вы не очень часто добавляете новые протоколы, и уж тем более не на лету из redis.

Основной сценарий, в котором я вижу, что используется строковый конструктор, - это копирование и вставка новых значений из списка IANA, который явно поощряет кодировки utf-8.

Мне сказали, что несколько сопоставимых платформ предоставляют только api строки utf-8, а не byte [] api вообще. Хотя мне было бы любопытно увидеть настоящие ссылки.

Конечно, go принимает "строку", но строка в Go в любом случае непрозрачна в байтах, а не в utf16, как в строке c #. Проблема не в UTF8. Если была строка utf8, не проблема.

От GO

Важно сразу указать, что строка содержит произвольные байты. Не требуется содержать текст Unicode, текст UTF-8 или любой другой предопределенный формат. Что касается содержимого строки, то оно в точности эквивалентно отрезку байтов.

Текущее использование без перегрузки для строки будет таким?

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

@Drawaes Не могли бы вы отменить изменения, которые вы

cc @karelz

Я только удалил перегрузку строки, которая не обсуждалась на собрании по проверке API, и была добавлена ​​только 4 дня назад, если не было встречи, о которой я не знал?

Обновлено с помощью общедоступного SslApplicationProtocol (строковый протокол) {} // предполагает utf-8 и общедоступную строку переопределения ToString () {} // Для отладчика. utf-8 или строковое представление необработанных байтов. Например: "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x3

Это был этот комментарий
Комментарий, где он был добавлен

Если я ошибаюсь, его можно было бы добавить обратно, но это было добавлено, насколько я могу судить, 4 дня назад, и на совещании по обзору обсуждались только конструктор с памятью только для чтения и константы для хорошо известных типов?

@Drawaes Да, была еще одна встреча, чтобы

Глядя на это по-другому

byte[] binaryProtocol // = ...;

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

Console.WriteLine(myProtocol == otherProtocol)

Потенциально может вывести false ?

В то время как

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

Console.WriteLine(myProtocol == otherProtocol)

Вернет true ?

Кроме ... ему нужен ReadOnlyMemory<byte> .ctor; поскольку в настоящее время вы не можете этого сделать ☹️

Protocol равно ReadOnlyMemory<byte> ; и есть только byte[] .ctor.

Таким образом, конструктор <-> симметрия объекта для SslApplicationProtocol настоящее время не работает. Вы не можете взять выходные свойства одного SslApplicationProtocol и создать другой, равный.

  • Вывод ToString() может быть недопустимым при помещении в string .ctor, поскольку .ctor интерпретирует его как utf8, а ToString может выводить шестнадцатеричные коды.
  • Protocol который не выполняет интерпретацию, не имеет .ctor, чтобы передать его; поэтому вам нужно будет выделить новый массив байтов, а затем перебрать Protocol устанавливая каждый байт индивидуально, а затем передать вновь выделенный byte[] в .ctor.

Я согласен, поскольку API для протокола асимметричен, что проблематично для любого API.

@ Priya91 Одна проблема,

Вышеупомянутая точка все еще остается в силе, получение байта [] в и всплытие памяти кажется асимметричным. А также опасения по поводу веревочки.

Вы не можете взять выходные свойства SslApplicationProtocol и создать другой, равный.

Не сработает ли следующее?
`` С #
var myProtocol = новый протокол SslApplicationProtocol (двоичный протокол);
var otherProtocol = новый SslApplicationProtocol (myProtocol.Protocol.ToArray ());

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

Я думаю, дело было в том, что вам пришлось скопировать его в массив, когда он снова стал памятью только для чтения. Почему бы просто не использовать постоянную память?

Немного странно использовать разные типы для конструктора и свойства. В некоторой версии предложения конструктор взял ReadOnlyMemory<byte> . Я не уверен, где это потеряно.

Почему бы просто не использовать постоянную память?
c# byte[] bytes = ... ReadOnlyMemory<byte> memory = bytes; var myProtocol = new SslApplicationProtocol(memory); bytes[0] = 20;
Дело в том, что мы должны копировать в ctor, что бы мы ни взяли. А потом мы возвращаем ReadOnlyMemory, и нам не нужно копировать.

Хорошо, я явно что-то упускаю (я поверю вам в этом). Но, насколько я понимаю, почему вам все равно нужно копировать все, что есть в конструкторе?

Насколько я понимаю ... опять же, мне может не хватать чего-то, что у вас есть x количество структур ALPN с непрозрачными байтами.

Когда вы приходите, чтобы отправить это клиенту, вам нужно подготовить список, чтобы вы это сделали (теперь я не буду использовать linq, но его пример кода, вы можете использовать эквивалент массива или что-то еще)

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

Я предположил, что мы хотим, чтобы SslApplicationProtocol был неизменным. Неизменяемые типы лучше подходят для представления логических констант.

Неизменяемые типы не могут хранить данные, которые являются изменяемыми и совместно используемыми. Массив байтов, переданный в ctor, является изменяемым и совместно используемым. Чтобы сделать его не доступным для общего доступа, автор должен сделать частную копию.

В вашем примере кода цикл может создавать разные данные в диапазоне результатов каждый раз, когда цикл запускается, даже если коллекция элементов alpn никогда не изменялась. Это потому, что элементы в коллекции могут быть изменены через «черный ход» байтового массива. А мы этого не хотим, не так ли?

А мы этого не хотим, не так ли?

Может быть весело :) Хотя скорее причина багов.

В любом случае должен быть .ctor

public SslApplicationProtocol(ReadOnlyMemory<byte> protocol) { }

Или возможно

public SslApplicationProtocol(ReadOnlySpan<byte> protocol) { }

Поскольку они правильно передают намерение использования параметра protocol ; а также принимать другие источники, кроме bcl byte[]

@KrzysztofCwalina уверен, что нет, однако я очень рано сказал, что изменчивость является проблемой для всех вариантов. Здесь мы говорим, что ALPN особенный по сравнению, скажем, со списком ALPN, сертификатом или чем-то еще, что можно изменить в пакете опций? Вот цитата из @stephentoub, когда я сказал, что мы должны заблокировать пакет параметров (который, к вашему сведению, убедил меня, что он не должен быть неизменным, поэтому я не уверен, почему теперь должен быть ALPN).

Почему проблема с изменяемым пакетом опций? Это ничем не отличается от всего остального: вы передаете это API, и API ожидает, что он будет в согласованном состоянии на протяжении всей операции ... если вы измените его во время выполнения операции, это ошибка, нет отличается от любого другого случая, когда вы что-то мутируете, в то время как вызываемый метод зависит от этого (например, буферы, переданные для чтения / записи). Если вы хотите относиться к нему как к неизменяемому и просто никогда не изменять его после создания, вы можете это сделать. Если вы хотите рассматривать его как изменяемый, чтобы вы могли изменять его между использованиями, вы можете это сделать.

@Drawaes Свойство пакета параметров IList<SslApplicationProtocol> можно изменить, но значение протокола структуры SslApplicaitonProtocol является неизменным. На данный момент мы предполагаем, что использование SslApplicationProtocol находится только в пакете параметров, но этот тип может использоваться вне пакета параметров в других сценариях в пользовательском коде.

@ Priya91 Я на 100% понимаю дизайн, но кажется произвольным, что ALPN защищен неизменяемостью с использованием копий, но не списка или чего-либо еще в SslStream. Итак, у нас есть произвольная неизменность и несогласованность, и у нас есть асимметричный API, который предоставляет ReadOnlyMemory в свойстве Protocol, но нет способа использовать это для создания нового?

В любом случае, если вы все хотите такой дизайн, пусть будет так.

@Drawaes , мы не будем выставлять пакеты с постоянными / предварительно приготовленными опциями. Мы действительно хотим, чтобы были открыты постоянные протоколы SslApplicationProtocols.

@benaadams , я согласен, что ReadOnlySpanбыл бы более гибким. Я не уверен, что нам нужна такая гибкость. Это затруднит использование API (Span менее известен, чем byte [], Encoding.UTF8.GetBytes возвращает массив и т. Д.).

Сказав это, я не настаиваю на каком-либо дизайне (на самом деле я думаю, что это немного косяк , и мы должны просто позволить разработчику, ответственному за эту функцию, то есть

и у нас есть асимметричный API, который предоставляет ReadOnlyMemory в свойстве Protocol, но нет способа использовать это для создания нового?

Я не понимаю этого утверждения, readonlymemory можно использовать для создания нового объекта SslApplicationProtocol, new SslApplicationProtocol (readonlymemory.toarray ()).

Память только для чтения может использоваться для создания нового объекта SslApplicationProtocol, new SslApplicationProtocol (readonlymemory.toarray ()).

Оно может. Однако вы берете неизменяемый элемент ReadOnlyMemory<byte> выделяя его для преобразования в изменяемый элемент byte[] а затем передаете изменяемый элемент в конструктор, чтобы он мог снова скопировать его и превратить в неизменяемый ReadOnlyMemory<byte> .

То , что я предлагаю, принимая параметр как ReadOnlyMemory<byte> транспортируют намерения функции правильно по-новому , что мы не могли сделать ранее с массивами. Он говорит, что функция, которая принимает это, не намеревается его изменять. В то время как функция, принимающая массив, не дает такой гарантии; параметр может быть изменен для вызываемого абонента после звонка.

Я не предлагаю удалять byte[] .ctor; поскольку это, вероятно, просто запутает людей; просто чтобы добавить дополнительный .ctor, который может напрямую брать свойство, которое предоставляет тип, и создавать другое, вместо того, чтобы преобразовывать его во что-то еще; или сначала переведите.

Также вы, вероятно, никогда не собираетесь делать
new SslApplicationProtocol(proto.Protocol));
или
new SslApplicationProtocol(proto.Protocol.ToArray()));
Как будто у вас есть протокол, зачем вам создавать идентичный; так что в основном это то, что api не самосогласован, что меня беспокоит. Однако это второстепенный момент.

Однако я думаю, что интерпретация ввода string как UFT8 проблематична, и точно так же без string .ctor вы могли бы легко сделать

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"));

Где явно указана кодировка, поскольку строка в .NET имеет 16-битный формат; который также может вполне успешно хранить двоичные данные, а не формат UFT8.

Я полагаю, в том же духе, что и ты.

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);

Чтобы сохранить естественный формат string - я просто думаю, что api, который принимает строку, автоматически предполагает, что другая кодировка вывода вызывает проблемы - запекает предположение о преобразовании в api.

Проблема с .NET apis, продемонстрированная .NET Standard 2.0 (и это удивительно), заключается в том, что они никогда не могут быть изменены после их запуска.

Перегрузка строки всегда может быть добавлена ​​позже, если есть потребность; однако, оказавшись там, его уже нельзя будет забрать или изменить его поведение.

С ReadOnlyMemory .ctor + https://github.com/dotnet/corefx/issues/24293 тогда вы сможете избежать цикла foreach char с no-alloc

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

(игнорируя защитную копию в .ctor)

@benaadams Каковы

Я не предлагаю удалять байт [] .ctor; поскольку это, вероятно, просто запутает людей; просто чтобы добавить дополнительный .ctor, который может напрямую брать свойство, которое предоставляет тип, и создавать другое, вместо того, чтобы преобразовывать его во что-то еще; или сначала переведите.

Будь то byte [] или Readonlymemoryего нужно скопировать в конструктор, а readonlymemory - это просто byte [], который не предоставляет средства для изменения нижележащего byte []. Поэтому я не вижу дополнительной ценности в readonlymemory в конструкторе. Я тоже не понимаю асимметричной точки API, на какие сценарии это влияет?

Что касается перегрузки конструктора строк, она предоставляется для удобства использования и удобства использования новых значений alpn, которые в настоящее время не являются константами, или для настраиваемых предварительно согласованных новых значений, которые могут использовать клиенты и серверы. Нашей целью не является обеспечение равенства для объекта sslaplicationprotocol, созданного с помощью другого tostring () для сопоставления, этот сценарий не является вариантом использования для этого api. Это похоже на то, как apis кодирования не совпадают с getstring и getbytes для несовместимых значений кодирования.

Надо сблизить дискуссию по этому поводу и прийти к консенсусу, это тянется довольно долго :(.

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

Что значит "utf-8"?
У нас есть кодировка строк UTF-16, так что это то, что мы собираемся получить. Нам нужно будет внутренне преобразовать его в строку UTF-8 и сохранить в «byte []», в который мы передаем.
ToString интерпретирует базовый "byte []" как строку UTF-8. Если это не удается, это будет строковое представление необработанных байтов, например «0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31».

Это последнее предложение ALPN из обсуждения в этой ветке. Публикуем это здесь для команды проверки API.

`` С #
пространство имен System.Net.Security
{
общедоступный частичный класс SslStream
{
общедоступная задача AuthenticateAsServerAsync (параметры SslServerAuthenticationOptions, отмена CancellationToken) {}
общедоступная задача AuthenticateAsClientAsync (параметры SslClientAuthenticationOptions, отмена CancellationToken) {}

общедоступный SslApplicationProtocol NegotiatedApplicationProtocol {получить; }
}

открытый класс SslServerAuthenticationOptions
{
public bool AllowRenegotiation {получить; установленный; }
общедоступный X509Certificate ServerCertificate {получить; установленный; }
public bool ClientCertificateRequired {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
общедоступный X509RevocationMode CheckCertificateRevocation {получить; установленный; }
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback RemoteCertificateValidationCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionPolicy {получить; установленный; }
}

открытый класс SslClientAuthenticationOptions
{
public bool AllowRenegotiation {получить; установленный; }
общедоступная строка TargetHost {получить; установленный; }
общедоступные X509CertificateCollection ClientCertificates {получить; установленный; }
общедоступный LocalCertificateSelectionCallback LocalCertificateSelectionCallback {получить; установленный; }
общедоступные протоколы SslProtocols EnabledSslProtocols {получить; установленный; }
общедоступный X509RevocationMode CheckCertificateRevocation {получить; установленный; }
публичный IListApplicationProtocols {получить; установленный; }
общедоступный RemoteCertificateValidationCallback RemoteCertificateValidationCallback {получить; установленный; }
общедоступная EncryptionPolicy EncryptionPolicy {получить; установленный; }
}

общедоступная структура SslApplicationProtocol: IEquatable
{
общедоступный статический только для чтения SslApplicationProtocol Http2;
общедоступный статический только для чтения SslApplicationProtocol Http11;
// Добавление других значений IANA остается на основе ввода данных пользователем.

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) { }

}
}
`` ''

У нас есть кодировка строк UTF-16, так что это то, что мы собираемся получить. Нам нужно будет внутренне преобразовать его в строку UTF-8 и сохранить в «byte []», в который мы передаем.

И в api не указано, что это запеченная автоматическая конвертация, например

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

В лучшем случае это побочный комментарий в этом выпуске.

В спецификации не говорится, что это должна быть строковая интерпретация UTF-8 - в ней говорится, что это может быть

Последовательность идентификации: точный набор значений октета, который идентифицирует протокол. Это может быть кодировка UTF-8 [RFC3629] имени протокола.

Насколько распространено то, что пользователи будут создавать свои собственные протоколы, поэтому потребуется удобный конструктор, который выполняет упрямое преобразование из байтов string в байты UTF-8?

Какое подмножество этих пользователей не понимает API кодирования, поэтому нужна помощь в выполнении

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

Точно так же любой, кто заблудился в раннем не-ascii с акцентами; пойдут ли они на дополнительный байт, который UTF8 создает для

// 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("мой протокол");

Или они предпочтут использовать кодировку Latin 2 или Windows 1250/1251/1252 из System.Text.Encoding.CodePages, которая сохраняет байт, а каждый символ по-прежнему имеет 1 байт; тогда как в UTF8 это 2 байта?

Я не думаю, что перегрузка строки является хорошей - это проблема, особенно с отказом от использования utf8 в качестве компактного строкового представления людьми, использующими латинские наборы символов, поскольку они используют свой локализованный 8-байтовый формат https://github.com / dotnet / coreclr / issues / 7083

Все текущие идентификаторы протокола IANA ALPN являются ASCII, поэтому фактических примеров или прецедентов использования UTF-8 нет:

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

@karelz Да, я имел в виду, что строка будет преобразована в байты с использованием кодировки utf-8.

Эта перегрузка строки не является важной частью API и не заслуживает того количества времени, которое уже было потрачено на нее. Это обеспечивает небольшое улучшение удобства использования по сравнению с другими перегрузками, вот и все. Сохраните его или удалите, но в любом случае пора двигаться дальше.

Независимо от того, является ли это byte [] или Readonlymemory, его нужно скопировать в конструктор, а readonlymemory - это просто byte [], который не предоставляет средства для изменения базового byte []. Поэтому я не вижу дополнительной ценности в readonlymemory в конструкторе.

Это хороший момент. Как вы говорите, его все равно нужно скопировать, поэтому ReadOnlySpan<byte> будет иметь большее значение, чем ReadOnlyMemory<byte> как он также может быть стековой памятью - и вы всегда можете получить его из ReadOnlyMemory<byte>

Хорошо, это до разработчиков, реализующих это, и MS, которая должна поддерживать это в конце. В основном я хотел сумку с опциями и делал лунные съемки для строителя, поэтому я очень счастлив, что получил то, что хотел. Я сделаю последний комментарий к конструктору, затем кто-нибудь из MS может принять решение, и они могут обновить главную проблему.

Итак, мое последнее слово ... как часто повторяется, дизайн API - это навсегда. Зачем брать строку и utf8 It? В любом случае строки UTF8 могут быть не за горами. Какова вероятность того, что кому-то действительно понадобится перегрузка прямо сейчас? Его можно добавить позже, если необходимо, вы всегда можете добавить, что не можете взять на вынос.

Вторая постоянная память .... массивы все равно неявно передаются в память, верно? Так что сделайте это памятью только для чтения и добавьте массив, если он вам нужен. Если вас беспокоит знакомство, если вы создаете свой собственный протокол ALPN, я все равно надеюсь на определенный уровень глубины знаний.

В любом случае дайте мне знать, что вы хотите выше .....

В случае неясно, я рекомендую только один конструктор памяти только для чтения.

@benaadams В спецификации не говорится, что это должна быть строковая интерпретация UTF-8 - в ней говорится, что это может быть

Верный. Спецификация намеренно неясна по неизвестным причинам, мы полагаем, что UTF-8 сильно намекает. Это наша интерпретация спецификации.
Кстати: мы обсуждали также ASCII. Если кому-то нужен UTF-16 или другие кодировки, они могут напрямую использовать перегрузку byte [].
На практике мы не думаем, что кто-то будет использовать что-либо иное, кроме предварительно определенных IANA строк ASCII / UTF-8.

Насколько распространено то, что пользователи будут создавать свои собственные протоколы

Пользователи могут использовать его для всех протоколов IANA, которые мы не включаем в качестве удобных констант. Это также результат долгой внутренней дискуссии об использовании только строки в качестве базовых данных вместо byte [] (что является дополнительным доказательством возможности). Добавление перегрузки строки удобно для удобства использования.

Эта перегрузка строки не является важной частью API и не заслуживает того количества времени, которое уже было потрачено на нее. ... в любом случае пора двигаться дальше.

Согласен 💯!

Строки @Drawaes UTF8 в любом случае могут быть не за

Я не думаю, что делать API-интерфейсы зависимыми друг от друга - хорошая идея. Мы не знаем, попадут ли на платформу строки UTF-8 и когда они это сделают.
Я рассматриваю удобный строковый API как способ передачи строки ASCII (или UTF-8) в соответствии со спецификацией.

Кстати: я обновлю верхний пост с копией и ссылкой на последнюю версию API из @ Priya91.

Спецификация намеренно неясна по неизвестным причинам, мы полагаем, что UTF-8 сильно намекает. Это наша интерпретация спецификации.

Это не так, он не использует термин RFC2119 (на который также ссылается спецификация)

  1. Требования Язык

    Ключевые слова «ДОЛЖНЫ», «НЕ ДОЛЖНЫ», «ОБЯЗАТЕЛЬНО», «ДОЛЖНЫ», «НЕ ДОЛЖНЫ»,
    «ДОЛЖЕН», «НЕ ДОЛЖЕН», «РЕКОМЕНДУЕТСЯ», «МОЖЕТ» и «ДОПОЛНИТЕЛЬНО» в этом
    документ следует интерпретировать, как описано в [RFC2119].

Он говорит «мог», что не входит в число этих терминов; вместо «РЕКОМЕНДУЕТСЯ» или даже «МОЖЕТ» - это скорее пример использования - чтобы прояснить, что, хотя это двоичное поле, текст, вероятно, является разумным вариантом.

Если вы хотите запечь это преобразование в: Я бы предложил каким-то образом явным образом указать в api, например

new SslApplicationProtocol(string uf8Protocol)

Я думаю, что люди, определяющие свои собственные протоколы, могут справиться с обременительным дополнительным этапом использования явного Encoder для строки, вместо того, чтобы упростить неоднозначную перегрузку строки .ctor.

В любом случае; Высказывал возражения, вносил предложения - не буду возражать против окончательного решения.

Обзор API: одобрено как последнее предложение. Мы обсудили предложенные выше альтернативы, но не сочли нужным их использовать.

Ладно круто, с нетерпением жду его появления на corefx

Была ли эта страница полезной?
0 / 5 - 0 рейтинги