feat: add Gemini and Spartan protocol support
This commit is contained in:
parent
a4a49f41d7
commit
fa7aa5557c
48 changed files with 4992 additions and 161 deletions
56
app/lib/protocols/tofu.dart
Normal file
56
app/lib/protocols/tofu.dart
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/// Trust-on-first-use pinning for the Gemini protocol. Gemini hosts use
|
||||
/// self-signed certificates with no authority behind them, so the client
|
||||
/// itself decides what to trust: the first visit pins the server
|
||||
/// certificate's fingerprint, and later visits compare against that pin.
|
||||
///
|
||||
/// A fingerprint change alone is ambiguous: it is the routine path when an
|
||||
/// operator renews an expired certificate and the alarm path when someone
|
||||
/// else is on the line. The decision below is expiry-aware, so the common
|
||||
/// renewal never interrupts the reader while an unexplained change, one that
|
||||
/// happens while the pinned certificate is still valid, does.
|
||||
library;
|
||||
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
/// A pinned server certificate: its SHA-256 fingerprint over the DER
|
||||
/// encoding, and the certificate's own end of validity, which tells a later
|
||||
/// change from a renewal.
|
||||
class TofuPin {
|
||||
const TofuPin({required this.fingerprint, required this.expires});
|
||||
|
||||
/// Sixty-four lowercase hex characters.
|
||||
final String fingerprint;
|
||||
final DateTime expires;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is TofuPin && other.fingerprint == fingerprint;
|
||||
|
||||
@override
|
||||
int get hashCode => fingerprint.hashCode;
|
||||
}
|
||||
|
||||
/// The pin of a DER-encoded certificate.
|
||||
TofuPin pinOf(List<int> derCertificate, {required DateTime expires}) =>
|
||||
TofuPin(fingerprint: sha256.convert(derCertificate).toString(), expires: expires);
|
||||
|
||||
/// What the client should do with a presented certificate.
|
||||
enum TofuDecision {
|
||||
/// Nothing is pinned yet; accept and pin what was presented.
|
||||
firstUse,
|
||||
|
||||
/// The presented certificate matches the pin.
|
||||
match,
|
||||
|
||||
/// The pin has expired and the presented certificate replaces it.
|
||||
rePin,
|
||||
|
||||
/// The pin is still valid and the presented certificate is not it.
|
||||
refuse,
|
||||
}
|
||||
|
||||
TofuDecision decideTofu(TofuPin? pin, String fingerprint, DateTime now) {
|
||||
if (pin == null) return TofuDecision.firstUse;
|
||||
if (pin.fingerprint == fingerprint) return TofuDecision.match;
|
||||
return pin.expires.isAfter(now) ? TofuDecision.refuse : TofuDecision.rePin;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue