Repository navigation
SPI for formats and storage: load any format, store it any way, use it through the standard interfaces #110
Description
Activity
- changed the title
[-]Interfaces can't hold everything in a KDBX file, so storage other than the Jackson model can't read and write KDBX[/-][+]SPI for formats and storage: load any format, store it any way, use it through the standard interfaces[/+]on Oct 3, 2026 Thanks - yes, this meets what #96 was after, and better than setters would have.
In this SPI's terms: mystic-crypt-ui exports its own vault to KDBX and imports KDBX into it. Today the export builds Jackson objects and reaches
uuid,timesandhistoryby reflection, because nothing else can set them. With #110 the export becomes a source pushingDatabaseData/GroupData/EntryDatainto the KDBX 4 writer, and the import a sink building our own objects from what the reader pushes. No Jackson objects in between, and the reflection goes in both directions.For that to work from outside the library:
- Public construction of the data records (and their builders), so a source outside the library can supply UUID, all four times with the expiry flag, and history.
- History inside
EntryData, as a list ofEntryDatacarrying the parent's UUID. For us history is content: our round-trip test asserts it field by field. - Times in the core - KDB has them too.
- The protected flag per property in the core, as you have it; we round-trip which properties are protected.
One question on the extensions rule: for a store that isn't Java objects - ours persists through XStream, you mention SQL - keeping an extension means serializing it. Will an extension have a defined serialized form (the XML fragment it came from would do), or is that left to each store? That decides whether a store like ours can carry AutoType, colours, tags and custom data at all, or has to drop them as it does now.
Java 17 is no problem here; the application is on 25.
If it helps, I can check the SPI against our export and import once there is a branch: our round-trip test reads both files back with keepassxc-cli and compares field by field, which might be a useful second check next to the XSD-validated file.
- added a commit that references this issue
on Oct 4, 2026 - addedVersion 3.2.0Planned for release 3.2.0Planned for release 3.2.0and removed
on Oct 4, 2026 Will an extension have a defined serialized form (the XML fragment it came from would do), or is that left to each store?
The intention is that you can do what you want, SQL merely given as an illustration. The mantra being:
Load any format, store it any way, and make everything available to users through the standard interfaces.
Pleased to hear that you'll be interested in checking the work as we proceed. It's good to know hat there is one customer out there!
Bear with me as I go along here, it's going to take a while to do and the work will benefit from your feedback please. Also though I'm really pleased to do it, this is rather a lot of work which doesn't appear in yesterday's to do list! I'd be grateful if you would comment on the design document.
I'm going to release 3.1.0 shortly. It will feature the Java 17 uplift. And will sort out load/save stream handling which has been wrong from the dawn of time (don't close streams that you did not open) and changing of Date to Instant which should have happened in the interfaces at 3.0.0. This may or may not be relevant to you, will appreciate your view on it as it changes the internal formal as well as at the interface.
Thanks. On 3.1.0 first, since you asked: I compiled mystic-crypt-ui against it. 18 errors, all in our two KDBX converters, all
DatetoInstant- and our own model isjava.timealready, so the change removes conversions on our side rather than adding them. The stream change doesn't touch us: we open and close both streams ourselves aroundloadandsave, and will move toread/writewith the upgrade. So no objection, andInstantis welcome.On the design document, from the point of view of a source and a sink outside the library:
- The four points from my last comment are covered: public records with builders, history inside
EntryDatawith the parent's UUID, times in the core, protection per property. One question on the last: a source can make a protected value today withnew PropertyValue.SealedStore(...). Will a sink keepisProtected()as it receives it, or re-derive protection from its ownStrategyby property name? We round-trip which properties are protected, including ad hoc ones, so we'd need the first. - Dropped data (decision 4): an application wants to show a person what an import or export left out, per entry, and to test it. A
Consumer<String>gives a log line; a small record - the element's UUID, what was dropped, and why - would let us list it per entry and assert it without parsing text. The string could still be itstoString(). - Extensions in other storage (decision 7): understood that the codec is deferred. Until it exists we keep dropping AutoType, colours and CustomData on import, as we do now. When it comes, an XML fragment per element is exactly what we would store - one vote for it, no hurry.
- A small one: the core table says "the five times", while
Timeshas fourInstants and theexpiresflag. IfLocationChangedis meant to be core, it's missing from the record; if the fifth is the flag, fine.
The offer stands: once there is a prototype build, I'll run our KDBX round trip against it - export through the SPI, read the file back with
keepassxc-cli, compare field by field - and report what differs.- The four points from my last comment are covered: public records with builders, history inside
thanks for feedback, I'm afraid (though I suppose better to fix bugs, really? 😄) yet another bug-fix release 3.1.1 is to follow please see #114
The problem
Only the Jackson model in
kdbx-databasecan read and write proper KDBX files.Database,GroupandEntrycan't hold everything a KDBX file contains, so keeping KeePass data in another form, such as a different in-memory model or a SQL database, either loses data or means reimplementing the KeePass XML schema.The
basicmodule doesn't solve this. It uses the KDBX encryption layers but writes its own XML (<database>, not<KeePassFile>), so KeePass can't read its files, and it can't read files that KeePass wrote.More generally, and a long-standing aim: databases in formats other than KDB and KDBX should be usable through the same
Database,GroupandEntryinterfaces. So the goal is:Proposal: an SPI for formats and storage
Separate three things that are currently bound together in each implementation:
Database,GroupandEntry, implemented by the storage, which is what applications use.Formats and storage talk through an SPI: a contract the library calls, not something applications use. It doesn't have to involve
ServiceLoaderdiscovery, because the caller chooses the format and the storage. Discovery could be added later.Content and events
DatabaseData,GroupDataandEntryData(with history), plus value types such asTimes,CustomIconandDeletedObject. 3.x requires Java 17 from 3.1.0, so these are records (with builders where there are many optional fields).database(DatabaseData),startGroup(GroupData),entry(EntryData),endGroup(),deletedObject(DeletedObject),end().void writeTo(Sink sink).So:
source.writeTo(sink).Common core and format-specific extensions
Formats don't share a schema, so the data objects have two parts:
Database,GroupandEntryexpose now.AutoType, colours, tags,CustomData, custom icons,Metasettings, history and so on. KDB would have its own (e.g. meta-stream entries), and so would any later format.Rules for extensions:
entry.getExtension(KdbxEntryExtension.class)returning anOptional. This adds no KeePass-specific methods toEntry, and fits the existingsupports…()capability methods onDatabase.What the formats and storage keep doing
Formats handle file details that aren't content:
HeaderHash, theBinariespool andRefnumbers, theProtectedattribute and decrypting protected values in order, date encoding, and the KDBX 4 inner header. None of it appears in the SPI.The sink has to say what happens with invalid input, such as
endGroupwithoutstartGroup, or a missing custom icon. Either readers guarantee the schema's rules, or sinks check them.Considered: derived interfaces
The first version of this issue proposed
KeePassEntry extends Entry,KeePassGroup extends GroupandKeePassDatabase extends Database, with getters and setters for every KDBX field, and the KDBX reader and writer working against them. It was set aside because:Derived interfaces for applications that want KeePass fields directly could still come later, built on the extensions.
What KDBX needs that the core doesn't hold
This is the specification for the KDBX extensions. It was checked against Reichl's KDBX 4.1 schema (
XSD/KDBX.4.1.reichl.xsd), not sample files, which may not contain every element. "Get" and "set" refer to the currentEntry,GroupandDatabaseinterfaces.Entry (
TEntry)Icon)Group (
TGroup)Database (
TMeta,TRoot)isRecycleBin)shouldProtect…Changedtime for each Meta field, DefaultUserName, MaintenanceHistoryDays, Color, MasterKeyChanged, MasterKeyChangeRec, MasterKeyChangeForce, MasterKeyChangeForceOnce, CustomIcons (UUID, Data, Name, LastModificationTime), EntryTemplatesGroup, HistoryMaxItems, HistoryMaxSize, LastSelectedGroup, LastTopVisibleGroup, CustomData with timesSetting UUIDs and times also comes up in #96.
Rules from the schema
CustomIconUUIDrefers to an icon inMeta/CustomIcons.String,BinaryandHistoryitems, and of child entries and groups, matters.Questions
EntryDataseems simplest).Instant. From 3.1.0EntryandGrouptimes arejava.time.Instantrather thanDate(KDBX times are UTC and KDB times are treated as UTC, so no zone is needed), and the SPI usesInstanttoo.Meta/Binaries,HeaderHash), but those are format details, so one KDBX extension should cover both.Testing
Examples
Database,GroupandEntry, and write it back with nothing lost.Order
After #109 (
write/read) and the move to Java 17, both in 3.1.0, so the new code uses the new stream handling and records from the start. The SPI is planned for release 3.2.0, as experimental at first, possibly with prototype releases for comment. The design is in FormatsAndStorageSPI.md on branchissue-110-spi.