From 7cb44889c7ab76d1a6a5b318bc24f05236224d1b Mon Sep 17 00:00:00 2001 From: Jo Rabin Date: Thu, 24 Aug 2023 15:14:36 +0100 Subject: [PATCH] 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;