/// <summary> /// Decrypts the message using RSA PKCS#1 v1.5 encryption. /// </summary> private ArraySegment <byte> Rsa_Decrypt( ArraySegment <byte> dataToDecrypt, ArraySegment <byte> headerToCopy, X509Certificate2 encryptingCertificate, bool useOaep) { // get the encrypting key. using (RSA rsa = encryptingCertificate.GetRSAPrivateKey()) { if (rsa == null) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No private key for certificate."); } int inputBlockSize = rsa.KeySize / 8; int outputBlockSize = Rsa_GetPlainTextBlockSize(encryptingCertificate, useOaep); // verify the input data is the correct block size. if (dataToDecrypt.Count % inputBlockSize != 0) { Utils.Trace("Message is not an integral multiple of the block size. Length = {0}, BlockSize = {1}.", dataToDecrypt.Count, inputBlockSize); } byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Rsa_Decrypt"); Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); using (MemoryStream ostrm = new MemoryStream( decryptedBuffer, headerToCopy.Count, decryptedBuffer.Length - headerToCopy.Count)) { // decrypt body. byte[] input = new byte[inputBlockSize]; for (int ii = dataToDecrypt.Offset; ii < dataToDecrypt.Offset + dataToDecrypt.Count; ii += inputBlockSize) { Array.Copy(dataToDecrypt.Array, ii, input, 0, input.Length); if (useOaep == true) { byte[] plainText = rsa.Decrypt(input, RSAEncryptionPadding.OaepSHA1); ostrm.Write(plainText, 0, plainText.Length); } else { byte[] plainText = rsa.Decrypt(input, RSAEncryptionPadding.Pkcs1); ostrm.Write(plainText, 0, plainText.Length); } } } // return buffers. return(new ArraySegment <byte>(decryptedBuffer, 0, (dataToDecrypt.Count / inputBlockSize) * outputBlockSize + headerToCopy.Count)); } }
/// <summary> /// Encrypts the message using RSA encryption. /// </summary> private ArraySegment <byte> Rsa_Encrypt( ArraySegment <byte> dataToEncrypt, ArraySegment <byte> headerToCopy, X509Certificate2 encryptingCertificate, RsaUtils.Padding padding) { RSA rsa = null; try { // get the encrypting key. rsa = encryptingCertificate.GetRSAPublicKey(); if (rsa == null) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate."); } int inputBlockSize = RsaUtils.GetPlainTextBlockSize(rsa, padding); int outputBlockSize = RsaUtils.GetCipherTextBlockSize(rsa, padding); // verify the input data is the correct block size. if (dataToEncrypt.Count % inputBlockSize != 0) { Utils.Trace("Message is not an integral multiple of the block size. Length = {0}, BlockSize = {1}.", dataToEncrypt.Count, inputBlockSize); } byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Rsa_Encrypt"); Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); RSAEncryptionPadding rsaPadding = RsaUtils.GetRSAEncryptionPadding(padding); using (MemoryStream ostrm = new MemoryStream( encryptedBuffer, headerToCopy.Count, encryptedBuffer.Length - headerToCopy.Count)) { // encrypt body. byte[] input = new byte[inputBlockSize]; for (int ii = dataToEncrypt.Offset; ii < dataToEncrypt.Offset + dataToEncrypt.Count; ii += inputBlockSize) { Array.Copy(dataToEncrypt.Array, ii, input, 0, input.Length); byte[] cipherText = rsa.Encrypt(input, rsaPadding); ostrm.Write(cipherText, 0, cipherText.Length); } } // return buffer return(new ArraySegment <byte>(encryptedBuffer, 0, (dataToEncrypt.Count / inputBlockSize) * outputBlockSize + headerToCopy.Count)); } finally { RsaUtils.RSADispose(rsa); } }
/// <summary> /// Reverse client is connected, send reverse hello message. /// </summary> private void OnReverseConnectComplete(object sender, IMessageSocketAsyncEventArgs result) { var ar = (ReverseConnectAsyncResult)result.UserToken; if (ar == null || m_pendingReverseHello != null) { return; } if (result.IsSocketError) { ar.Exception = new ServiceResultException(StatusCodes.BadNotConnected, result.SocketErrorString); ar.OperationCompleted(); return; } byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "OnReverseConnectConnectComplete"); try { // start reading messages. ar.Socket.ReadNextMessage(); // send reverse hello message. BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.ReverseHello); encoder.WriteUInt32(null, 0); encoder.WriteString(null, EndpointDescription.Server.ApplicationUri); encoder.WriteString(null, EndpointDescription.EndpointUrl); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); // set state to waiting for hello. State = TcpChannelState.Connecting; m_pendingReverseHello = ar; BeginWriteMessage(new ArraySegment <byte>(buffer, 0, size), null); buffer = null; } catch (Exception e) { ar.Exception = e; ar.OperationCompleted(); } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "OnReverseConnectComplete"); } } }
/// <summary> /// Encrypts the message using RSA PKCS#1 v1.5 encryption. /// </summary> private ArraySegment <byte> Rsa_Encrypt( ArraySegment <byte> dataToEncrypt, ArraySegment <byte> headerToCopy, X509Certificate2 encryptingCertificate, bool useOaep) { // get the encrypting key. RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)encryptingCertificate.PublicKey.Key; if (rsa == null) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate."); } int inputBlockSize = Rsa_GetPlainTextBlockSize(encryptingCertificate, useOaep); int outputBlockSize = rsa.KeySize / 8; // verify the input data is the correct block size. if (dataToEncrypt.Count % inputBlockSize != 0) { Utils.Trace("Message is not an integral multiple of the block size. Length = {0}, BlockSize = {1}.", dataToEncrypt.Count, inputBlockSize); } byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Rsa_Encrypt"); Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); MemoryStream ostrm = new MemoryStream( encryptedBuffer, headerToCopy.Count, encryptedBuffer.Length - headerToCopy.Count); // encrypt body. byte[] input = new byte[inputBlockSize]; for (int ii = dataToEncrypt.Offset; ii < dataToEncrypt.Offset + dataToEncrypt.Count; ii += inputBlockSize) { Array.Copy(dataToEncrypt.Array, ii, input, 0, input.Length); byte[] cipherText = rsa.Encrypt(input, useOaep); ostrm.Write(cipherText, 0, cipherText.Length); } ostrm.Close(); // return buffer return(new ArraySegment <byte>(encryptedBuffer, 0, (dataToEncrypt.Count / inputBlockSize) * outputBlockSize + headerToCopy.Count)); }
/// <summary> /// Decrypts the buffer using asymmetric encryption. /// </summary> /// <remarks> /// Start and count specify the block of data to be decrypted. /// The header specifies unencrypted data that must be copied to the output. /// </remarks> protected ArraySegment <byte> Decrypt( ArraySegment <byte> dataToDecrypt, ArraySegment <byte> headerToCopy, X509Certificate2 receiverCertificate) { switch (SecurityPolicyUri) { default: case SecurityPolicies.None: { byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt"); Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); Array.Copy(dataToDecrypt.Array, dataToDecrypt.Offset, decryptedBuffer, headerToCopy.Count, dataToDecrypt.Count); return(new ArraySegment <byte>(decryptedBuffer, 0, dataToDecrypt.Count + headerToCopy.Count)); } case SecurityPolicies.Basic256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: { return(Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1)); } case SecurityPolicies.Aes256_Sha256_RsaPss: { return(Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256)); } case SecurityPolicies.Basic128Rsa15: { return(Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1)); } } }
/// <summary> /// Secures the message using the security token. /// </summary> protected BufferCollection WriteSymmetricMessage( uint messageType, uint requestId, ChannelToken token, object messageBody, bool isRequest, out bool limitsExceeded) { limitsExceeded = false; bool success = false; BufferCollection chunksToProcess = null; try { // calculate chunk sizes. int maxCipherTextSize = SendBufferSize - TcpMessageLimits.SymmetricHeaderSize; int maxCipherBlocks = maxCipherTextSize / EncryptionBlockSize; int maxPlainTextSize = maxCipherBlocks * EncryptionBlockSize; int maxPayloadSize = maxPlainTextSize - SymmetricSignatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int headerSize = TcpMessageLimits.SymmetricHeaderSize + TcpMessageLimits.SequenceHeaderSize; // write the body to stream. ArraySegmentStream ostrm = new ArraySegmentStream( BufferManager, SendBufferSize, headerSize, maxPayloadSize); // check for encodeable body. IEncodeable encodeable = messageBody as IEncodeable; if (encodeable != null) { // debug code used to verify that message aborts are handled correctly. // int maxMessageSize = Quotas.MessageContext.MaxMessageSize; // Quotas.MessageContext.MaxMessageSize = Int32.MaxValue; BinaryEncoder.EncodeMessage(encodeable, ostrm, Quotas.MessageContext); // Quotas.MessageContext.MaxMessageSize = maxMessageSize; } // check for raw bytes. ArraySegment <byte>?rawBytes = messageBody as ArraySegment <byte>?; if (rawBytes != null) { BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteRawBytes(rawBytes.Value.Array, rawBytes.Value.Offset, rawBytes.Value.Count); encoder.Close(); } chunksToProcess = ostrm.GetBuffers("WriteSymmetricMessage"); // ensure there is at least one chunk. if (chunksToProcess.Count == 0) { byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteSymmetricMessage"); chunksToProcess.Add(new ArraySegment <byte>(buffer, 0, 0)); } BufferCollection chunksToSend = new BufferCollection(chunksToProcess.Capacity); int messageSize = 0; for (int ii = 0; ii < chunksToProcess.Count; ii++) { ArraySegment <byte> chunkToProcess = chunksToProcess[ii]; // nothing more to do if limits exceeded. if (limitsExceeded) { BufferManager.ReturnBuffer(chunkToProcess.Array, "WriteSymmetricMessage"); continue; } MemoryStream strm = new MemoryStream(chunkToProcess.Array, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(strm, Quotas.MessageContext); // check if the message needs to be aborted. if (MessageLimitsExceeded(isRequest, messageSize + chunkToProcess.Count - headerSize, ii + 1)) { encoder.WriteUInt32(null, messageType | TcpMessageType.Abort); // replace the body in the chunk with an error message. BinaryEncoder errorEncoder = new BinaryEncoder( chunkToProcess.Array, chunkToProcess.Offset, chunkToProcess.Count, Quotas.MessageContext); WriteErrorMessageBody(errorEncoder, (isRequest) ? StatusCodes.BadRequestTooLarge : StatusCodes.BadResponseTooLarge); int size = errorEncoder.Close(); chunkToProcess = new ArraySegment <byte>(chunkToProcess.Array, chunkToProcess.Offset, size); limitsExceeded = true; } // check if the message is complete. else if (ii == chunksToProcess.Count - 1) { encoder.WriteUInt32(null, messageType | TcpMessageType.Final); } // more chunks to follow. else { encoder.WriteUInt32(null, messageType | TcpMessageType.Intermediate); } int count = 0; count += TcpMessageLimits.SequenceHeaderSize; count += chunkToProcess.Count; count += SymmetricSignatureSize; // calculate the padding. int padding = 0; if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // reserve one byte for the padding size. count++; if (count % EncryptionBlockSize != 0) { padding = EncryptionBlockSize - (count % EncryptionBlockSize); } count += padding; } count += TcpMessageLimits.SymmetricHeaderSize; encoder.WriteUInt32(null, (uint)count); encoder.WriteUInt32(null, ChannelId); encoder.WriteUInt32(null, token.TokenId); uint sequenceNumber = GetNewSequenceNumber(); encoder.WriteUInt32(null, sequenceNumber); encoder.WriteUInt32(null, requestId); // skip body. strm.Seek(chunkToProcess.Count, SeekOrigin.Current); // update message size count. messageSize += chunkToProcess.Count; // write padding. if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { for (int jj = 0; jj <= padding; jj++) { encoder.WriteByte(null, (byte)padding); } } if (SecurityMode != MessageSecurityMode.None) { // calculate and write signature. byte[] signature = Sign(token, new ArraySegment <byte>(chunkToProcess.Array, 0, encoder.Position), isRequest); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } } if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // encrypt the data. ArraySegment <byte> dataToEncrypt = new ArraySegment <byte>(chunkToProcess.Array, TcpMessageLimits.SymmetricHeaderSize, encoder.Position - TcpMessageLimits.SymmetricHeaderSize); Encrypt(token, dataToEncrypt, isRequest); } // add the header into chunk. chunksToSend.Add(new ArraySegment <byte>(chunkToProcess.Array, 0, encoder.Position)); } // ensure the buffers don't get cleaned up on exit. success = true; return(chunksToSend); } finally { if (!success) { if (chunksToProcess != null) { chunksToProcess.Release(BufferManager, "WriteSymmetricMessage"); } } } }
/// <summary> /// Sends a OpenSecureChannel request. /// </summary> protected BufferCollection WriteAsymmetricMessage( uint messageType, uint requestId, X509Certificate2 senderCertificate, X509Certificate2Collection senderCertificateChain, X509Certificate2 receiverCertificate, ArraySegment <byte> messageBody) { bool success = false; BufferCollection chunksToSend = new BufferCollection(); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteAsymmetricMessage"); try { BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); int headerSize = 0; if (senderCertificateChain != null && senderCertificateChain.Count > 0) { int senderCertificateSize = 0; WriteAsymmetricMessageHeader( encoder, messageType | TcpMessageType.Intermediate, ChannelId, SecurityPolicyUri, senderCertificate, senderCertificateChain, receiverCertificate, out senderCertificateSize); headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate, senderCertificateSize); } else { WriteAsymmetricMessageHeader( encoder, messageType | TcpMessageType.Intermediate, ChannelId, SecurityPolicyUri, senderCertificate, receiverCertificate); headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate); } int signatureSize = GetAsymmetricSignatureSize(senderCertificate); // save the header. ArraySegment <byte> header = new ArraySegment <byte>(buffer, 0, headerSize); // calculate the space available. int plainTextBlockSize = GetPlainTextBlockSize(receiverCertificate); int cipherTextBlockSize = GetCipherTextBlockSize(receiverCertificate); int maxCipherTextSize = SendBufferSize - headerSize; int maxCipherBlocks = maxCipherTextSize / cipherTextBlockSize; int maxPlainTextSize = maxCipherBlocks * plainTextBlockSize; int maxPayloadSize = maxPlainTextSize - signatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int bytesToWrite = messageBody.Count; int startOfBytes = messageBody.Offset; while (bytesToWrite > 0) { encoder.WriteUInt32(null, GetNewSequenceNumber()); encoder.WriteUInt32(null, requestId); int payloadSize = bytesToWrite; if (payloadSize > maxPayloadSize) { payloadSize = maxPayloadSize; } else { UpdateMessageType(buffer, 0, messageType | TcpMessageType.Final); } // write the message body. encoder.WriteRawBytes(messageBody.Array, messageBody.Offset + startOfBytes, payloadSize); // calculate the amount of plain text to encrypt. int plainTextSize = encoder.Position - headerSize + signatureSize; // calculate the padding. int padding = 0; if (SecurityMode != MessageSecurityMode.None) { if (CertificateFactory.GetRSAPublicKeySize(receiverCertificate) <= TcpMessageLimits.KeySizeExtraPadding) { // need to reserve one byte for the padding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } encoder.WriteByte(null, (byte)padding); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)padding); } } else { // need to reserve one byte for the padding. plainTextSize++; // need to reserve one byte for the extrapadding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } byte paddingSize = (byte)(padding & 0xff); byte extraPaddingByte = (byte)((padding >> 8) & 0xff); encoder.WriteByte(null, paddingSize); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)paddingSize); } encoder.WriteByte(null, extraPaddingByte); } // update the plaintext size with the padding size. plainTextSize += padding; } // calculate the number of block to encrypt. int encryptedBlocks = plainTextSize / plainTextBlockSize; // calculate the size of the encrypted data. int cipherTextSize = encryptedBlocks * cipherTextBlockSize; // put the message size after encryption into the header. UpdateMessageSize(buffer, 0, cipherTextSize + headerSize); // write the signature. byte[] signature = Sign(new ArraySegment <byte>(buffer, 0, encoder.Position), senderCertificate); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } int messageSize = encoder.Close(); // encrypt the data. ArraySegment <byte> encryptedBuffer = Encrypt( new ArraySegment <byte>(buffer, headerSize, messageSize - headerSize), header, receiverCertificate); // check for math errors due to code bugs. if (encryptedBuffer.Count != cipherTextSize + headerSize) { throw new InvalidDataException("Actual message size is not the same as the predicted message size."); } // save chunk. chunksToSend.Add(encryptedBuffer); bytesToWrite -= payloadSize; startOfBytes += payloadSize; // reset the encoder to write the plaintext for the next chunk into the same buffer. if (bytesToWrite > 0) { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); ostrm.Seek(header.Count, SeekOrigin.Current); encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); } } // ensure the buffers don't get clean up on exit. success = true; return(chunksToSend); } catch (Exception ex) { throw new Exception("Could not write async message", ex); } finally { BufferManager.ReturnBuffer(buffer, "WriteAsymmetricMessage"); if (!success) { chunksToSend.Release(BufferManager, "WriteAsymmetricMessage"); } } }
private bool ProcessHelloMessage(ArraySegment <byte> messageChunk) { // validate the channel state. if (State != TcpChannelState.Connecting) { ForceChannelFault(StatusCodes.BadTcpMessageTypeInvalid, "Client sent an unexpected Hello message."); return(false); } try { MemoryStream istrm = new MemoryStream(messageChunk.Array, messageChunk.Offset, messageChunk.Count, false); BinaryDecoder decoder = new BinaryDecoder(istrm, Quotas.MessageContext); istrm.Seek(TcpMessageLimits.MessageTypeAndSize, SeekOrigin.Current); // read requested buffer sizes. uint protocolVersion = decoder.ReadUInt32(null); uint receiveBufferSize = decoder.ReadUInt32(null); uint sendBufferSize = decoder.ReadUInt32(null); uint maxMessageSize = decoder.ReadUInt32(null); uint maxChunkCount = decoder.ReadUInt32(null); // read the endpoint url. int length = decoder.ReadInt32(null); if (length > 0) { if (length > TcpMessageLimits.MaxEndpointUrlLength) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return(false); } byte[] endpointUrl = new byte[length]; for (int ii = 0; ii < endpointUrl.Length; ii++) { endpointUrl[ii] = decoder.ReadByte(null); } if (!SetEndpointUrl(new UTF8Encoding().GetString(endpointUrl, 0, endpointUrl.Length))) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return(false); } } decoder.Close(); // update receive buffer size. if (receiveBufferSize < ReceiveBufferSize) { ReceiveBufferSize = (int)receiveBufferSize; } if (ReceiveBufferSize < TcpMessageLimits.MinBufferSize) { ReceiveBufferSize = TcpMessageLimits.MinBufferSize; } // update send buffer size. if (sendBufferSize < SendBufferSize) { SendBufferSize = (int)sendBufferSize; } if (SendBufferSize < TcpMessageLimits.MinBufferSize) { SendBufferSize = TcpMessageLimits.MinBufferSize; } // update the max message size. if (maxMessageSize > 0 && maxMessageSize < MaxResponseMessageSize) { MaxResponseMessageSize = (int)maxMessageSize; } if (MaxResponseMessageSize < SendBufferSize) { MaxResponseMessageSize = SendBufferSize; } // update the max chunk count. if (maxChunkCount > 0 && maxChunkCount < MaxResponseChunkCount) { MaxResponseChunkCount = (int)maxChunkCount; } // send acknowledge. byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "ProcessHelloMessage"); try { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Acknowledge); encoder.WriteUInt32(null, 0); encoder.WriteUInt32(null, 0); // ProtocolVersion encoder.WriteUInt32(null, (uint)ReceiveBufferSize); encoder.WriteUInt32(null, (uint)SendBufferSize); encoder.WriteUInt32(null, (uint)MaxRequestMessageSize); encoder.WriteUInt32(null, (uint)MaxRequestChunkCount); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); // now ready for the open or bind request. State = TcpChannelState.Opening; BeginWriteMessage(new ArraySegment <byte>(buffer, 0, size), null); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "ProcessHelloMessage"); } } } catch (Exception e) { ForceChannelFault(e, StatusCodes.BadTcpInternalError, "Unexpected error while processing a Hello message."); } return(false); }