Azure REST API Accepts HTTP/2 Request it Cannot Process

Kaitlyn Kenwell 20 Reputation points
2024-05-24T13:57:08.9566667+00:00

Description

Our code is a Rust backend service, attempting to call the Azure /email:send API using reqwest, with rustls providing the TLS implementation. During the TLS handshake, rustls includes an ALPN extension in its ClientHello message to the server, offering the protocols h2 and http/1.1. In the server's portion of the handshake, it accepts the h2 protocol offered by the client. Once the handshake has been completed, the client proceeds to send its request using HTTP/2. Due to what appears to be a faulty implementation of the HTTP/2 protocol by the Azure servers themselves, the server always returns a 400 Bad Request response to the client, despite the request being well-formed, and despite the calling code functioning as expected if the HTTP client is forced to use HTTP/1.1.

The underlying HTTP/2 protocol implementation being used is h2, which the maintainers note has been validated with h2spec to be compliant with the HTTP/2 specification. I independently confirmed this against a server using this library. I also ran h2spec against the Azure endpoint our code was calling, and it failed 26 tests (27 when tested against a stricter interpretation of the specification).

Reproduction

A verbatim extract of our calling code can be found here. This code was compiled with the latest versions of the following libraries, configured with the following features:

  • serde, features = "derive"
  • serde_json
  • reqwest, default-features = false. features = ["rustls-tls", "http2", "json"]
  • time, features = ["parsing", "serde-well-known"]
  • sha2
  • hmac
  • base64

Note that the implementations of both sha256hash(input: &str) -> [u8; 32] and sha256hmac(key: &[u8], data: &[u8]) -> [u8; 32] are correct. The HMAC implementation is the basis of both our Azure and AWS request signing code.

self.host can be replaced with the Azure host to send the request to. self.api_key is the base64-encoded API key, self.api_version is the version of the email API, and self.client is the HTTP client. When constructed using Client::new(), the code will receive a 400 Bad Request response from the server. When constructed using Client::builder().http1_only().build().unwrap(), the request will complete successfully.

Resolution

The primary resolution for this issue would be to fix the underlying HTTP/2 implementation to ensure Azure services can correctly process requests sent using HTTP/2. If this is not feasible, Azure services should be configured to reject h2 protocol offers from clients, and fallback to HTTP/1.x, which is confirmed as working properly.

Azure Communication Services
Azure Communication Services
An Azure communication platform for deploying applications across devices and platforms.
894 questions
{count} votes

Accepted answer
  1. ajkuma 26,636 Reputation points Microsoft Employee
    2024-05-29T09:08:57.97+00:00

    Summarizing the answer shared by @Kaitlyn Kenwell

    Scenario:

    Rust backend service leveraging reqwest and rustls to call the Azure /emailAPI. During the TLS handshake, rustls offers h2 and http/1.1 protocols, and the server selects h2. However, when the client sends its request using HTTP/2, the server consistently returns a 400 Bad Request response. This issue does not occur when forcing the client to use HTTP/1.1.

    Resolution: Copying the answer shared by @Kaitlyn Kenwell

    Try fixing the underlying HTTP/2 implementation so Azure services can correctly process HTTP/2 requests. If this is not feasible, configure service to reject h2 protocol offers and fallback to HTTP/1.x.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.