PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Sunday, January 23, 2022

[FIXED] PHP / Docusign - Verify HMAC signature on completed event

 January 23, 2022     docusignapi, php, symfony     No comments   

Issue

I'm trying to secure my callback url when completed event is triggered.

My Controller:

    public function callbackSubscriptionCompleted(
        int $subscriptionId,
        DocusignService $docusignService,
        Request $request
    ) {
        $signature = $request->headers->get("X-DocuSign-Signature-1");
        $payload = file_get_contents('php://input');

        $isValid = $docusignService->isValidHash($signature, $payload);
        if (!$isValid) {
            throw new ApiException(
                Response::HTTP_BAD_REQUEST,
                'invalid_subscription',
                'Signature not OK'
            );
        }

        return new Response("Signature OK", Response::HTTP_OK);
    }

My DocusignService functions:

    private function createEnvelope(Company $company, Subscription $subscription, LegalRepresentative $legalRepresentative, Correspondent $correspondent, $correspondents) : array
    {
       // ...
       $data = [
            'disableResponsiveDocument' => 'false',
            'emailSubject' => 'Your Subscription',
            'emailBlurb' => 'Subscription pending',
            'status' => 'sent',
            'notification' => [
                'useAccountDefaults' => 'false',
                'reminders' => [
                    'reminderEnabled' => 'true',
                    'reminderDelay' => '1',
                    'reminderFrequency' => '1'
                ],
                'expirations' => [
                    'expireEnabled' => 'True',
                    'expireAfter' => '250',
                    'expireWarn' => '2'
                ]
            ],
            'compositeTemplates' => [
                [
                    'serverTemplates' => [
                        [
                            'sequence' => '1',
                            'templateId' => $this->templateId
                        ]
                    ],
                    'inlineTemplates' => [
                        [
                            'sequence' => '2',
                            'recipients' => [
                                'signers' => [
                                    [
                                        'email' => $legalRepresentative->getEmail(),
                                        'name' => $legalRepresentative->getLastname(),
                                        'recipientId' => '1',
                                        'recipientSignatureProviders' => [
                                            [
                                                'signatureProviderName' => 'universalsignaturepen_opentrust_hash_tsp',
                                                'signatureProviderOptions' => [
                                                    'sms' => substr($legalRepresentative->getCellphone(), 0, 3) == '+33'  ? $legalRepresentative->getCellphone() : '+33' . substr($legalRepresentative->getCellphone(), 1),
                                                ]
                                            ]
                                        ],
                                        'roleName' => 'Client',
                                        'clientUserId' => $legalRepresentative->getId(),
                                        'tabs' => [
                                            'textTabs' => $textTabs,
                                            'radioGroupTabs' => $radioTabs,
                                            'checkboxTabs' => $checkboxTabs
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ],
            'eventNotification' => [
                "url" => $this->router->generate("api_post_subscription_completed_callback", [
                    "subscriptionId" => $subscription->getId()
                ], UrlGeneratorInterface::ABSOLUTE_URL),
                "includeCertificateOfCompletion" => "false",
                "includeDocuments" => "true",
                "includeDocumentFields" => "true",
                "includeHMAC" => "true",
                "requireAcknowledgment" => "true",
                "envelopeEvents" => [
                    [
                        "envelopeEventStatusCode" => "completed"
                    ]
                ]
            ]
        ];

        $response = $this->sendRequest(
            'POST',
            $this->getBaseUri() . '/envelopes',
            [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $this->getCacheToken()
            ],
            json_encode($data)
        );
    }

    public function isValidHash(string $signature, string $payload): bool
    {
        $hexHash = hash_hmac('sha256',utf8_encode($payload),utf8_encode($this->hmacKey));
        $base64Hash = base64_encode(hex2bin($hexHash));

        return $signature === $base64Hash;
    }

I've created my hmac key in my Docusign Connect and i'm receiving the signature in the header and the payload but the verification always failed. I've followed the Docusign documentation here What's wrong ?

PS: Sorry for my bad english


Solution

Your code looks good to me. Make sure that you are only sending one HMAC signature. That way your hmacKey is the correct one.

As a check, I'd print out the utf8_encode($payload) and check that it looks right (it should be the incoming XML, no headers). Also, I don't think it should have a CR/NL in it at the beginning. That's the separator between the HTTP header and body.

Update

I have verified that the PHP code from the DocuSign web site works correctly.

The payload value must not contain either a leading or trailing newline. It should start with <?xml and end with >

I suspect that your software is adding a leading or trailing newline.

The secret (from DocuSign) ends with an =. It is a Base64 encoded value. Do not decode it. Just use it as a string.

Another update

The payload (the body of the request) contains zero new lines.

If you're printing the payload, you'll need to wrap it in <pre> since it includes < characters. Or look at the page source.

It contains UTF-8 XML such as

<?xml version="1.0" encoding="utf-8"?><DocuSignEnvelopeInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.docusign.net/API/3.0"><EnvelopeStatus><RecipientStatuses><RecipientStatus><Type>Signer</Type><Email>larry@worldwidecorp.us</Email><UserName>Larry Kluger</UserName><RoutingOrder>1</RoutingOrder><Sent>2020-08-05T03:11:13.057</Sent><Delivered>2020-08-05T03:11:27.657</Delivered><DeclineReason xsi:nil="true" /><Status>Delivered</Status><RecipientIPAddress>5.102.239.40</RecipientIPAddress><CustomFields /><TabStatuses><TabStatus><TabType>Custom</TabType><Status>Active</Status><XPosition>223</XPosition><YPosition>744....

We've done some more testing, and the line

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

should be very early in your script. The problem is that a framework can munge the php://input stream so it won't work properly thereafter.

Note this page from the Symfony site -- it indicates that the right way to get the request body is:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;

$app->before(function (Request $request) {
        $payload = $request->getContent();
        hmac_verify($payload, $secret);
});

I would try to use the Symfony code instead of file_get_contents('php://input');



Answered By - Larry K
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing