Main coves client
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}