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