开发者

OpenID - Generating Signature

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:

  1. 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).

  2. Should the last key value pair be follow by a line break?

  3. 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&openid.ext1.value.email=testingopenid5132011@gmail.com&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:testingopenid5132011@gmail.com\next1.type.lastname:http://axschema.org/namePerson/last\next1.value.lastname:lastname\n

The mac key as returned by the assoc开发者_运维知识库ation 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?


I had also problems generating a matching signature and finally found the solution.

  1. 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.

  2. 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.

  3. 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:myemail@gmail.com
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:

  • the complete redirect URL after the successful OpenID authentication (containing the positive assertion keys), can be copied from the web browser's address bar
  • the Base64-encoded MAC key, as returned in the prior association response

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));
    }

}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