Unable to use PLAINTEXT signature with a DotNetOpenAuth ServiceProvider
Asked Answered
L

2

69

I am building an OAuth 1.0(a) authorization server using DotNetOpenAuth (NuGet package DotNetOpenAuth.OAuth.ServiceProvider, version = 4.1.4.12333). The server is hosted in an ASP.NET application but that's irrelevant to the question.

My ServiceProvider is configured like this:

private ServiceProvider GetServiceProvider()
{
    var baseUri = "http://myauth.com";
    return new ServiceProvider(
        new ServiceProviderDescription
        {
            UserAuthorizationEndpoint = new MessageReceivingEndpoint(
                new Uri(baseUri + "/get_request_token"), 
                HttpDeliveryMethods.GetRequest
            ),
            RequestTokenEndpoint = new MessageReceivingEndpoint(
                new Uri(baseUri + "/authorize"), 
                HttpDeliveryMethods.PostRequest
            ),
            AccessTokenEndpoint = new MessageReceivingEndpoint(
                new Uri(baseUri + "/get_token"), 
                HttpDeliveryMethods.PostRequest
            ),
            ProtocolVersion = ProtocolVersion.V10a,
            TamperProtectionElements = new ITamperProtectionChannelBindingElement[] 
            {
                new PlaintextSigningBindingElement(),
                new HmacSha1SigningBindingElement(),
            },
        },
        tokenManager,
        new OAuthServiceProviderMessageFactory(tokenManager)
    );
}

The relevant part of my get_request_token endpoint looks like this:

var serviceProvider = GetServiceProvider();
var tokenRequest = serviceProvider.ReadTokenRequest();

Now when a consumer sends the following request to this endpoint:

GET /get_request_token?oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC&oauth_timestamp=1356006599&oauth_consumer_key=sampleconsumer&oauth_signature_method=plaintext&oauth_signature=samplesecret%26&oauth_version=1.0&oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1 HTTP/1.1

Host: localhost:8180
Connection: close

(broken for clarity):

oauth_nonce=C5657420BCE5F3224914304376B5334696B09B7FFC17C105A7F9629A008869DC
oauth_timestamp=1356006599
oauth_consumer_key=sampleconsumer
oauth_signature_method=plaintext
oauth_signature=samplesecret%26
oauth_version=1.0
oauth_callback=http%3a%2f%2flocalhost%3a30103%2fCustomOAuth1

The serviceProvider.ReadTokenRequest() method throws an exception:

