Runtime: إضافات SslStream المقترحة لـ ALPN

تم إنشاؤها على ١٢ أغسطس ٢٠١٧  ·  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
{
منطقية عامة AllowRenegotiation {get؛ يضع؛ }
شهادة خادم X509Certificate العامة {get؛ يضع؛ }
ClientCertificateRequired عام منطقي {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
X509RevocationMode العام CheckCertificateRevocation {get؛ يضع؛ }
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة RemoteCertificateValidationCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionPolicy {get؛ يضع؛ }
}

فئة عامة SslClientAuthenticationOptions
{
منطقية عامة AllowRenegotiation {get؛ يضع؛ }
السلسلة العامة TargetHost {get؛ يضع؛ }
شهادات العميل X509CertificateCollection العامة {get؛ يضع؛ }
LocalCertificateSelectionCallback العامة LocalCertificateSelectionCallback {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
X509RevocationMode العام CheckCertificateRevocation {get؛ يضع؛ }
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة RemoteCertificateValidationCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionPolicy {get؛ يضع؛ }
}

بنية عامة 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 ، لذا أخذ بايت [] بدلاً من ReadOnlyMemory

ملاحظات الاجتماع 5 سبتمبر 2017

  1. استخدم اقتراح واجهة برمجة التطبيقات المحدّث أعلاه مع أكياس الخيارات في طرق المصادقة.
  2. قم بإدخال نوع ApplicationProtocol بحيث يمكن تعريف قيم ALPN بشكل صحيح على أنها وحدات بايت أولية ، ولها ثوابت مشتركة ، ولها عوامل تشغيل مساواة.
  3. عدم السماح باستدعاءات الخلط بين المنشئات القديمة والطرق الجديدة. سيتم دعم الحد الأدنى فقط من المنشئين الذين يأخذون الدفق الداخلي ونسق الملكية.
  4. لا يزال هناك بعض النقاش المعلق حول نهج المصنع.

ملاحظات أخرى

  1. هذا يعني أنه يمكن إضافة خيارات مستقبلية دون التسبب في مشكلات توافق ثنائية (زيادة الخصائص على فئات ملموسة بدلاً من تغيير الأحمال الزائدة)
  2. لا ينبغي دعم الأساليب غير المتزامنة للطرق الجديدة لأنها عملية غير متزامنة تحت الغطاء ، لذا فإن إخفاء الخيوط يتعارض مع تفكير الإطار الحالي. يمكن للمستهلكين التفاف الطرق غير المتزامنة مع الحظر إذا رغبوا في ذلك (انظر HttpClient الحديث)
  3. لقد تسللت الجزء الأقصى للعميل ، لكنني سعيد بإسقاط هذا إذا كانت نقطة شائكة
  4. توجد رموز الإلغاء لكلتا الطريقتين وفقًا لتفكير إطار العمل الحالي
  5. لم يتم أخذ ValueTask في الاعتبار لأنه سيكون هناك عدد قليل جدًا من المرات التي لا يتسبب فيها ذلك في حدوث عمليات غير متزامنة ، ولكن إذا كان المعيار الجديد هو استخدام ValueTask ، فلا بأس بذلك أيضًا
  6. يجب توفير قائمة ثابتة لـ Http 1.1 و Http / 2 و Http / 2 عبر TLS لمنع المستخدمين من البحث عنها.
  7. يجب توفير طرق المساعدة في نتائج المصادقة حتى لا يضطر المستخدمون إلى البحث عن تمثيلات السلسلة في هذه الحالة ، لقد أضفت واحدة لـ IsHttp2.

قضايا التنفيذ المحتملة

يوجد الآن عدد من المعلمات التي يمكن تعديلها أثناء إنشاء اتصال. واحد من هؤلاء هو قاموس الشهادات. إذا قام المستهلك بتغيير هذه عن طريق إعادة استخدام خيارات التكوين لاتصال تم تكوينه بشكل مختلف ، فقد تواجه مشكلة أمنية. أحد الخيارات هو أن يكون لديك منشئ ، لكن هذا أمر مستهجن باعتباره نمط واجهة برمجة التطبيقات ويستخدم في الغالب في المكتبات / أطر العمل ذات المستوى الأعلى (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);

مراجع

دوت نت / وقت التشغيل # 17677 SNI
دوت نت / وقت التشغيل # 15813 ALPN
dotnet / وقت التشغيل # 23107 اقتراح واجهة برمجة التطبيقات السابق
RFC 7301 ALPN
جزء RFC 3546 SNI و Max
معرّفات بروتوكول IANA ALPN

[تحرير] تنسيق محدث بواسطةkarelz

api-approved area-System.Net.Security

التعليق الأكثر فائدة

karelz @ نعم قصدت أنه سيتم تحويل السلسلة إلى بايت باستخدام تشفير utf-8.

هذا الحمل الزائد للسلسلة ليس جزءًا مهمًا من واجهة برمجة التطبيقات ولا يستحق مقدار الوقت الذي تم قضاؤه بالفعل عليه. إنه يوفر تحسنًا طفيفًا في قابلية الاستخدام مقارنة بالحمل الزائد الآخر ، هذا كل شيء. احتفظ به أو احذفه ، ولكن في كلتا الحالتين ، حان الوقت للمضي قدمًا.

ال 268 كومينتر

سم مكعبgeoffkizer @ Priya91wfurt

Drawaes ما هو الدافع وراء إضافة حقيبة الخيارات بشكل منفصل ، هذا لا يعمل بشكل جيد مع تصميم API الحالي في SslStream. بدلاً من تقديم العديد من الأنواع الجديدة ، أعتقد أن الكشف عن كميات زائدة من أساليب AuthenticateAs [Client | Server] Async للحصول على بروتوكولات التطبيق التي يرغبون في المصافحة من أجلها سيكون أمرًا بسيطًا وإنجاز المهمة.

لأنك تحتاج أيضًا إلى حمل زائد آخر لـ SNI أو الأحمال الزائدة. الآن لديك مصفوفة أكبر. المشكلة السابقة المشار إليها dotnet / runtime # 23107 كانت في الأصل تسير في هذا المسار ولكن عدد التحميلات الزائدة أصبح زائدًا. هناك المزيد من الامتدادات التي لم تظهر بعد أو يتم اعتبارها مع TLS 1.2 و TLS 1.3 قاب قوسين أو أدنى.

ضع في اعتبارك بيانات ZeroRtt ، التي يجب أن تظهر على أنها واجهة برمجة تطبيقات والتي ستحتاج إلى تحميل زائد آخر. أعتقد أن السؤال هو كم عدد الأحمال الزائدة كثير جدًا؟

سأكون سعيدًا بإسقاط أنواع الإرجاع وربما يتم دمج العميل والخادم (ليس الأفضل ولكن مرحبًا) ولكن مجرد إضافة بروتوكول لا يغطي SNI. إنه مجرد ركل العلبة قليلاً على الطريق. كما أنه يجعل من الصعب على المستخدمين النهائيين أن تكون قائمة البروتوكولات الأولية غير ودية بعض الشيء بدون أي طرق مساعدة.

من خلال قراءة طلبات التعليقات (RFCs) ، هذا هو الاقتراح الذي لدي ،

ج #
مساحة الاسم System.Net.Security
{
الهيكل الجزئي العام TlsExtension
{
TlsExtension العام (TlsExtensionType extensionType ، SpanextensionData) {throw null؛ }
public TlsExtensionType ExtensionType {get => throw null؛ }
سبان العامةExtensionData {get => throw null؛ }
}
التعداد العام TlsExtensionType
{
اسم الخادم = 0 ،
ألبن = 16
}
فئة جزئية عامة SslStream: AuthenticatedStream
{
الفراغ الظاهري العام AuthenticateAsClient (string targetHost، System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates ، System.Security.Authentication.SslProtocols enabledSslProtocols ، bool checkCertificateRevocation، IListtlsExtension) {رمي فارغ؛ }
الفراغ الظاهري العام AuthenticateAsServer (System.Security.Cryptography.X509Certificates.X509 شهادة خادم شهادة ، عميل منطقي شهادة مطلوب ، System.Security.Authentication.SslProtocols ممكّنة SSlProtocols ، bool checkCertificateRevocation، IListtlsExtension) {رمي فارغ؛ }
المهمة الافتراضية العامة AuthenticateAsClientAsync (string targetHost، System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates ، System.Security.Authentication.SslProtocols ، SSlProtocols ، bool checkCertificateRevocation، IList)tlsExtension) {رمي فارغ؛ }
المهمة الافتراضية العامة AuthenticateAsServerAsync (System.Security.Cryptography.X509Certificates.X509Cert Certificate ServerCertificate، bool clientCertificateRequired، System.Security.Authentication.SslProtocols enabledSslProtocols، bool checkCertificateRevocation، IList)tlsExtension) {رمي فارغ؛ }
}
}

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

سم مكعبgeoffkizerstephentoubdavidsh @ caesar1995CIPop

CIPop نظرًا لأنك نظرت بالفعل في تطبيق Alpn ، هل لديك أية أفكار حول كيفية تحديد واجهات برمجة التطبيقات هنا ..

انتظر ، لكن هل يمكنني إضافة امتداد واحد فقط هنا؟ ماذا لو أردت ALPN و SNI (وهي حالة شائعة جدًا على جانب الخادم). أيضا كيف أقدم شهادات متعددة؟ الاستخدام العادي لـ SNI هو هذا

يقول العميل إنني متصل بـ www.mycooldomain.com

يقول الخادم حسنًا ، أتوقع إما www.mycooldomain.com أو www.mylesscooldomain.com ، نظرًا لأنك اخترت أول شهادة

ستحتاج إلى امتدادات (xxxxxxxxxx، params TlsExtension [])

وإذا قمت بذلك ، فأنت الآن تمنع أي أحمال زائدة في المستقبل ، وستقع في مشكلة. ولا يزال لا يوفر اسم الخادم -> تعيين الشهادة.

قد يصبح هذا أيضًا شيئًا ، dotnet / runtime # 22507 إذا كان هناك حقيبة خيارات يمكن إضافتها بسهولة ، مع التحميل الزائد سيكون فوضويًا.

أنا قلق قليلاً من أن نهج حقيبة الخيارات يحول ميزة مقيدة نسبيًا (ALPN) إلى صفقة أكبر (إصلاح SslStream API).

انتظر ، لكن هل يمكنني إضافة امتداد واحد فقط هنا؟

يمكن إجراء الأحمال الزائدة لاستخدام IListلهذه الحالة.

أيضا كيف أقدم شهادات متعددة؟

لست متأكدًا من كيفية معالجة هذا مع التصميم الحالي ، دعني أفكر في الأمر أكثر. سأضطر إلى التحقيق في كيفية توفير openssl / schannel لدعم امتدادات Tls ، ومعرفة ما إذا كان بإمكاننا محاكاة هذا التصميم. أوافق على أنه مع إضافة المزيد من الإضافات ، يجب علينا تصميم واجهة برمجة تطبيقات قابلة للتوسيع.

بدلاً من تجميع جميع الخيارات في حقيبة خيارات جديدة ، ألن يكون من الأفضل تحديد مجموعة الإعدادات المحددة المطلوبة لكل امتداد مدعوم بواسطة Sslstream ، وإضافة خاصية لها على TlsExtension Struct؟ لذلك سيحتوي TlsExtension على جميع الإعدادات المطلوبة لتنفيذ هذا الامتداد في المصافحة.

لذا فأنت تريد OptionsBag ولكن تترك الخيارات الحالية خارجها. بالتأكيد هذا احتمال. إذا كنت تريد فهم SChannel و OpenSsl لـ SNI على جانب الخادم ، فهناك طريقتان مختلفتان (بالطبع يختلفان: P)

OpenSsl تقوم بتوفير رد اتصال. عندما يتم اكتشاف SNI ، فإنه يستدعي رد الاتصال. باستخدام كائن SSL الخاص بك ، يمكنك بعد ذلك قراءة SNI من معلمات رد الاتصال و "تبديل" SSL_CTX (السياق) المرتبط به (ومن ثم تقوم عادةً بإنشاء وتجميع السياقات مع جميع الشهادات مقدمًا ، وهو بالمناسبة شيء يجب القيام به على أي حال ، لكن هذه مشكلة مختلفة ليوم آخر).

بالنسبة لـ SChannel ، يمكنك توفير قائمة بالشهادات عند إنشاء كائن المصادقة ، بدلاً من واحد ، ويتم اختياره بناءً على SNI.

بالنسبة لـ ALPN ، فإنك توفر الامتداد لـ Schannel كمخزن مؤقت إضافي للرمز المميز. أنت تكتبها بنفسها بشكل أساسي. ثم هناك عقار يمكنك فحصه للعثور على العقار المتفق عليه.

بالنسبة لـ OpenSsl ، قمت مرة أخرى بتعيين البروتوكولات ، وعلى جانب الخادم مرة أخرى تحصل على رد اتصال.

لذلك لست متأكدًا من أن التنفيذ يمنحنا حقًا الكثير من المساعدة هنا ، فبشكل أساسي لكل امتداد جديد يتم ترميزه يدويًا في OpenSsl ويضيفون طرقًا / عمليات رد نداء محددة لهم.

geoffkizer في ردك ، لقد تجاهلت SNI وهو أمر مهم جدًا ، لعدد من الأسباب (لدي أسباب داخلية) ولكن Tratcher سوف يعطيك بعضًا أنا متأكد أيضًا.

