Lompat ke konten
dokumentasi.id
Commit 0da916f
v1
Star0

Ada commit baru di branch utama.

2a17915

7

Verifikasi Host Key TOFU

Setiap koneksi SSH-family di Termimus melewati satu gerbang pemeriksaan host key: SshClientHandler. Handler ini membandingkan fingerprint SHA-256 dari public key yang dipresentasikan server dengan entri yang tersimpan di tabel known_hosts, memakai model Trust On First Use (TOFU). Selama fingerprint cocok, koneksi lanjut; begitu fingerprint berubah untuk alamat dan port yang sama, koneksi ditolak. README menempatkan mekanisme ini sebagai bagian dari jaminan keamanan lokal aplikasi dan menyebutnya secara eksplisit sebagai perlindungan terhadap serangan Man-in-the-Middle. README.md:88 README.md:219

Handler didefinisikan sebagai struct biasa yang membawa address, port, dan db: Arc<Database>, lalu mengimplementasikan trait Handler dari russh lewat method check_server_key. Komentar pada definisinya menyatakan bahwa penolakan dilakukan langsung bila key berubah untuk alamat:port yang sebelumnya sudah dipercaya, setara dengan perlindungan berkas known_hosts milik OpenSSH. src-tauri/src/ssh/mod.rs:47-56

Satu gerbang untuk semua koneksi SSH

SshClientHandler tidak dipakai di satu tempat saja. Empat jalur koneksi membangun handler yang sama dengan db yang sama, sehingga tidak ada jalur yang bisa melewati verifikasi.

graph LR
  term["SshManager::connect<br/>target dan jump host"]
  sftp["SftpManager::connect"]
  tun["TunnelManager::start"]
  detect["detect_os_via_exec"]
  handler["SshClientHandler.check_server_key"]
  get["Database.get_known_host"]
  save["Database.save_known_host"]
  kh[("known_hosts")]

  term --> handler
  sftp --> handler
  tun --> handler
  detect --> handler
  handler --> get
  get --> kh
  handler --> save
  save --> kh

  style handler fill:#f59e0b,color:#000
  style get fill:#8b5cf6,color:#fff
  style save fill:#8b5cf6,color:#fff
  style kh fill:#336791,color:#fff
  style term fill:#2d7d46,color:#fff
  style sftp fill:#2d7d46,color:#fff
  style tun fill:#2d7d46,color:#fff
  style detect fill:#2d7d46,color:#fff
%% fig: Empat jalur koneksi menuju satu pemeriksaan host key
%% desc: Terminal, SFTP, tunnel, dan deteksi OS semuanya membangun SshClientHandler yang membaca serta menulis tabel known_hosts.
Fig. 1: Empat jalur koneksi menuju satu pemeriksaan host keyTerminal, SFTP, tunnel, dan deteksi OS semuanya membangun SshClientHandler yang membaca serta menulis tabel known_hosts.

Keempat pemanggil itu adalah handshake target di SshManager::connect beserta handshake jump host di method yang sama, koneksi SFTP di SftpManager::connect, pembukaan tunnel di TunnelManager::start, dan koneksi latar untuk detect_os_via_exec. Pada SFTP, handler dibangun sebelum client::connect_stream dipanggil, sehingga kegagalan verifikasi muncul sebagai error koneksi SSH. src-tauri/src/ssh/mod.rs:464-481 src-tauri/src/ssh/mod.rs:369-375 src-tauri/src/sftp/mod.rs:67-82 src-tauri/src/tunnel/mod.rs:62-77 src-tauri/src/ssh/mod.rs:853-860

Tiga cabang keputusan

check_server_key menerima server_public_key, membentuk fingerprint dengan HashAlg::Sha256, mencatat key_type dari server_public_key.algorithm(), lalu mengambil entri lama lewat self.db.get_known_host(&self.address, self.port). Hasilnya bercabang menjadi tiga: entri belum ada, entri ada dan fingerprint sama, atau entri ada dan fingerprint berbeda. src-tauri/src/ssh/mod.rs:61-100 src-tauri/src/ssh/mod.rs:65-67

sequenceDiagram
    participant R as russh
    participant H as SshClientHandler
    participant DB as Database
    participant KH as known_hosts
    R->>H: check_server_key server_public_key
    H->>H: fingerprint HashAlg::Sha256 dan key_type
    H->>DB: get_known_host address port
    DB->>KH: SELECT by PRIMARY KEY address port
    KH-->>DB: baris atau kosong
    DB-->>H: Option KnownHost
    alt entri belum ada
        H->>DB: save_known_host entri baru
        DB->>KH: INSERT ON CONFLICT UPDATE
        H-->>R: Ok true
    else fingerprint sama
        H->>DB: save_known_host last_seen_at baru
        H-->>R: Ok true
    else fingerprint berbeda
        H-->>R: Err REMOTE HOST IDENTIFICATION HAS CHANGED
    end
    style H fill:#f59e0b,color:#000
    style R fill:#2d7d46,color:#fff
    style DB fill:#8b5cf6,color:#fff
    style KH fill:#336791,color:#fff
%% fig: Alur TOFU di dalam check_server_key
%% desc: Handler membaca known_hosts, lalu mencatat key baru, memperbarui last_seen_at, atau menolak koneksi saat fingerprint berubah.
Fig. 2: Alur TOFU di dalam check_server_keyHandler membaca known_hosts, lalu mencatat key baru, memperbarui last_seen_at, atau menolak koneksi saat fingerprint berubah.

