/// <summary> /// Decrypts the encrypted message and returns decrypted message. /// Schannel is not supported. /// </summary> /// <param name="messageToBeDecrypted">Message to be decrypted.</param> /// <param name="signature">Signature of the message, for windows sspi, signature can't be null.</param> /// <returns>Decrypted message</returns> public byte[] Decrypt(byte[] messageToBeDecrypted, byte[] signature) { SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, messageToBeDecrypted); SecurityBuffer tokenBuffer = new SecurityBuffer(SecurityBufferType.Token, signature); Decrypt(messageBuffer, tokenBuffer); return messageBuffer.Buffer; }
/// <summary> /// Encrypts the message and returns another byte array containing encrypted message. /// Schannel is not supported. /// </summary> /// <param name="messageToBeEncrypted">Message to be encrypted.</param> /// <param name="signature">Generated signature</param> /// <returns>Encrypted message</returns> public byte[] Encrypt(byte[] messageToBeEncrypted, out byte[] signature) { SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, messageToBeEncrypted); SecurityBuffer tokenBuffer = new SecurityBuffer( SecurityBufferType.Token, new byte[NativeMethods.MAX_TOKEN_SIZE]); Encrypt(messageBuffer, tokenBuffer); signature = tokenBuffer.Buffer; return messageBuffer.Buffer; }
/// <summary> /// Writes a sequence of bytes to the current stream. The data is encrypted first before sending out. /// </summary> /// <param name="buffer">The buffer to be sent.</param> /// <param name="offset">The offset in buffer at which to begin writing to the stream.</param> /// <param name="count">The number of bytes to be written.</param> /// <exception cref="IOException">Raised when attempting to read from/write to a remote connection which /// has been closed</exception> /// <exception cref="ArgumentOutOfRangeException">Raised when the offset incremented by count exceeds /// the length of buffer</exception> public override void Write(byte[] buffer, int offset, int count) { if (offset > buffer.Length - 1) { throw new ArgumentOutOfRangeException("offset"); } if (offset + count > buffer.Length) { throw new ArgumentOutOfRangeException("count"); } byte[] outBuffer = new byte[count]; Array.Copy(buffer, offset, outBuffer, 0, count); // Encrypt message SecurityPackageContextStreamSizes streamSizes = (SecurityPackageContextStreamSizes)context.QueryContextAttributes("SECPKG_ATTR_STREAM_SIZES"); SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, buffer); SecurityBuffer headerBuffer = new SecurityBuffer( SecurityBufferType.StreamHeader, new byte[streamSizes.Header]); SecurityBuffer trailerBuffer = new SecurityBuffer( SecurityBufferType.StreamTrailer, new byte[streamSizes.Trailer]); SecurityBuffer emptyBuffer = new SecurityBuffer(SecurityBufferType.Empty, null); context.Encrypt(headerBuffer, messageBuffer, trailerBuffer, emptyBuffer); byte[] encryptedMsg = ArrayUtility.ConcatenateArrays( headerBuffer.Buffer, messageBuffer.Buffer, trailerBuffer.Buffer); clientStream.Write(encryptedMsg, 0, encryptedMsg.Length); }
/// <summary> /// Reads a sequence of bytes from the current stream. /// </summary> /// <param name="buffer">The buffer that contains decrypted data.</param> /// <param name="offset">The offset in buffer at which to begin storing the decrypted data.</param> /// <param name="count">The maximum number of bytes to get.</param> /// <exception cref="ArgumentOutOfRangeException">Raised when buffer or the internal decryptedBuffer doesn't /// contain enough space /// </exception> /// <exception cref="IOException">Raised when attempting to read from/write to a remote connection which /// has been closed</exception> /// <returns>The actual number of bytes read into the buffer. Could be less than count</returns> public override int Read(byte[] buffer, int offset, int count) { if (offset > buffer.Length - 1) { throw new ArgumentOutOfRangeException("offset"); } if (offset + count > buffer.Length) { throw new ArgumentOutOfRangeException("count"); } if (!IsBufferEmpty()) { if (CheckAvailableCount(count)) { Array.Copy(decryptedBuffer, this.startIndex, buffer, offset, count); this.startIndex += count; return count; } else { int sizeRead = this.endIndex - this.startIndex; Array.Copy(decryptedBuffer, this.startIndex, buffer, offset, sizeRead); // All data is read, reset indices this.startIndex = 0; this.endIndex = 0; return sizeRead; } } byte[] encryptedMsg = null; int bytesReceived = 0; byte[] decryptedMsg = null; while (decryptedMsg == null) { // decryptedMsg being null indicates incomplete data, so we continue reading and decrypting. bytesReceived = clientStream.Read(recvBuffer, 0, recvBuffer.Length); // The connection has been closed by remote server if (bytesReceived == 0) { return 0; } // There's pooled data, concatenate the buffer together for decryption if (this.pooledBuffer != null && this.pooledBuffer.Length > 0) { encryptedMsg = new byte[this.pooledBuffer.Length + bytesReceived]; Array.Copy(this.pooledBuffer, encryptedMsg, this.pooledBuffer.Length); Array.Copy(recvBuffer, 0, encryptedMsg, this.pooledBuffer.Length, bytesReceived); this.pooledBuffer = null; } else { encryptedMsg = new byte[bytesReceived]; Array.Copy(recvBuffer, encryptedMsg, bytesReceived); } byte[] extraData = null; // Do decryption SecurityBuffer[] securityBuffers = new SecurityBuffer[] { new SecurityBuffer(SecurityBufferType.Data, encryptedMsg), new SecurityBuffer(SecurityBufferType.Empty, null), new SecurityBuffer(SecurityBufferType.Empty, null), new SecurityBuffer(SecurityBufferType.Empty, null) }; context.Decrypt(securityBuffers); for (int i = 0; i < securityBuffers.Length; i++) { if (securityBuffers[i].BufferType == SecurityBufferType.Data) { decryptedMsg = ArrayUtility.ConcatenateArrays(decryptedMsg, securityBuffers[i].Buffer); } else if (securityBuffers[i].BufferType == SecurityBufferType.Extra) { extraData = ArrayUtility.ConcatenateArrays(extraData, securityBuffers[i].Buffer); } } if (extraData != null && extraData.Length > 0) { this.pooledBuffer = extraData; } } Array.Copy(decryptedMsg, 0, this.decryptedBuffer, this.endIndex, decryptedMsg.Length); this.endIndex += decryptedMsg.Length; return Read(buffer, offset, count); }
/// <summary> /// validate netlogon signature token when AES is not negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffer, contains plain-text if requestConfidentiality is false; /// or cipher-text if requestConfidentiality is true; /// and signature. /// </param> /// <returns>true if validate success; otherwise, false.</returns> private static bool ValidateNetlogonSignatureTokenWhenAesIsNotNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; //If AES is not negotiated, a server receives a NL_AUTH_SIGNATURE structure. token = SspiUtility.ConcatenateSecurityBuffers(securityBuffers, SecurityBufferType.Token); NL_AUTH_SIGNATURE nlAuthSign = TypeMarshal.ToStruct<NL_AUTH_SIGNATURE>(token); //The SignatureAlgorithm bytes MUST be verified to ensure: //If AES is not negotiated, the first byte is set to 0x77. //The second byte is set to 0x00 if (nlAuthSign.SignatureAlgorithm != SignatureAlgorithm_Values.HMACMD5) { return false; } if (requestConfidentiality) { //If the Confidentiality option is requested from the application, //then the SealAlgorithm MUST be verified to ensure that //if AES is not negotiated, the first byte is set to 0x7A. //The second byte is set to 0x00. if (nlAuthSign.SealAlgorithm != SealAlgorithm_Values.RC4) { return false; } } else { //If the Confidentiality option is not requested, //then the SealAlgorithm MUST be verified to contain all 0xff bytes. if (nlAuthSign.SealAlgorithm != SealAlgorithm_Values.NotEncrypted) { return false; } } //The Pad MUST be verified to contain all 0xff bytes. if (nlAuthSign.Pad != Pad_Values.V1) { return false; } //The Flags data MAY be<86> disregarded. //The SequenceNumber MUST be decrypted. //If AES is not negotiated, then the server MUST use the RC4 algorithm. //The RC4key MUST be derived as follows. //SET zeroes to 4 bytes of 0 //CALL hmac_md5(zeroes, [4 bytes], SessionKey, size of SessionKey, TmpData) //CALL hmac_md5(Checksum, size of Checksum, TmpData, size of TmpData, DecryptionKey) byte[] tmpData; byte[] sequenceNumberDecryptionKey; using (HMACMD5 hmacMd5 = new HMACMD5(sessionKey)) { tmpData = hmacMd5.ComputeHash(new byte[4]); } using (HMACMD5 hmacMd5 = new HMACMD5(tmpData)) { sequenceNumberDecryptionKey = hmacMd5.ComputeHash(nlAuthSign.Checksum); } using (RC4 rc4 = RC4.Create()) { rc4.Key = sequenceNumberDecryptionKey; nlAuthSign.SequenceNumber = rc4.CreateDecryptor().TransformFinalBlock( nlAuthSign.SequenceNumber, 0, nlAuthSign.SequenceNumber.Length); } //A local copy of SequenceNumber MUST be computed using the following algorithm. byte[] copySequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The SequenceNumber MUST be compared to CopySeqNumber. if (!ArrayUtility.CompareArrays( copySequenceNumber, nlAuthSign.SequenceNumber)) { return false; } //ServerSequenceNumber MUST be incremented. sequenceNumber += 1; //If the Confidentiality option is requested, //the Confounder and the data MUST be decrypted. if (requestConfidentiality) { byte[] rc4DecryptionKey = ComputeEncryptionKeyWhenAesIsNotNegotiated( sessionKey, nlAuthSign.SequenceNumber); using (RC4 rc4 = RC4.Create()) { rc4.Key = rc4DecryptionKey; nlAuthSign.Confounder = rc4.CreateDecryptor().TransformFinalBlock( nlAuthSign.Confounder, 0, nlAuthSign.Confounder.Length); cipherText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); plainText = rc4.CreateDecryptor().TransformFinalBlock( cipherText, 0, cipherText.Length); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, plainText); } //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); byte[] checksum = ComputeSignatureWhenAesIsNotNegotiated( nlAuthSign, sessionKey, requestConfidentiality, plainText); //The first 8 bytes of the computed signature MUST be compared to the checksum. if (checksum != null && checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH && nlAuthSign.Checksum != null && nlAuthSign.Checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH) { for (int i = 0; i < NL_AUTH_SIGNATURE_CHECKSUM_LENGTH; i++) { if (checksum[i] != nlAuthSign.Checksum[i]) { return false; } } } return true; }
/// <summary> /// Decrypt data /// </summary> /// <param name="data"></param> /// <returns></returns> public byte[] Decrypt(byte[] data) { if (data == null || data.Length == 0) { return null; } byte[] dtlsDataBuffer = new byte[data.Length]; Array.Copy(data, dtlsDataBuffer, data.Length); SecurityBuffer dataBuffer = new SecurityBuffer(SecurityBufferType.Data, dtlsDataBuffer); SecurityBuffer emptyBuffer1 = new SecurityBuffer(SecurityBufferType.Empty, null); SecurityBuffer emptyBuffer2 = new SecurityBuffer(SecurityBufferType.Empty, null); SecurityBuffer emptyBuffer3 = new SecurityBuffer(SecurityBufferType.Empty, null); if (dtlsServerContext != null) { dtlsServerContext.Decrypt(dataBuffer, emptyBuffer1, emptyBuffer2, emptyBuffer3); } else { dtlsClientContext.Decrypt(dataBuffer, emptyBuffer1, emptyBuffer2, emptyBuffer3); } SecurityBuffer decryptedDataBuffer = (dataBuffer.BufferType == SecurityBufferType.Data) ? dataBuffer : (emptyBuffer1.BufferType == SecurityBufferType.Data) ? emptyBuffer1 : (emptyBuffer2.BufferType == SecurityBufferType.Data) ? emptyBuffer2 : (emptyBuffer3.BufferType == SecurityBufferType.Data) ? emptyBuffer3 : null; if (decryptedDataBuffer != null) { return decryptedDataBuffer.Buffer; } else { return null; } }
public override void Initialize(byte[] serverToken) { uint hResult; SecurityBuffer[] securityBuffers; if (this.packageType == SecurityPackageType.CredSsp) { //On calls to this function after the initial call, there must be two buffers. securityBuffers = new SecurityBuffer[2]; //The first has type SECBUFFER_TOKEN and contains the token received from the server. securityBuffers[0] = new SecurityBuffer(SecurityBufferType.Token, serverToken); //The second buffer has type SECBUFFER_EMPTY; set both the pvBuffer and cbBuffer members to zero. securityBuffers[1] = new SecurityBuffer(SecurityBufferType.Empty, new byte[0]); } else { securityBuffers = new SecurityBuffer[] { new SecurityBuffer(SecurityBufferType.Token, serverToken) }; } SecurityBuffer outTokenBuffer = new SecurityBuffer( SecurityBufferType.Token, new byte[NativeMethods.MAX_TOKEN_SIZE]); SecurityBufferDescWrapper serverTokenDescWrapper = new SecurityBufferDescWrapper(securityBuffers); SecurityBufferDescWrapper outBufferDescWrapper = new SecurityBufferDescWrapper(outTokenBuffer); uint outContextAttribute; SecurityInteger expiryTime = new SecurityInteger(); if (serverToken == null) { hResult = NativeMethods.InitializeSecurityContext( ref this.credentialHandle, IntPtr.Zero, this.serverPrincipalName, (int)this.securityContextAttributes, 0, (int)this.targetDataRepresentaion, IntPtr.Zero, 0, out this.contextHandle, out outBufferDescWrapper.securityBufferDesc, out outContextAttribute, out expiryTime); } else { if (this.contextHandle.LowPart == IntPtr.Zero && this.contextHandle.HighPart == IntPtr.Zero) { hResult = NativeMethods.InitializeSecurityContext( ref this.credentialHandle, IntPtr.Zero, this.serverPrincipalName, (int)this.securityContextAttributes, 0, (int)this.targetDataRepresentaion, ref serverTokenDescWrapper.securityBufferDesc, 0, out this.contextHandle, out outBufferDescWrapper.securityBufferDesc, out outContextAttribute, out expiryTime); } else { hResult = NativeMethods.InitializeSecurityContext( ref this.credentialHandle, ref this.contextHandle, this.serverPrincipalName, (int)this.securityContextAttributes, 0, (int)this.targetDataRepresentaion, ref serverTokenDescWrapper.securityBufferDesc, 0, out this.contextHandle, out outBufferDescWrapper.securityBufferDesc, out outContextAttribute, out expiryTime); } } serverTokenDescWrapper.FreeSecurityBufferDesc(); if (hResult == NativeMethods.SEC_E_OK) { this.needContinueProcessing = false; } else if (hResult == NativeMethods.SEC_I_CONTINUE_NEEDED) { this.needContinueProcessing = true; } else { throw new SspiException("Initialize failed.", hResult); } //Get token if success. this.token = null; SspiSecurityBuffer[] outBuffers = outBufferDescWrapper.securityBufferDesc.GetBuffers(); for (int i = 0; i < outBuffers.Length; i++) { if (outBuffers[i].bufferType == (uint)SecurityBufferType.Token) { if (outBuffers[i].bufferLength > 0) { this.token = new byte[outBuffers[i].bufferLength]; Marshal.Copy(outBuffers[i].pSecBuffer, this.token, 0, this.token.Length); } break; } } outBufferDescWrapper.FreeSecurityBufferDesc(); }
/// <summary> /// This takes the given byte array and verifies it using SSPI VerifySignature method. /// </summary> /// <param name="messageToBeVerified">Signed message to be verified.</param> /// <param name="signature">The signature of the message.</param> /// <exception cref="SspiException">If verify fail, this exception will be thrown.</exception> public bool Verify(byte[] messageToBeVerified, byte[] signature) { SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, messageToBeVerified); SecurityBuffer signatureBuffer = new SecurityBuffer(SecurityBufferType.Token, signature); return Verify(messageBuffer, signatureBuffer); }
/// <summary> /// Concatenate security buffers for computing checksum. /// </summary> /// <param name="securityBuffers">The security buffers.</param> /// <returns>Concatenated security buffers.</returns> private static byte[] ConcatenateSecurityBuffersForChecksum(SecurityBuffer[] securityBuffers) { byte[] buf = new byte[0]; for (int i = 0; i < securityBuffers.Length; i++) { SecurityBufferType securityBufferType = securityBuffers[i].BufferType; if ((securityBufferType & ~SecurityBufferType.AttrMask) == SecurityBufferType.Data && (securityBufferType & SecurityBufferType.ReadOnly) == 0 && securityBuffers[i].Buffer != null) { buf = ArrayUtility.ConcatenateArrays(buf, securityBuffers[i].Buffer); } } return buf; }
/// <summary> /// Concatenate all read-write (READONLY flag is not set) buffers of a specified type in the list. /// </summary> /// <param name="securityBuffers">Input security buffers.</param> /// <param name="targetTypes">Specified types.</param> /// <returns>A single byte array with all buffers concatenated.</returns> /// <exception cref="ArgumentNullException"> /// Thrown when securityBuffers is null. /// </exception> public static byte[] ConcatenateReadWriteSecurityBuffers( SecurityBuffer[] securityBuffers, params SecurityBufferType[] targetTypes) { if (securityBuffers == null || securityBuffers.Length == 0) { throw new ArgumentNullException("securityBuffers"); } for (int i = 0; i < securityBuffers.Length; i++) { if (securityBuffers[i] == null) { throw new ArgumentNullException("securityBuffers"); } } return ConcatenateSecurityBuffers(securityBuffers, targetTypes, false); }
/// <summary> /// Update buffers of a specified type in the list. /// Buffer will be separated automatically to fit the original length of a security buffer. /// If Buffer field of an input security buffer is null, it means the length is unlimited /// (that is all remaining data will be copied into it). /// Only read-write (READONLY flag is not set) security buffer will be updated. /// </summary> /// <param name="securityBuffers">Input security buffers.</param> /// <param name="targetTypes">Specified types.</param> /// <param name="buffer">The buffer to be updated into security buffers.</param> /// <exception cref="ArgumentNullException"> /// Thrown when securityBuffers or buffer is null. /// </exception> /// <exception cref="SspiException"> /// Total length of security buffers is not enough. /// </exception> public static void UpdateSecurityBuffers(SecurityBuffer[] securityBuffers, SecurityBufferType[] targetTypes, byte[] buffer) { if (securityBuffers == null || securityBuffers.Length == 0) { throw new ArgumentNullException("securityBuffers"); } for (int i = 0; i < securityBuffers.Length; i++) { if (securityBuffers[i] == null) { throw new ArgumentNullException("securityBuffers"); } } if (buffer == null) { throw new ArgumentNullException("buffer"); } int offset = 0; for (int i = 0; i < securityBuffers.Length; i++) { SecurityBufferType securityBufferType = (securityBuffers[i].BufferType & ~SecurityBufferType.AttrMask); bool isReadOnly = ((securityBuffers[i].BufferType & SecurityBufferType.ReadOnly) != 0) || ((securityBuffers[i].BufferType & SecurityBufferType.ReadOnlyWithChecksum) != 0); bool typeMatch = false; for (int j = 0; j < targetTypes.Length; j++) { if (securityBufferType == targetTypes[j]) { typeMatch = true; break; } } if (typeMatch && !isReadOnly) { int length = buffer.Length - offset; if (securityBuffers[i].Buffer != null && securityBuffers[i].Buffer.Length < length) { length = securityBuffers[i].Buffer.Length; } securityBuffers[i].Buffer = ArrayUtility.SubArray( buffer, offset, length); offset += length; } } if (offset < buffer.Length) { throw new SspiException("Total length of security buffers is not enough."); } else if (offset > buffer.Length) { //Unlikely to happen throw new InvalidOperationException("Extra data were written to security buffers."); } }
/// <summary> /// Update buffers of a specified type in the list. /// Buffer will be separated automatically to fit the original length of a security buffer. /// If Buffer field of an input security buffer is null, it means the length is unlimited /// (that is all remaining data will be copied into it). /// Only read-write (READONLY flag is not set) security buffer will be updated. /// </summary> /// <param name="securityBuffers">Input security buffers.</param> /// <param name="targetType">A specified type.</param> /// <param name="buffer">The buffer to be updated into security buffers.</param> /// <exception cref="ArgumentNullException"> /// Thrown when securityBuffers or buffer is null. /// </exception> /// <exception cref="SspiException"> /// Total length of security buffers is not enough. /// </exception> public static void UpdateSecurityBuffers(SecurityBuffer[] securityBuffers, SecurityBufferType targetType, byte[] buffer) { UpdateSecurityBuffers(securityBuffers, new SecurityBufferType[] { targetType }, buffer); }
private static byte[] ConcatenateSecurityBuffers( SecurityBuffer[] securityBuffers, SecurityBufferType[] targetTypes, bool bothReadOnlyAndReadWrite) { byte[] buf = new byte[0]; for (int i = 0; i < securityBuffers.Length; i++) { SecurityBufferType securityBufferType = (securityBuffers[i].BufferType & ~SecurityBufferType.AttrMask); bool typeMatch = false; for (int j = 0; j < targetTypes.Length; j++) { if (securityBufferType == targetTypes[j]) { typeMatch = true; break; } } if (typeMatch) { bool skip = !bothReadOnlyAndReadWrite && (((securityBuffers[i].BufferType & SecurityBufferType.ReadOnly) != 0) || ((securityBuffers[i].BufferType & SecurityBufferType.ReadOnlyWithChecksum) != 0)); if (!skip && securityBuffers[i].Buffer != null) { buf = ArrayUtility.ConcatenateArrays(buf, securityBuffers[i].Buffer); } } } return buf; }
/// <summary> /// Convert SecurityBuffer to SspiSecurityBuffer. /// </summary> /// <param name="securityBuffer">SecurityBuffer</param> /// <exception cref="ArgumentNullException">If securityBuffer is null, this exception will be thrown. /// </exception> internal SspiSecurityBuffer(SecurityBuffer securityBuffer) { if (securityBuffer == null) { throw new ArgumentNullException("securityBuffer"); } if (securityBuffer.Buffer == null || securityBuffer.Buffer.Length == 0) { this.bufferLength = 0; this.pSecBuffer = IntPtr.Zero; } else { this.bufferLength = (uint)securityBuffer.Buffer.Length; this.pSecBuffer = Marshal.AllocHGlobal((int)this.bufferLength); Marshal.Copy(securityBuffer.Buffer, 0, this.pSecBuffer, (int)this.bufferLength); } this.bufferType = (uint)securityBuffer.BufferType; }
/// <summary> /// Send source Data to remote endpoint through DTLS transport. /// </summary> /// <param name="data">The sending data.</param> public List<byte[]> Encrypt(byte[] data) { if (data == null) return null; List<byte[]> encryptedDataList = new List<byte[]>(); int consumedLen = 0; while (data.Length - consumedLen > 0) { int toSendLen = (int)Math.Min(data.Length - consumedLen, dtlsStreamSizes.MaximumMessage); byte[] dataToSend = new byte[toSendLen]; Array.Copy(data, consumedLen, dataToSend, 0, toSendLen); SecurityBuffer streamHd = new SecurityBuffer(SecurityBufferType.StreamHeader, new byte[dtlsStreamSizes.Header]); SecurityBuffer dataBuffer = new SecurityBuffer(SecurityBufferType.Data, dataToSend); SecurityBuffer streamTl = new SecurityBuffer(SecurityBufferType.StreamTrailer, new byte[dtlsStreamSizes.Trailer]); SecurityBuffer emptyBuffer = new SecurityBuffer(SecurityBufferType.Empty, null); if (dtlsServerContext != null) { dtlsServerContext.Encrypt(streamHd, dataBuffer, streamTl, emptyBuffer); } else { dtlsClientContext.Encrypt(streamHd, dataBuffer, streamTl, emptyBuffer); } byte[] dtlsEncrptedData = new byte[streamHd.Buffer.Length + dataBuffer.Buffer.Length + streamTl.Buffer.Length]; Array.Copy(streamHd.Buffer, dtlsEncrptedData, streamHd.Buffer.Length); Array.Copy(dataBuffer.Buffer, 0, dtlsEncrptedData, streamHd.Buffer.Length, dataBuffer.Buffer.Length); Array.Copy(streamTl.Buffer, 0, dtlsEncrptedData, streamHd.Buffer.Length + dataBuffer.Buffer.Length, streamTl.Buffer.Length); encryptedDataList.Add(dtlsEncrptedData); consumedLen += toSendLen; } return encryptedDataList; }
/// <summary> /// This takes the given byte array, signs it, and returns the signature. /// </summary> /// <param name="messageToBeSigned">Message to be signed.</param> /// <returns>Signature of message</returns> /// <exception cref="SspiException">If sign fail, this exception will be thrown.</exception> public byte[] Sign(byte[] messageToBeSigned) { SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, messageToBeSigned); SecurityBuffer tokenBuffer = new SecurityBuffer( SecurityBufferType.Token, new byte[NativeMethods.MAX_TOKEN_SIZE]); Sign(messageBuffer, tokenBuffer); return tokenBuffer.Buffer; }
/// <summary> /// This takes the given message, sign it and returns another byte array containing the original message /// and signature, the format of the returned byte array is as follow: /// |MESSAGE_LENGTH(4 bytes)|MESSAGE|SIGNATURE| /// </summary> /// <param name="messageToBeSigned">Message to be signed.</param> /// <returns>Signed message and signature, which contains the header.</returns> /// <exception cref="SspiException">If sign fail, this exception will be thrown.</exception> public byte[] SignMessage(byte[] messageToBeSigned) { SecurityBuffer messageBuffer = new SecurityBuffer(SecurityBufferType.Data, messageToBeSigned); SecurityBuffer signatureBuffer = new SecurityBuffer( SecurityBufferType.Token, new byte[NativeMethods.MAX_TOKEN_SIZE]); Sign(messageBuffer, signatureBuffer); int messageLength = messageBuffer.Buffer.Length; byte[] signedMessage = ArrayUtility.ConcatenateArrays( BitConverter.GetBytes(messageLength), messageBuffer.Buffer, signatureBuffer.Buffer); return signedMessage; }
/// <summary> /// initial netlogon signature token when AES is negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffers, contains input plain-text; output cipher-text and signature. /// </param> private static void InitialNetlogonSignatureTokenWhenAesIsNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; NL_AUTH_SHA2_SIGNATURE nlAuthSha2Sign = new NL_AUTH_SHA2_SIGNATURE(); //The SignatureAlgorithm first byte MUST be set to 0x13, //and the second byte MUST be set to 0x00. nlAuthSha2Sign.SignatureAlgorithm = NL_AUTH_SHA2_SIGNATURE_SignatureAlgorithm_Values.HMACSHA256; if (requestConfidentiality) { //If the Confidentiality option (section 3.3.1) is //requested from the application, then the SealAlgorithm //first byte MUST be set to 0x1A, the second byte MUST //be set to 0x00, and the Confounder MUST be filled with //cryptographically random data. nlAuthSha2Sign.SealAlgorithm = NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.AES128; nlAuthSha2Sign.Confounder = GenerateNonce(NL_AUTH_SIGNATURE_CONFOUNDER_LENGTH); } else { //If the Confidentiality option (section 3.3.1) is not //requested, then the SealAlgorithm MUST be filled with //two bytes of 0xff, and the Confounder is not included in the token. nlAuthSha2Sign.SealAlgorithm = NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.NotEncrypted; nlAuthSha2Sign.Confounder = null; } //The Pad MUST be filled with 0xff bytes. nlAuthSha2Sign.Pad = NL_AUTH_SHA2_SIGNATURE_Pad_Values.V1; //The Flags MUST be filled with 0x00 bytes. nlAuthSha2Sign.Flags = Flags_Values.V1; //The SequenceNumber MUST be computed using the following algorithm. nlAuthSha2Sign.SequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The ClientSequenceNumber MUST be incremented by 1. sequenceNumber += 1; //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); nlAuthSha2Sign.Checksum = ComputeSignatureWhenAesIsNegotiated( nlAuthSha2Sign, sessionKey, requestConfidentiality, plainText); //If the Confidentiality option is requested, the data and the Confounder //field MUST be encrypted. If AES is negotiated then the server MUST use //the AES-128 algorithm using the SessionKey with an initialization //vector constructed by concatenating the sequence number with //itself twice (thus getting 16 bytes of data) if (requestConfidentiality) { byte[] aesEncryptionKey = ComputeEncryptionKeyWhenAesIsNegotiated(sessionKey); using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = aesEncryptionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.SequenceNumber, nlAuthSha2Sign.SequenceNumber); nlAuthSha2Sign.Confounder = aes.Encrypt(nlAuthSha2Sign.Confounder); plainText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); cipherText = aes.Encrypt(plainText); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, cipherText); } else { cipherText = null; } //The SequenceNumber MUST be encrypted. //If AES is negotiated, then the server MUST use the AES-128 //algorithm using the SessionKey with an initialization vector //constructed by concatenating the first 8 bytes of the //checksum with itself twice (thus getting 16 bytes of data) using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.Checksum, // Checksum is only 8 bytes nlAuthSha2Sign.Checksum); nlAuthSha2Sign.SequenceNumber = aes.Encrypt(nlAuthSha2Sign.SequenceNumber); } nlAuthSha2Sign.Dummy = GenerateNonce(NL_AUTH_SHA2_SIGNATURE_DUMMY_LENGTH); // 24 == size of dummy token = TypeMarshal.ToBytes(nlAuthSha2Sign); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Token, token); }
/// <summary> /// initial netlogon signature token when AES is not negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffers, contains input plain-text; output cipher-text and signature. /// </param> private static void InitialNetlogonSignatureTokenWhenAesIsNotNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; NL_AUTH_SIGNATURE nlAuthSign = new NL_AUTH_SIGNATURE(); //The SignatureAlgorithm first byte MUST be set to 0x77 //and the second byte MUST be set to 0x00. nlAuthSign.SignatureAlgorithm = SignatureAlgorithm_Values.HMACMD5; if (requestConfidentiality) { //If the Confidentiality option (section 3.3.1) is requested //from the application, then the SealAlgorithm first byte MUST //be set to 0x7A, the second byte MUST be set to 0x00, and //the Confounder MUST be filled with cryptographically random data. nlAuthSign.SealAlgorithm = SealAlgorithm_Values.RC4; nlAuthSign.Confounder = GenerateNonce(NL_AUTH_SIGNATURE_CONFOUNDER_LENGTH); } else { //If the Confidentiality option is not requested, then the //SealAlgorithm MUST be filled with two bytes of value 0xff, //and the Confounder is not included in the token. nlAuthSign.SealAlgorithm = SealAlgorithm_Values.NotEncrypted; nlAuthSign.Confounder = null; } //The Pad MUST be filled with 0xff bytes. nlAuthSign.Pad = Pad_Values.V1; //The Flags MUST be filled with 0x00 bytes. nlAuthSign.Flags = new byte[NL_AUTH_SIGNATURE_FLAGS_LENGTH]; //The SequenceNumber MUST be computed using the following algorithm. nlAuthSign.SequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The ClientSequenceNumber MUST be incremented by 1. sequenceNumber += 1; //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); nlAuthSign.Checksum = ComputeSignatureWhenAesIsNotNegotiated( nlAuthSign, sessionKey, requestConfidentiality, plainText); //If the Confidentiality option is requested, the data and the Confounder //field MUST be encrypted. If AES is not negotiated, it MUST use the //RC4 algorithm. if (requestConfidentiality) { byte[] rc4EncryptionKey = ComputeEncryptionKeyWhenAesIsNotNegotiated( sessionKey, nlAuthSign.SequenceNumber); using (RC4 rc4 = RC4.Create()) { rc4.Key = rc4EncryptionKey; nlAuthSign.Confounder = rc4.CreateEncryptor().TransformFinalBlock( nlAuthSign.Confounder, 0, nlAuthSign.Confounder.Length); plainText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); cipherText = rc4.CreateEncryptor().TransformFinalBlock( plainText, 0, plainText.Length); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, cipherText); } else { cipherText = null; } //The SequenceNumber MUST be encrypted. //If AES is not negotiated, it MUST use the RC4 algorithm. //The RC4 key MUST be derived as follows: //SET zeroes to 4 bytes of 0 //CALL hmac_md5(zeroes, [4 bytes], SessionKey, size of SessionKey, TmpData) //CALL hmac_md5(Checksum, size of Checksum, TmpData, size of TmpData, EncryptionKey) byte[] tmpData; byte[] sequenceNumberEncryptionKey; using (HMACMD5 hmacMd5 = new HMACMD5(sessionKey)) { tmpData = hmacMd5.ComputeHash(new byte[4]); } using (HMACMD5 hmacMd5 = new HMACMD5(tmpData)) { sequenceNumberEncryptionKey = hmacMd5.ComputeHash(nlAuthSign.Checksum); } using (RC4 rc4 = RC4.Create()) { rc4.Key = sequenceNumberEncryptionKey; nlAuthSign.SequenceNumber = rc4.CreateEncryptor().TransformFinalBlock( nlAuthSign.SequenceNumber, 0, nlAuthSign.SequenceNumber.Length); } token = TypeMarshal.ToBytes(nlAuthSign); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Token, token); }
/// <summary> /// validate netlogon signature token when AES is negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffer, contains plain-text if requestConfidentiality is false; /// or cipher-text if requestConfidentiality is true; /// and signature. /// </param> /// <returns>true if validate success; otherwise, false.</returns> private static bool ValidateNetlogonSignatureTokenWhenAesIsNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; //If AES is negotiated, a server receives an NL_AUTH_SHA2_SIGNATURE structure token = SspiUtility.ConcatenateSecurityBuffers(securityBuffers, SecurityBufferType.Token); NL_AUTH_SHA2_SIGNATURE nlAuthSha2Sign = TypeMarshal.ToStruct<NL_AUTH_SHA2_SIGNATURE>(token); //The SignatureAlgorithm bytes MUST be verified to ensure: //If AES is negotiated, the first byte is set to 0x13. //The second byte is set to 0x00 if (nlAuthSha2Sign.SignatureAlgorithm != NL_AUTH_SHA2_SIGNATURE_SignatureAlgorithm_Values.HMACSHA256) { return false; } if (requestConfidentiality) { //If the Confidentiality option is requested from the application, //then the SealAlgorithm MUST be verified to ensure that //if AES is negotiated, the first byte is set to 0x1A; //The second byte is set to 0x00. if (nlAuthSha2Sign.SealAlgorithm != NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.AES128) { return false; } } else { //If the Confidentiality option is not requested, //then the SealAlgorithm MUST be verified to contain all 0xff bytes. if (nlAuthSha2Sign.SealAlgorithm != NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.NotEncrypted) { return false; } } //The Pad MUST be verified to contain all 0xff bytes. if (nlAuthSha2Sign.Pad != NL_AUTH_SHA2_SIGNATURE_Pad_Values.V1) { return false; } //The Flags data MAY be<86> disregarded. //The SequenceNumber MUST be decrypted. //If AES is negotiated, then the server MUST use the AES128 algorithm //and a key of SessionKey and an initialization vector constructed //by concatenating the checksum with itself (thus getting 16 bytes of data). using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.Checksum, nlAuthSha2Sign.Checksum); nlAuthSha2Sign.SequenceNumber = aes.Decrypt(nlAuthSha2Sign.SequenceNumber); } //A local copy of SequenceNumber MUST be computed using the following algorithm. byte[] copySequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The SequenceNumber MUST be compared to CopySeqNumber. if (!ArrayUtility.CompareArrays( copySequenceNumber, nlAuthSha2Sign.SequenceNumber)) { return false; } //ServerSequenceNumber MUST be incremented. sequenceNumber += 1; if (requestConfidentiality) { //If the Confidentiality option is requested, //the Confounder and the data MUST be decrypted. byte[] aesEncryptionKey = ComputeEncryptionKeyWhenAesIsNegotiated(sessionKey); using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = aesEncryptionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.SequenceNumber, nlAuthSha2Sign.SequenceNumber); nlAuthSha2Sign.Confounder = aes.Decrypt(nlAuthSha2Sign.Confounder); cipherText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); plainText = aes.Decrypt(cipherText); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, plainText); } //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); byte[] checksum = ComputeSignatureWhenAesIsNegotiated( nlAuthSha2Sign, sessionKey, requestConfidentiality, plainText); //The first 8 bytes of the computed signature MUST be compared to the checksum. if (checksum != null && checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH && nlAuthSha2Sign.Checksum != null && nlAuthSha2Sign.Checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH) { for (int i = 0; i < NL_AUTH_SIGNATURE_CHECKSUM_LENGTH; i++) { if (checksum[i] != nlAuthSha2Sign.Checksum[i]) { return false; } } } return true; }
public override void Accept(byte[] clientToken) { SecurityBuffer[] inSecurityBuffers; //There must be two buffers. inSecurityBuffers = new SecurityBuffer[3]; //The first buffer must be of type SECBUFFER_TOKEN and contain the security token //received from the client. inSecurityBuffers[0] = new SecurityBuffer(SecurityBufferType.Token, clientToken); //The second buffer should be of type SECBUFFER_EMPTY. inSecurityBuffers[1] = new SecurityBuffer(SecurityBufferType.Empty, new byte[0]); //The 3nd buffer should be of type Extra. inSecurityBuffers[2] = new SecurityBuffer(SecurityBufferType.Extra, new byte[0]); SecurityBufferDescWrapper inputBufferDescWrapper = new SecurityBufferDescWrapper(inSecurityBuffers); SecurityBuffer[] outSecurityBuffers; outSecurityBuffers = new SecurityBuffer[2]; //1 token outSecurityBuffers[0] = new SecurityBuffer(SecurityBufferType.Token, new byte[NativeMethods.MAX_TOKEN_SIZE]); //2 alert outSecurityBuffers[1] = new SecurityBuffer(SecurityBufferType.Alert, new byte[NativeMethods.MAX_TOKEN_SIZE]); SecurityBufferDescWrapper outputBufferDescWrapper = new SecurityBufferDescWrapper(outSecurityBuffers); SecurityInteger timeStamp; uint contextAttribute; uint hResult = 0; if (this.contextHandle.LowPart == IntPtr.Zero || this.contextHandle.HighPart == IntPtr.Zero) { hResult = NativeMethods.AcceptSecurityContext( ref this.credentialHandle, IntPtr.Zero, ref inputBufferDescWrapper.securityBufferDesc, (uint)this.securityContextAttributes, (uint)this.targetDataRepresentaion, ref this.contextHandle, out outputBufferDescWrapper.securityBufferDesc, out contextAttribute, out timeStamp); } else { hResult = NativeMethods.AcceptSecurityContext( ref this.credentialHandle, ref this.contextHandle, ref inputBufferDescWrapper.securityBufferDesc, (uint)this.securityContextAttributes, (uint)this.targetDataRepresentaion, ref this.contextHandle, out outputBufferDescWrapper.securityBufferDesc, out contextAttribute, out timeStamp); } inputBufferDescWrapper.FreeSecurityBufferDesc(); lastHResult = hResult; if (hResult == NativeMethods.SEC_E_OK) { this.needContinueProcessing = false; this.hasMoreFragments = false; } else if (hResult == NativeMethods.SEC_I_CONTINUE_NEEDED) { this.needContinueProcessing = true; this.hasMoreFragments = false; } else if (hResult == NativeMethods.SEC_I_MESSAGE_FRAGMENT) { this.needContinueProcessing = true; this.hasMoreFragments = true; } else { throw new SspiException("Accept failed.", hResult); } //Get token this.token = null; SspiSecurityBuffer[] buffers = outputBufferDescWrapper.securityBufferDesc.GetBuffers(); for (int i = 0; i < buffers.Length; i++) { if (buffers[i].bufferType == (uint)SecurityBufferType.Token) { if (buffers[i].bufferLength > 0) { this.token = new byte[buffers[i].bufferLength]; Marshal.Copy(buffers[i].pSecBuffer, this.token, 0, this.token.Length); } break; } } outputBufferDescWrapper.FreeSecurityBufferDesc(); }
/// <summary> /// Accept client token. /// </summary> /// <param name="clientToken">Token of client</param> /// <exception cref="SspiException">If Accept fail, this exception will be thrown.</exception> public override void Accept(byte[] clientToken) { SecurityBuffer[] inSecurityBuffers; //There must be two buffers. inSecurityBuffers = new SecurityBuffer[3]; //The first buffer must be of type SECBUFFER_TOKEN and contain the security token //received from the client. inSecurityBuffers[0] = new SecurityBuffer(SecurityBufferType.Token, clientToken); //The second buffer should be of type SECBUFFER_EMPTY. inSecurityBuffers[1] = new SecurityBuffer(SecurityBufferType.Empty, new byte[0]); //The 3nd buffer should be of type Extra. inSecurityBuffers[2] = new SecurityBuffer(SecurityBufferType.Extra, new byte[0]); SecurityBufferDescWrapper inputBufferDescWrapper = new SecurityBufferDescWrapper(inSecurityBuffers); SecurityBuffer[] outSecurityBuffers; outSecurityBuffers = new SecurityBuffer[2]; //1 token outSecurityBuffers[0] = new SecurityBuffer(SecurityBufferType.Token, new byte[Consts.MAX_TOKEN_SIZE]); //2 alert outSecurityBuffers[1] = new SecurityBuffer(SecurityBufferType.Alert, new byte[Consts.MAX_TOKEN_SIZE]); SecurityBufferDescWrapper outputBufferDescWrapper = new SecurityBufferDescWrapper(outSecurityBuffers); SecurityInteger timeStamp; uint contextAttribute; uint hResult = 0; if (this.contextHandle.LowPart == IntPtr.Zero || this.contextHandle.HighPart == IntPtr.Zero) { hResult = NativeMethods.AcceptSecurityContext( ref this.credentialHandle, IntPtr.Zero, ref inputBufferDescWrapper.securityBufferDesc, (uint)this.securityContextAttributes, (uint)this.targetDataRepresentaion, ref this.contextHandle, out outputBufferDescWrapper.securityBufferDesc, out contextAttribute, out timeStamp); } else { hResult = NativeMethods.AcceptSecurityContext( ref this.credentialHandle, ref this.contextHandle, ref inputBufferDescWrapper.securityBufferDesc, (uint)this.securityContextAttributes, (uint)this.targetDataRepresentaion, ref this.contextHandle, out outputBufferDescWrapper.securityBufferDesc, out contextAttribute, out timeStamp); } inputBufferDescWrapper.FreeSecurityBufferDesc(); lastHResult = hResult; if (hResult == NativeMethods.SEC_E_OK) { this.needContinueProcessing = false; this.hasMoreFragments = false; } else if (hResult == NativeMethods.SEC_I_CONTINUE_NEEDED) { this.needContinueProcessing = true; this.hasMoreFragments = false; } else if (hResult == NativeMethods.SEC_I_MESSAGE_FRAGMENT) { this.needContinueProcessing = true; this.hasMoreFragments = true; } else { throw new SspiException("Accept failed.", hResult); } //Get token this.token = null; SspiSecurityBuffer[] buffers = outputBufferDescWrapper.securityBufferDesc.GetBuffers(); for (int i = 0; i < buffers.Length; i++) { if (buffers[i].bufferType == (uint)SecurityBufferType.Token) { if (buffers[i].bufferLength > 0) { this.token = new byte[buffers[i].bufferLength]; Marshal.Copy(buffers[i].pSecBuffer, this.token, 0, this.token.Length); } break; } } outputBufferDescWrapper.FreeSecurityBufferDesc(); }