بالإضافة إلى ذلك ، كشيء أخير ، لا يحتوي SslStream على رمز إلغاء ، وهو عبارة عن هراء بعض الشيء ، يمكن للعميل إيقاف المصافحة بسهولة ، واليوم لا توجد طريقة لإيقافها بخلاف قتل التدفق الأساسي ، لذلك يحتاج التطبيق العلوي الوصول إلى تيار الشبكة.

تم التحديث لإسقاط النتائج.

لذلك لست متأكدًا من أن التنفيذ يمنحنا حقًا الكثير من المساعدة هنا ، فبشكل أساسي لكل امتداد جديد يتم ترميزه يدويًا في OpenSsl ويضيفون طرقًا / عمليات رد نداء محددة لهم.

يستخدم SslStream opensl و schannel ، مهما كانت واجهات برمجة التطبيقات التي نكشفها ، نحتاج إلى أن نكون قادرين على توصيلها بطبقات pal هذه. لذا فإن الأمر يستحق التفكير في التنفيذ هنا ، والحصول على أفكار من الأطر المستخدمة بالفعل

بالتأكيد أوافق ، كنت فقط أشير إلى أن كلاهما متعارض تمامًا للأسف. ستكون الحياة سهلة للغاية وإلا :)

لمعلوماتك ، يبدو أنه يمكنك إجراء مزيد من الاستقصاءات لاستدعاء SNI على القناة. مما يعني أنه في حالة ظهور رد اتصال على SslStream ، فإن سيناريو شيء مثل Let's Encrypt يوفر شهادة ديناميكية ممكن لكل من SslStream و OpenSsl. لذا ، ستكون إما قائمة أو رد نداء مثاليين ، إذا كان من الممكن توفير واحد فقط ، فسيكون رد الاتصال هو الأفضل.

يبدو أنه يمكنك توفير رد اتصال لـ SNI على القناة

هل يمكنك توفير رابط لواجهة برمجة التطبيقات لهذا الغرض؟

Drawaes أنا لا

ومع ذلك ، تحدثت مع

أعتقد أنه سيكون من المفيد تقسيم هذا الاقتراح إلى جزأين مختلفين:

(1) واجهات برمجة تطبيقات AuthenticateAsClient / Server جديدة تأخذ أكياس خيارات ورمزًا مميزًا للإلغاء ، بالإضافة إلى تعريف أكياس الخيارات التي تتطابق مع واجهات برمجة التطبيقات الحالية. يجب أن نكون واضحين بشأن المبادئ المستخدمة هنا. أعتقد أننا نخطط لأخذ كل من وسائط المُنشئ و Args الطريقة ووضعها في حقيبة الخيارات. هل هذا صحيح؟
(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; }

لا ينبغي أن تكون هذه لاغية ؛ يجب أن يكون لديهم فقط الإعدادات الافتراضية المناسبة.

يجب توفير قائمة ثابتة لـ Http 1.1 و Http / 2 و Http / 2 عبر TLS لمنع المستخدمين من البحث عنها

يجب علينا تحديدها بشكل صريح كجزء من اقتراح ALPN.

هل نحتاج بالفعل إلى http2 بدون TLS؟ هل هناك أي أسماء بروتوكولات أخرى محددة يجب أن ندرجها؟

أحد الخيارات هو أن يكون لديك منشئ ، لكن هذا أمر مستهجن باعتباره نمط واجهة برمجة التطبيقات ويستخدم في الغالب في المكتبات / أطر العمل ذات المستوى الأعلى (ASP.NET على سبيل المثال). هذا قابل للحل عن طريق أخذ لقطة داخلية للخيارات ولكن يجب أخذها في الاعتبار في أي نمذجة للتهديد.

إليك اقتراحي: اجعل هذه الأشياء تتجمد عند الاستخدام.

بالنسبة لخيارات مثل RemoteCertificateValidationCallback ، والتي يمكنني تحديدها اليوم في المُنشئ ، ماذا يحدث إذا قمت بتمرير شيء ما إلى المُنشئ ثم تركت هذا فارغًا في حقيبة الخيارات؟ هل نستخدم القيمة من حقيبة الخيارات (فارغة) ، أم نستخدم القيمة من المنشئ؟ وبالمثل بالنسبة لمنشئ آخر args.

حسنًا ، أنا جيد في تقسيم SNI و ALPN ليكونا منفصلين عن تغيير حقيبة الخيارات. سأطلق عددين فرعيين لهما وسأجيب على الأسئلة المحددة لهؤلاء وسأناقش فقط مفهوم حقيبة الخيارات هنا.

كيف تجميد استخدام البيان؟ هل هناك علم داخلي وإذا حاولت وضع شيء بعد ذلك فإنه يلقي؟ إذا كان الأمر كذلك ، فما هو الاستثناء المناسب هنا؟ ماذا لو كانت الشهادات في نهاية المطاف قائمة أو قاموس؟ علينا تجميد القائمة / القاموس كذلك .... كيف يتم ذلك؟ (تعجبني الفكرة فقط لست متأكدًا من كيفية تنفيذها).

الخيار الآخر هو أن يكون لديك رمز تجزئة أو مقارنة تتحقق من الإعدادات + الشهادات وما إلى ذلك (الكائنات الفرعية) وتتحقق من ذاكرة التخزين المؤقت الداخلية. إذا لم يكن موجودًا ، فقم بتكرار وضعه هناك. يوجد بالفعل ذاكرة تخزين مؤقت لبيانات الاعتماد الداخلية.

يمكنك حساب المرجع ببساطة لإزالة العناصر. بعد ذلك ، لن يكون هناك عبء كبير على العميل ، ونادرًا ما ينخفض ​​على الخادم إلى الصفر ما لم يكن الخادم الخاص بك لا يقوم بالكثير في هذه الحالة ، لن يكون تخصيص فئة أو اثنين مهمًا؟

أفكار؟ أفضل فرز بعض التفاصيل قبل تحديث الاقتراح مرة أخرى.

لماذا تعتبر حقيبة الخيارات قابلة للتغيير؟ لا يختلف الأمر عن أي شيء آخر: أنت تعطي هذا لواجهة برمجة التطبيقات ، وتتوقع واجهة برمجة التطبيقات أن تكون في حالة متسقة طوال مدة العملية ... إذا قمت بتغييرها أثناء الرحلة ، فهذا خطأ ، لا تختلف عن أي حالة أخرى حيث تقوم بتحوير شيء ما بينما تعتمد الطريقة التي يتم استدعاؤها عليها (على سبيل المثال ، تم تمرير المخازن المؤقتة للقراءة / الكتابة). إذا كنت ترغب في معاملته على أنه غير قابل للتغيير وعدم تغييره أبدًا بعد إنشائه ، يمكنك ذلك. إذا كنت ترغب في معاملته على أنه قابل للتغيير بحيث يمكنك تعديله بين الاستخدامات ، فيمكنك ذلك.

كيف تجميد استخدام البيان؟ هل هناك علم داخلي وإذا حاولت وضع شيء بعد ذلك فإنه يلقي؟ إذا كان الأمر كذلك ، فما هو الاستثناء المناسب هنا؟

نعم. InvalidOperationException. هذا ما يفعله HttpClient / HttpClientHandler لإعداداتها. راجع https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L589

ماذا لو كانت الشهادات في نهاية المطاف قائمة أو قاموس؟ علينا تجميد القائمة / القاموس كذلك .... كيف يتم ذلك؟ (تعجبني الفكرة فقط لست متأكدًا من كيفية تنفيذها).

يمكننا إنشاء قائمة / قاموس قابل للتجميد إذا لزم الأمر.

أنا بخير مع تركها قابلة للتغيير أيضًا. يبدو أنه أسهل نهج.

للمُنشئ مقابل معلمات حقيبة الخيار. يجب طرح استثناء إذا تم توفيرهما في المُنشئ وتم استخدام الخيارات. أو قم برميها فقط إذا تم توفيرها على المُنشئ ومختلفة ، أو خذ الأكثر تقييدًا (في المثال الخاص بك ، استخدم إعادة الاتصال وليس الصفري).

أنا أميل إلى الرمي على الرغم من ... فقط على جانب "الأمن". عادةً ما يكون الغموض الأقل والتباديل الأقل وتخمين القواعد أكثر أمانًا. مرة أخرى ما هو الاستثناء المناسب هنا؟

أفكر مليًا في الكيفية التي ستحتاج بها لاستخدام الخيارات ..... في الوقت الحالي لا يمكنني رؤية مشكلة فورية بخلاف الأداء الداخلي (يمكنك عمل ctx واحد وإعادة استخدامه مرارًا وتكرارًا على openssl) ولكني أعتقد أنه يمكن التعامل معها داخليًا. حتى الآن دعونا نترك التجميد على نار هادئة. تحور هو :)

أفترض فقط إضافة أشياء مثل اسم الخادم الذي تم التفاوض بشأنه على البروتوكول وما إلى ذلك لأن الخصائص جيدة بدلاً من كائن النتائج؟ (أفضل الخصائص ولكن أريد التحقق منها).

هذا هو النمط اليوم ، أليس كذلك؟ أنا سعيد بهذا.

تبدو حقيبة الخيارات جيدة ، ووجود حقيبة الخيارات هذه الخاصة بـ TlsExtension يجعل واجهة برمجة التطبيقات الجديدة أقل إرباكًا بشأن ما تنوي القيام به. في الوقت الحالي ، حافظ على دعم حقيبة الخيارات فقط تلك الإعدادات المطلوبة لـ alpn و sni ، وعندما تحتاج امتدادات tls الجديدة إلى الدعم ، يمكن تمديد هذه الحقيبة لإضافة خصائص جديدة. إذا كان هناك طلب حقيقي من المستخدم لتمديد حقيبة الخيارات إلى الإعدادات القديمة ، فيمكننا بالتأكيد تمديد واجهة برمجة التطبيقات.

ج #
مساحة الاسم System.Net.Security
{
الهيكل الجزئي العام TlsExtensionConfig
{
TlsExtension العامة (TlsExtensionType extensionType) {throw null؛ }
public TlsExtensionType ExtensionType {get => throw null؛ }
// دعم sni من جانب الخادم
الهوية العامةServerCertificates {get؛ يضع؛ }
// support sni من جانب العميل - هل هذا مطلوب ، نظرًا لأن targetHost هي بالفعل معلمة في
// مصادقة واجهات برمجة التطبيقات؟
السلسلة العامة ServerName {get؛ يضع؛ }
// دعم alpn
IList العامةApplicationProtocols {get؛ يضع؛ }
}

[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. لنفترض أننا حصلنا على القدرة على تحديد الأصفار (وهي ميزة تُطلب غالبًا) وهي ليست امتدادًا؟ الآن لدينا مناقشة "هل نزيد التحميل عليها أم نضيفها إلى حقيبة خيارات للإضافات ولكنها ليست امتدادًا".

تعجبني حقًا فكرة جميع الخيارات في حقيبة الخيارات ، وطرق المصادقة الخاصة بذلك فقط التي تدعم عدم التزامن ، وكلها تحتوي على إلغاء. هذا ليس محيرًا على الإطلاق ، لأنه إذا كنت تستخدم "الطريقة القديمة" ولا تريد أو تحتاج إلى أشياء جديدة ، فلن تحصل عليها ، فقط استمر في فعل ما كنت تفعله دائمًا.

بعد مناقشة غير متصلة بالإنترنت مع Drawaes سأعمل على هذا ، نظرًا لوجود حاجة ملحة لإنجاز ذلك. سننشئ نموذجًا أوليًا باستخدام واجهات برمجة التطبيقات المقترحة ، وأعتقد أننا قد نواجه مشكلة في تطبيق Unix ، حيث سنحتاج إلى هذه الخيارات أثناء إنشاء كائن SslStream ، أثناء إنشاء مثيل sslcontext ، وسنقوم بتعيين عمليات الاسترجاعات بعد ذلك. سيتضح لنا ما إذا كنا بحاجة إلى هذه الخيارات أثناء SslStream الجديد أو يمكننا تأجيلها لاحقًا أثناء إنشاء النموذج الأولي. سوف أقوم بتحديث الموضوع عندما يكون لدي المزيد من المعلومات.

يمكنني الإجابة على سؤال OpenSsl الآن. لا تقوم بإنشاء السياق (وبالتالي تحتاج إلى أي عمليات رد نداء وما إلى ذلك) حتى تضغط على طريقة AcquireServer / Client Credentials الخاصة على SecureChannel. لا تضغط على هذا حتى تقوم باستدعاء أول رمز مميز ، ولا يتم الضغط عليه حتى تبدأ المصافحة. لا يمكنك إنشاء كائن SslCtx بدون الشهادة على أي حال ، والتي يتم توفيرها في عملية AuthenticateAsClient / Server.

لست متأكدًا من أن نقل جميع الإعدادات إلى كائن إعدادات ينتهك نمط إنشاء استدعاء مجموعة ، في الواقع أود أن أقترح أنه يعيد فرضه عن طريق نقل الإعدادات الحالية خارج المُنشئ في الحالة "الجديدة". فيما يتعلق بجعل الأحمال الزائدة افتراضية ، لا أعتقد أن هذا كان جزءًا من التصميم؟

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
    }

ربما ترغب في قص القائمة للأشياء التي لا تحتاج إلى دعمها (سريعًا على سبيل المثال) ولكن فقط للحصول على فكرة عن الشكل الذي ستبدو عليه.

لست متأكدًا من أن نقل جميع الإعدادات إلى كائن إعدادات ينتهك نمط إنشاء مكالمة المجموعة

لم أقل أنه ينتهك ، لقد قلت في الواقع أن التصميم الأصلي لهذا الاقتراح (في الجزء العلوي من المشكلة) هو نقطة انطلاق جيدة.

أفتقد الفهم إذن :) لذا فأنت تقوم بعمل نموذج أولي لذلك لا يجب أن أقوم بتحديث اقتراح واجهة برمجة التطبيقات حتى ذلك الحين؟

