Martin Zeitler
Martin Zeitler

Reputation: 76679

How to validate a Huawei X-HW-SIGNATURE?

How can I validate a X-HW-SIGNATURE in PHP?

The documentation for request parameters reads:

Message header signature, which is mandatory, indicating the
signature information sent to your server that receives uplink messages.

There's also example data:

timestamp=1563105451261; nonce=:; value=E4YeOsnMtHZ6592U8B9S37238E+Hwtjfrmpf8AQXF+c=

The keys are:


This here is the part which I don't understand:

timestamp + nonce + Uplink message content: obtained after the encryption using the set password in HMAC-SHA256 algorithm and encoding in Base64.


How can I validate the message payload against the header signature?

What I've tried so far basically is:

private function parse_request_body(): void {
    $this->rawBody = stream_get_contents(STDIN);
    if (isset($_SERVER['X-HW-SIGNATURE']) && !empty($_SERVER['X-HW-SIGNATURE'])) {
        if (! $this->hmac_verify( $this->rawBody, $_SERVER['X-HW-SIGNATURE'] )) {
            // spoof message
        }
    }
}

private function hmac_verify( string $payload, string $signature ): bool {
    // the problem obviously lies here ...
    return true;
}

Upvotes: 2

Views: 258

Answers (3)

zhangxaochen
zhangxaochen

Reputation: 34017

Thank you for providing the information regarding this issue. We are very sorry that it brings you inconvinience and are now organizing R&D team to supplement the sample code.

The X-HW-SIGNATURE field is used to check whether the message is from Huawei service.

enter image description here

enter image description here

Usage:

Timestamp + nonce + Uplink message content are combined into a character string. Use the configured HMAC HMAC-SHA256 algorithm and encoding in Base64 to compare the obtained value with the value sent by the push service. If they are the same, the message is from the push service, and you do not need to parse the specific value of this field.

Upvotes: 0

Martin Zeitler
Martin Zeitler

Reputation: 76679

On another page of the documentation (X-HUAWEI-CALLBACK-ID), I've found a similar description:

Base64-encoded string that has been HMAC-SHA256 encrypted using the callback key. The string before encryption consists of the value of timestamp, value of nonce, and callback user name, without plus signs.


And here it's being described how to send push.hcm.upstream messages on Android. Sending an upstream message might be the best chance to obtain the payload, in order to validate a signature. The server-side procedure upon send as following:

When receiving the uplink message, the Push Kit server will:

  • Combine the receiving timestamp, colon (:), and uplink message into a character array to be encrypted (for example, 123456789:your_data).
  • Encrypt the character array in HMAC-SHA256 mode using an HMAC signature verification key, and encode the encrypted result in Base64 to generate a signature.
  • Transfer the signature and timestamp information to your app server through the X-HW-SIGNATURE and X-HW-TIMESTAMP fields in the HTTPS request header.

    Your app server needs to use the HTTPS request header and HMAC signature verification key to verify the validity of the uplink message.

Whatever "an HMAC signature verification key" may be; placeholder your_data sounds alike, as if (likely not yet base64 encoded) $payload->data would have been used to generate the signature:

/** Concatenate the input string, generate HMAC hash with SHA256 algorithm, then encode as base64. */
private function generate_signature( int $timestamp, string $nonce, string $data_str, string $secret_key): string {
    $input = $timestamp.$nonce.$data_str;
    $hmac = hash_hmac( 'sha256', $input, $secret_key );
    return base64_encode( $hmac );
}

/** Convert the received signature string to object. */
private function to_object( string $signature ): stdClass {
    $input = str_getcsv( $signature, '; ' );
    $data = new stdClass();
    $data->timestamp = (int) str_replace('timestamp=', '', $input[0]);
    $data->nonce  = (string) str_replace(   ' nonce=', '', $input[1]);
    $data->value  = (string) str_replace(   ' value=', '', $input[2]);
    return $data;
}

public function hmac_verify( string $raw_body, string $secret_key, string $signature ): bool {

    /* Extract data-string from the raw body. */
    $payload = json_decode( $raw_body );
    $data_str = base64_decode( $payload->data );

    /* Convert the received signature string to object. */
    $signature = $this->to_object( $signature );

    /* Generate a signature which to compare to. */
    $generated = $this->generate_signature( $signature->timestamp, $signature->nonce, $data_str, $secret_key);

    /* Compare the generated with the received signature. */
    return $generated === $signature->value;
}

Need to test this once with an actual $_POST ...


The "HMAC signature verification key" (per web-hook) can be obtained from the PushKit console:

screenshot

Upvotes: 0

Bossman
Bossman

Reputation: 1524

This is how i would go about verifying the signature. From my understanding from the doc. However it isn't 100% clear as they do not provide an example, which is a shame...

You should have (or be able to create one) a secret key within your Huawei account somewhere.

private function hmac_verify( string $payload, string $signature ): bool
{
    $secretKey = 'yoursecretkey';
    $parsedSignature = str_replace(';', '&', $signature); //'timestamp=1563105451261& nonce=:& value=E4YeOsnMtHZ6592U8B9S37238E+Hwtjfrmpf8AQXF+c='
    parse_str($parsedSignature, $signatureParts);

    // $signatureParts
    //
    // array(3) {
    //  ["timestamp"]=>
    //  string(13) "1563105451261"
    //  ["nonce"]=>
    //  string(1) ":"
    //  ["value"]=>
    //  string(44) "E4YeOsnMtHZ6592U8B9S37238E Hwtjfrmpf8AQXF c="
    // }

    $signed = hash_hmac("sha256", $signatureParts['timestamp'] + $signatureParts['nonce'] + $payload, $secretKey);

    return base64_encode($signed) === $signatureParts['value'];
}

Upvotes: 2

Related Questions