Skip to content

Instantly share code, notes, and snippets.

@jenky
Last active April 12, 2024 18:03
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jenky/a4465f73adf90206b3e98c3d36a3be4f to your computer and use it in GitHub Desktop.
Save jenky/a4465f73adf90206b3e98c3d36a3be4f to your computer and use it in GitHub Desktop.
AWS Cognito Identity SRP authentication helper
<?php
use Aws\AwsClient;
use Aws\Result;
use Carbon\Carbon;
use phpseclib3\Math\BigInteger;
class AwsCognitoIdentitySRP
{
const N_HEX = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1'.
'29024E088A67CC74020BBEA63B139B22514A08798E3404DD'.
'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245'.
'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED'.
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D'.
'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F'.
'83655D23DCA3AD961C62F356208552BB9ED529077096966D'.
'670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B'.
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9'.
'DE2BCBF6955817183995497CEA956AE515D2261898FA0510'.
'15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64'.
'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7'.
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B'.
'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C'.
'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31'.
'43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
const G_HEX = '2';
const INFO_BITS = 'Caldera Derived Key';
/**
* @var \phpseclib3\Math\BigInteger
*/
protected $N;
/**
* @var \phpseclib3\Math\BigInteger
*/
protected $g;
/**
* @var \phpseclib3\Math\BigInteger
*/
protected $k;
/**
* @var \phpseclib3\Math\BigInteger
*/
protected $a;
/**
* @var \phpseclib3\Math\BigInteger
*/
protected $A;
/**
* @var string
*/
protected $clientId;
/**
* @var string
*/
protected $poolName;
/**
* @var \Aws\AwsClient
*/
protected $client;
/**
* Create new AWS CognitoIDP instance.
*
* @param \Aws\AwsClient $client
* @param string $clientId
* @param string $poolName
* @return void
*/
public function __construct(AwsClient $client, string $clientId, string $poolName)
{
$this->N = new BigInteger(static::N_HEX, 16);
$this->g = new BigInteger(static::G_HEX, 16);
$this->k = new BigInteger($this->hexHash('00'.static::N_HEX.'0'.static::G_HEX), 16);
$this->smallA();
$this->largeA();
$this->client = $client;
$this->clientId = $clientId;
$this->poolName = $poolName;
}
/**
* Get random a value.
*
* @return \phpseclib3\Math\BigInteger
*/
public function smallA(): BigInteger
{
if (is_null($this->a)) {
$this->a = $this->generateRandomSmallA();
}
return $this->a;
}
/**
* Get the client's public value A with the generated random number a.
*
* @return \phpseclib3\Math\BigInteger
*/
public function largeA(): BigInteger
{
if (is_null($this->A)) {
$this->A = $this->calculateA($this->smallA());
}
return $this->A;
}
/**
* Generate random bytes as hexadecimal string.
*
* @param int $bytes
* @return \phpseclib3\Math\BigInteger
*/
public function bytes(int $bytes = 32): BigInteger
{
$bytes = bin2hex(random_bytes($bytes));
return new BigInteger($bytes, 16);
}
/**
* Converts a BigInteger (or hex string) to hex format padded with zeroes for hashing.
*
* @param \phpseclib3\Math\BigInteger|string $longInt
* @return string
*/
public function padHex($longInt): string
{
$hashStr = $longInt instanceof BigInteger ? $longInt->toHex() : $longInt;
if (strlen($hashStr) % 2 === 1) {
$hashStr = '0'.$hashStr;
} elseif (strpos('89ABCDEFabcdef', $hashStr[0] ?? '') !== false) {
$hashStr = '00'.$hashStr;
}
return $hashStr;
}
/**
* Calculate a hash from a hex string.
*
* @param string $value
* @return string
*/
public function hexHash(string $value): string
{
return $this->hash(hex2bin($value));
}
/**
* Calculate a hash from string.
*
* @param string $value
* @return string
*/
public function hash($value): string
{
$hash = hash('sha256', $value);
return str_repeat('0', 64 - strlen($hash)).$hash;
}
/**
* Performs modulo between big integers.
*
* @param \phpseclib3\Math\BigInteger $a
* @param \phpseclib3\Math\BigInteger $b
* @return \phpseclib3\Math\BigInteger
*/
protected function mod(BigInteger $a, BigInteger $b): BigInteger
{
return $a->powMod(new BigInteger(1), $b);
}
/**
* Generate a random big integer.
*
* @return \phpseclib3\Math\BigInteger
*/
public function generateRandomSmallA(): BigInteger
{
return $this->mod($this->bytes(128), $this->N);
}
/**
* Calculate the client's public value A = g^a%N.
*
* @param \phpseclib3\Math\BigInteger $a
* @return \phpseclib3\Math\BigInteger
*
* @throws \InvalidArgumentException
*/
public function calculateA(BigInteger $a): BigInteger
{
$A = $this->g->powMod($a, $this->N);
if ($this->mod($a, $this->N)->equals(new BigInteger(0))) {
throw new \InvalidArgumentException('Public key failed A mod N == 0 check.');
}
return $A;
}
/**
* Calculate the client's value U which is the hash of A and B.
*
* @param \phpseclib3\Math\BigInteger $A
* @param \phpseclib3\Math\BigInteger $B
* @return \phpseclib3\Math\BigInteger
*/
public function calculateU(BigInteger $A, BigInteger $B): BigInteger
{
$A = $this->padHex($A);
$B = $this->padHex($B);
return new BigInteger($this->hexHash($A.$B), 16);
}
/**
* Extract the pool ID from pool name.
*
* @return null|string
*/
protected function poolId(): ?string
{
return explode('_', $this->poolName)[1] ?? null;
}
/**
* Authenticate user with given username and password.
*
* @param string $username
* @param string $password
* @return \Aws\Result
*
* @throws \RuntimeException
*/
public function authenticateUser(string $username, string $password): Result
{
$result = $this->client->initiateAuth([
'AuthFlow' => 'USER_SRP_AUTH',
'ClientId' => $this->clientId,
'UserPoolId' => $this->poolName,
'AuthParameters' => [
'USERNAME' => $username,
'SRP_A' => $this->largeA()->toHex(),
],
]);
if ($result->get('ChallengeName') != 'PASSWORD_VERIFIER') {
throw new \RuntimeException("ChallengeName `{$result->get('ChallengeName')}` is not supported.");
}
return $this->client->respondToAuthChallenge([
'ChallengeName' => 'PASSWORD_VERIFIER',
'ClientId' => $this->clientId,
'ChallengeResponses' => $this->processChallenge($result, $password)
]);
}
/**
* Generate authentication challenge response params.
*
* @param \Aws\Result $result
* @param string $password
* @return array
*/
protected function processChallenge(Result $result, string $password): array
{
$challengeParameters = $result->get('ChallengeParameters');
$time = Carbon::now('UTC')->format('D M j H:i:s e Y');
$secretBlock = base64_decode($challengeParameters['SECRET_BLOCK']);
$userId = $challengeParameters['USER_ID_FOR_SRP'];
$hkdf = $this->getPasswordAuthenticationKey(
$userId,
$password,
$challengeParameters['SRP_B'],
$challengeParameters['SALT']
);
$msg = $this->poolId().$userId.$secretBlock.$time;
$signature = hash_hmac('sha256', $msg, $hkdf, true);
return [
'TIMESTAMP' => $time,
'USERNAME' => $userId,
'PASSWORD_CLAIM_SECRET_BLOCK' => $challengeParameters['SECRET_BLOCK'],
'PASSWORD_CLAIM_SIGNATURE' => base64_encode($signature),
];
}
/**
* Calculates the final hkdf based on computed S value, and computed U value and the key.
*
* @param string $username
* @param string $password
* @param string $server
* @param string $salt
* @return string
*
* @throws \RuntimeException
*/
protected function getPasswordAuthenticationKey(string $username, string $password, string $server, string $salt): string
{
$u = $this->calculateU($this->largeA(), $serverB = new BigInteger($server, 16));
if ($u->equals(new BigInteger(0))) {
throw new \RuntimeException('U cannot be zero.');
}
$usernamePassword = sprintf('%s%s:%s', $this->poolId(), $username, $password);
$usernamePasswordHash = $this->hash($usernamePassword);
$x = new BigInteger($this->hexHash($this->padHex($salt).$usernamePasswordHash), 16);
$gModPowXN = $this->g->modPow($x, $this->N);
$intValue2 = $serverB->subtract($this->k->multiply($gModPowXN));
$s = $intValue2->modPow($this->smallA()->add($u->multiply($x)), $this->N);
return $this->computeHkdf(
hex2bin($this->padHex($s)),
hex2bin($this->padHex($u))
);
}
/**
* Standard hkdf algorithm.
*
* @param string $ikm
* @param string $salt
* @return string
*/
protected function computeHkdf(string $ikm, string $salt): string
{
return hash_hkdf('sha256', $ikm, 16, static::INFO_BITS, $salt);
}
}
@jenky
Copy link
Author

