capacitor-time-integrity
v0.1.0
Published
Capacitor plugin that exposes the device monotonic clock so the app can detect wall-clock tampering
Maintainers
Readme
capacitor-time-integrity
Plugin Capacitor yang mengekspos monotonic clock device supaya aplikasi bisa mendeteksi jam kalender (wall clock) yang digeser — kasus utamanya: kecurangan absensi dengan mengubah jam device.
Dibangun untuk Capacitor 8.2.0 (@capacitor/core sebagai peer dependency ^8.2.0).
Kenapa plugin ini ada
iOS tidak menyediakan public API untuk membaca toggle Settings → General → Date & Time → Set Automatically. Yang bisa dilakukan adalah memakai jam kedua yang tidak bisa dimanipulasi user:
| | Wall clock | Monotonic clock |
|---|---|---|
| API iOS | Date() | CLOCK_MONOTONIC_RAW / mach_continuous_time() |
| API Android | System.currentTimeMillis() | SystemClock.elapsedRealtime() |
| Makna | tanggal & jam kalender | lama waktu sejak boot |
| Bisa di-set user | ya | tidak |
| Reset | tidak | saat reboot |
Dengan membandingkan laju keduanya terhadap satu titik acuan, perubahan jam terdeteksi tanpa perlu koneksi ke server.
Catatan penting soal pemilihan API iOS
ProcessInfo.systemUptime, CACurrentMediaTime(), dan mach_absolute_time()
berhenti saat device tidur (semantik CLOCK_UPTIME_RAW). Kalau dipakai untuk
deteksi ini, setiap iPhone yang di-lock beberapa jam akan menghasilkan drift
palsu yang besar. Plugin ini memakai CLOCK_MONOTONIC_RAW, yang terus berjalan
saat sleep — sepadan dengan SystemClock.elapsedRealtime() di Android.
Instalasi
Paket ini berada di luar repo aplikasi, sejajar dengan plugin internal lain
di ~/project/capacitor/. Aplikasi GreatDay memakainya lewat dependency lokal:
"capacitor-time-integrity": "file:../../capacitor/capacitor-time-integrity"Penting untuk CI. Path relatif di atas hanya ada di mesin developer. Runner GitLab yang meng-clone repo aplikasi saja akan gagal pada
pnpm install. Sebelum merge, publish paket ini ke registry lalu ganti dependency-nya menjadi versi terbit, sama seperticapacitor-plugin-security-checkdkk.:npm publish # dari folder plugin ini pnpm add [email protected] # dari repo aplikasi
Setelah mengubah kode native plugin, dari repo aplikasi jalankan:
pnpm install
pnpm exec cap sync # daftarkan modul Android + pod iOScap sync menuliskan:
android/capacitor.settings.gradle→include ':capacitor-time-integrity'android/app/capacitor.build.gradle→implementation project(':capacitor-time-integrity')android/app/src/main/assets/capacitor.plugins.json→com.greatday.plugins.timeintegrity.TimeIntegrityPluginios/App/Podfile→pod 'CapacitorTimeIntegrity', :path => '../../node_modules/capacitor-time-integrity'ios/App/App/capacitor.config.json→TimeIntegrityPluginpadapackageClassList
Build layer TypeScript (dist/) diperlukan sebelum publish atau bila paket ini
diimpor lewat nama paketnya:
npm install # sekali, untuk devDependencies (tsc, rollup)
npm run build # tsc + rollup → dist/API
import { TimeIntegrity } from 'capacitor-time-integrity';
const sample = await TimeIntegrity.getTimeInfo();getTimeInfo() => Promise<TimeSample>
| Field | Tipe | Keterangan |
|---|---|---|
| wallMs | number | Wall clock, ms epoch UTC (sama dengan Date.now()) |
| continuousMs | number | Monotonic clock, ms sejak boot — tidak bisa di-set user |
| bootTimeMs | number | Estimasi wall clock saat boot (iOS: kern.boottime) |
| monotonicReliable | boolean | false di web — pemanggil harus menonaktifkan deteksi |
| autoTimeSupported | boolean | true hanya di Android |
| autoTime | boolean \| null | Settings.Global.AUTO_TIME; null di iOS/web |
| autoTimeZone | boolean \| null | Settings.Global.AUTO_TIME_ZONE; null di iOS/web |
| platform | string | ios, android, atau web |
Cara pakai: anchor + dua sinyal
Simpan satu titik acuan ketika waktu server masih dipercaya:
const sample = await TimeIntegrity.getTimeInfo();
const anchor = {
serverMs, // waktu dari server
wallMs: sample.wallMs,
continuousMs: sample.continuousMs,
offsetMs: sample.wallMs - serverMs, // selisih jam device vs server
};Lalu setiap kali butuh waktu yang tepercaya:
const now = await TimeIntegrity.getTimeInfo();
const elapsed = now.continuousMs - anchor.continuousMs; // tidak bisa dipalsukan
const trustedNowMs = anchor.serverMs + elapsed; // valid meski offline
const driftMs = now.wallMs - (anchor.wallMs + elapsed); // ≠ 0 → jam digeser sejak anchorDua sinyal ini harus dipakai berdua, karena masing-masing menutup lubang yang lain:
| Sinyal | Menangkap | Butuh online? | Lubangnya |
|---|---|---|---|
| offsetMs | jam sudah salah sebelum anchor dibuat | ya (saat anchor dibuat) | tidak melihat perubahan setelah anchor |
| driftMs | jam digeser sesudah anchor dibuat | tidak | jadi 0 bila anchor diperbarui setelah jam digeser |
Kelalaian yang mudah terjadi: memperbarui anchor tepat sebelum pemeriksaan
(mis. karena waktu server dianggap kedaluwarsa) akan mereset wallMs baseline
ke jam yang sudah digeser, sehingga driftMs kembali 0. offsetMs yang dihitung
pada saat anchor dibuat tetap merekam manipulasinya.
trustedNowMs inilah yang sebaiknya dipakai sebagai timestamp transaksi, bukan
Date.now().
Batasan
- Reboot menghapus baseline.
continuousMsreset ke ~0, jadi anchor lama tidak valid (deteksi:continuousMssekarang <continuousMsanchor). Perlu sinkronisasi ulang ke server. Mitigasi parsial: simpan waktu tepercaya tertinggi yang pernah dilihat — waktu tidak bisa mundur, jadi wall clock yang lebih kecil dari nilai itu pasti dimanipulasi. - Auto-time ON bukan jaminan. Dengan NTP/DNS palsu, jam bisa salah walau
autoTime === true. Karena itu drift tetap perlu diperiksa di Android. - Web tidak didukung.
performance.now()reset per page load dan di-throttle di tab background;monotonicReliablesengajafalse. - Jailbreak/root bisa mem-hook API monotonic. Kelas ancaman berbeda, ditangani plugin deteksi root/jailbreak terpisah.
Implementasi di aplikasi ini
Layer Angular-nya ada di repo aplikasi GreatDay,
src/app/core/services/library/time-integrity/:
time-integrity.plugin.ts— bridgeregisterPlugin('TimeIntegrity')lokal, supaya aplikasi tidak bergantung pada hasil builddist/paket ini dan otomatis memakai implementasi native setelahcap sync.time-integrity.service.ts— pengelolaan anchor,getTrustedNowMs(),getDriftMs(),getOffsetMs(), dan ratchet waktu maju.
Setelah paket ini dipublish dan terpasang dari registry, time-integrity.plugin.ts
bisa diganti dengan import { TimeIntegrity } from 'capacitor-time-integrity'
(saat itu dist/ sudah tersedia di dalam tarball paket).
