import 'dart:convert'; import 'dart:typed_data'; import 'package:crypton/crypton.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:marianum_mobile/push/push_decryptor.dart'; import 'package:pointycastle/export.dart' as pc; /// Encrypts [plain] with [publicKey] using OAEP (SHA-1), mirroring Nextcloud's /// default padding. String _encryptOaep(RSAPublicKey publicKey, String plain) { final cipher = pc.OAEPEncoding(pc.RSAEngine()) ..init( true, pc.PublicKeyParameter(publicKey.asPointyCastle), ); final out = cipher.process(Uint8List.fromList(utf8.encode(plain))); return base64.encode(out); } /// Signs the encrypted bytes with the server private key (SHA512withRSA). String _sign(RSAPrivateKey serverPrivate, String subjectBase64) { final signature = serverPrivate.createSHA512Signature( Uint8List.fromList(base64.decode(subjectBase64)), ); return base64.encode(signature); } void main() { final device = RSAKeypair.fromRandom(); final server = RSAKeypair.fromRandom(); const subjectJson = '{"app":"spreed","subject":"Max: Hallo","type":"chat","id":"abc123","nid":42}'; group('PushDecryptor', () { test('decrypts an OAEP-encrypted subject', () { final encrypted = _encryptOaep(device.publicKey, subjectJson); final decryptor = PushDecryptor( devicePrivateKey: device.privateKey, serverPublicKey: server.publicKey, ); expect( decryptor.verify(encrypted, _sign(server.privateKey, encrypted)), isTrue, ); final subject = decryptor.decrypt(encrypted); expect(subject, isNotNull); expect(subject!.app, 'spreed'); expect(subject.isTalk, isTrue); expect(subject.id, 'abc123'); expect(subject.nid, 42); }); test('decrypts a PKCS1-encrypted subject (fallback)', () { // crypton's encrypt uses PKCS#1 v1.5. final encrypted = device.publicKey.encrypt(subjectJson); final subject = PushDecryptor( devicePrivateKey: device.privateKey, ).decrypt(encrypted); expect(subject, isNotNull); expect(subject!.subject, 'Max: Hallo'); }); test('rejects a signature made with the wrong key', () { final encrypted = _encryptOaep(device.publicKey, subjectJson); final wrong = RSAKeypair.fromRandom(); final decryptor = PushDecryptor( devicePrivateKey: device.privateKey, serverPublicKey: server.publicKey, ); expect( decryptor.verify(encrypted, _sign(wrong.privateKey, encrypted)), isFalse, ); }); test('parses delete-multiple subjects', () { const deleteJson = '{"delete-multiple":true,"nids":[1,2,3]}'; final encrypted = _encryptOaep(device.publicKey, deleteJson); final subject = PushDecryptor( devicePrivateKey: device.privateKey, ).decrypt(encrypted); expect(subject, isNotNull); expect(subject!.deleteMultiple, isTrue); expect(subject.isAnyDelete, isTrue); expect(subject.nids, [1, 2, 3]); }); }); }