Matthias created HTTPCLIENT-2433:
------------------------------------

             Summary: Async HTTP/2 authentication challenge replay corrupts 
repeatable FileEntityProducer request body
                 Key: HTTPCLIENT-2433
                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-2433
             Project: HttpComponents HttpClient
          Issue Type: Bug
          Components: HttpClient (async)
    Affects Versions: 5.6.1
         Environment: Apache HttpClient: 5.6.1
Apache HttpCore: 5.4
Java: Eclipse Temurin 17.0.20
Maven: 3.9.16
Operating system: Linux
Transport: clear-text HTTP/2 over loopback using the asynchronous client
Request entity: repeatable FileEntityProducer containing 2,000,000 bytes
Authentication: HTTP Basic after a 401 challenge
            Reporter: Matthias


h2. Summary

When the asynchronous HttpClient sends a PUT containing a repeatable 
{{FileEntityProducer}} over HTTP/2 and the server immediately responds with a 
{{401 Unauthorized}} Basic-authentication challenge, the authenticated replay 
can contain a corrupted request body.

The client receives {{200 OK}}, even though the body received for the 
authenticated request is truncated. The original unauthenticated HTTP/2 stream 
can simultaneously receive more bytes than the complete entity.

The equivalent HTTP/1.1 authentication replay works correctly. Sending the 
Authorization header preemptively over HTTP/2 also works correctly.

h2. Minimal reproducer

A standalone Java/Maven reproducer is available here:

[https://github.com/mkurz/apache-httpclient5-h2-auth-replay-reproducer|https://github.com/mkurz/apache-httpclient5-h2-auth-replay-reproducer]

It directly uses Apache HttpClient 5.6.1 and its transitive HttpCore 5.4 
dependency. It does not use sbt, Gigahorse, Sonatype, or any external server.

{code:bash}
git clone 
https://github.com/mkurz/apache-httpclient5-h2-auth-replay-reproducer.git
cd apache-httpclient5-h2-auth-replay-reproducer
mvn test
{code}

The test starts an in-process loopback server and sends a deterministic 
2,000,000-byte file using {{FileEntityProducer}}.

Three cases are tested:

# HTTP/2 with preemptive Basic authentication: passes.
# HTTP/1.1 with a Basic-authentication challenge: passes.
# HTTP/2 with the same authentication challenge: fails.

h2. Representative result

{code}
status=200 expected-length=2000000 
expected-sha1=b694f093d9545edaf64a849bf7e6fc2864a23a0f
  1: auth=false protocol=HTTP/2.0 length=3229953 exact=false 
sha1=6e23e0988c6d247aa984573ea4f40ee677b0ee0a event=end
  2: auth=true protocol=HTTP/2.0 length=999423 exact=false 
sha1=60d7a5d13f025e3a10080a71b6c36164b8520a2c event=end

Tests run: 3, Failures: 1, Errors: 0, Skipped: 0
{code}

The precise corrupted lengths and checksums vary between runs, but the HTTP/2 
challenge case consistently fails while both controls pass.

h2. Expected behavior

The authenticated replay should contain exactly the original 2,000,000 bytes 
with SHA-1:

{code}
b694f093d9545edaf64a849bf7e6fc2864a23a0f
{code}

A repeatable entity producer must be reset or reproduced independently for the 
authentication retry, without bytes leaking between the original and replayed 
HTTP/2 streams.

h2. Actual behavior

The server receives an oversized body on the original unauthenticated stream 
and a truncated body on the authenticated stream. It sends {{200 OK}} after 
consuming the authenticated stream, so the client reports a successful request 
despite the corrupted upload.

h2. Real-world occurrence

This was originally observed while sbt, through Gigahorse's Apache HttpClient 
backend, published Twirl snapshot artifacts to Sonatype Central snapshots. 
Publishing appeared to succeed, but subsequent dependency resolution failed 
because the downloaded artifact did not match its published checksum.

* Downstream checksum failure:
  
[https://github.com/playframework/play-doc/actions/runs/34967229654/job/104374507676|https://github.com/playframework/play-doc/actions/runs/34967229654/job/104374507676]
* Twirl commit whose snapshot publication reproduced the corruption:
  
[https://github.com/playframework/twirl/commit/e8c94e68fdb69fc1e4bef9b2689596cced5ac2f3|https://github.com/playframework/twirl/commit/e8c94e68fdb69fc1e4bef9b2689596cced5ac2f3]
* Original sbt investigation:
  [https://github.com/sbt/sbt/pull/9772|https://github.com/sbt/sbt/pull/9772]

Gigahorse 0.9.6 works around this for known credentials by sending Basic 
authentication preemptively, and sbt adopted that release in:

[https://github.com/sbt/sbt/pull/9775|https://github.com/sbt/sbt/pull/9775]

That workaround avoids the challenge/replay path. It does not fix 
challenge-based HTTP/2 authentication replay inside HttpClient.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to