Skip to content

Commit a868e77

Browse files
rbenzingclaude
andcommitted
Comprehensive code audit: decompose large files, remove V25Features flags, fix identity key persistence, add DI support
- Decompose GroupSession.cs (1610 lines) into 8 partial class files by region - Decompose DeviceManager.cs (992 lines) into 5 partial class files by region - Decompose LibEmiddleClient.cs (1344 lines) into 8 partial class files by region - Remove all V25Features feature flags and FeatureFlags.cs — all features always-on - Remove IV25FeaturesBuilder, V25FeaturesBuilder, WithV25Features, EnableStableBeta from builder system - Remove enableAdvancedGroupManagement constructor param from GroupSession; advanced management always enabled - Fix LoadOrGenerateIdentityKey to persist/load via CryptoProvider with scoped key IDs (fixes multi-client key collision) - Add ILibEmiddleClient interface and LibEmiddleServiceCollectionExtensions for DI registration - Fix CreateGroupAsync to route through SessionManager.CreateSessionAsync for consistency - Fix SendChatMessageAsync to delegate to SessionManager.GetOrCreateChatSessionAsync - Add SessionManager.GetOrCreateChatSessionAsync to avoid duplicate session creation - SEC-007: Add MessageRecord.SecureWipe() to zero sensitive bytes on history eviction - Always create LibEmiddleDiagnostics (no EnableHealthMonitoring gate) - Remove FeatureFlagsStatus from DiagnosticReport; remove PopulateFeatureFlagsStatus - Add Microsoft.Extensions.DependencyInjection.Abstractions 9.0.4 package reference - Add new test files: StubInfrastructureTests, SessionManagerBundleTests, and others - Clean up v2.5 doc comment labels and Requires V25Features references throughout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ec2b124 commit a868e77

65 files changed

Lines changed: 6484 additions & 4156 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ orleans.codegen.cs
252252

253253
# RIA/Silverlight projects
254254
Generated_Code/
255+
orchestration/
255256

256257
# Backup & report files from converting an old project file
257258
# to a newer Visual Studio version. Backup files are not needed,

CHANGELOG.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11-
- Nothing yet
11+
- **Bundle Caching**: `SessionPersistenceManager` now includes `SaveKeyBundleAsync()` and `LoadKeyBundleByIdentityKeyAsync()` for caching recipient X3DH key bundles to disk, enabling session creation from a bare identity key after the first exchange
12+
- **Password KDF Overload**: New `CryptoProvider.DeriveKeyFromPassword(string password, byte[] salt)` overload accepting a caller-supplied random salt for non-deterministic key derivation
13+
- **Shared Key Conversion Helper**: New `LibEmiddle.Core.KeyConversion.ConvertEd25519PublicKeyToX25519()` consolidating duplicate Ed25519→X25519 conversion logic previously spread across `DeviceManager` and `DeviceLinkingService`
14+
- **Unit Tests**: Added 60+ new unit tests covering `MessageSigning`, `AES` detached encryption, `Nonce` thread-safety, `EnhancedFileStorageProvider`, `PostQuantumCrypto`, stub infrastructure visibility, and `SessionManager` bundle/group session paths
1215

