73 lines
3.2 KiB
TypeScript
73 lines
3.2 KiB
TypeScript
|
|
import { ShadeSessionManager } from '../../packages/shade-core/src/index.js';
|
||
|
|
import { SubtleCryptoProvider, MemoryStorage } from '../../packages/shade-crypto-web/src/index.js';
|
||
|
|
|
||
|
|
const crypto = new SubtleCryptoProvider();
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
console.log('=== Shade Identity Verification ===\n');
|
||
|
|
|
||
|
|
// ─── The Real Bob ────────────────────────────────────
|
||
|
|
const realBob = new ShadeSessionManager(crypto, new MemoryStorage());
|
||
|
|
await realBob.initialize();
|
||
|
|
|
||
|
|
console.log('Real Bob fingerprint:');
|
||
|
|
console.log(' Full: ', await realBob.getIdentityFingerprint());
|
||
|
|
console.log(' Short: ', await realBob.getShortFingerprint());
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
// ─── An Imposter ─────────────────────────────────────
|
||
|
|
const evilBob = new ShadeSessionManager(crypto, new MemoryStorage());
|
||
|
|
await evilBob.initialize();
|
||
|
|
|
||
|
|
console.log('Evil Bob (imposter) fingerprint:');
|
||
|
|
console.log(' Full: ', await evilBob.getIdentityFingerprint());
|
||
|
|
console.log(' Short: ', await evilBob.getShortFingerprint());
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
console.log('━━━ The fingerprints are completely different ━━━');
|
||
|
|
console.log('In a real scenario, Alice would call Real Bob on the phone,');
|
||
|
|
console.log('read her safety number, and Real Bob would read his back.');
|
||
|
|
console.log('If the numbers match, Alice can trust that her session');
|
||
|
|
console.log('is talking to Real Bob and not Evil Bob.\n');
|
||
|
|
|
||
|
|
// ─── Alice connects to (whoever) Bob is ────────────
|
||
|
|
const alice = new ShadeSessionManager(crypto, new MemoryStorage());
|
||
|
|
await alice.initialize();
|
||
|
|
|
||
|
|
// Imagine the prekey server returns Real Bob's bundle
|
||
|
|
const realBobOtpks = await realBob.generateOneTimePreKeys(5);
|
||
|
|
const realBobBundle = await realBob.createPreKeyBundle();
|
||
|
|
realBobBundle.oneTimePreKey = {
|
||
|
|
keyId: realBobOtpks[0].keyId,
|
||
|
|
publicKey: realBobOtpks[0].keyPair.publicKey,
|
||
|
|
};
|
||
|
|
|
||
|
|
await alice.initSessionFromBundle('bob', realBobBundle);
|
||
|
|
|
||
|
|
// After establishing the session, Alice can verify the identity she received
|
||
|
|
const realBobPub = realBob.getPublicIdentity();
|
||
|
|
const evilBobPub = evilBob.getPublicIdentity();
|
||
|
|
|
||
|
|
console.log('Alice verifies who she actually connected to:');
|
||
|
|
console.log(' Is it Real Bob? ', await alice.verifyRemoteIdentity('bob', realBobPub.dhKey));
|
||
|
|
console.log(' Is it Evil Bob? ', await alice.verifyRemoteIdentity('bob', evilBobPub.dhKey));
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
// ─── Identity Change Scenario ─────────────────────────
|
||
|
|
console.log('━━━ Bob legitimately rotates his identity ━━━');
|
||
|
|
const newBundle = await realBob.rotateIdentity();
|
||
|
|
console.log('New Bob fingerprint:', await realBob.getIdentityFingerprint());
|
||
|
|
console.log();
|
||
|
|
console.log('Alice fetches the new bundle and verifies it matches');
|
||
|
|
console.log('expected new identity (after out-of-band confirmation).');
|
||
|
|
|
||
|
|
// Alice would compare fingerprints again, then accept the change:
|
||
|
|
await alice.acceptIdentityChange('bob', newBundle.identityDHKey);
|
||
|
|
console.log('Alice accepted the new identity.');
|
||
|
|
console.log();
|
||
|
|
|
||
|
|
console.log('=== Done ===');
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch(console.error);
|