هل تقوم بعمل نموذج أولي ، لذا لا يجب أن أقوم بتحديث اقتراح واجهة برمجة التطبيقات حتى ذلك الحين؟

أرغب في تنفيذ واختبار التصميم أولاً ، قبل أخذه إلى مراجعة api. أعتقد أن لدي ما يكفي من المدخلات للمتابعة ، بمجرد أن أنشر تغييراتي ، يمكنك البحث عنها لاقتراح التحسينات. يبدو جيدا؟

ممتاز ! ترميز سعيد.

بالنسبة لـ 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
};

نعم ، لا تستخدم تعدادًا لـ alpn. هذه مجموعة بيانات مفتوحة وقد يمنع التعداد دعم القيم الإضافية دون ترقية إطار العمل ، ناهيك عن القيم المخصصة. لا تؤثر القيمة أيضًا على تنفيذ ssl ، لذا لا يوجد سبب لتقييدها. تم ارتكاب خطأ مشابه في الماضي فيما يتعلق برموز حالة http ، ولكن على الأقل هناك يمكنك الإدلاء بها لحلها.

تشير المواصفات أيضًا إلى أهمية ترتيب القائمة ويجب أن تكون قابلة للتخصيص. استخدام الإشارات يمنع هذا.

الثوابت المعروفة جيدة بالرغم من ذلك.

من العدل أن الثوابت المعروفة جيدة بالنسبة لي.

أفترض أنه إذا أردنا حقًا أن نكون على المواصفات ، فسنستخدم ReadOnlySpan<byte> كنوع بيانات alpn. سوف يتجنب مشاكل مثل التشفير والغلاف ، ويكون أكثر كفاءة في التسلسل إلى السلك. الثوابت المعروفة ستجعل هذا قابلاً للاستخدام.

لا يمكننا الحصول على قائمة من الامتدادات.

للسلاسل الثابتة أو المخازن المؤقتة أو أيًا كان ما أود قوله
بروتوكول HTTP1.1
Http / 2 عبر SSL
WebRTC
WebRTC السري
بوب 3
IMAP
بروتوكول نقل الملفات

يجب أن يكون كافيا في الوقت الحالي. يبدو Http / 2 بدون tls سخيفًا لأنك تتصل بالفعل بنقطة نهاية TLS.

أود أن أقول إن سلوك ALPN يجب أن يكون ...

إذا قدمت بروتوكولات ولم يشارك الجانب الآخر على الإطلاق (لم يرسل العميل مطلقًا أو لم يستجب الخادم) ، فقد تم التفاوض على بروتوكول فارغ. إذا لم يكن هناك تطابق ولكن تم إرسال كلا الجانبين ، فهذا استثناء بفشل المصادقة. والباقي تطابق.

استنادًا إلى مناقشات منفصلة ، أعتقد أن حقيبة الخيارات الخاصة بنا ستحتاج أيضًا إلى علامة لدعم تعطيل إعادة التفاوض ، حيث يبدو أن هذا مطلوب لـ HTTP2.

لمعلوماتك التي ذكرتها أعلاه ، كانت ALPN هي الحاجة الملحة ، ولكن

يجب أن يدعم تطبيق TLS امتداد إشارة اسم الخادم (SNI) [TLS-EXT] إلى TLS. يجب أن يشير عملاء HTTP / 2 إلى اسم المجال الهدف عند التفاوض على TLS.

خائف SNI أمر لا بد منه أيضًا ...

نحن ندعم بالفعل SNI من جانب العميل ، وهو ما أعتقد أنه كل ما هو ضروري ليكون متوافقًا مع HTTP2.

نقطة جيدة ، هناك ضرورة واضحة ، ولكن الثغرة هي أنك لست مضطرًا للرد بأي شيء حتى لو كان يدعمه ... :) مرة أخرى إلى Backburner لـ SNI. (بافتراض أن العميل يرسلها ، لم أتحقق من ذلك ولكني سأأخذ كلمتك في هذا الأمر :))

حسنًا ، ربما يجب علينا التحقيق في هذا الأمر ... قد يكون الافتراض خاطئًا أن شركة sni تعمل في جانب العميل

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

لا يستخدم HttpClient في .NET Core 2.0 SslStream.

حقًا ... لذلك يستخدم curl وأي واجهة برمجة تطبيقات سحرية على النوافذ ... لكن الأداة المُدارة الجديدة ستفعل ذلك؟

حقًا ... لذلك يستخدم curl وأي واجهة برمجة تطبيقات سحرية على النوافذ ... لكن الأداة المُدارة الجديدة ستفعل ذلك؟

يستخدم 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 بالتأكيد ... أو هل يجب عليّ كسر wireshark؟

رائع ونعلم أن SSLStream يرسل SNI بالتأكيد ... أو هل يجب عليّ كسر wireshark؟

إذا كنت تتطوع ، فإن التحقق من الصحة يكون دائمًا جيدًا. :)

على الأقل يفعل ذلك على النوافذ
image

الحالة: لدي نموذج أولي لـ Unix ، لكن واجهة alpn apis مدعومة فقط من إصدار openssl 1.0.2. أعمل حاليًا على تصحيح جهازي للحصول على هذا الإصدار للحصول على تطبيقي للبناء.

ما هو الإصدار "min" لـ. net core هذه الأيام؟ (باستثناء macos)

أنا في حيرة من أمري من الإشارات إلى h2c - متى يكون من المنطقي مناقشتها في سياق SslStream؟ يجب ألا يتفاوض العميل والخادم مطلقًا على h2c باستخدام ALPN ، فقط h2.

آسف إذا فاتني شيء ما في المناقشة (تم التمرير من خلاله).

:) ثم نتفق كما قلت

يجب أن يكون كافيا في الوقت الحالي. يبدو Http / 2 بدون tls سخيفًا لأنك تتصل بالفعل بنقطة نهاية TLS.

من خلال تجربة النموذج الأولي ، هذه هي بنية واجهة برمجة التطبيقات التي أمتلكها ،

SslAuthenticationOptions -> حقيبة الخيارات التي تحتوي على إعدادات مشتركة بين العميل والخادم ، يتم تعيينها عند تهيئة المُنشئ. هذا يضمن أننا لسنا في حل لتحديد القيمة التي نختارها من خلال إتاحة هذه الخيارات فقط من خلال المُنشئ ، وليس من خلال أي من طرق المصادقة *.

SslClientAuthenticationOptions، SslServerAuthenticationOptions -> تحتوي هذه الحقيبة على خيارات خاصة بالعميل أو الخادم ، ويتم تعيينها من خلال إحدى طرق AuthenticateAs * ، والتي يتم خلالها العثور على هوية SslStream ، إما كعميل أو خادم.

ج #
فئة عامة SslAuthenticationOptions
{
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة UserCertificateValidationCallback {get؛ يضع؛ }
LocalCertificateSelectionCallback العامة UserCertificateSelectionCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionOption {get؛ يضع؛ }
}

