private MsQuicApi(NativeApi *vtable) { uint status; SetParamDelegate = Marshal.GetDelegateForFunctionPointer <SetParamDelegate>( vtable->SetParam); GetParamDelegate = Marshal.GetDelegateForFunctionPointer <GetParamDelegate>( vtable->GetParam); SetCallbackHandlerDelegate = Marshal.GetDelegateForFunctionPointer <SetCallbackHandlerDelegate>( vtable->SetCallbackHandler); RegistrationOpenDelegate = Marshal.GetDelegateForFunctionPointer <RegistrationOpenDelegate>( vtable->RegistrationOpen); RegistrationCloseDelegate = Marshal.GetDelegateForFunctionPointer <RegistrationCloseDelegate>( vtable->RegistrationClose); ConfigurationOpenDelegate = Marshal.GetDelegateForFunctionPointer <ConfigurationOpenDelegate>( vtable->ConfigurationOpen); ConfigurationCloseDelegate = Marshal.GetDelegateForFunctionPointer <ConfigurationCloseDelegate>( vtable->ConfigurationClose); ConfigurationLoadCredentialDelegate = Marshal.GetDelegateForFunctionPointer <ConfigurationLoadCredentialDelegate>( vtable->ConfigurationLoadCredential); ListenerOpenDelegate = Marshal.GetDelegateForFunctionPointer <ListenerOpenDelegate>( vtable->ListenerOpen); ListenerCloseDelegate = Marshal.GetDelegateForFunctionPointer <ListenerCloseDelegate>( vtable->ListenerClose); ListenerStartDelegate = Marshal.GetDelegateForFunctionPointer <ListenerStartDelegate>( vtable->ListenerStart); ListenerStopDelegate = Marshal.GetDelegateForFunctionPointer <ListenerStopDelegate>( vtable->ListenerStop); ConnectionOpenDelegate = Marshal.GetDelegateForFunctionPointer <ConnectionOpenDelegate>( vtable->ConnectionOpen); ConnectionCloseDelegate = Marshal.GetDelegateForFunctionPointer <ConnectionCloseDelegate>( vtable->ConnectionClose); ConnectionSetConfigurationDelegate = Marshal.GetDelegateForFunctionPointer <ConnectionSetConfigurationDelegate>( vtable->ConnectionSetConfiguration); ConnectionShutdownDelegate = Marshal.GetDelegateForFunctionPointer <ConnectionShutdownDelegate>( vtable->ConnectionShutdown); ConnectionStartDelegate = Marshal.GetDelegateForFunctionPointer <ConnectionStartDelegate>( vtable->ConnectionStart); StreamOpenDelegate = Marshal.GetDelegateForFunctionPointer <StreamOpenDelegate>( vtable->StreamOpen); StreamCloseDelegate = Marshal.GetDelegateForFunctionPointer <StreamCloseDelegate>( vtable->StreamClose); StreamStartDelegate = Marshal.GetDelegateForFunctionPointer <StreamStartDelegate>( vtable->StreamStart); StreamShutdownDelegate = Marshal.GetDelegateForFunctionPointer <StreamShutdownDelegate>( vtable->StreamShutdown); StreamSendDelegate = Marshal.GetDelegateForFunctionPointer <StreamSendDelegate>( vtable->StreamSend); StreamReceiveCompleteDelegate = Marshal.GetDelegateForFunctionPointer <StreamReceiveCompleteDelegate>( vtable->StreamReceiveComplete); StreamReceiveSetEnabledDelegate = Marshal.GetDelegateForFunctionPointer <StreamReceiveSetEnabledDelegate>( vtable->StreamReceiveSetEnabled); var cfg = new RegistrationConfig { AppName = ".NET", ExecutionProfile = QUIC_EXECUTION_PROFILE.QUIC_EXECUTION_PROFILE_LOW_LATENCY }; status = RegistrationOpenDelegate(ref cfg, out SafeMsQuicRegistrationHandle handle); QuicExceptionHelpers.ThrowIfFailed(status, "RegistrationOpen failed."); Registration = handle; }
// TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer. // Consider moving bigger logic like this outside of constructor call chains. private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, List <SslApplicationProtocol>?alpnProtocols) { // TODO: some of these checks should be done by the QuicOptions type. if (alpnProtocols == null || alpnProtocols.Count == 0) { throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } if ((flags & QUIC_CREDENTIAL_FLAGS.CLIENT) == 0) { if (certificate == null) { throw new Exception("Server must provide certificate"); } } else { flags |= QUIC_CREDENTIAL_FLAGS.INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAGS.NO_CERTIFICATE_VALIDATION; } if (!OperatingSystem.IsWindows()) { // Use certificate handles on Windows, fall-back to ASN1 otherwise. flags |= QUIC_CREDENTIAL_FLAGS.USE_PORTABLE_CERTIFICATES; } Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid); var settings = new QuicSettings { IsSetFlags = QuicSettingsIsSetFlags.PeerBidiStreamCount | QuicSettingsIsSetFlags.PeerUnidiStreamCount, PeerBidiStreamCount = (ushort)options.MaxBidirectionalStreams, PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams }; if (options.IdleTimeout != Timeout.InfiniteTimeSpan) { if (options.IdleTimeout <= TimeSpan.Zero) { throw new Exception("IdleTimeout must not be negative."); } ulong ms = (ulong)options.IdleTimeout.Ticks / TimeSpan.TicksPerMillisecond; if (ms > (1ul << 62) - 1) { throw new Exception("IdleTimeout is too large (max 2^62-1 milliseconds)"); } settings.IsSetFlags |= QuicSettingsIsSetFlags.IdleTimeoutMs; settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds; } uint status; SafeMsQuicConfigurationHandle?configurationHandle; MemoryHandle[]? handles = null; QuicBuffer[]? buffers = null; try { MsQuicAlpnHelper.Prepare(alpnProtocols, out handles, out buffers); status = MsQuicApi.Api.ConfigurationOpenDelegate(MsQuicApi.Api.Registration, (QuicBuffer *)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(QuicSettings), context: IntPtr.Zero, out configurationHandle); } finally { MsQuicAlpnHelper.Return(ref handles, ref buffers); } QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed."); try { CredentialConfig config = default; config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback. if (certificate != null) { if (OperatingSystem.IsWindows()) { config.Type = QUIC_CREDENTIAL_TYPE.CONTEXT; config.Certificate = certificate.Handle; status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } else { CredentialConfigCertificatePkcs12 pkcs12Config; byte[] asn1 = certificate.Export(X509ContentType.Pkcs12); fixed(void *ptr = asn1) { pkcs12Config.Asn1Blob = (IntPtr)ptr; pkcs12Config.Asn1BlobLength = (uint)asn1.Length; pkcs12Config.PrivateKeyPassword = IntPtr.Zero; config.Type = QUIC_CREDENTIAL_TYPE.PKCS12; config.Certificate = (IntPtr)(&pkcs12Config); status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } } } else { config.Type = QUIC_CREDENTIAL_TYPE.NONE; status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed."); } catch { configurationHandle.Dispose(); throw; } return(configurationHandle); }
public async ValueTask <MsQuicSecurityConfig?> CreateSecurityConfig(X509Certificate certificate, string?certFilePath, string?privateKeyFilePath) { MsQuicSecurityConfig?secConfig = null; var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); uint secConfigCreateStatus = MsQuicStatusCodes.InternalError; uint createConfigStatus; IntPtr unmanagedAddr = IntPtr.Zero; MsQuicNativeMethods.CertFileParams fileParams = default; try { if (certFilePath != null && privateKeyFilePath != null) { fileParams = new MsQuicNativeMethods.CertFileParams { PrivateKeyFilePath = Marshal.StringToCoTaskMemUTF8(privateKeyFilePath), CertificateFilePath = Marshal.StringToCoTaskMemUTF8(certFilePath) }; unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(fileParams)); Marshal.StructureToPtr(fileParams, unmanagedAddr, fDeleteOld: false); createConfigStatus = SecConfigCreateDelegate( _registrationContext, (uint)QUIC_SEC_CONFIG_FLAG.CERT_FILE, unmanagedAddr, null, IntPtr.Zero, SecCfgCreateCallbackHandler); } else if (certificate != null) { createConfigStatus = SecConfigCreateDelegate( _registrationContext, (uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT, certificate.Handle, null, IntPtr.Zero, SecCfgCreateCallbackHandler); } else { // If no certificate is provided, provide a null one. createConfigStatus = SecConfigCreateDelegate( _registrationContext, (uint)QUIC_SEC_CONFIG_FLAG.CERT_NULL, IntPtr.Zero, null, IntPtr.Zero, SecCfgCreateCallbackHandler); } QuicExceptionHelpers.ThrowIfFailed( createConfigStatus, "Could not create security configuration."); void SecCfgCreateCallbackHandler( IntPtr context, uint status, IntPtr securityConfig) { secConfig = new MsQuicSecurityConfig(this, securityConfig); secConfigCreateStatus = status; tcs.SetResult(); } await tcs.Task.ConfigureAwait(false); QuicExceptionHelpers.ThrowIfFailed( secConfigCreateStatus, "Could not create security configuration."); } finally { if (fileParams.CertificateFilePath != IntPtr.Zero) { Marshal.FreeCoTaskMem(fileParams.CertificateFilePath); } if (fileParams.PrivateKeyFilePath != IntPtr.Zero) { Marshal.FreeCoTaskMem(fileParams.PrivateKeyFilePath); } if (unmanagedAddr != IntPtr.Zero) { Marshal.FreeHGlobal(unmanagedAddr); } } return(secConfig); }
internal static unsafe void SetULongParam(MsQuicApi api, SafeHandle nativeObject, uint param, ulong value) { QuicExceptionHelpers.ThrowIfFailed( api.SetParamDelegate(nativeObject, param, sizeof(ulong), (byte *)&value), "Could not set ulong."); }
// TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer. // Consider moving bigger logic like this outside of constructor call chains. private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, List <SslApplicationProtocol>?alpnProtocols) { // TODO: some of these checks should be done by the QuicOptions type. if (alpnProtocols == null || alpnProtocols.Count == 0) { throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid); var settings = new QuicSettings { IsSetFlags = QuicSettingsIsSetFlags.PeerBidiStreamCount | QuicSettingsIsSetFlags.PeerUnidiStreamCount, PeerBidiStreamCount = (ushort)options.MaxBidirectionalStreams, PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams }; if (options.IdleTimeout != Timeout.InfiniteTimeSpan) { if (options.IdleTimeout <= TimeSpan.Zero) { throw new Exception("IdleTimeout must not be negative."); } ulong ms = (ulong)options.IdleTimeout.Ticks / TimeSpan.TicksPerMillisecond; if (ms > (1ul << 62) - 1) { throw new Exception("IdleTimeout is too large (max 2^62-1 milliseconds)"); } settings.IsSetFlags |= QuicSettingsIsSetFlags.IdleTimeoutMs; settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds; } uint status; SafeMsQuicConfigurationHandle?configurationHandle; MemoryHandle[]? handles = null; QuicBuffer[]? buffers = null; try { MsQuicAlpnHelper.Prepare(alpnProtocols, out handles, out buffers); status = MsQuicApi.Api.ConfigurationOpenDelegate(MsQuicApi.Api.Registration, (QuicBuffer *)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(QuicSettings), context: IntPtr.Zero, out configurationHandle); } finally { MsQuicAlpnHelper.Return(ref handles, ref buffers); } QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed."); try { // TODO: find out what to do for OpenSSL here -- passing handle won't work, because // MsQuic has a private copy of OpenSSL so the SSL_CTX will be incompatible. CredentialConfig config = default; config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback. if (certificate != null) { #if true // If using stub TLS. config.Type = QUIC_CREDENTIAL_TYPE.STUB_NULL; #else // TODO: doesn't work on non-Windows config.Type = QUIC_CREDENTIAL_TYPE.CONTEXT; config.Certificate = certificate.Handle; #endif } else { // TODO: not allowed for OpenSSL and server config.Type = QUIC_CREDENTIAL_TYPE.NONE; } status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed."); } catch { configurationHandle.Dispose(); throw; } return(configurationHandle); }
private unsafe MsQuicApi() { QuicExceptionHelpers.ThrowIfFailed( Interop.MsQuic.MsQuicOpen(version: 1, out MsQuicNativeMethods.NativeApi * registration), "Could not open MsQuic."); MsQuicNativeMethods.NativeApi nativeRegistration = *registration; RegistrationOpenDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.RegistrationOpenDelegate>( nativeRegistration.RegistrationOpen); RegistrationCloseDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.RegistrationCloseDelegate>( nativeRegistration.RegistrationClose); SecConfigCreateDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SecConfigCreateDelegate>( nativeRegistration.SecConfigCreate); SecConfigDeleteDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SecConfigDeleteDelegate>( nativeRegistration.SecConfigDelete); SessionOpenDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SessionOpenDelegate>( nativeRegistration.SessionOpen); SessionCloseDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SessionCloseDelegate>( nativeRegistration.SessionClose); SessionShutdownDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SessionShutdownDelegate>( nativeRegistration.SessionShutdown); ListenerOpenDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ListenerOpenDelegate>( nativeRegistration.ListenerOpen); ListenerCloseDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ListenerCloseDelegate>( nativeRegistration.ListenerClose); ListenerStartDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ListenerStartDelegate>( nativeRegistration.ListenerStart); ListenerStopDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ListenerStopDelegate>( nativeRegistration.ListenerStop); ConnectionOpenDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ConnectionOpenDelegate>( nativeRegistration.ConnectionOpen); ConnectionCloseDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ConnectionCloseDelegate>( nativeRegistration.ConnectionClose); ConnectionShutdownDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ConnectionShutdownDelegate>( nativeRegistration.ConnectionShutdown); ConnectionStartDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.ConnectionStartDelegate>( nativeRegistration.ConnectionStart); StreamOpenDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamOpenDelegate>( nativeRegistration.StreamOpen); StreamCloseDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamCloseDelegate>( nativeRegistration.StreamClose); StreamStartDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamStartDelegate>( nativeRegistration.StreamStart); StreamShutdownDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamShutdownDelegate>( nativeRegistration.StreamShutdown); StreamSendDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamSendDelegate>( nativeRegistration.StreamSend); StreamReceiveCompleteDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamReceiveCompleteDelegate>( nativeRegistration.StreamReceiveComplete); StreamReceiveSetEnabledDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.StreamReceiveSetEnabledDelegate>( nativeRegistration.StreamReceiveSetEnabled); SetContextDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SetContextDelegate>( nativeRegistration.SetContext); GetContextDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.GetContextDelegate>( nativeRegistration.GetContext); SetCallbackHandlerDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SetCallbackHandlerDelegate>( nativeRegistration.SetCallbackHandler); SetParamDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.SetParamDelegate>( nativeRegistration.SetParam); GetParamDelegate = Marshal.GetDelegateForFunctionPointer <MsQuicNativeMethods.GetParamDelegate>( nativeRegistration.GetParam); RegistrationOpenDelegate(Encoding.UTF8.GetBytes("SystemNetQuic"), out IntPtr ctx); _registrationContext = ctx; }
internal static unsafe void SetUShortParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param, ushort value) { QuicExceptionHelpers.ThrowIfFailed( api.SetParamDelegate(nativeObject, level, param, sizeof(ushort), (byte *)&value), "Could not set ushort."); }
// TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer. // Consider moving bigger logic like this outside of constructor call chains. private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, SslStreamCertificateContext?certificateContext, List <SslApplicationProtocol>?alpnProtocols, CipherSuitesPolicy?cipherSuitesPolicy) { // TODO: some of these checks should be done by the QuicOptions type. if (alpnProtocols == null || alpnProtocols.Count == 0) { throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } if (options.MaxBidirectionalStreams > ushort.MaxValue) { throw new Exception("MaxBidirectionalStreams overflow."); } if ((flags & QUIC_CREDENTIAL_FLAGS.CLIENT) == 0) { if (certificate == null && certificateContext == null) { throw new Exception("Server must provide certificate"); } } else { flags |= QUIC_CREDENTIAL_FLAGS.INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAGS.NO_CERTIFICATE_VALIDATION; } if (!OperatingSystem.IsWindows()) { // Use certificate handles on Windows, fall-back to ASN1 otherwise. flags |= QUIC_CREDENTIAL_FLAGS.USE_PORTABLE_CERTIFICATES; } Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid); var settings = new QuicSettings { IsSetFlags = QuicSettingsIsSetFlags.PeerBidiStreamCount | QuicSettingsIsSetFlags.PeerUnidiStreamCount, PeerBidiStreamCount = (ushort)options.MaxBidirectionalStreams, PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams }; if (options.IdleTimeout != Timeout.InfiniteTimeSpan) { if (options.IdleTimeout <= TimeSpan.Zero) { throw new Exception("IdleTimeout must not be negative."); } ulong ms = (ulong)options.IdleTimeout.Ticks / TimeSpan.TicksPerMillisecond; if (ms > (1ul << 62) - 1) { throw new Exception("IdleTimeout is too large (max 2^62-1 milliseconds)"); } settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds; } else { settings.IdleTimeoutMs = 0; } settings.IsSetFlags |= QuicSettingsIsSetFlags.IdleTimeoutMs; uint status; SafeMsQuicConfigurationHandle?configurationHandle; X509Certificate2[]? intermediates = null; MemoryHandle[]? handles = null; QuicBuffer[]? buffers = null; try { MsQuicAlpnHelper.Prepare(alpnProtocols, out handles, out buffers); status = MsQuicApi.Api.ConfigurationOpenDelegate(MsQuicApi.Api.Registration, (QuicBuffer *)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(QuicSettings), context: IntPtr.Zero, out configurationHandle); } finally { MsQuicAlpnHelper.Return(ref handles, ref buffers); } QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed."); try { CredentialConfig config = default; config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback. if (cipherSuitesPolicy != null) { config.Flags |= QUIC_CREDENTIAL_FLAGS.SET_ALLOWED_CIPHER_SUITES; config.AllowedCipherSuites = CipherSuitePolicyToFlags(cipherSuitesPolicy); } if (certificateContext != null) { certificate = (X509Certificate2?)_contextCertificate.GetValue(certificateContext); intermediates = (X509Certificate2[]?)_contextChain.GetValue(certificateContext); if (certificate == null || intermediates == null) { throw new ArgumentException(nameof(certificateContext)); } } if (certificate != null) { if (OperatingSystem.IsWindows()) { config.Type = QUIC_CREDENTIAL_TYPE.CONTEXT; config.Certificate = certificate.Handle; status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } else { CredentialConfigCertificatePkcs12 pkcs12Config; byte[] asn1; if (intermediates?.Length > 0) { X509Certificate2Collection collection = new X509Certificate2Collection(); collection.Add(certificate); for (int i = 0; i < intermediates?.Length; i++) { collection.Add(intermediates[i]); } asn1 = collection.Export(X509ContentType.Pkcs12) !; } else { asn1 = certificate.Export(X509ContentType.Pkcs12); } fixed(void *ptr = asn1) { pkcs12Config.Asn1Blob = (IntPtr)ptr; pkcs12Config.Asn1BlobLength = (uint)asn1.Length; pkcs12Config.PrivateKeyPassword = IntPtr.Zero; config.Type = QUIC_CREDENTIAL_TYPE.PKCS12; config.Certificate = (IntPtr)(&pkcs12Config); status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } } } else { config.Type = QUIC_CREDENTIAL_TYPE.NONE; status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); } #if TARGET_WINDOWS if ((Interop.SECURITY_STATUS)status == Interop.SECURITY_STATUS.AlgorithmMismatch && MsQuicApi.Tls13MayBeDisabled) { throw new QuicException(SR.net_ssl_app_protocols_invalid, null, (int)status); } #endif QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed."); } catch { configurationHandle.Dispose(); throw; } return(configurationHandle); }