Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e3130c5
Implement KeeShare support for group synchronization
natinew77-creator Dec 25, 2025
97311b5
feat(KeeShare): Add export, Android UI, and sync integration
natinew77-creator Dec 26, 2025
0464230
feat(KeeShare): Add device-specific paths and export-on-save
natinew77-creator Dec 26, 2025
cab3469
test(KeeShare): Add unit tests for device-specific path resolution
natinew77-creator Dec 26, 2025
5e7c47f
feat(KeeShare): Phase 5 - Material UI, Background Sync, Audit Log
natinew77-creator Dec 26, 2025
c8669ba
feat(KeeShare): Add Clear Trusted Signers menu option
natinew77-creator Dec 26, 2025
5fb1158
feat(KeeShare): Phase 6 - Perfection (Read-Only logic & Rich Status UI)
natinew77-creator Dec 26, 2025
b86fb7a
fix: Migrate to IOConnectionInfo for security (Phase 1 - WIP)
natinew77-creator Dec 27, 2025
60a8e46
fix: Complete IOConnectionInfo migration for all path handling
natinew77-creator Dec 27, 2025
e6a7c3d
fix: Move KeeShare export to background task (maintainer feedback)
natinew77-creator Dec 27, 2025
d4b619d
fix: Remove unused GenerateKeyPair and ComputeKeyFingerprint methods …
natinew77-creator Dec 27, 2025
17cce5c
fix: Use SelectStorageLocationActivity for file picker and confirm Ke…
natinew77-creator Dec 27, 2025
94e5282
fix: Critical compilation errors - missing using statements and broke…
natinew77-creator Dec 27, 2025
b09e58d
fix: CRITICAL - Add missing return statement in CountEntries method
natinew77-creator Dec 27, 2025
c55eb22
refactor: Extract magic strings to constants for maintainability
natinew77-creator Dec 27, 2025
d8148cb
fix: Add KeeShareSyncJobService declaration to all AndroidManifest files
natinew77-creator Dec 27, 2025
f6e1515
chore: Add .DS_Store to .gitignore
natinew77-creator Dec 27, 2025
39f54ad
Refine KeeShare implementation: refactor background sync to Operation…
natinew77-creator Jan 6, 2026
3d3ab7f
Refactor: Propagate StatusLogger in KeeShareSyncOperation
natinew77-creator Jan 6, 2026
cc7cbfb
FIX: Eradicate SaveAs() and use IFileStorage per maintainer feedback
natinew77-creator Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/Kp2aBusinessLogic/KeeShare/IKeeShareUserInteraction.cs
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
}
}
78 changes: 78 additions & 0 deletions src/Kp2aBusinessLogic/KeeShare/KeeShareAuditLog.cs
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)
Copy link
Owner

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

Copy link
Author

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

{
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}");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only log display name of path as the full path might contain sensitive information

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Uses displayName = ioc?.GetDisplayName() only. Commit: 60a8e46

}

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;
}
}
}
Loading