Cabang pertama adalah inti Trust On First Use: handler menyusun KnownHost baru berisi first_seen_at dan last_seen_at yang sama, menyimpannya lewat save_known_host, lalu mengembalikan Ok(true). Cabang kedua memperbarui last_seen_at pada entri lama tanpa menyentuh fingerprint, juga dengan Ok(true). Cabang ketiga tidak mengembalikan Ok(false), melainkan Err(anyhow!...) sehingga handshake berhenti. src-tauri/src/ssh/mod.rs:71-84 src-tauri/src/ssh/mod.rs:85-91 src-tauri/src/ssh/mod.rs:92-98

CabangKondisi matchAksi ke known_hostsNilai balik
Key baruNonesave_known_host dengan first_seen_at = last_seen_at = waktu sekarangOk(true)
Key cocokSome(known) if known.fingerprint == fingerprintsave_known_host dengan last_seen_at diperbaruiOk(true)
Key berubahSome(known)tidak ada penulisanErr berisi pesan REMOTE HOST IDENTIFICATION HAS CHANGED

Ketiga cabang itu berbagi satu sumber data: get_known_host melakukan SELECT address, port, key_type, fingerprint, first_seen_at, last_seen_at FROM known_hosts WHERE address = ?1 AND port = ?2. src-tauri/src/db/mod.rs:843-861

Bentuk data host yang dipercaya

Struct KnownHost memuat enam field yang seluruhnya diserialisasi: address, port, key_type, fingerprint, first_seen_at, dan last_seen_at. Tidak ada kolom tambahan untuk status atau alasan penolakan; satu baris mewakili satu pasangan alamat:port. src-tauri/src/db/models.rs:269-277

KolomTipe SQLPeran dalam verifikasi
addressTEXT NOT NULLbagian pertama PRIMARY KEY (address, port) dan kunci pencarian
portINTEGER NOT NULLbagian kedua primary key; port berbeda diperlakukan sebagai host berbeda
key_typeTEXT NOT NULLalgoritma key dari server_public_key.algorithm(), hanya untuk tampilan
fingerprintTEXT NOT NULLsatu-satunya nilai yang dibandingkan saat verifikasi
first_seen_atTEXT NOT NULLwaktu key pertama dipercaya, ikut dikutip di pesan penolakan
last_seen_atTEXT NOT NULLdiperbarui setiap kali fingerprint cocok, dipakai untuk urutan daftar

Skema tabelnya dideklarasikan dengan PRIMARY KEY (address, port), sehingga satu host hanya punya satu key yang dipercaya pada satu waktu. src-tauri/src/db/mod.rs:112-120 Penulisan memakai INSERT ... ON CONFLICT(address, port) DO UPDATE SET key_type=excluded.key_type, fingerprint=excluded.fingerprint, last_seen_at=excluded.last_seen_at; perhatikan bahwa first_seen_at sengaja tidak ikut di-update, jadi waktu kepercayaan pertama tetap terjaga walau last_seen_at terus bergerak. src-tauri/src/db/mod.rs:863-882

Ketika fingerprint berubah

⚠️ Cabang mismatch mengembalikan Err, sehingga client::connect_stream gagal dan koneksi tidak pernah sampai ke tahap autentikasi. Pesan yang dipakai menyebut address, port, fingerprint yang baru, fingerprint yang lama, dan first_seen_at dari key lama, lalu menyarankan menghapus entri lama dari Known Hosts bila perubahan itu memang dipercaya. src-tauri/src/ssh/mod.rs:92-98

Pada terminal, kegagalan ini muncul setelah progress step: 2 dengan nama host_key diumumkan, dan pemanggil melaporkannya lewat jalur error koneksi masing-masing, misalnya Connection to target failed: {e} untuk target dan SFTP SSH connection failed: {e} untuk SFTP. src-tauri/src/ssh/mod.rs:464-481 src-tauri/src/sftp/mod.rs:67-82

Mereset kepercayaan satu host

Satu-satunya cara memperbaiki mismatch adalah menghapus barisnya, karena handler tidak punya jalur untuk menimpa fingerprint yang berbeda secara diam-diam. Penghapusan itu tersedia lewat command known_host_delete, yang meneruskan address dan port ke delete_known_host dan mengeksekusi DELETE FROM known_hosts WHERE address = ?1 AND port = ?2. Daftar host yang dipercaya dibaca lewat known_host_list yang memanggil list_known_hosts dan mengurutkan hasilnya dengan ORDER BY last_seen_at DESC. src-tauri/src/commands/mod.rs:1427-1439 src-tauri/src/db/mod.rs:884-891 src-tauri/src/db/mod.rs:893-909

Di frontend kedua command itu dibungkus sebagai listKnownHosts dan deleteKnownHost pada objek api. src/lib/api.ts:354-357 Store useKnownHostsStore memanggil deleteKnownHost lalu langsung menyegarkan daftar, sehingga tabel di UI tidak menampilkan entri yang sudah dihapus. src/stores/useKnownHostsStore.ts:30-33 Sebelum penghapusan dijalankan, tab Known Hosts menampilkan dialog konfirmasi berjudul "Reset Trusted Host Key" yang menjelaskan bahwa Termimus akan menerima key baru server pada koneksi berikutnya. src/components/settings/SettingsView.tsx:457-467

Trust store tidak ikut ditimpa saat restore

Verifikasi TOFU hanya bermakna bila isi known_hosts tidak bisa diubah dari luar. Karena itu proses restore dan merge sengaja menolak mengganti fingerprint yang sudah dipercaya: entri baru dan entri yang fingerprint-nya identik tetap diimpor, sedangkan entri yang berbeda hanya dihitung sebagai known_hosts_conflicts dan dibiarkan di luar tabel. Aturan lengkapnya, termasuk efek opsi replace_all, dibahas di Backup, Sync Relay, dan API Server. src-tauri/src/db/mod.rs:1202-1233