Skip to content

Commit

Permalink
Merge pull request #5 from lukeIam/new
Browse files Browse the repository at this point in the history
New version 0.2.0
  • Loading branch information
lukeIam authored Apr 16, 2018
2 parents 2755a6a + 18a6d65 commit 6fbd168
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 20 deletions.
137 changes: 120 additions & 17 deletions Exporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using KeePass.Resources;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Interfaces;
using KeePassLib.Keys;
using KeePassLib.Security;
Expand Down Expand Up @@ -46,6 +47,7 @@ internal static void Export(PwDatabase sourceDb)
string targetFilePath = settingsEntry.Strings.ReadSafe("SubsetExport_TargetFilePath");
string keyFilePath = settingsEntry.Strings.ReadSafe("SubsetExport_KeyFilePath");
string tag = settingsEntry.Strings.ReadSafe("SubsetExport_Tag");
string keyTransformationRoundsString = settingsEntry.Strings.ReadSafe("SubsetExport_KeyTransformationRounds");

// If a key file is given it must exist.
if (!string.IsNullOrEmpty(keyFilePath) && !File.Exists(keyFilePath))
Expand All @@ -55,6 +57,15 @@ internal static void Export(PwDatabase sourceDb)
continue;
}

// If keyTransformationRounds are given it must be an integer.
ulong keyTransformationRounds = 0;
if (!string.IsNullOrEmpty(keyTransformationRoundsString) && !ulong.TryParse(keyTransformationRoundsString.Trim(), out keyTransformationRounds))
{
MessageService.ShowWarning("SubsetExport: keyTransformationRounds is given but can not be parsed as integer for: " +
settingsEntry.Strings.ReadSafe("Title"), keyFilePath);
continue;
}

// Require at least targetFilePath, tag and at least one of password or keyFilePath.
if (string.IsNullOrEmpty(targetFilePath) || string.IsNullOrEmpty(tag) || (password.IsEmpty && !File.Exists(keyFilePath)))
{
Expand All @@ -66,7 +77,7 @@ internal static void Export(PwDatabase sourceDb)
try
{
// Execute the export
CopyToNewDb(sourceDb, targetFilePath, password, keyFilePath, tag);
CopyToNewDb(sourceDb, targetFilePath, password, keyFilePath, tag, keyTransformationRounds);
}
catch (Exception e)
{
Expand All @@ -82,8 +93,9 @@ internal static void Export(PwDatabase sourceDb)
/// <param name="targetFilePath">The path for the target database.</param>
/// <param name="password">The password to protect the target database(optional if <para>keyFilePath</para> is set).</param>
/// <param name="keyFilePath">The path to a key file to protect the target database (optional if <para>password</para> is set).</param>
/// <param name="tag"></param>
private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, ProtectedString password, string keyFilePath, string tag)
/// <param name="tag">Tag to export.</param>
/// <param name="keyTransformationRounds">The keyTransformationRounds setting for the target database.</param>
private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, ProtectedString password, string keyFilePath, string tag, ulong keyTransformationRounds)
{
// Create a key for the target database
CompositeKey key = new CompositeKey();
Expand Down Expand Up @@ -172,9 +184,19 @@ private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, Prot
targetDatabase.MasterKeyChangeRec = sourceDb.MasterKeyChangeRec;
targetDatabase.Name = sourceDb.Name;
targetDatabase.RecycleBinEnabled = sourceDb.RecycleBinEnabled;

if (keyTransformationRounds == 0)
{
// keyTransformationRounds was not set -> use the one from the source database
keyTransformationRounds = sourceDb.KdfParameters.GetUInt64(AesKdf.ParamRounds, 0);
}

// Set keyTransformationRounds (min PwDefs.DefaultKeyEncryptionRounds)
targetDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, Math.Max(PwDefs.DefaultKeyEncryptionRounds, keyTransformationRounds));

// Copy the root group name
targetDatabase.RootGroup.Name = sourceDb.RootGroup.Name;
// Assign the properties of the source root group to the target root group
targetDatabase.RootGroup.AssignProperties(sourceDb.RootGroup, false, true);
HandleCustomIcon(targetDatabase, sourceDb, sourceDb.RootGroup);

