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