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);
 | 
						|
  }
 | 
						|
});
 |