فئة عامة SslClientAuthenticationOptions
{
السلسلة العامة TargetHost {get؛ يضع؛ }
شهادات العميل X509CertificateColllection العامة {get؛ يضع؛ }
public bool CheckCertificateRevocation {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
}

فئة عامة SslServerAuthenticationOptions
{
DefaultServerCertificate X509Certificate العامة {get؛ يضع؛ }
قاموس عامServerCertificates {get؛ يضع؛ }
ClientCertificateRequired عام منطقي {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
public bool CheckCertificateRevocation {get؛ يضع؛ }
}

فئة عامة SslStream: AuthenticatedStream
{
العامة SslStream (Stream innerStream ، SslAuthenticationOptions sslAuthenticationOptions) {}
بث SslStream العام (دفق داخلي ، ترك منطقي ، InnerStreamOpen ، SslAuthenticationOptions sslAuthenticationOptions) {}
المهمة الافتراضية العامة AuthenticateAsClientAsync (SslClientAuthenticationOptions sslClientAuthenticationOptions، CancellationToken cancellationToken) {}
المهمة الافتراضية العامة AuthenticateAsServerAsync (SslServerAuthenticationOptions sslServerAuthenticationOptions، CancellationToken cancellationToken) {}

public string NegotiatedApplicationProtocol { get; }

}
""

هل هناك خاصية على SSLStream لمعرفة اسم المضيف الذي تم الإشارة إليه؟ بخلاف أنها تبدو جيدة. آمل أن أقوم بتحديث المشكلة بالتصميم الليلة. أنا أحب المنشئ / المصادقة على فكرة الانقسام.

جميل ، لذا فإن جميع المُنشئين القدامى أصبحوا أغلفة للآلة الجديدة؟ ونفس الشيء بالنسبة لطرق AuthenticateAsClient / Server؟

هذا يعني أيضًا أن APLN تعمل مع جميع واجهات برمجة تطبيقات AuthenticateAsClient / Server القديمة ، وليس فقط الجديدة؟

هناك إشارة ضمنية إلى أن SslAuthenticationOptions قابلة للتغيير بين استدعاء المُنشئ واستدعاء المصادقة ... إذا كان هذا صحيحًا ، فأنت لست بحاجة إلى المنشئات الجديدة. على سبيل المثال

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

هل يمكن جعل EnabledSslProtocols لاغية بحيث يمكن أن تطفو مع افتراضية النظام؟ أم لا شيء يعمل من أجل ذلك؟

لست حريصًا على ذلك ... لأن التخصيصات تريد تخزين هذا الكائن مؤقتًا واستخدامه مرارًا وتكرارًا. أيضًا قد يكون هناك بعض الذكاء الذي يمكننا فعله باستخدام رمز التجزئة وذاكرة التخزين المؤقت ...؟

إذا كانت الخيارات ستظهر كخاصية ، فيجب أن تكون i / قابلية التغيير الخاصة بها أكثر وضوحًا / فرضًا.

تضمين التغريدة
الطريقة الصحيحة ستكون ،

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

لا يمكن تغيير الخيارات بعد إنشاء الكائن ، حيث لن تنعكس في سياق الأمان الذي تم إنشاؤه بعد المصادقة الأولى. بمعنى أنه لا يمكن تغيير سياق الأمان بمجرد إنشائه ، يجب التخلص منه أولاً ، لتهيئته بقيم جديدة. لذا ، فإن جعل هذه الخاصية قابلة للتغيير أمر مربك ، حيث لن يكون للتغييرات أي أهمية بعد إنشاء سياق الأمان.

ونعم ، ستكون جميع المنشئات والأساليب الأصلية القديمة أغلفة لواجهات برمجة التطبيقات الجديدة هذه.

إذا لم يعمل sslStream.AuthenticationOptions.ApplicationProtocols = new[] { HTTP2, HTTP11 }; فيجب أن يكون هناك خطأ في المترجم أو وقت التشغيل عند تجربته.

كائن الخيارات ليس سوى برنامج getter ولكن قائمة بروتوكول التطبيق قابلة للتغيير في تصميمك أعلاه.

الآن بعد أن فكرت في الأمر ، ربما ليس من المهم التفكير في سيناريو تغيير الخيارات خارج المُنشئ ، نظرًا لأن SslStream لن يسمح لك بإجراء عمليات توثيق متعددة ، ولا تكون هذه الخصائص مطلوبة إلا أثناء مرحلة المصافحة . لذلك أعتقد أن الطريقة المذكورة أعلاه مقبولة أيضًا ، يمكن تغيير الخيارات أي عدد من المرات قبل المصافحة ، بعد المصافحة حتى إذا تم تغيير الخيارات ، فإنها لا تؤثر على وظائف التشفير / فك التشفير عند الكتابة / القراءة.

:) أحب هذا الخيار ...

هل اعتبرنا التخصيصات على السلسلة -> ascii و alpn lookups؟ قد يكون هناك بعض الناس مستاءين إذا قمنا بإلقاء المزيد من التخصيصات في هذا المزيج. هل هناك طريقة يمكننا من خلالها تعديل واجهة برمجة التطبيقات للمساعدة في ذلك؟

إذا كان لديك التحميل الزائد public SslStream(Stream innerStream, SslAuthenticationOptions sslAuthenticationOptions) { } فيمكنك ترك الأمر للمتصل ليقرر ما إذا كان يريد إعادة الاستخدام أو التحوير ، طالما كنت واضحًا أنه النسخة الأصلية وليست نسخة.

أنا أحب هذا النهج. أعتقد أن وضع حقيبة الخيارات على المُنشئ له معنى كبير.

لا أعتقد أننا بحاجة إلى كشف خاصية AuthenticationOptions على الإطلاق. لا يبدو أن هذا يضيف شيئًا ، وهو أمر محير بعض الشيء. لا نكشف عن قوائم المُنشئ الموجودة على SslStream اليوم (على سبيل المثال RemoteCertificateValidationCallback).

هناك شيء واحد لست متأكدًا منه: يحتوي هذا الاقتراح بشكل فعال على "حقيبتين مختلفتين" ، أحدهما يُستخدم للمُنشئ والآخر يستخدم لطرق AuthenticateAs. (في الواقع ثلاثة ، نظرًا لوجود واحد لـ AuthenticateAsClient وآخر لـ AuthenticateAsServer).

هذا يعني أنه سيتعين على كل مستهلك إنشاء حقيبتين للخيار (أو استخدام واجهات برمجة التطبيقات الحالية).

إذا كنا ننتقل إلى أكياس الخيارات ، فأنا أتساءل عما إذا كان من الأفضل لنا مجرد امتلاك واحدة تُستخدم في المُنشئ ، مع نكهات العميل والخادم. هذا يسمح للمستهلك ببناء حقيبة خيارات واحدة فقط.

لذا يعجبني ذلك ، لأنه إذا قمت بذلك ، فسيكون 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 بتسجيل شيء ما أو شيء آخر؟ لهذا السبب الآن ، أنا ضد خيارات المُنشئ .. الأفضل هو أن يكون لديك حقيبة خيارات العميل ، وحقيبة خيارات الخادم مكشوفة من خلال أساليب المصادقة ، وبعد ذلك ، اجعل حقيبة الخيارات هذه لا تتضمن الخيارات الحالية المتاحة من خلال مُنشئ 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 إلى usercode.

الآن صحيح أنه لا يمكنك تغيير الاتصال الحالي من رمز المستخدم (عن طريق الصدفة أو غير ذلك) ولكن يمكنك تغيير الإعدادات لجميع الاتصالات المستقبلية.

geoffkizer هل

ج #
فئة عامة SslAuthenticationOptions
{
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة UserCertificateValidationCallback {get؛ يضع؛ }
LocalCertificateSelectionCallback العامة UserCertificateSelectionCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionOption {get؛ يضع؛ }
}

فئة عامة SslClientAuthenticationOptions: SslAuthenticationOptions
{
السلسلة العامة TargetHost {get؛ يضع؛ }
شهادات العميل X509CertificateColllection العامة {get؛ يضع؛ }
public bool CheckCertificateRevocation {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
}

فئة عامة SslServerAuthenticationOptions: SslAuthenticationOptions
{
DefaultServerCertificate X509Certificate العامة {get؛ يضع؛ }
قاموس عامServerCertificates {get؛ يضع؛ }
ClientCertificateRequired عام منطقي {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
public bool CheckCertificateRevocation {get؛ يضع؛ }
}

فئة عامة SslStream: AuthenticatedStream
{
العامة SslStream (Stream innerStream ، SslAuthenticationOptions sslAuthenticationOptions) {}
بث SslStream العام (دفق داخلي ، ترك منطقي ، InnerStreamOpen ، 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 للعميل مقابل الخادم أمر منطقي. لا يمكنك في الواقع استخدام الفئة الأساسية هنا ، لذلك لا معنى لوجود مُنشئ يأخذ الفئة الأساسية.
  • أتساءل عما إذا كان يجب علينا تبديل البارامتر بالترتيب مغادرة InnerStreamOpen. وهذا هو ، (إينيرستريم ، خيارات ، ترك إينرستريموبن). أتساءل أيضًا عما إذا كان يجب أن يكون هذا مجرد معلمة اختيارية مع افتراضي = خطأ. لست متأكدًا من التفكير الحالي في المعلمات الاختيارية ...

شيء أكثر جذرية قليلاً للنظر فيه:

بقدر ما أستطيع أن أرى ، السبب الوحيد لفصلنا طريقة المصادقة عن ctor هو أن طريقة المصادقة غير متزامنة.

بدلاً من ذلك ، يمكننا التفكير في نقل طريقة المصادقة إلى حقيبة الخيارات نفسها وجعلها تقوم بكل من الإنشاء والمصادقة. يؤدي هذا إلى تقليل العملية إلى خطوة واحدة وتجنب الحالة الغريبة نوعًا ما "تم إنشاء الدفق ولكن لم تتم مصادقته".

سيبدو شيء هكذا:

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 القادر على مشاركة الموارد المختلفة ، مثل المخازن المؤقتة لإعادة التدوير. لكن هذا اليوم شبه مستحيل بسبب عدد الإعدادات ، التي يمكن أن تتغير بين إنشاء دفقين.

ومع ذلك ، إذا كان بإمكاننا الحصول على "كائن" يمكن أن يكون فعليًا تتبعًا أو مصنعًا أو أي كائن (هذا هو السبب في أن السؤال القابل للتغيير / غير القابل للتغيير / البناء () مهم بالنسبة لي) ، فيمكننا إنشاء SSL_CTX لذلك وإعادة استخدامه هو - هي. هناك أيضًا الكثير من عمليات القفل لإنشاء SSL_CTX نظرًا لأنه يحتوي على قفل عام ... راجع مناقشة تأمين OpenSsl الخاصة بي ، حيث تمارس العديد من الاتصالات السريعة ضغطًا على هؤلاء.

على أي حال ، مجرد فكرة ، إذا كان من الممكن تصميم ذلك ، فقد يسهل الطريق إلى تدفق SSL أسرع على الأقل لنظام Linux. والذي سيكون رائعًا لأنه في النموذج يوجد الكثير من القفز على الأطواق لأنه تم تصميمه حول القناة وهو أمر مفهوم ، ولكن قد يكون هذا إيماءة تصميم صغيرة يمكن إعطاؤها؟

احتفظت بمصادقة AuthenticateAsClient / Server من أجل الاتساق / الاكتشاف.

يؤدي نقل AuthenticateAsync خارج SslStream إلى خيارات إلى نقلك إلى نمط استخدام مختلف تمامًا. إذا كنا نبدأ من الصفر ، فسأقول نعم ، لكنني لا أرغب في تفرع واجهة برمجة التطبيقات الحالية من هذا القبيل ، فإن ذلك سيجعل اعتماد الميزات الجديدة في الكود الحالي أكثر صعوبة. هل Corefx يصل لذلك؟

يؤدي نقل AuthenticateAsync خارج SslStream إلى خيارات إلى نقلك إلى نمط استخدام مختلف تمامًا.

أنا موافق. لكني أشعر أننا بالفعل جزء من مسار "نمط الاستخدام المختلف" بالفعل. يتطلب الاقتراح الأحدث من المستخدمين تغيير كل من استدعاء المُنشئ واستدعاء المصادقة بطرق غير بديهية. بمجرد أن نسير في هذا الطريق ، ليس من الواضح لي أين يجب أن نتوقف.

بدلاً من ذلك ، إذا أردنا تقليل التأثير على نمط الاستخدام إلى الحد الأدنى ، فيمكننا فقط إضافة حملتين زائديتين جديدتين.

هذا هو الطيف كما أراه:

(1) ما عليك سوى إضافة حمولات زائدة جديدة من ctor تأخذ "IListapplicationProtocols "و" Bool يدعم إعادة التفاوض "
=> أصغر تغيير ، أصغر تأثير على نموذج الاستخدام الحالي
(2) إضافة ctor جديد يأخذ OptionBag ، مع معلمات ctor الحالية بالإضافة إلى المعلمتين الجديدتين أعلاه.
=> يؤثر على نموذج الاستخدام الحالي إلى حد ما ، ولكن فقط للمنشئ
=> غريب بعض الشيء في هذا ctor يعتمد على OptionBag ، لكن طرق المصادقة ليست كذلك
+ ثم تنقّل تغييرات نموذج الاستخدام بمرور الوقت وينتهي بك الأمر بالعديد من الطرق المختلفة لتحقيق نفس الشيء
(3) إضافة ctors جديدة تأخذ ClientOptionsBag / ServerOptionsBag بالإضافة إلى أساليب المصادقة الجديدة مع الحد الأدنى من المعلمات
=> تغيير أكبر لنموذج الاستخدام
=> أكثر اتساقًا من (2) ، حيث تكون جميع الخيارات عبر حقيبة خيارات واحدة
(4) نموذج المصنع
=> تغيير كبير في نموذج الاستخدام
=> إذا كنا نبدأ من الصفر ، فربما نفعل ذلك

لست متأكدًا تمامًا من مكان الهبوط هنا. (2) هو الأقل تفضيلاً لدي.

إنه كذلك ، وعلى الرغم من أن هذا هو السيناريو "المثالي" الذي أشعر به (وفقًا لملف سابق قمت باختراقه معًا لخطوط الأنابيب في البداية) ، يمكننا القيام بشيء أشبه بـ X509 هنا حيث أنه غير قابل للتغيير بشكل أساسي بطبيعته ويمكنك الرجوع إلى لذلك إذا تخلص منها المستخدم فلا داعي للقلق.

لمعلوماتك ، كان تصميمي الأصلي متوافقًا مع مآخذ TCP حيث يكون لديهم مستمع يوفر لك مقبسًا لكل اتصال ، وكان هذا "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 من "السياق / المصنع / [أدخل الاسم]"

نعم ، وبعد ذلك ، هذا يجعلني أرغب في التعميم إلى أبعد من ذلك ، أبعد من SslStream ، ويجعلني أتساءل كيف يرتبط هذا بأشياء المشروع الأساسية.

من فضلك لا تفعل (1) ، لقد تجاوز المنشئون بالفعل عددًا معقولًا من المعلمات ، ولا يقربنا من معالجة سيناريوهات أخرى مثل SNI. (2) و (3) اختلافات جمالية إلى حد كبير. (4) هو تغيير أساسي في الاستخدام سيكون من الصعب استهلاكه. على سبيل المثال ، فهو يجمع بين رمز الإنشاء والاتصال الذي قد يحدث حاليًا في أماكن مختلفة في طلبي.

فهو يجمع بين رمز الإنشاء والاتصال الذي قد يحدث حاليًا في أماكن مختلفة في طلبي.

ألا ينطبق هذا أيضًا على (3)؟

tractcher تصميمي أعلاه لا يقوم السياق فقط بإنشاء الدفق الذي لا يزال بإمكانك استدعاء المصافحة التي لا يمكنك تكوينها.

إحدى المشكلات التي أراها مع 4 كما هو مقترح هي أنه يغير الوظائف الأساسية المفهومة لـ SslStream ، فأنت تقوم الآن بمصافحة الخيارات ، ثم ما هو الغرض من المصادقة * على sslstream. ستكون واجهات برمجة التطبيقات هذه زائدة عن الحاجة الآن ، وستظل لدينا مكشوفة ، وهذا يؤدي بطرق متعددة للقيام بنفس الشيء. أحب مقتطف رمز Drawaes أعلاه ، حيث لا تزال المصافحة محفوظة في sslstream ولديها الخيارات لتكون مصنعًا لإنتاج كائنات sslstream بإعدادات إدخال مختلفة. أعتقد أنه يمكننا المضي قدمًا في هذا النموذج حتى الآن ، ووضع الحد الأدنى من المقابض المطلوبة في مصنع الخيارات هذا في الوقت الحالي ، وتمديده لاحقًا لملحقات tls الأخرى.

أحب مقتطف رمز Drawaes أعلاه ، حيث لا تزال المصافحة محفوظة في sslstream ولديها الخيارات لتكون مصنعًا لإنتاج كائنات sslstream بإعدادات إدخال متنوعة

فاتني هذا في الأصل.

في أسلوب أسلوب "المصنع" ، لست متأكدًا من الهدف من وجود طريقة مصادقة منفصلة (PerformHandshakeAsync في مقتطف drawaes ). متى ستفعل أي شيء بخلاف "الإنشاء" متبوعًا مباشرة بـ PerformHandshakeAsync؟ ولا تزال أساليب المصادقة * زائدة عن الحاجة.

في حالة خطوط الأنابيب كان الأمر يتعلق

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

لذا فقد ساعدنا في نقل الخلق والمصادقة هنا. لكنها قد تكون مفيدة وقد لا تكون كذلك. أعتقد أن هذا هو النمط الذي ألمح إليه

الشيء الآخر هو ، إذا قمت بإرجاع مهمة من هناك لأن المصافحة غير متزامنة بطبيعتها ، فقد يبدو الرمز مثل هذا (وهو أمر قبيح)

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

} 

لذلك ينتهي بك الأمر دائمًا إلى الحصول على هذا الكائن من المهمة ... ليس أجمل شيء في العالم.

لا أفهم لماذا هذا قبيح. هناك الكثير من واجهات برمجة التطبيقات التي تعمل بهذا الشكل اليوم.

هذا النقاش برمته يدفعني نحو نهج الحد الأدنى هنا. يبدو أن إجراء تغيير أكثر تعقيدًا يفتح مجموعة من المشكلات الجديدة ، وأنا أفضل تأجيلها وجعل ALPN وما إلى ذلك يعمل.

مشكلة جانبية: SNI. هل هناك أي سبب يمنعنا من استخدام وسيطة "targetHost" في LocalCertificateSelectionCallback لجعل هذا يعمل على الخادم؟ على الخادم ، يتم ضبط هذا دائمًا على سلسلة فارغة اليوم.

geoffkizer لقد شعرت بالإغراء لاقتراح إجراء SNI بهذا

أنا سعيد لإسقاط هذا الشيء في كلتا الحالتين. أعني فقط ، لا يمكنني التفكير في العديد من الأمثلة التي تحتوي على معرفات قابلة للتخصيص داخل المهمة ، وهي "مفاجئة" لمعظم المبرمجين (ربما ليس في MS ولكن خارجها :)).

بالنسبة لإسقاطها لإنجازها ، كما قلت أعلاه ، سأكون على ما يرام مع ذلك ، ولكن بعد ذلك لا توجد حقيبة خيارات ، فقط أضف معلمة وحمل زائد وستنتهي الآن ويمكن التفكير في الأمر ، لدي ميزة صغيرة أنني كنت أفكر في هذا منذ أسبوع :)

في SNI ، توجد طريقة لاستخدام رد اتصال لتبديل الشهادات على قناة SChannel ، كنت أعاني من أجل العثور على واحدة ، لكن المصدر مغلق والوثائق عمرها بضع سنوات ، وقد يكون لديك المزيد من الموارد وجهات الاتصال للمساعدة في ذلك.

من فضلك لا ترجع للخيار 1 ... من فضلك ...

الجانب الإيجابي للخيار 1 ، إنه أمر قبيح جدًا أنه سيفرض تغييرًا سريعًا بعد: P

🤕

Tratcher نعم ، آلية LocalCertificateSelectionCallback بأكملها غريبة بعض الشيء ، ولا أفهم تمامًا كيف تعمل ، أو الأشياء التي تعمل على العميل مقابل الخادم.

LocalCertificateSelectionCallback هو فقط للعملاء. تأتي شهادات الاستضافة والشهادات المحلية من AuthenticateAsClientAsync ، وتأتي الشهادات عن بعد والمصدرين المقبولين من الخادم. يجب على العميل اختيار شهادة تطابق أحد المصدرين المقبولين.

بالنسبة إلى SNI ، يرسل العميل اسم مضيف واحدًا ، ثم يكون للخادم خيار اختيار شهادته. المشكلة بقدر ما أستطيع أن أرى في قناة SChannel ، يجب عليك توفير تلك القائمة مقدمًا وستختار قناة SChannel من تلك القائمة لك ، ولا يمكنك توفيرها من "الخارج". ولكن قد يكون هناك طريقة أفتقدها

LocalCertificateSelectionCallback هو فقط للعملاء.

منطقي. ومع ذلك ، يتم استدعاؤها بالفعل في حالة الخادم أيضًا. من الواضح أن جميع المعلمات مضبوطة على خالية / فارغة باستثناء مجموعة cert - نحن نبني مجموعة temp مع شهادة الخادم فقط. ثم نستخدم أي شهادة تعود من رد الاتصال هذا.

لماذا نفعل هذا ، ليس لدي أي فكرة.

الكود القديم شيء رائع وغامض ..

@ Priya91geoffkizer وقد تم اتخاذ قرار؟ الطريقة التي أراها هي

  1. حقيبة الخيارات الآن ، نوع من 1/2 طريقة لما ستكون عليه "المثالية"
  2. اربح بسرعة ALPN الآن وابدأ مناقشات API حول "هدف مستقبلي" مثل مصنع أو إعادة تصميم كاملة

شخصياً مع قيود الوقت لـ 2.1 Http / 2 أود أن أقول اذهب مع 2 ، أخرج ALPN وابدأ المناقشة الأكبر مع مزيد من الوقت في الجيب مع مراعاة أشياء مثل حجر الأساس وخطوط الأنابيب للمشروع.

على أي حال .. أفكار؟

اذهب مع حقيبة الخيار. التصميم الحالي غير مستدام ، ولا أتوقع حدوث إعادة تصميم أكثر دراماتيكية في أي وقت قريب. كما أن حقيبة الخيارات لم تعد تستغرق وقتًا طويلاً في التنفيذ.

Tratcher أي خيار حقيبة اقتراح؟ (2) أعلاه؟ (3) أعلاه؟ شيء آخر؟

(2) مع حقيبة الخيارات للمُنشئ هو الأقل تأثيرًا أثناء تلبية المتطلبات. يمكن أن يتعامل هذا بسهولة مع ALPN وخيار إعادة التفاوض. لا شيء هنا يتطلب تغييرات في أساليب المصادقة.

بالتفكير مسبقًا في SNI ، لا يزال من الممكن القيام بذلك باستخدام (2) إذا كان لديك خيارات محددة للعميل / الخادم وأحمال زائدة. أو تقدم حقائب خيارات منفصلة للعميل / الخادم لطرق المصادقة. أتردد في المضي قدمًا إلى (3) حيث ينتقل كل شيء إلى خيارات المُنشئ حيث يتطلب الأمر مزيدًا من الزخم للمُنفِّذ والمستهلكين ، لكنني أعتقد أن أفضل قرار يتم تقييمه من قبل المُنفِّذ. أتوقع أن يلبي أي من هذه الخيارات متطلبات ASP.NET Core الخاصة بنا.

حسنًا ، على الرغم من أن Tratcher يفكر في كيف كنت في البداية ، فقد حصلت على آمالي في إصلاح فعلي مستقبلي ولست متأكدًا الآن من أنه الاتجاه الصحيح.

ومع ذلك ، سأبتعد عن السلوك العام للإنترنت وأستسلم حيث يبدو أن هناك دعمًا قويًا لهذا التصميم. إذن ، هذا ما أعتقده لتحريك الأمور

@ Priya91 إذا كنت توافق على ما يبدو أنك تقوم بالتنفيذ / التحقيق وإذا كنت توافق مع Tratcher ، فهل يمكننا الحصول على تصميم حول هذا الموضوع هنا ، إذا وافق geoffkizer أيضًا على التصميم ، فسوف أقوم بتحديث الجزء العلوي ونحن يمكن أن تضغط ...

(تعليق للأجيال القادمة ، سنعود هنا في المستقبل القريب: P)

لقد عملت أيضًا على alpn للعمل على Linux ، واستخدمت حقيبة خيارات أساسية لمنطقة سطح api ، يمكنني تغيير ذلك بعد الموافقة على api. في غضون ذلك ، إليك الالتزام بتغييرات alpn على نظام Linux ، اختبار alpn يعمل ، لكن بعض الاختبارات الأخرى تفشل ، تحقق منها.

حسنًا Tratcher إذا كنت سعيدًا بواجهة برمجة التطبيقات العامة في هذا الالتزام ، يمكنني استخلاص وتحديث التعليق الأول في المشكلة ويمكننا المضي قدمًا؟

بجميع الطرق

لا أعتقد أننا يجب أن نفعل (2). إنها نصف خطوة في أحسن الأحوال نحو نهج حقيبة الخيارات الكاملة.

لقد كنت داعمًا لاستكشاف نهج حقيبة الخيارات ، ولكن لقد مرت أسابيع حرفيًا منذ أن بدأنا في هذا الأمر ولم يتقارب. في هذه المرحلة ، لا أعتقد أن الأمر يستحق الاستمرار في المتابعة.

أعتقد أننا يجب أن نفعل ببساطة 1 ، إضافة ctor الزائد.

Tratcher ، لقد

هذا هو النص الكامل للنهج (1). إذا كان هناك أي شيء غير صحيح هنا ، فيرجى إبلاغي بذلك.

ALPN:

ج #
فئة عامة SslStream: AuthenticatedStream
{
عام SslStream (
دفق داخلي ،
إجازة منطقية InnerStreamOpen ،
RemoteCertificateValidationCallback userCertificateValidationCallback،
LocalCertificateSelectionCallback userCertificateSelectionCallback ،
EncryptionPolicy تشفير السياسة ،
الأول قائمةapplicationProtocols) ؛

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

دعونا ننهي هذا في وضع عدم الاتصال.

geoffkizerTratcher هل لدينا توافق في الآراء بشأن التصميم؟

لا يوجد إجماع في هذه المرحلة.

لقد تحدثت إلى terrajobst اليوم ، وسنستخدم فتحة تصميم الثلاثاء 11 صباحًا العادية لمناقشة هذا الأمر. دعوة يجب أن تخرج قريبا.

Tratcher ، يرجى إعادة توجيه الدعوة حسب الاقتضاء.

@ Priya91karelzstephentoub، لمعلوماتك - ونأمل أن تتمكن من إجراء هذا اللقاء.

Drawaes سيكون من الرائع أن يكون لديك أيضًا ، لكن لست متأكدًا من مدى ملاءمة ذلك لجدولك ...

لا يتعلق باقتراح api ، لكن SecureTransport لا يحتوي على أي واجهات برمجة تطبيقات عامة لدعم ALPN ، لذلك سنقوم بإلقاء PNSE على OSX.

هاه. هذا مؤسف.

لا يحتوي SecureTransport على أي واجهات برمجة تطبيقات عامة لدعم 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. ذكر أحد التعليقات واجهة برمجة تطبيقات SSLSetALPNProtocols جديدة سيتم إصدارها في macOS 10.13 والتي هي حاليًا في مرحلة تجريبية. هل يمكن أن نضيء دعم ALPN على macOS إذا كانت واجهة برمجة التطبيقات هذه متاحة؟

يجب أن تكون الساعة 7 مساءً ldn على ما يرام طالما أن اختبار القلم الخاص بي الأسبوع المقبل لا يسير على ما يرام :)

هل يمكن أن نضيء دعم ALPN على macOS إذا كانت واجهة برمجة التطبيقات هذه متاحة؟

أفترض ذلك. @ Priya91؟

هل يمكن أن نضيء دعم ALPN على macOS إذا كانت واجهة برمجة التطبيقات هذه متاحة؟

ليس بسهولة. في الوقت الحالي ، نبني على الإصدار 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 للتحقيق. ونعم ، أوافق على أن ذلك سيكون أكثر قابلية للتطبيق.
سيكون السؤال المفتوح هو السلوك في الإصدارات الأقدم. سيكون من الجيد IMHO إذا كان كل شيء يعمل ، فقط ALPN سوف يطرح استثناء للمنصة غير المدعومة.

wfurt كيف

ماذا عن وضع احتياطي gracefull حيث يعود ApplicationProtocal الذي تم التفاوض بشأنه فارغًا ويقرر التطبيق ما إذا كان يعود إلى الوضع الافتراضي (HTTP / 1.1) أو يفشل؟ يجب أن يتعامل التطبيق بالفعل مع هذا السيناريو عندما لا يدعم الطرف البعيد ALPN.

أعتقد أن مُحمل المكتبة سيرفض تحميل الرقاقة (نظرًا لأنه لا يمكنه حل عملية الاستيراد) ، مما يعني أن أي شيء يلامس التشفير قد يتسبب في فشل تحميل التطبيق. (ما لم نقوم بالكثير من العمل المخيف)

bartonjs ،

بشكل عام ، PlatformNotSupportedExceptions هي ألغام أرضية للمستهلكين ، يفضل ASP.NET استخدام lightup / no-op لميزات اختيارية مثل ALPN.

stephentoub "عمل مخيف" :). على الرغم من أن القيام بذلك لزوج واحد من الوظائف قد يكون أكثر احتواءًا ذاتيًا مما لو كنا سنقوم بكل شيء كما فعلنا ضد OpenSSL من أجل الجهد المحمول.

أميل إلى الموافقة على Tratcher الجانب السلبي هو سحب متطلبات نظام تشغيل جديدة لشيء ربما يكون هامشيًا لمعظم المستخدمين. طلب 10.12.4 بدلاً من 10.12 لا يبدو سيئًا.

أود أن أعلق هذا الآن ويمكنني مشاركة المزيد من المعلومات عندما أقوم ببعض الاختبارات.

من بين جميع المقترحات ، يبدو التزام @ Priya91 هو الأبسط من حيث الاستهلاك والتنفيذ. سألخص:
ج #
فئة جزئية عامة SslStream
{
SslStream العام (خيارات البث الداخلي و SslAuthenticationOptions) {}
عام SslStream (Stream innerStream، bool leaveInnerStreamOpen، SslAuthenticationOptions options) {}

السلسلة العامة ServerIndicationName {get؛ }
السلسلة العامة ApplicationProtocol {get؛ }
}

فئة عامة SslAuthenticationOptions
{
منطقية عامة AllowRenegotiation {get؛ يضع؛ }
IList العامةApplicationProtocols {get؛ }
سياسة التشفير العامة؟ EncryptionPolicy {get؛ يضع؛ }
الهوية العامةServerCertificates {get؛ يضع؛ }
LocalCertificateSelectionCallback العامة UserCertificateSelectionCallback {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة RemoteCertificateValidationCallback {get؛ يضع؛ }
}
""

سيظل رد الاتصال مفضلًا على IDictionary<string, X509Certificate> لشهادات ServerCertificates ، لكنني تلقيت تقارير مختلطة حول ما إذا كان ذلك مدعومًا على Windows.

يعجبني أن فئة الخيارات ستوفر مكانًا لالتقاط مجموعة من الشهادات للقيام بتثبيت الجذر (بمجرد إضافة هذا الخيار إلى X509Chain).

ستنجح ، هناك بعض الأشياء التي ما زلت أرغب في حلها

  1. كيفية عمل Ssl_Ctx مشترك لـ OpenSsl
  2. هل يمكننا إجراء رد اتصال لشهادة الخادم لـ SNI؟
  3. رموز الإلغاء لطرق المصادقة
  4. تقليل التخصيصات المتضمنة في تنفيذ ALPN الحالي

أحتاج إلى الجلوس والتفكير فيما إذا كان يمكن حل 1،3،4. بالنسبة إلى الرقم 2 ، أود أن أقول تأجيل استخدام SNI إذا لم يكن حرجًا في هذه المرحلة حتى يتمكن شخص ما من إجراء بعض التحقيقات على جانب القناة (يدعم OpenSsl ذلك بالتأكيد).

لن يؤثر Drawaes 1،3،4 على مشكلة اقتراح api هنا. 1 و 4 عبارة عن تحسينات للأداء ، 2 و 3 طلبات مختلفة لواجهة برمجة التطبيقات. لن أعتبر هذه الحاصرات لاقتراح 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 صباحًا بتوقيت المحيط الهادئ.

(2) مع فئة ثابتة خاصة بهم. الآخرون يسببون ضوضاء API.

لقد قمت بتحديث الاقتراح بملاحظات الاجتماع وواجهة برمجة التطبيقات (API) المعدلة. اسمحوا لي أن أعرف إذا فاتني أي شيء.

أنت بحاجة إلى القدرة على تعطيل rengotiation وإزالة الحد الأقصى لحجم التجزئة في الوقت الحالي.

تعد إعادة التفاوض المعطلة أحد متطلبات http / 2

Drawaes تمت إضافة إعادة التفاوض.
لقد قمت بالفعل بإزالة الحجم الأقصى للجزء ، أليس كذلك؟

كلا ، لقد كان من جانب العميل ولكن لدي الآن :)

حصلتstephentoub على نقطة جيدة ، بعد الاجتماع مباشرة وأعتقد أنني @ halter73 سعيدًا أيضًا. مع نهج optionsbag يمكن إضافة "مصنع" بعد وحقيبة الخيارات قد تساعد بالفعل. لذلك سيكون مجاملة. من أجل إنجاحها ، ستحتاج إلى بعض الوصول الداخلي إلى SslStream لتتمكن من توفير القيم المخزنة مؤقتًا مباشرةً ، لذا قد تحتاج إلى أن تكون في التجميع.

لتوضيح هذا لا يؤثر بأي شكل من الأشكال على عمل optionsbag ، أو اقتراح API هذا.

طلبت karelz الإشارة إلى الأطر الأخرى باستخدام "نهج المصنع" ، ووجدت المراجع الخاصة بي (هناك في التعليقات قبل 23 يومًا ؛))

