veilid/veilid-flutter/lib/veilid_crypto.dart

281 lines
9.6 KiB
Dart
Raw Permalink Normal View History

2023-05-29 19:24:57 +00:00
import 'dart:async';
2023-08-03 01:09:47 +00:00
import 'dart:convert';
2023-05-29 19:24:57 +00:00
import 'dart:typed_data';
import 'package:charcode/charcode.dart';
2023-07-05 22:48:06 +00:00
import 'package:equatable/equatable.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
2023-05-29 19:24:57 +00:00
import 'veilid.dart';
//////////////////////////////////////
/// CryptoKind
typedef CryptoKind = int;
const CryptoKind cryptoKindVLD0 =
2023-08-05 17:50:16 +00:00
$V << 24 | $L << 16 | $D << 8 | $0 << 0; // "VLD0"
2023-05-29 19:24:57 +00:00
const CryptoKind cryptoKindNONE =
2023-08-05 17:50:16 +00:00
$N << 24 | $O << 16 | $N << 8 | $E << 0; // "NONE"
2023-05-29 19:24:57 +00:00
2023-07-30 19:57:06 +00:00
String cryptoKindToString(CryptoKind kind) =>
cryptoKindToBytes(kind).map(String.fromCharCode).join();
2023-05-29 19:24:57 +00:00
2023-07-21 18:30:10 +00:00
const CryptoKind bestCryptoKind = cryptoKindVLD0;
2023-07-09 14:55:43 +00:00
Uint8List cryptoKindToBytes(CryptoKind kind) {
2023-07-26 18:20:17 +00:00
final b = Uint8List(4);
ByteData.sublistView(b).setUint32(0, kind);
2023-07-09 14:55:43 +00:00
return b;
}
2023-05-29 19:24:57 +00:00
CryptoKind cryptoKindFromString(String s) {
if (s.codeUnits.length != 4) {
2023-07-26 18:20:17 +00:00
throw const FormatException('malformed string');
2023-05-29 19:24:57 +00:00
}
2023-07-30 19:57:06 +00:00
final kind =
ByteData.sublistView(Uint8List.fromList(s.codeUnits)).getUint32(0);
2023-05-29 19:24:57 +00:00
return kind;
}
//////////////////////////////////////
/// Types
2023-07-05 22:48:06 +00:00
@immutable
class Typed<V extends EncodedString> extends Equatable {
const Typed({required this.kind, required this.value});
2023-05-29 19:24:57 +00:00
2023-07-05 22:48:06 +00:00
factory Typed.fromString(String s) {
2023-07-26 18:20:17 +00:00
final parts = s.split(':');
2023-05-29 19:24:57 +00:00
if (parts.length < 2 || parts[0].codeUnits.length != 4) {
2023-07-26 18:20:17 +00:00
throw const FormatException('malformed string');
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
final kind = cryptoKindFromString(parts[0]);
2023-07-26 18:20:17 +00:00
final value = EncodedString.fromString<V>(parts.sublist(1).join(':'));
2023-07-05 22:48:06 +00:00
return Typed(kind: kind, value: value);
2023-05-29 19:24:57 +00:00
}
2023-07-26 18:20:17 +00:00
factory Typed.fromJson(dynamic json) => Typed.fromString(json as String);
final CryptoKind kind;
final V value;
@override
List<Object> get props => [kind, value];
@override
String toString() => '${cryptoKindToString(kind)}:$value';
2023-05-29 19:24:57 +00:00
2023-07-09 14:55:43 +00:00
Uint8List decode() {
2023-07-30 19:57:06 +00:00
final b = BytesBuilder()
..add(cryptoKindToBytes(kind))
..add(value.decode());
2023-07-25 05:04:22 +00:00
return b.toBytes();
2023-07-09 14:55:43 +00:00
}
2023-07-05 22:48:06 +00:00
String toJson() => toString();
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
@immutable
class KeyPair extends Equatable {
const KeyPair({required this.key, required this.secret});
2023-05-29 19:24:57 +00:00
2023-07-05 22:48:06 +00:00
factory KeyPair.fromString(String s) {
2023-07-26 18:20:17 +00:00
final parts = s.split(':');
2023-05-29 19:24:57 +00:00
if (parts.length != 2 ||
parts[0].codeUnits.length != 43 ||
parts[1].codeUnits.length != 43) {
2023-07-26 18:20:17 +00:00
throw const FormatException('malformed string');
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
final key = PublicKey.fromString(parts[0]);
final secret = PublicKey.fromString(parts[1]);
return KeyPair(key: key, secret: secret);
2023-05-29 19:24:57 +00:00
}
2023-07-26 18:20:17 +00:00
factory KeyPair.fromJson(dynamic json) => KeyPair.fromString(json as String);
final PublicKey key;
final PublicKey secret;
@override
List<Object> get props => [key, secret];
@override
String toString() => '$key:$secret';
2023-05-29 19:24:57 +00:00
2023-07-05 22:48:06 +00:00
String toJson() => toString();
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
@immutable
class TypedKeyPair extends Equatable {
const TypedKeyPair(
{required this.kind, required this.key, required this.secret});
2023-05-29 19:24:57 +00:00
2023-07-05 22:48:06 +00:00
factory TypedKeyPair.fromString(String s) {
2023-07-26 18:20:17 +00:00
final parts = s.split(':');
2023-05-29 19:24:57 +00:00
if (parts.length != 3 ||
parts[0].codeUnits.length != 4 ||
parts[1].codeUnits.length != 43 ||
parts[2].codeUnits.length != 43) {
2023-07-26 18:20:17 +00:00
throw VeilidAPIExceptionInvalidArgument('malformed string', 's', s);
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
final kind = cryptoKindFromString(parts[0]);
final key = PublicKey.fromString(parts[1]);
final secret = PublicKey.fromString(parts[2]);
return TypedKeyPair(kind: kind, key: key, secret: secret);
2023-05-29 19:24:57 +00:00
}
2023-07-05 22:48:06 +00:00
factory TypedKeyPair.fromJson(dynamic json) =>
TypedKeyPair.fromString(json as String);
2023-07-21 18:30:10 +00:00
factory TypedKeyPair.fromKeyPair(CryptoKind kind, KeyPair keyPair) =>
TypedKeyPair(kind: kind, key: keyPair.key, secret: keyPair.secret);
2023-07-26 18:20:17 +00:00
final CryptoKind kind;
final PublicKey key;
final PublicKey secret;
@override
List<Object> get props => [kind, key, secret];
@override
2023-07-30 19:57:06 +00:00
String toString() => '${cryptoKindToString(kind)}:$key:$secret';
2023-07-26 18:20:17 +00:00
String toJson() => toString();
2023-05-29 19:24:57 +00:00
}
typedef CryptoKey = FixedEncodedString43;
typedef Signature = FixedEncodedString86;
typedef Nonce = FixedEncodedString32;
typedef PublicKey = CryptoKey;
typedef SecretKey = CryptoKey;
typedef HashDigest = CryptoKey;
typedef SharedSecret = CryptoKey;
typedef CryptoKeyDistance = CryptoKey;
typedef TypedKey = Typed<CryptoKey>;
2023-07-02 15:31:53 +00:00
typedef TypedSecret = Typed<SecretKey>;
2023-07-07 23:32:59 +00:00
typedef TypedHashDigest = Typed<HashDigest>;
2023-07-02 15:31:53 +00:00
2023-05-29 19:24:57 +00:00
typedef TypedSignature = Typed<Signature>;
//////////////////////////////////////
/// VeilidCryptoSystem
abstract class VeilidCryptoSystem {
CryptoKind kind();
Future<SharedSecret> cachedDH(PublicKey key, SecretKey secret);
Future<SharedSecret> computeDH(PublicKey key, SecretKey secret);
Future<Uint8List> randomBytes(int len);
Future<int> defaultSaltLength();
Future<String> hashPassword(Uint8List password, Uint8List salt);
Future<bool> verifyPassword(Uint8List password, String passwordHash);
Future<SharedSecret> deriveSharedSecret(Uint8List password, Uint8List salt);
Future<Nonce> randomNonce();
Future<SharedSecret> randomSharedSecret();
Future<KeyPair> generateKeyPair();
Future<HashDigest> generateHash(Uint8List data);
//Future<HashDigest> generateHashReader(Stream<List<int>> reader);
Future<bool> validateKeyPair(PublicKey key, SecretKey secret);
2023-07-30 19:57:06 +00:00
Future<bool> validateKeyPairWithKeyPair(KeyPair keyPair) =>
validateKeyPair(keyPair.key, keyPair.secret);
2023-07-19 14:07:51 +00:00
2023-05-29 19:24:57 +00:00
Future<bool> validateHash(Uint8List data, HashDigest hash);
//Future<bool> validateHashReader(Stream<List<int>> reader, HashDigest hash);
Future<CryptoKeyDistance> distance(CryptoKey key1, CryptoKey key2);
Future<Signature> sign(PublicKey key, SecretKey secret, Uint8List data);
2023-07-30 19:57:06 +00:00
Future<Signature> signWithKeyPair(KeyPair keyPair, Uint8List data) =>
sign(keyPair.key, keyPair.secret, data);
2023-07-19 14:07:51 +00:00
2023-05-29 19:24:57 +00:00
Future<void> verify(PublicKey key, Uint8List data, Signature signature);
Future<int> aeadOverhead();
Future<Uint8List> decryptAead(Uint8List body, Nonce nonce,
SharedSecret sharedSecret, Uint8List? associatedData);
Future<Uint8List> encryptAead(Uint8List body, Nonce nonce,
SharedSecret sharedSecret, Uint8List? associatedData);
Future<Uint8List> cryptNoAuth(
Uint8List body, Nonce nonce, SharedSecret sharedSecret);
2023-08-03 01:09:47 +00:00
2023-09-25 02:36:15 +00:00
Future<Uint8List> encryptAeadWithNonce(
Uint8List body, SharedSecret secret) async {
// generate nonce
final nonce = await randomNonce();
// crypt and append nonce
final b = BytesBuilder()
..add(await encryptAead(body, nonce, secret, null))
..add(nonce.decode());
return b.toBytes();
}
Future<Uint8List> decryptAeadWithNonce(
Uint8List body, SharedSecret secret) async {
if (body.length < Nonce.decodedLength()) {
throw const FormatException('not enough data to decrypt');
}
final nonce =
Nonce.fromBytes(body.sublist(body.length - Nonce.decodedLength()));
final encryptedData = body.sublist(0, body.length - Nonce.decodedLength());
// decrypt
return decryptAead(encryptedData, nonce, secret, null);
}
Future<Uint8List> encryptAeadWithPassword(
Uint8List body, String password) async {
final ekbytes = Uint8List.fromList(utf8.encode(password));
final nonce = await randomNonce();
final saltBytes = nonce.decode();
final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes);
return Uint8List.fromList(
(await encryptAead(body, nonce, sharedSecret, null)) + saltBytes);
}
Future<Uint8List> decryptAeadWithPassword(
Uint8List body, String password) async {
if (body.length < Nonce.decodedLength()) {
throw const FormatException('not enough data to decrypt');
}
final ekbytes = Uint8List.fromList(utf8.encode(password));
final bodyBytes = body.sublist(0, body.length - Nonce.decodedLength());
final saltBytes = body.sublist(body.length - Nonce.decodedLength());
final nonce = Nonce.fromBytes(saltBytes);
final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes);
return decryptAead(bodyBytes, nonce, sharedSecret, null);
}
2023-08-03 01:09:47 +00:00
Future<Uint8List> encryptNoAuthWithNonce(
Uint8List body, SharedSecret secret) async {
// generate nonce
final nonce = await randomNonce();
// crypt and append nonce
final b = BytesBuilder()
..add(await cryptNoAuth(body, nonce, secret))
..add(nonce.decode());
return b.toBytes();
}
Future<Uint8List> decryptNoAuthWithNonce(
Uint8List body, SharedSecret secret) async {
2023-08-04 05:00:23 +00:00
if (body.length < Nonce.decodedLength()) {
2023-08-03 01:09:47 +00:00
throw const FormatException('not enough data to decrypt');
}
final nonce =
Nonce.fromBytes(body.sublist(body.length - Nonce.decodedLength()));
final encryptedData = body.sublist(0, body.length - Nonce.decodedLength());
// decrypt
return cryptNoAuth(encryptedData, nonce, secret);
}
Future<Uint8List> encryptNoAuthWithPassword(
Uint8List body, String password) async {
final ekbytes = Uint8List.fromList(utf8.encode(password));
final nonce = await randomNonce();
final saltBytes = nonce.decode();
final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes);
2023-09-24 19:59:47 +00:00
return Uint8List.fromList(
(await cryptNoAuth(body, nonce, sharedSecret)) + saltBytes);
2023-08-03 01:09:47 +00:00
}
Future<Uint8List> decryptNoAuthWithPassword(
Uint8List body, String password) async {
2023-08-04 05:00:23 +00:00
if (body.length < Nonce.decodedLength()) {
2023-08-03 01:09:47 +00:00
throw const FormatException('not enough data to decrypt');
}
final ekbytes = Uint8List.fromList(utf8.encode(password));
final bodyBytes = body.sublist(0, body.length - Nonce.decodedLength());
final saltBytes = body.sublist(body.length - Nonce.decodedLength());
final nonce = Nonce.fromBytes(saltBytes);
final sharedSecret = await deriveSharedSecret(ekbytes, saltBytes);
return cryptNoAuth(bodyBytes, nonce, sharedSecret);
}
2023-05-29 19:24:57 +00:00
}