From 4095cebedf9fb83ba80dff0400109b2e2b5d6e08 Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Tue, 22 Aug 2023 15:05:32 +0100 Subject: [PATCH 1/8] removing unnecessary config for compiler in jackson POM --- jackson/pom.xml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/jackson/pom.xml b/jackson/pom.xml index f6c2027b..6f721427 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -55,19 +55,4 @@ test - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${java.version} - ${java.version} - src/generated/java - - - - From eadb0b0607824bce17513185222fb879577c719c Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Wed, 23 Aug 2023 17:00:54 +0100 Subject: [PATCH 2/8] Tweaks to POMs --- .../org/linguafranca/pwdb/package-info.java | 9 +++++++++ jackson/pom.xml | 20 +++++-------------- pom.xml | 4 ++-- 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 all/src/main/java/org/linguafranca/pwdb/package-info.java diff --git a/all/src/main/java/org/linguafranca/pwdb/package-info.java b/all/src/main/java/org/linguafranca/pwdb/package-info.java new file mode 100644 index 00000000..a544df96 --- /dev/null +++ b/all/src/main/java/org/linguafranca/pwdb/package-info.java @@ -0,0 +1,9 @@ +/** + * This module provides a simple import of the database implementations of KeePass, "all" in the diagram below. + *

+ * + *

+ * @see Module Structure + * in the readme at GitHub for a discussion of the project modules and links to JavaDocs. + */ +package org.linguafranca.pwdb; \ No newline at end of file diff --git a/jackson/pom.xml b/jackson/pom.xml index 6f721427..1e63cf51 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -33,26 +33,16 @@ KeePassJava2-kdbx ${project.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + org.linguafranca.pwdb test ${project.version} test - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.0 - - - com.fasterxml.woodstox - woodstox-core - 6.5.0 - - - junit - junit - test - diff --git a/pom.xml b/pom.xml index f01f78b5..b6f03e3a 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.0.2 + 3.6.0 @@ -182,7 +182,7 @@ org.bouncycastle bcpkix-jdk18on - 1.74 + 1.76 org.slf4j From bc38b85e1f0e613104e2ae93223cbd4032d4296b Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Thu, 24 Aug 2023 15:11:36 +0100 Subject: [PATCH 3/8] Correct name --- .../converter/{ValueDeserialized.java => ValueDeserializer.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/{ValueDeserialized.java => ValueDeserializer.java} (100%) diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserialized.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java similarity index 100% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserialized.java rename to jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java From e2d74647fa279d5e17d9a1d599c2433718fbe772 Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Thu, 24 Aug 2023 15:13:00 +0100 Subject: [PATCH 4/8] Correct corruption of database by ValueSerializer #50 --- .../jackson/converter/ValueSerializer.java | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java index 640f5db8..4d0207d2 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java @@ -30,15 +30,7 @@ public class ValueSerializer extends StdSerializer{ - private StreamEncryptor encryptor; - - public ValueSerializer() { - super(ValueSerializer.class, false); - } - - public ValueSerializer(Class v) { - super(v); - } + private final StreamEncryptor encryptor; public ValueSerializer(StreamEncryptor encryptor) { super(ValueSerializer.class, false); @@ -52,8 +44,9 @@ public void serialize(Value value, JsonGenerator gen, SerializerProvider provide final ToXmlGenerator xmlGenerator = (ToXmlGenerator) gen; xmlGenerator.writeStartObject(); + String stringToWrite = value.getText(); //We need to encrypt and convert to base64 every protected element - if(value.getProtectOnOutput()) { + if (value.getProtectOnOutput()) { xmlGenerator.setNextIsAttribute(true); gen.writeStringField("Protected", "True"); String plain = value.getText(); @@ -63,17 +56,12 @@ public void serialize(Value value, JsonGenerator gen, SerializerProvider provide //Cipher byte[] encrypted = encryptor.encrypt(plain.getBytes()); //Convert to base64 - String base64 = new String(Base64.encodeBase64(encrypted)); - - //Destroy from memory the plain value - plain = null; - value.setText(base64); - + stringToWrite = new String(Base64.encodeBase64(encrypted)); } xmlGenerator.setNextIsAttribute(false); xmlGenerator.setNextIsUnwrapped(true); - xmlGenerator.writeStringField("text",value.getText()); + xmlGenerator.writeStringField("text", stringToWrite); gen.writeEndObject(); From 7cb44889c7ab76d1a6a5b318bc24f05236224d1b Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Thu, 24 Aug 2023 15:14:36 +0100 Subject: [PATCH 5/8] Correct protection of default fields #51 and tidy --- .../jackson/JacksonSerializableDatabase.java | 84 ++++++++++++++++--- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java index d9dd388a..2ecf4f58 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java @@ -19,14 +19,19 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; +import com.fasterxml.jackson.databind.MapperFeature; +import org.jetbrains.annotations.NotNull; +import org.linguafranca.pwdb.Entry; import org.linguafranca.pwdb.SerializableDatabase; import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.jackson.converter.ValueDeserialized; +import org.linguafranca.pwdb.kdbx.jackson.converter.ValueDeserializer; import org.linguafranca.pwdb.kdbx.jackson.converter.ValueSerializer; import org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses; import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile; @@ -41,6 +46,8 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_TITLE; + public class JacksonSerializableDatabase implements SerializableDatabase { public KeePassFile keePassFile; @@ -67,7 +74,7 @@ public JacksonSerializableDatabase(KeePassFile keePassFile) { public JacksonSerializableDatabase load(InputStream inputStream) throws IOException { XmlMapper mapper = new XmlMapper(); SimpleModule module = new SimpleModule(); - module.addDeserializer(EntryClasses.StringProperty.Value.class, new ValueDeserialized(encryptor)); + module.addDeserializer(EntryClasses.StringProperty.Value.class, new ValueDeserializer(encryptor)); mapper.registerModule(module); keePassFile = mapper.readValue(inputStream, KeePassFile.class); return this; @@ -76,35 +83,88 @@ public JacksonSerializableDatabase load(InputStream inputStream) throws IOExcept @Override public void save(OutputStream outputStream) throws IOException { - + prepareForSave(keePassFile.root.group); try { - - XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); - XmlMapper mapper = new XmlMapper(); + SimpleModule module = new SimpleModule(); module.addSerializer(EntryClasses.StringProperty.Value.class, new ValueSerializer(encryptor)); + // disable auto-detection, only use annotated values + XmlMapper mapper = XmlMapper.builder() + .disable(MapperFeature.AUTO_DETECT_CREATORS, + MapperFeature.AUTO_DETECT_FIELDS, + MapperFeature.AUTO_DETECT_GETTERS, + MapperFeature.AUTO_DETECT_SETTERS, + MapperFeature.AUTO_DETECT_IS_GETTERS) + .build(); mapper.registerModule(module); mapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION); mapper.enable(SerializationFeature.INDENT_OUTPUT); + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + + // set the serializer to Woodstox + System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); + XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); xmlOutputFactory.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true); xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false); xmlOutputFactory.setProperty(WstxInputProperties.P_RETURN_NULL_FOR_DEFAULT_NAMESPACE, true); OutputStreamWriter osw = new OutputStreamWriter(outputStream); XMLStreamWriter sw = xmlOutputFactory.createXMLStreamWriter(osw); - sw.setPrefix("xml", "http://www.w3.org/XML/1998/namespace"); + try { + sw.setPrefix("xml", "http://www.w3.org/XML/1998/namespace"); - mapper.writeValue(sw, keePassFile); - - sw.writeEndDocument(); - sw.close(); + mapper.writeValue(sw, keePassFile); + + sw.writeEndDocument(); + } finally { + sw.close(); + osw.close(); + } } catch(Exception e) { throw new IllegalStateException(e); } } - + /** + * Create a list of names of properties that should be encrypted by default + */ + @NotNull + private List getToEncrypt() { + final List toEncrypt = new ArrayList<>(); + for (String propertyName: Entry.STANDARD_PROPERTY_NAMES) { + if (keePassFile.meta.memoryProtection.shouldProtect(propertyName)) { + toEncrypt.add(propertyName); + } + } + return toEncrypt; + } + + /** + * Utility to mark fields that need to be encrypted and vice versa + * + * @param parent the group to start from + */ + private static void prepareForSave(JacksonGroup parent){ + for (JacksonGroup group: parent.groups) { + prepareForSave(group); + } + for (JacksonEntry entry: parent.entries) { + for (EntryClasses.StringProperty property : entry.string) { + boolean shouldProtect = parent.database.shouldProtect(property.getKey()); + property.getValue().setProtectOnOutput(shouldProtect || property.getValue().getProtectOnOutput()); + } + if (Objects.nonNull(entry.history)) { + for (JacksonEntry entry2 : entry.history.getEntry()) { + for (EntryClasses.StringProperty property : entry2.string) { + boolean shouldProtect = parent.database.shouldProtect(property.getKey()); + property.getValue().setProtectOnOutput(shouldProtect || property.getValue().getProtectOnOutput()); + } + } + } + } + } + @Override public byte[] getHeaderHash() { return keePassFile.meta.headerHash; From 4a007f3e6cd14a2d826e719210c77f7a2de8cf7f Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Thu, 24 Aug 2023 15:16:41 +0100 Subject: [PATCH 6/8] Various tidies --- .../pwdb/kdbx/validation/Issue33Test.java | 7 +++++++ jackson/pom.xml | 5 +++++ .../linguafranca/pwdb/kdbx/jackson/JacksonEntry.java | 6 +++--- .../linguafranca/pwdb/kdbx/jackson/JacksonGroup.java | 11 ++++++----- .../pwdb/kdbx/jackson/JacksonHistory.java | 4 ++-- .../kdbx/jackson/converter/ValueDeserializer.java | 12 ++++++------ .../pwdb/kdbx/jackson/model/EntryClasses.java | 2 +- .../pwdb/kdbx/jaxb/JaxbSerializableDatabase.java | 2 +- 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue33Test.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue33Test.java index 09486d82..322ee10a 100644 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue33Test.java +++ b/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue33Test.java @@ -10,6 +10,7 @@ import org.linguafranca.pwdb.kdbx.KdbxCreds; import org.linguafranca.pwdb.kdbx.Util; import org.linguafranca.pwdb.kdbx.dom.DomDatabaseWrapper; +import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; import org.linguafranca.pwdb.kdbx.jaxb.JaxbDatabase; import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase; @@ -59,4 +60,10 @@ public void testSimpleDatabase() throws IOException { SimpleDatabase database = SimpleDatabase.load(CREDENTIALS, inputStream); database.save(new StreamFormat.None(), new Credentials.None(), Files.newOutputStream(Paths.get(TEST_OUTPUT_DIR, "Issue33Simple.xml"))); } + + @Test + public void testJacksonDatabase() throws IOException { + JacksonDatabase database = JacksonDatabase.load(CREDENTIALS, inputStream); + database.save(new StreamFormat.None(), new Credentials.None(), Files.newOutputStream(Paths.get(TEST_OUTPUT_DIR, "Issue33Jackson.xml"))); + } } diff --git a/jackson/pom.xml b/jackson/pom.xml index 1e63cf51..0f1d8021 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -38,6 +38,11 @@ jackson-dataformat-xml 2.15.0 + + com.fasterxml.woodstox + woodstox-core + 6.5.0 + org.linguafranca.pwdb test diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java index c3110e0e..92bee42e 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java @@ -94,18 +94,18 @@ public class JacksonEntry extends AbstractEntry string; - @JacksonXmlProperty(localName = "Binary") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "Binary") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) protected List binary; @JacksonXmlProperty(localName = "AutoType") protected AutoType autoType; - @JacksonXmlProperty(localName = "History") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "History") /* Workaround jackson */ protected JacksonHistory history; @JsonIgnore diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java index c7a9dc43..436d66d5 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java @@ -21,7 +21,9 @@ import java.util.List; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import org.jetbrains.annotations.NotNull; +import org.linguafranca.pwdb.base.AbstractGroup; import org.linguafranca.pwdb.kdbx.jackson.converter.StringToBooleanConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.UUIDToBase64Converter; import org.linguafranca.pwdb.kdbx.jackson.converter.Base64ToUUIDConverter; @@ -51,9 +53,8 @@ "entry", "group", }) -@JsonIgnoreProperties(ignoreUnknown = true) -public class JacksonGroup - extends org.linguafranca.pwdb.base.AbstractGroup { +@JsonIgnoreProperties(ignoreUnknown=true) +public class JacksonGroup extends AbstractGroup { @JacksonXmlProperty(localName = "UUID") @JsonDeserialize(converter = Base64ToUUIDConverter.class) @@ -100,12 +101,12 @@ public class JacksonGroup @JsonSerialize(converter = UUIDToBase64Converter.class) protected UUID lastTopVisibleEntry; - @JacksonXmlProperty(localName = "Entry") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "Entry") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) protected List entries; - @JacksonXmlProperty(localName = "Group") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "Group") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) protected List groups; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java index 05ba3537..c9ea238c 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java @@ -23,12 +23,12 @@ public class JacksonHistory { - @JacksonXmlProperty(localName = "Entry") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "Entry") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) private List entry; public JacksonHistory() { - entry = new ArrayList(); + entry = new ArrayList<>(); } /** diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java index e448589e..8f7fda9b 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java @@ -29,20 +29,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -public class ValueDeserialized extends StdDeserializer { +public class ValueDeserializer extends StdDeserializer { private StreamEncryptor encryptor; - public ValueDeserialized() { - super(ValueDeserialized.class); + public ValueDeserializer() { + super(ValueDeserializer.class); } - public ValueDeserialized(Class v) { + public ValueDeserializer(Class v) { super(v); } - public ValueDeserialized(StreamEncryptor encryptor) { - super(ValueDeserialized.class); + public ValueDeserializer(StreamEncryptor encryptor) { + super(ValueDeserializer.class); this.encryptor = encryptor; } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java index 553203de..05b9c441 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java @@ -212,7 +212,7 @@ public void setRef(String ref) { public static class History { - @JacksonXmlProperty(localName = "Entry") /** Workaround jackson **/ + @JacksonXmlProperty(localName = "Entry") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) private List list; diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabase.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabase.java index b9a0f034..188af88b 100644 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabase.java +++ b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabase.java @@ -186,7 +186,7 @@ public void afterMarshal(Object source) { } /** - * Create a list of names of peroperties that should be encrypted by default + * Create a list of names of properties that should be encrypted by default */ @NotNull private List getToEncrypt() { From 324da07071b172d0e3d1b476acd03a3fe96d0c65 Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Fri, 25 Aug 2023 16:16:20 +0100 Subject: [PATCH 7/8] Upate ChangeLog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c87262d1..a791c2db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,13 @@ Trying to follow the suggestions at [Keep a Change Log](http://keepachangelog.com) and [Semantic Versioning](http://semver.org/spec/v2.0.0.html) -##[2.2.2] +## [2.2.2-SNAPSHOT] ### Added - implementation of database using Jackson -##[2.2.1] +## [2.2.1] 2023-08-21 ### Added From 78fa7a0ad00dd3a80af67fcc512381c87b0240a1 Mon Sep 17 00:00:00 2001 From: Giuseppe Valente Date: Sat, 26 Aug 2023 13:16:00 +0200 Subject: [PATCH 8/8] #28 Added required methods getPsswordAsBytes and getPropertyAsBytes --- CHANGELOG.md | 14 ++ all/pom.xml | 2 +- database/pom.xml | 2 +- .../java/org/linguafranca/pwdb/Entry.java | 25 +++- .../linguafranca/pwdb/base/AbstractEntry.java | 6 + dom/pom.xml | 2 +- .../pwdb/kdbx/dom/DomEntryWrapper.java | 5 + example/pom.xml | 2 +- jackson/pom.xml | 2 +- .../pwdb/kdbx/jackson/JacksonEntry.java | 5 + .../pwdb/kdbx/jackson/model/EntryClasses.java | 4 + jaxb/pom.xml | 2 +- .../pwdb/kdbx/jaxb/JaxbEntry.java | 10 ++ .../pwdb/kdbx/jaxb/base/ValueBinding.java | 4 + kdb/pom.xml | 2 +- .../org/linguafranca/pwdb/kdb/KdbEntry.java | 12 ++ kdbx/pom.xml | 2 +- pom.xml | 2 +- readme.md | 2 +- simple/pom.xml | 2 +- .../pwdb/kdbx/simple/SimpleEntry.java | 9 ++ test/pom.xml | 2 +- .../pwdb/checks/BasicDatabaseChecks.java | 104 ++++++++++++++ .../pwdb/checks/DatabaseLoaderChecks.java | 58 ++++++++ .../pwdb/checks/SaveAndReloadChecks.java | 133 ++++++++++++++++++ 25 files changed, 400 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a791c2db..931750dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ Trying to follow the suggestions at [Keep a Change Log](http://keepachangelog.com) and [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +## [2.2.3-SNAPSHOT] + +### Changed + +- Deprecated method Entry.getPassword(): This means that the method getPassword() in the Entry class has been marked as deprecated. Deprecated methods are no longer recommended for use and might be removed in future versions of the code. + +### Added + +- byte[] getPasswordAsBytes() to AbstractEntry class: This means that a new method getPasswordAsBytes() has been added to the AbstractEntry class. This method likely returns the password in the form of a byte array instead of a plain string. + +- byte[] getPropertyAsBytes(String name) to the Entry interface: This indicates that a new method getPropertyAsBytes(String name) has been added to the Entry interface. This method likely returns a specific property as a byte array. + +- Added test cases to ensure the byte password is used correctly: This means that new test cases have been added to the codebase to ensure that the handling of byte passwords is functioning correctly. These tests will help verify that the newly introduced methods are working as expected. + ## [2.2.2-SNAPSHOT] ### Added diff --git a/all/pom.xml b/all/pom.xml index 892f7056..a0748435 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -19,7 +19,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml diff --git a/database/pom.xml b/database/pom.xml index 67b6015d..002d10a0 100644 --- a/database/pom.xml +++ b/database/pom.xml @@ -3,7 +3,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/database/src/main/java/org/linguafranca/pwdb/Entry.java b/database/src/main/java/org/linguafranca/pwdb/Entry.java index 4780a0ff..fc7d0cf7 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Entry.java +++ b/database/src/main/java/org/linguafranca/pwdb/Entry.java @@ -118,6 +118,17 @@ interface Matcher { */ void setProperty(String name, String value); + /** + * Gets the value of a property. + * + *

All implementations of Entry are required to support reading and writing of + * {@link #STANDARD_PROPERTY_NAMES}. + * @param name the name of the property to get + * @return a value or null if the property is not known, or if setting of arbitrary properties is not supported + * @see Database#supportsNonStandardPropertyNames() + */ + + byte[] getPropertyAsBytes(String name); /** * Removes this non-standard property, if it exists. * @@ -219,11 +230,23 @@ interface Matcher { * Gets the (unencrypted) password field for this entry. * *

Implementations should Touch LastAccessedTime when this method is called. - * + * + * @deprecated because the string is immutable, and cannot be wiped from memory + * * @return a password */ + @Deprecated String getPassword(); + /** + * Gets the (unencrypted) password field for this entry. + * + *

Implementations should Touch LastAccessedTime when this method is called. + * + * @return a password + */ + public byte[] getPasswordAsBytes(); + /** * Sets the plaintext password for this Entry. * diff --git a/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java b/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java index af5614fc..26d69ff3 100644 --- a/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java +++ b/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java @@ -84,10 +84,16 @@ public void setUsername(String username) { } @Override + @Deprecated public String getPassword() { return getProperty(STANDARD_PROPERTY_NAME_PASSWORD); } + @Override + public byte[] getPasswordAsBytes() { + return getPropertyAsBytes(STANDARD_PROPERTY_NAME_PASSWORD); + } + @Override public void setPassword(String pass) { setProperty(STANDARD_PROPERTY_NAME_PASSWORD, pass); diff --git a/dom/pom.xml b/dom/pom.xml index f3ccfe79..674166f4 100644 --- a/dom/pom.xml +++ b/dom/pom.xml @@ -3,7 +3,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomEntryWrapper.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomEntryWrapper.java index 39575301..a97f61f8 100644 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomEntryWrapper.java +++ b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomEntryWrapper.java @@ -79,6 +79,11 @@ public void setProperty(String name, String value) { database.setDirty(true); } + @Override + public byte[] getPropertyAsBytes(String name) { + return getProperty(name).getBytes(); + } + @Override public boolean removeProperty(String name) throws IllegalArgumentException { if (STANDARD_PROPERTY_NAMES.contains(name)) throw new IllegalArgumentException("may not remove property: " + name); diff --git a/example/pom.xml b/example/pom.xml index bc807577..cbf75d8b 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jackson/pom.xml b/jackson/pom.xml index 0f1d8021..63415651 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -18,7 +18,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java index 92bee42e..64c845ef 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java @@ -316,4 +316,9 @@ protected void touch() { this.times.setLastModificationTime(new Date()); this.database.setDirty(true); } + + @Override + public byte[] getPropertyAsBytes(String name) { + return getByteContent(getStringProperty(name, string)); + } } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java index 05b9c441..b1963dd3 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java +++ b/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java @@ -44,6 +44,10 @@ public static String getStringContent(StringProperty property) { return property == null || property.value == null ? null : property.value.text; } + public static byte[] getByteContent(StringProperty property) { + return property == null || property.value == null ? null : property.value.text.getBytes(); + } + public static BinaryProperty getBinaryProp(String name, List binary) { for (BinaryProperty property : binary) { if (property.key.equals(name)) { diff --git a/jaxb/pom.xml b/jaxb/pom.xml index fbe8e56a..1493f87d 100644 --- a/jaxb/pom.xml +++ b/jaxb/pom.xml @@ -19,7 +19,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbEntry.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbEntry.java index 1eafe10d..d9601c44 100644 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbEntry.java +++ b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbEntry.java @@ -280,4 +280,14 @@ protected void touch() { database.setDirty(true); delegate.getTimes().setLastModificationTime(new Date()); } + + @Override + public byte[] getPropertyAsBytes(String name) { + for (StringField field: delegate.getString()){ + if (field.getKey().equals(name)){ + return field.getValue().getValueAsByte(); + } + } + return null; + } } diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/ValueBinding.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/ValueBinding.java index a89fc2a2..b9d75705 100644 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/ValueBinding.java +++ b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/ValueBinding.java @@ -22,6 +22,10 @@ public String getValue(){ return value; } + public byte[] getValueAsByte() { + return value.getBytes(); + } + public void setValue(String string){ value = string; } diff --git a/kdb/pom.xml b/kdb/pom.xml index 611a3231..9db0908e 100644 --- a/kdb/pom.xml +++ b/kdb/pom.xml @@ -3,7 +3,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java index ac9927b7..16642466 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java @@ -250,4 +250,16 @@ public boolean getExpires() { protected void touch() { lastModificationTime = new Date(); } + + @Override + public byte[] getPropertyAsBytes(String name) { + switch (name) { + case STANDARD_PROPERTY_NAME_USER_NAME: return getUsername().getBytes(); + case STANDARD_PROPERTY_NAME_PASSWORD: return getPassword().getBytes(); + case STANDARD_PROPERTY_NAME_URL: return getUrl().getBytes(); + case STANDARD_PROPERTY_NAME_TITLE: return getTitle().getBytes(); + case STANDARD_PROPERTY_NAME_NOTES: return getNotes().getBytes(); + default: return null; + } + } } diff --git a/kdbx/pom.xml b/kdbx/pom.xml index c642ea8d..f1972337 100644 --- a/kdbx/pom.xml +++ b/kdbx/pom.xml @@ -19,7 +19,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index b6f03e3a..ebfb082b 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.linguafranca.pwdb KeePassJava2-parent - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT database test diff --git a/readme.md b/readme.md index d7a208b0..ab96cb98 100644 --- a/readme.md +++ b/readme.md @@ -270,7 +270,7 @@ bindings might be useful for building other interfaces. A DOM based implementation of KDBX. Being DOM based it is rather slow, but messes less with existing content than the other two implementations. Known to work on Android. -domKeePassJava2-jackson +jacksonKeePassJava2-jackson Javadocs A Jackson based implementation of KDBX. Intended to replace the Simple XML implementation. Simple XML seems no longer to be maintained. diff --git a/simple/pom.xml b/simple/pom.xml index 4fbe068d..d7d120ca 100644 --- a/simple/pom.xml +++ b/simple/pom.xml @@ -19,7 +19,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT ../pom.xml 4.0.0 diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleEntry.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleEntry.java index 93f4f105..ae23d178 100644 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleEntry.java +++ b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleEntry.java @@ -267,4 +267,13 @@ protected void touch() { this.times.setLastModificationTime(new Date()); this.database.setDirty(true); } + + @Override + public byte[] getPropertyAsBytes(String name) { + if(name != null) { + return getStringContent(getStringProperty(name, string)).getBytes(); + } + return null; + + } } \ No newline at end of file diff --git a/test/pom.xml b/test/pom.xml index 47961c4c..529ff049 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -3,7 +3,7 @@ KeePassJava2-parent org.linguafranca.pwdb - 2.2.2-SNAPSHOT + 2.2.3-SNAPSHOT 4.0.0 diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java index dd0d06b5..0bdf2479 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java +++ b/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -167,6 +168,60 @@ public void testSetFields () { assertEquals("Entry 2", e1.getPath()); } + @Test + public void testSetFieldsPasswordAsBytes() { + E e1 = database.newEntry("Entry 1"); + e1.setNotes("this looks a little like Entry 2"); + assertEquals("this looks a little like Entry 2", e1.getNotes()); + e1.setUsername("jake@window.com"); + assertEquals("jake@window.com", e1.getUsername()); + e1.setPassword("supercalifragelisticexpialidocious"); + assertEquals("supercalifragelisticexpialidocious", new String(e1.getPasswordAsBytes())); + e1.setUrl("https://window.com"); + assertEquals("https://window.com", e1.getUrl()); + + + Assert.assertTrue(e1.match("2")); + Assert.assertTrue(e1.matchTitle("1")); + Assert.assertFalse(e1.matchTitle("doggy")); + + I ic1 = database.newIcon(27); + e1.setIcon(ic1); + assertEquals(e1.getIcon(), ic1); + + // databases have to support setting of standard properties + e1.setProperty(Entry.STANDARD_PROPERTY_NAME_TITLE, "A title"); + assertEquals("A title", e1.getTitle()); + e1.setProperty(Entry.STANDARD_PROPERTY_NAME_USER_NAME, "username"); + assertEquals("username", e1.getUsername()); + e1.setProperty(Entry.STANDARD_PROPERTY_NAME_NOTES, "notes"); + assertEquals("notes", e1.getNotes()); + e1.setProperty(Entry.STANDARD_PROPERTY_NAME_PASSWORD, "password"); + assertEquals("password", new String(e1.getPasswordAsBytes())); + e1.setProperty(Entry.STANDARD_PROPERTY_NAME_URL, "url"); + assertEquals("url", e1.getUrl()); + + try { + e1.setProperty("silly", "hello"); + assertEquals("hello", e1.getProperty("silly")); + List properties = new ArrayList<>(Entry.STANDARD_PROPERTY_NAMES); + properties.add("silly"); + // remove all properties to show that getProperties returns all the values we want + properties.removeAll(e1.getPropertyNames()); + Assert.assertEquals(0, properties.size()); + } catch (UnsupportedOperationException e) { + // databases don't have to support arbitrary properties + assertFalse(database.supportsNonStandardPropertyNames()); + assertArrayEquals(e1.getPropertyNames().toArray(), Entry.STANDARD_PROPERTY_NAMES.toArray()); + } + + e1.setNotes("How much is that doggy in the window?"); + assertEquals("How much is that doggy in the window?", e1.getNotes()); + e1.setTitle("Entry 2"); + assertEquals("Entry 2", e1.getTitle()); + assertEquals("Entry 2", e1.getPath()); + } + @Test public void testTimes() { long beforeSecond = Instant.now().toEpochMilli()/1000; @@ -233,6 +288,22 @@ public void testNewEntry() { Assert.assertEquals(0, l.size()); } + @Test + public void testNewEntryPasswordBytes() { + E e2 = database.newEntry(); + Assert.assertNull(e2.getParent()); + assertEquals("", new String(e2.getPasswordAsBytes())); + Assert.assertNotNull(e2.getUuid()); + assertEquals("", e2.getUrl()); + assertEquals("", e2.getNotes()); + assertEquals("", e2.getUsername()); + assertEquals("", e2.getTitle()); + Assert.assertNull(e2.getProperty("silly")); + List l = e2.getPropertyNames(); + l.removeAll(Entry.STANDARD_PROPERTY_NAMES); + Assert.assertEquals(0, l.size()); + } + @Test public void testCopy() throws IOException { E entry1 = database.newEntry(); @@ -265,4 +336,37 @@ public void testCopy() throws IOException { assertEquals(group1.getIcon(), group2.getIcon()); assertNotEquals(group1.getUuid(), group2.getUuid()); } + + @Test + public void testCopyPasswordBytes() throws IOException { + E entry1 = database.newEntry(); + entry1.setTitle("Entry"); + entry1.setUsername("Username"); + entry1.setPassword("Password"); + entry1.setUrl("https://dont.follow.me"); + entry1.setNotes("Notes"); + entry1.setIcon(database.newIcon(2)); + + // create a new Database + Database database2 = createDatabase(); + // create a new Entry in new Database + E entry2 = database2.newEntry(entry1); + + assertEquals(entry1.getTitle(), entry2.getTitle()); + assertEquals(entry1.getUsername(), entry2.getUsername()); + assertEquals(true, Arrays.equals(entry1.getPasswordAsBytes(), entry2.getPasswordAsBytes())); + assertEquals(entry1.getUrl(), entry2.getUrl()); + assertEquals(entry1.getNotes(), entry2.getNotes()); + assertEquals(entry1.getIcon(), entry2.getIcon()); + assertNotEquals(entry1.getUuid(), entry2.getUuid()); + + G group1 = database.newGroup(); + group1.setName("Group"); + group1.setIcon(database.newIcon(3)); + + G group2 = database2.newGroup(group1); + assertEquals(group1.getName(), group2.getName()); + assertEquals(group1.getIcon(), group2.getIcon()); + assertNotEquals(group1.getUuid(), group2.getUuid()); + } } diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java index 140ea1c1..b7d49116 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java +++ b/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java @@ -23,6 +23,7 @@ import java.io.PrintStream; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -98,4 +99,61 @@ public boolean matches(Entry entry) { Date expected = sdf.parse("2015-10-24T17:20:41Z"); Assert.assertEquals(expected, c); } + + @Test + public void test123FileBytePassword() throws ParseException { + + // visit all groups and entries and list them to console + database.visit(new Visitor.Print(printStream)); + + // find all entries in the database + // the kdb version has three additional system related entries + List anything = database.findEntries(""); + Assert.assertTrue(10 <= anything.size()); + + // find all entries in the database that have the string "test" in them + List tests = database.findEntries("test"); + for (Entry tes: tests) { + printStream.println(tes.getTitle()); + } + Assert.assertEquals(4, tests.size()); + if (tests.size() > 0) { + // copy the password of the first entry to the clipboard + byte[] pass = tests.get(0).getPasswordAsBytes(); +/* + StringSelection selection = new StringSelection(pass); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); + printStream.println(pass + " copied to clip board"); +*/ + // all the relevant entries should have the password 123 + byte[] pass2 = tests.get(0).getPasswordAsBytes(); + Assert.assertEquals(true, Arrays.equals(pass, pass2)); + Assert.assertEquals("123", new String(pass2)); + } + + List passwords = database.findEntries("password"); + Assert.assertEquals(4, passwords.size()); + for (Entry passwordEntry : passwords) { + Assert.assertEquals(true, Arrays.equals(passwordEntry.getTitle().getBytes(), passwordEntry.getPasswordAsBytes())); + printStream.println(passwordEntry.getTitle()); + } + + List entries = database.findEntries(new Entry.Matcher() { + @Override + public boolean matches(Entry entry) { + return entry.getTitle().equals("hello world"); + }}); + + Assert.assertEquals(1, entries.size()); + assertEquals("pass", new String(entries.get(0).getPasswordAsBytes())); + + // kdb files don't have a time zone so can't make head or tail of the date - test file seems to have a local time in it + if (skipDateCheck) { + return; + } + + Date c = entries.get(0).getCreationTime(); + Date expected = sdf.parse("2015-10-24T17:20:41Z"); + Assert.assertEquals(expected, c); + } } diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java index 82c4733f..9f72833d 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java +++ b/test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java @@ -81,6 +81,33 @@ public void saveAndReloadTest() throws IOException { printStream.format("Test took %d millis", System.currentTimeMillis() - now); } + @Test + public void saveAndReloadTestPasswordBytes() throws IOException { + + long now = System.currentTimeMillis(); + + // create database with known content + D output = createNewDatabase(); + verifyContentsPasswordBytes(output); + //output.save(new StreamFormat.None(), new Credentials.None(), printStream); + + FileOutputStream fos = new FileOutputStream("testOutput/test1.kdbx"); + saveDatabase(output, getCreds("123".getBytes()), fos); + Assert.assertFalse(output.isDirty()); + fos.flush(); + fos.close(); + // make sure that saving didn't mess up content + verifyContentsPasswordBytes(output); + //output.save(new StreamFormat.None(), new Credentials.None(), printStream); + + + FileInputStream fis = new FileInputStream("testOutput/test1.kdbx"); + D input = loadDatabase(getCreds("123".getBytes()), fis); + verifyContentsPasswordBytes(input); + //input.save(new StreamFormat.None(), new Credentials.None(), printStream); + printStream.format("Test took %d millis", System.currentTimeMillis() - now); + } + /** * Test verifies that attachments are saved and reloaded correctly */ @@ -177,6 +204,25 @@ private void verifyContents(D database) { } } + private void verifyContentsPasswordBytes(D database) { + for (Integer g = 0; g< 5; g++){ + G group = database.getRootGroup().getGroups().get(g); + assertEquals(g.toString(), group.getName()); + assertEquals(g + 1, group.getEntries().size()); + assertEquals(g+1, group.getEntriesCount()); + assertEquals(database.getRootGroup(), group.getParent()); + for (int e = 0; e <= g; e++) { + E entry = group.getEntries().get(e); + assertEquals(g + "-" + e, entry.getTitle()); + assertEquals(g + " - un - " + e, entry.getUsername()); + assertEquals(g + "- p -" + e, new String(entry.getPasswordAsBytes())); + assertEquals(g + "- url - " + e, entry.getUrl()); + assertEquals(g + "- n - " + e, entry.getNotes()); + assertEquals(group, entry.getParent()); + } + } + } + /** * Outputs the database to a file - we can try to read it in other versions of the program. Run "manually". * @@ -289,4 +335,91 @@ public void testNewDatabase() throws IOException { assertEquals(0, root.getGroups().size()); assertEquals(0, database.findEntries("").size()); } + + @Test + public void testNewDatabasePasswordBytes() throws IOException { + D database = getDatabase(); + G root = database.getRootGroup(); + Assert.assertTrue(root.isRootGroup()); + assertEquals(0, root.getGroups().size()); + assertEquals(0, root.getEntries().size()); + + Assert.assertTrue(database.shouldProtect("Password")); + Assert.assertFalse(database.shouldProtect("Title")); + Assert.assertFalse(database.shouldProtect("Bogus")); + + assertEquals("New Database", database.getName()); + database.setName("Modified Database"); + assertEquals("Modified Database", database.getName()); + + assertEquals("New Database created by KeePassJava2", database.getDescription()); + database.setDescription("Test Database"); + assertEquals("Test Database", database.getDescription()); + + G group1 = database.newGroup("Group 1"); + UUID newGroupUUID = group1.getUuid(); + + root.addGroup(group1); + assertEquals("Group 1", group1.getName()); + Assert.assertFalse(group1.isRootGroup()); + Assert.assertTrue(root.isRootGroup()); + + assertEquals(1, root.getGroups().size()); + assertEquals(newGroupUUID, root.getGroups().get(0).getUuid()); + + group1.setParent(root); + root.addGroup(group1); + + root.removeGroup(group1); + assertNull(group1.getParent()); + assertEquals(0, root.getGroups().size()); + root.addGroup(group1); + assertEquals(1, root.getGroups().size()); + assertEquals(newGroupUUID, root.getGroups().get(0).getUuid()); + + try { + root.setParent(group1); + Assert.fail("Cannot add root group to another group"); + } catch (Exception ignored) { + } + + G group2 = database.newGroup(); + group2.setName("Group 2"); + group1.addGroup(group2); + assertEquals(1, group1.getGroups().size()); + assertEquals(1, root.getGroups().size()); + + root.addGroup(group2); + assertEquals(0, group1.getGroups().size()); + assertEquals(2, root.getGroups().size()); + + E entry1 = database.newEntry(); + entry1.setTitle("A new entry"); + assertEquals("A new entry", entry1.getTitle()); + entry1.setUsername("user name"); + assertEquals("user name", entry1.getUsername()); + entry1.setProperty("random", "new"); + assertEquals("new", entry1.getProperty("random")); + entry1.setProperty("random", "old"); + assertEquals("old", entry1.getProperty("random")); + + + group2.addEntry(entry1); + + assertEquals(1, group2.getEntries().size()); + entry1.setPassword("pass"); + assertEquals("pass", new String(entry1.getPasswordAsBytes())); + + E entry2 = database.newEntry(entry1); + entry2.setPassword("pass2"); + assertEquals("pass2", new String(entry2.getPasswordAsBytes())); + group2.addEntry(entry2); + + assertEquals(2, group2.getEntries().size()); + root.removeGroup(group1); + root.removeGroup(group2); + + assertEquals(0, root.getGroups().size()); + assertEquals(0, database.findEntries("").size()); + } }