76 lines
1.8 KiB
JavaScript
76 lines
1.8 KiB
JavaScript
/**
|
|
* TOFU Key Pinning Service
|
|
* Stores seller contact keys on first use, warns on key changes
|
|
*/
|
|
|
|
const PINNED_KEYS_STORAGE = 'kashilo_pinned_keys'
|
|
|
|
class KeyPinningService {
|
|
constructor() {
|
|
this.pinnedKeys = this.load()
|
|
}
|
|
|
|
load() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(PINNED_KEYS_STORAGE)) || {}
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
save() {
|
|
localStorage.setItem(PINNED_KEYS_STORAGE, JSON.stringify(this.pinnedKeys))
|
|
}
|
|
|
|
/**
|
|
* Check a listing's contact key against the pinned key
|
|
* @param {string} listingId
|
|
* @param {string} contactPublicKey
|
|
* @returns {'ok'|'new'|'changed'}
|
|
* - 'ok': key matches pinned key
|
|
* - 'new': first time seeing this listing, key is now pinned
|
|
* - 'changed': key differs from pinned key (possible attack)
|
|
*/
|
|
check(listingId, contactPublicKey) {
|
|
if (!listingId || !contactPublicKey) return 'new'
|
|
|
|
const pinned = this.pinnedKeys[listingId]
|
|
if (!pinned) {
|
|
this.pin(listingId, contactPublicKey)
|
|
return 'new'
|
|
}
|
|
|
|
if (pinned === contactPublicKey) return 'ok'
|
|
|
|
return 'changed'
|
|
}
|
|
|
|
pin(listingId, contactPublicKey) {
|
|
this.pinnedKeys[listingId] = contactPublicKey
|
|
this.save()
|
|
}
|
|
|
|
/**
|
|
* Accept a changed key (user explicitly trusts the new key)
|
|
*/
|
|
acceptChange(listingId, newPublicKey) {
|
|
this.pinnedKeys[listingId] = newPublicKey
|
|
this.save()
|
|
}
|
|
|
|
/**
|
|
* Remove pin for a listing
|
|
*/
|
|
unpin(listingId) {
|
|
delete this.pinnedKeys[listingId]
|
|
this.save()
|
|
}
|
|
|
|
clear() {
|
|
this.pinnedKeys = {}
|
|
localStorage.removeItem(PINNED_KEYS_STORAGE)
|
|
}
|
|
}
|
|
|
|
export const keyPinningService = new KeyPinningService()
|