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/// ## 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}