maxbeckers/amazon-alexa-php

Prechecks fail with: The skill end-point is not accepting valid signed requests.

trx222 opened this issue · 7 comments

Hi, and thanks for your lib. We use it since 2 or 3 years now.

Actually I have a problem when I try to send a skill for certification - the prechecks fail with
"The skill end-point is not accepting valid signed requests.

Bildschirmfoto 2021-11-18 um 08 21 19
"

and on server side - an nginx webserver with valid ssl certificate I get this error in log file:

2021/11/18 08:20:55 [error] 7286#7286: *295852312 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught MaxBeckers\AmazonAlexa\Exception\RequestInvalidSignatureException: Cert ssl verification failed. in /home/data/.../backend/vendor/maxbeckers/amazon-alexa-php/src/Validation/RequestValidator.php:166 Stack trace: #0 /home/data/.../backend/vendor/maxbeckers/amazon-alexa-php/src/Validation/RequestValidator.php(110): MaxBeckers\AmazonAlexa\Validation\RequestValidator->verifyCert(Object(MaxBeckers\AmazonAlexa\Request\Request), '-----BEGIN CERT...') #1 /home/data/.../backend/vendor/maxbeckers/amazon-alexa-php/src/Validation/RequestValidator.php(56): MaxBeckers\AmazonAlexa\Validation\RequestValidator->validateSignature(Object(MaxBeckers\AmazonAlexa\Request\Request)) #2 /home/data/.../backend/interface/index.php(43): MaxBeckers\AmazonAlexa\Validation\RequestValidator->validate(Object(MaxBeckers\AmazonAlexa\Request\Request)) #3 {main} thrown in /home/data/" while reading response header from upstream, client: 72.21.217.106, server: ---.com, request: "POST /backend/interface/ HTTP/1.1", upstream: "fastcgi://unix:/run/php/php7.2-fpm.sock:", host: "---.com"

The skill still works in the test simulator and it worked also the last year. Nothing was changed on the code the last year and the prececks were running without problems the whole time. Something changed now with the signature check. I already removed the downloaded certificate from /tmp but without success.

Does anyone has an idea what may be wrong? Again, it is strange because the checks worked weeks before and only store meta data changed.

Best regards and thanks

Hi @trx222,

thanks for your Issue. I don't know about this issue. Seems to be that there changed sth. on Amazon? I didn't find anything about a change here in the documentation.
The implementation of the RequestValidator is implemented with this Documentation.

When you can figure out the problem feel free to create a PR to fix the lib.

I hope I'll find also some time to figure out the problem.

It is really strange because also in the prechecks a lot of requests work, I see it in the logfile on my site. But then comes one request where it does not. I will go on to find out and keep you informed.

Hello. I'm facing the same issue. I'll keep you updated if I find the issue.

I solved it in my code - I try to cleanup everything tomorrow and then send a PR if I can add it to the lib.

I used the sample code from here: https://en.philipp-guttmann.de/Blog/Alexa_Skill_Endpoint_PHP/

Did you manage to pinpoint where the problem was ?
The main difference I spotted with your example is
$certData = @file_get_contents($localCertPath);
vs
$SignatureCertChainUrl_Content = file_get_contents($SignatureCertChainUrl_File); $Signature_PublicKey = openssl_pkey_get_public($SignatureCertChainUrl_Content); $Signature_PublicKey_Data = openssl_pkey_get_details($Signature_PublicKey); $Signature_PublicKey_Data['key']

But I still got the cert validation error.

@SpeedPony I got it working but actually in a quick and dirty way, because of some time trouble. Thatswhy I need some time to make it clean. If it helps, maybe you can use it:

In my Interface, right after include section, I added this code:

$requestBody = file_get_contents('php://input');

$post = json_decode($requestBody);

date_default_timezone_set('UTC');

$SignatureCertChainUrl = $_SERVER['HTTP_SIGNATURECERTCHAINURL'];

if ($post->request->timestamp > date('Y-m-d\TH:i:s\Z', time()-150) && preg_match('/https:\/\/s3\.amazonaws\.com(:433)?\/echo\.api\//', $SignatureCertChainUrl)) 
{
	$SignatureCertChainUrl_File = md5($SignatureCertChainUrl);
	$SignatureCertChainUrl_File = "./tmp/" . $SignatureCertChainUrl_File . '.pem';
	 
	if (!file_exists($SignatureCertChainUrl_File)) 
        {
		file_put_contents($SignatureCertChainUrl_File, file_get_contents($SignatureCertChainUrl));
	}	

	$SignatureCertChainUrl_Content = file_get_contents($SignatureCertChainUrl_File);	
	$Signature_Content = $_SERVER['HTTP_SIGNATURE'];

	$SignatureCertChainUrl_Content_Array = @openssl_x509_parse($SignatureCertChainUrl_Content);

	$Signature_PublicKey = @openssl_pkey_get_public($SignatureCertChainUrl_Content);
	$Signature_PublicKey_Data = @openssl_pkey_get_details($Signature_PublicKey);
	$Signature_Content_Decoded = @base64_decode($Signature_Content);

	$Signature_Verify = @openssl_verify($requestBody, $Signature_Content_Decoded, $Signature_PublicKey_Data['key'], 'sha1');

       $a = preg_match('/echo-api\.amazon\.com/', base64_decode($SignatureCertChainUrl_Content));
       $b = $SignatureCertChainUrl_Content_Array['validTo_time_t'] > time();
       $c = $SignatureCertChainUrl_Content_Array['validFrom_time_t'] < time();
   
       if ($a && $b && $c && $Signature_Content && $Signature_Verify == 1) 
       {
       }
      else
      {
         http_response_code(400);
         exit;      
      }
}
else
{
   http_response_code(400);
   exit;
}

make sure that "./tmp/" exists and is writeable for the webserver

and after this I started with old code - I just had to add the "http_response_code(400);" lines, that were missing before:

if ($requestBody)
{
   $alexaRequest = Request::fromAmazonRequest($requestBody, $_SERVER['HTTP_SIGNATURECERTCHAINURL'], $_SERVER['HTTP_SIGNATURE']);
   
   if (!$alexaRequest)
   {
      http_response_code(400);
      exit;
   }

   // Request validation
   $validator = new RequestValidator();
   $validator->validate($alexaRequest);
 [...]
}
else
{
   http_response_code(400);
}

this works for me ...

Sorry , I am a bit busy so it took to long. Thank you @maxbeckers