I am creating Alexa custom skill with the PHP Http Endpoint, I have stucked at verify Alexa request, Following official document
Here i started like:
<?php
**// this variable represent the request is valid or not**
$invalid = true;
**// checking request header has SIGNATURECERTCHAINURL, SIGNATURE and timestamp**
if(!empty($_SERVER['HTTP_SIGNATURECERTCHAINURL']) && !empty($_SERVER['HTTP_SIGNATURE']) && !empty($request['request']['timestamp'])){
$parsed_url = parse_url($_SERVER['HTTP_SIGNATURECERTCHAINURL']);
$current_timestamp = time();
**// all the check as document said like request url must have https, host=s3.amazonaws.com, path start with /echo.api/, if port is there so it should be 443, timestamp should be less than 150 second more from the current timestamp**
if(
(!empty($parsed_url['scheme']) && strtolower($parsed_url['scheme']) == 'https') &&
(!empty($parsed_url['host']) && strtolower($parsed_url['host']) == 's3.amazonaws.com') &&
(!empty($parsed_url['path']) && substr($parsed_url['path'], 0, 10) == '/echo.api/') &&
(empty($parsed_url['port']) || $parsed_url['port'] == 443) &&
( ($current_timestamp - strtotime($request['request']['timestamp'])) <= 150)
){
Now getting pem files multiple certificate(signing certificate and so more) and we will validate all certificate and its time, will extract the public key from signing certificate
$cert_data = file_get_contents($_SERVER['HTTP_SIGNATURECERTCHAINURL']);
preg_match_all('/-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----/s', $cert_data, $matches);
**// matches[0] as an array of the certificate**
if(!empty($matches[0])){
$signed_cert_public_key = '';
$issueWithCrt = false;
**// validating all the certificate one by one**
foreach($matches[0] as $lp_cert){
**// getting public key from certificate**
$pub_key_detail = openssl_pkey_get_public($lp_cert);
$keyData = openssl_pkey_get_details($pub_key_detail);
if(!empty($keyData['key'])){
**// public_key represent public key for the certificate**
$public_key = $keyData['key'];
$certinfo = openssl_x509_parse($lp_cert);
if(!empty($certinfo["name"])){
**// time validity check for the certificate**
if(
!empty($certinfo["validFrom_time_t"]) && !empty($certinfo["validTo_time_t"]) &&
($current_timestamp >= $certinfo["validFrom_time_t"] && $current_timestamp <= $certinfo["validTo_time_t"])
){
**// If certificate is signing certificate then we will extract the public key for next process**
if($certinfo["name"] == "/CN=echo-api.amazon.com"){
if(!empty($certinfo["extensions"]["subjectAltName"]) && $certinfo["extensions"]["subjectAltName"] == "DNS:echo-api.amazon.com"){
$signed_cert_public_key = $public_key;
}
else{
$issueWithCrt = true;
break;
}
}
}
else{
$issueWithCrt = true;
break;
}
}
else{
$issueWithCrt = true;
break;
}
}
else{
$issueWithCrt = true;
break;
}
}
Now converting to hash value to request body and will compare it with the HTTP_SIGNATURE
if(!$issueWithCrt && $signed_cert_public_key != ''){
**// Generate a SHA-1 hash value from the full HTTP request body to produce the derived hash value.**
$request_body_sha1 = sha1(json_encode($_POST));
**//Base64-decode the value of the Signature header in the request to obtain the encrypted signature.**
$get_encrpted_signature = base64_decode($_SERVER['HTTP_SIGNATURE']);
openssl_public_decrypt($get_encrpted_signature, $decrypted, $signed_cert_public_key, OPENSSL_NO_PADDING);
**// below the hash values are not getting match**
**// Compare the asserted hash value and derived hash** values to ensure that they match. If they do not match, discard the request.
if(hash_equals(sha1($decrypted), $request_body_sha1)){
$invalid = false;
}
}
finally printing the invalid variable value
}
}
}
if($invalid){
echo "Request is invalid";
}
else{
echo "Request is valid";
}
Below is full code:
<?php
**// this variable represent the request is valid or not**
$invalid = true;
**// checking request header has SIGNATURECERTCHAINURL, SIGNATURE and timestamp**
if(!empty($_SERVER['HTTP_SIGNATURECERTCHAINURL']) && !empty($_SERVER['HTTP_SIGNATURE']) && !empty($request['request']['timestamp'])){
$parsed_url = parse_url($_SERVER['HTTP_SIGNATURECERTCHAINURL']);
$current_timestamp = time();
**// all the check as document said like request url must have https, host=s3.amazonaws.com, path start with /echo.api/, if port is there so it should be 443, timestamp should be less than 150 second more from the current timestamp**
if(
(!empty($parsed_url['scheme']) && strtolower($parsed_url['scheme']) == 'https') &&
(!empty($parsed_url['host']) && strtolower($parsed_url['host']) == 's3.amazonaws.com') &&
(!empty($parsed_url['path']) && substr($parsed_url['path'], 0, 10) == '/echo.api/') &&
(empty($parsed_url['port']) || $parsed_url['port'] == 443) &&
( ($current_timestamp - strtotime($request['request']['timestamp'])) <= 150)
){
**// Now getting pem files multiple certificate(signing certificate and so more) and we will validate all certificate and its time, will extract the public key from signing certificate**
$cert_data = file_get_contents($_SERVER['HTTP_SIGNATURECERTCHAINURL']);
preg_match_all('/-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----/s', $cert_data, $matches);
**// matches[0] as an array of the certificate**
if(!empty($matches[0])){
$signed_cert_public_key = '';
$issueWithCrt = false;
**// validating all the certificate one by one**
foreach($matches[0] as $lp_cert){
**// getting public key from certificate**
$pub_key_detail = openssl_pkey_get_public($lp_cert);
$keyData = openssl_pkey_get_details($pub_key_detail);
if(!empty($keyData['key'])){
**// public_key represent public key for the certificate**
$public_key = $keyData['key'];
$certinfo = openssl_x509_parse($lp_cert);
if(!empty($certinfo["name"])){
**// time validity check for the certificate**
if(
!empty($certinfo["validFrom_time_t"]) && !empty($certinfo["validTo_time_t"]) &&
($current_timestamp >= $certinfo["validFrom_time_t"] && $current_timestamp <= $certinfo["validTo_time_t"])
){
**// If certificate is signing certificate then we will extract the public key for next process**
if($certinfo["name"] == "/CN=echo-api.amazon.com"){
if(!empty($certinfo["extensions"]["subjectAltName"]) && $certinfo["extensions"]["subjectAltName"] == "DNS:echo-api.amazon.com"){
$signed_cert_public_key = $public_key;
}
else{
$issueWithCrt = true;
break;
}
}
}
else{
$issueWithCrt = true;
break;
}
}
else{
$issueWithCrt = true;
break;
}
}
else{
$issueWithCrt = true;
break;
}
}
// Now converting to hash value to request body and will compare it with the HTTP_SIGNATURE
if(!$issueWithCrt && $signed_cert_public_key != ''){
**// Generate a SHA-1 hash value from the full HTTP request body to produce the derived hash value.**
$request_body_sha1 = sha1(json_encode($_POST));
**//Base64-decode the value of the Signature header in the request to obtain the encrypted signature.**
$get_encrpted_signature = base64_decode($_SERVER['HTTP_SIGNATURE']);
openssl_public_decrypt($get_encrpted_signature, $decrypted, $signed_cert_public_key, OPENSSL_NO_PADDING);
**// below the hash values are not getting match**
**// Compare the asserted hash value and derived hash** values to ensure that they match. If they do not match, discard the request.
if(hash_equals(sha1($decrypted), $request_body_sha1)){
$invalid = false;
}
}
}
}
}
if($invalid){
echo "Request is invalid";
}
else{
echo "Request is valid";
}
I am facing issue only in matching the asserted hash value and derived hash value both are different
Please help me, take a look and correct me where i am wrong