// Find all entries matching the tag
PwObjectList<PwEntry> entries = new PwObjectList<PwEntry>();
Expand All @@ -184,13 +206,16 @@ private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, Prot
foreach (PwEntry entry in entries)
{
// Get or create the target group in the target database (including hierarchy)
PwGroup targetGroup = CreateTargetGroupInDatebase(entry, targetDatabase);
PwGroup targetGroup = CreateTargetGroupInDatebase(entry, targetDatabase, sourceDb);

// Clone entry
PwEntry peNew = new PwEntry(false, false);
peNew.Uuid = entry.Uuid;
peNew.AssignProperties(entry, false, true, true);

// Handle custom icon
HandleCustomIcon(targetDatabase, sourceDb, entry);

// Add entry to the target group in the new database
targetGroup.AddEntry(peNew, true);
}
Expand Down Expand Up @@ -218,16 +243,17 @@ private static void CopyToNewDb(PwDatabase sourceDb, string targetFilePath, Prot
/// </summary>
/// <param name="entry">An entry wich is located in the folder with the target structure.</param>
/// <param name="targetDatabase">The target database in which the folder structure should be created.</param>
/// <param name="sourceDatabase">The source database from which the folder properties should be taken.</param>
/// <returns>The target folder in the target database.</returns>
private static PwGroup CreateTargetGroupInDatebase(PwEntry entry, PwDatabase targetDatabase)
private static PwGroup CreateTargetGroupInDatebase(PwEntry entry, PwDatabase targetDatabase, PwDatabase sourceDatabase)
{
// Collect all group names from the entry up to the root group
PwGroup group = entry.ParentGroup;
List<string> list = new List<string>();
List<PwUuid> list = new List<PwUuid>();

while (group != null)
{
list.Add(group.Name);
list.Add(group.Uuid);
group = group.ParentGroup;
}

Expand All @@ -236,17 +262,94 @@ private static PwGroup CreateTargetGroupInDatebase(PwEntry entry, PwDatabase tar
// groups are in a bottom-up oder -> reverse to get top-down
list.Reverse();

// Create a string representing the folder structure for FindCreateSubTree()
string groupPath = string.Join("/", list.ToArray());

// Find the leaf folder or create it including hierarchical folder structure
PwGroup targetGroup = targetDatabase.RootGroup.FindCreateSubTree(groupPath, new char[]
// Create group structure for the new entry (copying group properties)
PwGroup lastGroup = targetDatabase.RootGroup;
foreach (PwUuid id in list)
{
'/'
});
// Does the target group already exist?
PwGroup newGroup = lastGroup.FindGroup(id, false);
if (newGroup != null)
{
lastGroup = newGroup;
continue;
}

// Get the source group
PwGroup sourceGroup = sourceDatabase.RootGroup.FindGroup(id, true);

// Create a new group and asign all properties from the source group
newGroup = new PwGroup();
newGroup.AssignProperties(sourceGroup, false, true);
HandleCustomIcon(targetDatabase, sourceDatabase, sourceGroup);

// Add the new group at the right position in the target database
lastGroup.AddGroup(newGroup, true);

lastGroup = newGroup;
}

// Return the target folder (leaf folder)
return targetGroup;
return lastGroup;
}

/// <summary>
/// Copies the custom icons required for this group to the target database.
/// </summary>
/// <param name="targetDatabase">The target database where to add the icons.</param>
/// <param name="sourceDatabase">The source database where to get the icons from.</param>
/// <param name="sourceGroup">The source group which icon should be copied (if it is custom).</param>
private static void HandleCustomIcon(PwDatabase targetDatabase, PwDatabase sourceDatabase, PwGroup sourceGroup)
{
// Does the group not use a custom icon or is it already in the target database
if (sourceGroup.CustomIconUuid.Equals(PwUuid.Zero) ||
targetDatabase.GetCustomIconIndex(sourceGroup.CustomIconUuid) != -1)
{
return;
}

// Check if the custom icon really is in the source database
int iconIndex = sourceDatabase.GetCustomIconIndex(sourceGroup.CustomIconUuid);
if (iconIndex < 0 || iconIndex > sourceDatabase.CustomIcons.Count - 1)
{
MessageService.ShowWarning("Can't locate custom icon (" + sourceGroup.CustomIconUuid.ToHexString() +
") for group " + sourceGroup.Name);
}

// Get the custom icon from the source database
PwCustomIcon customIcon = sourceDatabase.CustomIcons[iconIndex];

// Copy the custom icon to the target database
targetDatabase.CustomIcons.Add(customIcon);
}

/// <summary>
/// Copies the custom icons required for this group to the target database.
/// </summary>
/// <param name="targetDatabase">The target database where to add the icons.</param>
/// <param name="sourceDb">The source database where to get the icons from.</param>
/// <param name="entry">The entry which icon should be copied (if it is custom).</param>
private static void HandleCustomIcon(PwDatabase targetDatabase, PwDatabase sourceDb, PwEntry entry)
{
// Does the entry not use a custom icon or is it already in the target database
if (entry.CustomIconUuid.Equals(PwUuid.Zero) ||
targetDatabase.GetCustomIconIndex(entry.CustomIconUuid) != -1)
{
return;
}

// Check if the custom icon really is in the source database
int iconIndex = sourceDb.GetCustomIconIndex(entry.CustomIconUuid);
if (iconIndex < 0 || iconIndex > sourceDb.CustomIcons.Count - 1)
{
MessageService.ShowWarning("Can't locate custom icon (" + entry.CustomIconUuid.ToHexString() +
") for entry " + entry.Strings.ReadSafe("Title"));
}

// Get the custom icon from the source database
PwCustomIcon customIcon = sourceDb.CustomIcons[iconIndex];

// Copy the custom icon to the target database
targetDatabase.CustomIcons.Add(customIcon);
}
}
}
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@

[assembly: Guid("3463A091-5683-487E-843F-47086294C5C1")]

[assembly: AssemblyVersion("0.1.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0.0")]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ If you have more experience with KeePass plugins, I would be very grateful if yo
(optional if `Password` is set)
- add a string field with the name `SubsetExport_TargetFilePath` and set a target database (e.g. `C:\sync\mobile.kdbx`)
- add a string field with the name `SubsetExport_Tag` and set the tag that should be exported (e.g. `MobileSync`)
- add a string field with the name `SubsetExport_KeyTransformationRounds` and set the number of KeyTransformationRounds for the target database (e.g. `10000000`)
(optional - if not set the value of the source database is used)
- Every time the (source) database is saved the target databases will be recreated automatically

![create](https://user-images.githubusercontent.com/5115160/38439682-da51a266-39de-11e8-9cc4-744d5a3f0dae.png)
Expand All @@ -36,3 +38,4 @@ But KeePassSubsetExport has some advantages:
- The folder structure is copied to the target database
- Multiple export jobs are supported
- Key-File protection of the target databases is supported
- KeyTransformationRounds of the target database is set to the number of the source database (can be overwritten)
2 changes: 1 addition & 1 deletion keepass.version
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
:
KeePassSubsetExport:0.1.0
KeePassSubsetExport:0.2.0
:

0 comments on commit 6fbd168

Please sign in to comment.