예제 #1
0
        /// <summary>
        /// Queries the security package's expections regarding message/token/signature/padding buffer sizes.
        /// </summary>
        /// <returns></returns>
        private SecPkgContext_Sizes QueryBufferSizes()
        {
            SecPkgContext_Sizes sizes  = new SecPkgContext_Sizes();
            SecurityStatus      status = SecurityStatus.InternalError;
            bool gotRef = false;

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                this.ContextHandle.DangerousAddRef(ref gotRef);
            }
            catch (Exception)
            {
                if (gotRef)
                {
                    this.ContextHandle.DangerousRelease();
                    gotRef = false;
                }

                throw;
            }
            finally
            {
                if (gotRef)
                {
                    status = ContextNativeMethods.QueryContextAttributes_Sizes(
                        ref this.ContextHandle.rawHandle,
                        ContextQueryAttrib.Sizes,
                        ref sizes
                        );
                    this.ContextHandle.DangerousRelease();
                }
            }

            if (status != SecurityStatus.OK)
            {
                throw new SSPIException("Failed to query context buffer size attributes", status);
            }

            return(sizes);
        }
예제 #2
0
        /// <summary>
        /// Safely invokes the native DecryptMessage function, making sure that handle ref counting is
        /// performed in a proper CER.
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="qualityOfProtection"></param>
        /// <param name="bufferAdapter"></param>
        /// <param name="sequenceNumber"></param>
        /// <returns></returns>
        internal static SecurityStatus SafeDecryptMessage(
            SafeContextHandle handle,
            int qualityOfProtection,
            SecureBufferAdapter bufferAdapter,
            int sequenceNumber)
        {
            SecurityStatus status = SecurityStatus.InvalidHandle;
            bool           gotRef = false;

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                handle.DangerousAddRef(ref gotRef);
            }
            catch (Exception)
            {
                if (gotRef)
                {
                    handle.DangerousRelease();
                    gotRef = false;
                }

                throw;
            }
            finally
            {
                if (gotRef)
                {
                    status = ContextNativeMethods.DecryptMessage(
                        ref handle.rawHandle,
                        bufferAdapter.Handle,
                        sequenceNumber,
                        qualityOfProtection
                        );

                    handle.DangerousRelease();
                }
            }

            return(status);
        }
예제 #3
0
        /// <summary>
        /// Called by the ImpersonationHandle when it is released, either by disposale or finalization.
        /// </summary>
        internal void RevertImpersonate()
        {
            bool gotRef = false;

            if (impersonating == false || this.Disposed)
            {
                return;
            }

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                this.ContextHandle.DangerousAddRef(ref gotRef);
            }
            catch (Exception)
            {
                if (gotRef)
                {
                    this.ContextHandle.DangerousRelease();
                    gotRef = false;
                }

                throw;
            }
            finally
            {
                if (gotRef)
                {
                    ContextNativeMethods.RevertSecurityContext(
                        ref this.ContextHandle.rawHandle
                        );

                    this.ContextHandle.DangerousRelease();

                    this.impersonating = false;
                }
            }
        }
예제 #4
0
        internal static SecurityStatus SafeQueryContextAttribute(
            SafeContextHandle handle,
            ContextQueryAttrib attribute,
            ref byte[] buffer
            )
        {
            bool gotRef = false;

            SecurityStatus status = SecurityStatus.InternalError;

            RuntimeHelpers.PrepareConstrainedRegions();

            int    pointerSize  = System.Environment.Is64BitOperatingSystem ? 8 : 4; //NOTE: update this when 128 bit processors exist
            IntPtr alloc_buffer = Marshal.AllocHGlobal(sizeof(uint) + pointerSize);  //NOTE: this is at most 4 + sizeof(void*) bytes

            //see struct SecPkgContext_SessionKey
            // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380096(v=vs.85).aspx
            try
            {
                handle.DangerousAddRef(ref gotRef);
            }
            catch (Exception)
            {
                if (gotRef)
                {
                    handle.DangerousRelease();
                    gotRef = false;
                    buffer = null;
                }

                throw;
            }
            finally
            {
                if (gotRef)
                {
                    status = ContextNativeMethods.QueryContextAttributes(
                        ref handle.rawHandle,
                        attribute,
                        alloc_buffer
                        );
                    if (status == SecurityStatus.OK)
                    {
                        KeyStruct key = new KeyStruct();

                        Marshal.PtrToStructure(alloc_buffer, key);   // fit to the proper size, read a byte[]

                        byte[] sizedBuffer = new byte[key.size];

                        for (int i = 0; i < key.size; i++)
                        {
                            sizedBuffer[i] = Marshal.ReadByte(key.data, i);
                        }

                        buffer = sizedBuffer;
                    }
                    handle.DangerousRelease();
                }
            }
            Marshal.FreeHGlobal(alloc_buffer);
            return(status);
        }
