You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			418 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			418 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
| /* global libsignal, textsecure */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| const {
 | |
|   SecretSessionCipher,
 | |
|   createCertificateValidator,
 | |
|   _createSenderCertificateFromBuffer,
 | |
|   _createServerCertificateFromBuffer,
 | |
| } = window.Signal.Metadata;
 | |
| const {
 | |
|   bytesFromString,
 | |
|   stringFromBytes,
 | |
|   arrayBufferToBase64,
 | |
| } = window.Signal.Crypto;
 | |
| 
 | |
| function InMemorySignalProtocolStore() {
 | |
|   this.store = {};
 | |
| }
 | |
| 
 | |
| function toString(thing) {
 | |
|   if (typeof thing === 'string') {
 | |
|     return thing;
 | |
|   }
 | |
|   return arrayBufferToBase64(thing);
 | |
| }
 | |
| 
 | |
| InMemorySignalProtocolStore.prototype = {
 | |
|   Direction: {
 | |
|     SENDING: 1,
 | |
|     RECEIVING: 2,
 | |
|   },
 | |
| 
 | |
|   getIdentityKeyPair() {
 | |
|     return Promise.resolve(this.get('identityKey'));
 | |
|   },
 | |
|   getLocalRegistrationId() {
 | |
|     return Promise.resolve(this.get('registrationId'));
 | |
|   },
 | |
|   put(key, value) {
 | |
|     if (
 | |
|       key === undefined ||
 | |
|       value === undefined ||
 | |
|       key === null ||
 | |
|       value === null
 | |
|     ) {
 | |
|       throw new Error('Tried to store undefined/null');
 | |
|     }
 | |
|     this.store[key] = value;
 | |
|   },
 | |
|   get(key, defaultValue) {
 | |
|     if (key === null || key === undefined) {
 | |
|       throw new Error('Tried to get value for undefined/null key');
 | |
|     }
 | |
|     if (key in this.store) {
 | |
|       return this.store[key];
 | |
|     }
 | |
| 
 | |
|     return defaultValue;
 | |
|   },
 | |
|   remove(key) {
 | |
|     if (key === null || key === undefined) {
 | |
|       throw new Error('Tried to remove value for undefined/null key');
 | |
|     }
 | |
|     delete this.store[key];
 | |
|   },
 | |
| 
 | |
|   isTrustedIdentity(identifier, identityKey) {
 | |
|     if (identifier === null || identifier === undefined) {
 | |
|       throw new Error('tried to check identity key for undefined/null key');
 | |
|     }
 | |
|     if (!(identityKey instanceof ArrayBuffer)) {
 | |
|       throw new Error('Expected identityKey to be an ArrayBuffer');
 | |
|     }
 | |
|     const trusted = this.get(`identityKey${identifier}`);
 | |
|     if (trusted === undefined) {
 | |
|       return Promise.resolve(true);
 | |
|     }
 | |
|     return Promise.resolve(toString(identityKey) === toString(trusted));
 | |
|   },
 | |
|   loadIdentityKey(identifier) {
 | |
|     if (identifier === null || identifier === undefined) {
 | |
|       throw new Error('Tried to get identity key for undefined/null key');
 | |
|     }
 | |
|     return Promise.resolve(this.get(`identityKey${identifier}`));
 | |
|   },
 | |
|   saveIdentity(identifier, identityKey) {
 | |
|     if (identifier === null || identifier === undefined) {
 | |
|       throw new Error('Tried to put identity key for undefined/null key');
 | |
|     }
 | |
|     const address = libsignal.SignalProtocolAddress.fromString(identifier);
 | |
| 
 | |
|     const existing = this.get(`identityKey${address.getName()}`);
 | |
|     this.put(`identityKey${address.getName()}`, identityKey);
 | |
| 
 | |
|     if (existing && toString(identityKey) !== toString(existing)) {
 | |
|       return Promise.resolve(true);
 | |
|     }
 | |
| 
 | |
|     return Promise.resolve(false);
 | |
|   },
 | |
| 
 | |
|   /* Returns a prekeypair object or undefined */
 | |
|   loadPreKey(keyId) {
 | |
|     let res = this.get(`25519KeypreKey${keyId}`);
 | |
|     if (res !== undefined) {
 | |
|       res = { pubKey: res.pubKey, privKey: res.privKey };
 | |
|     }
 | |
|     return Promise.resolve(res);
 | |
|   },
 | |
|   storePreKey(keyId, keyPair) {
 | |
|     return Promise.resolve(this.put(`25519KeypreKey${keyId}`, keyPair));
 | |
|   },
 | |
|   removePreKey(keyId) {
 | |
|     return Promise.resolve(this.remove(`25519KeypreKey${keyId}`));
 | |
|   },
 | |
| 
 | |
|   /* Returns a signed keypair object or undefined */
 | |
|   loadSignedPreKey(keyId) {
 | |
|     let res = this.get(`25519KeysignedKey${keyId}`);
 | |
|     if (res !== undefined) {
 | |
|       res = { pubKey: res.pubKey, privKey: res.privKey };
 | |
|     }
 | |
|     return Promise.resolve(res);
 | |
|   },
 | |
|   storeSignedPreKey(keyId, keyPair) {
 | |
|     return Promise.resolve(this.put(`25519KeysignedKey${keyId}`, keyPair));
 | |
|   },
 | |
|   removeSignedPreKey(keyId) {
 | |
|     return Promise.resolve(this.remove(`25519KeysignedKey${keyId}`));
 | |
|   },
 | |
| 
 | |
|   loadSession(identifier) {
 | |
|     return Promise.resolve(this.get(`session${identifier}`));
 | |
|   },
 | |
|   storeSession(identifier, record) {
 | |
|     return Promise.resolve(this.put(`session${identifier}`, record));
 | |
|   },
 | |
|   removeSession(identifier) {
 | |
|     return Promise.resolve(this.remove(`session${identifier}`));
 | |
|   },
 | |
|   removeAllSessions(identifier) {
 | |
|     // eslint-disable-next-line no-restricted-syntax
 | |
|     for (const id in this.store) {
 | |
|       if (id.startsWith(`session${identifier}`)) {
 | |
|         delete this.store[id];
 | |
|       }
 | |
|     }
 | |
|     return Promise.resolve();
 | |
|   },
 | |
| };
 | |
