Re: [OAUTH-WG] Signatures spec proposal, take 2

Eran Hammer-Lahav <eran@hueniverse.com> Fri, 24 September 2010 20:32 UTC

Return-Path: <eran@hueniverse.com>
X-Original-To: oauth@core3.amsl.com
Delivered-To: oauth@core3.amsl.com
Received: from localhost (localhost [127.0.0.1]) by core3.amsl.com (Postfix) with ESMTP id 589103A691F for <oauth@core3.amsl.com>; Fri, 24 Sep 2010 13:32:18 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.495
X-Spam-Level:
X-Spam-Status: No, score=-2.495 tagged_above=-999 required=5 tests=[AWL=0.103, BAYES_00=-2.599, HTML_MESSAGE=0.001]
Received: from mail.ietf.org ([64.170.98.32]) by localhost (core3.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id L3aYUH66Guky for <oauth@core3.amsl.com>; Fri, 24 Sep 2010 13:32:01 -0700 (PDT)
Received: from p3plex1out01.prod.phx3.secureserver.net (p3plex1out01.prod.phx3.secureserver.net [72.167.180.17]) by core3.amsl.com (Postfix) with SMTP id 6E1F33A69EE for <oauth@ietf.org>; Fri, 24 Sep 2010 13:32:00 -0700 (PDT)
Received: (qmail 32428 invoked from network); 24 Sep 2010 20:32:31 -0000
Received: from unknown (HELO smtp.ex1.secureserver.net) (72.167.180.19) by p3plex1out01.prod.phx3.secureserver.net with SMTP; 24 Sep 2010 20:32:31 -0000
Received: from P3PW5EX1MB01.EX1.SECURESERVER.NET ([10.6.135.20]) by P3PW5EX1HT001.EX1.SECURESERVER.NET ([72.167.180.19]) with mapi; Fri, 24 Sep 2010 13:32:31 -0700
From: Eran Hammer-Lahav <eran@hueniverse.com>
To: Dirk Balfanz <balfanz@google.com>, Richard Barnes <rbarnes@bbn.com>
Date: Fri, 24 Sep 2010 13:31:51 -0700
Thread-Topic: [OAUTH-WG] Signatures spec proposal, take 2
Thread-Index: ActcF6tNiBRD3wpJTXm7tZcgP6aeZAAD9llF
Message-ID: <C8C258C7.3AD10%eran@hueniverse.com>
In-Reply-To: <AANLkTimdZOuB+n8e2NRULgr7cQvwShKiEw+vQnDmCujw@mail.gmail.com>
Accept-Language: en-US
Content-Language: en
X-MS-Has-Attach:
X-MS-TNEF-Correlator:
acceptlanguage: en-US
Content-Type: multipart/alternative; boundary="_000_C8C258C73AD10eranhueniversecom_"
MIME-Version: 1.0
Cc: OAuth WG <oauth@ietf.org>
Subject: Re: [OAUTH-WG] Signatures spec proposal, take 2
X-BeenThere: oauth@ietf.org
X-Mailman-Version: 2.1.9
Precedence: list
List-Id: OAUTH WG <oauth.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/listinfo/oauth>, <mailto:oauth-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/oauth>
List-Post: <mailto:oauth@ietf.org>
List-Help: <mailto:oauth-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/oauth>, <mailto:oauth-request@ietf.org?subject=subscribe>
X-List-Received-Date: Fri, 24 Sep 2010 20:32:18 -0000

Most developers will do one of two things:

1. Completely ignore the HTTP request and just look at the signed data.
2. Come up with some simple rules to normalize both the request URI and signed data and compare them to make sure they are the same.

Why is this bad?

- This proposal recreates SOAP, only using JSON instead of XML. It treats HTTP as a stupid transport where the HTTP fields are more of a liability than an asset. This design would work much better if you had a single endpoint API with a structured payload that is signed using a magic signature. All this HTTP request construction nonsense goes away and you don't need to repeat parameters, methods, etc. This is exactly why Roy Fielding, who knows a little bit about HTTP, rejected this approach of duplicating request data in the transmission.

- At the time the application gets to look at the signed data and the HTTP request header, it is already too late for outer security layers to do their jobs. A firewall configured to block requests to certain URI paths or HTTP methods will fail to stop requests were the internal signed data is different (and potentially harmful) than the external HTTP request header. This moved the burden of enforcing such a security policy from the firewall to the application layer aware of the OAuth signature format.

- Any kind of URI normalization and comparison is hard, and will result in some failures. OAuth 1.0a has shown this difficulty, but recent experience has completely reversed the narrative this proposal is based on, that developers cannot figure it out. As I said many times before, developers just need the right motivation to spend an extra hour to figure out why their signatures fail (yes, an hour!) and providers should stop treating their developers badly and provide quality debugging information.

- I am pretty sure that the vast majority of platforms these days allow the application full access to the raw request header as sent over the wire. Before we go and design another solution like the OAuth 1.0a signature process which was based on limitations in platforms such as PHP4, we should do our homework and determine how hard it really is to access the raw request header. This proposal is an optimization to a problem that does not really exist.

- OAuth 2.0 now fully supports bearer tokens which removes the requirement to use signatures. I think it is perfectly fine to raise the deployment bar for the more advance cases where signatures are required. Let's be honest here - most of the applications currently using OAuth 1.0a will be happy with 2.0 without signatures.

- By working closer to the HTTP layer, we not only improve HTTP, but also give one more reason for HTTP libraries to add native OAuth signature support.

This is not a complete list.

EHL


On 9/24/10 11:38 AM, "Dirk Balfanz" <balfanz@google.com> wrote:



On Fri, Sep 24, 2010 at 10:08 AM, Richard L. Barnes <rbarnes@bbn.com> wrote:
I think it's more robust to verify than to generate. In option 2, you have to "guess" what the signer actually signed. To be fair, you have pretty good signals, like the HTTP request line, the Host: header, etc., but in the end, you don't _know_ that the signer really saw the same thing when they generated the signature. I can't help but feeling that that's a bit of a hack. In option 1, you always know what the signer saw when they generated the signature, and it's up to you (the verifier) to decide whether that matches your idea of what your endpoint looks like.

Generating does not imply guessing: The signer can specify what he signed without providing the data directly.  Quoting from another thread:
"
1. Signer computes signature sig_val over data object:
  { user_agent: "Mozilla", method: "GET" }
2. Signer sends { signed_fields: ['user_agent', 'method'], sig: sig_val }
3. Recipient reconstructs data object using signed_fields
4. Recipient verifies sig_val == sign(reconstructed_object)
"

If the spec is written properly, the recipient should be able to look at the names of the fields ('user_agent', 'method') and use them to reconstruct the original object.

User-agent and method are well-defined both for senders and receivers of HTTP requests. What's less well-defined is the URL, which is what Eran is objecting to. So in practice, it looks more like this:

1. Signer generates URL using some library, e.g.:
    paramsMap = new Map();
    paramsMap.put('param1', 'value1');
    paramsMap.put('param2', 'value2');

    uri = new UriBuilder()
      .setScheme(Scheme.HTTP)
      .setHost('WWW.foo.com <http://WWW.foo.com> ')
      .setPath('/somePath')
      .setQueryParams(paramsMap)
      .build().toString();
   // uri now looks something like "http://WWW.foo.com/somePath?param1=value1&param2=value2"

2. They then use a different library to send the HTTP request
    request = new GetRequest();
    request.setHeader('signed-token', sign('GET', uri));
    request.execute(uri);

The problem is that we don't know what the execute method on GetRequest does with the URI. It probably will use a library (possibly different from the one used in step 1) to decompose the URI back into its parts, so it can figure out whether to use SSL, which host and port to connect to, etc. Is it going to normalize the hostname to lowercase in the process? Is it going to escape the query parameters? Is it going to add ":80" to the Host:-header because that's the port it's going to connect to? Is it going to put the query parameters into a different order?, etc., all of which would cause the recipient of the message to put back together a _different_ URI from what the sender saw.

OAuth1 therefore defined a bunch of rules on how to "normalize" the URI to make sure that both the sender and the receiver saw the same URI even if the http library does something funny. Many people thought that those rules were too complicated. There is currently an argument over whether or not the complexity of the rules can be hidden in libraries, and I'm personally a bit on the fence on this. What I _do_ object to, more on a philosophical level, is that we can never know for sure what the http library is doing to the request, and that therefore we can never be sure whether the normalization rules we have come up with cover all the crazy libraries out there. There is a symmetric problem on the receiver side - where the servlet APIs may or may not have messed with the parameters before you get to reconstruct the URI.

The JSON token proposal does something simpler: you get to see the URI as the sender saw it (in this case with the uppercase WWW, without the :80, etc.), and you get to decide whether that matches your endpoint. So instead of wondering in what order the signer saw the query parameters when he signed them, and whether they were escaped or not, you simply check that all the query parameters that he signed (as evidenced in the JSON token) are indeed present in the HTTP request, and vice versa, etc. It's a comparable amount of work, but it seems cleaner, less hacky to me.

Dirk.


The idea of allowing signed fields to change en route to the server strikes me as a little odd.  Sure, you could ignore the method, path, and host values in HTTP and just act on the enveloped data, but at that point, why not just do away with the overhead of HTTP and run the whole thing over TCP?

--Richard