Earlier this year Mads Krisensen (of BlogEngine.net fame) posted a lightweight implementation of OpenID using C#. In the comments on Mads' post, Andrew Arnott (a developer of the DotNetOpenId library) mentioned that the example Mads had posted could "be hacked with a single change of a word in the URL." This is what is technically referred to as a Very Bad Thing. Andrew and another poster named "neil" went on to elaborate that implementing OpenID's "check_authentication" algorithm would close this security hole. Unfortunately as of the writing of this article neither Mads nor any of the commentors have provided an implementation of check_authentication that works with the class Mads posted (bear in mind that Andrew only brought up this issue less than a week ago, so Mads may very well be working on it).
Fast forward to yesterday when I was researching my options for implementing OpenID for the next release of the ASP.Net MVC Membership Starter Kit. I liked Mads solution more than the other OpenID libraries that are current available because of its brevity and how easy it is to include it in a project without introducing an extra assembly dependency, so I decided to go ahead and add the check_authentication functionality. A quick read of that portion of the OpenID spec and a couple hours of coding/testing and I think I'm about finished.
Here is the method you need to add to Mads' class:
1: private static bool CheckAuthentication( NameValueCollection query )
<pre class="alteven"><span class="lnum"> 2:</span> {</pre>
<pre class="alt"><span class="lnum"> 3:</span>  </pre>
<pre class="alteven"><span class="lnum"> 4:</span> <span class="rem">//### get data required for check_authentication</span></pre>
<pre class="alt"><span class="lnum"> 5:</span> <span class="kwrd">string</span> mode = <span class="str">"check_authentication"</span>;</pre>
<pre class="alteven"><span class="lnum"> 6:</span> <span class="kwrd">string</span> handle = query[<span class="str">"openid.assoc_handle"</span>];</pre>
<pre class="alt"><span class="lnum"> 7:</span> <span class="kwrd">string</span> signature = query[<span class="str">"openid.sig"</span>];</pre>
<pre class="alteven"><span class="lnum"> 8:</span> <span class="kwrd">string</span> signed = query[<span class="str">"openid.signed"</span>];</pre>
<pre class="alt"><span class="lnum"> 9:</span> <span class="kwrd">string</span> extra = <span class="kwrd">string</span>.Empty;</pre>
<pre class="alteven"><span class="lnum"> 10:</span>  </pre>
<pre class="alt"><span class="lnum"> 11:</span> <span class="rem">//### loop through fields required by "openid.signed" and retrieve that data</span></pre>
<pre class="alteven"><span class="lnum"> 12:</span> <span class="kwrd">if</span>( !<span class="kwrd">string</span>.IsNullOrEmpty(signed) )</pre>
<pre class="alt"><span class="lnum"> 13:</span> {</pre>
<pre class="alteven"><span class="lnum"> 14:</span> <span class="kwrd">string</span>[] exemptions = { <span class="str">"mode"</span>, <span class="str">"assoc_handle"</span>, <span class="str">"sig"</span>, <span class="str">"signed"</span> };</pre>
<pre class="alt"><span class="lnum"> 15:</span> <span class="kwrd">string</span>[] fields = signed.Split(<span class="str">','</span>);</pre>
<pre class="alteven"><span class="lnum"> 16:</span> <span class="kwrd">foreach</span>( <span class="kwrd">string</span> field <span class="kwrd">in</span> fields )</pre>
<pre class="alt"><span class="lnum"> 17:</span> {</pre>
<pre class="alteven"><span class="lnum"> 18:</span> <span class="kwrd">if</span>( exemptions.Contains(field) ) <span class="kwrd">continue</span>;</pre>
<pre class="alt"><span class="lnum"> 19:</span> extra += <span class="kwrd">string</span>.Format( <span class="str">"openid.{0}={1}&"</span>, field, HttpUtility.UrlEncode( query[ <span class="str">"openid."</span> + field ] ) );</pre>
<pre class="alteven"><span class="lnum"> 20:</span> }</pre>
<pre class="alt"><span class="lnum"> 21:</span> extra = <span class="str">"&"</span> + extra.Substring( 0, extra.Length - 1 );</pre>
<pre class="alteven"><span class="lnum"> 22:</span> }</pre>
<pre class="alt"><span class="lnum"> 23:</span>  </pre>
<pre class="alteven"><span class="lnum"> 24:</span> <span class="rem">//### combine all the data together to form the request</span></pre>
<pre class="alt"><span class="lnum"> 25:</span> <span class="kwrd">string</span> post = <span class="kwrd">string</span>.Format( <span class="str">"openid.mode={0}&openid.assoc_handle={1}&openid.sig={2}&openid.signed={3}{4}"</span>,</pre>
<pre class="alteven"><span class="lnum"> 26:</span> mode,</pre>
<pre class="alt"><span class="lnum"> 27:</span> HttpUtility.UrlEncode( handle ),</pre>
<pre class="alteven"><span class="lnum"> 28:</span> HttpUtility.UrlEncode( signature ),</pre>
<pre class="alt"><span class="lnum"> 29:</span> HttpUtility.UrlEncode( signed ),</pre>
<pre class="alteven"><span class="lnum"> 30:</span> extra</pre>
<pre class="alt"><span class="lnum"> 31:</span> );</pre>
<pre class="alteven"><span class="lnum"> 32:</span>  </pre>
<pre class="alt"><span class="lnum"> 33:</span> <span class="rem">//### begin sending request</span></pre>
<pre class="alteven"><span class="lnum"> 34:</span> HttpWebRequest request = (HttpWebRequest)WebRequest.Create( query[<span class="str">"openid.op_endpoint"</span>] );</pre>
<pre class="alt"><span class="lnum"> 35:</span> request.Method = <span class="str">"POST"</span>;</pre>
<pre class="alteven"><span class="lnum"> 36:</span> request.ContentType = <span class="str">"application/x-www-form-urlencoded"</span>;</pre>
<pre class="alt"><span class="lnum"> 37:</span> request.ContentLength = post.Length;</pre>
<pre class="alteven"><span class="lnum"> 38:</span>  </pre>
<pre class="alt"><span class="lnum"> 39:</span> <span class="rem">//### transmit POST data</span></pre>
<pre class="alteven"><span class="lnum"> 40:</span> <span class="kwrd">using</span>( StreamWriter sw = <span class="kwrd">new</span> StreamWriter(request.GetRequestStream()) )</pre>
<pre class="alt"><span class="lnum"> 41:</span> sw.Write(post);</pre>
<pre class="alteven"><span class="lnum"> 42:</span>  </pre>
<pre class="alt"><span class="lnum"> 43:</span> <span class="rem">//### get response</span></pre>
<pre class="alteven"><span class="lnum"> 44:</span> <span class="kwrd">string</span> html = <span class="str">""</span>;</pre>
<pre class="alt"><span class="lnum"> 45:</span> <span class="kwrd">using</span>( HttpWebResponse response = (HttpWebResponse)request.GetResponse() )</pre>
<pre class="alteven"><span class="lnum"> 46:</span> <span class="kwrd">using</span>( StreamReader sr = <span class="kwrd">new</span> StreamReader( response.GetResponseStream() ) )</pre>
<pre class="alt"><span class="lnum"> 47:</span> html = sr.ReadToEnd();</pre>
<pre class="alteven"><span class="lnum"> 48:</span>  </pre>
<pre class="alt"><span class="lnum"> 49:</span> <span class="rem">//### determine if check_authentication passed or not</span></pre>
<pre class="alteven"><span class="lnum"> 50:</span> <span class="kwrd">if</span>( <span class="kwrd">string</span>.IsNullOrEmpty(html) || !html.StartsWith( <span class="str">"is_valid:"</span> ) || html.StartsWith( <span class="str">"is_valid:false"</span> ) )</pre>
<pre class="alt"><span class="lnum"> 51:</span> <span class="kwrd">return</span> <span class="kwrd">false</span>;</pre>
<pre class="alteven"><span class="lnum"> 52:</span> <span class="kwrd">else</span> <span class="kwrd">if</span>( html.StartsWith( <span class="str">"is_valid:true"</span> ) )</pre>
<pre class="alt"><span class="lnum"> 53:</span> <span class="kwrd">return</span> <span class="kwrd">true</span>;</pre>
<pre class="alteven"><span class="lnum"> 54:</span> <span class="kwrd">else</span></pre>
<pre class="alt"><span class="lnum"> 55:</span> <span class="kwrd">throw</span> <span class="kwrd">new</span> InvalidOperationException( <span class="str">"Unexpected return from OpenID check_authentication."</span> );</pre>
<pre class="alteven"><span class="lnum"> 56:</span>  </pre>
<pre class="alt"><span class="lnum"> 57:</span> }</pre>
Now you need to make sure that method is called from somewhere. I chose to make the method private and just call it from inside the Authenticate method. Within the Authenticate method replace:
1: // Make sure the incoming request's identity matches the one stored in session
2: if( query["openid.claimed_id"] != data.Identity )
3: return data;
... with...
1: // Make sure the incoming request's identity matches the one stored in session
2: if( query["openid.claimed_id"] != data.Identity )
3: return data;
4: else if( !CheckAuthentication( query ) )
5: throw new UnauthorizedAccessException( "OpenID False Verification Detected" );
And that's it!
If anyone with more experience than I in OpenID waters sees a problem with my implementation, let me know and I will try to get it fixed quickly.
NOTE: I am aware that Mads' implementation and the use of 'check_authentication' is considered an overly chatty use of OpenID. It seems to me that the extra complexity required to implement OpenID 2.0 protocol is just not worthwhile for most OpenID consumers. Feel free to let me know why this is a stupid position to take.