OpenID security: RPs don't ensure that auth was approved by the actual provider

722 views Asked by At

General Description

I implemented an OP (OpenID Provider), using DotNetOpenAuth. I am testing it against example RPs (relying parties), such as Drupal's OpenID login and the OpenIdRelyingPartyWebForms project in the DotNetOpenAuth's Samples solution.

The problem is that, as far as I can tell, when a browser bounces against my OP and sends a "successful authentication" request (mode: id_res, claimed_id: smth, etc.) back to the RP, the RP doesn't try to perform a server-side request to the OP and ask if it has actually authenticated the user. I can see that there is a openid.sig signature returned from the OP, but again, I don't see how the RP could possibly verify it, since it didn't exchange keys with the OP.

So the question is: Is there some setting on the OP's side which I can enable to make the workflow secure?

Technical Details

I'm using Wireshark to sniff HTTP traffic on the RP side. There's no HTTPS, so I can see and read all messages. Below you can see what happens exactly. B = Browser, OP = OpenID Provider, RP = Relying Party. The domain names are replaced with *.example.com.

  1. (B –> RP) User tries to visit a members-only resource on the relying party. He inputs the OP endpoint which the browser posts to the RP.

    openid_identifier: http://OP.example.com/OpenId/Provider.aspx?xrds

  2. (RP –> OP –> RP) RP issues a server-side request to my OP which returns an XRDS document. I cannot see anything similar to secret key exchange here.

    <?xml version="1.0" encoding="UTF-8"?>
    <xrds:XRDS
        xmlns:xrds="xri://$xrds"
        xmlns:openid="http://openid.net/xmlns/1.0"
        xmlns="xri://$xrd*($v*2.0)">
        <XRD>
            <Service priority="10">
                <Type>http://specs.openid.net/auth/2.0/server</Type>
                <Type>http://openid.net/extensions/sreg/1.1</Type>
                <URI>http://OP.example.com/OpenId/Provider.aspx</URI>
            </Service>
        </XRD>
    </xrds:XRDS>
    
  3. (RP –> B –> OP) Relying party 302-redirects the user to OP's /OpenId/Provider.aspx?[params] URL, where params are the following:

    openid.claimed_id: http://specs.openid.net/auth/2.0/identifier_select
    openid.identity: http://specs.openid.net/auth/2.0/identifier_select
    openid.assoc_handle: {634730422000625000}{jkQC1Q==}{32}
    openid.return_to: http://RP.example.com/login.aspx?ReturnUrl=%2FMembersOnly%2FDefault.aspx&dnoa.receiver=ctl00_Main_OpenIdLogin1&dnoa.UsePersistentCookie=Session&dnoa.userSuppliedIdentifier=http%3A%2F%2FOP.example.com%2FOpenId%2FProvider.aspx%3Fxrds
    openid.realm: http://RP.example.com/
    openid.mode: checkid_setup
    openid.ns: http://specs.openid.net/auth/2.0
    openid.ns.sreg: http://openid.net/extensions/sreg/1.1
    openid.sreg.policy_url: http://RP.example.com/PrivacyPolicy.aspx
    openid.sreg.required: email,gender,postcode,timezone
    
  4. (OP –> B –> RP) Provider authenticates the user and 302-redirects him back to the RP with the following URL parameters:

    ReturnUrl: /MembersOnly/Default.aspx
    dnoa.receiver: ctl00_Main_OpenIdLogin1
    dnoa.UsePersistentCookie: Session
    dnoa.userSuppliedIdentifier: http://OP.example.com/OpenId/Provider.aspx?xrds
    openid.claimed_id: http://OP.example.com/OpenId/User.aspx/2925
    openid.identity: http://OP.example.com/OpenId/User.aspx/2925
    openid.sig: pWJ0ugjQATKGgRSW740bml9LDsSxFiJ+a9OLO6NlsvY=
    openid.signed: claimed_id,identity,assoc_handle,op_endpoint,return_to,response_nonce,ns.sreg,sreg.nickname,sreg.email
    openid.assoc_handle: {634730422000625000}{jkQC1Q==}{32}
    openid.op_endpoint: http://OP.example.com/OpenId/Provider.aspx
    openid.return_to: http://RP.example.com/login.aspx?ReturnUrl=%2FMembersOnly%2FDefault.aspx&dnoa.receiver=ctl00_Main_OpenIdLogin1&dnoa.UsePersistentCookie=Session&dnoa.userSuppliedIdentifier=http%3A%2F%2FOP.example.com%2FOpenId%2FProvider.aspx%3Fxrds
    openid.response_nonce: 2012-05-19T16:40:11ZSfsL4BK1
    openid.mode: id_res
    openid.ns: http://specs.openid.net/auth/2.0
    openid.ns.sreg: http://openid.net/extensions/sreg/1.1
    openid.sreg.nickname: [email protected]
    openid.sreg.email: [email protected]
    
  5. (RP –> OP) The RP performs a server-side HTTP request to the OP. There is no data transferred, just a GET request to the previously acquired user identity URL. Why does it make this request at all, by the way?

    GET /OpenId/User.aspx/2925 HTTP/1.1
    
  6. (OP –> RP) The OP replies with another XRDS document:

    <xrds:XRDS
        xmlns:xrds="xri://$xrds"
        xmlns:openid="http://openid.net/xmlns/1.0"
        xmlns="xri://$xrd*($v*2.0)">
        <XRD>
            <Service priority="10">
                <Type>http://specs.openid.net/auth/2.0/signon</Type>
                <Type>http://openid.net/extensions/sreg/1.1</Type>
                <URI>http://OP.example.com/OpenId/Provider.aspx</URI>
            </Service>
            <Service priority="20">
                <Type>http://openid.net/signon/1.0</Type>
                <Type>http://openid.net/extensions/sreg/1.1</Type>
                <URI>http://OP.example.com/OpenId/Provider.aspx</URI>
            </Service>
        </XRD>
    </xrds:XRDS>
    
  7. (RP –> B) That's it. The user is authorized and RP shows him the members-only resource.

2

There are 2 answers

1
Andrew Arnott On BEST ANSWER

RPs can operate in stateful or stateless modes (also known as smart and dumb modes, respectively). Check out network flow charts for each.

There is a one-time key exchange between RP and OP, provided the RP is operating in stateful mode. If in stateless mode, you'll see a message from the RP to the OP after each authentication to verify the assertion's signature.

Regarding your question on #5 (the HTTP HEAD request to the claimed identifier) this is the DotNetOpenAuth RP checking that the OP is authoritative for the identity it is asserting. Since it had previously pulled this URL, the cache kicks in and avoids the content transferral.

0
Dmytro Shevchenko On

I'm feeling stupid now, I missed something basic—there is a key exchange between RP and OP, but it happens only once, and then the key is cached at both sides for some time.

So my implementation of OpenID provider turns out to be secure :)