internal static unsafe int Wrap(SafeDeleteContext securityContext, ReadOnlySpan <byte> buffer, [NotNull] ref byte[]?output, bool isConfidential) { SecPkgContext_Sizes sizes = default; bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); Debug.Assert(success); // alloc new output buffer if not supplied or too small int resultSize = buffer.Length + sizes.cbMaxSignature; if (output == null || output.Length < resultSize) { output = new byte[resultSize]; } // make a copy of user data for in-place encryption buffer.CopyTo(output.AsSpan(sizes.cbMaxSignature, buffer.Length)); fixed(byte *outputPtr = output) { // Prepare buffers TOKEN(signature), DATA and Padding. Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer *tokenBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; tokenBuffer->pvBuffer = (IntPtr)(outputPtr); tokenBuffer->cbBuffer = sizes.cbMaxSignature; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = (IntPtr)(outputPtr + sizes.cbMaxSignature); dataBuffer->cbBuffer = buffer.Length; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; uint qop = isConfidential ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; int errorCode = GlobalSSPI.SSPIAuth.EncryptMessage(securityContext, ref sdcInOut, qop); if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(null, e); } throw new Win32Exception(errorCode); } // return signed size return(tokenBuffer->cbBuffer + dataBuffer->cbBuffer); } }
public static unsafe SecurityStatusPal DecryptMessage(SafeDeleteContext securityContext, byte[] buffer, ref int offset, ref int count) { const int NumSecBuffers = 4; // data + empty + empty + empty var unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers]; var sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers); sdcInOut.pBuffers = unmanagedBuffer; fixed(byte *bufferPtr = buffer) { Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[0]; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = (IntPtr)bufferPtr + offset; dataBuffer->cbBuffer = count; for (int i = 1; i < NumSecBuffers; i++) { Interop.SspiCli.SecBuffer *emptyBuffer = &unmanagedBuffer[i]; emptyBuffer->BufferType = SecurityBufferType.SECBUFFER_EMPTY; emptyBuffer->pvBuffer = IntPtr.Zero; emptyBuffer->cbBuffer = 0; } Interop.SECURITY_STATUS errorCode = (Interop.SECURITY_STATUS)GlobalSSPI.SSPISecureChannel.DecryptMessage(securityContext, ref sdcInOut, 0); // Decrypt may repopulate the sec buffers, likely with header + data + trailer + empty. // We need to find the data. count = 0; for (int i = 0; i < NumSecBuffers; i++) { // Successfully decoded data and placed it at the following position in the buffer, if ((errorCode == Interop.SECURITY_STATUS.OK && unmanagedBuffer[i].BufferType == SecurityBufferType.SECBUFFER_DATA) // or we failed to decode the data, here is the encoded data. || (errorCode != Interop.SECURITY_STATUS.OK && unmanagedBuffer[i].BufferType == SecurityBufferType.SECBUFFER_EXTRA)) { offset = (int)((byte *)unmanagedBuffer[i].pvBuffer - bufferPtr); count = unmanagedBuffer[i].cbBuffer; Debug.Assert(offset >= 0 && count >= 0, $"Expected offset and count greater than 0, got {offset} and {count}"); Debug.Assert(checked (offset + count) <= buffer.Length, $"Expected offset+count <= buffer.Length, got {offset}+{count}>={buffer.Length}"); break; } } return(SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(errorCode)); } }
internal static unsafe int Unwrap(SafeDeleteContext securityContext, Span <byte> buffer, out int newOffset, out bool wasConfidential) { fixed(byte *bufferPtr = buffer) { Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer *streamBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; streamBuffer->pvBuffer = (IntPtr)bufferPtr; streamBuffer->cbBuffer = buffer.Length; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = IntPtr.Zero; dataBuffer->cbBuffer = 0; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; uint qop; int errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, out qop); if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(null, e); } throw new Win32Exception(errorCode); } if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA) { throw new InternalException(dataBuffer->BufferType); } wasConfidential = qop != Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); newOffset = (int)((byte *)dataBuffer->pvBuffer - bufferPtr); return(dataBuffer->cbBuffer); } }
internal static unsafe NegotiateAuthenticationStatusCode UnwrapInPlace( SafeDeleteContext securityContext, Span <byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) { fixed(byte *inputPtr = input) { Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer *streamBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; streamBuffer->pvBuffer = (IntPtr)inputPtr; streamBuffer->cbBuffer = input.Length; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = IntPtr.Zero; dataBuffer->cbBuffer = 0; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; uint qop; int errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, out qop); if (errorCode != 0) { unwrappedOffset = 0; unwrappedLength = 0; wasEncrypted = false; return(errorCode switch { (int)Interop.SECURITY_STATUS.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered, _ => NegotiateAuthenticationStatusCode.InvalidToken });
public static unsafe SecurityStatusPal EncryptMessage(SafeDeleteContext securityContext, ReadOnlyMemory <byte> input, int headerSize, int trailerSize, ref byte[] output, out int resultSize) { // Ensure that there is sufficient space for the message output. int bufferSizeNeeded; try { bufferSizeNeeded = checked (input.Length + headerSize + trailerSize); } catch { NetEventSource.Fail(securityContext, "Arguments out of range"); throw; } if (output == null || output.Length < bufferSizeNeeded) { output = new byte[bufferSizeNeeded]; } // Copy the input into the output buffer to prepare for SCHANNEL's expectations input.Span.CopyTo(new Span <byte>(output, headerSize, input.Length)); const int NumSecBuffers = 4; // header + data + trailer + empty var unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers]; var sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers); sdcInOut.pBuffers = unmanagedBuffer; fixed(byte *outputPtr = output) { Interop.SspiCli.SecBuffer *headerSecBuffer = &unmanagedBuffer[0]; headerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_HEADER; headerSecBuffer->pvBuffer = (IntPtr)outputPtr; headerSecBuffer->cbBuffer = headerSize; Interop.SspiCli.SecBuffer *dataSecBuffer = &unmanagedBuffer[1]; dataSecBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize); dataSecBuffer->cbBuffer = input.Length; Interop.SspiCli.SecBuffer *trailerSecBuffer = &unmanagedBuffer[2]; trailerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_TRAILER; trailerSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize + input.Length); trailerSecBuffer->cbBuffer = trailerSize; Interop.SspiCli.SecBuffer *emptySecBuffer = &unmanagedBuffer[3]; emptySecBuffer->BufferType = SecurityBufferType.SECBUFFER_EMPTY; emptySecBuffer->cbBuffer = 0; emptySecBuffer->pvBuffer = IntPtr.Zero; int errorCode = GlobalSSPI.SSPISecureChannel.EncryptMessage(securityContext, ref sdcInOut, 0); if (errorCode != 0) { if (NetEventSource.IsEnabled) { NetEventSource.Info(securityContext, $"Encrypt ERROR {errorCode:X}"); } resultSize = 0; return(SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode)); } Debug.Assert(headerSecBuffer->cbBuffer >= 0 && dataSecBuffer->cbBuffer >= 0 && trailerSecBuffer->cbBuffer >= 0); Debug.Assert(checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer) <= output.Length); resultSize = checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer); return(new SecurityStatusPal(SecurityStatusPalErrorCode.OK)); } }
private static unsafe int DecryptNtlm( SafeDeleteContext securityContext, Span <byte> buffer, bool isConfidential, out int newOffset) { const int NtlmSignatureLength = 16; // For the most part the arguments are verified in Decrypt(). if (buffer.Length < NtlmSignatureLength) { Debug.Fail("Argument 'count' out of range."); throw new Win32Exception((int)Interop.SECURITY_STATUS.InvalidToken); } fixed(byte *bufferPtr = buffer) { SecurityBufferType realDataType = SecurityBufferType.SECBUFFER_DATA; Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer *tokenBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; tokenBuffer->pvBuffer = (IntPtr)bufferPtr; tokenBuffer->cbBuffer = NtlmSignatureLength; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = (IntPtr)(bufferPtr + NtlmSignatureLength); dataBuffer->cbBuffer = buffer.Length - NtlmSignatureLength; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; uint qop; int errorCode; if (!isConfidential) { realDataType |= SecurityBufferType.SECBUFFER_READONLY; dataBuffer->BufferType = realDataType; } errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, out qop); if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(null, e); } throw new Win32Exception(errorCode); } if (isConfidential && qop == Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT) { Debug.Fail($"Expected qop = 0, returned value = {qop}"); throw new InvalidOperationException(SR.net_auth_message_not_encrypted); } if (dataBuffer->BufferType != realDataType) { throw new InternalException(dataBuffer->BufferType); } Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); newOffset = (int)((byte *)dataBuffer->pvBuffer - bufferPtr); return(dataBuffer->cbBuffer); } }
internal static unsafe int Decrypt( SafeDeleteContext securityContext, Span <byte> buffer, bool isConfidential, bool isNtlm, out int newOffset) { if (isNtlm) { return(DecryptNtlm(securityContext, buffer, isConfidential, out newOffset)); } // // Kerberos and up // fixed(byte *bufferPtr = buffer) { Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer *streamBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; streamBuffer->pvBuffer = (IntPtr)bufferPtr; streamBuffer->cbBuffer = buffer.Length; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = IntPtr.Zero; dataBuffer->cbBuffer = 0; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; uint qop; int errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, out qop); if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(null, e); } throw new Win32Exception(errorCode); } if (qop == Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT && isConfidential) { Debug.Fail($"Expected qop = 0, returned value = {qop}"); throw new InvalidOperationException(SR.net_auth_message_not_encrypted); } if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA) { throw new InternalException(dataBuffer->BufferType); } Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); newOffset = (int)((byte *)dataBuffer->pvBuffer - bufferPtr); return(dataBuffer->cbBuffer); } }
internal static unsafe int Encrypt( SafeDeleteContext securityContext, ReadOnlySpan <byte> buffer, bool isConfidential, bool isNtlm, [NotNull] ref byte[]?output) { SecPkgContext_Sizes sizes = default; bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); Debug.Assert(success); int maxCount = checked (int.MaxValue - 4 - sizes.cbBlockSize - sizes.cbSecurityTrailer); if (buffer.Length > maxCount) { throw new ArgumentOutOfRangeException(nameof(buffer.Length), SR.Format(SR.net_io_out_range, maxCount)); } int resultSize = buffer.Length + sizes.cbSecurityTrailer + sizes.cbBlockSize; if (output == null || output.Length < resultSize + 4) { output = new byte[resultSize + 4]; } // Make a copy of user data for in-place encryption. buffer.CopyTo(output.AsSpan(4 + sizes.cbSecurityTrailer)); fixed(byte *outputPtr = output) { // Prepare buffers TOKEN(signature), DATA and Padding. Interop.SspiCli.SecBuffer *unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[3]; Interop.SspiCli.SecBuffer *tokenBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer *dataBuffer = &unmanagedBuffer[1]; Interop.SspiCli.SecBuffer *paddingBuffer = &unmanagedBuffer[2]; tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; tokenBuffer->pvBuffer = (IntPtr)(outputPtr + 4); tokenBuffer->cbBuffer = sizes.cbSecurityTrailer; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = (IntPtr)(outputPtr + 4 + sizes.cbSecurityTrailer); dataBuffer->cbBuffer = buffer.Length; paddingBuffer->BufferType = SecurityBufferType.SECBUFFER_PADDING; paddingBuffer->pvBuffer = (IntPtr)(outputPtr + 4 + sizes.cbSecurityTrailer + buffer.Length); paddingBuffer->cbBuffer = sizes.cbBlockSize; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(3) { pBuffers = unmanagedBuffer }; if (isNtlm && !isConfidential) { dataBuffer->BufferType |= SecurityBufferType.SECBUFFER_READONLY; } uint qop = isConfidential ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; int errorCode = GlobalSSPI.SSPIAuth.EncryptMessage(securityContext, ref sdcInOut, qop); if (errorCode != 0) { Exception e = new Win32Exception(errorCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(null, e); } throw new Win32Exception(errorCode); } // Compacting the result. resultSize = tokenBuffer->cbBuffer; bool forceCopy = false; if (resultSize != sizes.cbSecurityTrailer) { forceCopy = true; output.AsSpan(4 + sizes.cbSecurityTrailer, dataBuffer->cbBuffer).CopyTo(output.AsSpan(4 + resultSize, dataBuffer->cbBuffer)); } resultSize += dataBuffer->cbBuffer; if (paddingBuffer->cbBuffer != 0 && (forceCopy || resultSize != (buffer.Length + sizes.cbSecurityTrailer))) { output.AsSpan(4 + sizes.cbSecurityTrailer + buffer.Length, paddingBuffer->cbBuffer).CopyTo(output.AsSpan(4 + resultSize, paddingBuffer->cbBuffer)); } resultSize += paddingBuffer->cbBuffer; BinaryPrimitives.WriteInt32LittleEndian(output, resultSize); return(resultSize + 4); } }
public static unsafe SecurityStatusPal EncryptMessage(SafeDeleteContext securityContext, byte[] input, int offset, int size, int headerSize, int trailerSize, ref byte[] output, out int resultSize) { // Ensure that there is sufficient space for the message output. int bufferSizeNeeded; try { bufferSizeNeeded = checked (size + headerSize + trailerSize); } catch { if (GlobalLog.IsEnabled) { GlobalLog.Assert("SslStreamPal.Windows: SecureChannel#" + LoggingHash.HashString(securityContext) + "::Encrypt", "Arguments out of range."); } Debug.Fail("SslStreamPal.Windows: SecureChannel#" + LoggingHash.HashString(securityContext) + "::Encrypt", "Arguments out of range."); throw; } if (output == null || output.Length < bufferSizeNeeded) { output = new byte[bufferSizeNeeded]; } // Copy the input into the output buffer to prepare for SCHANNEL's expectations Buffer.BlockCopy(input, offset, output, headerSize, size); const int NumSecBuffers = 4; // header + data + trailer + empty var unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers]; var sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers); sdcInOut.pBuffers = unmanagedBuffer; fixed(byte *outputPtr = output) { Interop.SspiCli.SecBuffer *headerSecBuffer = &unmanagedBuffer[0]; headerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_HEADER; headerSecBuffer->pvBuffer = (IntPtr)outputPtr; headerSecBuffer->cbBuffer = headerSize; Interop.SspiCli.SecBuffer *dataSecBuffer = &unmanagedBuffer[1]; dataSecBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize); dataSecBuffer->cbBuffer = size; Interop.SspiCli.SecBuffer *trailerSecBuffer = &unmanagedBuffer[2]; trailerSecBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM_TRAILER; trailerSecBuffer->pvBuffer = (IntPtr)(outputPtr + headerSize + size); trailerSecBuffer->cbBuffer = trailerSize; Interop.SspiCli.SecBuffer *emptySecBuffer = &unmanagedBuffer[3]; emptySecBuffer->BufferType = SecurityBufferType.SECBUFFER_EMPTY; emptySecBuffer->cbBuffer = 0; emptySecBuffer->pvBuffer = IntPtr.Zero; int errorCode = GlobalSSPI.SSPISecureChannel.EncryptMessage(securityContext, sdcInOut, 0); if (errorCode != 0) { if (GlobalLog.IsEnabled) { GlobalLog.Print("SslStreamPal.Windows: SecureChannel#" + LoggingHash.HashString(securityContext) + "::Encrypt ERROR" + errorCode.ToString("x")); } resultSize = 0; return(SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode)); } Debug.Assert(headerSecBuffer->cbBuffer >= 0 && dataSecBuffer->cbBuffer >= 0 && trailerSecBuffer->cbBuffer >= 0); Debug.Assert(checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer) <= output.Length); resultSize = checked (headerSecBuffer->cbBuffer + dataSecBuffer->cbBuffer + trailerSecBuffer->cbBuffer); return(new SecurityStatusPal(SecurityStatusPalErrorCode.OK)); } }