Skip to content

Commit

Permalink
feat(notifications_push_repository): Verify push notification signature
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <[email protected]>
  • Loading branch information
provokateurin committed Nov 17, 2024
1 parent 64bf100 commit 181c2f9
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';

import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
Expand All @@ -19,16 +21,28 @@ abstract class PushNotification implements Built<PushNotification, PushNotificat
factory PushNotification.fromEncrypted(
Map<String, dynamic> json,
String accountID,
RSAPrivateKey privateKey,
RSAPrivateKey devicePrivateKey,
RSAPublicKey userPublicKey,
) {
final subject = notifications.DecryptedSubject.fromEncrypted(privateKey, json['subject'] as String);
final subject = json['subject'] as String;
final signature = json['signature'] as String;

final valid = userPublicKey.verifySHA512Signature(
base64.decode(subject),
base64.decode(signature),
);
if (!valid) {
throw Exception('Failed to verify push notification signature!');
}

final decryptedSubject = notifications.DecryptedSubject.fromEncrypted(devicePrivateKey, subject);

return PushNotification(
(b) => b
..accountID = accountID
..priority = json['priority'] as String
..type = json['type'] as String
..subject.replace(subject),
..subject.replace(decryptedSubject),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,20 @@ Future<BuiltList<PushNotification>> parseEncryptedPushNotifications(
Uint8List notifications,
String accountID,
) async {
final privateKey = await getDevicePrivateKey(storage);
final subscriptions = await storage.readSubscriptions();

final subscription = subscriptions[accountID];
if (subscription == null) {
throw Exception('Subscription for account $accountID not found.');
}

final pushDevice = subscription.pushDevice;
if (pushDevice == null) {
throw Exception('Push device for account $accountID not found.');
}

final userPublicKey = RSAPublicKey.fromPEM(pushDevice.publicKey);
final devicePrivateKey = await getDevicePrivateKey(storage);

final builder = ListBuilder<PushNotification>();

Expand All @@ -37,7 +50,8 @@ Future<BuiltList<PushNotification>> parseEncryptedPushNotifications(
PushNotification.fromEncrypted(
data,
accountID,
privateKey,
devicePrivateKey,
userPublicKey,
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ PushNotification {
);
});

test('fromEncrypted', () {
const privateKeyPEM = '''
group('fromEncrypted', () {
const devicePrivateKeyPEM = '''
-----BEGIN RSA PRIVATE KEY-----
MIICHwIBADANBgkqhkiG9w0BAQEFAASCAgkwggIFAgEAAm4BELTz808T8iAkvBkg
tnWs4a1aNcCFAAX54ePLK40YAL/tQjUGoIe0+zO7yzMT0bydk6BFOdyrIP2iwALN
Expand All @@ -140,21 +140,65 @@ VQI3BH+FwRTRntc3caGF4qVixb+Wu6OLwHg77MjdvKEo8KqTiQjxgAjmUkXPaS8N
4FkEfiY9QA36EQI3AKxizo9goAHnTmY1OVi+4GLp0HroWP64RjW8R/cUemggMqEa
UJYvEQEss8/UoYhOACOm5PEqNg==
-----END RSA PRIVATE KEY-----''';
final privateKey = RSAPrivateKey.fromPEM(privateKeyPEM);
final devicePrivateKey = RSAPrivateKey.fromPEM(devicePrivateKeyPEM);

const userPrivateKeyPEM = '''
-----BEGIN RSA PRIVATE KEY-----
MIIB1wIBADANBgkqhkiG9w0BAQEFAASCAcEwggG9AgEAAl4BimSPD4wH/LwlJk3H
dj6FCPqwZDBgLQQGVZsC6iZuCRMOH9paXuCOSBMw6l9IDrHy23jEasfu6tOnA/vy
QP01oPa0Kkp7qUFdN/eVHOdfBp0KKEPhr6bGGr1Lh+BJAgMBAAECXgFFGmWPVEgV
PuaEr6LXRuwVHckfnXz6PpIWKQR7DZiw5ENFYJIUGZlPsnolCMv2JzvKc66MYlnK
G+I+Lpm9mc2bPbj3aq8vh25mjiyn2mgB9AdlGoDNcW4QN8pisQ0CLxnR3uq23oPB
1hpR/eU+vU5iQ1/ZwKZUf24CihDFGpS9je1X43TYCRDMH3sMFttXAi8PRlpU/AdO
xpqZYcoilhhu/numcU1qEBgiDiTuTHizVGw/OwmDeq3SHjJLhMV9XwIvAVwonbxc
JByFpoVDFlwjpIlQezABEcHJpIXFt/Rp3gPOAf5rILBwac4WqmiMm6kCLwSYBgbV
HWWFuW0zydUJCyQmiQ2PudaSLI/hbR32Bb75PuztVnkiZjBxQHMR5UtFAi8TXKJV
4gkQHdyTMlUgtwTItoS5AWmZU5FUJbDva9S3JerKrTkbeslJiHEhSYrbZw==
-----END RSA PRIVATE KEY-----
''';
final userPrivateKey = RSAPrivateKey.fromPEM(userPrivateKeyPEM);

const subject =
'AOXrekPv+79XU82vEXx5WiA9WREus8uYYkfijtKdl4ggWRvvykaY5hQP7OT5P7iKSCzjmO7yNQTuXDJXYtWo/1Pq0AYSVrA3y37pNYr8d/WZklfvQtxIB6o/HTG6pUd1kER7QxVkP7RSHvw/9PU=';

expect(
PushNotification.fromEncrypted(
json.decode(
'{"priority":"priority","type":"type","subject":"$subject"}',
) as Map<String, dynamic>,
'accountID',
privateKey,
),
equalsBuilt(createPushNotification()),
);
test('Valid signature', () {
const signature =
'AFhc8Bp2PFwbrsqr9Ygk9T4JRwaqnsojvJ0MnkMIKpX8TYe0/SWj1bVQhWamMQ1uQ3xeFIOFHP3AkoqJ+id7f9CpqETOSqTrUHDFDedCbb8Lwoa95q4lnchDvI6Hbw==';

expect(
PushNotification.fromEncrypted(
json.decode(
'{"priority":"priority","type":"type","subject":"$subject","signature":"$signature"}',
) as Map<String, dynamic>,
'accountID',
devicePrivateKey,
userPrivateKey.publicKey,
),
equalsBuilt(createPushNotification()),
);
});

test('Invalid signature', () {
const signature = 'abcd';

expect(
() => PushNotification.fromEncrypted(
json.decode(
'{"priority":"priority","type":"type","subject":"$subject","signature":"$signature"}',
) as Map<String, dynamic>,
'accountID',
devicePrivateKey,
userPrivateKey.publicKey,
),
throwsA(
isA<Exception>().having(
(e) => e.toString(),
'toString',
'Exception: Failed to verify push notification signature!',
),
),
);
});
});
});
}

0 comments on commit 181c2f9

Please sign in to comment.