예제 #5
0
        /// <summary>
        /// Performs and continues the authentication cycle.
        /// </summary>
        /// <remarks>
        /// This method is performed iteratively to continue and end the authentication cycle with the
        /// client. 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="clientToken">The most recently received token from the client.</param>
        /// <param name="nextToken">The servers next authentication token in the cycle, that must
        /// be sent to the client.</param>
        /// <returns>A status message indicating the progression of the authentication cycle.
        /// A status of 'OK' indicates that the cycle is complete, from the servers's perspective. If the nextToken
        /// is not null, it must be sent to the client.
        /// A status of 'Continue' indicates that the output token should be sent to the client and
        /// a response should be anticipated.</returns>
        public SecurityStatus AcceptToken(byte[] clientToken, out byte[] nextToken)
        {
            SecureBuffer clientBuffer;
            SecureBuffer outBuffer;

            SecurityStatus status;
            TimeStamp      rawExpiry = new TimeStamp();

            SecureBufferAdapter clientAdapter;
            SecureBufferAdapter outAdapter;

            if (this.Disposed)
            {
                throw new ObjectDisposedException("ServerContext");
            }
            else if (this.Initialized)
            {
                throw new InvalidOperationException(
                          "Attempted to continue initialization of a ServerContext after initialization had completed."
                          );
            }

            clientBuffer = new SecureBuffer(clientToken, BufferType.Token);

            outBuffer = new SecureBuffer(
                new byte[this.Credential.PackageInfo.MaxTokenLength],
                BufferType.Token
                );

            using (clientAdapter = new SecureBufferAdapter(clientBuffer))
            {
                using (outAdapter = new SecureBufferAdapter(outBuffer))
                {
                    if (this.ContextHandle.IsInvalid)
                    {
                        status = ContextNativeMethods.AcceptSecurityContext_1(
                            ref this.Credential.Handle.rawHandle,
                            IntPtr.Zero,
                            clientAdapter.Handle,
                            requestedAttribs,
                            SecureBufferDataRep.Network,
                            ref this.ContextHandle.rawHandle,
                            outAdapter.Handle,
                            ref this.finalAttribs,
                            ref rawExpiry
                            );
                    }
                    else
                    {
                        status = ContextNativeMethods.AcceptSecurityContext_2(
                            ref this.Credential.Handle.rawHandle,
                            ref this.ContextHandle.rawHandle,
                            clientAdapter.Handle,
                            requestedAttribs,
                            SecureBufferDataRep.Network,
                            ref this.ContextHandle.rawHandle,
                            outAdapter.Handle,
                            ref this.finalAttribs,
                            ref rawExpiry
                            );
                    }
                }
            }

            if (status == SecurityStatus.OK)
            {
                nextToken = null;

                base.Initialize(rawExpiry.ToDateTime());

                if (outBuffer.Length != 0)
                {
                    nextToken = new byte[outBuffer.Length];
                    Array.Copy(outBuffer.Buffer, nextToken, nextToken.Length);
                }
                else
                {
                    nextToken = null;
                }
            }
            else if (status == SecurityStatus.ContinueNeeded)
            {
                nextToken = new byte[outBuffer.Length];
                Array.Copy(outBuffer.Buffer, nextToken, nextToken.Length);
            }
            else
            {
                throw new SSPIException("Failed to call AcceptSecurityContext", status);
            }

            return(status);
        }
예제 #6
0
        /// <summary>
        /// Changes the current thread's security context to impersonate the user of the client.
        /// </summary>
        /// <remarks>
        /// Requires that the security package provided with the server's credentials, as well as the
        /// client's credentials, support impersonation.
        ///
        /// Currently, only one thread may initiate impersonation per security context. Impersonation may
        /// follow threads created by the initial impersonation thread, however.
        /// </remarks>
        /// <returns>A handle to capture the lifetime of the impersonation. Dispose the handle to revert
        /// impersonation. If the handle is leaked, the impersonation will automatically revert at a
        /// non-deterministic time when the handle is finalized by the Garbage Collector.</returns>
        public ImpersonationHandle ImpersonateClient()
        {
            ImpersonationHandle handle;
            SecurityStatus      status = SecurityStatus.InternalError;
            bool gotRef = false;

            if (this.Disposed)
            {
                throw new ObjectDisposedException("ServerContext");
            }
            else if (this.Initialized == false)
            {
                throw new InvalidOperationException(
                          "The server context has not been completely initialized."
                          );
            }
            else if (impersonating)
            {
                throw new InvalidOperationException("Cannot impersonate again while already impersonating.");
            }
            else if (this.SupportsImpersonate == false)
            {
                throw new InvalidOperationException(
                          "The ServerContext is using a security package that does not support impersonation."
                          );
            }

            handle = new ImpersonationHandle(this);
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                this.ContextHandle.DangerousAddRef(ref gotRef);
            }
            catch (Exception)
            {
                if (gotRef)
                {
                    this.ContextHandle.DangerousRelease();
                    gotRef = false;
                }

                throw;
            }
            finally
            {
                if (gotRef)
                {
                    status = ContextNativeMethods.ImpersonateSecurityContext(
                        ref this.ContextHandle.rawHandle
                        );

                    this.ContextHandle.DangerousRelease();

                    this.impersonating = status == SecurityStatus.OK;
                }
            }

            if (status == SecurityStatus.NoImpersonation)
            {
                throw new SSPIException("Impersonation could not be performed.", status);
            }
            else if (status == SecurityStatus.Unsupported)
            {
                throw new SSPIException("Impersonation is not supported by the security context's Security Support Provider.", status);
            }
            else if (status != SecurityStatus.OK)
            {
                throw new SSPIException("Failed to impersonate the client", status);
            }

            if (this.impersonating && this.impersonationSetsThreadPrinciple)
            {
                SetThreadPrinciple();
            }

            return(handle);
        }
예제 #7
0
        /// <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);
        }
예제 #8
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);
            }
        }
예제 #9
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);
        }
예제 #10
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);
        }