-
Notifications
You must be signed in to change notification settings - Fork 455
feat: Implement KeeShare support for secure group synchronization #3130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
e3130c5
97311b5
0464230
cab3469
5e7c47f
c8669ba
5fb1158
b86fb7a
60a8e46
e6a7c3d
d4b619d
17cce5c
94e5282
b09e58d
c55eb22
d8148cb
f6e1515
39f54ad
3d3ab7f
cc7cbfb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| using System; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace keepass2android.KeeShare | ||
| { | ||
| /// <summary> | ||
| /// Result of a user's trust decision for an untrusted signer | ||
| /// </summary> | ||
| public enum TrustDecision | ||
| { | ||
| /// <summary>Trust this signer permanently (add to trusted keys)</summary> | ||
| TrustPermanently, | ||
| /// <summary>Trust this signer for this session only</summary> | ||
| TrustOnce, | ||
| /// <summary>Reject this signer (do not import)</summary> | ||
| Reject, | ||
| /// <summary>User cancelled the dialog</summary> | ||
| Cancel | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Information about an untrusted signer presented to the user | ||
| /// </summary> | ||
| public class UntrustedSignerInfo | ||
| { | ||
| /// <summary>Name of the signer from the signature file</summary> | ||
| public string SignerName { get; set; } | ||
|
|
||
| /// <summary>SHA-256 fingerprint of the public key (hex, lowercase)</summary> | ||
| public string KeyFingerprint { get; set; } | ||
|
|
||
| /// <summary>Path to the share file</summary> | ||
| public string SharePath { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Get a formatted fingerprint for display (e.g., "AB:CD:EF:12:...") | ||
| /// </summary> | ||
| public string FormattedFingerprint | ||
| { | ||
| get | ||
| { | ||
| if (string.IsNullOrEmpty(KeyFingerprint) || KeyFingerprint.Length < 2) | ||
| return KeyFingerprint; | ||
|
|
||
| // Format as colon-separated pairs for readability | ||
| var result = new System.Text.StringBuilder(); | ||
| for (int i = 0; i < KeyFingerprint.Length; i += 2) | ||
| { | ||
| if (i > 0) result.Append(':'); | ||
| result.Append(KeyFingerprint.Substring(i, Math.Min(2, KeyFingerprint.Length - i)).ToUpperInvariant()); | ||
| } | ||
| return result.ToString(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Interface for handling user prompts during KeeShare import. | ||
| /// Implement this in the Android UI layer to show dialogs to the user. | ||
| /// </summary> | ||
| public interface IKeeShareUserInteraction | ||
| { | ||
| /// <summary> | ||
| /// Prompt the user to trust an unknown signer. | ||
| /// Called when a share file is signed by a key not in the trusted store. | ||
| /// </summary> | ||
| /// <param name="signerInfo">Information about the untrusted signer</param> | ||
| /// <returns>User's trust decision</returns> | ||
| Task<TrustDecision> PromptTrustDecisionAsync(UntrustedSignerInfo signerInfo); | ||
|
|
||
| /// <summary> | ||
| /// Notify the user that imports were completed. | ||
| /// Called after CheckAndImport finishes processing all shares. | ||
| /// </summary> | ||
| /// <param name="results">List of import results</param> | ||
| void NotifyImportResults(System.Collections.Generic.List<KeeShareImportResult> results); | ||
|
|
||
| /// <summary> | ||
| /// Check if auto-import is enabled in user preferences. | ||
| /// If false, shares will not be imported automatically on database load. | ||
| /// </summary> | ||
| bool IsAutoImportEnabled { get; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Default implementation that rejects all untrusted signers (no UI). | ||
| /// Use this as a fallback when no UI handler is registered. | ||
| /// </summary> | ||
| public class DefaultKeeShareUserInteraction : IKeeShareUserInteraction | ||
| { | ||
| public Task<TrustDecision> PromptTrustDecisionAsync(UntrustedSignerInfo signerInfo) | ||
| { | ||
| // No UI available - reject by default for security | ||
| Kp2aLog.Log($"KeeShare: No UI handler registered. Rejecting untrusted signer '{signerInfo?.SignerName}'"); | ||
| return Task.FromResult(TrustDecision.Reject); | ||
| } | ||
|
|
||
| public void NotifyImportResults(System.Collections.Generic.List<KeeShareImportResult> results) | ||
| { | ||
| // No UI - just log | ||
| if (results == null) return; | ||
| foreach (var result in results) | ||
| { | ||
| if (result.IsSuccess) | ||
| Kp2aLog.Log($"KeeShare: Imported {result.EntriesImported} entries from {result.SharePath}"); | ||
| } | ||
| } | ||
|
|
||
| public bool IsAutoImportEnabled => true; // Default to enabled for backward compatibility | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using KeePassLib; | ||
| using keepass2android.KeeShare; | ||
|
|
||
| namespace keepass2android.KeeShare | ||
| { | ||
| public static class KeeShareAuditLog | ||
| { | ||
| public enum AuditAction | ||
| { | ||
| ImportSuccess, | ||
| ImportFailure, | ||
| ExportSuccess, | ||
| ExportFailure, | ||
| TrustDecision, | ||
| SignatureVerified, | ||
| SignatureRejected | ||
| } | ||
|
|
||
| public class AuditEntry | ||
| { | ||
| public DateTime Timestamp { get; set; } | ||
| public AuditAction Action { get; set; } | ||
| public string SourcePath { get; set; } | ||
| public string Details { get; set; } | ||
| public string Fingerprint { get; set; } | ||
| } | ||
|
|
||
| private static List<AuditEntry> _entries = new List<AuditEntry>(); | ||
|
|
||
| public static void Log(AuditAction action, string path, string details, string fingerprint = null) | ||
| { | ||
| var entry = new AuditEntry | ||
| { | ||
| Timestamp = DateTime.UtcNow, | ||
| Action = action, | ||
| SourcePath = path, | ||
| Details = details, | ||
| Fingerprint = fingerprint | ||
| }; | ||
|
|
||
| lock (_entries) | ||
| { | ||
| _entries.Add(entry); | ||
| if (_entries.Count > 1000) | ||
| _entries.RemoveAt(0); // Keep last 1000 | ||
| } | ||
|
|
||
| // Also log to system log for now | ||
| Kp2aLog.Log($"[KeeShare Audit] {action}: {path} - {details}"); | ||
|
||
| } | ||
|
|
||
| public static List<AuditEntry> GetEntries() | ||
| { | ||
| lock (_entries) | ||
| { | ||
| return new List<AuditEntry>(_entries); | ||
| } | ||
| } | ||
|
|
||
| public static AuditEntry GetLastEntryForPath(string path) | ||
| { | ||
| if (string.IsNullOrEmpty(path)) return null; | ||
|
|
||
| lock (_entries) | ||
| { | ||
| // Traverse backwards to find latest | ||
| for (int i = _entries.Count - 1; i >= 0; i--) | ||
| { | ||
| if (_entries[i].SourcePath == path) | ||
| return _entries[i]; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use IOConnectionInfo for path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! Method signature changed to accept IOConnectionInfo. Commit: 60a8e46