Reputation: 1355
I've been writing my own implementation of Open ID RP (yes I know there are plenty already built, I'm doing it for "fun"). Everything works fine until I am in the verification step and calculate the hash and compare it against the sig I got from the OP in the positive assertion.
I've read the spec up and down but there are a few things that weren't clear to me:
Do I only include the key value pairs in the openid namespace or everything that's in the list in openid.signed? 6.1 makes it sound like I should only use openid. keys even though I have some other stuff hanging out in openid.signed (ax).
Should the last key value pair be follow by a line break?
I'm assuming the values should be url encoded (as not to have colons in the value). If so, I would also assume the hex values, such as %3D, should be uppercase. I ran into that on an OAuth 1.0 implementation, since .NET's built in URL encoding uses lower case hex letters.
I'm fairly certain the encoding and algorithm are fine but my base string is off. Here is a completely unaltered version of an example which I can't get to work:
Querystring I get back from the OP with the positive assertion:
openid.ns=http://specs.openid.net/auth/2.0&openid.mode=id_res&openid.op_endpoint=https://www.google.com/accounts/o8/ud&openid.response_nonce=2011-05-13T08:18:42ZBHyiLFGyNT-SqQ&openid.return_to=http://mysite.com/Account/Login.aspx&openid.assoc_handle=AOQobUc4P9MWC3faGcMkfTb2U10KfGQ-6cm9L4pLDQmeoY2DE6XRGtN0&openid.signed=op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,ns.ext1,ext1.mode,ext1.type.firstname,ext1.value.firstname,ext1.type.email,ext1.value.email,ext1.type.lastname,ext1.value.lastname&openid.sig=KSXw+bv7sLlQyUIflA3Jzx5VoPk=&openid.identity=https://www.google.com/accounts/o8/id?id=AItOawkDYxJln6LwTAdl0kP8xdMT71SoRufUFA4&openid.claimed_id=https://www.google.com/accounts/o8/id?id=AItOawkDYxJln6LwTAdl0kP8xdMT71SoRufUFA4&openid.ns.ext1=http://openid.net/srv/ax/1.0&openid.ext1.mode=fetch_response&openid.ext1.type.firstname=http://axschema.org/namePerson/first&openid.ext1.value.firstname=firstname&openid.ext1.type.email=http://schema.openid.net/contact/email&[email protected]&openid.ext1.type.lastname=http://axschema.org/namePerson/last&openid.ext1.value.lastname=lastname
The base string I built using that querystring:
op_endpoint:https://www.google.com/accounts/o8/ud\nclaimed_id:https://www.google.com/accounts/o8/id?id=AItOawkDYxJln6LwTAdl0kP8xdMT71SoRufUFA4\nidentity:https://www.google.com/accounts/o8/id?id=AItOawkDYxJln6LwTAdl0kP8xdMT71SoRufUFA4\nreturn_to:http://mysite.com/Account/Login.aspx\nresponse_nonce:2011-05-13T08:18:42ZBHyiLFGyNT-SqQ\nassoc_handle:AOQobUc4P9MWC3faGcMkfTb2U10KfGQ-6cm9L4pLDQmeoY2DE6XRGtN0\nns.ext1:http://openid.net/srv/ax/1.0\next1.mode:fetch_response\next1.type.firstname:http://axschema.org/namePerson/first\next1.value.firstname:firstname\next1.type.email:http://schema.openid.net/contact/email\next1.value.email:[email protected]\next1.type.lastname:http://axschema.org/namePerson/last\next1.value.lastname:lastname\n
The mac key as returned by the assocation request:
U/1wUBAU2aYIR+2eIsugXyEOpmE=
Using all of this with HMAC-SHA1, the hash I get is:
9HMRL4je44Oz90s1f8pw5qpZ8HQ=
But as you can see from openid.sig, it should be
KSXw+bv7sLlQyUIflA3Jzx5VoPk=
Am I formulating the base string incorrectly? Am I calculating the hash wrong? How is something this "simple" taking so long to implement correctly?
Upvotes: 3
Views: 1550
Reputation: 36
I had also problems generating a matching signature and finally found the solution.
As you already suspected, you have to add the values from the openid.ax
namespace applying the same rule of adding the key/value pair without the openid.
prefix. If there are no openid.ax
keys, then something is wrong.
Yes, the last key/value pair is followed by a newline (attention: only an \n
). This could have been mentioned more clearly in the OpenID specification.
You are wrong about the URL encoding, it's exactly the other way around: The values must be URL-decoded. Also this is not explicitly told in the spec. Don't confuse colons and semicolons, you are not allowed to have colons, but only in the key part, so there is no problem about this.
So if you try with this string and add the missing key/value pairs, it should work:
ns:http://specs.openid.net/auth/2.0 op_endpoint:https://www.google.com/accounts/o8/ud claimed_id:https://www.google.com/accounts/o8/id?id=AItOawlvj7acGYj-NH1kKKl3RswJlLCKpl9LIwk identity:https://www.google.com/accounts/o8/id?id=AItOawlvj7acGYj-NH1kKKl3RswJlLCKpl9LIwk return_to:http://mysite.com/Account/Login.aspx response_nonce:2011-05-12T03:56:09ZoeDC9WFOgOBaAQ assoc_handle:AOQobUdHugprvbsK2-8NCtS2uBomRDGJQGOKDmqEwxco8Rny47rdZlBp ns.ext1:http://openid.net/srv/ax/1.0 ext1.mode:fetch_response ext1.type.firstname:http://axschema.org/namePerson/first ext1.value.firstname:First ext1.type.email:http://schema.openid.net/contact/email ext1.value.email:[email protected] ext1.type.lastname:http://axschema.org/namePerson/last ext1.value.lastname:Name
This little console application re-generates the signature (using HMAC-SHA256), it needs two parameters:
Code:
using System;
public class OpenIdSignatureVerification {
public static void Main(string[] args) {
if (args.Length != 2) {
Console.Error.WriteLine("Usage: assertion_url mac_key");
Environment.Exit(1);
}
string url = args[0];
int pos = url.IndexOf('?');
if (pos == -1) {
Console.Error.WriteLine("No query string found");
Environment.Exit(1);
}
url = url.Substring(pos + 1);
Console.WriteLine(String.Format("Query string: {0}", url));
System.Collections.Generic.Dictionary<string, string> dict = new System.Collections.Generic.Dictionary<string, string>();
foreach (string part in url.Split('&')) {
string[] keyValue = part.Split('=');
if (keyValue.Length != 2) continue;
dict[keyValue[0]] = System.Web.HttpUtility.UrlDecode(keyValue[1]);
}
string hashInput = String.Empty;
string[] signed = dict["openid.signed"].Replace("%2C", ",").Split(',');
foreach (string key in signed) hashInput += key + ":" + dict["openid." + key] + "\n";
string macKey = args[1];
Console.WriteLine(String.Format("Hash input: {0}\n", hashInput));
Console.WriteLine(String.Format("MAC Key: {0}", macKey));
byte[] encodedHashInput = System.Text.Encoding.UTF8.GetBytes(hashInput);
System.Security.Cryptography.HMACSHA256 signer = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(macKey));
string hashOutput = Convert.ToBase64String(signer.ComputeHash(encodedHashInput));
Console.WriteLine(String.Format("Signature hash (expected) : {0}", dict["openid.sig"]));
Console.WriteLine(String.Format("Signature hash (calculated): {0}", hashOutput));
}
}
Upvotes: 2