
org.linguafranca.pwdb.kdbx.jackson.JacksonSerializableDatabase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of KeePassJava2-jackson Show documentation
Show all versions of KeePassJava2-jackson Show documentation
Contains a Jackson XML implementation for KDBX.
The newest version!
/*
* Copyright 2023 Giuseppe Valente
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.linguafranca.pwdb.kdbx.jackson;
import com.ctc.wstx.api.WstxInputProperties;
import com.ctc.wstx.api.WstxOutputProperties;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import org.linguafranca.pwdb.PropertyValue;
import org.linguafranca.pwdb.SerializableDatabase;
import org.linguafranca.pwdb.kdbx.Helpers;
import org.linguafranca.pwdb.kdbx.jackson.converter.ValueDeserializer;
import org.linguafranca.pwdb.kdbx.jackson.converter.ValueSerializer;
import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile;
import org.linguafranca.pwdb.security.StreamEncryptor;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Objects;
public class JacksonSerializableDatabase implements SerializableDatabase {
/**
* By default, deserialization will fail if an unknown property is found. Historically,
* this has been because the file mapping is incomplete, rather than the incoming file being wrong.
* So use this feature with caution, if at all.
*/
public static boolean FAIL_ON_UNKNOWN_PROPERTIES = true;
public KeePassFile keePassFile;
private StreamEncryptor encryptor;
private PropertyValue.Strategy propertyValueStrategy = new PropertyValue.Strategy.Default();
public static KeePassFile createEmptyDatabase() throws IOException {
InputStream inputStream = JacksonSerializableDatabase.class.getClassLoader()
.getResourceAsStream("base.kdbx.xml");
XmlMapper mapper = new XmlMapper();
return mapper.readValue(inputStream, KeePassFile.class);
}
public JacksonSerializableDatabase() {
}
public JacksonSerializableDatabase(KeePassFile keePassFile) {
this.keePassFile = keePassFile;
}
@Override
public JacksonSerializableDatabase load(InputStream inputStream) throws IOException {
XmlMapper mapper = new XmlMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
JacksonSerializableDatabase.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule module = new SimpleModule();
module.addDeserializer(PropertyValue.class, new ValueDeserializer(encryptor, propertyValueStrategy));
mapper.registerModule(module);
keePassFile = mapper.readValue(inputStream, KeePassFile.class);
return this;
}
@Override
public void save(OutputStream outputStream) {
try {
SimpleModule module = new SimpleModule();
module.addSerializer(PropertyValue.class, new ValueSerializer(encryptor, propertyValueStrategy));
// 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);
try {
sw.setPrefix("xml", "http://www.w3.org/XML/1998/namespace");
mapper.writeValue(sw, keePassFile);
sw.writeEndDocument();
} finally {
sw.close();
osw.close();
}
} catch(Exception e) {
throw new IllegalStateException(e);
}
}
@Override
public byte[] getHeaderHash() {
return keePassFile.meta.headerHash;
}
@Override
public void setHeaderHash(byte[] hash) {
this.keePassFile.meta.headerHash = hash;
}
public static void addBinary(KeePassFile keePassFile, int index, byte[] payload) {
KeePassFile.Binary newBin = new KeePassFile.Binary();
newBin.setId(index);
newBin.setValue(Helpers.encodeBase64Content(payload, true));
newBin.setCompressed(true);
if (keePassFile.meta.binaries == null) {
keePassFile.createBinaries();
}
keePassFile.meta.binaries.add(newBin);
}
// TODO this gets binary at index but does not get binary with ID
@Override
public byte[] getBinary(int index) {
KeePassFile.Binary binary = keePassFile.meta.binaries.get(index);
String value = binary.getValue();
return Helpers.decodeBase64Content(value.getBytes(), binary.getCompressed());
}
@Override
public int getBinaryCount() {
if (Objects.isNull(keePassFile.meta.binaries)){
return 0;
}
return keePassFile.meta.binaries.size();
}
/**
* On load add parents
*
* @param parent a parent to recurse
*/
static void fixUp(JacksonGroup parent) {
for (JacksonGroup group : parent.groups) {
group.parent = parent;
group.database = parent.database;
fixUp(group);
}
for (JacksonEntry entry : parent.entries) {
entry.database = parent.database;
entry.parent = parent;
}
}
@Override
public StreamEncryptor getEncryption() {
return this.encryptor;
}
@Override
public void setEncryption(StreamEncryptor encryption) {
this.encryptor = encryption;
}
@Override
public void addBinary(int index, byte[] payload) {
addBinary(keePassFile, index, payload);
}
public PropertyValue.Strategy getPropertyValueStrategy() {
return propertyValueStrategy;
}
public void setPropertyValueStrategy(PropertyValue.Strategy propertyValueStrategy) {
this.propertyValueStrategy = propertyValueStrategy;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy