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.
		
		
		
		
		
			
		
			
				
	
	
		
			464 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			464 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
| import { fromBase64, fromHex } from 'bytebuffer';
 | |
| import chai, { expect } from 'chai';
 | |
| import chaiBytes from 'chai-bytes';
 | |
| import { to_hex } from 'libsodium-wrappers-sumo';
 | |
| 
 | |
| import { SogsBlinding } from '../../../../session/apis/open_group_api/sogsv3/sogsBlinding';
 | |
| import { concatUInt8Array, getSodiumRenderer } from '../../../../session/crypto';
 | |
| import { KeyPrefixType } from '../../../../session/types';
 | |
| import { StringUtils } from '../../../../session/utils';
 | |
| import { ByteKeyPair } from '../../../../session/utils/User';
 | |
| 
 | |
| chai.use(chaiBytes);
 | |
| 
 | |
| describe('OpenGroupAuthentication', () => {
 | |
|   const secondPartPrivKey = new Uint8Array([
 | |
|     186,
 | |
|     198,
 | |
|     231,
 | |
|     30,
 | |
|     253,
 | |
|     125,
 | |
|     250,
 | |
|     74,
 | |
|     131,
 | |
|     201,
 | |
|     142,
 | |
|     210,
 | |
|     79,
 | |
|     37,
 | |
|     74,
 | |
|     178,
 | |
|     194,
 | |
|     103,
 | |
|     249,
 | |
|     204,
 | |
|     219,
 | |
|     23,
 | |
|     42,
 | |
|     82,
 | |
|     128,
 | |
|     160,
 | |
|     68,
 | |
|     74,
 | |
|     210,
 | |
|     78,
 | |
|     137,
 | |
|     204,
 | |
|   ]);
 | |
|   const signingKeysA: ByteKeyPair = {
 | |
|     // 881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4
 | |
|     privKeyBytes: new Uint8Array([
 | |
|       192,
 | |
|       16,
 | |
|       216,
 | |
|       158,
 | |
|       204,
 | |
|       186,
 | |
|       245,
 | |
|       209,
 | |
|       198,
 | |
|       209,
 | |
|       157,
 | |
|       247,
 | |
|       102,
 | |
|       198,
 | |
|       238,
 | |
|       223,
 | |
|       150,
 | |
|       93,
 | |
|       74,
 | |
|       40,
 | |
|       165,
 | |
|       111,
 | |
|       135,
 | |
|       201,
 | |
|       252,
 | |
|       129,
 | |
|       158,
 | |
|       219,
 | |
|       89,
 | |
|       137,
 | |
|       109,
 | |
|       217,
 | |
|       ...secondPartPrivKey,
 | |
|     ]),
 | |
|     // 057aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d
 | |
|     pubKeyBytes: secondPartPrivKey,
 | |
|   };
 | |
| 
 | |
|   const signingKeysB: ByteKeyPair = {
 | |
|     privKeyBytes: new Uint8Array([
 | |
|       130,
 | |
|       56,
 | |
|       83,
 | |
|       227,
 | |
|       58,
 | |
|       149,
 | |
|       251,
 | |
|       148,
 | |
|       119,
 | |
|       85,
 | |
|       180,
 | |
|       81,
 | |
|       17,
 | |
|       190,
 | |
|       245,
 | |
|       33,
 | |
|       219,
 | |
|       6,
 | |
|       246,
 | |
|       238,
 | |
|       110,
 | |
|       61,
 | |
|       191,
 | |
|       133,
 | |
|       244,
 | |
|       223,
 | |
|       32,
 | |
|       32,
 | |
|       121,
 | |
|       172,
 | |
|       138,
 | |
|       198,
 | |
|       215,
 | |
|       25,
 | |
|       249,
 | |
|       139,
 | |
|       235,
 | |
|       31,
 | |
|       251,
 | |
|       12,
 | |
|       100,
 | |
|       87,
 | |
|       84,
 | |
|       131,
 | |
|       231,
 | |
|       45,
 | |
|       87,
 | |
|       251,
 | |
|       204,
 | |
|       133,
 | |
|       20,
 | |
|       3,
 | |
|       118,
 | |
|       71,
 | |
|       29,
 | |
|       47,
 | |
|       245,
 | |
|       62,
 | |
|       216,
 | |
|       163,
 | |
|       254,
 | |
|       248,
 | |
|       195,
 | |
|       109,
 | |
|     ]),
 | |
|     pubKeyBytes: new Uint8Array([
 | |
|       215,
 | |
|       25,
 | |
|       249,
 | |
|       139,
 | |
|       235,
 | |
|       31,
 | |
|       251,
 | |
|       12,
 | |
|       100,
 | |
|       87,
 | |
|       84,
 | |
|       131,
 | |
|       231,
 | |
|       45,
 | |
|       87,
 | |
|       251,
 | |
|       204,
 | |
|       133,
 | |
|       20,
 | |
|       3,
 | |
|       118,
 | |
|       71,
 | |
|       29,
 | |
|       47,
 | |
|       245,
 | |
|       62,
 | |
|       216,
 | |
|       163,
 | |
|       254,
 | |
|       248,
 | |
|       195,
 | |
|       109,
 | |
|     ]),
 | |
|   };
 | |
|   const serverPubKey = new Uint8Array(
 | |
|     fromHex('c3b3c6f32f0ab5a57f853cc4f30f5da7fda5624b0c77b3fb0829de562ada081d').toArrayBuffer()
 | |
|   );
 | |
| 
 | |
|   const ts = 1642472103;
 | |
|   const method = 'GET';
 | |
|   const path = '/room/the-best-room/messages/recent?limit=25';
 | |
| 
 | |
|   const nonce = new Uint8Array(fromBase64('CdB5nyKVmQGCw6s0Bvv8Ww==').toArrayBuffer());
 | |
| 
 | |
|   const body = 'hello 🎂';
 | |
| 
 | |
|   describe('HeaderCreation', () => {
 | |
|     describe('Blinded Headers', () => {
 | |
|       it('should produce correct X-SOGS-Nonce', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: true,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Nonce']).to.be.equal('CdB5nyKVmQGCw6s0Bvv8Ww==');
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Pubkey', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: true,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Pubkey']).to.be.equal(
 | |
|           '1598932d4bccbe595a8789d7eb1629cefc483a0eaddc7e20e8fe5c771efafd9af5'
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Timestamp', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: true,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Timestamp']).to.be.equal('1642472103');
 | |
|       });
 | |
|       it('should produce correct X-SOGS-Signature without body', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: true,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Signature']).to.be.equal(
 | |
|           'gYqpWZX6fnF4Gb2xQM3xaXs0WIYEI49+B8q4mUUEg8Rw0ObaHUWfoWjMHMArAtP9QlORfiydsKWz1o6zdPVeCQ=='
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Signature with body', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: true,
 | |
|           body,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Signature']).to.be.equal(
 | |
|           'hZCg5pEoy9t98umaY6fNarzcLP5UKUF8chz5mIjwwrRIQLy1kinRoYcNPdFOpJu8heA0val4viymXRTp1DGeBg=='
 | |
|         );
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('Unblinded Headers', () => {
 | |
|       it('should produce correct X-SOGS-Nonce', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: false,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Nonce']).to.be.equal('CdB5nyKVmQGCw6s0Bvv8Ww==');
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Pubkey', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: false,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Pubkey']).to.be.equal(
 | |
|           '00bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc'
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Timestamp', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: false,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Timestamp']).to.be.equal('1642472103');
 | |
|       });
 | |
|       it('should produce correct X-SOGS-Signature without body', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: false,
 | |
|           body: null,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Signature']).to.be.equal(
 | |
|           'xxLpXHbomAJMB9AtGMyqvBsXrdd2040y+Ol/IKzElWfKJa3EYZRv1GLO6CTLhrDFUwVQe8PPltyGs54Kd7O5Cg=='
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       it('should produce correct X-SOGS-Signature with body', async () => {
 | |
|         const headers = await SogsBlinding.getOpenGroupHeaders({
 | |
|           signingKeys: signingKeysA,
 | |
|           serverPK: serverPubKey,
 | |
|           nonce,
 | |
|           method,
 | |
|           path,
 | |
|           timestamp: ts,
 | |
|           blinded: false,
 | |
|           body,
 | |
|         });
 | |
|         expect(headers['X-SOGS-Signature']).to.be.equal(
 | |
|           'uumZNee7NUb0lVufegjzgjjnj4pe3kAe6OYw7iVTJrcxxxdIxUCgt5/xliBWqPlgY6ReUZRAuptNa4nprv7nCA=='
 | |
|         );
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('Blinded Message Encryption', () => {
 | |
|     it('Should encrypt blinded message correctly', async () => {
 | |
|       const dataUint = new Uint8Array(StringUtils.encode(body, 'utf8'));
 | |
|       const data = await SogsBlinding.encryptBlindedMessage({
 | |
|         rawData: dataUint,
 | |
|         senderSigningKey: signingKeysA,
 | |
|         serverPubKey,
 | |
|         recipientSigningKey: signingKeysB,
 | |
|       });
 | |
|       if (data) {
 | |
|         const decrypted = await decryptBlindedMessage(
 | |
|           data,
 | |
|           signingKeysA,
 | |
|           signingKeysB,
 | |
|           serverPubKey
 | |
|         );
 | |
|         expect(decrypted?.messageText).to.be.equal(body);
 | |
|         expect(decrypted?.senderED25519PubKey).to.be.equal(to_hex(signingKeysA.pubKeyBytes));
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * This function is actually just used for testing and is useless IRL.
 | |
|  * We should probably move it somewhere else.
 | |
|  *
 | |
|  * The function you are looking for is `decryptWithSessionBlindingProtocol`
 | |
|  * @param data The data to be decrypted from the sender
 | |
|  * @param aSignKeyBytes the sender's keypair bytes
 | |
|  * @param bSignKeyBytes the receivers keypair bytes
 | |
|  * @param serverPubKey the server the message is sent to
 | |
|  */
 | |
| const decryptBlindedMessage = async (
 | |
|   data: Uint8Array,
 | |
|   aSignKeyBytes: ByteKeyPair,
 | |
|   bSignKeyBytes: ByteKeyPair,
 | |
|   serverPubKey: Uint8Array
 | |
| ): Promise<| {
 | |
|     messageText: string;
 | |
|     senderED25519PubKey: string;
 | |
|     senderSessionId: string;
 | |
|   }
 | |
| | undefined> => {
 | |
|   const sodium = await getSodiumRenderer();
 | |
| 
 | |
|   const aBlindingValues = SogsBlinding.getBlindingValues(serverPubKey, aSignKeyBytes, sodium);
 | |
|   const bBlindingValues = SogsBlinding.getBlindingValues(serverPubKey, bSignKeyBytes, sodium);
 | |
|   const { publicKey: kA } = aBlindingValues;
 | |
|   const { a: b, publicKey: kB } = bBlindingValues;
 | |
| 
 | |
|   const k = sodium.crypto_core_ed25519_scalar_reduce(sodium.crypto_generichash(64, serverPubKey));
 | |
| 
 | |
|   const decryptKey = sodium.crypto_generichash(
 | |
|     32,
 | |
|     concatUInt8Array(sodium.crypto_scalarmult_ed25519_noclamp(b, kA), kA, kB)
 | |
|   );
 | |
| 
 | |
|   const version = data[0];
 | |
|   if (version !== 0) {
 | |
|     window?.log?.error(
 | |
|       'decryptBlindedMessage - Dropping message due to unsupported encryption version'
 | |
|     );
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   const nonceLength = sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
 | |
|   const ciphertextIncoming = data.slice(1, data.length - nonceLength);
 | |
|   const nonceIncoming = data.slice(data.length - nonceLength);
 | |
| 
 | |
|   const plaintextIncoming = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
 | |
|     null,
 | |
|     ciphertextIncoming,
 | |
|     null,
 | |
|     nonceIncoming,
 | |
|     decryptKey
 | |
|   );
 | |
| 
 | |
|   if (plaintextIncoming.length <= 32) {
 | |
|     // throw Error;
 | |
|     window?.log?.error('decryptBlindedMessage: plaintext insufficient length');
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   const msg = plaintextIncoming.slice(0, plaintextIncoming.length - 32);
 | |
|   const senderEdpk = plaintextIncoming.slice(plaintextIncoming.length - 32);
 | |
| 
 | |
|   if (to_hex(kA) !== to_hex(sodium.crypto_scalarmult_ed25519_noclamp(k, senderEdpk))) {
 | |
|     throw Error;
 | |
|   }
 | |
| 
 | |
|   const messageText = StringUtils.decode(msg, 'utf8');
 | |
| 
 | |
|   const senderSessionId = `${KeyPrefixType.standard}${to_hex(
 | |
|     sodium.crypto_sign_ed25519_pk_to_curve25519(senderEdpk)
 | |
|   )}`;
 | |
|   const senderED25519PubKey = to_hex(senderEdpk);
 | |
| 
 | |
|   return {
 | |
|     messageText,
 | |
|     senderED25519PubKey,
 | |
|     senderSessionId,
 | |
|   };
 | |
| };
 |