wip: Add the basics for the Double Ratchet
This commit is contained in:
parent
56ae882aa0
commit
d3c8d813a9
@ -6,3 +6,7 @@ linter:
|
|||||||
use_setters_to_change_properties: false
|
use_setters_to_change_properties: false
|
||||||
avoid_positional_boolean_parameters: false
|
avoid_positional_boolean_parameters: false
|
||||||
avoid_bool_literals_in_conditional_expressions: false
|
avoid_bool_literals_in_conditional_expressions: false
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- "lib/protobuf/*.dart"
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
flutter pinnedJDK android.platform-tools dart # Flutter/Android
|
flutter pinnedJDK android.platform-tools dart # Flutter/Android
|
||||||
gitlint jq # Code hygiene
|
gitlint jq # Code hygiene
|
||||||
ripgrep # General utilities
|
ripgrep # General utilities
|
||||||
|
protobuf
|
||||||
];
|
];
|
||||||
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||||
JAVA_HOME = pinnedJDK;
|
JAVA_HOME = pinnedJDK;
|
||||||
|
263
lib/protobuf/schema.pb.dart
Normal file
263
lib/protobuf/schema.pb.dart
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: schema.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
|
||||||
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
class OMEMOMessage extends $pb.GeneratedMessage {
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'OMEMOMessage', createEmptyInstance: create)
|
||||||
|
..a<$core.int>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'n', $pb.PbFieldType.QU3)
|
||||||
|
..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pn', $pb.PbFieldType.QU3)
|
||||||
|
..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dhPub', $pb.PbFieldType.QY)
|
||||||
|
..a<$core.List<$core.int>>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ciphertext', $pb.PbFieldType.OY)
|
||||||
|
;
|
||||||
|
|
||||||
|
OMEMOMessage._() : super();
|
||||||
|
factory OMEMOMessage({
|
||||||
|
$core.int? n,
|
||||||
|
$core.int? pn,
|
||||||
|
$core.List<$core.int>? dhPub,
|
||||||
|
$core.List<$core.int>? ciphertext,
|
||||||
|
}) {
|
||||||
|
final _result = create();
|
||||||
|
if (n != null) {
|
||||||
|
_result.n = n;
|
||||||
|
}
|
||||||
|
if (pn != null) {
|
||||||
|
_result.pn = pn;
|
||||||
|
}
|
||||||
|
if (dhPub != null) {
|
||||||
|
_result.dhPub = dhPub;
|
||||||
|
}
|
||||||
|
if (ciphertext != null) {
|
||||||
|
_result.ciphertext = ciphertext;
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
factory OMEMOMessage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||||
|
factory OMEMOMessage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOMessage clone() => OMEMOMessage()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOMessage copyWith(void Function(OMEMOMessage) updates) => super.copyWith((message) => updates(message as OMEMOMessage)) as OMEMOMessage; // ignore: deprecated_member_use
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOMessage create() => OMEMOMessage._();
|
||||||
|
OMEMOMessage createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<OMEMOMessage> createRepeated() => $pb.PbList<OMEMOMessage>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOMessage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<OMEMOMessage>(create);
|
||||||
|
static OMEMOMessage? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.int get n => $_getIZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set n($core.int v) { $_setUnsignedInt32(0, v); }
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasN() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearN() => clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.int get pn => $_getIZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set pn($core.int v) { $_setUnsignedInt32(1, v); }
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasPn() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearPn() => clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.List<$core.int> get dhPub => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set dhPub($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasDhPub() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearDhPub() => clearField(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.List<$core.int> get ciphertext => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set ciphertext($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasCiphertext() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearCiphertext() => clearField(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OMEMOAuthenticatedMessage extends $pb.GeneratedMessage {
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'OMEMOAuthenticatedMessage', createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'mac', $pb.PbFieldType.QY)
|
||||||
|
..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'message', $pb.PbFieldType.QY)
|
||||||
|
;
|
||||||
|
|
||||||
|
OMEMOAuthenticatedMessage._() : super();
|
||||||
|
factory OMEMOAuthenticatedMessage({
|
||||||
|
$core.List<$core.int>? mac,
|
||||||
|
$core.List<$core.int>? message,
|
||||||
|
}) {
|
||||||
|
final _result = create();
|
||||||
|
if (mac != null) {
|
||||||
|
_result.mac = mac;
|
||||||
|
}
|
||||||
|
if (message != null) {
|
||||||
|
_result.message = message;
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
factory OMEMOAuthenticatedMessage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||||
|
factory OMEMOAuthenticatedMessage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOAuthenticatedMessage clone() => OMEMOAuthenticatedMessage()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOAuthenticatedMessage copyWith(void Function(OMEMOAuthenticatedMessage) updates) => super.copyWith((message) => updates(message as OMEMOAuthenticatedMessage)) as OMEMOAuthenticatedMessage; // ignore: deprecated_member_use
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOAuthenticatedMessage create() => OMEMOAuthenticatedMessage._();
|
||||||
|
OMEMOAuthenticatedMessage createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<OMEMOAuthenticatedMessage> createRepeated() => $pb.PbList<OMEMOAuthenticatedMessage>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOAuthenticatedMessage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<OMEMOAuthenticatedMessage>(create);
|
||||||
|
static OMEMOAuthenticatedMessage? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get mac => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set mac($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasMac() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearMac() => clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get message => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set message($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasMessage() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearMessage() => clearField(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OMEMOKeyExchange extends $pb.GeneratedMessage {
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'OMEMOKeyExchange', createEmptyInstance: create)
|
||||||
|
..a<$core.int>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pkId', $pb.PbFieldType.QU3)
|
||||||
|
..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'spkId', $pb.PbFieldType.QU3)
|
||||||
|
..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ik', $pb.PbFieldType.QY)
|
||||||
|
..a<$core.List<$core.int>>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ek', $pb.PbFieldType.QY)
|
||||||
|
..aQM<OMEMOAuthenticatedMessage>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'message', subBuilder: OMEMOAuthenticatedMessage.create)
|
||||||
|
;
|
||||||
|
|
||||||
|
OMEMOKeyExchange._() : super();
|
||||||
|
factory OMEMOKeyExchange({
|
||||||
|
$core.int? pkId,
|
||||||
|
$core.int? spkId,
|
||||||
|
$core.List<$core.int>? ik,
|
||||||
|
$core.List<$core.int>? ek,
|
||||||
|
OMEMOAuthenticatedMessage? message,
|
||||||
|
}) {
|
||||||
|
final _result = create();
|
||||||
|
if (pkId != null) {
|
||||||
|
_result.pkId = pkId;
|
||||||
|
}
|
||||||
|
if (spkId != null) {
|
||||||
|
_result.spkId = spkId;
|
||||||
|
}
|
||||||
|
if (ik != null) {
|
||||||
|
_result.ik = ik;
|
||||||
|
}
|
||||||
|
if (ek != null) {
|
||||||
|
_result.ek = ek;
|
||||||
|
}
|
||||||
|
if (message != null) {
|
||||||
|
_result.message = message;
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
factory OMEMOKeyExchange.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||||
|
factory OMEMOKeyExchange.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOKeyExchange clone() => OMEMOKeyExchange()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated(
|
||||||
|
'Using this can add significant overhead to your binary. '
|
||||||
|
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||||
|
'Will be removed in next major version')
|
||||||
|
OMEMOKeyExchange copyWith(void Function(OMEMOKeyExchange) updates) => super.copyWith((message) => updates(message as OMEMOKeyExchange)) as OMEMOKeyExchange; // ignore: deprecated_member_use
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOKeyExchange create() => OMEMOKeyExchange._();
|
||||||
|
OMEMOKeyExchange createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<OMEMOKeyExchange> createRepeated() => $pb.PbList<OMEMOKeyExchange>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static OMEMOKeyExchange getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<OMEMOKeyExchange>(create);
|
||||||
|
static OMEMOKeyExchange? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.int get pkId => $_getIZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set pkId($core.int v) { $_setUnsignedInt32(0, v); }
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasPkId() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearPkId() => clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.int get spkId => $_getIZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set spkId($core.int v) { $_setUnsignedInt32(1, v); }
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasSpkId() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearSpkId() => clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.List<$core.int> get ik => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set ik($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasIk() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearIk() => clearField(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.List<$core.int> get ek => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set ek($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasEk() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearEk() => clearField(4);
|
||||||
|
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
OMEMOAuthenticatedMessage get message => $_getN(4);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
set message(OMEMOAuthenticatedMessage v) { setField(5, v); }
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
$core.bool hasMessage() => $_has(4);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
void clearMessage() => clearField(5);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
OMEMOAuthenticatedMessage ensureMessage() => $_ensure(4);
|
||||||
|
}
|
||||||
|
|
7
lib/protobuf/schema.pbenum.dart
Normal file
7
lib/protobuf/schema.pbenum.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: schema.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||||
|
|
48
lib/protobuf/schema.pbjson.dart
Normal file
48
lib/protobuf/schema.pbjson.dart
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: schema.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
import 'dart:convert' as $convert;
|
||||||
|
import 'dart:typed_data' as $typed_data;
|
||||||
|
@$core.Deprecated('Use oMEMOMessageDescriptor instead')
|
||||||
|
const OMEMOMessage$json = const {
|
||||||
|
'1': 'OMEMOMessage',
|
||||||
|
'2': const [
|
||||||
|
const {'1': 'n', '3': 1, '4': 2, '5': 13, '10': 'n'},
|
||||||
|
const {'1': 'pn', '3': 2, '4': 2, '5': 13, '10': 'pn'},
|
||||||
|
const {'1': 'dh_pub', '3': 3, '4': 2, '5': 12, '10': 'dhPub'},
|
||||||
|
const {'1': 'ciphertext', '3': 4, '4': 1, '5': 12, '10': 'ciphertext'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `OMEMOMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List oMEMOMessageDescriptor = $convert.base64Decode('CgxPTUVNT01lc3NhZ2USDAoBbhgBIAIoDVIBbhIOCgJwbhgCIAIoDVICcG4SFQoGZGhfcHViGAMgAigMUgVkaFB1YhIeCgpjaXBoZXJ0ZXh0GAQgASgMUgpjaXBoZXJ0ZXh0');
|
||||||
|
@$core.Deprecated('Use oMEMOAuthenticatedMessageDescriptor instead')
|
||||||
|
const OMEMOAuthenticatedMessage$json = const {
|
||||||
|
'1': 'OMEMOAuthenticatedMessage',
|
||||||
|
'2': const [
|
||||||
|
const {'1': 'mac', '3': 1, '4': 2, '5': 12, '10': 'mac'},
|
||||||
|
const {'1': 'message', '3': 2, '4': 2, '5': 12, '10': 'message'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `OMEMOAuthenticatedMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List oMEMOAuthenticatedMessageDescriptor = $convert.base64Decode('ChlPTUVNT0F1dGhlbnRpY2F0ZWRNZXNzYWdlEhAKA21hYxgBIAIoDFIDbWFjEhgKB21lc3NhZ2UYAiACKAxSB21lc3NhZ2U=');
|
||||||
|
@$core.Deprecated('Use oMEMOKeyExchangeDescriptor instead')
|
||||||
|
const OMEMOKeyExchange$json = const {
|
||||||
|
'1': 'OMEMOKeyExchange',
|
||||||
|
'2': const [
|
||||||
|
const {'1': 'pk_id', '3': 1, '4': 2, '5': 13, '10': 'pkId'},
|
||||||
|
const {'1': 'spk_id', '3': 2, '4': 2, '5': 13, '10': 'spkId'},
|
||||||
|
const {'1': 'ik', '3': 3, '4': 2, '5': 12, '10': 'ik'},
|
||||||
|
const {'1': 'ek', '3': 4, '4': 2, '5': 12, '10': 'ek'},
|
||||||
|
const {'1': 'message', '3': 5, '4': 2, '5': 11, '6': '.OMEMOAuthenticatedMessage', '10': 'message'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `OMEMOKeyExchange`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List oMEMOKeyExchangeDescriptor = $convert.base64Decode('ChBPTUVNT0tleUV4Y2hhbmdlEhMKBXBrX2lkGAEgAigNUgRwa0lkEhUKBnNwa19pZBgCIAIoDVIFc3BrSWQSDgoCaWsYAyACKAxSAmlrEg4KAmVrGAQgAigMUgJlaxI0CgdtZXNzYWdlGAUgAigLMhouT01FTU9BdXRoZW50aWNhdGVkTWVzc2FnZVIHbWVzc2FnZQ==');
|
9
lib/protobuf/schema.pbserver.dart
Normal file
9
lib/protobuf/schema.pbserver.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: schema.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||||
|
|
||||||
|
export 'schema.pb.dart';
|
||||||
|
|
281
lib/src/double_ratchet.dart
Normal file
281
lib/src/double_ratchet.dart
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
|
import 'package:omemo_dart/src/bundle.dart';
|
||||||
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
|
import 'package:omemo_dart/src/key.dart';
|
||||||
|
import 'package:omemo_dart/src/x3dh.dart';
|
||||||
|
|
||||||
|
class OmemoRatchetStepResult {
|
||||||
|
|
||||||
|
const OmemoRatchetStepResult(this.header, this.cipherText);
|
||||||
|
final List<int> header;
|
||||||
|
final List<int> cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OmemoEncryptionResult {
|
||||||
|
|
||||||
|
const OmemoEncryptionResult(this.cipherText, this.keys);
|
||||||
|
/// The encrypted plaintext
|
||||||
|
final List<int> cipherText;
|
||||||
|
/// Mapping between Device id and the key to decrypt cipherText;
|
||||||
|
final Map<String, List<int>> keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The session state of one party
|
||||||
|
class AliceOmemoSession {
|
||||||
|
|
||||||
|
AliceOmemoSession(
|
||||||
|
this.dhs,
|
||||||
|
this.dhr,
|
||||||
|
this.ek,
|
||||||
|
this.rk,
|
||||||
|
this.cks,
|
||||||
|
this.ckr,
|
||||||
|
this.ns,
|
||||||
|
this.nr,
|
||||||
|
this.pn,
|
||||||
|
// this.skippedMessages,
|
||||||
|
this.ad,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The Diffie-Hellman sending key pair
|
||||||
|
final OmemoKeyPair dhs;
|
||||||
|
|
||||||
|
/// The Diffie-Hellman receiving key pair
|
||||||
|
final OmemoPublicKey dhr;
|
||||||
|
|
||||||
|
/// The EK used by X3DH
|
||||||
|
final OmemoKeyPair ek;
|
||||||
|
|
||||||
|
/// The Root Key
|
||||||
|
List<int> rk;
|
||||||
|
|
||||||
|
/// Sending Chain Key
|
||||||
|
List<int> cks;
|
||||||
|
|
||||||
|
/// Receiving Chain Key
|
||||||
|
List<int>? ckr;
|
||||||
|
|
||||||
|
/// Message number for sending
|
||||||
|
int ns;
|
||||||
|
|
||||||
|
/// Message number for receiving
|
||||||
|
int nr;
|
||||||
|
|
||||||
|
/// Number of messages in the previous sending chain
|
||||||
|
int pn;
|
||||||
|
|
||||||
|
/// The associated data from the X3DH
|
||||||
|
final List<int> ad;
|
||||||
|
|
||||||
|
// TODO(PapaTutuWawa): Track skipped over message keys
|
||||||
|
|
||||||
|
static Future<AliceOmemoSession> newSession(OmemoBundle bundle, OmemoKeyPair ik) async {
|
||||||
|
// TODO(PapaTutuWawa): Error handling
|
||||||
|
final x3dhResult = await x3dhFromBundle(bundle, ik);
|
||||||
|
final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||||
|
final dhr = bundle.ik;
|
||||||
|
final ek = x3dhResult.ek;
|
||||||
|
final sk = x3dhResult.sk;
|
||||||
|
final kdfRkResult = await kdfRk(sk, await dh(dhs, dhr, 2));
|
||||||
|
|
||||||
|
return AliceOmemoSession(
|
||||||
|
dhs,
|
||||||
|
dhr,
|
||||||
|
ek,
|
||||||
|
kdfRkResult.rk,
|
||||||
|
kdfRkResult.ck,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
x3dhResult.ad,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The associated_data parameter is implicit as it belongs to the session
|
||||||
|
Future<List<int>> _encrypt(List<int> mk, List<int> plaintext, List<int> associatedData) async {
|
||||||
|
final algorithm = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 80,
|
||||||
|
);
|
||||||
|
final hkdfResult = await algorithm.deriveKey(
|
||||||
|
secretKey: SecretKey(mk),
|
||||||
|
nonce: List<int>.filled(32, 0x00),
|
||||||
|
info: utf8.encode(encryptHkdfInfoString),
|
||||||
|
);
|
||||||
|
final bytes = await hkdfResult.extractBytes();
|
||||||
|
|
||||||
|
final encKey = bytes.sublist(0, 32);
|
||||||
|
final authKey = bytes.sublist(32, 64);
|
||||||
|
final iv = bytes.sublist(64, 82);
|
||||||
|
|
||||||
|
// TODO(PapaTutuWawa): Remove once done
|
||||||
|
assert(encKey.length == 32);
|
||||||
|
assert(authKey.length == 32);
|
||||||
|
assert(iv.length == 16);
|
||||||
|
|
||||||
|
// 32 = 256 / 8
|
||||||
|
final encodedPlaintext = pkcs7padding(plaintext, 32);
|
||||||
|
|
||||||
|
final aesAlgorithm = AesCbc.with256bits(
|
||||||
|
macAlgorithm: Hmac.sha256(),
|
||||||
|
);
|
||||||
|
final secretBox = await aesAlgorithm.encrypt(
|
||||||
|
encodedPlaintext,
|
||||||
|
secretKey: SecretKey(encKey),
|
||||||
|
nonce: iv,
|
||||||
|
);
|
||||||
|
|
||||||
|
final ad_ = associatedData.sublist(0, ad.length);
|
||||||
|
final message = OMEMOMessage.fromBuffer(associatedData.sublist(ad.length))
|
||||||
|
..ciphertext = secretBox.cipherText;
|
||||||
|
final messageBytes = message.writeToBuffer();
|
||||||
|
|
||||||
|
final input = concat([ad_, messageBytes]);
|
||||||
|
final authBytes = (await Hmac.sha256().calculateMac(
|
||||||
|
input,
|
||||||
|
secretKey: SecretKey(authKey),
|
||||||
|
)).bytes.sublist(0, 16);
|
||||||
|
|
||||||
|
final authenticatedMessage = OMEMOAuthenticatedMessage()
|
||||||
|
..mac = authBytes
|
||||||
|
..message = messageBytes;
|
||||||
|
|
||||||
|
return authenticatedMessage.writeToBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<int>> ratchetStep(List<int> plaintext) async {
|
||||||
|
final kdfResult = await kdfCk(cks);
|
||||||
|
final message = OMEMOMessage()
|
||||||
|
..dhPub = await dhs.pk.getBytes()
|
||||||
|
..pn = pn
|
||||||
|
..n = ns;
|
||||||
|
final header = message.writeToBuffer();
|
||||||
|
|
||||||
|
cks = kdfResult.ck;
|
||||||
|
ns++;
|
||||||
|
|
||||||
|
return _encrypt(
|
||||||
|
kdfResult.mk,
|
||||||
|
plaintext,
|
||||||
|
concat([ad, header]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<OmemoEncryptionResult> encryptForSessions(List<AliceOmemoSession> sessions, String plaintext) async {
|
||||||
|
// TODO(PapaTutuWawa): Generate random data
|
||||||
|
final key = List<int>.filled(32, 0x0);
|
||||||
|
final algorithm = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 80,
|
||||||
|
);
|
||||||
|
final result = await algorithm.deriveKey(
|
||||||
|
secretKey: SecretKey(key),
|
||||||
|
nonce: List<int>.filled(32, 0x0),
|
||||||
|
info: utf8.encode(encryptionHkdfInfoString),
|
||||||
|
);
|
||||||
|
final bytes = await result.extractBytes();
|
||||||
|
|
||||||
|
final encKey = bytes.sublist(0, 32);
|
||||||
|
final authKey = bytes.sublist(32, 64);
|
||||||
|
final iv = bytes.sublist(64, 80);
|
||||||
|
|
||||||
|
final encodedPlaintext = pkcs7padding(utf8.encode(plaintext), 32);
|
||||||
|
final aesAlgorithm = AesCbc.with256bits(
|
||||||
|
macAlgorithm: Hmac.sha256(),
|
||||||
|
);
|
||||||
|
final secretBox = await aesAlgorithm.encrypt(
|
||||||
|
encodedPlaintext,
|
||||||
|
secretKey: SecretKey(encKey),
|
||||||
|
nonce: iv,
|
||||||
|
);
|
||||||
|
final hmac = (await Hmac.sha256().calculateMac(
|
||||||
|
secretBox.cipherText,
|
||||||
|
secretKey: SecretKey(authKey),
|
||||||
|
)).bytes.sublist(0, 16);
|
||||||
|
|
||||||
|
final keyData = concat([encKey, hmac]);
|
||||||
|
|
||||||
|
final keyMap = <String, List<int>>{};
|
||||||
|
for (final session in sessions) {
|
||||||
|
final ratchetKey = await session.ratchetStep(keyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OmemoEncryptionResult(
|
||||||
|
secretBox.cipherText,
|
||||||
|
keyMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of the KDF_RK function from the Double Ratchet spec.
|
||||||
|
class KdfRkResult {
|
||||||
|
|
||||||
|
const KdfRkResult(this.rk, this.ck);
|
||||||
|
/// 32 byte Root Key
|
||||||
|
final List<int> rk;
|
||||||
|
|
||||||
|
/// 32 byte Chain Key
|
||||||
|
final List<int> ck;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of the KDF_CK function from the Double Ratchet spec.
|
||||||
|
class KdfCkResult {
|
||||||
|
|
||||||
|
const KdfCkResult(this.ck, this.mk);
|
||||||
|
/// 32 byte Chain Key
|
||||||
|
final List<int> ck;
|
||||||
|
|
||||||
|
/// 32 byte Message Key
|
||||||
|
final List<int> mk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Amount of messages we may skip per session
|
||||||
|
const maxSkip = 1000;
|
||||||
|
|
||||||
|
/// Info string for KDF_RK
|
||||||
|
const kdfRkInfoString = 'OMEMO Root Chain';
|
||||||
|
|
||||||
|
/// Info string for ENCRYPT
|
||||||
|
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
||||||
|
|
||||||
|
/// Info string for encrypting a message
|
||||||
|
const encryptionHkdfInfoString = 'OMEMO Payload';
|
||||||
|
|
||||||
|
/// Flags for KDF_CK
|
||||||
|
const kdfCkNextMessageKey = 0x01;
|
||||||
|
const kdfCkNextChainKey = 0x02;
|
||||||
|
|
||||||
|
Future<KdfRkResult> kdfRk(List<int> rk, List<int> dhOut) async {
|
||||||
|
final algorithm = Hkdf(
|
||||||
|
hmac: Hmac(Sha256()),
|
||||||
|
outputLength: 32,
|
||||||
|
);
|
||||||
|
final result = await algorithm.deriveKey(
|
||||||
|
secretKey: SecretKey(dhOut),
|
||||||
|
nonce: rk,
|
||||||
|
info: utf8.encode(kdfRkInfoString),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(PapaTutuWawa): Does the rk in the tuple (rk, ck) refer to the input rk?
|
||||||
|
return KdfRkResult(rk, await result.extractBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<KdfCkResult> kdfCk(List<int> ck) async {
|
||||||
|
final hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32);
|
||||||
|
final newCk = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(ck),
|
||||||
|
nonce: [kdfCkNextChainKey],
|
||||||
|
);
|
||||||
|
final mk = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(ck),
|
||||||
|
nonce: [kdfCkNextMessageKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
return KdfCkResult(
|
||||||
|
await newCk.extractBytes(),
|
||||||
|
await mk.extractBytes(),
|
||||||
|
);
|
||||||
|
}
|
15
lib/src/helpers.dart
Normal file
15
lib/src/helpers.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/// Flattens [inputs] and concatenates the elements.
|
||||||
|
List<int> concat(List<List<int>> inputs) {
|
||||||
|
final tmp = List<int>.empty(growable: true);
|
||||||
|
for (final input in inputs) {
|
||||||
|
tmp.addAll(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> pkcs7padding(List<int> input, int size) {
|
||||||
|
final paddingLength = size - input.length % size;
|
||||||
|
final padding = List<int>.filled(paddingLength, 0x0);
|
||||||
|
return concat([input, padding]);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import 'dart:math';
|
|||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:omemo_dart/src/bundle.dart';
|
import 'package:omemo_dart/src/bundle.dart';
|
||||||
import 'package:omemo_dart/src/errors.dart';
|
import 'package:omemo_dart/src/errors.dart';
|
||||||
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
import 'package:omemo_dart/src/key.dart';
|
import 'package:omemo_dart/src/key.dart';
|
||||||
|
|
||||||
/// The overarching assumption is that we use Ed25519 keys for the identity keys
|
/// The overarching assumption is that we use Ed25519 keys for the identity keys
|
||||||
@ -88,16 +89,6 @@ Future<List<int>> kdf(List<int> km) async {
|
|||||||
return output.extractBytes();
|
return output.extractBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flattens [inputs] and concatenates the elements.
|
|
||||||
List<int> concat(List<List<int>> inputs) {
|
|
||||||
final tmp = List<int>.empty(growable: true);
|
|
||||||
for (final input in inputs) {
|
|
||||||
tmp.addAll(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Alice builds a session with Bob using his bundle [bundle] and Alice's identity key
|
/// Alice builds a session with Bob using his bundle [bundle] and Alice's identity key
|
||||||
/// pair [ik].
|
/// pair [ik].
|
||||||
Future<X3DHAliceResult> x3dhFromBundle(OmemoBundle bundle, OmemoKeyPair ik) async {
|
Future<X3DHAliceResult> x3dhFromBundle(OmemoBundle bundle, OmemoKeyPair ik) async {
|
||||||
|
20
protobuf/schema.proto
Normal file
20
protobuf/schema.proto
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Taken from https://xmpp.org/extensions/xep-0384.html#protobuf-schema
|
||||||
|
message OMEMOMessage {
|
||||||
|
required uint32 n = 1;
|
||||||
|
required uint32 pn = 2;
|
||||||
|
required bytes dh_pub = 3;
|
||||||
|
optional bytes ciphertext = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OMEMOAuthenticatedMessage {
|
||||||
|
required bytes mac = 1;
|
||||||
|
required bytes message = 2; // Byte-encoding of an OMEMOMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
message OMEMOKeyExchange {
|
||||||
|
required uint32 pk_id = 1;
|
||||||
|
required uint32 spk_id = 2;
|
||||||
|
required bytes ik = 3;
|
||||||
|
required bytes ek = 4;
|
||||||
|
required OMEMOAuthenticatedMessage message = 5;
|
||||||
|
}
|
@ -5,12 +5,14 @@ version: 0.1.0
|
|||||||
environment:
|
environment:
|
||||||
sdk: '>=2.17.0 <3.0.0'
|
sdk: '>=2.17.0 <3.0.0'
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
pinenacl: ^0.5.1
|
pinenacl: ^0.5.1
|
||||||
|
protobuf: ^2.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
|
protoc_plugin: ^20.0.1
|
||||||
test: ^1.21.0
|
test: ^1.21.0
|
||||||
very_good_analysis: ^3.0.1
|
very_good_analysis: ^3.0.1
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user