1316
### Changed
14-
- Nothing yet
15-
16-
### Deprecated
17-
- Nothing yet
18-
19-
### Removed
20-
- Nothing yet
17+
- **Password Key Derivation**: Migrated from HKDF with a fixed static salt to **Argon2id** (via libsodium's `crypto_pwhash`) for memory-hard password-based key derivation (64 MB, 2 passes). The deterministic overload uses a fixed application-specific salt; the new overload accepts a random salt
18+
- **CreateSessionAsync Bundle Handling**: Now accepts a serialized `X3DHPublicBundle` as UTF-8 JSON bytes and automatically caches the bundle via `SaveKeyBundleAsync()` for future bare-key lookups
19+
- **GetOrCreateRecipientBundleAsync**: Replaced stub implementation (threw `InvalidOperationException`) with a proper lookup using the new `LoadKeyBundleByIdentityKeyAsync()`. Throws `ArgumentException` with a descriptive message if no cached bundle is found
20+
- **Batch Serialization**: Replaced reflection-based `MakeGenericMethod` in `BatchAsync` with the BCL's `JsonSerializer.Serialize(object?, Type, JsonSerializerOptions?)` overload, eliminating runtime reflection
21+
- **Disposal Guards**: Changed all `bool _disposed` fields to `volatile bool _disposed` across all `IDisposable` classes for correct thread-safe disposal checks
22+
- **Timer Callback**: `EnhancedFileStorageProvider.CleanupExpiredItems` refactored to `async void` + inner `DoCleanupExpiredItemsAsync()` pattern, eliminating the `.Wait()` call that could deadlock on synchronization contexts
2123

2224
### Fixed
23-
- Nothing yet
25+
- **AES-GCM Detached Decryption**: Corrected the P/Invoke signature for `crypto_aead_aes256gcm_decrypt_detached_afternm` in `Sodium.cs` — removed the erroneous `out ulong mlen_p` second parameter that does not exist in this libsodium entry point, which was causing all AES-GCM detached decryption to fail
26+
- **MessageSigning Verification**: Fixed reversed argument order in `Sodium.SignVerifyDetached()` call inside `MessageSigning.VerifyObject<T>()` — signature and message bytes were swapped, causing all signature verification to fail
27+
- **Session File Integrity**: `EncryptAndSaveSessionAsync` now checks the return value of `StoreKeyAsync` and removes the orphaned session file if the encryption key cannot be persisted
2428

2529
### Security
26-
- Nothing yet
30+
- **Argon2id Password Hardening**: The Argon2id KDF (64 MB memory, 2 iterations) is orders of magnitude more resistant to GPU/ASIC brute-force attacks than the previous HKDF-with-fixed-salt approach
2731

2832
## [2.5.1] - 2025-12-22
2933

LibEmiddle.Abstractions/IChatSession.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,21 @@ public interface IChatSession : ISession
9595
/// </summary>
9696
bool IsValid();
9797

98-
// --- v2.5 Enhanced Methods (Optional - requires V25Features.EnableAsyncMessageStreams) ---
98+
// --- Async Stream Methods ---
9999

100100
/// <summary>
101-
/// Gets an async stream of incoming messages (v2.5).
101+
/// Gets an async stream of incoming messages.
102102
/// This runs in parallel with the MessageReceived event.
103-
/// Requires V25Features.EnableAsyncMessageStreams = true.
103+
/// Requires async streaming to be enabled when the session is created.
104104
/// </summary>
105105
/// <param name="cancellationToken">Token to cancel the stream.</param>
106106
/// <returns>Async enumerable of message received events.</returns>
107107
IAsyncEnumerable<MessageReceivedEventArgs> GetMessageStreamAsync(CancellationToken cancellationToken = default);
108108

109109
/// <summary>
110-
/// Gets an async stream of incoming messages with optional filtering (v2.5).
110+
/// Gets an async stream of incoming messages with optional filtering.
111111
/// This runs in parallel with the MessageReceived event.
112-
/// Requires V25Features.EnableAsyncMessageStreams = true.
112+
/// Requires async streaming to be enabled when the session is created.
113113
/// </summary>
114114
/// <param name="messageFilter">Optional filter predicate for messages.</param>
115115
/// <param name="cancellationToken">Token to cancel the stream.</param>
@@ -118,10 +118,11 @@ IAsyncEnumerable<MessageReceivedEventArgs> GetFilteredMessageStreamAsync(
118118
Func<MessageReceivedEventArgs, bool>? messageFilter = null,
119119
CancellationToken cancellationToken = default);
120120

121+
// --- Message Batching Methods ---
122+
121123
/// <summary>
122-
/// Sends a message with optional batching (v2.5).
124+
/// Sends a message with optional batching.
123125
/// If batching is enabled, the message may be queued for later transmission.
124-
/// Requires V25Features.EnableMessageBatching = true.
125126
/// </summary>
126127
/// <param name="message">The plaintext message to send.</param>
127128
/// <param name="priority">Priority level for batching.</param>
@@ -130,15 +131,15 @@ IAsyncEnumerable<MessageReceivedEventArgs> GetFilteredMessageStreamAsync(
130131
Task<bool> SendMessageAsync(string message, MessagePriority priority, bool forceSend = false);
131132

132133
/// <summary>
133-
/// Gets the current message batcher if batching is enabled (v2.5).
134-
/// Requires V25Features.EnableMessageBatching = true.
134+
/// Gets the current message batcher if batching is enabled.
135+
/// Returns null if batching is not configured for this session.
135136
/// </summary>
136137
/// <returns>The message batcher, or null if batching is not enabled.</returns>
137138
IMessageBatcher? GetMessageBatcher();
138139

139140
/// <summary>
140-
/// Forces any pending batched messages to be sent immediately (v2.5).
141-
/// Requires V25Features.EnableMessageBatching = true.
141+
/// Forces any pending batched messages to be sent immediately.
142+
/// Returns 0 if batching is not configured for this session.
142143
/// </summary>
143144
/// <returns>The number of messages flushed.</returns>
144145
Task<int> FlushPendingMessagesAsync();

LibEmiddle.Abstractions/IGroupSession.cs

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -145,26 +145,23 @@ public interface IGroupSession : ISession
145145
/// <exception cref="ArgumentException">Thrown when serializedState is null, empty, or invalid</exception>
146146
Task<bool> RestoreSerializedStateAsync(string serializedState);
147147

148-
// --- v2.5 Enhanced Group Management Methods (Optional - requires V25Features.EnableAdvancedGroupManagement) ---
148+
// --- Enhanced Group Management Methods ---
149149

150150
/// <summary>
151-
/// Gets all members of the group with their roles and permissions (v2.5).
152-
/// Requires V25Features.EnableAdvancedGroupManagement = true.
151+
/// Gets all members of the group with their roles and permissions.
153152
/// </summary>
154153
/// <returns>Collection of group members with enhanced information.</returns>
155154
Task<IReadOnlyCollection<GroupMember>> GetMembersAsync();
156155

157156
/// <summary>
158-
/// Gets a specific member by their public key (v2.5).
159-
/// Requires V25Features.EnableAdvancedGroupManagement = true.
157+
/// Gets a specific member by their public key.
160158
/// </summary>
161159
/// <param name="memberPublicKey">The public key of the member to find.</param>
162160
/// <returns>The group member, or null if not found.</returns>
163161
Task<GroupMember?> GetMemberAsync(byte[] memberPublicKey);
164162

165163
/// <summary>
166-
/// Changes a member's role in the group (v2.5).
167-
/// Requires V25Features.EnableAdvancedGroupManagement = true and appropriate permissions.
164+
/// Changes a member's role in the group.
168165
/// </summary>
169166
/// <param name="memberPublicKey">The public key of the member.</param>
170167
/// <param name="newRole">The new role to assign.</param>
@@ -173,8 +170,7 @@ public interface IGroupSession : ISession
173170
Task<bool> ChangeMemberRoleAsync(byte[] memberPublicKey, MemberRole newRole);
174171

175172
/// <summary>
176-
/// Grants specific permissions to a member (v2.5).
177-
/// Requires V25Features.EnableAdvancedGroupManagement = true and appropriate permissions.
173+
/// Grants specific permissions to a member.
178174
/// </summary>
179175
/// <param name="memberPublicKey">The public key of the member.</param>
180176
/// <param name="permissions">The permissions to grant.</param>
@@ -183,8 +179,7 @@ public interface IGroupSession : ISession
183179
Task<bool> GrantPermissionsAsync(byte[] memberPublicKey, IEnumerable<GroupPermission> permissions);
184180

185181
/// <summary>
186-
/// Revokes specific permissions from a member (v2.5).
187-
/// Requires V25Features.EnableAdvancedGroupManagement = true and appropriate permissions.
182+
/// Revokes specific permissions from a member.
188183
/// </summary>
189184
/// <param name="memberPublicKey">The public key of the member.</param>
190185
/// <param name="permissions">The permissions to revoke.</param>
@@ -193,8 +188,7 @@ public interface IGroupSession : ISession
193188
Task<bool> RevokePermissionsAsync(byte[] memberPublicKey, IEnumerable<GroupPermission> permissions);
194189

195190
/// <summary>
196-
/// Mutes a member for a specified duration (v2.5).
197-
/// Requires V25Features.EnableAdvancedGroupManagement = true and ModerateMembers permission.
191+
/// Mutes a member for a specified duration.
198192
/// </summary>
199193
/// <param name="memberPublicKey">The public key of the member to mute.</param>
200194
/// <param name="duration">How long to mute the member for.</param>
@@ -204,17 +198,15 @@ public interface IGroupSession : ISession
204198
Task<bool> MuteMemberAsync(byte[] memberPublicKey, TimeSpan duration, string? reason = null);
205199

206200
/// <summary>
207-
/// Unmutes a member (v2.5).
208-
/// Requires V25Features.EnableAdvancedGroupManagement = true and ModerateMembers permission.
201+
/// Unmutes a member.
209202
/// </summary>
210203
/// <param name="memberPublicKey">The public key of the member to unmute.</param>
211204
/// <returns>True if the member was unmuted successfully.</returns>
212205
/// <exception cref="UnauthorizedAccessException">Thrown when the current user lacks permission.</exception>
213206
Task<bool> UnmuteMemberAsync(byte[] memberPublicKey);
214207

215208
/// <summary>
216-
/// Sets metadata for a group member (v2.5).
217-
/// Requires V25Features.EnableAdvancedGroupManagement = true and appropriate permissions.
209+
/// Sets metadata for a group member.
218210
/// </summary>
219211
/// <param name="memberPublicKey">The public key of the member.</param>
220212
/// <param name="key">The metadata key.</param>
@@ -224,8 +216,7 @@ public interface IGroupSession : ISession
224216
Task<bool> SetMemberMetadataAsync(byte[] memberPublicKey, string key, string value);
225217

226218
/// <summary>
227-
/// Creates an invitation code for the group (v2.5).
228-
/// Requires V25Features.EnableAdvancedGroupManagement = true and AddMember permission.
219+
/// Creates an invitation code for the group.
229220
/// </summary>
230221
/// <param name="expiresIn">How long the invitation should be valid for.</param>
231222
/// <param name="maxUses">Maximum number of times the invitation can be used (null for unlimited).</param>
@@ -235,26 +226,23 @@ public interface IGroupSession : ISession
235226
Task<string> CreateInvitationAsync(TimeSpan expiresIn, int? maxUses = null, MemberRole defaultRole = MemberRole.Member);
236227

237228
/// <summary>
238-
/// Revokes an invitation code (v2.5).
239-
/// Requires V25Features.EnableAdvancedGroupManagement = true and AddMember permission.
229+
/// Revokes an invitation code.
240230
/// </summary>
241231
/// <param name="invitationCode">The invitation code to revoke.</param>
242232
/// <returns>True if the invitation was revoked successfully.</returns>
243233
/// <exception cref="UnauthorizedAccessException">Thrown when the current user lacks permission.</exception>
244234
Task<bool> RevokeInvitationAsync(string invitationCode);
245235

246236
/// <summary>
247-
/// Joins the group using an invitation code (v2.5).
248-
/// Requires V25Features.EnableAdvancedGroupManagement = true.
237+
/// Joins the group using an invitation code.
249238
/// </summary>
250239
/// <param name="invitationCode">The invitation code to use.</param>
251240
/// <param name="memberPublicKey">The public key of the new member.</param>
252241
/// <returns>True if the member joined successfully.</returns>
253242
Task<bool> JoinWithInvitationAsync(string invitationCode, byte[] memberPublicKey);
254243

255244
/// <summary>
256-
/// Gets group statistics and insights (v2.5).
257-
/// Requires V25Features.EnableAdvancedGroupManagement = true.
245+
/// Gets group statistics and insights.
258246
/// </summary>
259247
/// <returns>Group statistics including member activity, message counts, etc.</returns>
260248
Task<GroupStatistics> GetGroupStatisticsAsync();

LibEmiddle.Domain/Diagnostics/DiagnosticReport.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ public class DiagnosticReport
5858
/// </summary>
5959
public PerformanceStatistics Performance { get; set; } = new();
6060

61-
/// <summary>
62-
/// Feature flags status.
63-
/// </summary>
64-
public FeatureFlagsStatus Features { get; set; } = new();
65-
6661
/// <summary>
6762
/// Security audit information.
6863
/// </summary>
@@ -140,23 +135,6 @@ public class PerformanceStatistics
140135
public TimeSpan Uptime { get; set; }
141136
}
142137

143-
/// <summary>
144-
/// Feature flags status for diagnostic report.
145-
/// </summary>
146-
public class FeatureFlagsStatus
147-
{
148-
public bool AsyncMessageStreams { get; set; }
149-
public bool MessageBatching { get; set; }
150-
public bool AdvancedGroupManagement { get; set; }
151-
public bool HealthMonitoring { get; set; }
152-
public bool FluentBuilder { get; set; }
153-
public bool PluggableStorage { get; set; }
154-
public bool PostQuantumPreparation { get; set; }
155-
public bool WebRTCTransport { get; set; }
156-
public bool ConnectionPooling { get; set; }
157-
public bool SessionBackup { get; set; }
158-
}
159-
160138
/// <summary>
161139
/// Security audit information for diagnostic report.
162140
/// </summary>

LibEmiddle.Domain/FeatureFlags.cs

Lines changed: 0 additions & 121 deletions
This file was deleted.

0 commit comments

Comments
 (0)