109 lines
3.7 KiB
Dart
109 lines
3.7 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:crypto/crypto.dart';
|
|
|
|
import 'cbc.dart';
|
|
import 'ecc/curve.dart';
|
|
import 'ecc/ec_public_key.dart';
|
|
import 'eq.dart';
|
|
import 'invalid_mac_exception.dart';
|
|
import 'kdf/derived_root_secrets.dart';
|
|
import 'kdf/hkdfv3.dart';
|
|
import 'legacy_message_exception.dart';
|
|
import 'util/byte_util.dart';
|
|
|
|
const String provision = 'Mixin Provisioning Message';
|
|
|
|
class ProvisionEnvelope {
|
|
ProvisionEnvelope(this.publicKey, this.body);
|
|
|
|
ProvisionEnvelope.fromJson(Map<String, dynamic> json)
|
|
: publicKey = base64Decode(json['public_key'] as String),
|
|
body = base64Decode(json['body'] as String);
|
|
|
|
final Uint8List publicKey;
|
|
final Uint8List body;
|
|
|
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
|
'public_key': base64Encode(publicKey),
|
|
'body': base64Encode(body),
|
|
};
|
|
}
|
|
|
|
Uint8List decrypt(String privateKey, String content) {
|
|
final ourPrivateKey = base64Decode(privateKey);
|
|
final envelopeDecode = base64Decode(content);
|
|
|
|
final map = jsonDecode(String.fromCharCodes(envelopeDecode));
|
|
final provisionEnvelope =
|
|
ProvisionEnvelope.fromJson(map as Map<String, dynamic>);
|
|
final publicKeyable = Curve.decodePoint(provisionEnvelope.publicKey, 0);
|
|
final message = provisionEnvelope.body;
|
|
if (message[0] != 1) {
|
|
throw LegacyMessageException('Invalid version');
|
|
}
|
|
final iv = Uint8List.fromList(message.getRange(1, 16 + 1).toList());
|
|
final mac = message.getRange(message.length - 32, message.length).toList();
|
|
final ivAndCiphertext =
|
|
Uint8List.fromList(message.getRange(0, message.length - 32).toList());
|
|
final cipherText = Uint8List.fromList(
|
|
message.getRange(16 + 1, message.length - 32).toList());
|
|
final sharedSecret = Curve.calculateAgreement(
|
|
publicKeyable, Curve.decodePrivatePoint(ourPrivateKey));
|
|
|
|
final derivedSecretBytes = HKDFv3().deriveSecrets(sharedSecret,
|
|
Uint8List.fromList(utf8.encode(provision)), DerivedRootSecrets.size);
|
|
|
|
final aesKey =
|
|
Uint8List.fromList(derivedSecretBytes.getRange(0, 32).toList());
|
|
final macKey = Uint8List.fromList(
|
|
derivedSecretBytes.getRange(32, derivedSecretBytes.length).toList());
|
|
|
|
if (!verifyMAC(macKey, ivAndCiphertext, mac)) {
|
|
throw InvalidMacException("MAC doesn't match!");
|
|
}
|
|
final plaintext = aesCbcDecrypt(aesKey, iv, cipherText);
|
|
return plaintext;
|
|
}
|
|
|
|
bool verifyMAC(Uint8List key, Uint8List input, List<int> mac) {
|
|
final hmacSha256 = Hmac(sha256, key);
|
|
final digest = hmacSha256.convert(input);
|
|
return eq(digest.bytes, mac);
|
|
}
|
|
|
|
class ProvisioningCipher {
|
|
ProvisioningCipher(this._theirPublicKey);
|
|
|
|
final ECPublicKey _theirPublicKey;
|
|
|
|
Uint8List encrypt(Uint8List message) {
|
|
final ourKeyPair = Curve.generateKeyPair();
|
|
final sharedSecret =
|
|
Curve.calculateAgreement(_theirPublicKey, ourKeyPair.privateKey);
|
|
final derivedSecret = HKDFv3().deriveSecrets(
|
|
sharedSecret, Uint8List.fromList(utf8.encode(provision)), 64);
|
|
final parts = ByteUtil.splitTwo(derivedSecret, 32, 32);
|
|
|
|
final version = Uint8List.fromList([1]);
|
|
final ciphertext = getCiphertext(parts[0], message);
|
|
final mac = _getMac(parts[1], ByteUtil.combine([version, ciphertext]));
|
|
final body = ByteUtil.combine([version, ciphertext, mac]);
|
|
final envelope = ProvisionEnvelope(ourKeyPair.publicKey.serialize(), body);
|
|
final result = jsonEncode(envelope);
|
|
return Uint8List.fromList(utf8.encode(result));
|
|
}
|
|
|
|
Uint8List getCiphertext(Uint8List key, Uint8List message) {
|
|
final iv = Uint8List(16);
|
|
final m = aesCbcEncrypt(key, iv, message);
|
|
return Uint8List.fromList(iv + m);
|
|
}
|
|
|
|
Uint8List _getMac(Uint8List key, Uint8List message) {
|
|
final hmacSha256 = Hmac(sha256, key);
|
|
final digest = hmacSha256.convert(message);
|
|
return Uint8List.fromList(digest.bytes);
|
|
}
|
|
}
|