- User model with Ed25519 keypair identity - Register/login on home server (creates keypair) - Join guest servers with signature verification - Public endpoints for cross-server identity - Channel and message CRUD - JWT authentication middleware Architecture: Home server model, no federation, client-driven sync
162 lines
4.4 KiB
Dart
162 lines
4.4 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:bcrypt/bcrypt.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:crypton/crypton.dart';
|
|
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
|
|
|
import '../config/config.dart';
|
|
|
|
class KeyPair {
|
|
final String publicKey;
|
|
final String privateKey;
|
|
|
|
KeyPair({required this.publicKey, required this.privateKey});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'publicKey': publicKey,
|
|
'privateKey': privateKey,
|
|
};
|
|
|
|
factory KeyPair.fromJson(Map<String, dynamic> json) {
|
|
return KeyPair(
|
|
publicKey: json['publicKey'] as String,
|
|
privateKey: json['privateKey'] as String,
|
|
);
|
|
}
|
|
}
|
|
|
|
class CryptoService {
|
|
/// Generate a new Ed25519 key pair
|
|
static KeyPair generateKeyPair() {
|
|
final keyPair = ECKeypair.fromRandom();
|
|
return KeyPair(
|
|
publicKey: keyPair.publicKey.toString(),
|
|
privateKey: keyPair.privateKey.toString(),
|
|
);
|
|
}
|
|
|
|
/// Sign data with private key
|
|
static String sign(String data, String privateKey) {
|
|
final privKey = ECPrivateKey.fromString(privateKey);
|
|
final signature = privKey.createSignature(data);
|
|
return signature.toString();
|
|
}
|
|
|
|
/// Verify signature with public key
|
|
static bool verify(String data, String signature, String publicKey) {
|
|
try {
|
|
final pubKey = ECPublicKey.fromString(publicKey);
|
|
final sig = ECSignature.fromString(signature);
|
|
return pubKey.verifySignature(data, sig);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Encrypt private key with password (for storage/backup)
|
|
static EncryptedKey encryptPrivateKey(String privateKey, String password) {
|
|
final salt = BCrypt.gensalt();
|
|
final key = _deriveKey(password, salt);
|
|
final encrypted = _xorEncrypt(privateKey, key);
|
|
return EncryptedKey(
|
|
encryptedData: encrypted,
|
|
salt: salt,
|
|
);
|
|
}
|
|
|
|
/// Decrypt private key with password
|
|
static String? decryptPrivateKey(
|
|
String encryptedData, String salt, String password) {
|
|
try {
|
|
final key = _deriveKey(password, salt);
|
|
return _xorDecrypt(encryptedData, key);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static String _deriveKey(String password, String salt) {
|
|
final bytes = utf8.encode(password + salt);
|
|
final hash = sha256.convert(bytes);
|
|
return hash.toString();
|
|
}
|
|
|
|
static String _xorEncrypt(String data, String key) {
|
|
final dataBytes = utf8.encode(data);
|
|
final keyBytes = utf8.encode(key);
|
|
final encrypted = <int>[];
|
|
for (var i = 0; i < dataBytes.length; i++) {
|
|
encrypted.add(dataBytes[i] ^ keyBytes[i % keyBytes.length]);
|
|
}
|
|
return base64Encode(encrypted);
|
|
}
|
|
|
|
static String _xorDecrypt(String encrypted, String key) {
|
|
final encryptedBytes = base64Decode(encrypted);
|
|
final keyBytes = utf8.encode(key);
|
|
final decrypted = <int>[];
|
|
for (var i = 0; i < encryptedBytes.length; i++) {
|
|
decrypted.add(encryptedBytes[i] ^ keyBytes[i % keyBytes.length]);
|
|
}
|
|
return utf8.decode(decrypted);
|
|
}
|
|
}
|
|
|
|
class EncryptedKey {
|
|
final String encryptedData;
|
|
final String salt;
|
|
|
|
EncryptedKey({required this.encryptedData, required this.salt});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'encryptedData': encryptedData,
|
|
'salt': salt,
|
|
};
|
|
}
|
|
|
|
class AuthService {
|
|
/// Generate JWT token for user
|
|
static String generateToken(String userId, String username) {
|
|
final payload = {
|
|
'sub': userId,
|
|
'username': username,
|
|
};
|
|
|
|
final jwt = JWT(payload);
|
|
return jwt.sign(
|
|
SecretKey(Config.jwtSecret),
|
|
expiresIn: Config.tokenExpiry,
|
|
);
|
|
}
|
|
|
|
/// Verify JWT token and return payload
|
|
static Map<String, dynamic>? verifyToken(String token) {
|
|
try {
|
|
final jwt = JWT.verify(token, SecretKey(Config.jwtSecret));
|
|
return Map<String, dynamic>.from(jwt.payload as Map);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Hash password with bcrypt
|
|
static String hashPassword(String password) {
|
|
return BCrypt.hashpw(password, BCrypt.gensalt());
|
|
}
|
|
|
|
/// Verify password against hash
|
|
static bool verifyPassword(String password, String hash) {
|
|
return BCrypt.checkpw(password, hash);
|
|
}
|
|
|
|
/// Generate a nonce for signature verification
|
|
static String generateNonce() {
|
|
final bytes = List<int>.generate(32, (_) => DateTime.now().microsecondsSinceEpoch % 256);
|
|
final randomBytes = sha256.convert(bytes as Uint8List);
|
|
return randomBytes.toString().substring(0, 32);
|
|
}
|
|
}
|