This article was originally posted on my PFE blog here: http://blogs.technet.com/b/bshukla/archive/2012/08/22/3324650.aspx.
I noticed a few inaccuracies with the article and since I am not a PFE anymore, I can’t edit original article but posting updated article here for everyone to benefit from.
The Problem
We all at one point or another have come across a coding issue where we are trying to connect to a website using a script and the website is secure with either self-signed or untrusted SSL certificate. This poses a challenge. A challenge to tell code to ignore the trust related issues and move forward with rest of the logic. The error you are presented with when certificate trust fails looks something like this:
Exception calling “DownloadString” with “1” argument(s): “The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.”
The solution
When using PowerShell, the logic is relatively simple. Using .Net framework class libraries, you can set custom validation of server certificate by the client. In plain english, you can override the certificate trust by telling code that you explicitly trust the certificate presented by the server. Here’s what the one-liner looks like:
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
Once set, you can create web client object and proceed with your web client calls. Simplified example would look something like the following:
$wc = New-Object System.Net.WebClient $src = $wc.downloadstring(https://server/abc.htm)
$src = $wc.downloadstring(https://server/abc.htm)
Note that this setting is per session setting and will be valid only for given session or PowerShell Window.
The Confusion Addressed
Now let’s talk about the confusion from original post. After overriding certificate trust, I received malformed header error:
Exception calling “DownloadString” with “1” argument(s): “The server committed a protocol violation. Section=ResponseHeader Detail=CR must be followed by LF”
It looked as if I hadn’t resolved SSL trust error. However, if you pay close attention, you will notice that the issue is protocol violation. In this case a malformed header. Response header isn’t presenting information as it should and is missing LF after presenting CR. And that’s where most readers assumed, what followed was a solution to certificate errors when it actually was solution to address malformed headers issue.
As I mentioned in my original article, you can address malformed headers issue in one of the two ways. You can use it on sources that you trust. I would be very carful using it against any website that you may not trust. Consider yourself warned.
Here are two methods to address malformed headers:
- Create PowerShell.exe.config in $PSHOME location. Personally, I don’t like this idea as adding following lines to the file means telling PowerShell to ignore such protocol violations forever. Not a good security measure. However, if you are curious, here’s how your PowerShell.exe.config would look like (if you create a new one). it’s the “useUnsafeHeaderParsing” that is important to understand.
<!–?xml version=”1.0″ encoding=”utf-8″ ?–>
<configuration>
<system.net>
<settings>
<httpwebrequest useunsafeheaderparsing=”true”>
</httpwebrequest></settings>
</configuration> - Since I didn’t like the idea of leaving doors wide open, I found another way of doing it at Lee Holmes’ Blog. This sure looks ugly because we are trying to change Internal .Net system settings. The code would have been prettier otherwise. The reason I like this is, your settings of ignoring what follows are short lived and will go away when you close your PowerShell session (i.e. close current PowerShell window). This is better approach to security than opening a hole in your system every time you run PowerShell as described in option 1 above.
$netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection]) if($netAssembly) { $bindingFlags = [Reflection.BindingFlags] "Static,GetProperty,NonPublic" $settingsType = $netAssembly.GetType("System.Net.Configuration.SettingsSectionInternal") $instance = $settingsType.InvokeMember("Section", $bindingFlags, $null, $null, @()) if($instance) { $bindingFlags = "NonPublic","Instance" $useUnsafeHeaderParsingField = $settingsType.GetField("useUnsafeHeaderParsing", $bindingFlags) if($useUnsafeHeaderParsingField) { $useUnsafeHeaderParsingField.SetValue($instance, $true) } } }
Hopefully this helps you address WebClient errors, namely SSL trust and malformed headers. Cheers!
[…] trust issues for the per-server checks, and I could not have worked it out if I hadn’t found this blog post by Bhargav Shukla explaining how to handle this […]