The UnauthorizedTokenRequest message required protections {All} but the channel could only apply {Expiration, ReplayProtection}.
   at DotNetOpenAuth.Messaging.Channel.ProcessIncomingMessage(IProtocolMessage message)
   at DotNetOpenAuth.Messaging.Channel.ReadFromRequest(HttpRequestBase httpRequest)
   at DotNetOpenAuth.Messaging.Channel.TryReadFromRequest[TRequest](HttpRequestBase httpRequest, TRequest& request)
   at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest(HttpRequestBase request)
   at DotNetOpenAuth.OAuth.ServiceProvider.ReadTokenRequest()
   at OAuthServers.OAuth1.Services.OAuth1Service.Any(GetRequestTokenRequest request)
   at lambda_method(Closure , Object , Object )
   at ServiceStack.ServiceHost.ServiceRunner`1.Execute(IRequestContext requestContext, Object instance, TRequest request)

On the other hand if the client sends the following request:

GET /get_request_token?oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken&oauth_consumer_key=sampleconsumer&oauth_nonce=rGFvxlWm&oauth_signature_method=HMAC-SHA1&oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d&oauth_version=1.0&oauth_timestamp=1356007830 HTTP/1.1

Host: localhost:8180
Connection: close

(broken for clarity):

oauth_callback=http%3a%2f%2flocalhost%3a65271%2foauth1%2fHandleAccessToken
oauth_consumer_key=sampleconsumer
oauth_nonce=rGFvxlWm
oauth_signature_method=HMAC-SHA1
oauth_signature=HV%2f5Vq%2b0cF3NrtiISE9k4jmgCrY%3d
oauth_version=1.0
oauth_timestamp=1356007830

it succeeds.

As you can see the only difference between those 2 requests is the oauth_signature_method being used. In the first case PLAINTEXT is used whereas in the second HMAC-SHA1.

Is it possible to make DotNetOpenAuth accept a PLAINTEXT signature method for the request token endpoint along with the GET verb (even if the OAuth 1.0(a) specification recommends POST to be used for this endpoint)? Is there some config option that could relax this requirement on the server?

At the moment modifying the client is not an option for me.

Lanielanier answered 20/12, 2012 at 13:8 Comment(6)
Are you using SSL? plain text requires HTTPS URLs.Nance
No, I am not using SSL for the testing site. I have setup relaxSslRequirements="true" in my web.config on the <messaging> element. Let me try activating SSL to see if this works.Lanielanier
Activating SSL on the server didn't solve the problem. It's worth mentioning that I am using an invalid SSL certificate on the server (IIS Express) and ignored SSL errors from the client using the ServicePointManager.ServerCertificateValidationCallback but normally this shouldn't affect the server, should it? Anyway I will end up modifying the client to use HMAC-SHA1, but just wanted to know if this could be solved without modifying the client (out of personal interest and to better know how DotNetOpenAuth works).Lanielanier
My other idea is that your server side code adds two signing binding elements (plaintext and hmac-sha1). Can you try lsting just the plaintext element and see if that gets the client to work?Nance
I've already tried with plaintext signing element only as well as hmac-sha1 only. Neither of them worked.Lanielanier
Is this answer of use to you? https://mcmap.net/q/281949/-authentication-to-freshbooks-via-dotnetopenauthTiannatiara
C
1

Following block of code may help you to generate plain text signature

public static string GetSignature(OAuthSignatureMethod signatureMethod,             AuthSignatureTreatment signatureTreatment, string signatureBase, string consumerSecret, string tokenSecret)
{
    if (tokenSecret.IsNullOrBlank())
    {
        tokenSecret = String.Empty;
    }

    consumerSecret = UrlEncodeRelaxed(consumerSecret);
    tokenSecret = UrlEncodeRelaxed(tokenSecret);

    string signature;
    switch (signatureMethod)
    {
        case OAuthSignatureMethod.HmacSha1:
        {
            var crypto = new HMACSHA1();
            var key = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);

            crypto.Key = _encoding.GetBytes(key);
            signature = signatureBase.HashWith(crypto);

            break;
        }
        case OAuthSignatureMethod.PlainText:
        {
            signature = "{0}&{1}".FormatWith(consumerSecret, tokenSecret);

            break;
        }
        default:
            throw new NotImplementedException("Only HMAC-SHA1 is currently supported.");
        }

        var result = signatureTreatment == OAuthSignatureTreatment.Escaped
            ? UrlEncodeRelaxed(signature)
            : signature;

        return result;
    }
Carnassial answered 20/9, 2013 at 12:34 Comment(0)
C
1

OAuth Authentication is done in three steps:

  1. The Consumer obtains an unauthorized Request Token.

  2. The User authorizes the Request Token.

  3. The Consumer exchanges the Request Token for an Access Token.

So here's what that would look like:

public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();

public InMemoryTokenManager(string consumerKey, string consumerSecret)
{
    if (String.IsNullOrEmpty(consumerKey))
    {
        throw new ArgumentNullException("consumerKey");
    }

    this.ConsumerKey = consumerKey;
    this.ConsumerSecret = consumerSecret;
}

public string ConsumerKey { get; private set; }

public string ConsumerSecret { get; private set; }

#region ITokenManager Members

public string GetConsumerSecret(string consumerKey)
{
    if (consumerKey == this.ConsumerKey)
    {
        return this.ConsumerSecret;
    }
    else
    {
        throw new ArgumentException("Unrecognized consumer key.", "consumerKey");
    }
}

public string GetTokenSecret(string token)
{
    return this.tokensAndSecrets[token];
}

public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
    this.tokensAndSecrets[response.Token] = response.TokenSecret;
}

public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
    this.tokensAndSecrets.Remove(requestToken);
    this.tokensAndSecrets[accessToken] = accessTokenSecret;
}

/// <summary>
/// Classifies a token as a request token or an access token.
/// </summary>
/// <param name="token">The token to classify.</param>
/// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
public TokenType GetTokenType(string token)
{
    throw new NotImplementedException();
}

#endregion

#region IOpenIdOAuthTokenManager Members

public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization)
{
    this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
}

#endregion
}
Cottar answered 18/1, 2014 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.