jenky commented Jul 16, 2021

We were able to fix the response Incorrect username or password by fixing the timestamp in the challenge response.

Add Carbon in the beginng of the file

use Carbon\Carbon;

and change line 286 from

$time = now()->tz('UTC')->format('D M j H:i:s e Y');

to

$time = Carbon::now()->tz('UTC')->format('D M j H:i:s e Y');

Yes. It should be Carbon::now() for general usage.

@eschricker
Copy link

The code also works with phpseclib3. Then the use statement and the return types using BigInteger needs to be adjusted.

Under which license is the code published?

@jenky
Copy link
Author

jenky commented Aug 19, 2021

The code also works with phpseclib3. Then the use statement and the return types using BigInteger needs to be adjusted.

Under which license is the code published?

Just do whatever you want with it, that's why I published it as public gist. Maybe leave a backlink to this gist would be great.

@eschricker
Copy link

Thanks, that's great.
The link is already documented in the code in case something might change in the future.

@steveWinter
Copy link

Thanks so much for this @jenky I've spent all day doing pretty much exactly what you said - reverse engineering the JavaScript library, and I was stuck getting errors with the password claim signature - this saved me a bunch of time (and if i'd found it this morning would have saved me even more).

How do I buy you a beer?

@jenky
Copy link
Author

