Powershell API Authorization Encoding

I am working in an environment where I am unable to load custom modules, so the github solutions really won’t work for me.

The authentication encoding method is a horribly twisted process.

The latest attempt is (Keys and data are from the Authentication example):

$intKey = "■■■■■■■■■■■■■■■■■■■■"
$secretkey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$hostname = "■■■■■■■■■■■■■■■■■■■■■■■■■■■■"
$path = "/accounts/v1/account/list"
$params = 'realname=First%20Last&username=root'
$method = "POST"
$date="Tue, 21 Aug 2012 17:29:18 -0000"

$lines = @($date,$method,$hostname,$path,$params)
$jlines  = [string]::Join("`n", $lines)

$hmacsha1 = New-Object System.Security.Cryptography.HMACSHA1
$hmacsha1.Key = [Text.Encoding]::ASCII.GetBytes($secretkey)
$signature = $hmacsha1.ComputeHash([Text.Encoding]::ASCII.GetBytes($jlines))
$hash_hex = [System.BitConverter]::ToString($signature) -replace '-', ''
$auth = $integration + ":" + $hash_hex
[byte[]]$plainText_bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
$return = [System.Convert]::ToBase64String($plainText_bytes)
$authorize = "Authorization : Basic " + $return

The Result in the example is:
Authorization: Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6MmQ5N2Q2MTY2MzE5NzgxYjVhM2EwN2FmMzlkMzY2ZjQ5MTIzNGVkYw==

However the result I am getting is:
Authorization: Basic OjBGNzBCRTUzQTE1QkYxMzY3MkIwMkNCQ0EyOTFGODFCREUzREU5RDQ=

Which is definitely not the expected result. An idea why it appears to be encoding differently?

Phew I just noticed you said the secrets in your post are from the authentication examples provided in the Duo docs! FYI I edited your post to remove the secret key before I saw that, because you should never store or transmit your secrets in an insecure system that can be accessed by the public. This is to protect the integrity and security of your Duo integration (for the folks reading this at home :wink: )

Unfortunately I cannot help much with your API question as this is outside the realm of my personal expertise. I’ll take a look at our docs and support cases though and follow up here if I find anything that is helpful for you!

I have tried a variation of the encryption documented in the duo-psmodule documented on github.
Duo-PSModule/Duo.psm1 at master · mbegan/Duo-PSModule · GitHub
Again all the keys and information are from the Duo Documentation.

$intKey = "■■■■■■■■■■■■■■■■■■■■"
$secretkey = "Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep"
$hostname = "■■■■■■■■■■■■■■■■■■■■■■■■■■■■"
$path = "/accounts/v1/account/list"
$params = 'realname=First%20Last&username=root'
$method = "POST"
$date='Tue, 21 Aug 2012 17:29:18 -0000'
$lines = @($date,$method,$hostname,$path,$params)
$jlines  = [string]::Join("`n", $lines)

Output of $jlines at this point is:

Tue, 21 Aug 2012 17:29:18 -0000
POST
■■■■■■■■■■■■■■■■■■■■■■■■■■■■
/accounts/v1/account/list
realname=First%20Last&username=root

[byte[]]$key_bytes = [System.Text.Encoding]::UTF8.GetBytes($secretkey)
[byte[]]$data_bytes = [System.Text.Encoding]::UTF8.GetBytes($jlines)
$hmacsha1 = New-Object System.Security.Cryptography.HMACSHA1
$hmacsha1.Key = $key_bytes
$hash_bytes = $hmacsha1.ComputeHash($data_bytes)
$hash_hex = [System.BitConverter]::ToString($hmacsha1.Hash)
$return = $hash_hex.Replace("-","").ToLower()

The output of $return at this point is:
0f70be53a15bf13672b02cbca291f81bde3de9d4

Which of course does not meet the expected output of RElXSjhYNkFFWU9SNU9NQzZUUTE6MmQ5N2Q2MTY2MzE5NzgxYjVhM2EwN2FmMzlkMzY2ZjQ5MTIzNGVkYw==

