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
|
||||
avoid_positional_boolean_parameters: 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
|
||||
gitlint jq # Code hygiene
|
||||
ripgrep # General utilities
|
||||
protobuf
|
||||
];
|
||||
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||
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:omemo_dart/src/bundle.dart';
|
||||
import 'package:omemo_dart/src/errors.dart';
|
||||
import 'package:omemo_dart/src/helpers.dart';
|
||||
import 'package:omemo_dart/src/key.dart';
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// pair [ik].
|
||||
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:
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
||||
|
||||
dependencies:
|
||||
cryptography: ^2.0.5
|
||||
pinenacl: ^0.5.1
|
||||
protobuf: ^2.1.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
protoc_plugin: ^20.0.1
|
||||
test: ^1.21.0
|
||||
very_good_analysis: ^3.0.1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user