مراجع لأطر أخرى
GO - حقيبة الخيارات
NodeJs - حقيبة خيارات مع مصنع / مزود
جيتي - مصنع السياق

تبدو واجهة برمجة التطبيقات المقترحة في البداية جيدة ولديها بعض الأسئلة ،

  1. لماذا يجب أن يكون EncryptionPolicy و SslProtocols من الأنواع nullable ، ويمكن أن يتخذوا الخيار الافتراضي على أنه لا شيء صحيح؟
  2. يجب أن يقوم ApplicaitonProtocol بتنفيذ IEquatableواجهه المستخدم
  3. يجب أن تكون جميع أكياس الخيارات وبروتوكول التطبيق داخل SslStream أو أن تحتوي على بادئة Ssl في أسمائها ، نظرًا لأنها تنطبق فقط على TLS ، ولدينا أيضًا NegotiateStream في نفس مساحة الاسم.
  1. يجب أن تكون EncryptionPolicy و SslProtocols nullable حتى يمكن أن تطفو مع النظام الافتراضي. بمعنى آخر ، يمكنك معرفة ما إذا كان المستخدم قد حددها عن قصد أم لا.
  2. لا رأي. فحوصات المساواة المباشرة هي كل ما أتوقع أن أحتاجه.
  3. داخل؟ تقصد الطبقات المتداخلة؟ من فضلك لا. التجاور مع بادئة "Ssl" جيد.

