Ошибка подписи веб-перехватчика Stripe - Stripe.net

Я пытаюсь реализовать полосовой веб-перехватчик, используя библиотеку С # Stripe.net от Джейми Дэвиса. Я настроил тестовую конечную точку на панели инструментов полосы и сгенерировал секрет. Конечная точка обрабатывается нормально и сгенерирует StripeEvent с помощью StripeEventUtility.ParseEvent. Проблема в том, что при использовании функции ConstructEvent я не могу получить совпадающие подписи. Любая помощь или предложения будут очень благодарны.

isSignaturePresent возвращает false

//call to create event
stripeEvent = ConstructEvent(json, Request.Headers["Stripe-Signature"], 
SecretFromStripeDashBoard);


private StripeEvent ConstructEvent(string json, string 
stripeSignatureHeader, string secret, int tolerance = 300)
    {
        var signatureItems = parseStripeSignature(stripeSignatureHeader);

        var signature = computeSignature(secret, signatureItems["t"].FirstOrDefault(), json);

        if (!isSignaturePresent(signature, signatureItems["v1"]))
            throw new Exception("The signature for the webhook is not present in the Stripe-Signature header.");

        //var utcNow = EpochUtcNowOverride ?? DateTime.UtcNow.ConvertDateTimeToEpoch();
        //var webhookUtc = Convert.ToInt32(signatureItems["t"].FirstOrDefault());

        //if (utcNow - webhookUtc > tolerance)
        //    throw new Exception("The webhook cannot be processed because the current timestamp is above the allowed tolerance.");

        return Mapper<StripeEvent>.MapFromJson(json);
    }

    private ILookup<string, string> parseStripeSignature(string stripeSignatureHeader)
    {
        return stripeSignatureHeader.Trim()
            .Split(',')
            .Select(item => item.Trim().Split('='))
            .ToLookup(item => item[0], item => item[1]);
    }

    private bool isSignaturePresent(string signature, IEnumerable<string> signatures)
    {
        return signatures.Any(key => secureCompare(key, signature));
    }

    private string computeSignature(string secret, string timestamp, string payload)
    {
        var secretBytes = Encoding.UTF8.GetBytes(secret);
        var payloadBytes = Encoding.UTF8.GetBytes($"{timestamp}.{payload}");

        var cryptographer = new HMACSHA256(secretBytes);
        var hash = cryptographer.ComputeHash(payloadBytes);

        return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }

    private bool secureCompare(string a, string b)
    {
        if (a.Length != b.Length) return false;

        var result = 0;

        for (var i = 0; i < a.Length; i++)
        {
            result |= a[i] ^ b[i];
        }

        return result == 0;
    }
}

person stephen    schedule 10.05.2017    source источник
comment
Как вы инициализируете переменную json?   -  person Ywain    schedule 10.05.2017
comment
я использую var json = JsonSerializer.SerializeToString (request);   -  person stephen    schedule 10.05.2017
comment
Итак, вы повторно сериализуете десериализованное тело запроса. Однако очень маловероятно, что это вернет ту же строку, что и исходное тело запроса. Вам необходимо использовать необработанное тело запроса, переданное вашим веб-сервером / фреймворком.   -  person Ywain    schedule 10.05.2017
comment
попробую, спасибо   -  person stephen    schedule 10.05.2017
comment
Да, это сработало, спасибо.   -  person stephen    schedule 10.05.2017
comment
Это также происходит в Scala с платформой Play. По умолчанию тело запроса анализируется на json, и если вы пытаетесь получить доступ к строке, это десериализованный json, который отличается от необработанного тела, поэтому проверка sig не выполняется. В этом вопросе я подробно описал, как получить необработанное тело: stackoverflow.com/questions/59332440/   -  person Ali    schedule 14.12.2019


Ответы (3)


Я ответил в комментариях выше, но напомню, что проблема заключалась в том, что строка json, предоставленная методу ConstructEvent, не содержала точной полезной нагрузки, отправленной Stripe.

Скорее вы инициализировали полезную нагрузку с помощью:

var json = JsonSerializer.SerializeToString(request);

то есть вы повторно сериализовали тело десериализованного запроса. Однако очень маловероятно, что вы получите ту же строку, что и исходная полезная нагрузка, отправленная Stripe. Например. Stripe может отправить эту полезную нагрузку:

{
  "a_key": "a_value",
  "another_key": "another_value"
}

но после десериализации + повторной сериализации ваша строка JSON может содержать это значение:

{"another_key": "another_value","a_key": "a_value"}

поскольку порядок ключей не гарантируется, в игру вступают другие параметры форматирования (новые строки, отступы).

Подписи веб-перехватчиков генерируются с использованием точной полезной нагрузки (в виде необработанной строки), поэтому при проверке подписей вы также должны предоставить эту точную полезную нагрузку, передаваемую вашим веб-сервером или фреймворком.

person Ywain    schedule 10.05.2017
comment
Привет @Ywain, Как получить точную полезную нагрузку из тела запроса. Я пробовал несколько методов (получение тела как [FromBody], получение тела из StreamReader и т. Д.), Но ни один из них не помог решить проблему. - person Isuru Siriwardana; 03.03.2020
comment
Привет, @IsuruSiriwardana. Если вы используете ASP.NET, вы сможете получить необработанное тело примерно так: string rawBody; using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { rawBody = await reader.ReadToEndAsync(); } - person Ywain; 05.03.2020

Как указывалось в предыдущем ответе, строка json должна быть идентична загрузке воспроизведения, отправляемой полосой. В моей ситуации мне пришлось удалить \r, чтобы он работал следующим образом var json = (await stripStream.ReadToEndAsync()).Replace("\r", ""); Вот код:

 [HttpPost("webhook")]
    public async Task<IActionResult> Index()
    {
        try
        {
            using (var stripStream = new StreamReader(HttpContext.Request.Body))
            {
                var json = (await stripStream.ReadToEndAsync()).Replace("\r", "");
                var secretKey = _settings.Value.StripeSettings.WebhookSecretKey;
                // validate webhook called by stripe only
                Logger.Information($"[WEBHOOK]: Validating webhook signature {secretKey.Substring(0, 4)}**********");
                var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], secretKey);
                Logger.Information($"[WEBHOOK]: Webhook signature validated");

                // handle stripe event
                await this._stripeWebhookService.HandleAsync(stripeEvent);

                return Ok();
            }
        }catch(Exception){ // handle exception}
person Benzara Tahar    schedule 26.10.2020

Мое приложение и в этот момент продолжало генерировать исключение.

 var stripeEvent = EventUtility.ConstructEvent(
              json,
              Request.Headers["Stripe-Signature"],
              secret
            );

Читая журналы исключений, он сказал, что я использую новую версию пакета NuGet stripe.net и более старую версию на панели управления полосами. Я обновляю свою учетную запись до последней версии API, и ошибка больше не возникает.

Будьте осторожны, обновляя версии на панели инструментов, если вы используете более старые версии в других приложениях. У вас есть 72 часа, чтобы отменить обновление на панели управления, если оно нарушит работу других приложений. Я начинал с нуля, поэтому обновление не было для меня проблемой.

person user2325149    schedule 29.12.2020