Search code examples
powershellcurlhmacsha1oauth-1.0a

Powershell OAuth 1.0 'one-legged' authentication with HMAC-SHA1 fails


I'm trying to write a Powershell script so I can get data from a web API that uses 'one-legged' (this means that you only have the consumer key (username) and consumer secret (password) ) OAuth1.0 authentication. I've studied the OAuth1.0 docs (https://oauth.net/core/1.0/) and then produced the following script:

$response                   = $null
$url                        = 'https://api.holidaypictures.com/api/v1.0/0'

$oauth_consumer_key         = '[email protected]'
$oauth_consumer_secret      = 'SuperSecretHash'
$oauth_nonce                = -join ((65..90) + (97..122) | Get-Random -Count 12 | % {[char]$_})
$oauth_signature_method     = 'HMAC-SHA1'
$oauth_timestamp            = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalSeconds
$oauth_token                = '' # Don't have this because 'one-legged' authentication
$oauth_token_secret         = '' # Don't have this because 'one-legged' authentication
$oauth_version              = '1.0'

$method                     = 'POST'

$base_string  = 'oauth_consumer_key=' + $oauth_consumer_key
$base_string += '&oauth_nonce=' + $oauth_nonce
$base_string += '&oauth_signature_method=' + $oauth_signature_method
$base_string += '&oauth_timestamp=' + $oauth_timestamp
$base_string += '&oauth_token=' + $oauth_token
$base_string += '&oauth_version=' + $oauth_version

$signature_base_string      = $method + '&' + [System.Web.HttpUtility]::UrlEncode($url) + '&' + [System.Web.HttpUtility]::UrlEncode($base_string)

$key = $oauth_consumer_secret + '&' + $oauth_token_secret
$hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
$hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($key);
$oauth_signature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($signature_base_string)));
$oauth_signature
$oauth_signature = [System.Web.HttpUtility]::UrlEncode($oauth_signature)

$oauth_signature

$auth  = 'OAuth '
$auth += 'oauth_consumer_key="' + $oauth_consumer_key + '",'
$auth += 'oauth_nonce="' + $oauth_nonce + '",'
$auth += 'oauth_signature="' + $oauth_signature + '",'
$auth += 'oauth_signature_method="' + $oauth_signature_method + '",'
$auth += 'oauth_timestamp="' + $oauth_timestamp + '",'
$auth += 'oauth_token="' + $oauth_token + '",'
$auth += 'oauth_version="' + $oauth_version + '"'

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("Authorization", $auth)
$body = "{`n    `"pageSize`": 10,`n `"sort`": [`n       {`n         `"key`": `"info.date`",`n           `"direction`": -1`n         `n      }`n ],`n    `"idSelection`": [],`n  `"query`": []`n}"

$response = Invoke-RestMethod -Method $method -Headers $headers -body $body -Uri $url

When I run this code the following error occurs:

Invoke-RestMethod : {"status":401,"message":"Could not authorize request","scope":"SECURITY","details":null}
At line:47 char:13
+ $response = Invoke-RestMethod -Method $method -Headers $headers -body ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (Method: POST, Reque\u2026application/json
}:HttpRequestMessage) [Invoke-RestMethod], HttpResponseException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

In other words: It fails to authenticate. Is there any who can help me out on this one? Would be kindly appreciated!

It's about the MoreApp.com API by the way... Don't know if that helps.


Solution

  • I think the issue is that [System.Web.HttpUtility]::UrlEncode is not encoding to RFC3986 as required by OAuth. For one thing it does not capitalize the percent encodings. According to RFC3986, "URI producers and normalizers should use uppercase hexadecimal digits for all percent-encodings." I am not sure if it is escaping all of the same characters that RFC3986 requires either.

    There is a function "ConvertTo-PSUrlEncodedString" in the PSAuth module that does RFC3986 encoding. I stole that function and brought it into your script, replacing all of the [System.Web.HttpUtility]::UrlEncode with the ConvertTo-PSUrlEncodedString function. Then it worked. But it might just be better to use PSAuth instead since it does some additional things like sort the parameters, add port numbers, etc to ensure that everything is compliant with OAuth.