0

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

lazyCoder
  • 2,544
  • 3
  • 22
  • 41

0 Answers0