diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart
index 6d243f21..60d9a64c 100644
--- a/lib/encryption/cross_signing.dart
+++ b/lib/encryption/cross_signing.dart
@@ -16,9 +16,9 @@
* along with this program. If not, see .
*/
-import 'dart:convert';
import 'dart:typed_data';
+import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:olm/olm.dart' as olm;
import '../matrix.dart';
@@ -33,7 +33,7 @@ class CrossSigning {
(String secret) async {
final keyObj = olm.PkSigning();
try {
- return keyObj.init_with_seed(base64.decode(secret)) ==
+ return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
} catch (_) {
return false;
@@ -45,7 +45,7 @@ class CrossSigning {
(String secret) async {
final keyObj = olm.PkSigning();
try {
- return keyObj.init_with_seed(base64.decode(secret)) ==
+ return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
} catch (_) {
return false;
@@ -87,8 +87,8 @@ class CrossSigning {
);
await handle.maybeCacheAll();
}
- final masterPrivateKey =
- base64.decode(await handle.getStored(EventTypes.CrossSigningMasterKey));
+ final masterPrivateKey = base64decodeUnpadded(
+ await handle.getStored(EventTypes.CrossSigningMasterKey));
final keyObj = olm.PkSigning();
String? masterPubkey;
try {
@@ -153,7 +153,7 @@ class CrossSigning {
// we don't care about signing other cross-signing keys
} else {
// okay, we'll sign a device key with our self signing key
- selfSigningKey ??= base64.decode(await encryption.ssss
+ selfSigningKey ??= base64decodeUnpadded(await encryption.ssss
.getCached(EventTypes.CrossSigningSelfSigning) ??
'');
if (selfSigningKey.isNotEmpty) {
@@ -163,7 +163,7 @@ class CrossSigning {
}
} else if (key is CrossSigningKey && key.usage.contains('master')) {
// we are signing someone elses master key
- userSigningKey ??= base64.decode(await encryption.ssss
+ userSigningKey ??= base64decodeUnpadded(await encryption.ssss
.getCached(EventTypes.CrossSigningUserSigning) ??
'');
if (userSigningKey.isNotEmpty) {
diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart
index 15977bb9..5f9f6547 100644
--- a/lib/encryption/key_manager.dart
+++ b/lib/encryption/key_manager.dart
@@ -18,6 +18,7 @@
import 'dart:convert';
+import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:olm/olm.dart' as olm;
import 'package:collection/collection.dart';
@@ -49,7 +50,7 @@ class KeyManager {
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
return false;
}
- return keyObj.init_with_private_key(base64.decode(secret)) ==
+ return keyObj.init_with_private_key(base64decodeUnpadded(secret)) ==
info.authData['public_key'];
} catch (_) {
return false;
@@ -579,7 +580,7 @@ class KeyManager {
return;
}
final privateKey =
- base64.decode((await encryption.ssss.getCached(megolmKey))!);
+ base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo();
String backupPubKey;
@@ -723,7 +724,7 @@ class KeyManager {
return; // nothing to do
}
final privateKey =
- base64.decode((await encryption.ssss.getCached(megolmKey))!);
+ base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
// decryption is needed to calculate the public key and thus see if the claimed information is in fact valid
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo(false);
diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart
index fc591522..60e4dabf 100644
--- a/lib/encryption/ssss.dart
+++ b/lib/encryption/ssss.dart
@@ -24,6 +24,7 @@ import 'dart:typed_data';
import 'package:base58check/base58.dart';
import 'package:crypto/crypto.dart';
import 'package:collection/collection.dart';
+import 'package:matrix/encryption/utils/base64_unpadded.dart';
import '../matrix.dart';
import '../src/utils/crypto/crypto.dart' as uc;
@@ -85,7 +86,7 @@ class SSSS {
[String? ivStr]) async {
Uint8List iv;
if (ivStr != null) {
- iv = base64.decode(ivStr);
+ iv = base64decodeUnpadded(ivStr);
} else {
iv = Uint8List.fromList(uc.secureRandomBytes(16));
}
@@ -108,15 +109,15 @@ class SSSS {
static Future decryptAes(
_Encrypted data, Uint8List key, String name) async {
final keys = deriveKeys(key, name);
- final cipher = base64.decode(data.ciphertext);
+ final cipher = base64decodeUnpadded(data.ciphertext);
final hmac = base64
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
.replaceAll(RegExp(r'=+$'), '');
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
throw Exception('Bad MAC');
}
- final decipher =
- await uc.aesCtr.encrypt(cipher, keys.aesKey, base64.decode(data.iv));
+ final decipher = await uc.aesCtr
+ .encrypt(cipher, keys.aesKey, base64decodeUnpadded(data.iv));
return String.fromCharCodes(decipher);
}
diff --git a/lib/encryption/utils/base64_unpadded.dart b/lib/encryption/utils/base64_unpadded.dart
new file mode 100644
index 00000000..c66b64e8
--- /dev/null
+++ b/lib/encryption/utils/base64_unpadded.dart
@@ -0,0 +1,13 @@
+import 'dart:convert';
+import 'dart:typed_data';
+
+/// decodes base64
+///
+/// Dart's native [base64.decode] requires a padded base64 input String.
+/// This function allows unpadded base64 too.
+///
+/// See: https://github.com/dart-lang/sdk/issues/39510
+Uint8List base64decodeUnpadded(String s) {
+ final needEquals = (4 - (s.length % 4)) % 4;
+ return base64.decode(s + ('=' * needEquals));
+}
diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart
index aef6518c..515d0744 100644
--- a/lib/encryption/utils/bootstrap.dart
+++ b/lib/encryption/utils/bootstrap.dart
@@ -26,6 +26,7 @@ import '../encryption.dart';
import '../ssss.dart';
import '../key_manager.dart';
import '../../matrix.dart';
+import 'base64_unpadded.dart';
enum BootstrapState {
/// Is loading.
@@ -385,7 +386,7 @@ class Bootstrap {
}
} else {
Logs().v('Get stored key...');
- masterSigningKey = base64.decode(
+ masterSigningKey = base64decodeUnpadded(
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ??
'');
if (masterSigningKey.isEmpty) {
diff --git a/lib/src/utils/crypto/encrypted_file.dart b/lib/src/utils/crypto/encrypted_file.dart
index 754e5ced..d3dd874b 100644
--- a/lib/src/utils/crypto/encrypted_file.dart
+++ b/lib/src/utils/crypto/encrypted_file.dart
@@ -18,6 +18,8 @@
import 'dart:typed_data';
import 'dart:convert';
+import 'package:matrix/encryption/utils/base64_unpadded.dart';
+
import 'crypto.dart';
class EncryptedFile {
@@ -52,7 +54,7 @@ Future decryptFile(EncryptedFile input) async {
return null;
}
- final key = base64.decode(base64.normalize(input.k));
- final iv = base64.decode(base64.normalize(input.iv));
+ final key = base64decodeUnpadded(base64.normalize(input.k));
+ final iv = base64decodeUnpadded(base64.normalize(input.iv));
return await aesCtr.encrypt(input.data, key, iv);
}
diff --git a/test/encryption/utils_test.dart b/test/encryption/utils_test.dart
new file mode 100644
index 00000000..242703ac
--- /dev/null
+++ b/test/encryption/utils_test.dart
@@ -0,0 +1,43 @@
+/*
+ * Famedly Matrix SDK
+ * Copyright (C) 2022 Famedly GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import 'dart:convert';
+
+import 'package:matrix/encryption/utils/base64_unpadded.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Utils', () {
+ const base64input = 'foobar';
+ final utf8codec = Utf8Codec();
+ test('base64 padded', () {
+ final paddedBase64 = base64.encode(base64input.codeUnits);
+
+ final decodedPadded =
+ utf8codec.decode(base64decodeUnpadded(paddedBase64));
+ expect(decodedPadded, base64input, reason: 'Padded base64 decode');
+ });
+
+ test('base64 unpadded', () {
+ const unpaddedBase64 = 'Zm9vYmFy';
+ final decodedUnpadded =
+ utf8codec.decode(base64decodeUnpadded(unpaddedBase64));
+ expect(decodedUnpadded, base64input, reason: 'Unpadded base64 decode');
+ });
+ });
+}