Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connecting via HTTP (unsecure) to tls-rustls-enabled HTTP/2 server returns an obsolete HTTP/0.9 response #121

Closed
Andrew15-5 opened this issue Jun 12, 2024 · 5 comments

Comments

@Andrew15-5
Copy link

Here is a MWA:

use axum_server_dual_protocol::ServerExt; // (1)

fn main() {
    let app = axum::Router::new().route("/", axum::routing::get(|| async {}));
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        let tls_config =
            axum_server::tls_rustls::RustlsConfig::from_pem_file("cert.pem", "key.pem")
                .await
                .unwrap();
        let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
        println!("Listenning on {addr}");
        // axum_server_dual_protocol::bind_dual_protocol(addr, tls_config) // (1)
        //     .set_upgrade(true) // (1)
        axum_server::bind_rustls(addr, tls_config) // (2)
            // axum_server::bind(addr) // (3)
            .serve(app.into_make_service())
            .await
            .unwrap();
    });
}
[dependencies]
axum = { version = "0.7.5", features = ["http2"] }
tokio = { version = "1.38.0", features = ["full"] }
axum-server = { version = "0.6.0", features = ["tls-rustls"] }
axum-server-dual-protocol = "0.6.0"

I also created an SSL certificate and a private key with:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=localhost"

Here is what I get with curl -sSLD - --cacert cert.pem --http0.9 http://localhost:3000 | bat -Ap:

\u{15}\u{3}\u{3}␀\u{2}\u{2}2

And here is what I get with curl -sSLD - --cacert cert.pem --http0.9 https://localhost:3000 | bat -Ap:

HTTP/2·200·␍␊
content-length:·0␍␊
date:·Wed,·12·Jun·2024·16:09:02·GMT␍␊
␍␊

If instead of secure server (2) I use unsecure one (3) I get (HTTP):

HTTP/1.1·200·OK␍␊
content-length:·0␍␊
date:·Wed,·12·Jun·2024·16:14:18·GMT␍␊
␍␊

and (HTTPS):

curl: (35) error:0A00010B:SSL routines::wrong version number

For dual protocol version (1) I get HTTP/2 200 for HTTPS and HTTP/1.1 200 OK for HTTP when .set_upgrade(true) isn't used, and if it is used then HTTP/2 200 for HTTPS and HTTP/1.1 301 Moved Permanently + HTTP/2 200 for HTTP.

This is very closely related to #48, but I figured that this is a bug, rather than a feature request, because the server neither used HTTP/1.1 (OK) as a response nor sent an error response (HTTP/1.1 5xx). Since the first case means basically the same what #48 describes (IIUC), then the only other thing left is to send some error response (I don't know if this must be 5xx or some other one). This is also the easiest of the two, IIUC.

If neither can be implemented, then this behavior should be documented (HTTP/0.9 response).

@Andrew15-5 Andrew15-5 changed the title Connecting via HTTP (unsecured) to tls-rustls-enabled HTTP/2 server returns an obsolete HTTP/0.9 response Connecting via HTTP (unsecure) to tls-rustls-enabled HTTP/2 server returns an obsolete HTTP/0.9 response Jun 12, 2024
@daxpedda
Copy link
Collaborator

I'm not entirely sure what the issue is.
Is the issue that HTTP/0.9 requests are accepted and responded to with higher HTTP versions instead of with an error?

I would have to go through the spec and figure out exactly how HTTP is supposed to react to that, but I would appreciate if you go into more detail.

@Andrew15-5
Copy link
Author

Is the issue that HTTP/0.9 requests are accepted

I haven't tried that, and I don't think anyone wants to.

       --http0.9
              (HTTP) Tells curl to be fine with HTTP version 0.9 response.

