Stores buffers to provide tokens and data to the native SSPI APIs.
The buffer is translated into a SecureBufferInternal for the actual call. To keep the call setup code simple, and to centralize the buffer pinning code, this class stores and returns buffers as regular byte arrays. The buffer pinning support code in SecureBufferAdapter handles conversion to SecureBufferInternal for pass to the managed api, as well as pinning relevant chunks of memory. Furthermore, the native API may not use the entire buffer, and so a mechanism is needed to communicate the usage of the buffer separate from the length of the buffer.
 /// <summary>
 /// Initializes a SecureBufferAdapter to carry a single buffer to the native api.
 /// </summary>
 /// <param name="buffer"></param>
 public SecureBufferAdapter(SecureBuffer buffer)
     : this(new[] { buffer })
 {
 }
 /// <summary>
 /// Initializes a SecureBufferAdapter to carry a single buffer to the native api.
 /// </summary>
 /// <param name="buffer"></param>
 public SecureBufferAdapter( SecureBuffer buffer )
     : this( new[] { buffer } )
 {
 }
Example #3
0
        /// <summary>
        /// Verifies the signature of a signed message
        /// </summary>
        /// <remarks>
        /// The expected structure of the signed message buffer is as follows:
        ///  - 4 bytes, unsigned integer in big endian format indicating the length of the plaintext message
        ///  - 2 bytes, unsigned integer in big endian format indicating the length of the signture
        ///  - The plaintext message
        ///  - The message's signature.
        /// </remarks>
        /// <param name="signedMessage">The packed signed message.</param>
        /// <param name="origMessage">The extracted original message.</param>
        /// <returns>True if the message has a valid signature, false otherwise.</returns>
        public bool VerifySignature( byte[] signedMessage, out byte[] origMessage )
        {
            SecurityStatus status = SecurityStatus.InternalError;

            SecPkgContext_Sizes sizes;
            SecureBuffer dataBuffer;
            SecureBuffer signatureBuffer;
            SecureBufferAdapter adapter;

            CheckLifecycle();

            sizes = QueryBufferSizes();
            
            if ( signedMessage.Length < 2 + 4 + sizes.MaxSignature )
            {
                throw new ArgumentException( "Input message is too small to possibly fit a valid message" );
            }

            int position = 0;
            int messageLen;
            int sigLen;

            messageLen = ByteWriter.ReadInt32_BE( signedMessage, 0 );
            position += 4;

            sigLen = ByteWriter.ReadInt16_BE( signedMessage, position );
            position += 2;

            if ( messageLen + sigLen + 2 + 4 > signedMessage.Length )
            {
                throw new ArgumentException( "The buffer contains invalid data - the embedded length data does not add up." );
            }

            dataBuffer = new SecureBuffer( new byte[messageLen], BufferType.Data );
            Array.Copy( signedMessage, position, dataBuffer.Buffer, 0, messageLen );
            position += messageLen;

            signatureBuffer = new SecureBuffer( new byte[sigLen], BufferType.Token );
            Array.Copy( signedMessage, position, signatureBuffer.Buffer, 0, sigLen );
            position += sigLen;

            using ( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
            {
                status = ContextNativeMethods.SafeVerifySignature(
                    this.ContextHandle,
                    0,
                    adapter,
                    0
                );
            }

            if ( status == SecurityStatus.OK )
            {
                origMessage = dataBuffer.Buffer;
                return true;
            }
            else if ( status == SecurityStatus.MessageAltered ||
                      status == SecurityStatus.OutOfSequence )
            {
                origMessage = null;
                return false;
            }
            else
            {
                throw new SspiException( "Failed to determine the veracity of a signed message.", status );
            }
        }
Example #4
0
        /// <summary>
        /// Signs the message using the context's session key.
        /// </summary>
        /// <remarks>
        /// The structure of the returned buffer is as follows:
        ///  - 4 bytes, unsigned big-endian integer indicating the length of the plaintext message
        ///  - 2 bytes, unsigned big-endian integer indicating the length of the signture
        ///  - The plaintext message
        ///  - The message's signature.
        /// </remarks>
        /// <param name="message"></param>
        /// <returns></returns>
        public byte[] MakeSignature( byte[] message )
        {
            SecurityStatus status = SecurityStatus.InternalError;

            SecPkgContext_Sizes sizes;
            SecureBuffer dataBuffer;
            SecureBuffer signatureBuffer;
            SecureBufferAdapter adapter;

            CheckLifecycle();

            sizes = QueryBufferSizes();

            dataBuffer = new SecureBuffer( new byte[message.Length], BufferType.Data );
            signatureBuffer = new SecureBuffer( new byte[sizes.MaxSignature], BufferType.Token );

            Array.Copy( message, dataBuffer.Buffer, message.Length );

            using ( adapter = new SecureBufferAdapter( new[] { dataBuffer, signatureBuffer } ) )
            {
                status = ContextNativeMethods.SafeMakeSignature(
                    this.ContextHandle,
                    0,
                    adapter,
                    0
                );
            }

            if ( status != SecurityStatus.OK )
            {
                throw new SspiException( "Failed to create message signature.", status );
            }

            byte[] outMessage;
            int position = 0;

            // Enough room for 
            //  - original message length (4 bytes)
            //  - signature length        (2 bytes)
            //  - original message
            //  - signature

            outMessage = new byte[4 + 2 + dataBuffer.Length + signatureBuffer.Length];

            ByteWriter.WriteInt32_BE( dataBuffer.Length, outMessage, position );
            position += 4;

            ByteWriter.WriteInt16_BE( (Int16)signatureBuffer.Length, outMessage, position );
            position += 2;

            Array.Copy( dataBuffer.Buffer, 0, outMessage, position, dataBuffer.Length );
            position += dataBuffer.Length;

            Array.Copy( signatureBuffer.Buffer, 0, outMessage, position, signatureBuffer.Length );
            position += signatureBuffer.Length;

            return outMessage;
        }
Example #5
0
        /// <summary>
        /// Decrypts a previously encrypted message.
        /// </summary>
        /// <remarks>
        /// The expected format of the buffer is as follows:
        ///  - 2 bytes, an unsigned big-endian integer indicating the length of the trailer buffer size
        ///  - 4 bytes, an unsigned big-endian integer indicating the length of the message buffer size.
        ///  - 2 bytes, an unsigned big-endian integer indicating the length of the encryption padding buffer size.
        ///  - The trailer buffer
        ///  - The message buffer
        ///  - The padding buffer.
        /// </remarks>
        /// <param name="input">The packed and encrypted data.</param>
        /// <returns>The original plaintext message.</returns>
        public byte[] Decrypt( byte[] input )
        {
            SecPkgContext_Sizes sizes;

            SecureBuffer trailerBuffer;
            SecureBuffer dataBuffer;
            SecureBuffer paddingBuffer;
            SecureBufferAdapter adapter;

            SecurityStatus status;
            byte[] result = null;
            int remaining;
            int position;

            int trailerLength;
            int dataLength;
            int paddingLength;

            CheckLifecycle();

            sizes = QueryBufferSizes();

            // This check is required, but not sufficient. We could be stricter.
            if( input.Length < 2 + 4 + 2 + sizes.SecurityTrailer )
            {
                throw new ArgumentException( "Buffer is too small to possibly contain an encrypted message" );
            }

            position = 0;

            trailerLength = ByteWriter.ReadInt16_BE( input, position );
            position += 2;

            dataLength = ByteWriter.ReadInt32_BE( input, position );
            position += 4;

            paddingLength = ByteWriter.ReadInt16_BE( input, position );
            position += 2;

            if ( trailerLength + dataLength + paddingLength + 2 + 4 + 2 > input.Length )
            {
                throw new ArgumentException( "The buffer contains invalid data - the embedded length data does not add up." );
            }

            trailerBuffer = new SecureBuffer( new byte[trailerLength], BufferType.Token );
            dataBuffer = new SecureBuffer( new byte[dataLength], BufferType.Data );
            paddingBuffer = new SecureBuffer( new byte[paddingLength], BufferType.Padding );

            remaining = input.Length - position;

            if( trailerBuffer.Length <= remaining )
            {
                Array.Copy( input, position, trailerBuffer.Buffer, 0, trailerBuffer.Length );
                position += trailerBuffer.Length;
                remaining -= trailerBuffer.Length;
            }
            else
            {
                throw new ArgumentException( "Input is missing data - it is not long enough to contain a fully encrypted message" );
            }
            
            if( dataBuffer.Length <= remaining )
            {
                Array.Copy( input, position, dataBuffer.Buffer, 0, dataBuffer.Length );
                position += dataBuffer.Length;
                remaining -= dataBuffer.Length;
            }
            else
            {
                throw new ArgumentException( "Input is missing data - it is not long enough to contain a fully encrypted message" );
            }

            if( paddingBuffer.Length <= remaining )
            {
                Array.Copy( input, position, paddingBuffer.Buffer, 0, paddingBuffer.Length );
            }
            // else there was no padding.
            

            using( adapter = new SecureBufferAdapter( new [] { trailerBuffer, dataBuffer, paddingBuffer } ) )
            {
                status = ContextNativeMethods.SafeDecryptMessage(
                    this.ContextHandle,
                    0,
                    adapter,
                    0
                );
            }

            if( status != SecurityStatus.OK )
            {
                throw new SspiException( "Failed to encrypt message", status );
            }

            result = new byte[dataBuffer.Length];
            Array.Copy( dataBuffer.Buffer, 0, result, 0, dataBuffer.Length );

            return result;
        }
Example #6
0
        /// <summary>
        /// Encrypts the byte array using the context's session key.
        /// </summary>
        /// <remarks>
        /// The structure of the returned data is as follows:
        ///  - 2 bytes, an unsigned big-endian integer indicating the length of the trailer buffer size
        ///  - 4 bytes, an unsigned big-endian integer indicating the length of the message buffer size.
        ///  - 2 bytes, an unsigned big-endian integer indicating the length of the encryption padding buffer size.
        ///  - The trailer buffer
        ///  - The message buffer
        ///  - The padding buffer.
        /// </remarks>
        /// <param name="input">The raw message to encrypt.</param>
        /// <returns>The packed and encrypted message.</returns>
        public byte[] Encrypt( byte[] input )
        {
            // The message is encrypted in place in the buffer we provide to Win32 EncryptMessage
            SecPkgContext_Sizes sizes;

            SecureBuffer trailerBuffer;
            SecureBuffer dataBuffer;
            SecureBuffer paddingBuffer;
            SecureBufferAdapter adapter;

            SecurityStatus status = SecurityStatus.InvalidHandle;
            byte[] result;

            CheckLifecycle();

            sizes = QueryBufferSizes();

            trailerBuffer = new SecureBuffer( new byte[sizes.SecurityTrailer], BufferType.Token );
            dataBuffer = new SecureBuffer( new byte[input.Length], BufferType.Data );
            paddingBuffer = new SecureBuffer( new byte[sizes.BlockSize], BufferType.Padding );

            Array.Copy( input, dataBuffer.Buffer, input.Length );

            using( adapter = new SecureBufferAdapter( new[] { trailerBuffer, dataBuffer, paddingBuffer } ) )
            {
                status = ContextNativeMethods.SafeEncryptMessage(
                    this.ContextHandle,
                    0,
                    adapter,
                    0
                );
            }

            if( status != SecurityStatus.OK )
            {
                throw new SspiException( "Failed to encrypt message", status );
            }

            int position = 0;

            // Enough room to fit:
            //  -- 2 bytes for the trailer buffer size
            //  -- 4 bytes for the message size
            //  -- 2 bytes for the padding size.
            //  -- The encrypted message
            result = new byte[2 + 4 + 2 + trailerBuffer.Length + dataBuffer.Length + paddingBuffer.Length];

            ByteWriter.WriteInt16_BE( (short)trailerBuffer.Length, result, position );
            position += 2;

            ByteWriter.WriteInt32_BE( dataBuffer.Length, result, position );
            position += 4;

            ByteWriter.WriteInt16_BE( (short)paddingBuffer.Length, result, position );
            position += 2;

            Array.Copy( trailerBuffer.Buffer, 0, result, position, trailerBuffer.Length );
            position += trailerBuffer.Length;

            Array.Copy( dataBuffer.Buffer, 0, result, position, dataBuffer.Length );
            position += dataBuffer.Length;

            Array.Copy( paddingBuffer.Buffer, 0, result, position, paddingBuffer.Length );
            position += paddingBuffer.Length;
                        
            return result;
        }
        /// <summary>
        /// Performs and continues the authentication cycle.
        /// </summary>
        /// <remarks>
        /// This method is performed iteratively to start, continue, and end the authentication cycle with the
        /// server. Each stage works by acquiring a token from one side, presenting it to the other side
        /// which in turn may generate a new token.
        /// 
        /// The cycle typically starts and ends with the client. On the first invocation on the client,
        /// no server token exists, and null is provided in its place. The client returns its status, providing
        /// its output token for the server. The server accepts the clients token as input and provides a 
        /// token as output to send back to the client. This cycle continues until the server and client 
        /// both indicate, typically, a SecurityStatus of 'OK'.
        /// </remarks>
        /// <param name="serverToken">The most recently received token from the server, or null if beginning
        /// the authentication cycle.</param>
        /// <param name="outToken">The clients next authentication token in the authentication cycle.</param>
        /// <returns>A status message indicating the progression of the authentication cycle.
        /// A status of 'OK' indicates that the cycle is complete, from the client's perspective. If the outToken
        /// is not null, it must be sent to the server.
        /// A status of 'Continue' indicates that the output token should be sent to the server and 
        /// a response should be anticipated.</returns>
        public SecurityStatus Init( byte[] serverToken, out byte[] outToken )
        {
            TimeStamp rawExpiry = new TimeStamp();

            SecurityStatus status;

            SecureBuffer outTokenBuffer;
            SecureBufferAdapter outAdapter;

            SecureBuffer serverBuffer;
            SecureBufferAdapter serverAdapter;
            
            if( this.Disposed )
            {
                throw new ObjectDisposedException( "ClientContext" );
            }
            else if( ( serverToken != null ) && ( this.ContextHandle.IsInvalid ) )
            {
                throw new InvalidOperationException( "Out-of-order usage detected - have a server token, but no previous client token had been created." );
            }
            else if( ( serverToken == null ) && ( this.ContextHandle.IsInvalid == false ) )
            {
                throw new InvalidOperationException( "Must provide the server's response when continuing the init process." );
            }
            
            // The security package tells us how big its biggest token will be. We'll allocate a buffer
            // that size, and it'll tell us how much it used.
            outTokenBuffer = new SecureBuffer( 
                new byte[ this.Credential.PackageInfo.MaxTokenLength ], 
                BufferType.Token 
            );

            serverBuffer = null;
            if ( serverToken != null )
            {
                serverBuffer = new SecureBuffer( serverToken, BufferType.Token );
            }

            // Some notes on handles and invoking InitializeSecurityContext
            //  - The first time around, the phContext parameter (the 'old' handle) is a null pointer to what 
            //    would be an RawSspiHandle, to indicate this is the first time it's being called. 
            //    The phNewContext is a pointer (reference) to an RawSspiHandle struct of where to write the 
            //    new handle's values.
            //  - The next time you invoke ISC, it takes a pointer to the handle it gave you last time in phContext,
            //    and takes a pointer to where it should write the new handle's values in phNewContext.
            //  - After the first time, you can provide the same handle to both parameters. From MSDN:
            //       "On the second call, phNewContext can be the same as the handle specified in the phContext
            //        parameter."
            //    It will overwrite the handle you gave it with the new handle value.
            //  - All handle structures themselves are actually *two* pointer variables, eg, 64 bits on 32-bit 
            //    Windows, 128 bits on 64-bit Windows.
            //  - So in the end, on a 64-bit machine, we're passing a 64-bit value (the pointer to the struct) that
            //    points to 128 bits of memory (the struct itself) for where to write the handle numbers.
            using ( outAdapter = new SecureBufferAdapter( outTokenBuffer ) )
            {
                if ( this.ContextHandle.IsInvalid )
                {
                    status = ContextNativeMethods.InitializeSecurityContext_1(
                        ref this.Credential.Handle.rawHandle,
                        IntPtr.Zero,
                        this.serverPrinc,
                        this.requestedAttribs,
                        0,
                        SecureBufferDataRep.Network,
                        IntPtr.Zero,
                        0,
                        ref this.ContextHandle.rawHandle,
                        outAdapter.Handle,
                        ref this.finalAttribs,
                        ref rawExpiry
                    );
                }
                else
                {
                    using ( serverAdapter = new SecureBufferAdapter( serverBuffer ) )
                    {
                        status = ContextNativeMethods.InitializeSecurityContext_2(
                            ref this.Credential.Handle.rawHandle,
                            ref this.ContextHandle.rawHandle,
                            this.serverPrinc,
                            this.requestedAttribs,
                            0,
                            SecureBufferDataRep.Network,
                            serverAdapter.Handle,
                            0,
                            ref this.ContextHandle.rawHandle,
                            outAdapter.Handle,
                            ref this.finalAttribs,
                            ref rawExpiry
                        );
                    }
                }
            }

            if( status.IsError() == false )
            {
                if( status == SecurityStatus.OK )
                {
                    base.Initialize( rawExpiry.ToDateTime() );
                }

                outToken = null;

                if( outTokenBuffer.Length != 0 )
                {
                    outToken = new byte[outTokenBuffer.Length];
                    Array.Copy( outTokenBuffer.Buffer, outToken, outToken.Length );
                }
            }
            else
            {
                throw new SspiException( "Failed to invoke InitializeSecurityContext for a client", status );
            }

            return status;
        }