Unable to use PLAINTEXT signature with a DotNetOpenAuth ServiceProvider

C#asp.netDotnetopenauth

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDarin DimitrovView Question on Stackoverflow
Solution 1 - C#krishView Answer on Stackoverflow
Solution 2 - C#user3210324View Answer on Stackoverflow