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.