نعم نفس الشيء 2 لا يهمني. 3 بادئة فئة Ssl جيدة

سؤال واحد ، هل المخزن المؤقت للبروتوكول هو مجرد محتوى بايتات معتمة لهذا البروتوكول أم البايتات المعتمة + بادئة الحجم؟

كما أضع بادئة Ssl على الفصول في الأعلى

Drawaes أتوقع منهم أن

رائع ، ليس لدي تفضيل طالما أنه واضح :)

أود أن أقترح إزالة sni apis من هنا ، لأننا لا نخطط لتنفيذها في الوقت الحالي. الاحتفاظ فقط بقيم alpn و allowrenegotiation. بالنسبة إلى sni ، يمكن أن يكون هناك اقتراح API منفصل. تم تحديث المشكلة الرئيسية بهذا الأمر ، كما تم تنفيذ iequatable على applnprotocol.

Tratcher أنا لا أفهم هذا ،

يجب أن تكون EncryptionPolicy و SslProtocols nullable حتى يمكن أن تطفو مع النظام الافتراضي. بمعنى آخر ، يمكنك معرفة ما إذا كان المستخدم قد حددها عن قصد أم لا.

إذا لم يقم المستخدم بتعيينها ، فيمكن أن تكون الإعدادات الافتراضية بلا ، وستكون القيمة الخالية بلا داخليًا. لا أفهم ميزة nullable التي يقدمها أكثر من لا شيء.

هناك بروتوكول SslProtocol جديد كل بضع سنوات. نريد تحديث الإعدادات الافتراضية ، لكننا نريد أيضًا تكريم الإعدادات الصريحة للمطورين. إذا كانت SslProtocol معلمة مطلوبة ، فمن الصعب معرفة ما إذا كان المطور قد قام بتعيينها لأن هذا ما أراده أو لمجرد أن هذا هو ما كان متاحًا في ذلك الوقت. إذا كان من الممكن تركه فارغًا ، فمن الواضح أن المطور اختار استخدام الإعدادات الافتراضية للنظام ، حتى لو تغيرت هذه الإعدادات الافتراضية.

حسنًا ، دعني أضعها على هذا النحو ، إذا لم أقم بتعيين خاصية EncryptionPolicy ، فستستخدم أي قيمة افتراضية تتطلبها الحالة الحالية لأمن SSL. إذا قام المستخدم صراحة بتغيير القيمة الافتراضية ، استخدم قيمة المستخدمين.

Tratcher SslProtocols.None تم اختياره بالفعل ليعني ذلك في النموذج الحالي. بينما null قيمة أفضل ("لا أعرف ، يجب أن تفعل الشيء الصحيح") بشكل عام ، لا أعتقد أننا نريد طريقتين مختلفتين للتعبير عنها. لذلك سأدافع عن إعادة استخدام القيمة المثقلة بـ None .

أنا موافق. إذا كان الحقل الأخضر فارغًا ، فسيكون أكثر وضوحًا من حيث النية ولكنه مكانه. لأكون صريحًا ، فإن الأمر يتراجع تمامًا عما تريده على أي حال. بدلاً من اختيار البروتوكولات التي تريدها ، يجب أن تختار البروتوكولات التي تقوم بتعطيلها.

لذلك قد تقول إنني لا أريد ssl3 أو tls1 ولكن أعطني أي شيء آخر يمكنك التعامل معه. الذي كان تصميمي منذ 12 شهرًا ، لكننا على الأرجح بعيدون جدًا عن هذا الحد لتغيير ذلك.

فيما يتعلق بـ SslServerAuthenticationOptions ، هل من الممكن أيضًا إضافة دعم لاختيار المصدرين الموثوق بهم هنا؟
انظر: https://github.com/dotnet/corefx/issues/23938

ayende يرجى تقديم مشكلة منفصلة لذلك.

لقد ربطه هنا بناءً على اقتراحي. قد يكون من المفيد فتح مشكلة SNI على الرغم من أننا أسقطناها من هنا.

يجب أن يكون كل من SNI و CAs المسموح به على دراية ببعضهما البعض.

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

يجب أن تكون هذه Ssl [خادم | عميل] 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 المكرر.

تم تغييره إلى هيكل ، ولم يكن هناك خلاف على استخدام البنية. هل نذهب مع ReadOnlyMemory؟ ما هي الميزة التي يقدمها ذلك على البايت []

نظرًا لأننا نحتاج إلى النسخ على أي حال ، أعتقد أن البايت [] جيد.

Re: ClientCertificateRequ {ired | ested}: إذا تم تعيينه على "صحيح" بدون رد اتصال مخصص ، فهو مطلوب. إذا تم التعيين على true باستخدام رد اتصال مخصص ، فهذا هو ما يقرره رد الاتصال المخصص.

ما يتلخص في (لمصطلحات RFC) هو "إرسال طلب شهادة [العميل]" https://tools.ietf.org/html/rfc5246#section -7.4.4

إذن ، ربما RequestClientCertificate ؟

كان ReadOnlyMemory أكثر أهمية قبل SslApplicationProtocol عندما كنا نضيف IList مباشرةللخيارات. الآن لا يهم.

RE: شهادة العميل ، هل نريد حقًا إعادة تسمية واجهات برمجة التطبيقات الحالية؟ سيؤدي ذلك إلى الكثير من الارتباك بقدر ما ينقذ.

إعادة التسمية ، لا أشعر بقوة في كلتا الحالتين. لدينا فرصة هنا لتوضيح هذه الأشياء التي ربما لن نحصل عليها مرة أخرى لفترة طويلة. لكن كما قلت ، لست متأكدًا من أنه يستحق ذلك.

RE: ذاكرة للقراءة فقط ... دعونا نضعها بطريقة أخرى ... لماذا لا تستخدمه؟ إنه لا يعطي مرونة بعيدة عن المصفوفة. لا يمكنك مقارنة المرجع على أي حال لأنك كما قلت تقوم بنسخها.

تم تغيير ذاكرة القراءة فقط إلى بايت []. أعتقد أنه تم حل جميع الأسئلة المعلقة. karelzKrzysztofCwalinabartonjsterrajobstweshaggard هل يمكن ان يرجى القيام المراجعة النهائية ووضع علامة على API كما وافقت. شكرا!

انتظر ، لماذا يتم التخلص من ذاكرة القراءة فقط؟ يقضي الناس وقتًا في إضافة مفهوم "الذاكرة" بشكل نشط عبر الإطار ، فالبايت [] قابل للتحويل إلى ذاكرة للقراءة فقط ولكن ليس العكس. اتفقنا أيضًا على نسخ البيانات ، وبالتالي لا يمكنك إجراء مساواة المرجع على أي حال والتي ستكون هي الجانب الصعودي الوحيد. أخيرًا ، إذا كانت الذاكرة بحاجة إلى التثبيت لنقلها إلى واجهة برمجة التطبيقات الأصلية ، فيمكنك تثبيتها مرة واحدة ، والاستيلاء على الدبوس مقابل تكلفة العد المرجعي.

Tratcher ذكرنا للتو أننا في البداية استخدمنا readonlymemory لجعل الحقول http1.1 و http2 الثابتة للقراءة فقط ثابتة. التراجع عنها.

تحديث: يجب أن نجعل إعداد checkCertificateRevocation في حقيبة الخيارات ليكون تعدادًا. لأنها ليست قابلة للتوسيع للتصميم المستقبلي. لنفترض أننا قررنا إضافة تعداد إبطال شهادة جديد ، والآن يمكن للمستخدم تعيين قيم متضاربة من خلال هذه الإعدادات المختلفة وسيتعين علينا حذف قيمة bool. لذلك من المنطقي إضافة التعداد الآن على حقيبة الخيارات.

غير هذا،

ج #
public bool CheckCertificateRevocation {get؛ يضع؛ }

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"

يبدو معقولا. هل يستحق التحقق من عدم وجود أحرف multibyte utf8 في السلسلة ورميها إذا كانت موجودة؟ لا يزال بإمكانك تعيين هؤلاء عبر readonlybytes ويمكن أن يوقف مشكلات لصق نسخ الأحرف المخفية (مثلما يحدث مع بصمات الأصابع المنسوخة من MMC التي أهدرت ساعات من حياتي فيها)

Drawaes أي أمثلة؟

على سبيل المثال ، إذا قمت بنسخ بصمة الإبهام ولصقها من المكوّن الإضافي للشهادة لـ mmc ، فستنتهي بحرف Unicode مخفي. إذا حاولت بعد ذلك استخدام هذا لمطابقة بصمة الإبهام في مخزن الشهادات في الكود ، فلن تتمكن من العثور عليه. بعد ذلك تقضي ساعتين في فحص كل شيء فقط لتجد أنه في مقابل السلسلة ، يقفز رقم العمود بين نهاية بصمة الإبهام و ".

إنه ليس حرجًا ولكنه يمثل نقطة نجاح ، فمن غير المرجح أن ترغب في تحويل حرف Unicode إلى utf8 مع رمز نقطة. إذا قمت بذلك ، فستعرف ذلك ويمكنك استخدام بايت للقراءة فقط.

على أي حال ، ليس هناك صفقة فاشلة فقط شيء يجعلك عندما تضغط عليه يجعلك لا تنساه أبدًا.

قصدته مثال على تسلسل شار / بايت. هل كانت المسافة (0x20) هي الحرف الخاطئ؟

إنها في البداية آسف .. إنها 0x200e وهي علامة من اليسار إلى اليمين. هناك مسار من الأحلام المحطمة في تدفق التكديس ؛)

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

لذا اقتراحي هو هذا. إذا أرسل أي شخص سلسلة ينتج عنها مجموعة البت الأكثر أهمية في أي من البايتات ، فقم بإزالتها كاستثناء. سيوقف مشاكل وقت التشغيل الفردية المطابقة مبكرًا وهناك طريقة للتغلب عليها إذا كنت تقصد ذلك.

ويرجع ذلك جزئيًا أيضًا إلى عدم وجود طريقة للقول "سوف نفسر هذا على أنه utf8" تقريبًا كما نحتاج إلى سلسلة utf8 ؛)

آه ، فهمت. هذه مشكلة في محرر الكود أكثر من كونها مشكلة في واجهة برمجة التطبيقات. بناءً على ترميز ملفك ، قد يتم إسقاط تلك البايت الإضافية. ثانيًا ، تخمين القيم هو أبعد من اختصاص واجهة برمجة التطبيقات هذه.

حسنًا ، لقد فكرت في هذا أكثر ، يجب التخلص من الحمل الزائد تمامًا. هناك ثوابت للحالة العامة ، لأي شيء آخر هو "متقدم" ويجب على المستخدم التحكم في السلسلة التي تم إدخالها.

يمكنهم التحويل إلى UTF8 إذا أرادوا ، ولكن لا يوجد نوع سلسلة utf8 ، لذلك ليس من الواضح ما سيفعله إرسال سلسلة في. لا يوجد شيء في المواصفات حول UTF8 ولكن مجرد بايتات معتمة ، يساعد التحميل الزائد قليلاً جدًا ويضيف فخًا محتملاً.

قد يكون الملاءمة الشائعة هو مفتاح / قيمة Redis ثنائية لها تحويل ضمني إلى سلسلة (بدون توسيع أو تضييق)

لذا فإن استخدام قيمة ثنائية من Redis سيتم تفسيره على أنه Utf8 وإما الحصول على كل بايت آخر يتم تناوله أو إنتاج تفسير Utf8 غير صالح.

