Compare commits

...

2 Commits

6 changed files with 97 additions and 76 deletions

View File

@ -1,6 +1,7 @@
## 0.3.2 ## 0.3.2
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue. - **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
- **BREAKING**: Changed order of parameters of `CryptographicHashManager.hashFromData`
## 0.3.1 ## 0.3.1

View File

@ -74,12 +74,6 @@ const forwardedXmlns = 'urn:xmpp:forward:0';
// XEP-0300 // XEP-0300
const hashXmlns = 'urn:xmpp:hashes:2'; const hashXmlns = 'urn:xmpp:hashes:2';
const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names'; const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names';
const hashSha256 = 'sha-256';
const hashSha512 = 'sha-512';
const hashSha3256 = 'sha3-256';
const hashSha3512 = 'sha3-512';
const hashBlake2b256 = 'blake2b-256';
const hashBlake2b512 = 'blake2b-512';
// XEP-0308 // XEP-0308
const lmcXmlns = 'urn:xmpp:message-correct:0'; const lmcXmlns = 'urn:xmpp:message-correct:0';

View File

@ -4,62 +4,84 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
XMLNode constructHashElement(String algo, String base64Hash) { /// Hash names
const _hashSha256 = 'sha-256';
const _hashSha512 = 'sha-512';
const _hashSha3256 = 'sha3-256';
const _hashSha3512 = 'sha3-512';
const _hashBlake2b256 = 'blake2b-256';
const _hashBlake2b512 = 'blake2b-512';
/// Helper method for building a <hash /> element according to XEP-0300.
XMLNode constructHashElement(HashFunction hash, String value) {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'hash', tag: 'hash',
xmlns: hashXmlns, xmlns: hashXmlns,
attributes: {'algo': algo}, attributes: {'algo': hash.toName()},
text: base64Hash, text: value,
); );
} }
enum HashFunction { enum HashFunction {
/// SHA-256
sha256, sha256,
/// SHA-256
sha512, sha512,
/// SHA3-256
sha3_256, sha3_256,
/// SHA3-512
sha3_512, sha3_512,
/// BLAKE2b-256
blake2b256, blake2b256,
blake2b512,
}
extension HashNameToEnumExtension on HashFunction { /// BLAKE2b-512
String toName() { blake2b512;
switch (this) {
case HashFunction.sha256:
return hashSha256;
case HashFunction.sha512:
return hashSha512;
case HashFunction.sha3_256:
return hashSha3512;
case HashFunction.sha3_512:
return hashSha3512;
case HashFunction.blake2b256:
return hashBlake2b256;
case HashFunction.blake2b512:
return hashBlake2b512;
}
}
}
HashFunction hashFunctionFromName(String name) { /// Get a HashFunction from its name [name] according to either
/// - IANA's hash name register (http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml)
/// - XEP-0300
factory HashFunction.fromName(String name) {
switch (name) { switch (name) {
case hashSha256: case _hashSha256:
return HashFunction.sha256; return HashFunction.sha256;
case hashSha512: case _hashSha512:
return HashFunction.sha512; return HashFunction.sha512;
case hashSha3256: case _hashSha3256:
return HashFunction.sha3_256; return HashFunction.sha3_256;
case hashSha3512: case _hashSha3512:
return HashFunction.sha3_512; return HashFunction.sha3_512;
case hashBlake2b256: case _hashBlake2b256:
return HashFunction.blake2b256; return HashFunction.blake2b256;
case hashBlake2b512: case _hashBlake2b512:
return HashFunction.blake2b512; return HashFunction.blake2b512;
} }
throw Exception(); throw Exception();
} }
/// Return the hash function's name according to IANA's hash name register or XEP-0300.
String toName() {
switch (this) {
case HashFunction.sha256:
return _hashSha256;
case HashFunction.sha512:
return _hashSha512;
case HashFunction.sha3_256:
return _hashSha3512;
case HashFunction.sha3_512:
return _hashSha3512;
case HashFunction.blake2b256:
return _hashBlake2b256;
case HashFunction.blake2b512:
return _hashBlake2b512;
}
}
}
class CryptographicHashManager extends XmppManagerBase { class CryptographicHashManager extends XmppManagerBase {
CryptographicHashManager() : super(cryptographicHashManager); CryptographicHashManager() : super(cryptographicHashManager);
@ -68,17 +90,19 @@ class CryptographicHashManager extends XmppManagerBase {
@override @override
List<String> getDiscoFeatures() => [ List<String> getDiscoFeatures() => [
'$hashFunctionNameBaseXmlns:$hashSha256', '$hashFunctionNameBaseXmlns:$_hashSha256',
'$hashFunctionNameBaseXmlns:$hashSha512', '$hashFunctionNameBaseXmlns:$_hashSha512',
//'$hashFunctionNameBaseXmlns:$hashSha3256', //'$hashFunctionNameBaseXmlns:$_hashSha3256',
//'$hashFunctionNameBaseXmlns:$hashSha3512', //'$hashFunctionNameBaseXmlns:$_hashSha3512',
//'$hashFunctionNameBaseXmlns:$hashBlake2b256', //'$hashFunctionNameBaseXmlns:$_hashBlake2b256',
'$hashFunctionNameBaseXmlns:$hashBlake2b512', '$hashFunctionNameBaseXmlns:$_hashBlake2b512',
]; ];
/// Compute the raw hash value of [data] using the algorithm specified by [function].
/// If the function is not supported, an exception will be thrown.
static Future<List<int>> hashFromData( static Future<List<int>> hashFromData(
List<int> data,
HashFunction function, HashFunction function,
List<int> data,
) async { ) async {
// TODO(PapaTutuWawa): Implement the others as well // TODO(PapaTutuWawa): Implement the others as well
HashAlgorithm algo; HashAlgorithm algo;

View File

@ -13,7 +13,7 @@ class FileMetadataData {
this.name, this.name,
this.size, this.size,
required this.thumbnails, required this.thumbnails,
Map<String, String>? hashes, Map<HashFunction, String>? hashes,
}) : hashes = hashes ?? const {}; }) : hashes = hashes ?? const {};
/// Parse [node] as a FileMetadataData element. /// Parse [node] as a FileMetadataData element.
@ -31,9 +31,11 @@ class FileMetadataData {
final size = final size =
sizeElement != null ? int.parse(sizeElement.innerText()) : null; sizeElement != null ? int.parse(sizeElement.innerText()) : null;
final hashes = <String, String>{}; final hashes = <HashFunction, String>{};
for (final e in node.findTags('hash')) { for (final e in node.findTags('hash')) {
hashes[e.attributes['algo']! as String] = e.innerText(); final hashFunction =
HashFunction.fromName(e.attributes['algo']! as String);
hashes[hashFunction] = e.innerText();
} }
// Thumbnails // Thumbnails
@ -75,7 +77,7 @@ class FileMetadataData {
final int? height; final int? height;
final List<Thumbnail> thumbnails; final List<Thumbnail> thumbnails;
final String? desc; final String? desc;
final Map<String, String> hashes; final Map<HashFunction, String> hashes;
final int? length; final int? length;
final String? name; final String? name;
final int? size; final int? size;

View File

@ -8,23 +8,9 @@ import 'package:moxxmpp/src/xeps/xep_0447.dart';
enum SFSEncryptionType { enum SFSEncryptionType {
aes128GcmNoPadding, aes128GcmNoPadding,
aes256GcmNoPadding, aes256GcmNoPadding,
aes256CbcPkcs7, aes256CbcPkcs7;
}
extension SFSEncryptionTypeNamespaceExtension on SFSEncryptionType { factory SFSEncryptionType.fromNamespace(String xmlns) {
String toNamespace() {
switch (this) {
case SFSEncryptionType.aes128GcmNoPadding:
return sfsEncryptionAes128GcmNoPaddingXmlns;
case SFSEncryptionType.aes256GcmNoPadding:
return sfsEncryptionAes256GcmNoPaddingXmlns;
case SFSEncryptionType.aes256CbcPkcs7:
return sfsEncryptionAes256CbcPkcs7Xmlns;
}
}
}
SFSEncryptionType encryptionTypeFromNamespace(String xmlns) {
switch (xmlns) { switch (xmlns) {
case sfsEncryptionAes128GcmNoPaddingXmlns: case sfsEncryptionAes128GcmNoPaddingXmlns:
return SFSEncryptionType.aes128GcmNoPadding; return SFSEncryptionType.aes128GcmNoPadding;
@ -37,6 +23,18 @@ SFSEncryptionType encryptionTypeFromNamespace(String xmlns) {
throw Exception(); throw Exception();
} }
String toNamespace() {
switch (this) {
case SFSEncryptionType.aes128GcmNoPadding:
return sfsEncryptionAes128GcmNoPaddingXmlns;
case SFSEncryptionType.aes256GcmNoPadding:
return sfsEncryptionAes256GcmNoPaddingXmlns;
case SFSEncryptionType.aes256CbcPkcs7:
return sfsEncryptionAes256CbcPkcs7Xmlns;
}
}
}
class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource { class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource {
StatelessFileSharingEncryptedSource( StatelessFileSharingEncryptedSource(
this.encryption, this.encryption,
@ -63,13 +61,15 @@ class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource {
)!; )!;
// Find hashes // Find hashes
final hashes = <String, String>{}; final hashes = <HashFunction, String>{};
for (final hash in element.findTags('hash', xmlns: hashXmlns)) { for (final hash in element.findTags('hash', xmlns: hashXmlns)) {
hashes[hash.attributes['algo']! as String] = hash.text!; final hashFunction =
HashFunction.fromName(hash.attributes['algo']! as String);
hashes[hashFunction] = hash.text!;
} }
return StatelessFileSharingEncryptedSource( return StatelessFileSharingEncryptedSource(
encryptionTypeFromNamespace(element.attributes['cipher']! as String), SFSEncryptionType.fromNamespace(element.attributes['cipher']! as String),
key, key,
iv, iv,
hashes, hashes,
@ -80,7 +80,7 @@ class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource {
final List<int> key; final List<int> key;
final List<int> iv; final List<int> iv;
final SFSEncryptionType encryption; final SFSEncryptionType encryption;
final Map<String, String> hashes; final Map<HashFunction, String> hashes;
final StatelessFileSharingUrlSource source; final StatelessFileSharingUrlSource source;
@override @override

View File

@ -87,7 +87,7 @@ class StickerPack {
var hashValue = ''; var hashValue = '';
if (hashAvailable) { if (hashAvailable) {
final hash = node.firstTag('hash', xmlns: hashXmlns)!; final hash = node.firstTag('hash', xmlns: hashXmlns)!;
hashAlgorithm = hashFunctionFromName(hash.attributes['algo']! as String); hashAlgorithm = HashFunction.fromName(hash.attributes['algo']! as String);
hashValue = hash.innerText(); hashValue = hash.innerText();
} }
@ -144,7 +144,7 @@ class StickerPack {
text: summary, text: summary,
), ),
constructHashElement( constructHashElement(
hashAlgorithm.toName(), hashAlgorithm,
hashValue, hashValue,
), ),
@ -193,7 +193,7 @@ class StickerPack {
final hashes = List<List<int>>.empty(growable: true); final hashes = List<List<int>>.empty(growable: true);
for (final hash in sticker.metadata.hashes.keys) { for (final hash in sticker.metadata.hashes.keys) {
hashes.add([ hashes.add([
...utf8.encode(hash), ...utf8.encode(hash.toName()),
0x1f, 0x1f,
...utf8.encode(sticker.metadata.hashes[hash]!), ...utf8.encode(sticker.metadata.hashes[hash]!),
0x1f, 0x1f,
@ -217,11 +217,11 @@ class StickerPack {
// Calculate the hash // Calculate the hash
final rawHash = await CryptographicHashManager.hashFromData( final rawHash = await CryptographicHashManager.hashFromData(
hashFunction,
[ [
...metaString, ...metaString,
...stickersString, ...stickersString,
], ],
hashFunction,
); );
return base64.encode(rawHash).substring(0, 24); return base64.encode(rawHash).substring(0, 24);
} }