Compare commits
2 Commits
09b8613c80
...
320f4a8d4c
Author | SHA1 | Date | |
---|---|---|---|
320f4a8d4c | |||
1fdefacd52 |
@ -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
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
@ -4,60 +4,82 @@ 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 {
|
||||||
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
@ -35,6 +21,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 {
|
||||||
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user