Its not nice to work with I agree.

Here is a reusable PowerShell function you can use

function New-DuoRequest(){
    param(
        [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            $apiHost,
        
        [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [ValidateNotNull()]
            $apiEndpoint,
        
        [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            $apiKey,
        
        [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [ValidateNotNull()]
            $apiSecret,
        
        [Parameter(Mandatory=$false,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [ValidateNotNull()]
            $requestMethod = 'GET',
        
        [Parameter(Mandatory=$false,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [ValidateNotNull()]
            [System.Collections.Hashtable]$requestParams
    )
    $date = (Get-Date).ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss -0000")
    $formattedParams = ($requestParams.Keys | Sort-Object | ForEach-Object {$_ + "=" + [uri]::EscapeDataString($requestParams.$_)}) -join "&"
    
    #DUO Params formatted and stored as bytes with StringAPIParams
    $requestToSign = (@(
        $Date.Trim(),
        $requestMethod.ToUpper().Trim(),
        $apiHost.ToLower().Trim(),
        $apiEndpoint.Trim(),
        $formattedParams
    ).trim() -join "`n").ToCharArray().ToByte([System.IFormatProvider]$UTF8)
 
    $hmacsha1 = [System.Security.Cryptography.HMACSHA1]::new($apiSecret.ToCharArray().ToByte([System.IFormatProvider]$UTF8))
    $hmacsha1.ComputeHash($requestToSign) | Out-Null
    $authSignature = [System.BitConverter]::ToString($hmacsha1.Hash).Replace("-", "").ToLower()

    $authHeader = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(('{0}:{1}' -f $apiKey, $authSignature)))

    $httpRequest = @{
        URI         = ('https://{0}{1}' -f $apiHost, $apiEndpoint)
        Headers     = @{
            "X-Duo-Date"    = $Date
            "Authorization" = "Basic $authHeader"
        }
        Body = $requestParams
        Method      = $requestMethod
        ContentType = 'application/x-www-form-urlencoded'
    }
    
    $httpRequest
}

# Calling the function

$values = @{
    
    apiHost = 'myapi.duo$ecurity.com'
    apiEndpoint     = '/admin/v1/users'
    requestMethod   = 'GET'
    requestParams   = @{username="$env:Username"}
    apiSecret       = '*************'
    apiKey          = '*************'
}
$contructWebRequest = New-DuoRequest @values

# Send the request
$wr = Invoke-WebRequest @contructWebRequest
Write-host "Your User ID is $((($wr.Content | ConvertFrom-Json).response).user_id)"

When I use the values from the example

$values = @{

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■'
apiEndpoint     = '/accounts/v1/account/list'
requestMethod   = 'POST'
requestParams   = @{realname="First%20Last";username="root"}
apiSecret       = 'Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep'
apiKey          = '■■■■■■■■■■■■■■■■■■■■'

}

And I force $date to “Tue, 21 Aug 2012 17:29:18 -0000” in the function, I get a value of RElXSjhYNkFFWU9SNU9NQzZUUTE6ZmVmNWZjOTU0MjdhMjhlMDk5ODQyNDYzZWMwOGRmYmE5ZjFlYTJkNA== which does not appear to agree with the Examples value of RElXSjhYNkFFWU9SNU9NQzZUUTE6MmQ5N2Q2MTY2MzE5NzgxYjVhM2EwN2FmMzlkMzY2ZjQ5MTIzNGVkYw==

But it is a lot closer.

I was able to get a successful /auth/v2/check using that function.

Confirmed… if I put in my own ikey, skey and Host (■■■■■■■■■■■■■■■■■■■■■■■■■■) the function returns my user ID, or at least I’m assuming it is a user ID; (20 character string uppercase letters and numbers) not sure where to verify it.

Using that function I was able to generate a working Powershell Lambda for AWS Client VPN Endpoint Client Connection Handler to use Duo for MFA.