This is the third in a series of posts looking at the detail of how message exchanges occur over a WCF channel using the named pipe binding.
In the first post of the series, we introduced the Framing Protocol.
In the second we looked into the protocol’s upgrade mechanism whereby a security context for message exchange is negotiated, if Transport security is configured on the binding.
In this post I want to consider what best practice might be as regards the choice of configuration options for security when using NetNamedPipeBinding.
First, let’s review the options:
- We can include Transport security in the binding, or exclude it completely (SecurityMode=None)
- If we include Transport security:
- We have three possible settings for the ProtectionLevel, which affects whether the messages passed are signed/encrypted. These are: None; Sign; and EncryptAndSign
- We have two effective options for the AllowedImpersonationLevel, Identification (server can identify the client) and Impersonation (server can access local resources as the client). We can also set this level to Delegation on the client, but it has the same effect as setting Impersonation.
Choosing protection level
As we saw in my last post, these settings determine what happens to messages as they pass from the stream upgrade provider in the client-side channel stack to the stream upgrade provider in the service-side channel stack. Signing gives message integrity assurance – i.e. that the message bytes are not changed en route between these two points. Encryption gives message privacy assurance – i.e. that an eavesdropper cannot read the bytes as they travel between these two points and learn the contents of the messages.
Those are the “benefits”. What about the costs? We saw that each of these functions involves an SSPI call into the security provider from each of the upgrade providers, in respect of every message, thus involving at least two context switches between user mode and kernel mode for every message. The nature of the functions is that they perform cryptographic operations on the message data, typically not a trivial workload in terms of CPU clock cycles.
So how do we balance the cost and benefit to decide whether we need the signing and encryption services? It seems to me that the supposed benefits are minimal. When remote connections are precluded, as they are by the ACLs created by the binding, named pipes are mechanisms implemented by the operating system entirely in memory. When messages are sent via a pipe there is no network or file IO involved. There is no way to eavesdrop on a particular open pipe instance being used to transfer data, or write data to it, unless one can obtain the handle to the specific instance concerned, and WCF does not provide any obvious means to discover the handle of a currently open pipe being used by the binding. In short, the only possible attacker you are going to be foiling by signing or encrypting the messages is someone who has already succeeded in attaching a debugger to your client or service process, or who has attached a kernel debugger, and who has also expended a lot of time discovering and attacking the pipe, when it would probably be far simpler to look at or change the data in the messages before they are signed/encrypted or after the binding has decrypted them.
So I have come to the conclusion that I cannot conceive any scenario where the ProtectionLevel settings Sign and EncryptAndSign are useful when using the standard NetNamedPipeBinding. If anyone can suggest any, I would welcome your comments.
So in my view our options are narrowed to:
- Use Transport security with ProtectionLevel=None, if you need the service to identify or impersonate its clients
- Dispense with Transport security completely if your service does not need to identify or impersonate its clients
Choosing impersonation level
The key questions here are (1) does the service need to know the identity of each individual client using it; and (2) does the service need to access any resource on the local machine using the client identity. This is an application-specific question and there can be no generic answers.
If the answer is yes to either question, the simplest way to implement the required functionality is to use Transport security with ProtectionLevel=None. In client config, set the ClientCredentials.Windows.AllowedImpersonationLevel to Indentification if only the identity of the client is required by the service, or to Impersonation if the service needs to access a resource acting as the client.
Dispensing with Transport security
If the answer is “no“ to both the above questions, then we don’t need WCF’s Transport security at all. Although it sounds slightly scary to be “dispensing with Transport security”, as I’ve explained above this doesn’t mean one is removing all security: the named pipe transport is inherently just as secure without the WCF-induced SSPI security layered on top of it. What is more, you can still control access to the service effectively by leveraging the ACL associated with the pipe, as I have shown here: I regard the ACL as an important part of the true “Transport security” provided by this message transport.
Getting rid of WCF’s Transport security corresponds to setting the security mode of the binding (NetNamedPipeSecurityMode) to “None” instead of “Transport”, either in the config file or in code (in the constructor of the instance of the binding, for example). When you do this, the upgrade phase of the Framing Protocol is not invoked at all by the client-side channel: there is no longer an Upgrade Provider in the channel stack.
One caveat: although the upgrade mechanism is an optional part of the Framing Protocol preamble, this is not a detail negotiated as part of the protocol. So both client and service must have agreed beforehand whether Transport security is being used, and the binding configuration must match on each side. WCF is not very forgiving if there is a mismatch of expectations, and it may not be immediately apparent what is wrong.
For example, take a client configured with security mode None and a service configured with security mode Transport. The client channel stack will fail to send the Framing Protocol’s UpgradeRequest record at the point where the WCF service implementation is expecting it. When this occurs, what actually happens is that the service aborts the channel and the pipe instance is abruptly closed. No framing protocol Fault Record is sent back to the client to point out the error of its ways: from the client’s point of view this is a pity, since all that the client will see is an IO error when it attempts to read the pipe.
The opposite configuration mismatch is not so much of a problem for the client, as the exception seen is fairly clear and explanatory:
System.ServiceModel.ProtocolException: The requested upgrade is not supported by 'net.pipe://localhost/wcfdemonpserver/NPService'. This could be due to mismatched bindings (for example security enabled on the client and not on the server).