| 
 | |
| describe('SecretSessionCipher', () => {
 | |
|   it('successfully roundtrips', async function thisNeeded() {
 | |
|     this.timeout(4000);
 | |
| 
 | |
|     const aliceStore = new InMemorySignalProtocolStore();
 | |
|     const bobStore = new InMemorySignalProtocolStore();
 | |
| 
 | |
|     await _initializeSessions(aliceStore, bobStore);
 | |
| 
 | |
|     const aliceIdentityKey = await aliceStore.getIdentityKeyPair();
 | |
| 
 | |
|     const trustRoot = await libsignal.Curve.async.generateKeyPair();
 | |
|     const senderCertificate = await _createSenderCertificateFor(
 | |
|       trustRoot,
 | |
|       '+14151111111',
 | |
|       1,
 | |
|       aliceIdentityKey.pubKey,
 | |
|       31337
 | |
|     );
 | |
|     const aliceCipher = new SecretSessionCipher(aliceStore);
 | |
| 
 | |
|     const ciphertext = await aliceCipher.encrypt(
 | |
|       new libsignal.SignalProtocolAddress('+14152222222', 1),
 | |
|       senderCertificate,
 | |
|       bytesFromString('smert za smert')
 | |
|     );
 | |
| 
 | |
|     const bobCipher = new SecretSessionCipher(bobStore);
 | |
| 
 | |
|     const decryptResult = await bobCipher.decrypt(
 | |
|       createCertificateValidator(trustRoot.pubKey),
 | |
|       ciphertext,
 | |
|       31335
 | |
|     );
 | |
| 
 | |
|     assert.strictEqual(
 | |
|       stringFromBytes(decryptResult.content),
 | |
|       'smert za smert'
 | |
|     );
 | |
|     assert.strictEqual(decryptResult.sender.toString(), '+14151111111.1');
 | |
|   });
 | |
| 
 | |
|   it('fails when untrusted', async function thisNeeded() {
 | |
|     this.timeout(4000);
 | |
| 
 | |
|     const aliceStore = new InMemorySignalProtocolStore();
 | |
|     const bobStore = new InMemorySignalProtocolStore();
 | |
| 
 | |
|     await _initializeSessions(aliceStore, bobStore);
 | |
| 
 | |
|     const aliceIdentityKey = await aliceStore.getIdentityKeyPair();
 | |
| 
 | |
|     const trustRoot = await libsignal.Curve.async.generateKeyPair();
 | |
|     const falseTrustRoot = await libsignal.Curve.async.generateKeyPair();
 | |
|     const senderCertificate = await _createSenderCertificateFor(
 | |
|       falseTrustRoot,
 | |
|       '+14151111111',
 | |
|       1,
 | |
|       aliceIdentityKey.pubKey,
 | |
|       31337
 | |
|     );
 | |
|     const aliceCipher = new SecretSessionCipher(aliceStore);
 | |
| 
 | |
|     const ciphertext = await aliceCipher.encrypt(
 | |
|       new libsignal.SignalProtocolAddress('+14152222222', 1),
 | |
|       senderCertificate,
 | |
|       bytesFromString('и вот я')
 | |
|     );
 | |
| 
 | |
|     const bobCipher = new SecretSessionCipher(bobStore);
 | |
| 
 | |
|     try {
 | |
|       await bobCipher.decrypt(
 | |
|         createCertificateValidator(trustRoot.pubKey),
 | |
|         ciphertext,
 | |
|         31335
 | |
|       );
 | |
|       throw new Error('It did not fail!');
 | |
|     } catch (error) {
 | |
|       assert.strictEqual(error.message, 'Invalid signature');
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   it('fails when expired', async function thisNeeded() {
 | |
|     this.timeout(4000);
 | |
| 
 | |
|     const aliceStore = new InMemorySignalProtocolStore();
 | |
|     const bobStore = new InMemorySignalProtocolStore();
 | |
| 
 | |
|     await _initializeSessions(aliceStore, bobStore);
 | |
| 
 | |
|     const aliceIdentityKey = await aliceStore.getIdentityKeyPair();
 | |
| 
 | |
|     const trustRoot = await libsignal.Curve.async.generateKeyPair();
 | |
|     const senderCertificate = await _createSenderCertificateFor(
 | |
|       trustRoot,
 | |
|       '+14151111111',
 | |
|       1,
 | |
|       aliceIdentityKey.pubKey,
 | |
|       31337
 | |
|     );
 | |
|     const aliceCipher = new SecretSessionCipher(aliceStore);
 | |
| 
 | |
|     const ciphertext = await aliceCipher.encrypt(
 | |
|       new libsignal.SignalProtocolAddress('+14152222222', 1),
 | |
|       senderCertificate,
 | |
|       bytesFromString('и вот я')
 | |
|     );
 | |
| 
 | |
|     const bobCipher = new SecretSessionCipher(bobStore);
 | |
| 
 | |
|     try {
 | |
|       await bobCipher.decrypt(
 | |
|         createCertificateValidator(trustRoot.pubKey),
 | |
|         ciphertext,
 | |
|         31338
 | |
|       );
 | |
|       throw new Error('It did not fail!');
 | |
|     } catch (error) {
 | |
|       assert.strictEqual(error.message, 'Certificate is expired');
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   it('fails when wrong identity', async function thisNeeded() {
 | |
|     this.timeout(4000);
 | |
| 
 | |
|     const aliceStore = new InMemorySignalProtocolStore();
 | |
|     const bobStore = new InMemorySignalProtocolStore();
 | |
| 
 | |
|     await _initializeSessions(aliceStore, bobStore);
 | |
| 
 | |
|     const trustRoot = await libsignal.Curve.async.generateKeyPair();
 | |
|     const randomKeyPair = await libsignal.Curve.async.generateKeyPair();
 | |
|     const senderCertificate = await _createSenderCertificateFor(
 | |
|       trustRoot,
 | |
|       '+14151111111',
 | |
|       1,
 | |
|       randomKeyPair.pubKey,
 | |
|       31337
 | |
|     );
 | |
|     const aliceCipher = new SecretSessionCipher(aliceStore);
 | |
| 
 | |
|     const ciphertext = await aliceCipher.encrypt(
 | |
|       new libsignal.SignalProtocolAddress('+14152222222', 1),
 | |
|       senderCertificate,
 | |
|       bytesFromString('smert za smert')
 | |
|     );
 | |
| 
 | |
|     const bobCipher = new SecretSessionCipher(bobStore);
 | |
| 
 | |
|     try {
 | |
|       await bobCipher.decrypt(
 | |
|         createCertificateValidator(trustRoot.puKey),
 | |
|         ciphertext,
 | |
|         31335
 | |
|       );
 | |
|       throw new Error('It did not fail!');
 | |
|     } catch (error) {
 | |
|       assert.strictEqual(error.message, 'Invalid public key');
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   // private SenderCertificate _createCertificateFor(
 | |
|   //   ECKeyPair trustRoot
 | |
|   //   String sender
 | |
|   //   int deviceId
 | |
|   //   ECPublicKey identityKey
 | |
|   //   long expires
 | |
|   // )
 | |
|   async function _createSenderCertificateFor(
 | |
|     trustRoot,
 | |
|     sender,
 | |
|     deviceId,
 | |
|     identityKey,
 | |
|     expires
 | |
|   ) {
 | |
|     const serverKey = await libsignal.Curve.async.generateKeyPair();
 | |
| 
 | |
|     const serverCertificateCertificateProto = new textsecure.protobuf.ServerCertificate.Certificate();
 | |
|     serverCertificateCertificateProto.id = 1;
 | |
|     serverCertificateCertificateProto.key = serverKey.pubKey;
 | |
|     const serverCertificateCertificateBytes = serverCertificateCertificateProto
 | |
|       .encode()
 | |
|       .toArrayBuffer();
 | |
| 
 | |
|     const serverCertificateSignature = await libsignal.Curve.async.calculateSignature(
 | |
|       trustRoot.privKey,
 | |
|       serverCertificateCertificateBytes
 | |
|     );
 | |
| 
 | |
|     const serverCertificateProto = new textsecure.protobuf.ServerCertificate();
 | |
|     serverCertificateProto.certificate = serverCertificateCertificateBytes;
 | |
|     serverCertificateProto.signature = serverCertificateSignature;
 | |
|     const serverCertificate = _createServerCertificateFromBuffer(
 | |
|       serverCertificateProto.encode().toArrayBuffer()
 | |
|     );
 | |
| 
 | |
|     const senderCertificateCertificateProto = new textsecure.protobuf.SenderCertificate.Certificate();
 | |
|     senderCertificateCertificateProto.sender = sender;
 | |
|     senderCertificateCertificateProto.senderDevice = deviceId;
 | |
|     senderCertificateCertificateProto.identityKey = identityKey;
 | |
|     senderCertificateCertificateProto.expires = expires;
 | |
|     senderCertificateCertificateProto.signer = textsecure.protobuf.ServerCertificate.decode(
 | |
|       serverCertificate.serialized
 | |
|     );
 | |
|     const senderCertificateBytes = senderCertificateCertificateProto
 | |
|       .encode()
 | |
|       .toArrayBuffer();
 | |
| 
 | |
|     const senderCertificateSignature = await libsignal.Curve.async.calculateSignature(
 | |
|       serverKey.privKey,
 | |
|       senderCertificateBytes
 | |
|     );
 | |
| 
 | |
|     const senderCertificateProto = new textsecure.protobuf.SenderCertificate();
 | |
|     senderCertificateProto.certificate = senderCertificateBytes;
 | |
|     senderCertificateProto.signature = senderCertificateSignature;
 | |
|     return _createSenderCertificateFromBuffer(
 | |
|       senderCertificateProto.encode().toArrayBuffer()
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   // private void _initializeSessions(
 | |
|   //   SignalProtocolStore aliceStore, SignalProtocolStore bobStore)
 | |
|   async function _initializeSessions(aliceStore, bobStore) {
 | |
|     const aliceAddress = new libsignal.SignalProtocolAddress('+14152222222', 1);
 | |
|     await aliceStore.put(
 | |
|       'identityKey',
 | |
|       await libsignal.Curve.generateKeyPair()
 | |
|     );
 | |
|     await bobStore.put('identityKey', await libsignal.Curve.generateKeyPair());
 | |
| 
 | |
|     await aliceStore.put('registrationId', 57);
 | |
|     await bobStore.put('registrationId', 58);
 | |
| 
 | |
|     const bobPreKey = await libsignal.Curve.async.generateKeyPair();
 | |
|     const bobIdentityKey = await bobStore.getIdentityKeyPair();
 | |
|     const bobSignedPreKey = await libsignal.KeyHelper.generateSignedPreKey(
 | |
|       bobIdentityKey,
 | |
|       2
 | |
|     );
 | |
| 
 | |
|     const bobBundle = {
 | |
|       identityKey: bobIdentityKey.pubKey,
 | |
|       registrationId: 1,
 | |
|       preKey: {
 | |
|         keyId: 1,
 | |
|         publicKey: bobPreKey.pubKey,
 | |
|       },
 | |
|       signedPreKey: {
 | |
|         keyId: 2,
 | |
|         publicKey: bobSignedPreKey.keyPair.pubKey,
 | |
|         signature: bobSignedPreKey.signature,
 | |
|       },
 | |
|     };
 | |
|     const aliceSessionBuilder = new libsignal.SessionBuilder(
 | |
|       aliceStore,
 | |
|       aliceAddress
 | |
|     );
 | |
|     await aliceSessionBuilder.processPreKey(bobBundle);
 | |
| 
 | |
|     await bobStore.storeSignedPreKey(2, bobSignedPreKey.keyPair);
 | |
|     await bobStore.storePreKey(1, bobPreKey);
 | |
|   }
 | |
| });
 |