jenky commented Oct 12, 2021

Thanks so much for this @jenky I've spent all day doing pretty much exactly what you said - reverse engineering the JavaScript library, and I was stuck getting errors with the password claim signature - this saved me a bunch of time (and if i'd found it this morning would have saved me even more).

How do I buy you a beer?

Cool, I'm happy to see that my little code would solve your problem. You can buy me a beer here if you can make it, otherwise it's fine.

Happy coding!

@mtindall89
Copy link

mtindall89 commented Nov 11, 2021

Hello - When I try and use this library, i keep getting a PHP fatal Allowed memory size of 1073741824 bytes exhauted - any ideas? It happens particularly on the authenticateUser ftn call

$this->awsClient = new CognitoIdentityProviderClient([
   'credentials' => false,
   'region' => $this->region,
    'version' => 'latest'
]);
  
$srp = new \AwsCognitoIdentitySRP($this->awsClient, $this->clientId, $this->poolId);

$result = $srp->authenticateUser($this->username, $this->password);

if (!$result) {
    throw new \RuntimeException('Unable to obtain access token from AWS CognitoIdp.');
}

@jenky
Copy link
Author

jenky commented Nov 11, 2021

Hello - When I try and use this library, i keep getting a PHP fatal Allowed memory size of 1073741824 bytes exhauted - any ideas? It happens particularly on the authenticateUser ftn call

$this->awsClient = new CognitoIdentityProviderClient([
   'credentials' => false,
   'region' => $this->region,
    'version' => 'latest'
]);
  
$srp = new \AwsCognitoIdentitySRP($this->awsClient, $this->clientId, $this->poolId);

$result = $srp->authenticateUser($this->username, $this->password);

if (!$result) {
    throw new \RuntimeException('Unable to obtain access token from AWS CognitoIdp.');
}

What is your PHP version? And I think cognito IDP doesn't have the latest version. You should use the version from the code example or remove the version option.

@mtindall89
Copy link

Hello - When I try and use this library, i keep getting a PHP fatal Allowed memory size of 1073741824 bytes exhauted - any ideas? It happens particularly on the authenticateUser ftn call

$this->awsClient = new CognitoIdentityProviderClient([
   'credentials' => false,
   'region' => $this->region,
    'version' => 'latest'
]);
  
$srp = new \AwsCognitoIdentitySRP($this->awsClient, $this->clientId, $this->poolId);

$result = $srp->authenticateUser($this->username, $this->password);

if (!$result) {
    throw new \RuntimeException('Unable to obtain access token from AWS CognitoIdp.');
}

What is your PHP version? And I think cognito IDP doesn't have the latest version. You should use the version from the code example or remove the version option.

PHP 7.4.25
It seems version is required, and I get the same fatal when I use the version in your example

