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 = "■■■■■■■■■■■■■■■■■■■■"
$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:

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

[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:

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(){
            $requestMethod = 'GET',
    $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 = (@(
    ).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'

# 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.

Hi Michael, I just wanted to thank you for your post. The script you provided was incredibly helpful in resolving an issue with my company’s environment.

In case it can prove useful to others, here was our problem. When first setting up a directory sync for Duo, I imported the mail attribute from our Active Directory to the alias1 attribute and I imported UserPrincipalName for our username attribute. At the time, these were different values. However we recently began a project to update our UPNs to use a new domain suffix. In the end this caused the UPN to match the values in our mail attributes. In our testing phase we flipped the domain suffix in our UPNs from the old value to the new value, which led to my username (UPN) and alias1 (mail) value being identical. Duo doesn’t permit this, so two things happened when I synced my account from AD to Duo: 1) Duo failed to update my username to the new UPN value and 2) my Duo account was moved to the trash because my previous username was no longer present in AD.

By removing alias1 from our directory sync and customizing the script Michael provided to remove the now read-writable alias1 field, I was able to automate the removal of the no longer needed alias1 attribute from all Duo accounts in our environment.