02-12-2021 03:56 PM
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?
Solved! Go to Solution.
02-18-2021 01:46 PM
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)"
02-15-2021 05:12 AM
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 )
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!
02-15-2021 10:01 AM
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==
02-18-2021 01:46 PM
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)"
05-27-2021 10:53 AM
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.
02-19-2021 06:50 AM
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.
02-19-2021 02:21 PM
I was able to get a successful /auth/v2/check using that function.
03-31-2021 01:11 PM
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.
03-31-2021 01:35 PM
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.
05-18-2022 07:45 AM
Thanks @Michael_Maher !
If anyone else also is using PowerShell with a different Culture/language:
Delete the $date variable and add these two lines:
$culture = [CultureInfo]'en-us'
$date = (Get-Date).ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss -0000", $culture)
and then add -UseBasicParsing to the WebRequest:
$wr = Invoke-WebRequest @contructWebRequest -UseBasicParsing
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide