Use the default configuration like this:
client.open_connection(/*...*/, 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
unknown
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.
Default verifier behaviours with a custom store file:
client.open_connection(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig {
known_hosts: KnownHosts::HostsFile("MyCustomFile".to_string()),
..Default::default()
}),
);
Custom verifier behaviours with a custom store:
client.open_connection(/*...*/, 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),
),
]),
}),
);
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 toCertVerifierBehaviour::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 (viaKnownHosts::Store(my_cert_store)
), it is up to the user to update its store accordingly.CertConnectionAbortEvent
: signals that the connection was aborted during the certificate verification (through theCertVerifierAction::AbortConnection
).
Here is a simple example for a custom handler of CertInteractionEvent
:
fn handle_cert_events(mut cert_action_events: EventReader<CertInteractionEvent>) {
// We may receive a CertInteractionEvent during the connection
for cert_event in cert_action_events.iter() {
match cert_event.status {
// We want to abort the connection if the certificate is untrusted, else we continue
CertVerificationStatus::UntrustedCertificate => cert_event
.apply_cert_verifier_action(CertVerifierAction::AbortConnection)
.unwrap(),
_ => cert_event
.apply_cert_verifier_action(CertVerifierAction::TrustOnce)
.unwrap(),
}
}
}
Fingerprints in Quinnet are a SHA-256 hash of the certificate data in DER form.
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.
Example:
::1 o1cpTe602uTq4pVwT+km8QtEPQE/xCAgk+3AicW/i9g=
1234::1234 kzXIwhvMSbWCQOimT3btnFlmc/Lq0UN0JhSeQadaGbg=
This simple format implies 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.