/*++ * ProcessHandshakeSuccess - * Called on successful completion of Handshake - * used to set header/trailer sizes for encryption use * * Fills in the information about established protocol * --*/ internal void ProcessHandshakeSuccess() { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::ProcessHandshakeSuccess"); StreamSizes streamSizes; SslStreamPal.QueryContextStreamSizes(_securityContext, out streamSizes); if (streamSizes != null) { try { _headerSize = streamSizes.header; _trailerSize = streamSizes.trailer; _maxDataSize = checked (streamSizes.maximumMessage - (_headerSize + _trailerSize)); } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::ProcessHandshakeSuccess", "StreamSizes out of range."); } throw; } } SslStreamPal.QueryContextConnectionInfo(_securityContext, out _connectionInfo); GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::ProcessHandshakeSuccess"); }
internal SecureChannel(string hostname, bool serverMode, SslProtocols sslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool remoteCertRequired, bool checkCertName, bool checkCertRevocationStatus, EncryptionPolicy encryptionPolicy, LocalCertSelectionCallback certSelectionDelegate) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::.ctor", "hostname:" + hostname + " #clientCertificates=" + ((clientCertificates == null) ? "0" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo))); if (Logging.On) { Logging.PrintInfo(Logging.Web, this, ".ctor", "hostname=" + hostname + ", #clientCertificates=" + ((clientCertificates == null) ? "0" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)) + ", encryptionPolicy=" + encryptionPolicy); } SslStreamPal.VerifyPackageInfo(); _destination = hostname; GlobalLog.Assert(hostname != null, "SecureChannel#{0}::.ctor()|hostname == null", Logging.HashString(this)); _hostName = hostname; _serverMode = serverMode; _sslProtocols = sslProtocols; _serverCertificate = serverCertificate; _clientCertificates = clientCertificates; _remoteCertRequired = remoteCertRequired; _securityContext = null; _checkCertRevocation = checkCertRevocationStatus; _checkCertName = checkCertName; _certSelectionDelegate = certSelectionDelegate; _refreshCredentialNeeded = true; _encryptionPolicy = encryptionPolicy; GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::.ctor"); }
/*++ * Encrypt - Encrypts our bytes before we send them over the wire * * PERF: make more efficient, this does an extra copy when the offset * is non-zero. * * Input: * buffer - bytes for sending * offset - * size - * output - Encrypted bytes * --*/ internal SecurityStatusPal Encrypt(byte[] buffer, int offset, int size, ref byte[] output, out int resultSize) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::Encrypt"); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::Encrypt() - offset: " + offset.ToString() + " size: " + size.ToString() + " buffersize: " + buffer.Length.ToString()); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::Encrypt() buffer:"); GlobalLog.Dump(buffer, Math.Min(buffer.Length, 128)); byte[] writeBuffer; try { if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) { throw new ArgumentOutOfRangeException("offset"); } if (size < 0 || size > (buffer == null ? 0 : buffer.Length - offset)) { throw new ArgumentOutOfRangeException("size"); } resultSize = 0; int bufferSizeNeeded = checked (size + _headerSize + _trailerSize); if (output != null && bufferSizeNeeded <= output.Length) { writeBuffer = output; } else { writeBuffer = new byte[bufferSizeNeeded]; } Buffer.BlockCopy(buffer, offset, writeBuffer, _headerSize, size); } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::Encrypt", "Arguments out of range."); } throw; } SecurityStatusPal secStatus = SslStreamPal.EncryptMessage(_securityContext, writeBuffer, size, _headerSize, _trailerSize, out resultSize); if (secStatus != SecurityStatusPal.OK) { GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::Encrypt ERROR", secStatus.ToString()); } else { output = writeBuffer; GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::Encrypt OK", "data size:" + resultSize.ToString()); } return(secStatus); }
internal ChannelBinding GetChannelBinding(ChannelBindingKind kind) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::GetChannelBindingToken", kind.ToString()); ChannelBinding result = null; if (_securityContext != null) { result = SslStreamPal.QueryContextChannelBinding(_securityContext, kind); } GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::GetChannelBindingToken", Logging.HashString(result)); return(result); }
internal SecurityStatusPal Decrypt(byte[] payload, ref int offset, ref int count) { GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::Decrypt() - offset: " + offset.ToString() + " size: " + count.ToString() + " buffersize: " + payload.Length.ToString()); if (offset < 0 || offset > (payload == null ? 0 : payload.Length)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::Encrypt", "Argument 'offset' out of range."); throw new ArgumentOutOfRangeException("offset"); } if (count < 0 || count > (payload == null ? 0 : payload.Length - offset)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::Encrypt", "Argument 'count' out of range."); throw new ArgumentOutOfRangeException("count"); } SecurityStatusPal secStatus = SslStreamPal.DecryptMessage(_securityContext, payload, ref offset, ref count); return(secStatus); }
public static SecurityBuffer[] GetIncomingSecurityBuffers(SslAuthenticationOptions options, ref SecurityBuffer incomingSecurity) { SecurityBuffer alpnBuffer = null; SecurityBuffer[] incomingSecurityBuffers = null; if (options.ApplicationProtocols != null && options.ApplicationProtocols.Count != 0) { byte[] alpnBytes = SslStreamPal.ConvertAlpnProtocolListToByteArray(options.ApplicationProtocols); alpnBuffer = new SecurityBuffer(alpnBytes, 0, alpnBytes.Length, SecurityBufferType.SECBUFFER_APPLICATION_PROTOCOLS); } if (incomingSecurity != null) { if (alpnBuffer != null) { incomingSecurityBuffers = new SecurityBuffer[] { incomingSecurity, new SecurityBuffer(null, 0, 0, SecurityBufferType.SECBUFFER_EMPTY), alpnBuffer }; } else { incomingSecurityBuffers = new SecurityBuffer[] { incomingSecurity, new SecurityBuffer(null, 0, 0, SecurityBufferType.SECBUFFER_EMPTY) }; } } else if (alpnBuffer != null) { incomingSecurity = alpnBuffer; } return(incomingSecurityBuffers); }
// This will initiate renegotiation or PHA for Tls1.3 private async Task RenegotiateAsync <TIOAdapter>(CancellationToken cancellationToken) where TIOAdapter : IReadWriteAdapter { if (Interlocked.Exchange(ref _nestedAuth, 1) == 1) { throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate")); } if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read")); } if (Interlocked.Exchange(ref _nestedWrite, 1) == 1) { _nestedRead = 0; throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write")); } try { if (_buffer.ActiveLength > 0) { throw new InvalidOperationException(SR.net_ssl_renegotiate_buffer); } _sslAuthenticationOptions !.RemoteCertRequired = true; _isRenego = true; SecurityStatusPal status = Renegotiate(out byte[]? nextmsg); if (nextmsg is { Length : > 0 }) { await TIOAdapter.WriteAsync(InnerStream, nextmsg, 0, nextmsg.Length, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { if (status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation) { // Peer does not want to renegotiate. That should keep session usable. return; } throw SslStreamPal.GetException(status); } _buffer.EnsureAvailableSpace(InitialHandshakeBufferSize); ProtocolToken message; do { message = await ReceiveBlobAsync <TIOAdapter>(cancellationToken).ConfigureAwait(false); if (message.Size > 0) { await TIOAdapter.WriteAsync(InnerStream, message.Payload !, 0, message.Size, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } }while (message.Status.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded); CompleteHandshake(_sslAuthenticationOptions !); }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { int copyBytes; if (_decryptedBytesCount != 0) { copyBytes = CopyDecryptedData(buffer); return(copyBytes); } copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); int readBytes = await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); if (readBytes == 0) { return(0); } int payloadBytes = GetFrameSize(new ReadOnlySpan <byte>(_internalBuffer, _internalOffset, readBytes)); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } readBytes = await FillBufferAsync(adapter, payloadBytes).ConfigureAwait(false); Debug.Assert(readBytes >= 0); if (readBytes == 0) { throw new IOException(SR.net_io_eof); } // At this point, readBytes contains the size of the header plus body. // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = payloadBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(readBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {status}"); } if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired) { return(0); } throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status)); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }
private ValueTask WriteSingleChunk <TIOAdapter>(TIOAdapter writeAdapter, ReadOnlyMemory <byte> buffer) where TIOAdapter : struct, ISslIOAdapter { // Request a write IO slot. Task ioSlot = writeAdapter.WriteLockAsync(); if (!ioSlot.IsCompletedSuccessfully) { // Operation is async and has been queued, return. return(WaitForWriteIOSlot(writeAdapter, ioSlot, buffer)); } byte[] rentedBuffer = ArrayPool <byte> .Shared.Rent(buffer.Length + FrameOverhead); byte[] outBuffer = rentedBuffer; SecurityStatusPal status = EncryptData(buffer, ref outBuffer, out int encryptedBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { // Re-handshake status is not supported. ArrayPool <byte> .Shared.Return(rentedBuffer); return(new ValueTask(Task.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.net_io_encrypt, SslStreamPal.GetException(status)))))); } ValueTask t = writeAdapter.WriteAsync(outBuffer, 0, encryptedBytes); if (t.IsCompletedSuccessfully) { ArrayPool <byte> .Shared.Return(rentedBuffer); FinishWrite(); return(t); } else { return(CompleteAsync(t, rentedBuffer)); } async ValueTask WaitForWriteIOSlot(TIOAdapter wAdapter, Task lockTask, ReadOnlyMemory <byte> buff) { await lockTask.ConfigureAwait(false); await WriteSingleChunk(wAdapter, buff).ConfigureAwait(false); } async ValueTask CompleteAsync(ValueTask writeTask, byte[] bufferToReturn) { try { await writeTask.ConfigureAwait(false); } finally { ArrayPool <byte> .Shared.Return(bufferToReturn); FinishWrite(); } } }
/*++ * GenerateToken - Called after each successive state * in the Client - Server handshake. This function * generates a set of bytes that will be sent next to * the server. The server responds, each response, * is pass then into this function, again, and the cycle * repeats until successful connection, or failure. * * Input: * input - bytes from the wire * output - ref to byte [], what we will send to the * server in response * Return: * errorCode - an SSPI error code * --*/ private SecurityStatusPal GenerateToken(byte[] input, int offset, int count, ref byte[] output) { #if TRACE_VERBOSE GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::GenerateToken, _refreshCredentialNeeded = " + _refreshCredentialNeeded); #endif if (offset < 0 || offset > (input == null ? 0 : input.Length)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::GenerateToken", "Argument 'offset' out of range."); throw new ArgumentOutOfRangeException("offset"); } if (count < 0 || count > (input == null ? 0 : input.Length - offset)) { GlobalLog.Assert(false, "SecureChannel#" + Logging.HashString(this) + "::GenerateToken", "Argument 'count' out of range."); throw new ArgumentOutOfRangeException("count"); } SecurityBuffer incomingSecurity = null; SecurityBuffer[] incomingSecurityBuffers = null; if (input != null) { incomingSecurity = new SecurityBuffer(input, offset, count, SecurityBufferType.Token); incomingSecurityBuffers = new SecurityBuffer[] { incomingSecurity, new SecurityBuffer(null, 0, 0, SecurityBufferType.Empty) }; } SecurityBuffer outgoingSecurity = new SecurityBuffer(null, SecurityBufferType.Token); SecurityStatusPal errorCode = 0; bool cachedCreds = false; byte[] thumbPrint = null; // // Looping through ASC or ISC with potentially cached credential that could have been // already disposed from a different thread before ISC or ASC dir increment a cred ref count. // try { do { thumbPrint = null; if (_refreshCredentialNeeded) { cachedCreds = _serverMode ? AcquireServerCredentials(ref thumbPrint) : AcquireClientCredentials(ref thumbPrint); } if (_serverMode) { errorCode = SslStreamPal.AcceptSecurityContext( ref _credentialsHandle, ref _securityContext, incomingSecurity, outgoingSecurity, _remoteCertRequired); } else { if (incomingSecurity == null) { errorCode = SslStreamPal.InitializeSecurityContext( ref _credentialsHandle, ref _securityContext, _destination, incomingSecurity, outgoingSecurity); } else { errorCode = SslStreamPal.InitializeSecurityContext( _credentialsHandle, ref _securityContext, _destination, incomingSecurityBuffers, outgoingSecurity); } } } while (cachedCreds && _credentialsHandle == null); } finally { if (_refreshCredentialNeeded) { _refreshCredentialNeeded = false; // // Assuming the ISC or ASC has referenced the credential, // we want to call dispose so to decrement the effective ref count. // if (_credentialsHandle != null) { _credentialsHandle.Dispose(); } // // This call may bump up the credential reference count further. // Note that thumbPrint is retrieved from a safe cert object that was possible cloned from the user passed cert. // if (!cachedCreds && _securityContext != null && !_securityContext.IsInvalid && _credentialsHandle != null && !_credentialsHandle.IsInvalid) { SslSessionsCache.CacheCredential(_credentialsHandle, thumbPrint, _sslProtocols, _serverMode, _encryptionPolicy); } } } output = outgoingSecurity.token; #if TRACE_VERBOSE GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::GenerateToken()", Interop.MapSecurityStatus((uint)errorCode)); #endif return((SecurityStatusPal)errorCode); }
// // Acquire Server Side Certificate information and set it on the class. // private bool AcquireServerCredentials(ref byte[] thumbPrint) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::AcquireServerCredentials"); X509Certificate localCertificate = null; bool cachedCred = false; if (_certSelectionDelegate != null) { X509CertificateCollection tempCollection = new X509CertificateCollection(); tempCollection.Add(_serverCertificate); localCertificate = _certSelectionDelegate(string.Empty, tempCollection, null, Array.Empty <string>()); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireServerCredentials() Use delegate selected Cert"); } else { localCertificate = _serverCertificate; } if (localCertificate == null) { throw new NotSupportedException(SR.net_ssl_io_no_server_cert); } // SECURITY: Accessing X509 cert Credential is disabled for semitrust. // We no longer need to demand for unmanaged code permissions. // EnsurePrivateKey should do the right demand for us. X509Certificate2 selectedCert = EnsurePrivateKey(localCertificate); if (selectedCert == null) { throw new NotSupportedException(SR.net_ssl_io_no_server_cert); } GlobalLog.Assert(localCertificate.Equals(selectedCert), "AcquireServerCredentials()|'selectedCert' does not match 'localCertificate'."); // // Note selectedCert is a safe ref possibly cloned from the user passed Cert object // byte[] guessedThumbPrint = selectedCert.GetCertHash(); try { SafeFreeCredentials cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint, _sslProtocols, _serverMode, _encryptionPolicy); if (cachedCredentialHandle != null) { _credentialsHandle = cachedCredentialHandle; _serverCertificate = localCertificate; cachedCred = true; } else { _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(selectedCert, _sslProtocols, _encryptionPolicy, _serverMode); thumbPrint = guessedThumbPrint; _serverCertificate = localCertificate; } } finally { // An extra cert could have been created, dispose it now. if ((object)localCertificate != (object)selectedCert) { selectedCert.Dispose(); } } GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::AcquireServerCredentials, cachedCreds = " + cachedCred.ToString(), Logging.ObjectToString(_credentialsHandle)); return(cachedCred); }
/*++ * AcquireCredentials - Attempts to find Client Credential * Information, that can be sent to the server. In our case, * this is only Client Certificates, that we have Credential Info. * * How it works: * case 0: Cert Selection delegate is present * Always use its result as the client cert answer. * Try to use cached credential handle whenever feasible. * Do not use cached anonymous creds if the delegate has returned null * and the collection is not empty (allow responding with the cert later). * * case 1: Certs collection is empty * Always use the same statically acquired anonymous SSL Credential * * case 2: Before our Connection with the Server * If we have a cached credential handle keyed by first X509Certificate **content** in the passed collection, then we use that cached * credential and hoping to restart a session. * * Otherwise create a new anonymous (allow responding with the cert later). * * case 3: After our Connection with the Server (i.e. during handshake or re-handshake) * The server has requested that we send it a Certificate then * we Enumerate a list of server sent Issuers trying to match against * our list of Certificates, the first match is sent to the server. * * Once we got a cert we again try to match cached credential handle if possible. * This will not restart a session but helps minimizing the number of handles we create. * * In the case of an error getting a Certificate or checking its private Key we fall back * to the behavior of having no certs, case 1. * * Returns: True if cached creds were used, false otherwise. * * --*/ private bool AcquireClientCredentials(ref byte[] thumbPrint) { GlobalLog.Enter("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials"); // Acquire possible Client Certificate information and set it on the handle. X509Certificate clientCertificate = null; // This is a candidate that can come from the user callback or be guessed when targeting a session restart. ArrayList filteredCerts = new ArrayList(); // This is an intermediate client certs collection that try to use if no selectedCert is available yet. string[] issuers = null; // This is a list of issuers sent by the server, only valid is we do know what the server cert is. bool sessionRestartAttempt = false; // If true and no cached creds we will use anonymous creds. if (_certSelectionDelegate != null) { issuers = GetRequestCertificateAuthorities(); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() calling CertificateSelectionCallback"); X509Certificate2 remoteCert = null; try { X509Certificate2Collection dummyCollection; remoteCert = CertificateValidationPal.GetRemoteCertificate(_securityContext, out dummyCollection); clientCertificate = _certSelectionDelegate(_hostName, ClientCertificates, remoteCert, issuers); } finally { if (remoteCert != null) { remoteCert.Dispose(); } } if (clientCertificate != null) { if (_credentialsHandle == null) { sessionRestartAttempt = true; } filteredCerts.Add(clientCertificate); if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_got_certificate_from_delegate); } } else { if (ClientCertificates.Count == 0) { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_delegate_and_have_no_client_cert); } sessionRestartAttempt = true; } else { if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_delegate_but_have_client_cert); } } } } else if (_credentialsHandle == null && _clientCertificates != null && _clientCertificates.Count > 0) { // This is where we attempt to restart a session by picking the FIRST cert from the collection. // Otherwise it is either server sending a client cert request or the session is renegotiated. clientCertificate = ClientCertificates[0]; sessionRestartAttempt = true; if (clientCertificate != null) { filteredCerts.Add(clientCertificate); } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_attempting_restart_using_cert, (clientCertificate == null ? "null" : clientCertificate.ToString(true)))); } } else if (_clientCertificates != null && _clientCertificates.Count > 0) { // // This should be a server request for the client cert sent over currently anonymous sessions. // issuers = GetRequestCertificateAuthorities(); if (Logging.On) { if (issuers == null || issuers.Length == 0) { Logging.PrintInfo(Logging.Web, this, SR.net_log_no_issuers_try_all_certs); } else { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_server_issuers_look_for_matching_certs, issuers.Length)); } } for (int i = 0; i < _clientCertificates.Count; ++i) { // // Make sure we add only if the cert matches one of the issuers. // If no issuers were sent and then try all client certs starting with the first one. // if (issuers != null && issuers.Length != 0) { X509Certificate2 certificateEx = null; X509Chain chain = null; try { certificateEx = MakeEx(_clientCertificates[i]); if (certificateEx == null) { continue; } GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() root cert:" + certificateEx.Issuer); chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreInvalidName; chain.Build(certificateEx); bool found = false; // // We ignore any errors happened with chain. // if (chain.ChainElements.Count > 0) { for (int ii = 0; ii < chain.ChainElements.Count; ++ii) { string issuer = chain.ChainElements[ii].Certificate.Issuer; found = Array.IndexOf(issuers, issuer) != -1; if (found) { GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() matched:" + issuer); break; } GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() no match:" + issuer); } } if (!found) { continue; } } finally { if (chain != null) { chain.Dispose(); } if (certificateEx != null && (object)certificateEx != (object)_clientCertificates[i]) { certificateEx.Dispose(); } } } if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_selected_cert, _clientCertificates[i].ToString(true))); } filteredCerts.Add(_clientCertificates[i]); } } bool cachedCred = false; // This is a return result from this method. X509Certificate2 selectedCert = null; // This is a final selected cert (ensured that it does have private key with it). clientCertificate = null; if (Logging.On) { Logging.PrintInfo(Logging.Web, this, SR.Format(SR.net_log_n_certs_after_filtering, filteredCerts.Count)); if (filteredCerts.Count != 0) { Logging.PrintInfo(Logging.Web, this, SR.net_log_finding_matching_certs); } } // // ATTN: When the client cert was returned by the user callback OR it was guessed AND it has no private key, // THEN anonymous (no client cert) credential will be used. // // SECURITY: Accessing X509 cert Credential is disabled for semitrust. // We no longer need to demand for unmanaged code permissions. // EnsurePrivateKey should do the right demand for us. for (int i = 0; i < filteredCerts.Count; ++i) { clientCertificate = filteredCerts[i] as X509Certificate; if ((selectedCert = EnsurePrivateKey(clientCertificate)) != null) { break; } clientCertificate = null; selectedCert = null; } GlobalLog.Assert(((object)clientCertificate == (object)selectedCert) || clientCertificate.Equals(selectedCert), "AcquireClientCredentials()|'selectedCert' does not match 'clientCertificate'."); GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() Selected Cert = " + (selectedCert == null ? "null" : selectedCert.Subject)); try { // Try to locate cached creds first. // // SECURITY: selectedCert ref if not null is a safe object that does not depend on possible **user** inherited X509Certificate type. // byte[] guessedThumbPrint = selectedCert == null ? null : selectedCert.GetCertHash(); SafeFreeCredentials cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint, _sslProtocols, _serverMode, _encryptionPolicy); // We can probably do some optimization here. If the selectedCert is returned by the delegate // we can always go ahead and use the certificate to create our credential // (instead of going anonymous as we do here). if (sessionRestartAttempt && cachedCredentialHandle == null && selectedCert != null) { GlobalLog.Print("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials() Reset to anonymous session."); // IIS does not renegotiate a restarted session if client cert is needed. // So we don't want to reuse **anonymous** cached credential for a new SSL connection if the client has passed some certificate. // The following block happens if client did specify a certificate but no cached creds were found in the cache. // Since we don't restart a session the server side can still challenge for a client cert. if ((object)clientCertificate != (object)selectedCert) { selectedCert.Dispose(); } guessedThumbPrint = null; selectedCert = null; clientCertificate = null; } if (cachedCredentialHandle != null) { if (Logging.On) { Logging.PrintInfo(Logging.Web, SR.net_log_using_cached_credential); } _credentialsHandle = cachedCredentialHandle; _selectedClientCertificate = clientCertificate; cachedCred = true; } else { _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(selectedCert, _sslProtocols, _encryptionPolicy, _serverMode); thumbPrint = guessedThumbPrint; // Delay until here in case something above threw. _selectedClientCertificate = clientCertificate; } } finally { // An extra cert could have been created, dispose it now. if (selectedCert != null && (object)clientCertificate != (object)selectedCert) { selectedCert.Dispose(); } } GlobalLog.Leave("SecureChannel#" + Logging.HashString(this) + "::AcquireClientCredentials, cachedCreds = " + cachedCred.ToString(), Logging.ObjectToString(_credentialsHandle)); return(cachedCred); }
internal Exception GetException() { // If it's not done, then there's got to be an error, even if it's // a Handshake message up, and we only have a Warning message. return(this.Done ? null : SslStreamPal.GetException(Status)); }
private async ValueTask <int> ReadAsyncInternal <TIOAdapter>(TIOAdapter adapter, Memory <byte> buffer) where TIOAdapter : ISslIOAdapter { if (Interlocked.Exchange(ref _nestedRead, 1) == 1) { throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, nameof(SslStream.ReadAsync), "read")); } try { while (true) { if (_decryptedBytesCount != 0) { return(CopyDecryptedData(buffer)); } int copyBytes = await adapter.ReadLockAsync(buffer).ConfigureAwait(false); if (copyBytes > 0) { return(copyBytes); } ResetReadBuffer(); // Read the next frame header. if (_internalBufferCount < SecureChannel.ReadHeaderSize) { // We don't have enough bytes buffered, so issue an initial read to try to get enough. This is // done in this method both to better consolidate error handling logic (the first read is the special // case that needs to differentiate reading 0 from > 0, and everything else needs to throw if it // doesn't read enough), and to minimize the chances that in the common case the FillBufferAsync // helper needs to yield and allocate a state machine. int readBytes = await adapter.ReadAsync(_internalBuffer.AsMemory(_internalBufferCount)).ConfigureAwait(false); if (readBytes == 0) { return(0); } _internalBufferCount += readBytes; if (_internalBufferCount < SecureChannel.ReadHeaderSize) { await FillBufferAsync(adapter, SecureChannel.ReadHeaderSize).ConfigureAwait(false); } } Debug.Assert(_internalBufferCount >= SecureChannel.ReadHeaderSize); // Parse the frame header to determine the payload size (which includes the header size). int payloadBytes = GetFrameSize(_internalBuffer.AsSpan(_internalOffset)); if (payloadBytes < 0) { throw new IOException(SR.net_frame_read_size); } // Read in the rest of the payload if we don't have it. if (_internalBufferCount < payloadBytes) { await FillBufferAsync(adapter, payloadBytes).ConfigureAwait(false); } // Set _decrytpedBytesOffset/Count to the current frame we have (including header) // DecryptData will decrypt in-place and modify these to point to the actual decrypted data, which may be smaller. _decryptedBytesOffset = _internalOffset; _decryptedBytesCount = payloadBytes; SecurityStatusPal status = DecryptData(); // Treat the bytes we just decrypted as consumed // Note, we won't do another buffer read until the decrypted bytes are processed ConsumeBufferedBytes(payloadBytes); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { byte[] extraBuffer = null; if (_decryptedBytesCount != 0) { extraBuffer = new byte[_decryptedBytesCount]; Buffer.BlockCopy(_internalBuffer, _decryptedBytesOffset, extraBuffer, 0, _decryptedBytesCount); _decryptedBytesCount = 0; } if (NetEventSource.IsEnabled) { NetEventSource.Info(null, $"***Processing an error Status = {status}"); } if (status.ErrorCode == SecurityStatusPalErrorCode.Renegotiate) { if (!_sslAuthenticationOptions.AllowRenegotiation) { if (NetEventSource.IsEnabled) { NetEventSource.Fail(this, "Renegotiation was requested but it is disallowed"); } throw new IOException(SR.net_ssl_io_renego); } await ReplyOnReAuthenticationAsync(adapter, extraBuffer).ConfigureAwait(false); // Loop on read. continue; } if (status.ErrorCode == SecurityStatusPalErrorCode.ContextExpired) { return(0); } throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status)); } } } catch (Exception e) { if (e is IOException || (e is OperationCanceledException && adapter.CancellationToken.IsCancellationRequested)) { throw; } throw new IOException(SR.net_io_read, e); } finally { _nestedRead = 0; } }