@jenky
Copy link
Author

jenky commented Nov 11, 2021

Maybe try to use this version?

@mtindall89
Copy link

If I keep the rest of the code the same and just change version to 2014-06-30 I get the an exception with message "The cognito-idp service does not have version: 2014-06-30".

If I try to change the client to the CognitoSyncClient mentioned there, I get exception with message "Operation not found: InitiateAuth" once it gets to the auth ftn

I have been stuck for 3 days on this I will happily buy you some beers if we can get past this pain!

@jenky
Copy link
Author

jenky commented Nov 11, 2021

Try using example code with your hard-coded value. Pay attention to namespace as well, please note that phpseclib version in the example is 2.x, you might need to update the namespace if you're using 3.x version. Maybe try to debug or dump the variable step by step to pin point which line of code causing the error.

@mtindall89
Copy link

I'm using phpseclib 2.x specifically so I can use your class as is. I did remove the namespace and that's it (as I don't have/use that namespace).

I have narrowed it down to the initiateAuth call being where this happens. I had this same issue when trying build this whole process myself, which led me to your class.

So frustrating, as I'm not even sure how to debug further since the memory error really seems to prevent getting any deeper. And can't find anyone else who's reported this type of issue.

@mtindall89
Copy link

FYI to anyone who may come across it, the issue was that PHP was needing to allocate 600+mb to complete this process (Seems high to me, unsure if that's normal/expected), and the server we were running this on had a low php memory limit. Increasing that resolved the issue. It seems that the BigInteger library uses a lot of memory.

@mastercho
Copy link

Since yesterday i start getting requests exceeded seems its IP related and AWS banned IP because from localhost requests still works. Is there a way to apply proxy into lib so requests go troughs proxy to AWS?

@jenky
Copy link
Author

jenky commented Nov 18, 2021

Since yesterday i start getting requests exceeded seems its IP related and AWS banned IP because from localhost requests still works. Is there a way to apply proxy into lib so requests go troughs proxy to AWS?

This simple class doesn't handle the HTTP request. In fact it uses aws client to make the request. You should looking for the help from aws/aws-sdk-php

@mastercho
Copy link

Since yesterday i start getting requests exceeded seems its IP related and AWS banned IP because from localhost requests still works. Is there a way to apply proxy into lib so requests go troughs proxy to AWS?

This simple class doesn't handle the HTTP request. In fact it uses aws client to make the request. You should looking for the help from aws/aws-sdk-php

i know and what is documneted here https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html not working maybe someone managed to find workaround thats why i did ask :)

@jenky
Copy link
Author

jenky commented Nov 18, 2021

Since yesterday i start getting requests exceeded seems its IP related and AWS banned IP because from localhost requests still works. Is there a way to apply proxy into lib so requests go troughs proxy to AWS?

This simple class doesn't handle the HTTP request. In fact it uses aws client to make the request. You should looking for the help from aws/aws-sdk-php

i know and what is documneted here https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html not working maybe someone managed to find workaround thats why i did ask :)

Did you try to set proxy on http option?
image

@mastercho
Copy link

Since yesterday i start getting requests exceeded seems its IP related and AWS banned IP because from localhost requests still works. Is there a way to apply proxy into lib so requests go troughs proxy to AWS?

This simple class doesn't handle the HTTP request. In fact it uses aws client to make the request. You should looking for the help from aws/aws-sdk-php

i know and what is documneted here https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html not working maybe someone managed to find workaround thats why i did ask :)

Did you try to set proxy on http option? image

like i said in previous comment i tried...

@tar-2
Copy link

tar-2 commented Jul 1, 2022

That would be awesome! I'm still trying to work it out on my own as well. I think the second link that I shared can be a good starting point as I haven't seen any other solid implementation for the DEVICE_PASSWORD_VERIFIER challenge. I can create a user pool on my personal account and give you the credentials with the proper IAM permissions or there is also this program offered by AWS - we can open a repo.

Let me know what you think.

I'm also getting the "incorrect username or password" error: were you able to solve it? Thanks in advance. @marco3211 @jenky

@ayoub-bousetta
Copy link

Life saver...

@kiwialec
Copy link

This is hopefully so painfully obvious that no one else has this problem, but I debugged 'Incorrect username or password' for hours.

It turned out that I was using my IdentityPoolId instead of my UserPoolId in the poolId field. Changing it got the auth working immediately

@surfjam518
Copy link

