godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Specify hostname for self-signed certificates for ENet clients #1993

Closed v3141 closed 3 years ago

v3141 commented 3 years ago

Describe the project you are working on

Multiplayer battle royale style game

Describe the problem or limitation you are having in your project

I'm hosting my Godot game servers on arbitrary AWS EC2 instances. As such, for DTLS, I can't use CA-signed TLS certs since these instances are behind arbitrary IP addresses, not a domain. My only option is to provide a self-signed cert in the client app, and its corresponding private key in the dedicated server on EC2. Right now, the issue with using self-signed certs is that clients don't verify their connection using the public key they're given. Clients rely on receiving the public key (X509cert) from the server, which is susceptible to MitM attacks - anyone can provide the client with their own self signed cert.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Clients should have the option to verify their DTLS connection by using the self-signed cert they're packaged and distributed with. Obviously, you can't verify self-signed certs with CAs. Rather, the client should have the option to check whether the public key it's packaged with matches the public key of the server. A simple way to do this would be to encrypt some secret with the X509cert provided in the client, and sending that ciphertext to the server. If the server truly has the authority to establish a DTLS connection with the client, it should be able to decrypt the ciphertext using its private key. Since MitMs don't have access to the server's private key, they won't be able to decrypt anything encrypted using the server's public key, which is the same as the X509cert provided in the client app.

With this method, it's given that the client must download the app from a trusted source (e.g. Google Play Store/Apple App Store). Since the client's app comes bundled with the cert, if they can't download the app safely, then their DTLS connection will be screwed.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

I have no idea how this can be implemented in C++. It's possible that this sort of thing is too low level, in which can we can close this proposal. I think it would be best if this were implemented in the handshake, before any communication of sensitive data.

If this enhancement will not be used often, can it be worked around with a few lines of script?

No idea, but I doubt it.

Is there a reason why this should be core and not an add-on in the asset library?

DTLS is part of the core of Godot now, so this should be part of the core.

v3141 commented 3 years ago

cc @Faless

Faless commented 3 years ago

Right now, the issue with using self-signed certs is that clients don't verify their connection using the public key they're given. Clients rely on receiving the public key (X509cert) from the server, which is susceptible to MitM attacks - anyone can provide the client with their own self signed cert.

First off, it is already possible to verify self-signed certificates. Simply bundle the certificate in the client app, and pass it to PacketPeerDTLS.connect_to_peer, setting the for_hostname parameter to whatever your self-signed certificate has in the CN value. That will effectively require that the DTLS server we are connecting to uses such certificate. The connection will fail if a different certificate is provided.

Now if I guessed correctly, your problem is that you have this setup with ENet, which forces the DTLS hostname to the host/IP you are connecting to. If that's a case, I think we could fix this by just adding a set_dtls_hostname to the ENet class or an optional for_hostname parameter to set_dtls_certificate.

v3141 commented 3 years ago

Yep exactly. I've just tested my client/server setup using PacketPeerDTLS and DTLSServer, and the connection works great. The server is authenticated when the proper hostname + cert is used.

It would be great if this also worked with ENet.

Wafflily commented 3 years ago

Yes that's exactly my problem. We have DTLS self-signed certificates implemented with ENet, but no way to make them work!

Wafflily commented 3 years ago

I must be missing something. With ENet, there's 2 meanings that 'verifying certificate' can have. 1) ask a CA to validate the server certificate 2) check that the server certificate matches the client certificate

So dtls_verify=true can do 1), 2) or both. We know it doesn't do 2) alone, and that's the option we want to enable (verifying self-signed certificates). I have a hard time believing it does 1) alone. That would mean that setting a certificate on the client side is currently absolutely useless. What's left is, it does both. But then, we don't need to add another layer of complexity like this:

If that's a case, I think we could fix this by just adding a set_dtls_hostname to the ENet class or an optional for_hostname parameter to set_dtls_certificate.

We just need the option to do 2) without 1).

Faless commented 3 years ago

@Wafflily no, (D)TLS has 2 steps: 1 - Check the certificate is from a trusted CA (this includes self signed certificates as that certificate acts as a CA certificate). 2 - Check that the hostname we are connecting to matches whatever the certificate specify in the CN field.

The part that's failing in your self signed certificate is that the CN value does not match the hostname you are connecting to.

Wafflily commented 3 years ago

I'm still a bit fuzzy. I think you're saying that it checks that the server's certificate is from a trusted CA, and in the case of a self-signed certificate, it does it by comparing it to the client's certificate which acts as a CA certificate. The reason for also checking the hostname is that a MITM could intercept the certificate and pass it as its own, but couldn't fake its hostname. Am I getting close?

akien-mga commented 3 years ago

Implemented for 4.0 in https://github.com/godotengine/godot/pull/50710 and for 3.4 in https://github.com/godotengine/godot/pull/51434.

Yesyoor commented 2 years ago

Well how does it work now? Setting the CN value as a hostname serversides does not work and it also doens't work if I set it on the client side. My server is running in a docker container, the client from the hostmachine in the Godot Editor. Always get an internal handshake error from the external c++ lib

Calinou commented 2 years ago

@Yesyoor Please open a new issue on the main Godot repository with a minimal reproduction project attached.