تقدم المواصفات utf-8 كاقتراح لتحديد قيم المواصفات ، وقد استخدمت جميع المواصفات الحالية هذا الترميز.

تسلسل التعريف: المجموعة الدقيقة من قيم الثمانيات التي
يحدد البروتوكول. قد يكون هذا هو ترميز UTF-8
[RFC3629] لاسم البروتوكول.

الحق وجميع العناصر الحالية ذات الصلة لها ثابت. هذا هو أيضًا اعتبارات IANA لتخصيصها. المواصفات نفسها

تتم تسمية البروتوكولات بواسطة سلاسل بايت غير شفافة وغير شفافة ومسجلة لدى IANA ، كما هو موضح بمزيد من التفصيل
في القسم 6 ("اعتبارات IANA") من هذا المستند.

ويمكن أن يكون ضعيفًا مثل ما تحصل عليه مواصفات IETF ، فهو ليس بالأحرف الصغيرة "must": P

أعتقد أن السؤال الحقيقي هو. ما هي حجة الاستخدام للتمرير في سلسلة مُيسَّرة تستدعي المشكلات المحتملة؟

تقدم المواصفات utf-8 كاقتراح لتحديد قيم المواصفات ، وقد استخدمت جميع المواصفات الحالية هذا الترميز.

يختلف ذلك عن التحويل التلقائي للإدخال إلى الوظيفة من تمثيل 2 بايت لكل حرف ( string ) إلى تمثيل بايت واحد لكل حرف ( utf8 ) بحيث يمكن أن يتطابق مع التمثيل عبر السلك.

عندما يكون على سبيل المثال ، إذا جاء من مفتاح redis ، فمن المحتمل أن يكون في التمثيل على السلك ، لذلك ستسقط كل بايت آخر في التحويل التلقائي من string إلى utf8 . أو قد يكون بتنسيق سلسلة.

المشكلة التي لدي هي التحويل التلقائي من سلسلة 16 بايت إلى تنسيق ثنائي غير شفاف 8 بايت

^ ^ ^ أن. القلق الذي يساورني هو أنه لا يوجد شيء يقول "سيتم افتراض أنه UTF8" في تعريف API بخلاف التعليق. هل يمكن أن يكون لديك على سبيل المثال
(تجاهل الشكل والتسمية)

CreateUTF8ApplicationLayerProtocolName(string inputString);

حيث كان من الواضح ما الذي سيحدث ، ولكن وجود مُنشئ تلقائي مُحمّل بشكل زائد يمثل مشكلة.

بعض الأمثلة الخاصة بك هي نسخ لصق والبعض الآخر يتعامل مع الإدخال على أنه ديناميكي. يبدو أن لصق النسخ أكثر احتمالًا ، فأنت لا تضيف بروتوكولات جديدة كثيرًا ، وبالتأكيد ليس سريعًا من redis.

السيناريو الرئيسي الذي أرى استخدام مُنشئ السلسلة له هو عند نسخ لصق قيم جديدة من قائمة IANA ، والتي تشجع صراحةً ترميزات utf-8.

لقد تم إخباري بأن العديد من الأنظمة الأساسية المماثلة تعرض فقط واجهة برمجة تطبيقات سلسلة utf-8 ، بدون بايت [] API على الإطلاق. سأكون فضوليًا لرؤية مراجع فعلية بالرغم من ذلك.

من المؤكد أن go يأخذ "سلسلة" ولكن السلسلة في Go عبارة عن بايتات معتمة على أي حال وليست utf16 مثل سلسلة ac #. المشكلة ليست UTF8. إذا كان هناك سلسلة utf8 ، فلا مشكلة.

من GO

من المهم أن تذكر مقدمًا أن السلسلة تحتوي على وحدات بايت عشوائية. لا يلزم الاحتفاظ بنص Unicode أو نص UTF-8 أو أي تنسيق آخر محدد مسبقًا. بقدر ما يتعلق الأمر بمحتوى السلسلة ، فهي تعادل تمامًا شريحة من البايت.

الاستخدام الحالي دون زيادة الحمل لسلسلة سيكون هذا؟

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

Drawaes هل يمكنك رجاءً إعادة التغييرات التي

cc karelz

لقد قمت فقط بإزالة الحمل الزائد للسلسلة الذي لم تتم مناقشته في اجتماع مراجعة واجهة برمجة التطبيقات ، وتمت إضافته منذ 4 أيام فقط ، ما لم يكن هناك اجتماع لم أكن أعرفه؟

تم التحديث باستخدام SslApplicationProtocol العام (بروتوكول سلسلة) {} // يفترض utf-8 وسلسلة تجاوز عامة ToString () {} // لمصحح الأخطاء. utf-8 أو تمثيل سلسلة للبايت الخام. على سبيل المثال ، "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x3

كان هذا التعليق
التعليق حيث تمت إضافته

إذا كنت مخطئًا ، فيمكن إضافته مرة أخرى ، ولكن تمت إضافته بقدر ما أستطيع أن أقول قبل 4 أيام وناقش اجتماع المراجعة المنشئ بذاكرة للقراءة فقط و consts للأنواع المعروفة جيدًا؟

Drawaes نعم ، كان هناك اجتماع آخر لمتابعة الأسئلة المفتوحة من المراجعة الأولية. وجاء في الاجتماع 22 سبتمبر 2017 نتيجة لذلك.

أبحث عنها بطريقة أخرى

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> . لأنك لا تستطيع فعل ذلك حاليًا ☹️

Protocol هو ReadOnlyMemory<byte> ؛ ولا يوجد سوى دكتور byte[] .

لذا لا يعمل المُنشئ <-> تناظر الكائن لـ SslApplicationProtocol حاليًا. لا يمكنك أخذ خصائص المخرجات لـ SslApplicationProtocol وإنشاء أخرى مساوية.

  • قد يكون الإخراج ToString() غير صالح عند وضعه في string .
  • Protocol الذي لن يقوم بالترجمة ليس لديه. لذلك ستحتاج إلى تخصيص مصفوفة بايت جديدة ثم التكرار على Protocol إعداد كل بايت على حدة ، ثم إعطاء byte[] المخصص حديثًا إلى.

أوافق على اعتبار أن واجهة برمجة التطبيقات للبروتوكول غير متماثل وهو ما يمثل مشكلة لأي واجهة برمجة تطبيقات.

@ Priya91 إحدى

النقطة أعلاه لا تزال قائمة ، حيث يبدو أن أخذ بايت [] في الذاكرة وظهورها يبدو غير متماثل. وكذلك الهواجس على الوتر.

لا يمكنك أخذ خصائص إخراج SslApplicationProtocol وإنشاء أخرى مساوية.

هل ما يلي لا يعمل؟
ج #
var myProtocol = جديد SslApplicationProtocol (binaryProtocol) ؛
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 ولكن رمز المثال الخاص به ، يمكنك استخدام مصفوفة equiv أو أي شيء آخر)

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 غير قابل للتغيير. الأنواع غير القابلة للتغيير أفضل لتمثيل الثوابت المنطقية.

لا يمكن للأنواع غير القابلة للتغيير تخزين البيانات غير القابلة للتغيير والمشاركة. مصفوفة البايت التي تم تمريرها إلى المُنشئ قابلة للتغيير والمشاركة. لجعله غير مشترك ، يحتاج المنشئ إلى عمل نسخة خاصة.

في مثال الكود الخاص بك ، يمكن أن تنتج الحلقة بيانات مختلفة في مدى النتيجة في كل مرة يتم فيها تشغيل الحلقة ، حتى إذا لم يتم تغيير مجموعة عناصر alpn على الإطلاق. وذلك لأنه يمكن تغيير العناصر الموجودة في المجموعة من خلال مجموعة البايت "الباب الخلفي". ونحن لا نريد ذلك ، أليس كذلك؟

ونحن لا نريد ذلك ، أليس كذلك؟

قد يكون ممتعًا :) على الرغم من أنه من المحتمل أن يكون سببًا للبق.

بغض النظر ، يجب أن يكون دكتور

public SslApplicationProtocol(ReadOnlyMemory<byte> protocol) { }

او ربما

public SslApplicationProtocol(ReadOnlySpan<byte> protocol) { }

لأنها تنقل بشكل صحيح نية استخدام المعلمة protocol ؛ وكذلك قبول مصادر أخرى غير مجرد bcl byte[]

KrzysztofCwalina متأكد من أننا لا نفعل ذلك مع ذلك قلت في وقت مبكر حقًا أن قابلية التغيير كانت مشكلة لجميع الخيارات. نحن هنا نقول إن ALPN مميز مقارنة بالقول ، قائمة ALPN ، الشهادة أو أي شيء آخر يمكن تغييره في حقيبة الخيارات؟ هذا هو الاقتباس من stephentoub عندما قلت إننا يجب أن

لماذا تعتبر حقيبة الخيارات قابلة للتغيير؟ لا يختلف الأمر عن أي شيء آخر: أنت تعطي هذا لواجهة برمجة التطبيقات ، وتتوقع واجهة برمجة التطبيقات أن تكون في حالة متسقة طوال مدة العملية ... إذا قمت بتغييرها أثناء الرحلة ، فهذا خطأ ، لا تختلف عن أي حالة أخرى حيث تقوم بتحوير شيء ما بينما تعتمد الطريقة التي يتم استدعاؤها عليها (على سبيل المثال ، تم تمرير المخازن المؤقتة للقراءة / الكتابة). إذا كنت ترغب في معاملته على أنه غير قابل للتغيير وعدم تغييره أبدًا بعد إنشائه ، يمكنك ذلك. إذا كنت ترغب في معاملته على أنه قابل للتغيير بحيث يمكنك تعديله بين الاستخدامات ، فيمكنك ذلك.

Drawaes يمكن تغيير خاصية حقيبة الخيارات IList<SslApplicationProtocol> ، لكن قيمة بروتوكول البنية SslApplicaitonProtocol غير قابلة للتغيير. في الوقت الحالي ، نفترض أن استخدام SslApplicationProtocol يقع فقط في حقيبة الخيارات ، ولكن يمكن استخدام هذا النوع خارج حقيبة الخيارات في سيناريوهات أخرى في كود المستخدم.

@ Priya91 أنا أفهم التصميم بنسبة 100٪ ، ولكن يبدو من التعسفي أن ALPN محمي بالثبات باستخدام النسخ ، ولكن ليس القائمة ، أو أي شيء آخر في SslStream. إذن لدينا ثبات تعسفي وعدم تناسق ، ولدينا واجهة برمجة تطبيقات غير متناظرة تكشف ذاكرة القراءة فقط على خاصية البروتوكول ولكن لا توجد طريقة لاستخدام هذا لإنشاء واحدة جديدة؟

على أي حال ، إذا كان هذا هو التصميم الذي تريده جميعًا ، فليكن.

Drawaes ، لن

benaadams ، أوافق على أن ReadOnlySpanسيكون أكثر مرونة. لست متأكدًا من أننا بحاجة إلى هذه المرونة. ستجعل واجهات برمجة التطبيقات أكثر صعوبة في الاستخدام (Span أقل شهرة من البايت [] ، Encoding.UTF8.GetBytes تعيد المصفوفة ، إلخ).

بعد قولي هذا ، أنا لا أضغط من أجل أي تصميم هنا (في الواقع أعتقد أنه نوع من تقسيم الشعر ويجب علينا فقط السماح للمطور المسؤول عن هذه الميزة ، مثل @ Priya91 ، بإجراء المكالمة). لقد كنت أعلق للتو على أنه يجب على المُنشئ أن ينسخ بغض النظر عما نمرره ، وبالتالي فإن الحجة القائلة بأنه يجب علينا استخدام ReadOnlyMemory لأنها للقراءة فقط لا تبدو صالحة.

ولدينا واجهة برمجة تطبيقات غير متماثلة تعرض ReadOnlyMemory على خاصية البروتوكول ولكن لا توجد طريقة لاستخدام هذا لإنشاء واحدة جديدة؟

لا أفهم هذا البيان ، يمكن استخدام readonlymemory لإنشاء كائن SslApplicationProtocol جديد ، SslApplicationProtocol (readonlymemory.toarray ()).

يمكن استخدام readonlymemory لإنشاء كائن SslApplicationProtocol جديد ، SslApplicationProtocol جديد (readonlymemory.toarray ()).

يمكن. ومع ذلك ، فأنت تأخذ عنصرًا غير قابل للتغيير ReadOnlyMemory<byte> لتخصيصه لتحويله إلى عنصر قابل للتغيير byte[] ثم تمرير العنصر القابل للتغيير إلى المنشئ ، حتى يتمكن من نسخه مرة أخرى وتحويله إلى ReadOnlyMemory<byte> ثابت

ما أقترحه هو أخذ المعلمة كـ ReadOnlyMemory<byte> ينقل الغرض من الوظيفة بشكل صحيح بطريقة جديدة لم نتمكن من القيام بها سابقًا مع المصفوفات. تقول أن الوظيفة التي تأخذ هذا لا تنوي تعديلها. في حين أن الوظيفة التي تأخذ مصفوفة لا تشير إلى مثل هذا الضمان ؛ يمكن تغيير المعلمة من أجل المستدعي بعد المكالمة.

أنا لا أقترح إزالة الممثل byte[] . لأن ذلك من المحتمل أن يربك الناس ؛ فقط لإضافة .ctor إضافي يمكنه أن يأخذ الخاصية التي يعرضها النوع مباشرة وينشئ واحدة أخرى ، بدلاً من الاضطرار إلى تحويلها إلى شيء آخر ؛ أو ترجمها أولاً.