Thanks so much for this, was a life saver. BUT!!! the service I am accessing has now switched on MFA permanently and I need to have my system capable of accessing it unattended.

The answer is to implement Device Authentication. This seems to almost work for me but fails as the final step (DEVICE_PASSWORD_VERIFIER) just thowing the usual 'Incorrect username or password'.

As far as I can see there are two potential problem areas:
1 - confirmDevice - Where you have to confirm the device you're using and send over DeviceSecretVerifierConfig details including a PasswordVerifier and salt. Given that this is the primary area of difference from your code above I think this is where it is going wrong and hence even if I am getting things correct on the finl DEVICE_PASSWORD_VERIFIER section it will of course fail due to this earlier issue. This is my hack:

    $passSalt = $this->getDeviceSecretVerifierConfig($deviceGroupKey, $deviceK);
    $result = $this->client->confirmDevice([
        'AccessToken' => $accessT,
        'DeviceKey' => $deviceK,
        'DeviceName' => $location." Auto",
        'DeviceSecretVerifierConfig' => [
            'PasswordVerifier' => $passSalt['PasswordVerifier'],
            'Salt' => $passSalt['Salt'],
        ],
    ]);

    public function getDeviceSecretVerifierConfig(string $deviceGroupKey, string $deviceKey): array
    {
            $randomPassword = $this->bytes(40);
            $fullPassword = $this->hash(sprintf('%s%s:%s', $deviceGroupKey, $deviceKey, $randomPassword));
            $salt = $this->bytes(16);
            $SaltToHashDevices = $this->padHex(new BigInteger($salt->toHex(),16));

            $passwordVerifier = $this->g->modPow(
            new BigInteger($this->hexHash($SaltToHashDevices . $fullPassword), 16),
            $this->N
            );
            return [
                    'Salt' => base64_encode($this->padHex($SaltToHashDevices)),
                    'PasswordVerifier' => base64_encode($this->padHex($passwordVerifier)),
                    'rndPass' => $randomPassword //->toString()
            ];
    }

2 - DEVICE_PASSWORD_VERIFIER has a problem... as said though I believe the problem is most likely with the above.

So to the question, has anyone expanded this for device authentication, and if you have it working PLEASE cna you let me have the solution as it is making me mad.

@surfjam518
Copy link

commented

So After many hours....... I have managed to fix it. The issue was in my getDeviceSecretVerifierConfig function which now reads as follows:

public function getDeviceSecretVerifierConfig(string $deviceGroupKey, string $deviceKey): array
{

    $randomPassword = $this->bytes(40);
    $fullPassword = $this->hash(sprintf('%s%s:%s', $deviceGroupKey, $deviceKey, $randomPassword));

    $salt = $this->bytes(16);
    $SaltToHashDevices = $this->padHex(new BigInteger($salt->toHex(),16));

    $x = new BigInteger($this->hexHash($SaltToHashDevices.$fullPassword), 16);
    $gModPowXN = $this->g->modPow($x, $this->N);
    $passwordVerifier = $this->padHex($gModPowXN->toHex());

    return [
        'Salt' => base64_encode(hex2bin($SaltToHashDevices)),
        'PasswordVerifier' => base64_encode(hex2bin($passwordVerifier)),
        'rndPass' => $randomPassword
    ];
}

Hopefully this will save someone else a whole lot of time if they need to add device verification. Thankfully I can now wave goodbye to sms MFA requests.... or at least until the service is 'played' with again.

Enjoy

@marcocot
Copy link

marcocot commented Nov 28, 2023

Hi, we are trying to implement device authentication, using this code. The problem is that after responding to the SMS_MFA challenge, I try to call confirmDevice like this:

$response = $this->client->respondToAuthChallenge([
    "ChallengeName"      => "SMS_MFA",
    "ChallengeResponses" => $challengeResponses,
    'Session'            => $session,
    'ClientId'           => $this->appClientId,
    'UserPoolId'         => $this->userPoolId,
]);


$metadata = $response->get('AuthenticationResult');

$accessToken = $metadata['AccessToken'] ?? null;
$deviceGroupKey = $metadata['NewDeviceMetadata']['DeviceGroupKey'] ?? null;
$deviceKey = $metadata['NewDeviceMetadata']['DeviceKey'] ?? null;