Without it (I don't remember) it won't show the response or something.

The problem is that response is being downgraded, which is not how web works, AFAIK. The downgraded version is also the discontinued HTTP/0.9 which is also outputs some garbage that I don't understand.

This whole mess shouldn't exist and if you try to access an HTTPS-only sever via HTTP it instead should return some standard error (I really hope that there is a standard way of handling this situation) with client's/request's version of HTTP response (not downgraded nor upgraded), i.e., with HTTP/1.1 response in this case.

but I would appreciate if you go into more detail.

"Details" of what exactly?

@daxpedda
Copy link
Collaborator

daxpedda commented Aug 1, 2024

but I would appreciate if you go into more detail.

"Details" of what exactly?

Unfortunately I have a hard time parsing out the issue, it would be really helpful if you can describe your issue more concisely for me.

E.g.

  1. This is what I tried.
  2. This is the output.
  3. This is what I expected instead.

Is the issue that HTTP/0.9 requests are accepted

I haven't tried that, and I don't think anyone wants to.

       --http0.9
              (HTTP) Tells curl to be fine with HTTP version 0.9 response.

I don't think its a bug to accept HTTP/0.9 requests, a HTTP/0.9 response however would be one.
I'm confused to why you used this flag in the first place.

The problem is that response is being downgraded, which is not how web works, AFAIK. The downgraded version is also the discontinued HTTP/0.9 which is also outputs some garbage that I don't understand.

I can't find an example in your OP of a downgrade.
If there is a downgrade happening, it would be a bug indeed.
Could you structure your scenario in the way I outlined above so I am able to reproduce it locally?

This whole mess shouldn't exist and if you try to access an HTTPS-only sever via HTTP it instead should return some standard error (I really hope that there is a standard way of handling this situation) with client's/request's version of HTTP response (not downgraded nor upgraded), i.e., with HTTP/1.1 response in this case.

Getting access to a HTTPS only server over HTTP would be a very serious bug!
I personally am unable to reproduce this.

After doing a lot of different attempts, I had some similar output to yours, but that is not the response, that is the request!
So maybe you are confusing the client request with the server response?

I also realized why you were using --http0.9, because when I tried to connect to a HTTPS only server over HTTP, it told me curl: (1) Received HTTP/0.9 when not allowed. I'm pretty sure that this is incorrect, because dumping the response to a file I can see its just a TLS decryption error.

E.g. using wget -q http://localhost:3000 -O test I get:
0x15 0x03 0x03 0x00 0x02 0x02 0x32

Following TLSPlaintext (because ServerHello is part of the handshake protocol):

You can reproduce the exact same response with cURL as well if you circumvent the error message by "allowing" HTTP/0.9:
curl --http0.9 http://localhost:3000 -o test

I hope this solved the mystery.

@Andrew15-5
Copy link
Author

Andrew15-5 commented Aug 2, 2024

  1. This is what I tried.

  2. This is the output.

  3. This is what I expected instead.

Easy:

  1. I tried sending an unsecure HTTP/1.1 request to the server that is configured for HTTPS-only
  2. The server returns an HTTP/0.9 response
  3. I expected an HTTP/1.1 response instead (with some error code, probably)

I'm confused to why you used this flag in the first place.

Because otherwise the curl command wouldn't show me the HTTP/0.9 response.

I can't find an example in your OP of a downgrade.
If there is a downgrade happening, it would be a bug indeed.

Going from HTTP version 1.1 to 0.9 is downgrading, the way I see it. You can't decrease version or security level, only increase. The same way you can go from HTTP to HTTPS, but not the other way around.

so I am able to reproduce it locally?

http.zip

Run cargo run and curl -sSLD - --cacert cert.pem --http0.9 http://localhost:3000 | bat -Ap.

So maybe you are confusing the client request with the server response?

No. Client sends a currently standard HTTP/1.1 request since it's http:// so it can't use a higher version.

man page
       -D, --dump-header <filename>
              (HTTP FTP) Write the received protocol headers to the specified file. If no headers
              are received, the use of this option will create an empty file.

              When  used in FTP, the FTP server response lines are considered being "headers" and
              thus are saved there.

              If this option is used several times, the last one will be used.

              Example:
               curl --dump-header store.txt https://example.com

              See also -o, --output.

0x15 0x03 0x03 0x00 0x02 0x02 0x32

Yes, so the curl's output checks out then: \u{15}\u{3}\u{3}␀\u{2}\u{2}2. It basically says the same thing but in different form through the bat command.

wget -O- http://localhost:3000 | bat -Ap Returns the same thing. But it also says:

Connecting to localhost (localhost)|127.0.0.1|:3000... connected.
HTTP request sent, awaiting response... 200 No headers, assuming HTTP/0.9
Length: unspecified

So if 2 of the most popular CLI tools says that a response without headers is an HTTP/0.9 response, then it's probably correct. We can ask their maintainers or someone else about this matter to confirm.

I realized that since it's all bytes from the TLSPlaintext struct it's better to use xxd instead:

00000000: 1503 0300 0202 32                        ......2

I hope this solved the mystery.

I guess the mystery of what those 7 bytes mean — yes, but... I probably need to find information about this.

If negotiating a version of TLS prior to 1.3,
   a server MUST check that the message either contains no data after
   legacy_compression_methods or that it contains a valid extensions
   block with no data following.  If not, then it MUST abort the
   handshake with a "decode_error" alert.
   decode_error:  A message could not be decoded because some field was
      out of the specified range or the length of the message was
      incorrect.  This alert is used for errors where the message does
      not conform to the formal protocol syntax.  This alert should
      never be observed in communication between proper implementations,
      except when messages were corrupted in the network.

Well, I guess that's the answer. It looks like requesting anything unsecure from a secure-only server is not a proper communication implementation. Though this is not directly says here, I don't know where could this be mentioned. Maybe in RFC for SSL/HTTPS (if it exists)?

In a browser, it shows those 7 bites in an empty page without any network logs. So the solution really is to separately process http:// requests, i.e., the #48 problem. Maybe this can boost the priority of #48? :)

@daxpedda
Copy link
Collaborator

daxpedda commented Aug 2, 2024

So if 2 of the most popular CLI tools says that a response without headers is an HTTP/0.9 response, then it's probably correct. We can ask their maintainers or someone else about this matter to confirm.

Well, I think we can definitely say that this is incorrect.
Only because the server response is unable to get parsed by the client, because its TLS, doesn't mean its HTTP/0.9.
Though I don't believe that's cURL or Wget fault, the server definitely isn't sending HTTP/0.9.

Considering that they do actually support the TLS protocol, I guess they could try and improve their protocol detection mechanism?

Well, I guess that's the answer. It looks like requesting anything unsecure from a secure-only server is not a proper communication implementation. Though this is not directly says here, I don't know where could this be mentioned. Maybe in RFC for SSL/HTTPS (if it exists)?

Honestly, this all seems like the most obvious outcome to me.
It is indeed the reason why #48 exists.


Some well meaning feedback:
It was not clear to me from your OP what the problem is.

Here is what I get with curl -sSLD - --cacert cert.pem --http0.9 http://localhost:3000 | bat -Ap:

\u{15}\u{3}\u{3}␀\u{2}\u{2}2

This is the server response.

And here is what I get with curl -sSLD - --cacert cert.pem --http0.9 https://localhost:3000 | bat -Ap:

HTTP/2·200·␍␊
content-length:·0␍␊
date:·Wed,·12·Jun·2024·16:09:02·GMT␍␊
␍␊

This is the client request.
I understand ofc you were just dumping the cURL output.

But your problem is that the server response is HTTP/0.9. The most crucial piece of information is how you established that the server response is HTTP/0.9. I only found out by not using your commands and seeing the cURL error response.

  1. This is what I tried.
  2. This is the output.
  3. This is what I expected instead.

Easy:

  1. I tried sending an unsecure HTTP/1.1 request to the server that is configured for HTTPS-only
  2. The server returns an HTTP/0.9 response
  3. I expected an HTTP/1.1 response instead (with some error code, probably)

This is supposed to give structure to your issue report to reduce any misinterpretation and confusion on my side.
My suggestion was to take all the information you have in your OP and structure it through these questions.

E.g.:

  1. This is what I did:
    This is how I generated my certificate: ....
    This is my server setup: <insert Rust code here>.
    This is the command I tried: curl --cacert cert.pem http://localhost:3000.
  2. This is the response I got:
    curl: (1) Received HTTP/0.9 when not allowed
  3. This is the response I expected:
    A HTTP/1.1 or HTTP/2 error response.

Notes:
When using curl --http0.9 --cacert cert.pem http://localhost:3000 | bat -Ap to allow HTTP/0.9 responses, I got the following:
\u{15}\u{3}\u{3}␀\u{2}\u{2}2

Repeat this patterns for multiple scenarios instead of merging them all in one.

I hope this is taken as constructive feedback, in no way is this meant as any form of disrespect.


In that light, I will close the issue.
Hopefully in the future we can fix #48 to mitigate this issue entirely.

@daxpedda daxpedda closed this as not planned Won't fix, can't repro, duplicate, stale Aug 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants