diff options
-rw-r--r-- | README.md | 41 | ||||
-rw-r--r-- | docs/Certificates.md | 88 |
2 files changed, 111 insertions, 18 deletions
@@ -12,7 +12,7 @@ A Client/Server game networking plugin using [QUIC](https://www.chromium.org/qui - [Quickstart](#quickstart) - [Client](#client) - [Server](#server) - - [Certificates](#certificates) + - [Certificates and server authentication](#certificates-and-server-authentication) - [Logs](#logs) - [Examples](#examples) - [Chat example](#chat-example) @@ -67,7 +67,7 @@ The implementation uses [tokio channels](https://tokio.rs/tokio/tutorial/channel Those are the features/tasks that will probably come next (in no particular order): -- [ ] Security: More certificates support, see [certificates](#certificates) +- [x] Security: More certificates support, see [certificates](#certificates) - [x] Feature: Send messages from the server to a specific client - [x] Feature: Send messages from the server to a selected group of clients - [x] Feature: Raise connection/disconnection events from the plugins @@ -77,7 +77,8 @@ Those are the features/tasks that will probably come next (in no particular orde - [ ] Performance: Messages aggregation before sending - [ ] Clean: Rework the error handling - [x] Clean: Rework the configuration input for the client & server plugins -- [ ] Documentation: Document the API +- [ ] Documentation: Fully document the API +- [ ] Tests: Add tests ## Quickstart @@ -108,14 +109,8 @@ fn start_connection(client: ResMut<Client>) { CertificateVerificationMode::SkipVerification, ) .unwrap(); - - // You can already send message(s) even before being connected, they will be buffered. - // To be trully connected, you should wait for a ConnectionEvent - // or check client.is_connected() - client - .send_message(...) - .unwrap(); -} + + // When trully connected, you will receive a ConnectionEvent ``` - To process server messages, you can use a bevy system such as the one below. The function `receive_message` is generic, here `ServerMessage` is a user provided enum deriving `Serialize` and `Deserialize`. @@ -197,29 +192,37 @@ fn handle_client_messages( You can also use `server.broadcast_message`, which will send a message to all connected clients. "Connected" here means connected to the server plugin, which happens before your own app handshakes/verifications if you have any. Use `send_group_message` if you want to control the recipients. -## Certificates +## Certificates and server authentication Bevy Quinnet (through Quinn & QUIC) uses TLS 1.3 for authentication, the server needs to provide the client with a certificate confirming its identity, and the client must be configured to trust the certificates it receives from the server. -Here are the current options available to the server and client plugins: +Here are the current options available to the server and client plugins for the server authentication: - Client : - - [x] Skip certificate verification - - [ ] "Trust on first use" certificates - - [x] Accept certificates issued by a Certificate Authority + - [x] Skip certificate verification (messages are still encrypted, but the server is not authentified) + - [x] Accept certificates issued by a Certificate Authority (implemented in [Quinn](https://github.com/quinn-rs/quinn), using [rustls](https://github.com/rustls/rustls)) + - [x] [Trust on first use](https://en.wikipedia.org/wiki/Trust_on_first_use) certificates (implemented in Quinnet, using [rustls](https://github.com/rustls/rustls)) - Server: - [x] Generate and issue a self-signed certificate - [x] Issue an already existing certificate (CA or self-signed) -- On the client: +On the client: ```rust // To accept any certificate client.connect(/*...*/, CertificateVerificationMode::SkipVerification); // To only accept certificates issued by a Certificate Authority client.connect(/*...*/, CertificateVerificationMode::SignedByCertificateAuthority); + // To use the default configuration of the Trust on first use authentication scheme + client.connect(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { + // You can configure TrustOnFirstUse through the TrustOnFirstUseConfig: + // Provide your own fingerprint store variable/file, + // or configure the actions to apply for each possible certificate verification status. + ..Default::default() + }), + ); ``` -- On the server: +On the server: ```rust // To generate a new self-signed certificate on each startup @@ -237,6 +240,8 @@ Here are the current options available to the server and client plugins: }); ``` +See more about certificates in the [certificates readme](docs/Certificates.md) + ## Logs For logs configuration, see the unoffical [bevy cheatbook](https://bevy-cheatbook.github.io/features/log.html). diff --git a/docs/Certificates.md b/docs/Certificates.md new file mode 100644 index 0000000..b2cf0ff --- /dev/null +++ b/docs/Certificates.md @@ -0,0 +1,88 @@ +# Certificates and server authentication + +## Trust on first use + +### Default configuration + +Use it like this: +```rust +client.connect(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { + ..Default::default() + }), +); +``` + +With the default configuration, known hosts and their fingerprints are stored in a file, which defaults to `quinnet/known_hosts`. +The defaults verifier behaviours are: +- For an `unknwon` certificate (first time this server is encountered) => the client trusts this certificate, stores its fingerprint and continue the connection; +- For a `trusted` certificate (the fingerprint matches the one stored for this server) => the client trusts this certificate and continue the connection; +- For an `untrusted` certificate (the certificate's fingerprint does not match the one in the store) => the client raises an event to the Bevy app and waits for an action to apply. + +### Examples configurations + +Default verifier behaviours with a custom store file: +```rust +client.connect(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { + known_hosts: KnownHosts::HostsFile("MyCustomFile".to_string()), + ..Default::default() + }), +); +``` + +Custom verifier behaviours with a custom store: +```rust +client.connect(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { + known_hosts: KnownHosts::Store(my_cert_store), + verifier_behaviour: HashMap::from([ + ( + CertVerificationStatus::UnknownCertificate, + CertVerifierBehaviour::ImmediateAction(CertVerifierAction::TrustAndStore), + ), + ( + CertVerificationStatus::UntrustedCertificate, + CertVerifierBehaviour::ImmediateAction(CertVerifierAction::AbortConnection), + ), + ( + CertVerificationStatus::TrustedCertificate, + CertVerifierBehaviour::ImmediateAction(CertVerifierAction::TrustOnce), + ), + ]), + }), +); +``` + +### Events + +The Quinnet client plugin raises Bevy events during the connection process (during the certificate verification). + +- `CertInteractionEvent`: a user action is requested before continuing. This event is only raised when the verifier behaviour for a specific certificate status is set to `CertVerifierBehaviour::RequestClientAction` +- `CertTrustUpdateEvent`: the client plugin encoutered a new trust entry to register. If the store is a file, the client plugin has already updated it. If the store is a custom hashmap given to the client plugin (via `KnownHosts::Store(my_cert_store)`), it is up to the user to update its sotre accordingly. +- `CertConnectionAbortEvent`: signals that the connection was aborted during the certificate verification (through the `CertVerifierAction::AbortConnection`). + +Here is a simple example for a custom handler for `CertInteractionEvent`: + +```rust +fn handle_cert_events(mut cert_action_events: EventReader<CertInteractionEvent>) { + for cert_event in cert_action_events.iter() { + match cert_event.status { + CertVerificationStatus::UntrustedCertificate => cert_event + .apply_cert_verifier_action(CertVerifierAction::AbortConnection) + .unwrap(), + _ => cert_event + .apply_cert_verifier_action(CertVerifierAction::TrustOnce) + .unwrap(), + } + } +} +``` + +### Fingerprints + +Fingerprints in Quinnet are a SHA-256 hash of the certificate data in DER form. + +### Known hosts file format + +This hosts file format is really simplistic for now. +There is one line per entry, and each entry is a server name (as dns or ip) followed by a space, followed by the currently known fingerprint encoded in base64. + +This means that if two servers are hosted on the same machine on two different ports, they should currently share the same certificate to avoid any conflict. |