$passSalt = $srp->getDeviceSecretVerifierConfig($deviceGroupKey, $deviceKey);
$result = $this->client->confirmDevice([
    'AccessToken' => $accessToken,
    'DeviceKey' => $deviceKey,
    'DeviceName' => "My personal location" . " Auto",
    'DeviceSecretVerifierConfig' => [
        'PasswordVerifier' => $passSalt['PasswordVerifier'],
        'Salt' => $passSalt['Salt'],
    ],
]);

but the client returns the error "Invalid device key given.". does anyone have ideas on how to fix this?
thanks!

@revuponline
Copy link

you are a life saver. thank you. your buy me a beer link 404s but if you've got another send it over.

@jenky
Copy link
Author

jenky commented Jan 30, 2024

You can try this one. I'm glad that my code was able to help.

@dineshrsys
Copy link

I have question what will be in the case of DEVICE_SRP_AUTH, DEVICE_PASSWORD_VERIFIER

@Masook-ahmad
Copy link

Hello @jenky,
we're encountering an issue with the DEVICE_PASSWORD_VERIFIER challenge - it's returning an 'Incorrect username and password' error. Could you possibly assist us by providing code that utilizes both DEVICE_SRP_AUTH and DEVICE_PASSWORD_VERIFIER? We believe this could resolve our problem.

public function authenticateUser(string $username, string $password, string $deviceKey)
{
try {
$result = $this->client->initiateAuth([
'AuthFlow' => 'USER_PASSWORD_AUTH',
'ClientId' => config('services.aws_cognito.client_id'),
'UserPoolId' => config('services.aws_cognito.user_pool_id'),
'AuthParameters' => [
'USERNAME' => $username,
'PASSWORD' => $password,
'DEVICE_KEY' => $deviceKey,
],
]);

        if ($result->get('ChallengeName') == 'DEVICE_SRP_AUTH') {
            $result = $this->client->respondToAuthChallenge([
                'ChallengeName' => 'DEVICE_SRP_AUTH',
                'ClientId' => $this->clientId,
                'ChallengeResponses' => [
                    'USERNAME' => $username,
                    'DEVICE_KEY' => $deviceKey,
                    'SRP_A' => $this->largeA()->toHex(),
                ],
                'SESSION' => $result->get('Session'),
            ]);
        }


        if ($result->get('ChallengeName') != 'DEVICE_PASSWORD_VERIFIER') {
            throw new RuntimeException("ChallengeName `{$result->get('ChallengeName')}` is not supported.");
        }

        if ($result->get('ChallengeName') == 'DEVICE_PASSWORD_VERIFIER') {
            $challengeParameters = $this->processChallenge($result, $password);
            $result =  $this->client->respondToAuthChallenge([
                'ChallengeName' => 'DEVICE_PASSWORD_VERIFIER',
                'ClientId' => $this->clientId,
                'ChallengeResponses' => $challengeParameters,
            ]);
        }
    } catch (\Throwable $th) {
        print_r($th->getMessage());
        die;
    }
}

/**
 * Generate authentication challenge response params.
 */
/**
 * Generate authentication challenge response params.
 */
protected function processChallenge(Result $result, $password): array
{
    $challengeParameters = $result->get('ChallengeParameters');
    $time = Carbon::now()->tz('UTC')->format('D M j H:i:s e Y');
    $secretBlock = base64_decode($challengeParameters['SECRET_BLOCK']);
    $userName = $challengeParameters['USERNAME'];
    $deviceKey = $challengeParameters['DEVICE_KEY'];
    $deviceGroupKey = '*********';


    //$fullPassword = $this->getDeviceSecretVerifierConfig($deviceGroupKey, $userName);
    $hkdf = $this->getPasswordAuthenticationKey(
        $userName,
        $password,
        $challengeParameters['SRP_B'],
        $challengeParameters['SALT']
    );

   
    $msg = $this->poolId().$userName.$secretBlock.$time;
    $signature = hash_hmac('sha256', $msg, $hkdf, true);

    return [
        'TIMESTAMP' => $time,
        'USERNAME' => $userName,
        'DEVICE_KEY' => $deviceKey,
        'PASSWORD_CLAIM_SECRET_BLOCK' => $challengeParameters['SECRET_BLOCK'],
        'PASSWORD_CLAIM_SIGNATURE' => base64_encode($signature),
    ];
}

Thank you!

@subhan300
Copy link

@steveWinter Hi, can you plz share you js code in which you have mplenented password_verifirer challenge ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment