1import 'dart:async'; 2import 'dart:typed_data'; 3 4/// Represents a cryptographic key that can sign and verify JWTs. 5/// 6/// This is a placeholder for the Key class from @atproto/jwk. 7/// In the full implementation, this should be imported from the jwk package. 8/// 9/// The Key class contains: 10/// - JWK representation (public and private) 11/// - Supported algorithms 12/// - createJwt() method for signing 13/// - verifyJwt() method for verification 14/// 15/// ## Key Serialization (IMPLEMENTED) 16/// 17/// DPoP keys are fully serialized and persisted in session storage via: 18/// 19/// 1. FlutterKey.toJson() / FlutterKey.privateJwk: 20/// - Serializes the full JWK including private key components 21/// - Used when storing sessions to secure storage 22/// 23/// 2. FlutterKey.fromJwk(Map<String, dynamic> jwk): 24/// - Reconstructs a Key from serialized JWK 25/// - Validates JWK structure and throws on corruption 26/// - Used when restoring sessions from storage 27/// 28/// This ensures DPoP keys persist across app restarts, maintaining 29/// token binding consistency and avoiding unnecessary token refreshes. 30abstract class Key { 31 /// Create a signed JWT with the given header and payload. 32 Future<String> createJwt( 33 Map<String, dynamic> header, 34 Map<String, dynamic> payload, 35 ); 36 37 /// The list of algorithms this key supports. 38 List<String> get algorithms; 39 40 /// The bare JWK (public key components only, for DPoP proofs). 41 /// Returns null for symmetric keys. 42 Map<String, dynamic>? get bareJwk; 43 44 /// The key ID (kid) from the JWK. 45 /// Returns null if the key doesn't have a kid. 46 String? get kid; 47 48 /// The usage of this key ('sign' or 'enc'). 49 String get usage; 50 51 // TODO: Uncomment these when implementing serialization: 52 // Map<String, dynamic> toJson(); 53 // static Key fromJson(Map<String, dynamic> json); 54} 55 56/// Factory function that creates a cryptographic key for the given algorithms. 57/// 58/// The key should support at least one of the provided algorithms. 59/// Algorithms are typically in order of preference. 60/// 61/// Common algorithms: 62/// - ES256, ES384, ES512 (Elliptic Curve) 63/// - ES256K (secp256k1) 64/// - RS256, RS384, RS512 (RSA) 65/// - PS256, PS384, PS512 (RSA-PSS) 66typedef RuntimeKeyFactory = FutureOr<Key> Function(List<String> algs); 67 68/// Generates cryptographically secure random bytes. 69/// 70/// Returns a Uint8List of the specified length filled with random bytes. 71/// Must use a cryptographically secure random number generator. 72typedef RuntimeRandomValues = FutureOr<Uint8List> Function(int length); 73 74/// Digest algorithm specification. 75class DigestAlgorithm { 76 /// The hash algorithm name: 'sha256', 'sha384', or 'sha512'. 77 final String name; 78 79 const DigestAlgorithm({required this.name}); 80 81 const DigestAlgorithm.sha256() : name = 'sha256'; 82 const DigestAlgorithm.sha384() : name = 'sha384'; 83 const DigestAlgorithm.sha512() : name = 'sha512'; 84} 85 86/// Computes a cryptographic hash (digest) of the input data. 87/// 88/// The algorithm specifies which hash function to use (SHA-256, SHA-384, SHA-512). 89/// Returns the hash as a Uint8List. 90typedef RuntimeDigest = 91 FutureOr<Uint8List> Function(Uint8List data, DigestAlgorithm alg); 92 93/// Acquires a lock for the given name and executes the function while holding the lock. 94/// 95/// This ensures that only one execution of the function can run at a time for a given lock name. 96/// This is critical for preventing race conditions during token refresh operations. 97/// 98/// Example: 99/// ```dart 100/// final result = await requestLock('token-refresh', () async { 101/// // Critical section - only one execution at a time 102/// return await refreshToken(); 103/// }); 104/// ``` 105typedef RuntimeLock = 106 Future<T> Function<T>(String name, FutureOr<T> Function() fn); 107 108/// Platform-specific runtime implementation for cryptographic operations. 109/// 110/// This interface defines the core cryptographic primitives needed for OAuth: 111/// - Key generation (createKey) 112/// - Random number generation (getRandomValues) 113/// - Cryptographic hashing (digest) 114/// - Optional locking mechanism (requestLock) 115/// 116/// Implementations must use secure cryptographic libraries: 117/// - For Dart: pointycastle (ECDSA), crypto (SHA hashing) 118/// - Random values must come from dart:math.Random.secure() 119/// 120/// Security considerations: 121/// - Keys must be generated using cryptographically secure randomness 122/// - Private keys must never be logged or exposed 123/// - Hash functions must be collision-resistant (SHA-256 minimum) 124/// - Lock implementation should prevent race conditions in token refresh 125abstract class RuntimeImplementation { 126 /// Creates a cryptographic key that supports at least one of the given algorithms. 127 /// 128 /// The algorithms list is typically sorted by preference, with the most preferred first. 129 /// 130 /// For OAuth DPoP, common algorithm preferences are: 131 /// - ES256K (secp256k1) - preferred for atproto 132 /// - ES256, ES384, ES512 (NIST curves) 133 /// - PS256, PS384, PS512 (RSA-PSS) 134 /// - RS256, RS384, RS512 (RSA-PKCS1) 135 /// 136 /// Throws if no suitable key can be generated for any of the algorithms. 137 RuntimeKeyFactory get createKey; 138 139 /// Generates cryptographically secure random bytes. 140 /// 141 /// MUST use a cryptographically secure random number generator. 142 /// In Dart, use Random.secure() from dart:math. 143 /// 144 /// Never use a regular Random() - this is a security vulnerability. 145 RuntimeRandomValues get getRandomValues; 146 147 /// Computes a cryptographic hash of the input data. 148 /// 149 /// Supported algorithms: SHA-256, SHA-384, SHA-512 150 /// 151 /// Implementation should use the crypto package's sha256, sha384, sha512. 152 RuntimeDigest get digest; 153 154 /// Optional platform-specific lock implementation. 155 /// 156 /// If provided, this will be used to prevent concurrent token refresh operations. 157 /// If not provided, a local (in-memory) lock implementation will be used as fallback. 158 /// 159 /// The lock should be: 160 /// - Re-entrant safe (same isolate can acquire multiple times) 161 /// - Fair (FIFO order) 162 /// - Automatically released on error 163 /// 164 /// For Flutter apps, the default local lock is usually sufficient. 165 /// For multi-process scenarios, you may need a platform-specific implementation. 166 RuntimeLock? get requestLock; 167}