Merge branch 'base64-unpadded' into 'main'
fix: Allow unpadded base64 decoding Closes #261 See merge request famedly/company/frontend/famedlysdk!933
This commit is contained in:
commit
a50e3cfa2e
|
|
@ -16,9 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
|
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
|
|
@ -33,7 +33,7 @@ class CrossSigning {
|
||||||
(String secret) async {
|
(String secret) async {
|
||||||
final keyObj = olm.PkSigning();
|
final keyObj = olm.PkSigning();
|
||||||
try {
|
try {
|
||||||
return keyObj.init_with_seed(base64.decode(secret)) ==
|
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
|
||||||
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
|
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -45,7 +45,7 @@ class CrossSigning {
|
||||||
(String secret) async {
|
(String secret) async {
|
||||||
final keyObj = olm.PkSigning();
|
final keyObj = olm.PkSigning();
|
||||||
try {
|
try {
|
||||||
return keyObj.init_with_seed(base64.decode(secret)) ==
|
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
|
||||||
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
|
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -87,8 +87,8 @@ class CrossSigning {
|
||||||
);
|
);
|
||||||
await handle.maybeCacheAll();
|
await handle.maybeCacheAll();
|
||||||
}
|
}
|
||||||
final masterPrivateKey =
|
final masterPrivateKey = base64decodeUnpadded(
|
||||||
base64.decode(await handle.getStored(EventTypes.CrossSigningMasterKey));
|
await handle.getStored(EventTypes.CrossSigningMasterKey));
|
||||||
final keyObj = olm.PkSigning();
|
final keyObj = olm.PkSigning();
|
||||||
String? masterPubkey;
|
String? masterPubkey;
|
||||||
try {
|
try {
|
||||||
|
|
@ -153,7 +153,7 @@ class CrossSigning {
|
||||||
// we don't care about signing other cross-signing keys
|
// we don't care about signing other cross-signing keys
|
||||||
} else {
|
} else {
|
||||||
// okay, we'll sign a device key with our self signing key
|
// 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) ??
|
.getCached(EventTypes.CrossSigningSelfSigning) ??
|
||||||
'');
|
'');
|
||||||
if (selfSigningKey.isNotEmpty) {
|
if (selfSigningKey.isNotEmpty) {
|
||||||
|
|
@ -163,7 +163,7 @@ class CrossSigning {
|
||||||
}
|
}
|
||||||
} else if (key is CrossSigningKey && key.usage.contains('master')) {
|
} else if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||||
// we are signing someone elses master key
|
// we are signing someone elses master key
|
||||||
userSigningKey ??= base64.decode(await encryption.ssss
|
userSigningKey ??= base64decodeUnpadded(await encryption.ssss
|
||||||
.getCached(EventTypes.CrossSigningUserSigning) ??
|
.getCached(EventTypes.CrossSigningUserSigning) ??
|
||||||
'');
|
'');
|
||||||
if (userSigningKey.isNotEmpty) {
|
if (userSigningKey.isNotEmpty) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
@ -49,7 +50,7 @@ class KeyManager {
|
||||||
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
|
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return keyObj.init_with_private_key(base64.decode(secret)) ==
|
return keyObj.init_with_private_key(base64decodeUnpadded(secret)) ==
|
||||||
info.authData['public_key'];
|
info.authData['public_key'];
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -579,7 +580,7 @@ class KeyManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final privateKey =
|
final privateKey =
|
||||||
base64.decode((await encryption.ssss.getCached(megolmKey))!);
|
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
|
||||||
final decryption = olm.PkDecryption();
|
final decryption = olm.PkDecryption();
|
||||||
final info = await getRoomKeysBackupInfo();
|
final info = await getRoomKeysBackupInfo();
|
||||||
String backupPubKey;
|
String backupPubKey;
|
||||||
|
|
@ -723,7 +724,7 @@ class KeyManager {
|
||||||
return; // nothing to do
|
return; // nothing to do
|
||||||
}
|
}
|
||||||
final privateKey =
|
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
|
// decryption is needed to calculate the public key and thus see if the claimed information is in fact valid
|
||||||
final decryption = olm.PkDecryption();
|
final decryption = olm.PkDecryption();
|
||||||
final info = await getRoomKeysBackupInfo(false);
|
final info = await getRoomKeysBackupInfo(false);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import 'dart:typed_data';
|
||||||
import 'package:base58check/base58.dart';
|
import 'package:base58check/base58.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
|
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
import '../src/utils/crypto/crypto.dart' as uc;
|
import '../src/utils/crypto/crypto.dart' as uc;
|
||||||
|
|
@ -85,7 +86,7 @@ class SSSS {
|
||||||
[String? ivStr]) async {
|
[String? ivStr]) async {
|
||||||
Uint8List iv;
|
Uint8List iv;
|
||||||
if (ivStr != null) {
|
if (ivStr != null) {
|
||||||
iv = base64.decode(ivStr);
|
iv = base64decodeUnpadded(ivStr);
|
||||||
} else {
|
} else {
|
||||||
iv = Uint8List.fromList(uc.secureRandomBytes(16));
|
iv = Uint8List.fromList(uc.secureRandomBytes(16));
|
||||||
}
|
}
|
||||||
|
|
@ -108,15 +109,15 @@ class SSSS {
|
||||||
static Future<String> decryptAes(
|
static Future<String> decryptAes(
|
||||||
_Encrypted data, Uint8List key, String name) async {
|
_Encrypted data, Uint8List key, String name) async {
|
||||||
final keys = deriveKeys(key, name);
|
final keys = deriveKeys(key, name);
|
||||||
final cipher = base64.decode(data.ciphertext);
|
final cipher = base64decodeUnpadded(data.ciphertext);
|
||||||
final hmac = base64
|
final hmac = base64
|
||||||
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
|
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
|
||||||
.replaceAll(RegExp(r'=+$'), '');
|
.replaceAll(RegExp(r'=+$'), '');
|
||||||
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
|
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
|
||||||
throw Exception('Bad MAC');
|
throw Exception('Bad MAC');
|
||||||
}
|
}
|
||||||
final decipher =
|
final decipher = await uc.aesCtr
|
||||||
await uc.aesCtr.encrypt(cipher, keys.aesKey, base64.decode(data.iv));
|
.encrypt(cipher, keys.aesKey, base64decodeUnpadded(data.iv));
|
||||||
return String.fromCharCodes(decipher);
|
return String.fromCharCodes(decipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import '../encryption.dart';
|
||||||
import '../ssss.dart';
|
import '../ssss.dart';
|
||||||
import '../key_manager.dart';
|
import '../key_manager.dart';
|
||||||
import '../../matrix.dart';
|
import '../../matrix.dart';
|
||||||
|
import 'base64_unpadded.dart';
|
||||||
|
|
||||||
enum BootstrapState {
|
enum BootstrapState {
|
||||||
/// Is loading.
|
/// Is loading.
|
||||||
|
|
@ -385,7 +386,7 @@ class Bootstrap {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logs().v('Get stored key...');
|
Logs().v('Get stored key...');
|
||||||
masterSigningKey = base64.decode(
|
masterSigningKey = base64decodeUnpadded(
|
||||||
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ??
|
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ??
|
||||||
'');
|
'');
|
||||||
if (masterSigningKey.isEmpty) {
|
if (masterSigningKey.isEmpty) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
|
|
||||||
import 'crypto.dart';
|
import 'crypto.dart';
|
||||||
|
|
||||||
class EncryptedFile {
|
class EncryptedFile {
|
||||||
|
|
@ -52,7 +54,7 @@ Future<Uint8List?> decryptFile(EncryptedFile input) async {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final key = base64.decode(base64.normalize(input.k));
|
final key = base64decodeUnpadded(base64.normalize(input.k));
|
||||||
final iv = base64.decode(base64.normalize(input.iv));
|
final iv = base64decodeUnpadded(base64.normalize(input.iv));
|
||||||
return await aesCtr.encrypt(input.data, key, iv);
|
return await aesCtr.encrypt(input.data, key, iv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue