aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md41
-rw-r--r--docs/Certificates.md88
2 files changed, 111 insertions, 18 deletions
diff --git a/README.md b/README.md
index 4bfaf3d..25afc02 100644
--- a/README.md
+++ b/README.md
@@ -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.