كما انك ربما لن فعلا القيام
new SslApplicationProtocol(proto.Protocol));
أو
new SslApplicationProtocol(proto.Protocol.ToArray()));
كما لو كان لديك البروتوكول ، فلماذا تنشئ بروتوكولًا متطابقًا ؛ لذلك في الغالب أن واجهة برمجة التطبيقات ليست متسقة مع نفسها مما يزعجني. ومع ذلك فهي نقطة ثانوية.

ومع ذلك ، أعتقد أن تفسير مُدخل string كـ UFT8 يمثل مشكلة ، وبالمثل بدون مُدخِل string يمكنك القيام به بسهولة

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 - أعتقد فقط أن واجهة برمجة التطبيقات التي تأخذ سلسلة وتفترض تلقائيًا أن ترميزًا مختلفًا للمخرجات يتطلب مشكلة - هو وضع افتراض تحويل في واجهة برمجة التطبيقات.

المشكلة مع .NET apis كما هو موضح بواسطة .NET Standard 2.0 (وهو أمر مذهل) - هو أنه لا يمكن تغييرها

يمكن دائمًا إضافة الحمل الزائد للسلسلة لاحقًا إذا كان هناك طلب ؛ ولكن بمجرد وصوله إلى هناك ، لا يمكن أبدًا نقله أو تغيير سلوكه.

باستخدام ReadOnlyMemory .ctor + https://github.com/dotnet/corefx/issues/24293 ، ستتمكن من تجنب حلقة foreach char مع عدم التخصيص

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

(تجاهل النسخة الدفاعية في .ctor)

benaadams ما هي حالات الاستخدام لشخص ما للقيام بذلك؟

أنا لا أقترح إزالة البايت [] .ctor؛ لأن ذلك من المحتمل أن يربك الناس ؛ فقط لإضافة .ctor إضافي يمكنه أن يأخذ الخاصية التي يعرضها النوع مباشرة وينشئ واحدة أخرى ، بدلاً من الاضطرار إلى تحويلها إلى شيء آخر ؛ أو ترجمها أولاً.

سواء كانت بايت [] أو ذاكرة القراءة فقطيجب نسخها في المنشئ ، وذاكرة القراءة فقط هي مجرد بايت [] لا يكشف عن وسائل تعديل البايت الأساسي []. لذلك لا أرى أي قيمة إضافية في ذاكرة القراءة فقط في المنشئ. أنا أيضًا لا أفهم نقطة واجهة برمجة التطبيقات غير المتكافئة ، ما السيناريوهات التي تتأثر بذلك؟

فيما يتعلق بالحمل الزائد لمنشئ السلسلة ، يتم توفيره لسهولة الاستخدام والراحة باستخدام قيم alpn الجديدة التي ليست ثوابت حاليًا ، أو للقيم الجديدة المتفق عليها مسبقًا والتي قد يستخدمها العملاء والخوادم. ليس هدفنا توفير المساواة لكائن sslaplicationprotocol الذي تم إنشاؤه باستخدام سلسلة tostring () أخرى لمطابقتها ، فهذا السيناريو ليس حالة استخدام لواجهة برمجة التطبيقات هذه. إنه مشابه لكيفية عدم تطابق apis للتشفير مع getstring و getbytes لقيم التشفير غير المتوافقة.

يجب أن نلتقي في النقاش حول هذه المسألة وأن نتوصل إلى إجماع ، فالأمر يتأخر لفترة طويلة :(.

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

ماذا يعني "UTF-8"؟
لدينا ترميز UTF-16 للسلاسل ، وهذا ما سنحصل عليه. سيتعين علينا تحويلها داخليًا إلى سلسلة UTF-8 وتخزينها في "بايت []" الذي نمرره.
سوف يفسر ToString "بايت []" الأساسي على أنه سلسلة UTF-8. إذا فشل ذلك ، فسيكون تمثيل سلسلة للبايتات الأولية ، على سبيل المثال "0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31".

هذا هو أحدث اقتراح ALPN من المناقشة حول هذا الموضوع. نشر ذلك هنا لفريق مراجعة واجهة برمجة التطبيقات.

ج #
مساحة الاسم System.Net.Security
{
فئة جزئية عامة SslStream
{
المهمة العامة AuthenticateAsServerAsync (خيارات SslServerAuthenticationOptions ، إلغاء CancellationToken) {}
المهمة العامة AuthenticateAsClientAsync (خيارات SslClientAuthenticationOptions ، إلغاء CancellationToken) {}

public SslApplicationProtocol NegotiatedApplicationProtocol {get؛ }
}

فئة عامة SslServerAuthenticationOptions
{
منطقية عامة AllowRenegotiation {get؛ يضع؛ }
شهادة خادم X509Certificate العامة {get؛ يضع؛ }
ClientCertificateRequired عام منطقي {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
X509RevocationMode العام CheckCertificateRevocation {get؛ يضع؛ }
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة RemoteCertificateValidationCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionPolicy {get؛ يضع؛ }
}

فئة عامة SslClientAuthenticationOptions
{
منطقية عامة AllowRenegotiation {get؛ يضع؛ }
السلسلة العامة TargetHost {get؛ يضع؛ }
شهادات العميل X509CertificateCollection العامة {get؛ يضع؛ }
LocalCertificateSelectionCallback العامة LocalCertificateSelectionCallback {get؛ يضع؛ }
العامة SslProtocols EnabledSslProtocols {get؛ يضع؛ }
X509RevocationMode العام CheckCertificateRevocation {get؛ يضع؛ }
IList العامةApplicationProtocols {get؛ يضع؛ }
RemoteCertificateValidationCallback العامة RemoteCertificateValidationCallback {get؛ يضع؛ }
EncryptionPolicy العامة EncryptionPolicy {get؛ يضع؛ }
}

بنية عامة 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 وتخزينها في "بايت []" الذي نمرره.

ولا يُنقل في api أن هذا مخبوز في التحويل التلقائي على سبيل المثال

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

Atm ، في أفضل الأحوال ، تعليق جانبي في هذا العدد.

المواصفات لا يقول أنه يجب أن يكون UTF-8 سلسلة التفسير - تقول انها قد تكون

تسلسل التعريف: المجموعة الدقيقة من قيم الثماني التي تحدد البروتوكول. قد يكون هذا هو ترميز UTF-8 [RFC3629] لاسم البروتوكول.

ما مدى شيوع أن يصنع المستخدمون بروتوكولاتهم الخاصة ، لذا فهم بحاجة إلى مُنشئ ملائم يقوم بإجراء تحويل برأي من string إلى UTF-8 بايت؟

ما هي المجموعة الفرعية من هؤلاء المستخدمين التي لن تفهم واجهات برمجة التطبيقات للتشفير ، لذا فهي بحاجة إلى المساعدة للقيام بها

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

بالتساوي أي شخص ضال في وقت مبكر non-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 / قضايا / 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.

هذا الحمل الزائد للسلسلة ليس جزءًا مهمًا من واجهة برمجة التطبيقات ولا يستحق مقدار الوقت الذي تم قضاؤه بالفعل عليه. إنه يوفر تحسنًا طفيفًا في قابلية الاستخدام مقارنة بالحمل الزائد الآخر ، هذا كل شيء. احتفظ به أو احذفه ، ولكن في كلتا الحالتين ، حان الوقت للمضي قدمًا.

سواء كانت بايت [] أو ذاكرة القراءة فقط ، يجب نسخها في المُنشئ ، والذاكرة للقراءة فقط هي مجرد بايت [] لا يعرض وسيلة لتعديل البايت الأساسي []. لذلك لا أرى أي قيمة إضافية في ذاكرة القراءة فقط في المنشئ.

إنها نقطة جيدة. كما تقول ، يجب نسخها على أي حال ، لذا فإن ReadOnlySpan<byte> ستكون أكبر من ReadOnlyMemory<byte> حيث يمكن أن تكون أيضًا ذاكرة مكدس - ويمكنك دائمًا الحصول على واحدة من ReadOnlyMemory<byte>

حسنًا ، الأمر متروك للمطور في تنفيذه و MS الذي يجب أن يدعمه في النهاية. في الغالب ، كنت أرغب في الحصول على حقيبة خيارات وكنت أعمل على إطلاق النار على عامل بناء ، لذلك أنا سعيد جدًا لأنني حصلت على ما أريد. سأدلي بتعليق أخير على المُنشئ ، ثم يمكن لشخص ما في MS اتخاذ قرار ، وهو مرحب به لتحديث المشكلة الرئيسية.

لذا فإن كلمتي الأخيرة .. كما تتكرر في كثير من الأحيان تصميم API هو إلى الأبد. لماذا تأخذ سلسلة و utf8 ذلك؟ قد تكون سلاسل UTF8 قريبة على أي حال. ما هو احتمال أن يحتاج شخص ما بالفعل إلى زيادة الحمل الآن؟ يمكن إضافته لاحقًا إذا لزم الأمر ، يمكنك دائمًا إضافة ما لا يمكنك تناوله.

الذاكرة الثانية للقراءة فقط .... المصفوفات الملقاة ضمنيًا في الذاكرة على أي حال ، أليس كذلك؟ لذا اجعلها ذاكرة للقراءة فقط وأضف مصفوفة إذا كنت في حاجة إليها. إذا كان القلق هو الألفة جيدًا إذا كنت تقوم بإنشاء بروتوكول ALPN الخاص بك ، آمل أن يكون لديك مستوى معين من عمق المعرفة على أي حال.

على أي حال اسمحوا لي أن أعرف ما تريد أعلاه .....

Incase It's not clear مُنشئ ذاكرة للقراءة فقط هو توصيتي.

benaadams لا تقول المواصفات أنه يجب أن يكون تفسير سلسلة UTF-8 - تقول إنه يمكن أن يكون كذلك

صيح. المواصفات غير واضحة عن قصد لأسباب غير معروفة ، نعتقد أن UTF-8 مُلمح بشدة. هو تفسيرنا للمواصفات.
راجع للشغل: لقد ناقشنا أيضًا ASCII. إذا احتاج أي شخص إلى ترميز UTF-16 أو ترميزات أخرى ، فيمكنه استخدام التحميل الزائد للبايت مباشرة.
من الناحية العملية ، لا نعتقد أن أي شخص سيستخدم أي شيء مختلف عن سلاسل ASCII / UTF-8 المحددة مسبقًا من IANA.

ما مدى شيوع أن يقوم المستخدمون بإنشاء البروتوكولات الخاصة بهم

يمكن للمستخدمين استخدامه لجميع بروتوكولات IANA التي لا نقوم بتضمينها كثوابت ملائمة. وهو أيضًا نتيجة نقاش داخلي طويل حول استخدام السلسلة فقط باعتبارها البيانات الأساسية بدلاً من البايت [] (وهو دليل أكثر على الميزة). تعد إضافة الحمل الزائد للسلسلة ملائمة للاستخدام.

هذا الحمل الزائد للسلسلة ليس جزءًا مهمًا من واجهة برمجة التطبيقات ولا يستحق مقدار الوقت الذي تم قضاؤه بالفعل عليه. ... في كلتا الحالتين حان الوقت للمضي قدمًا.

متفق عليه 💯!

قد تكون سلاسل Drawaes UTF8 قريبة على أي حال.

لا أعتقد أنه من الجيد جعل واجهات برمجة التطبيقات تعتمد على بعضها البعض. لا نعرف ما إذا / متى تدخل سلاسل UTF-8 إلى النظام الأساسي.
أرى سلسلة API الملائمة كطريقة لتمرير سلسلة ASCII (أو UTF-8) ، وفقًا لإرشادات المواصفات.

راجع للشغل: سأقوم بتحديث المنشور الأعلى بنسخ ورابط لأحدث إصدار من API من @ Priya91.

المواصفات غير واضحة عن قصد لأسباب غير معروفة ، نعتقد أن UTF-8 مُلمح بشدة. هو تفسيرنا للمواصفات.

إنه ليس كذلك ، فهو لا يستخدم مصطلح RFC2119 (والذي تشير إليه المواصفات أيضًا)

  1. لغة المتطلبات

    الكلمات الرئيسية "MUST" ، "MUST NOT" ، "REQUIRED" ، "SHALL" ، "SHALL NOT" ،
    "SHOULD" و "SHOULD NOT" و "RECOMMENDED" و "MAY" و "OPTIONAL" في هذا
    يجب تفسير المستند كما هو موضح في [RFC2119].

يقول "يمكن" التي ليست واحدة من هذه المصطلحات ؛ بدلاً من "موصى به" أو "MAY" - أكثر من استخدام كمثال - لتوضيح أنه على الرغم من كونه حقلًا ثنائيًا ، فمن المحتمل أن يكون النص خيارًا معقولاً.

إذا كنت تريد خبز هذا التحويل في ؛ أود أن أقترح أن أكون صريحًا في API بطريقة ما على سبيل المثال

new SslApplicationProtocol(string uf8Protocol)

أعتقد أن الأشخاص الذين يحددون البروتوكولات الخاصة بهم يمكنهم التعامل مع الخطوة الإضافية المرهقة المتمثلة في استخدام أداة تشفير صريحة للسلسلة بدلاً من وجود حمل مفرط لسلسلة مقنع غامض لتسهيل الأمر.

على أي حال؛ لقد رفعت اعتراضاتي وقدمت اقتراحات - لن أعترض على القرار النهائي.

مراجعة API: تمت الموافقة عليها كأحدث اقتراح. ناقشنا البدائل المقترحة أعلاه ، لكننا لم نشعر بأننا يجب أن نأخذها.

حسنًا ، رائع ، أتطلع إلى أن تكون في corefx

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات