diff --git a/.circleci/config.yml b/.circleci/config.yml index 5151a953..1890bf8f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,26 +6,6 @@ version: 2.1 # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. - build-and-test-8: - # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/ - # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. - # Be sure to update the Docker image tag below to openjdk version of your application. - # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/openjdk - docker: - - image: cimg/openjdk:8.0 - # Add steps to the job - # See: https://circleci.com/docs/2.0/configuration-reference/#steps - steps: - # Checkout the code as the first step. - - checkout - # Use mvn clean and package as the standard maven build phase - - run: - name: Build - command: mvn -B -DskipTests clean package - # Then run your tests! - - run: - name: Test - command: mvn -P inhibitConsoleOutput test build-and-test-11: # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/ @@ -51,8 +31,7 @@ jobs: # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - build-8-11: # This is the name of the workflow, feel free to change it to better match your workflow. + build-11: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. jobs: - - build-and-test-8 - build-and-test-11 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..721db89e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*.java] +charset = utf-8 +indent_style = space +indent_size = 4 +# DO NOT USE insert_final_newline = true +#insert_final_newline = true + +[*.yaml] +indent_size = 2 +indent_style = space + +[*.sh] +end_of_line = lf + +[*.cmd] +end_of_line = crlf + +[*.py] +indent_size = 4 +indent_style = space \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ddff4b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,39 @@ +* -text + +*.css text +*.scss text +*.sass text +*.html text +*.java text +*.js text +*.json text +*.properties text +*.txt text +*.xml text +*.py text +*.cmd text +*.bat text +*.yaml text +*.yml text +*.sql text +# *.ts text + +.gitignore text +.gitattributes text +.slugignore text +.editorconfig text + +COPYING text +README.md text +mvnw text + + +# These files are binary and should be left untouched +*.class binary +*.jar binary +*.gif binary +*.jpg binary +*.png binary +*.ico binary +*.mp4 binary +*.xcf binary diff --git a/.gitignore b/.gitignore index 3ee52c53..3da9b48e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build .vscode/* *.versionsBackup **/target +.idea #settings.gradle #gradlew @@ -10,4 +11,5 @@ build #.gradle #gradle #build.gradle +Calc*_V3.class diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3d066ab9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: java -jdk: openjdk7 -script: mvn clean test diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a0aead..c6df680d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,39 @@ 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] +## [3.0.0-SNAPSHOT] (ongoing) + +### Changed + +- [Issue 81, 83] Remove junit dependency from main code and resolve test failure from UTF-8 encoding in test resources +- [Issue 87] Problem with incorrect serialization of CustomIcons in Jackson implementation +- [Issue 88] Incompatibility to KeePass due t missing empty element in autotype field in Jackson implementation +- [Issue 90] Update dependencies to resolve security vulnerabilities +- update to Java 11 +- refactor API + - remove complicated generics + - fluent element builder + - additional convenience methods +- refactor examples +- refactor modules + - rename Jackson database to KDBX database + - add experimental `basic` database support + - remove Simple database + - remove JAXB database + - remove DOM database + - remove `test` and `util` modules +- refactor tests + - restructure + - create a test-jar for dependencies + - "upgrade" to JUnit 5 + + +## [2.2.4] 2025-03-05 + +- [Issue 76, 78] Resolve incompatibility with KeePassXC (empty elements) +- [Issue 73] Trying to resolve dependency clashes for Woodstox etc + +## [2.2.3] 2025-01-05 ### Added @@ -13,6 +45,12 @@ Trying to follow the suggestions at [Keep a Change Log](http://keepachangelog.co - Jackson implementation supports this interface - Other implementations throw exceptions appropriately as unsupported +- [Issue #70] Improved support for KDBX 4.1 format (Jackson only) +- [ISSUE #71] Module restructure to avoid test files, junit being pulled in unnecessarily + +### Fixed + +KDBX file format version 4.1 now supported (Issue-70) (in Jackson, not Simple) ## [2.2.2] 2024-09-06 diff --git a/DatabaseStructure.svg b/DatabaseStructure.svg index 0694e7aa..6e7d6b57 100644 --- a/DatabaseStructure.svg +++ b/DatabaseStructure.svg @@ -1,4 +1,21 @@ + +
Save/Load
Save/Load
SerializableDatabase
(Jackson, DOM, JAXB, Simple)
SerializableDatabase...
Encrypt/Decrypt
Encrypt/Decrypt
StreamFormat
(Kdbx, None)
StreamFormat...
Read/Write Values
Read/Write Values
Save/Load
Save/Load
Database Implementation
(Memory Model, DOM etc.)
Database Implementation...
Save/Load
Save/Load
User Read/Write
User Read/Write
Database Facade
(Jackson, DOM Wrapper, JAXB, SImple)
Database Facade...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/KdbxDiagram.svg b/KdbxDiagram.svg index fb278b1e..6aaefa8e 100644 --- a/KdbxDiagram.svg +++ b/KdbxDiagram.svg @@ -1,4 +1,21 @@ + +
Magic Number
Magic Number
Version
Version
Header
Header
Header
Header
Header
Header
Header 0
Header 0
...
...
Value of Header 9
Value of Header 9
0
0
8
8
4
4
Type
Type
Length
Length
Value
Value
Encrypted
Encryp...
Unencrypted
Unencr...
Sequence
Sequence
MD5 Hash
MD5 Hash
Length
Length
KDBX 3.1 File
KDBX 3.1 File
Header Format
Header Format
Hashed Block
Format
Hashed Block...
Unencrypted
Payload
(Optionally Compressed)
Unencrypted...
36
36
0
0
40
40
12
12
1
1
3
3
0
0
Byte
Offset
Byte...
Byte
Offset
Byte...
Byte
Offset
Byte...
Magic Number
Magic Number
Version
Version
Header
Header
Header
Header
Header
Header
Header 0
Header 0
...
...
0
0
8
8
32
32
Type
Type
Length
Length
Value
Value
Unencrypted
Unencr...
Length
Length
Header Format
Header Format
Hashed Block
Format
Hashed Block...
HMAC-SHA-256
HMAC-SHA-256
36
36
0
0
12
12
1
1
5
5
0
0
Byte
Offset
Byte...
Byte
Offset
Byte...
Byte
Offset
Byte...
KDBX 4 File
KDBX 4 File
As described at http://keepass.info/...
SHA-256
SHA-256
Encrypted
Payload
(Optionally Compressed)
Encrypted...
HMAC-SHA-256
HMAC-SHA-256
Inner Header
Inner Header
Hashed Blocks
Hashed Blocks
Inner Header
Inner Header
...
...
Header 0
Header 0
XML Database
XML Database
Hashed Blocks
Hashed Blocks
XML Database
XML Database
Copyright (c) 2023 Jo Rabin, Licensed under the Apache2 license. See KeePassJava2.
Copyright (c) 2023 Jo Rabin, Licensed under the Apache2 license. See KeePassJava2.
Text is not SVG - cannot display
\ No newline at end of file diff --git a/ModuleStructure.svg b/ModuleStructure.svg index 9579dffe..d55374a0 100644 --- a/ModuleStructure.svg +++ b/ModuleStructure.svg @@ -1,4 +1,21 @@ + + -
all
all
simple
simple
jaxb
jaxb
dom
dom
kdb
kdb
kdbx
kdbx
database
database
Test
Test
example
example
Other, future
Other,...
KDBX Implementations
KDBX I...
KDB Implementation
KDB Im...
???
???
Implementatiions
of the 
org.linguafranca.pwdb.Database Interface
Implem...
jackson
jackson
Text is not SVG - cannot display
\ No newline at end of file +
all
all
kdb
kdb
kdbx-io
kdbx-io
database
database
Test
Test
example
example
Other, future
Other,...
KDBX Implementations
KDBX I...
KDB Implementation
KDB Im...
???
???
Implementatiions
of the 
org.linguafranca.pwdb.Database Interface
Implem...
kdbx-database
kdbx-database
???
???
Text is not SVG - cannot display
\ No newline at end of file diff --git a/PropertyValueProtection.md b/PropertyValueProtection.md index ccb6f87d..f0e12a05 100644 --- a/PropertyValueProtection.md +++ b/PropertyValueProtection.md @@ -8,7 +8,7 @@ that have sensitive values are called "Protected Properties". KDBX files contain a list of the standard properties and whether they are to be treated as protected by default. The standard properties include Password, which is so treated. There doesn't seem to be a way of updating this list in the Windows KeePass implementation, and in any case documentation says that the -list is updated to default after load of a database, so it seems that it ignored. +list is updated to default after load of a database, so it seems that it is ignored. Individual properties can be marked as "protect in memory", but there is no way to indicate that all properties of that name should be protected. @@ -63,7 +63,7 @@ luck trying to avoid strings at all. Likewise, if you collect passwords from a d After the deserialization process, the passwords are stored in RAM. Therefore: **Hashing the password is not feasible**, as the hash function is a one-way process. -This means that once a password is hashed, it cannot be reverted back to its original form, +This means that once a password is hashed, it cannot be reverted to its original form, which makes hashing unsuitable in this context. Since we need to retrieve and view the saved passwords (as per the requirements), hashing does not meet the objective. @@ -71,9 +71,10 @@ the saved passwords (as per the requirements), hashing does not meet the objecti What key should be used for this process? If the key is stored in RAM, we are essentially creating the same security vulnerability we are trying to avoid. -While storing data in an encrypted form may seem appealing, keeping it encrypted in the KDBX InnerStream is impractical. -This approach requires that the encrypted property values appear in the same order for both encryption and decryption, which makes it cumbersome. -Encrypting and decrypting all protected fields each time we need to access or manipulate just one of them would add significant overhead. +While storing data in an encrypted form may seem appealing, keeping it encrypted in the KDBX InnerStream +is impractical. This approach requires that the encrypted property values appear in the same order for both +encryption and decryption, which makes it cumbersome. Encrypting and decrypting all protected fields each +time we need to access or manipulate just one of them would add significant overhead. If the data is to be encrypted, then we need to find some way of storing the encryption key that is not open to simple inspection. diff --git a/KDBX.3.1.xsd b/XSD/KDBX.3.1.xsd similarity index 99% rename from KDBX.3.1.xsd rename to XSD/KDBX.3.1.xsd index ff22a5c7..be5c71f8 100644 --- a/KDBX.3.1.xsd +++ b/XSD/KDBX.3.1.xsd @@ -1,18 +1,19 @@ diff --git a/XSD/KDBX.4.1.reichl.xsd b/XSD/KDBX.4.1.reichl.xsd new file mode 100644 index 00000000..6ccdda40 --- /dev/null +++ b/XSD/KDBX.4.1.reichl.xsd @@ -0,0 +1,509 @@ + + + + + + KDBX 4.1 XML Schema. Copyright (C) 2007-2025 Dominik Reichl. https://keepass.info/help/kb/kdbx.html + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the application that has generated the XML document. + + + + + Hash of the (unencrypted) header of a KDBX file. Used only in KDBX files prior to version 4. In KDBX ≥ 4, the integrity and the authenticity are ensured using a HMAC instead (see KDBX spec.). + + + + + Last date/time when a database setting (stored in the Meta element) has been changed. + + + + + + + + + User name that is used by default for new entries. + + + + + + Number of days until history entries are deleted in a database maintenance operation. + + + + + Database color. The user interface can colorize elements with this color in order to allow the user to quickly identify the database. + + + + + Last date/time when the master key has been changed. + + + + + Number of days until a change of the master key is recommended. -1 means never. + + + + + + + + + + Number of days until a change of the master key is enforced. -1 means never. + + + + + + + + + + If true, a change of the master key should be enforced once directly after the user opens the database. + + + + + + + + + + + + + + UUID of the group that is used as recycle bin. Zero UUID = create new group when necessary. + + + + + + + + Maximum number of history entries that each entry may have. -1 means unlimited. + + + + + + + + + + Maximum estimated size in bytes (in the process memory) of the history of each entry. -1 means unlimited. + + + + + + + + + + + + In this element, the content of each binary is stored. Used only in unencrypted XML files and in KDBX files prior to version 4. In KDBX ≥ 4, binaries are stored in the inner header (encrypted) instead. + + + + + + + + + + + + + + + + + Process memory protection settings, describing which standard fields should be protected. KeePass resets these settings to their default values after opening a database. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reference to a binary content stored in the inner header (KDBX file) or in the Meta/Binaries element (unencrypted XML file). + + + + + + + + + + + + + + + + + + Used in a KDBX file. + + + + + Used in an unencrypted XML file. + + + + + + + + + + + If the attribute is true, the content of the element has been encrypted (and Base64-encoded). See "inner encryption" on https://keepass.info/help/kb/kdbx.html + + + + + + Custom data item (key/value pair) for plugins/ports. The key should be unique, e.g. "PluginName_ItemName". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When the user deletes an object (group, entry, ...), an item is created in this list. When synchronizing/merging database files, this information can be used to decide whether an object has been deleted. + + + + + + + + + + + + + + + + + + + In general, last access times are not reliable, because an access is not considered to be a database change. See the UIFlags value 0x20000: https://keepass.info/help/v2_dev/customize.html#uiflags + + + + + + + Cf. LastAccessTime. + + + + + Last date/time when the object has been moved (within its parent group or to a different group). This is used by the synchronization algorithm to determine the latest location of the object. + + + + + + + + + + + + + See the folder "Ext/Images_Client_HighRes" in the KeePass source code package. + + + + + Reference to a custom icon stored in the KeePassFile/Meta/CustomIcons element. If non-zero, it overrides IconID. + + + + + + Specifies whether the group is displayed as expanded in the user interface. + + + + + + + + + UUID of the group in which the current group was stored previously. This information can for instance be used by a recycle bin restoration command. + + + + + Tags associated with the group, separated using ';'. https://keepass.info/help/v2/entry.html#tags + + + + + + + + + + + + + + See TGroup/IconID. + + + + + See TGroup/CustomIconUUID. + + + + + + + https://keepass.info/help/base/autourl.html#override + + + + + https://keepass.info/help/v2/entry.html#gen https://keepass.info/help/kb/pw_quality_est.html + + + + + See TGroup/Tags. + + + + + See TGroup/PreviousParentGroup. + + + + + + + + + + https://keepass.info/help/v2/entry.html#hst + + + + + + + + + + + + https://keepass.info/help/base/autotype.html + + + + + + + + + + No obfuscation. + + + + + Two-channel auto-type obfuscation. https://keepass.info/help/v2/autotype_obfuscation.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A hexadecimal CSS color of the form "#RRGGBB". For example, "#FFFF00" is yellow. An empty string means to use the default value (chosen by the application, suitable for the current UI). + + + + + + + + + + + + + + + + + In a KDBX ≥ 4 file, dates are stored as the number of seconds (Int64) elapsed since 0001-01-01 00:00:00 UTC encoded using Base64. In an unencrypted XML file, dates are stored as xs:dateTime. + + + + + + + + + + + + + + + + + A 128-bit UUID encoded using Base64. + + + + + + \ No newline at end of file diff --git a/XSD/KDBX.4.1.xsd b/XSD/KDBX.4.1.xsd new file mode 100644 index 00000000..c6b3058a --- /dev/null +++ b/XSD/KDBX.4.1.xsd @@ -0,0 +1,584 @@ + + + + + + The KDBX 4.1 XML format as used by KeePass 2.x compatible programs. + + This is an update of earlier versions of the schema for versions 3.1 and 4 to be found at ../KDBX.{v}.xsd + + The KDBX schema is documented (with somewhat different conventions) at + https://keepass.info/help/download/KDBX_XML.xsd. This schema was originally created as a) that XSD did + not exist at the time b) this schema was originally created to provide for JAXB bindings and was + tailored for that purpose. + + In this version, artefacts that existed only to allow for JAXB purposes, for example abstract "parented" + elements to make it easier to add transient data in Java, have been removed. Also some correlation was + carried out with the XSD at keepass.info, which as mentioned did not originally exist at the time this + file was originally created. xs:sequence has been changed to xs:all where appropriate. + + Conventions in this schema: + + KeePassFile, Group, Entry, Times and History are top level elements as they may be useful for + creating fragments or something. + + There are types for almost everything that contains documentation as to its purpose + and how it's used and if there are restrictions on the values. + + Everything else is simply included inline for the sake of brevity. Undoubtedly + more documentation could most usefully be added. + + Some elements are probably optional but are not marked as such. In real life they may be serialised + as empty elements and some are serialised with content "null". Actual serialisations will depend on + the view that various implementations have taken, so flexibility is required when loading. Historically + this was particularly true of boolean values. + + + + + + + + The root element. + + + + + + + This contains database settings and also contains information + about the last state of the program that used the database. Presumably assuming + that the same program will be used to open it again, or that a different program + would want/need the same settings. + + + + + + The name of an application. This might be an application + that created the original document or it could be an application that + altered it at some point. + + + + + + Header hash removed from version 4. Hashes now contained + in the kdbx header itself. + + + + + + The last time something in the enclosing meta element changed. + + + + + + + + + + + + + + + + + + When the XML database is loaded into memory, sensitive + fields will be encrypted in that stream. This element specifies which of the + standardFields are encrypted in that stream, which is decrypted using Salsa20 + for KDBX3 (or ChaCha20 by default for V4) and the InnerRandomStream encryption header. + + ./Entry/String/Value/@Protected signifies that the value of a stringField is + so encrypted and applies also to customFields. + + Attachments are optionally memory protected in V4. + + + + + + + + + + + + + + + + + + + + + + + + + Used to store binary values in version 3.1 - in version 4 + such values are now transferred as part of the KDBX inner headers. + + + + + + + + + + + + + + This is the root group which contains everything. + + + + + + When the user permanently deletes an wrappedEntry or a group, + an item is created in this node. This allows the synchronization function + to decide whether an object has been deleted or is just missing in one of + the databases that are being synchronized. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When it was last updated, resulting in an update to History if that is + active. + + + + + + When it was originally created. + + + + + When was last used (read). + + + + + When it expires if it expires. + + + + + True if it expires. + + + + + How often the object has been used (e.g. by copying data of it to the + clipboard, performing auto-type with it, etc.). However, a change to the usage count is + not considered to be a database change (i.e. the database is not marked as changed by the + UI), thus this value is typically incorrect (and the same applies to the last access + time). + + + + + + The date/time when the object was last moved. This allows the + synchronization function to correctly determine the most recent location of the + object. + + + + + + + + + + + + + + + + + + + + + I think the preferred values are "True" and "False", but for the sake of accommodating + various non-conformant applications ... accept a wider variety. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is ISO Date time stored as UTC (Z time zone). + + In KDBX Version 4 this becomes a base64 encoded offset in seconds + from 0001-01-01T00:00:00Z - if stored in a KDBX encoded file, but + is a xs:dateTime otherwise. There is no attribute to say what the format is + so be prepared to do either. + + + + + + + + + + + + + + XSD doesn't do comma (or semicolon) separated lists, so we can't model this handily. + + Assume that the format is something like [{token}{[,;] token}*]+ where white space is ignored except + as part of the name of the token. + + See https://keepass.info/help/v2/entry.html#tags + + + + + + + + + This is where the values of the database are actually stored. You can have String valued + fields, and you can have Binary valued fields. There are "Default" String fields (username and so on) and + there are custom string fields (custom only in that their names are not the names of default string + fields). Not really clear whether the keys are case-sensitive. + + Note that the "Protected" attribute is used in encrypted KDBX files and "ProtectInMemory" is used + in unencrypted XML files. + + + + + + + + + + + + + + + + + + + + If derived from a file then the Key element conventionally contains the file name. + I think it would be useful to add a mime type on this ... + + The Value element is the value of /./meta/binaries/binary/@ID that is referenced from this + binary (V3 and XML formatted documents). Or in the inner header of a KDBX file. + + + + + + + + + + + + + + + + + An index to one of the 69 built in icons. + + + + + + + + + + + + Simply put: "#rrggbb". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specifies the override URL, as documented at + http://keepass.info/help/base/autourl.html#override + + + + + + + + + + Stores everything related to auto-type for this wrappedEntry. + http://keepass.info/help/base/autotype.html + + + + + + + 1 if Two-Channel Auto-Type Obfuscation is enabled, otherwise 0. + http://keepass.info/help/v2/autotype_obfuscation.html + + + + + + + + + + + + + Custom window/sequence association + + + + + + + + + + + + + + + + Number of days before triggering an event e.g. suggesting a password change. + -1 means never. 0 means every time. + + + + + + + + + + + + A base64 encoded UUID. Each Group and Entry has a UUID. When added to History the same + UUID is maintained. + + + + + + + + A reference to the UUID e.g. a Group like the Recycle bin. If, for + example, the recycle bin is enabled but has not been used, then it won't have been + created and the value stored here is a 0 UUID. + + + + + + + + A reference to the custom icon UUID found in the meta/customIcons list. + + + + + + + + + Binary field for elements are centrally stored in this element. + The same Binary element may be referenced from many Entries. + + + + + + The ID attribute is referenced in the Ref attribute of + a corresponding Binary element of an Entry. The Compressed attribute specifies + whether the base64 encoded value is to be decompressed to recover the binary + data. + + + + + + + + + + + + + + + + + + + + Third party programs and plugins can put custom data here. + Unique element names should be used, e.g. "PluginName_ItemName". + + + + + + + Note that "LastModificationTime" is actually only allowed when this appears in "Meta" + and not when it appears in "Entry" or "Group" + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KDBX.4.xsd b/XSD/KDBX.4.xsd similarity index 99% rename from KDBX.4.xsd rename to XSD/KDBX.4.xsd index 4fb7d70a..d0b7b130 100644 --- a/KDBX.4.xsd +++ b/XSD/KDBX.4.xsd @@ -1,18 +1,19 @@ - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT + KeePassJava2.parent + mh.keepass + 1.0.0.1 ../pom.xml @@ -27,32 +28,17 @@ KeePassJava2 KeePassJava2 :: All - KDB Database support as well as KDBX 3.1: DOM, JAXB and Simple implementations. + Composite containing KDBX and KDB Database support - org.linguafranca.pwdb - KeePassJava2-kdb - ${project.version} - - - org.linguafranca.pwdb - KeePassJava2-dom - ${project.version} - - - org.linguafranca.pwdb - KeePassJava2-jaxb - ${project.version} - - - org.linguafranca.pwdb - KeePassJava2-simple + mh.keepass + KeePassJava2.kdb ${project.version} - org.linguafranca.pwdb - KeePassJava2-jackson + mh.keepass + KeePassJava2.kdbx.database ${project.version} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/AbstractJaxbParentedBinding.java b/all/src/main/java/org/linguafranca/all/IgnoreMe.java similarity index 56% rename from jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/AbstractJaxbParentedBinding.java rename to all/src/main/java/org/linguafranca/all/IgnoreMe.java index f9cb3ae1..d2d2bb71 100644 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/AbstractJaxbParentedBinding.java +++ b/all/src/main/java/org/linguafranca/all/IgnoreMe.java @@ -1,30 +1,24 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.jaxb.base; - -import javax.xml.bind.annotation.XmlTransient; +package org.linguafranca.all; /** - * Ancestor class for Entry and Group Jaxb Bindings, allowing insertion of - * a reference to the parent group. - * - * @author jo + * This is here for Javadoc generation purposes only */ -public abstract class AbstractJaxbParentedBinding { - @XmlTransient - public AbstractJaxbParentedBinding parent; +public class IgnoreMe { } diff --git a/all/src/main/java/org/linguafranca/all/package-info.java b/all/src/main/java/org/linguafranca/all/package-info.java new file mode 100644 index 00000000..83edcabd --- /dev/null +++ b/all/src/main/java/org/linguafranca/all/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ + +/** + * This package/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.all; \ No newline at end of file diff --git a/all/src/main/java/org/linguafranca/pwdb/IgnoreMe.java b/all/src/main/java/org/linguafranca/pwdb/IgnoreMe.java deleted file mode 100644 index 0f2521c5..00000000 --- a/all/src/main/java/org/linguafranca/pwdb/IgnoreMe.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.linguafranca.pwdb; - -/** - * This is here for Javadoc generation purposes only - */ -public class IgnoreMe { -} diff --git a/all/src/main/java/org/linguafranca/pwdb/package-info.java b/all/src/main/java/org/linguafranca/pwdb/package-info.java deleted file mode 100644 index a544df96..00000000 --- a/all/src/main/java/org/linguafranca/pwdb/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * 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/basic/pom.xml b/basic/pom.xml new file mode 100644 index 00000000..87186e99 --- /dev/null +++ b/basic/pom.xml @@ -0,0 +1,72 @@ + + + + + 4.0.0 + + mh.keepass + KeePassJava2.parent + 1.0.0.1 + + + basic + PWDB :: Basic + Experimental database implementation to explore serialization. + + true + + + + + mh.keepass + database + ${project.version} + compile + + + mh.keepass + KeePassJava2.kdbx.io + 1.0.0.1 + compile + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.21.2 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.21.2 + + + com.fasterxml.woodstox + woodstox-core + 7.1.0 + + + mh.keepass + database + ${project.version} + test-jar + test + + + \ No newline at end of file diff --git a/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabase.java b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabase.java new file mode 100644 index 00000000..7abd4f4f --- /dev/null +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabase.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.jspecify.annotations.Nullable; +import org.linguafranca.pwdb.*; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.format.KdbxStreamFormat; +import org.linguafranca.pwdb.protect.ProtectedDatabase; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Instant; +import java.util.Objects; + +/** + * A basic implementation of a Database for experimentation and testing + */ +public class BasicDatabase extends ProtectedDatabase { + private final BasicGroup root = new BasicGroup(this, "Root"); + private String databaseName = "New Database"; + private String databaseDescription = "Database created on " + Instant.now().toString(); + private StreamFormat streamFormat; + + @Override + public Group getRootGroup() { + return root; + } + + @Override + public Group newGroup() { + return new BasicGroup(this); + } + + @Override + public Entry newEntry() { + return new BasicEntry(this); + } + + @Override + public Icon newIcon() { + return new BasicIcon(); + } + + @Override + public Icon newIcon(Integer i) { + return new BasicIcon(i); + } + + @Override + public boolean isRecycleBinEnabled() { + return false; + } + + @Override + public void enableRecycleBin(boolean enable) { + throw new UnsupportedOperationException(); + } + + @Override + public @Nullable Group getRecycleBin() { + return null; + } + + @Override + public String getName() { + return databaseName; + } + + @Override + public void setName(String name) { + this.databaseName = name; + } + + @Override + public String getDescription() { + return databaseDescription; + } + + @Override + public void setDescription(String description) { + this.databaseDescription = description; + } + + @Override + public void save(Credentials credentials, OutputStream outputStream) throws IOException { + if (Objects.isNull(streamFormat)) { + streamFormat = new KdbxStreamFormat(new KdbxHeader(4)); + } + save(streamFormat, credentials, outputStream); + } + + @Override + public void save(StreamFormat streamFormat, + Credentials credentials, + OutputStream outputStream) throws IOException { + KdbxStreamFormat kdbxStreamFormat = (KdbxStreamFormat) streamFormat; + KdbxHeader header = kdbxStreamFormat.getStreamConfiguration(); + OutputStream encryptedOutputStream = KdbxSerializer.createEncryptedOutputStream(credentials, header, outputStream); + BasicDatabaseSerializer bds = new BasicDatabaseSerializer.Xml(header.getInnerStreamEncryptor()); + bds.save(this, encryptedOutputStream); + } + + @Override + public StreamFormat getStreamFormat() { + return streamFormat; + } + + + /** + * On load add parents + * + * @param parent a parent to recurse + */ + void fixUp(BasicGroup parent) { + parent.database = this; + for (Group group : parent.getGroups()) { + BasicGroup basicGroup = (BasicGroup) group; + basicGroup.parent = parent; + fixUp(basicGroup); + } + + for (Entry entry : parent.getEntries()) { + BasicEntry basicEntry = (BasicEntry) entry; + basicEntry.database = this; + basicEntry.parent = parent; + } + } + +} diff --git a/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializer.java b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializer.java new file mode 100644 index 00000000..c44425f1 --- /dev/null +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializer.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.linguafranca.pwdb.PropertyValue; +import org.linguafranca.pwdb.StreamFormat; +import org.linguafranca.pwdb.protect.ProtectedDatabase; +import org.linguafranca.pwdb.security.StreamEncryptor; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +/** + * This class is responsible for serializing and deserializing a BasicDatabase to and from a stream. + */ +public interface BasicDatabaseSerializer { + + /** + * Serialize a BasicDatabase to a stream + * + * @param database the database to serialize + * @param outputStream the stream to serialize to + */ + void save(BasicDatabase database, OutputStream outputStream) throws IOException; + + /** + * Deserialize a BasicDatabase from a stream + * + * @param inputStream the serialized form of the database + * @return the deserialized database + */ + BasicDatabase load(InputStream inputStream) throws IOException; + + /** + * Serialize a BasicDatabase to a stream, with a runtime exception thrown if an IOException occurs + */ + default void saveNx(BasicDatabase database, OutputStream outputStream){ + try { + save(database, outputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Deserialize a BasicDatabase from a stream, with a runtime exception thrown if an IOException occurs + */ + default BasicDatabase loadNx(InputStream inputStream){ + try { + return load(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** + * XML implementation of containing BasicDatabaseSerializer + */ + class Xml implements BasicDatabaseSerializer { + private final ObjectMapper objectMapper; + private final StreamEncryptor streamEncryptor; + + public Xml() { + this(new StreamEncryptor.None()); + } + + public Xml(StreamEncryptor streamEncryptor) { + this.streamEncryptor = streamEncryptor; + this.objectMapper = init(); + } + + @Override + public void save(BasicDatabase database, OutputStream outputStream) throws IOException { + objectMapper.writer().withRootName("database").writeValue(outputStream, database); + } + + @Override + public BasicDatabase load(InputStream inputStream) throws IOException { + BasicDatabase database2 = objectMapper.readValue(inputStream, BasicDatabase.class); + database2.fixUp((BasicGroup) database2.getRootGroup()); + return database2; + } + + private ObjectMapper init() { + SimpleModule module = new SimpleModule() + .addDeserializer(PropertyValue.class, new PropertyValueDeserializer(streamEncryptor)) + .addSerializer(PropertyValue.class, new PropertyValueSerializer(streamEncryptor)) + .addDeserializer(BasicIcon.class, new IconDeserializer()) + .addSerializer(BasicIcon.class, new IconSerializer()); + XmlMapper mapper = XmlMapper.builder() + // ignore everything except fields + .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) + .visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE) + .visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE) + .visibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.NONE) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + // even though the source file is not annotated, we want annotations in the Configuration.class + .enable(MapperFeature.USE_ANNOTATIONS) + // add mixin to main classes + .addMixIn(BasicGroup.class, Configuration.class) + .addMixIn(BasicEntry.class, Configuration.class) + .addMixIn(BasicDatabase.class, Configuration.class) + .build(); + + + mapper + // for PropertyValue serialization + .registerModule(module) + // for Instant processing + .registerModule(new JavaTimeModule()) + // pretty print + .enable(SerializationFeature.INDENT_OUTPUT) + // suppress empty fields + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + return mapper; + } + + /** + * Configure special treatment on serialization of properties + */ + public static class PropertyValueSerializer extends JsonSerializer { + StreamEncryptor streamEncryptor; + + public PropertyValueSerializer() { + this(new StreamEncryptor.None()); + } + + public PropertyValueSerializer(StreamEncryptor streamEncryptor) { + this.streamEncryptor = streamEncryptor; + } + + /** + * Don't serialize empty properties + *

+ * TODO: at present, policy is to have all 5 standard properties present with empty + * value if necessary. Review policy. Deserialization messes up constructor + * behaviour which is to initialise those properties that way. + */ + @Override + public boolean isEmpty(SerializerProvider provider, PropertyValue value) { + // TODO: return (value.getValueAsString().isEmpty()); + return false; + } + + /** + * write protected properties with a flag to show they need to be protected on input + */ + @Override + public void serialize(PropertyValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + final ToXmlGenerator xmlGenerator = (ToXmlGenerator) gen; + // find out of we are serializing binaries + Object c = xmlGenerator.getOutputContext().getParent().getCurrentName(); + boolean asBinary = c.equals("binaries"); + xmlGenerator.writeStartObject(); + + if (value.isProtected()) { + xmlGenerator.setNextIsAttribute(true); + xmlGenerator.writeStringField("protected", "true"); + asBinary = true; // write password as binary + } + + if (asBinary) { + xmlGenerator.setNextIsAttribute(true); + xmlGenerator.writeStringField("binary", "true"); + } + xmlGenerator.setNextIsAttribute(false); + xmlGenerator.setNextIsUnwrapped(true); + + if (asBinary) { + byte[] bytes = value.getValueAsBytes(); + if (value.isProtected()) { + bytes = streamEncryptor.encrypt(bytes); + } + xmlGenerator.writeBinaryField("text", bytes); + } else { + xmlGenerator.writeStringField("text", value.getValueAsString()); + } + xmlGenerator.writeEndObject(); + } + } + + /** + * Property deserialization + */ + public static class PropertyValueDeserializer extends JsonDeserializer { + PropertyValue.Strategy strategy; + StreamEncryptor streamEncryptor; + + public PropertyValueDeserializer() { + this(new StreamEncryptor.None()); + } + + public PropertyValueDeserializer(StreamEncryptor streamEncryptor) { + this.streamEncryptor = streamEncryptor; + this.strategy = new PropertyValue.Strategy.Default(); + } + + @Override + public PropertyValue deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + + if (node.isTextual()) { + return strategy.newUnprotected().of(node.textValue()); + } + if (node.has("binary")) { + if (node.has("protected")) { + byte[] bytes = streamEncryptor.decrypt(node.get("").binaryValue()); + return strategy.newProtected().of(bytes); + } + return strategy.newUnprotected().of(node.get("").binaryValue()); + } + throw new IllegalStateException("Unknown property value format " + node); + } + } + + /** + * Configure special treatment for serialization of icons + */ + public static class IconSerializer extends JsonSerializer { + /** + * flatten icon representation + */ + @Override + public void serialize(BasicIcon icon, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + final ToXmlGenerator xmlGenerator = (ToXmlGenerator) gen; + xmlGenerator.writeStartObject(); + xmlGenerator.setNextIsAttribute(true); + xmlGenerator.writeStringField("index", String.valueOf(icon.getIndex())); + xmlGenerator.writeEndObject(); + } + } + + /** + * Icon deserialization + */ + public static class IconDeserializer extends JsonDeserializer { + PropertyValue.Strategy strategy = new PropertyValue.Strategy.Default(); + + @Override + public BasicIcon deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + + if (node.has("index")) { + return new BasicIcon(node.get("index").asInt()); + } + throw new IllegalStateException("Unknown icon value format " + node); + } + } + + /** + * Class is for binding Jackson annotations to a later Jackson "Mix-In" + */ + public abstract static class Configuration { + // Tell Jackson how to treat lists of Entry and Groups + @JsonPropertyOrder({ "uuid", "icon", "properties", "binaries" }) + protected static class BasicEntry {} + + @JacksonXmlProperty(localName = "entry") + @JacksonXmlElementWrapper(useWrapping = false) + @JsonPropertyOrder({ "uuid", "icon", "properties", "binaries" }) + protected List entries; + + @JsonPropertyOrder({ "uuid", "name", "icon", "group", "entry" }) + protected static class BasicGroup {} + + @JacksonXmlProperty(localName = "group") + @JacksonXmlElementWrapper(useWrapping = false) + protected List groups; + // tell Jackson to ignore these fields + @JsonIgnore + BasicGroup parent; + @JsonIgnore + BasicDatabase database; + @JsonIgnore + ProtectedDatabase valueStrategy; + @JsonIgnore + boolean isDirty; + @JsonIgnore + StreamFormat streamFormat; + } + } +} diff --git a/basic/src/main/java/org/linguafranca/pwdb/basic/BasicEntry.java b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicEntry.java new file mode 100644 index 00000000..adcbafdd --- /dev/null +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicEntry.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.jspecify.annotations.Nullable; +import org.linguafranca.pwdb.*; +import org.linguafranca.pwdb.abstractdb.AbstractEntry; + +import java.time.Instant; +import java.util.*; + +public class BasicEntry extends AbstractEntry { + protected BasicDatabase database; + protected BasicGroup parent; + + private final UUID uuid; + private BasicIcon icon; + private final Map properties = new HashMap<>(); + private final Map binaries = new HashMap<>(); + + private final Instant creationTime; + private @Nullable Instant accessTime; + private @Nullable Instant modifiedTime; + private Instant expiryTime; + private boolean isExpires; + + + public BasicEntry() { + this.uuid = UUID.randomUUID(); + this.creationTime = Instant.now(); + this.icon = new BasicIcon(1); + for (String name : Entry.STANDARD_PROPERTY_NAMES) { + properties.put(name, new PropertyValue.StringStore("")); + } + } + + public BasicEntry(BasicDatabase basicDatabase) { + this(); + this.database = basicDatabase; + } + + @Override + protected void touch() { + database.setDirty(true); + } + + @Override + public @Nullable String getProperty(String name) { + if (!STANDARD_PROPERTY_NAMES.contains(name) && !database.supportsNonStandardPropertyNames()) { + throw new IllegalArgumentException("Property " + name + " is not a standard property name"); + } + if (!properties.containsKey(name)) { + return null; + } + updateAccessTime(); + return properties.get(name).getValueAsString(); + } + + @Override + public BasicEntry setProperty(String name, String value) { + updateModifiedTime(); + properties.put(name, PropertyValue.StringStore.getFactory().of(value)); + return this; + } + + @Override + public boolean removeProperty(String name) throws IllegalArgumentException, UnsupportedOperationException { + if (Entry.STANDARD_PROPERTY_NAMES.contains(name)) { + throw new IllegalArgumentException("Cannot remove standard property"); + } + if (!database.supportsNonStandardPropertyNames()) { + throw new UnsupportedOperationException("Database does not support non-standard properties"); + } + boolean exists = properties.containsKey(name); + if (exists) { + updateModifiedTime(); + } + properties.remove(name); + return exists; + } + + @Override + public List getPropertyNames() { + return new ArrayList<>(properties.keySet()); + } + + @Override + public PropertyValue getPropertyValue(String name) { + return properties.get(name); + } + + @Override + public BasicEntry setPropertyValue(String name, PropertyValue value) { + properties.put(name, value); + return this; + } + + public BasicEntry addProperty(String name, byte[] value){ + properties.put(name, database.getPropertyValueStrategy().getFactoryFor(name).of(value)); + return this; + } + public BasicEntry addProperty(String name, char[] value) { + properties.put(name, database.getPropertyValueStrategy().getFactoryFor(name).of(value)); + return this; + } + + public BasicEntry addProperty(String name, CharSequence value){ + properties.put(name, database.getPropertyValueStrategy().getFactoryFor(name).of(value)); + return this; + } + @Override + public byte@Nullable [] getBinaryProperty(String name) { + if (!binaries.containsKey(name)) { + return null; + } + updateAccessTime(); + return binaries.get(name).getValueAsBytes(); + } + + @Override + public void setBinaryProperty(String name, byte[] value) { + updateModifiedTime(); + binaries.put(name, PropertyValue.BytesStore.getFactory().of(value)); + } + + @Override + public boolean removeBinaryProperty(String name) throws UnsupportedOperationException { + boolean exists = binaries.containsKey(name); + if (exists) {updateModifiedTime();} + binaries.remove(name); + return exists; + } + + @Override + public List getBinaryPropertyNames() { + return List.of(binaries.keySet().toArray(String[]::new)); + } + + @Override + public BasicDatabase getDatabase() { + return database; + } + + @Override + public @Nullable BasicGroup getParent() { + return parent; + } + + @Override + public UUID getUuid() { + return uuid; + } + + @Override + public BasicIcon getIcon() { + return icon; + } + + @Override + public void setIcon(Icon icon) { + this.icon = (BasicIcon) icon; + } + + @Override + public Date getLastAccessTime() { + if (accessTime == null) { + return fromInstant(this.creationTime); + } + return fromInstant(this.accessTime); + } + + @Override + public Date getCreationTime() { + return fromInstant(creationTime); + } + + @Override + public boolean getExpires() { + return this.isExpires; + } + + @Override + public void setExpires(boolean expires) { + this.isExpires = expires; + } + + @Override + public Date getExpiryTime() { + return fromInstant(this.expiryTime); + } + + @Override + public void setExpiryTime(Date expiryTime) throws IllegalArgumentException { + this.expiryTime = expiryTime.toInstant(); + } + + @Override + public Date getLastModificationTime() { + if (modifiedTime == null) { + return fromInstant(this.creationTime); + } + return fromInstant(this.modifiedTime); + } + + private void updateAccessTime() { + accessTime = Instant.now(); + touch(); + } + + private void updateModifiedTime() { + modifiedTime = Instant.now(); + touch(); + } + + private static @Nullable Date fromInstant(@Nullable Instant instant) { + if (instant == null) { + return null; + } + return Date.from(instant); + } +} diff --git a/basic/src/main/java/org/linguafranca/pwdb/basic/BasicGroup.java b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicGroup.java new file mode 100644 index 00000000..adde7c00 --- /dev/null +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicGroup.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.jspecify.annotations.Nullable; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Group; +import org.linguafranca.pwdb.Icon; +import org.linguafranca.pwdb.abstractdb.AbstractGroup; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class BasicGroup extends AbstractGroup { + protected BasicGroup parent; + protected BasicDatabase database; + private final UUID uuid; + private String name; + private BasicIcon icon; + + private final List groups = new ArrayList<>(); + private final List entries = new ArrayList<>(); + + + + BasicGroup () { + this.database = null; + this.uuid = UUID.randomUUID(); + this.icon = new BasicIcon(); + this.name = ""; + } + BasicGroup(BasicDatabase database) { + this(); + this.database = database; + } + + BasicGroup(BasicDatabase database, String name) { + this(database); + this.name = name; + } + + @Override + public boolean isRootGroup() { + return database.getRootGroup().equals(this); + } + + @Override + public boolean isRecycleBin() { + return false; + } + + @Override + public @Nullable BasicGroup getParent() { + return parent; + } + + @Override + public void setParent(Group parent) { + this.parent = (BasicGroup) parent; + } + + @Override + public List getGroups() { + return new ArrayList<>(groups); + } + + @Override + public int getGroupsCount() { + return groups.size(); + } + + @Override + public Group addGroup(Group group) { + BasicGroup basicGroup = (BasicGroup) group; + groups.add(basicGroup); + basicGroup.parent = this; + return group; + } + + @Override + public Group removeGroup(Group group) { + BasicGroup basicGroup = (BasicGroup) group; + groups.remove(basicGroup); + basicGroup.parent = null; + return group; + } + + @Override + public List getEntries() { + return Collections.unmodifiableList(entries); + } + + @Override + public int getEntriesCount() { + return entries.size(); + } + + @Override + public BasicEntry addEntry(Entry entry) { + BasicEntry basicEntry = (BasicEntry) entry; + entries.add(basicEntry); + basicEntry.parent = this; + return basicEntry; + } + + @Override + public BasicEntry removeEntry(Entry entry) { + BasicEntry basicEntry = (BasicEntry) entry; + entries.remove(basicEntry); + (basicEntry).parent = null; + return basicEntry; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void setName(String name) { + this.name=name; + } + + @Override + public UUID getUuid() { + return this.uuid; + } + + @Override + public Icon getIcon() { + return this.icon; + } + + @Override + public void setIcon(Icon icon) { + this.icon = (BasicIcon) icon; + } + + @Override + public Database getDatabase() { + return this.database; + } +} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbIcon.java b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicIcon.java similarity index 62% rename from jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbIcon.java rename to basic/src/main/java/org/linguafranca/pwdb/basic/BasicIcon.java index c558be3b..19006b47 100644 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbIcon.java +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/BasicIcon.java @@ -1,36 +1,34 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.jaxb; +package org.linguafranca.pwdb.basic; import org.linguafranca.pwdb.Icon; -/** - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class JaxbIcon implements Icon{ +import java.util.Objects; + +public class BasicIcon implements Icon { private int index; - public JaxbIcon(int iconID) { - index = iconID; + public BasicIcon() { } - public JaxbIcon() { - index = 0; + public BasicIcon(Integer i) { + this.index = i; } @Override @@ -45,17 +43,13 @@ public void setIndex(int index) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - JaxbIcon that = (JaxbIcon) o; - - return index == that.index; - + if (!(o instanceof BasicIcon)) return false; + BasicIcon basicIcon = (BasicIcon) o; + return index == basicIcon.index; } @Override public int hashCode() { - return index; + return Objects.hashCode(index); } } diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBasicDatabaseTest.java b/basic/src/main/java/org/linguafranca/pwdb/basic/package-info.java similarity index 52% rename from jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBasicDatabaseTest.java rename to basic/src/main/java/org/linguafranca/pwdb/basic/package-info.java index d4d08454..21f36f62 100644 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBasicDatabaseTest.java +++ b/basic/src/main/java/org/linguafranca/pwdb/basic/package-info.java @@ -1,36 +1,28 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.jaxb; - -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; -import org.linguafranca.pwdb.Database; - -import java.io.IOException; - /** - * @author jo + * This package contains the interfaces that define the org.linguafranca Database API. + *

+ * The API is designed to be a simple, low-level, read-write API for password databases. + *

+ * Class instances of the interfaces are contained in the sub-packages of this package. */ -public class JaxbBasicDatabaseTest extends BasicDatabaseChecks { - - public JaxbBasicDatabaseTest() throws IOException { - } +@NullMarked +package org.linguafranca.pwdb.basic; - @Override - public Database createDatabase() { - return new JaxbDatabase(); - } -} +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseKdbxTest.java b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseKdbxTest.java new file mode 100644 index 00000000..47409b70 --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseKdbxTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.format.KdbxStreamFormat; +import org.linguafranca.pwdb.test.DatabaseTestBase; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.*; + +@Execution(CONCURRENT) +public class BasicDatabaseKdbxTest { + + private static final Supplier dbFunc = ()-> new DatabaseTestBase(BasicDatabase::new, + (_, inputStream) -> + new BasicDatabaseSerializer.Xml().loadNx(inputStream), + (database, _, outputStream) -> + new BasicDatabaseSerializer.Xml().saveNx((BasicDatabase) database, outputStream), + (_) -> null); + + @Test + public void testKdbx(@TempDir Path tempPath) throws IOException { + DatabaseTestBase databaseTestBase = BasicDatabaseKdbxTest.dbFunc.get(); + Database db = databaseTestBase.database; + db.setName("test"); + db.getRootGroup().addEntry("test") + .addProperty(PASSWORD,"password") + .addProperty(URL,"http://example.com") + .addProperty(USER_NAME,"user"); + + Path dbPath = tempPath.resolve("test.kdbx"); + db.save(new KdbxStreamFormat(), new KdbxCredentials(KdbConsts.PASSWORD_BYTES), Files.newOutputStream(dbPath)); + + KdbxHeader header = new KdbxHeader(); + InputStream unencryptedInputStream = KdbxSerializer.createUnencryptedInputStream(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), + header, Files.newInputStream(dbPath)); + + BasicDatabaseSerializer.Xml xml = new BasicDatabaseSerializer.Xml(header.getInnerStreamEncryptor()); + Database db2 = xml.load(unencryptedInputStream); + assertEquals(db.getName(), db2.getName()); + assertEquals(db2.getRootGroup().findEntries(e -> true, true).size(), db.getRootGroup().findEntries(e -> true, true).size()); + assertEquals("password", db2.getRootGroup().findEntries(e -> e.getTitle().equals("test"), true).get(0).getPropertyValue(PASSWORD).getValueAsString()); + } +} diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializerTest.java b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializerTest.java new file mode 100644 index 00000000..5cfe5ca1 --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseSerializerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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.basic; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Group; +import org.linguafranca.pwdb.Visitor; +import org.linguafranca.pwdb.security.StreamEncryptor; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +@Execution(CONCURRENT) +class BasicDatabaseSerializerTest { + + static BasicDatabase setUpBasicDatabase() { + + BasicDatabase database; + database = new BasicDatabase(); + database.setName("My Database"); + database.setDescription("A database of passwords"); + database.getRootGroup().addGroup("Group 1") + .addEntry("Entry 1") + .addProperty(Entry.STANDARD_PROPERTY_NAME.USER_NAME, "John Doe") + .addProperty(Entry.STANDARD_PROPERTY_NAME.PASSWORD, "password123".getBytes()) + .addEntry("Entry 2") + .addProperty(Entry.STANDARD_PROPERTY_NAME.USER_NAME, "Jane Doe") + .addProperty(Entry.STANDARD_PROPERTY_NAME.PASSWORD, "password123".getBytes()) + .addProperty("non-standard", "non-standard value") + .addEntry("Entry 3") + .setBinaryProperty("gif", new byte[]{1, 2, 3, 4, 5}); + return database; + } + + public static Visitor setUpVisitor(BasicDatabase database, PrintStream out) { + Visitor visitor = new Visitor() { + @Override + public void startVisit(Group group) { + out.println("Group: " + group.getName() + " " + group.getUuid()); + assertNotNull(database.findGroup(group.getUuid())); + assertNotNull(group.getDatabase()); + if (!group.isRootGroup()) { + assertNotNull(group.getParent()); + } + } + + @Override + public void endVisit(Group group) { + + } + + @Override + public void visit(Entry entry) { + out.println("Entry: " + entry.getTitle() + " " + entry.getUuid()); + for (String propertyName: entry.getPropertyNames()) { + out.println(" " + propertyName + ": " + entry.getPropertyValue(propertyName).getValueAsString()); + } + for (String propertyName: entry.getBinaryPropertyNames()) { + out.println(" " + propertyName + ": " + Arrays.toString(entry.getBinaryProperty(propertyName))); + } + Entry entry1 = database.findEntry(entry.getUuid()); + assertNotNull(entry1, "Entry not found " + entry.getUuid()); + assertEquals(entry.getPropertyNames().size(), entry1.getPropertyNames().size()); + assertNotNull(entry.getDatabase()); + assertNotNull(entry.getParent()); + } + + @Override + public boolean isEntriesFirst() { + return false; + } + }; + return visitor; + } + + @Test + void saveAndLoad(@TempDir Path tempPath) throws IOException { + PrintStream out = getTestPrintStream(); + + BasicDatabase basicDatabase = setUpBasicDatabase(); + OutputStream outputStream = Files.newOutputStream(tempPath.resolve("test.xml")); + new BasicDatabaseSerializer.Xml(new StreamEncryptor.Salsa20(new byte[0])).save(basicDatabase, outputStream); + + InputStream inputStream = Files.newInputStream(tempPath.resolve( "test.xml")); + BasicDatabase loadedDatabase = new BasicDatabaseSerializer.Xml(new StreamEncryptor.Salsa20(new byte[0])).load(inputStream); + loadedDatabase.visit(setUpVisitor(basicDatabase, out)); + out.println(loadedDatabase.getRootGroup().getDatabase().getName()); + } +} \ No newline at end of file diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseTest.java b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseTest.java new file mode 100644 index 00000000..61c3ca01 --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicDatabaseTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.test.*; + +import java.util.function.Supplier; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@Execution(CONCURRENT) +public class BasicDatabaseTest + implements + TrivialDatabaseTest, + GroupsAndEntriesTest, + RecycleBinTest, + ProtectedPropertyTest, + ProtectedPropertyTest2 { + + private static final Supplier dbFunc = ()-> new DatabaseTestBase(BasicDatabase::new, + (_, inputStream) -> + new BasicDatabaseSerializer.Xml().loadNx(inputStream), + (database, _, outputStream) -> + new BasicDatabaseSerializer.Xml().saveNx((BasicDatabase) database, outputStream), + (_) -> null); + + @Override + public Supplier getDbFunc() { + return dbFunc; + } +} diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/BasicEntryTest.java b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicEntryTest.java new file mode 100644 index 00000000..d0b923cb --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicEntryTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; + +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@Execution(CONCURRENT) +class BasicEntryTest { + + @Test + void testGetProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + entry.setProperty("name", "value"); + assertEquals("value", entry.getProperty("name")); + } + + @Test + void testSetProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + entry.setProperty("name", "value"); + assertEquals("value", entry.getProperty("name")); + } + + @Test + void testRemoveProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + entry.setProperty("name", "value"); + assertTrue(entry.removeProperty("name")); + assertNull(entry.getProperty("name")); + } + + @Test + void testGetPropertyNames() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + entry.setProperty("name1", "value1"); + entry.setProperty("name2", "value2"); + List propertyNames = entry.getPropertyNames(); + assertTrue(propertyNames.contains("name1")); + assertTrue(propertyNames.contains("name2")); + } + + @Test + void testGetBinaryProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + byte[] data = {1, 2, 3}; + entry.setBinaryProperty("binary", data); + assertArrayEquals(data, entry.getBinaryProperty("binary")); + } + + @Test + void testSetBinaryProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + byte[] data = {1, 2, 3}; + entry.setBinaryProperty("binary", data); + assertArrayEquals(data, entry.getBinaryProperty("binary")); + } + + @Test + void testRemoveBinaryProperty() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + byte[] data = {1, 2, 3}; + entry.setBinaryProperty("binary", data); + assertTrue(entry.removeBinaryProperty("binary")); + assertNull(entry.getBinaryProperty("binary")); + } + + @Test + void testGetBinaryPropertyNames() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + + byte[] data1 = {1, 2, 3}; + byte[] data2 = {4, 5, 6}; + entry.setBinaryProperty("binary1", data1); + entry.setBinaryProperty("binary2", data2); + List binaryPropertyNames = entry.getBinaryPropertyNames(); + assertTrue(binaryPropertyNames.contains("binary1")); + assertTrue(binaryPropertyNames.contains("binary2")); + } + + @Test + void testGetParent() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + assertNull(entry.getParent()); + } + + @Test + void testGetUuid() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + UUID uuid = entry.getUuid(); + assertNotNull(uuid); + } + + @Test + void testGetIcon() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + assertNotNull(entry.getIcon()); + } + + @Test + void testSetIcon() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + entry.setIcon(new BasicIcon(1) {}); + assertEquals(1, entry.getIcon().getIndex()); + } + + @Test + void testGetLastAccessTime() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + Date lastAccessTime = entry.getLastAccessTime(); + assertNotNull(lastAccessTime); + } + + @Test + void testGetCreationTime() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + Date creationTime = entry.getCreationTime(); + assertNotNull(creationTime); + } + + @Test + void testGetExpires() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + assertFalse(entry.getExpires()); + } + + @Test + void testSetExpires() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + entry.setExpires(true); + assertTrue(entry.getExpires()); + } + + @Test + void testGetExpiryTime() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + assertNull(entry.getExpiryTime()); + } + + @Test + void testSetExpiryTime() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + Date expiryTime = Date.from(Instant.now()); + entry.setExpiryTime(expiryTime); + assertEquals(expiryTime, entry.getExpiryTime()); + } + + @Test + void testGetLastModificationTime() { + BasicEntry entry= new BasicEntry(new BasicDatabase()); + assertEquals(entry.getCreationTime(), entry.getLastModificationTime()); + } +} diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/BasicGroupTest.java b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicGroupTest.java new file mode 100644 index 00000000..9882b169 --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/BasicGroupTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.basic; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Group; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@Execution(CONCURRENT) +class BasicGroupTest { + + @Test + void testGetName() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + group.setName("Test Group"); + assertEquals("Test Group", group.getName()); + } + + @Test + void testSetName() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + group.setName("Test Group"); + assertEquals("Test Group", group.getName()); + } + + @Test + void testGetEntries() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Entry entry = new BasicEntry(new BasicDatabase()); + group.addEntry(entry); + List entries = group.getEntries(); + assertTrue(entries.contains(entry)); + } + + @Test + void testAddEntry() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Entry entry = new BasicEntry(new BasicDatabase()); + group.addEntry(entry); + assertTrue(group.getEntries().contains(entry)); + } + + @Test + void testRemoveEntry() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Entry entry = new BasicEntry(new BasicDatabase()); + group.addEntry(entry); + group.removeEntry(entry); + assertFalse(group.getEntries().contains(entry)); + } + + @Test + void testGetGroups() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Group subGroup = new BasicGroup(new BasicDatabase()); + group.addGroup(subGroup); + List groups = group.getGroups(); + assertTrue(groups.contains(subGroup)); + } + + @Test + void testAddGroup() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Group subGroup = new BasicGroup(new BasicDatabase()); + group.addGroup(subGroup); + assertTrue(group.getGroups().contains(subGroup)); + } + + @Test + void testRemoveGroup() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + Group subGroup = new BasicGroup(new BasicDatabase()); + group.addGroup(subGroup); + assertTrue(group.getGroups().contains(subGroup)); + assertEquals(1, group.getGroups().size()); + assertEquals(group, subGroup.getParent()); + group.removeGroup(subGroup); + assertEquals(0, group.getGroups().size()); + assertNull(subGroup.getParent()); + assertFalse(group.getGroups().contains(subGroup)); + } + + @Test + void testGetParent() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + assertNull(group.getParent()); + } + + @Test + void testGetUuid() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + UUID uuid = group.getUuid(); + assertNotNull(uuid); + } + + @Test + void testGetIcon() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + assertEquals(0, group.getIcon().getIndex()); + } + + @Test + void testSetIcon() { + BasicGroup group = new BasicGroup(new BasicDatabase()); + group.setIcon(new BasicIcon(1)); + assertEquals(1, group.getIcon().getIndex()); + } +} \ No newline at end of file diff --git a/basic/src/test/java/org/linguafranca/pwdb/basic/package-info.java b/basic/src/test/java/org/linguafranca/pwdb/basic/package-info.java new file mode 100644 index 00000000..5ecf339c --- /dev/null +++ b/basic/src/test/java/org/linguafranca/pwdb/basic/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.basic; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/basic/src/test/resources/junit-platform.properties b/basic/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..2115a2f1 --- /dev/null +++ b/basic/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = true diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml deleted file mode 100644 index a8c1de44..00000000 --- a/bitbucket-pipelines.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This is a sample build configuration for Java (Maven). -# Check our guides at https://confluence.atlassian.com/x/zd-5Mw for more examples. -# Only use spaces to indent your .yml configuration. -# ----- -# You can specify a custom docker image from Docker Hub as your build environment. -image: maven:3.5.2-jdk-7 - -pipelines: - #only on this branch - bitbucket-build: - - step: - caches: - - maven - script: # Modify the commands below to build your repository. - - mvn -B verify # -B batch mode makes Maven less verbose \ No newline at end of file diff --git a/database/pom.xml b/database/pom.xml index f4095dec..5fb14aab 100644 --- a/database/pom.xml +++ b/database/pom.xml @@ -1,31 +1,71 @@ - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - ../pom.xml - - 4.0.0 + - database - PWDB :: Database - Contains root definitions of PWDB Databases etc. + + + KeePassJava2.parent + mh.keepass + 1.0.0.1 + ../pom.xml + + 4.0.0 - - - org.jetbrains - annotations - 24.1.0 - - - com.google.guava - guava - 33.2.1-android - + database + PWDB :: Database + Contains root definitions of PWDB Databases etc. + + + + + org.bouncycastle + bcpkix-jdk18on + - org.bouncycastle - bcpkix-jdk18on + + commons-io + commons-io - + + com.google.code.findbugs + jsr305 + 3.0.2 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + \ No newline at end of file diff --git a/database/src/main/java/org/linguafranca/pwdb/Credentials.java b/database/src/main/java/org/linguafranca/pwdb/Credentials.java index 7f03b64e..58dc7a5b 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Credentials.java +++ b/database/src/main/java/org/linguafranca/pwdb/Credentials.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; diff --git a/database/src/main/java/org/linguafranca/pwdb/Database.java b/database/src/main/java/org/linguafranca/pwdb/Database.java index 632f35ae..a92231a7 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Database.java +++ b/database/src/main/java/org/linguafranca/pwdb/Database.java @@ -1,22 +1,23 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import java.io.IOException; import java.io.OutputStream; @@ -49,26 +50,26 @@ * the caller without affecting the underlying database structure, however changes * to the Groups and Entries contained in the lists do modify the database. */ -public interface Database , G extends Group, E extends Entry, I extends Icon> { +public interface Database { /** * get the root group for the database * @return the root group */ - G getRootGroup(); + Group getRootGroup(); /** * Create a new Group * @return the group created */ - G newGroup(); + Group newGroup(); /** * Create a new named Group * @param name the name of the group * @return the group created */ - G newGroup(String name); + Group newGroup(String name); /** * Create a new Group copying the details of the supplied group, but not copying its children @@ -77,19 +78,19 @@ public interface Database , G extends Group group); + Group newGroup(Group group); /** * Create a new Entry * @return the entry created */ - E newEntry(); + Entry newEntry(); /** * Create a new Entry with a title * @return the entry created */ - E newEntry(String title); + Entry newEntry(String title); /** * Create a new Entry copying the details of the supplied entry @@ -98,27 +99,27 @@ public interface Database , G extends Group entry); + Entry newEntry(Entry entry); /** * Create a new default icon * @return the created icon */ - I newIcon(); + Icon newIcon(); /** * Create a new icon with a specified index * @param i the index of the icon to create * @return the created icon */ - I newIcon(Integer i); + Icon newIcon(Integer i); /** * Find an entry with this UUID anywhere in the database except the recycle bin * @param uuid the UUID * @return an entry or null if not found */ - @Nullable E findEntry(UUID uuid); + @Nullable Entry findEntry(UUID uuid); /** * Delete an entry with this UUID from anywhere in the database except the recycle bin @@ -133,7 +134,7 @@ public interface Database , G extends Group, G extends Group, G extends Group visitor); + void visit(Visitor visitor); /** * Visit all entries starting from a group * @param group the group to start at * @param visitor the visitor to use */ - void visit(G group, Visitor visitor); + void visit(Group group, Visitor visitor); /** * Find all entries that match the criteria @@ -192,7 +193,7 @@ public interface Database , G extends Group findEntries(Entry.Matcher matcher); + List findEntries(Entry.Matcher matcher); /** * Find all entries that match {@link Entry#match(String)} @@ -200,7 +201,7 @@ public interface Database , G extends Group findEntries(String find); + List findEntries(String find); /** * Gets the name of the database or null if not supported @@ -237,10 +238,20 @@ public interface Database , G extends Group void save(StreamFormat streamFormat, Credentials credentials, OutputStream outputStream) throws IOException; + /** + * Save the database to a stream and closes the stream - avoiding checked exception + */ + void saveNx(StreamFormat streamFormat, Credentials credentials, OutputStream outputStream); + /** * Get the format the database was loaded from */ @@ -264,12 +275,13 @@ public interface Database , G extends Group listShouldProtect(); /** * Get the default means of storage of unprotected and protected property values */ - public PropertyValue.Strategy getPropertyValueStrategy(); + PropertyValue.Strategy getPropertyValueStrategy(); /** * Set the default means of storage of unprotected and protected property values diff --git a/database/src/main/java/org/linguafranca/pwdb/Entry.java b/database/src/main/java/org/linguafranca/pwdb/Entry.java index 2c2a95a3..1d7acf64 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Entry.java +++ b/database/src/main/java/org/linguafranca/pwdb/Entry.java @@ -1,23 +1,23 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; @@ -43,30 +43,40 @@ * * @author Jo */ -public interface Entry , G extends Group, E extends Entry, I extends Icon> { +public interface Entry { /** * Standard properties are attributes of Entries that are accessible either by * dedicated methods, such as getPassword, or by {@link #getProperty(String)} */ + String STANDARD_PROPERTY_NAME_TITLE = "Title"; String STANDARD_PROPERTY_NAME_USER_NAME = "UserName"; String STANDARD_PROPERTY_NAME_PASSWORD = "Password"; String STANDARD_PROPERTY_NAME_URL = "URL"; - String STANDARD_PROPERTY_NAME_TITLE = "Title"; String STANDARD_PROPERTY_NAME_NOTES = "Notes"; - List STANDARD_PROPERTY_NAMES = Collections.unmodifiableList(Arrays.asList( + /** allows for static import of name */ + interface STANDARD_PROPERTY_NAME { + String TITLE = STANDARD_PROPERTY_NAME_TITLE; + String USER_NAME = STANDARD_PROPERTY_NAME_USER_NAME; + String PASSWORD = STANDARD_PROPERTY_NAME_PASSWORD; + String URL = STANDARD_PROPERTY_NAME_URL; + String NOTES = STANDARD_PROPERTY_NAME_NOTES; + } + + List STANDARD_PROPERTY_NAMES = List.of( + STANDARD_PROPERTY_NAME_TITLE, STANDARD_PROPERTY_NAME_USER_NAME, STANDARD_PROPERTY_NAME_PASSWORD, STANDARD_PROPERTY_NAME_URL, - STANDARD_PROPERTY_NAME_TITLE, - STANDARD_PROPERTY_NAME_NOTES)); + STANDARD_PROPERTY_NAME_NOTES); /** * Interface to implement when using the {@link #match(Entry.Matcher)} * method */ interface Matcher { + // needs to be raw type so that we can use lambdas boolean matches(Entry entry); } @@ -104,7 +114,7 @@ interface Matcher { * @return a value or null if the property is not known, or if setting of arbitrary properties is not supported * @see Database#supportsNonStandardPropertyNames() */ - String getProperty(String name); + @Nullable String getProperty(String name); /** * Gets the value of a property as a PropertyValue. @@ -118,7 +128,8 @@ interface Matcher { PropertyValue getPropertyValue(String name); /** - * Sets the value of a property. Use of this method is not recommended for fields with protected values. + * Sets the value of a property as a String in memory. + * Use of this method is not recommended for fields with protected values. * Use {@link #setPropertyValue(String, PropertyValue)} * *

Other than the {@link #STANDARD_PROPERTY_NAMES} support for this method is optional. @@ -129,10 +140,10 @@ interface Matcher { * non-standard properties are not supported * @see Database#supportsNonStandardPropertyNames() */ - void setProperty(String name, String value); + Entry setProperty(String name, String value); /** - * Sets the value of a property as a property value. The method does not check whether the + * Sets the value of a property as a property value in memory. The method does not check whether the * passed PropertyValue is protected relative to {@link Database#shouldProtect(String)} * *

Other than the {@link #STANDARD_PROPERTY_NAMES} support for this method is optional. @@ -143,10 +154,71 @@ interface Matcher { * non-standard properties are not supported * @see Database#supportsNonStandardPropertyNames() */ - void setPropertyValue(String name, PropertyValue value); + Entry setPropertyValue(String name, PropertyValue value); + + /** + * Adds (or modifies) the value of a property as a property value in memory. The method uses the + * PropertyValue.Strategy to determine how to determine the storage format in memory. + * + *

Other than the {@link #STANDARD_PROPERTY_NAMES} support for this method is optional. + * + * @param name the name of the property to set + * @param value the value to set it to + * @throws UnsupportedOperationException if the name is not one of the standard properties and + * non-standard properties are not supported + * @see Database#supportsNonStandardPropertyNames() + */ + Entry addProperty(String name, byte[] value); /** - * Removes this non-standard property, if it exists. + * Adds (or modifies) the value of a property as a property value in memory. The method uses the + * PropertyValue.Strategy to determine how to determine the storage format in memory. + * + *

Other than the {@link #STANDARD_PROPERTY_NAMES} support for this method is optional. + * + * @param name the name of the property to set + * @param value the value to set it to + * @throws UnsupportedOperationException if the name is not one of the standard properties and + * non-standard properties are not supported + * @see Database#supportsNonStandardPropertyNames() + */ + Entry addProperty(String name, char[] value); + + /** + * Adds (or modifies) the value of a property as a property value in memory. The method uses the + * PropertyValue.Strategy to determine how to determine the storage format in memory. + * + *

Other than the {@link #STANDARD_PROPERTY_NAMES} support for this method is optional. + * + * @param name the name of the property to set + * @param value the value to set it to + * @throws UnsupportedOperationException if the name is not one of the standard properties and + * non-standard properties are not supported + * @see Database#supportsNonStandardPropertyNames() + */ + Entry addProperty(String name, CharSequence value); + + /** + * Adds a newly created entry to the Group parent of the current Entry, if there is one. Intended primary for + * fluid building of entries within a group. + * + * @throws IllegalStateException if there is no parent + * @param name the title of the entry + * @return the newly created entry + */ + Entry addEntry(String name); + + /** + * Adds a newly created entry to the Group parent of the current Entry, if there is one. Intended primary for + * fluid building of entries within a group. + * + * @throws IllegalStateException if there is no parent + * @return the newly created entry + */ + Entry addEntry(); + + /** + * Removes this property, if it exists and if it is a non-standard property * * @return true if the property exists and was removed, false otherwise * @param name the value of the property to remove @@ -174,7 +246,7 @@ interface Matcher { * @return a value or null if the property is not known, or if setting of arbitrary properties is not supported * @see Database#supportsBinaryProperties */ - byte[] getBinaryProperty(String name); + byte@Nullable [] getBinaryProperty(String name); /** * Sets the value of a binary property. @@ -209,11 +281,17 @@ interface Matcher { */ List getBinaryPropertyNames(); + /** + * Get the database this entry belongs to + * @return a database + */ + Database getDatabase(); + /** * Get the parent of this entry * @return a parent or null if this entry currently does not have a parent */ - @Nullable G getParent(); + @Nullable Group getParent(); /** * Get the UUID of this entry. Databases (like KDB) that do not natively support @@ -221,7 +299,7 @@ interface Matcher { * * @return a UUID */ - @NotNull UUID getUuid(); + UUID getUuid(); /** * Get the username field of this entry @@ -248,6 +326,7 @@ interface Matcher { *

Implementations should Touch LastAccessedTime when this method is called. * * @return a password + * @deprecated use {@link #getPropertyValue(String)} with {@link #STANDARD_PROPERTY_NAME_PASSWORD} */ @Deprecated String getPassword(); @@ -258,7 +337,7 @@ interface Matcher { *

Implementations should Touch LastModifiedTime when this method is called. * * @param pass a password - * @deprecated use {@link #setPropertyValue(String, PropertyValue)} + * @deprecated use {@link #setPropertyValue(String, PropertyValue)} with {@link #STANDARD_PROPERTY_NAME_PASSWORD} */ @Deprecated void setPassword(String pass); @@ -342,13 +421,13 @@ interface Matcher { * Returns the {@link Icon} associated with this entry. * @return an Icon */ - I getIcon(); + Icon getIcon(); /** * Sets the {@link Icon} associated with this entry. * @param icon an Icon */ - void setIcon(I icon); + void setIcon(Icon icon); /** * Returns the date at which any value was retrieved from this entry. diff --git a/database/src/main/java/org/linguafranca/pwdb/Group.java b/database/src/main/java/org/linguafranca/pwdb/Group.java index 152a3f12..dae9cf23 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Group.java +++ b/database/src/main/java/org/linguafranca/pwdb/Group.java @@ -1,23 +1,22 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.jetbrains.annotations.NotNull; - import javax.annotation.Nullable; import java.util.List; import java.util.UUID; @@ -37,7 +36,7 @@ * * @author Jo */ -public interface Group , G extends Group, E extends Entry, I extends Icon> { +public interface Group { /** * Returns true if this is the root group of a database */ @@ -54,7 +53,7 @@ public interface Group , G extends Group, G extends Group, G extends Group getGroups(); + List getGroups(); /** * Returns the number of groups that are direct children of this group * *

It's possible that returning a list as in {@link #getGroups()} may incur significantly - * more overhead so use this method if only the count is reuqired + * more overhead so use this method if only the count is required */ int getGroupsCount(); @@ -90,7 +89,15 @@ public interface Group , G extends Group, G extends Group findGroups(String groupName); + List findGroups(String groupName); /** * Removes the group supplied from this group. The group removed - * no longer has a parent and so in not part of the database any more unless + * no longer has a parent and so in no longer part of the database unless * it is re-added to another group. *

- * If the group is incompatible with the databse an exception is thrown. + * If the group is incompatible with the database an exception is thrown. *

* If the group is not present no error is thrown. * @param group the group to remove * @return the group passed for removal * @throws IllegalArgumentException if the group is not a child of this group */ - G removeGroup(G group); + Group removeGroup(Group group); /** * Returns a modifiable by the caller list of entries contained in this group. */ - List getEntries(); + List getEntries(); /** * Returns the number of entries in this group * *

It's possible that returning a list as in {@link #getEntries()} may incur significantly - * more overhead so use this method if only the count is reuqired + * more overhead so use this method if only the count is required */ int getEntriesCount(); @@ -135,14 +142,14 @@ public interface Group , G extends GroupEntry match is described under {@link Entry#match(String)} * - *

Note: finding within recycle bin is supported, recusion into recycle bin is inhibited + *

Note: finding within recycle bin is supported, recursion into recycle bin is inhibited * * @param match the text to match - * @param recursive whether to include sub groups in the process + * @param recursive whether to include subgroups in the process * @return a modifiable-by-caller list * @see Entry#match(String) */ - List findEntries(String match, boolean recursive); + List findEntries(String match, boolean recursive); /** * Finds all entries in this group that match using the matcher supplied. @@ -150,14 +157,14 @@ public interface Group , G extends GroupEntry match is described under {@link Entry#match(Entry.Matcher)} * - *

Note: finding within recycle bin is supported, recusion into recycle bin is inhibited + *

Note: finding within recycle bin is supported, recursion into recycle bin is inhibited * - * @param matcher the mathcher to use - * @param recursive whether to include sub groups in the process + * @param matcher the matcher to use + * @param recursive whether to include subgroups in the process * @return a modifiable-by-caller list * @see Entry#match(Entry.Matcher) */ - List findEntries(Entry.Matcher matcher, boolean recursive); + List findEntries(Entry.Matcher matcher, boolean recursive); /** * Adds an entry to this group removing it from another group @@ -165,7 +172,20 @@ public interface Group , G extends Group, G extends Group parent); + void copy(Group parent); /** * Returns an XPath-like string of the names of Groups from the Root @@ -210,10 +230,10 @@ public interface Group , G extends Group getFactoryFor(String prop newUnprotected(); } + /** + * The default implementation of the Strategy interface in which the only protected property is Password. + * It uses SealedStore and BytesStore for protected and unprotected properties. It is immutable. + */ class Default implements Strategy { @Override public List getProtectedProperties() { - //noinspection ArraysAsListWithZeroOrOneArgument - return new ArrayList<>(Arrays.asList(Entry.STANDARD_PROPERTY_NAME_PASSWORD)); + return List.of(Entry.STANDARD_PROPERTY_NAME_PASSWORD); } @Override @@ -122,15 +140,31 @@ public Factory newUnprotected() { return BytesStore.getFactory(); } } + + /** + * A mutable implementation of the Strategy interface, which allows the protected properties to be set. + * By default, the only protected property is Password. + * It uses SealedStore and BytesStore for protected and unprotected properties. + */ + class MutableProtectionStrategy extends PropertyValue.Strategy.Default { + List protectedProperties = new ArrayList<>(); + { + protectedProperties.add(Entry.STANDARD_PROPERTY_NAME_PASSWORD); + } + @Override + public List getProtectedProperties() { + return protectedProperties; + } + } } /** - * Values are stored as strings. + * Property Values are stored as strings. */ class StringStore implements PropertyValue { private final String value; private final static PropertyValue.Factory factory = - new PropertyValue.Factory(){ + new PropertyValue.Factory<>(){ @Override public StringStore of(CharSequence aCharSequence) { @@ -195,7 +229,7 @@ class BytesStore implements PropertyValue, Serializable { private final byte[] value; private final static PropertyValue.Factory factory = - new PropertyValue.Factory(){ + new PropertyValue.Factory<>(){ @Override public BytesStore of(CharSequence aCharSequence) { @@ -216,6 +250,7 @@ public BytesStore of(byte[] value) { public static PropertyValue.Factory getFactory() { return factory; } + public BytesStore(CharSequence aString) { this.value = charSequenceToBytes(aString); } @@ -230,13 +265,13 @@ public BytesStore(byte [] value) { @Override public String getValueAsString() { - return new String(this.value); + return new String(this.value, StandardCharsets.UTF_8); } @Override public CharSequence getValue() { ByteBuffer bb = ByteBuffer.wrap(this.value); - return Charsets.UTF_8.decode(bb); + return StandardCharsets.UTF_8.decode(bb); } @Override @@ -305,7 +340,7 @@ private byte[] getBytes() { } private final static PropertyValue.Factory factory = - new PropertyValue.Factory(){ + new PropertyValue.Factory<>(){ @Override public SealedStore of(CharSequence aCharSequence) { @@ -415,7 +450,7 @@ public SealedStore(CharSequence charSequence) { @Override public String getValueAsString() { - return new String(getBytes()); + return new String(getBytes(), StandardCharsets.UTF_8); } @Override diff --git a/database/src/main/java/org/linguafranca/pwdb/SerializableDatabase.java b/database/src/main/java/org/linguafranca/pwdb/SerializableDatabase.java index 74e7a961..a956839e 100644 --- a/database/src/main/java/org/linguafranca/pwdb/SerializableDatabase.java +++ b/database/src/main/java/org/linguafranca/pwdb/SerializableDatabase.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; diff --git a/database/src/main/java/org/linguafranca/pwdb/StreamConfiguration.java b/database/src/main/java/org/linguafranca/pwdb/StreamConfiguration.java index 4b717495..f6f3709f 100644 --- a/database/src/main/java/org/linguafranca/pwdb/StreamConfiguration.java +++ b/database/src/main/java/org/linguafranca/pwdb/StreamConfiguration.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; /** diff --git a/database/src/main/java/org/linguafranca/pwdb/StreamFormat.java b/database/src/main/java/org/linguafranca/pwdb/StreamFormat.java index bc8779bc..ed006e17 100644 --- a/database/src/main/java/org/linguafranca/pwdb/StreamFormat.java +++ b/database/src/main/java/org/linguafranca/pwdb/StreamFormat.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; diff --git a/database/src/main/java/org/linguafranca/pwdb/Visitor.java b/database/src/main/java/org/linguafranca/pwdb/Visitor.java index a7e3536b..fe767f41 100644 --- a/database/src/main/java/org/linguafranca/pwdb/Visitor.java +++ b/database/src/main/java/org/linguafranca/pwdb/Visitor.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -23,24 +24,24 @@ * * @author jo */ -public interface Visitor , G extends Group, E extends Entry, I extends Icon> { +public interface Visitor { /** * Called on entry to a group visit * @param group the group being visited */ - void startVisit(G group); + void startVisit(Group group); /** * Called on exit from a group visit * @param group the group being exited */ - void endVisit(G group); + void endVisit(Group group ); /** * Called on visit to an entry * @param entry the entry being visited */ - void visit(E entry); + void visit(Entry entry); /** * called to determine whether to visit entries before subgroups, or not @@ -51,17 +52,16 @@ public interface Visitor , G extends Group, G extends Group, E extends Entry, I extends Icon> - implements Visitor { + abstract class Default implements Visitor { @Override - public void startVisit(G group) {} + public void startVisit(Group group) {} @Override - public void endVisit(G group) {} + public void endVisit(Group group) {} @Override - public void visit(E entry) {} + public void visit(Entry entry) {} @Override public boolean isEntriesFirst() { @@ -72,8 +72,7 @@ public boolean isEntriesFirst() { /** * Visitor prints the Groups and Entries it visits to console */ - class Print , G extends Group, E extends Entry, I extends Icon> - extends Default { + class Print extends Default{ private final PrintStream printStream; @@ -86,12 +85,12 @@ public Print(PrintStream out) { } @Override - public void startVisit(G group) { + public void startVisit(Group group ) { printStream.println(group.toString()); } @Override - public void visit(E entry) { + public void visit(Entry entry) { printStream.println(entry.toString()); } diff --git a/database/src/main/java/org/linguafranca/pwdb/base/AbstractDatabase.java b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractDatabase.java similarity index 74% rename from database/src/main/java/org/linguafranca/pwdb/base/AbstractDatabase.java rename to database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractDatabase.java index e8cada25..03ffb624 100644 --- a/database/src/main/java/org/linguafranca/pwdb/base/AbstractDatabase.java +++ b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractDatabase.java @@ -1,23 +1,25 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.base; +package org.linguafranca.pwdb.abstractdb; import org.linguafranca.pwdb.*; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -27,7 +29,7 @@ * * @author Jo */ -public abstract class AbstractDatabase, G extends Group, E extends Entry, I extends Icon> implements Database { +public abstract class AbstractDatabase implements Database { private boolean isDirty; @@ -41,69 +43,69 @@ public void setDirty(boolean dirty) { } @Override - public void visit(Visitor visitor) { + public void visit(Visitor visitor) { visitor.startVisit(getRootGroup()); visit(getRootGroup(), visitor); visitor.endVisit(getRootGroup()); } @Override - public void visit(G group, Visitor visitor) { + public void visit(Group group, Visitor visitor) { if (visitor.isEntriesFirst()) { - for (E entry : group.getEntries()) { + for (Entry entry : group.getEntries()) { visitor.visit(entry); } } - for (G g : group.getGroups()) { + for (Group g : group.getGroups()) { visitor.startVisit(g); visit(g, visitor); visitor.endVisit(g); } if (!visitor.isEntriesFirst()) { - for (E entry : group.getEntries()) { + for (Entry entry : group.getEntries()) { visitor.visit(entry); } } } @Override - public List findEntries(Entry.Matcher matcher) { + public List findEntries(Entry.Matcher matcher) { return getRootGroup().findEntries(matcher, true); } @Override - public List findEntries(String find) { + public List findEntries(String find) { return getRootGroup().findEntries(find, true); } @Override - public G newGroup(String name) { - G result = newGroup(); + public Group newGroup(String name) { + Group result = newGroup(); result.setName(name); return result; } @Override - public G newGroup(Group group) { - G result = newGroup(); + public Group newGroup(Group group) { + Group result = newGroup(); result.setName(group.getName()); result.setIcon(this.newIcon(group.getIcon().getIndex())); return result; } @Override - public E newEntry(String title) { - E result = newEntry(); + public Entry newEntry(String title) { + Entry result = newEntry(); result.setTitle(title); return result; } @Override - public E newEntry(Entry entry) { - E result = newEntry(); + public Entry newEntry(Entry entry) { + Entry result = newEntry(); for (String propertyName: entry.getPropertyNames()) { try { // all implementations must support setting of STANDARD_PROPERTY_NAMES @@ -130,8 +132,8 @@ public E newEntry(Entry entry) { } @Override - public E findEntry(final UUID uuid) { - List entries = findEntries(entry -> entry.getUuid().equals(uuid)); + public Entry findEntry(final UUID uuid) { + List entries = findEntries(entry -> entry.getUuid().equals(uuid)); if (entries.size() > 1) { throw new IllegalStateException("Two entries same UUID"); } @@ -143,7 +145,7 @@ public E findEntry(final UUID uuid) { @Override public boolean deleteEntry(final UUID uuid) { - E e = findEntry(uuid); + Entry e = findEntry(uuid); if (e == null) { return false; } @@ -158,13 +160,13 @@ public boolean deleteEntry(final UUID uuid) { } @Override - public G findGroup(final UUID uuid){ - final List groups = new ArrayList<>(); - visit(new Visitor.Default() { + public Group findGroup(final UUID uuid){ + final List groups = new ArrayList<>(); + visit(new Visitor.Default() { // set to true while visiting sub groups of recycle bin boolean recycle; @Override - public void startVisit(G group) { + public void startVisit(Group group) { if (!recycle && group.getUuid().equals(uuid)) { groups.add(group); } @@ -174,7 +176,7 @@ public void startVisit(G group) { } @Override - public void endVisit(G group) { + public void endVisit(Group group) { if (group.isRecycleBin()) { recycle = false; } @@ -191,7 +193,7 @@ public void endVisit(G group) { @Override public boolean deleteGroup(final UUID uuid) { - G g = findGroup(uuid); + Group g = findGroup(uuid); if (g==null) { return false; } @@ -207,14 +209,14 @@ public boolean deleteGroup(final UUID uuid) { @Override public void emptyRecycleBin() { - G recycle = getRecycleBin(); + Group recycle = getRecycleBin(); if (recycle == null) { return; } - for (G g: recycle.getGroups()){ + for (Group g: recycle.getGroups()){ recycle.removeGroup(g); } - for (E e: recycle.getEntries()){ + for (Entry e: recycle.getEntries()){ recycle.removeEntry(e); } } @@ -231,7 +233,7 @@ public boolean supportsBinaryProperties() { @Override public boolean supportsRecycleBin() { - return true; + return false; } @Override @@ -262,4 +264,21 @@ public boolean supportsPropertyValueStrategy(){ return false; } + @Override + public void saveNx(Credentials credentials, OutputStream outputStream) { + try { + save(credentials, outputStream); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void saveNx(StreamFormat streamFormat, Credentials credentials, OutputStream outputStream) { + try { + save(streamFormat, credentials, outputStream); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractEntry.java similarity index 75% rename from database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java rename to database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractEntry.java index 47f233d2..145617f1 100644 --- a/database/src/main/java/org/linguafranca/pwdb/base/AbstractEntry.java +++ b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractEntry.java @@ -1,20 +1,21 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.base; +package org.linguafranca.pwdb.abstractdb; import org.linguafranca.pwdb.*; @@ -23,7 +24,7 @@ * * @author Jo */ -public abstract class AbstractEntry, G extends Group, E extends Entry, I extends Icon> implements Entry { +public abstract class AbstractEntry implements Entry { @Override public boolean matchTitle(String text){ @@ -57,7 +58,7 @@ public boolean match(Entry.Matcher matcher) { @Override public String getPath() { - G parent = this.getParent(); + Group parent = this.getParent(); String result = ""; if (parent != null) { result = parent.getPath(); @@ -130,9 +131,34 @@ public PropertyValue getPropertyValue(String name) { } @Override - public void setPropertyValue(String name, PropertyValue value) { + public Entry setPropertyValue(String name, PropertyValue value) { + throw new UnsupportedOperationException(); + } + + public Entry addProperty(String name, byte[] value){ + throw new UnsupportedOperationException(); + } + public Entry addProperty(String name, char[] value){ + throw new UnsupportedOperationException(); + } + public Entry addProperty(String name, CharSequence value){ throw new UnsupportedOperationException(); } + public Entry addEntry(){ + if (this.getParent()!=null) { + return this.getParent().addEntry(); + } + throw new IllegalStateException("Entry has no group parent"); + } + + public Entry addEntry(String title){ + if (this.getParent()!=null) { + return this.getParent().addEntry(title); + } + throw new IllegalStateException("Entry has no group parent"); + } + + protected abstract void touch(); } diff --git a/database/src/main/java/org/linguafranca/pwdb/base/AbstractGroup.java b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractGroup.java similarity index 52% rename from database/src/main/java/org/linguafranca/pwdb/base/AbstractGroup.java rename to database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractGroup.java index cf13c0af..6a4c5f48 100644 --- a/database/src/main/java/org/linguafranca/pwdb/base/AbstractGroup.java +++ b/database/src/main/java/org/linguafranca/pwdb/abstractdb/AbstractGroup.java @@ -1,25 +1,24 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.base; +package org.linguafranca.pwdb.abstractdb; -import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.Entry; import org.linguafranca.pwdb.Group; -import org.linguafranca.pwdb.Icon; import java.util.ArrayList; import java.util.List; @@ -30,52 +29,52 @@ * * @author Jo */ -public abstract class AbstractGroup, G extends org.linguafranca.pwdb.base.AbstractGroup, E extends Entry, I extends Icon> implements Group { +public abstract class AbstractGroup implements Group { @Override - public List findGroups(String group1) { - ArrayList result = new ArrayList<>(); - for (G g: getGroups()) { - if (g.getName().equals(group1)) { - result.add(g); - } - } - return result; + public Entry addEntry() { + Entry entry = getDatabase().newEntry(); + return addEntry(entry); } @Override - public List findEntries(String find, boolean recursive) { - /* - * Local helper class to avoid violating DRY in {@link AbstractGroup#findEntries(String, boolean)}. - * Would-be lambda in Java 8. - */ - class TextMatcher implements Entry.Matcher { - - private final String text; + public Entry addEntry(String title) { + Entry entry = getDatabase().newEntry(title); + return addEntry(entry); + } - private TextMatcher(String text) { - this.text = text; - } + @Override + public Group addGroup(String name) { + Group group = getDatabase().newGroup(name); + return addGroup(group); + } - @Override - public boolean matches(Entry entry) { - return entry.match(text); + @Override + public List findGroups(String group) { + ArrayList result = new ArrayList<>(); + for (Group g: getGroups()) { + if (g.getName().equals(group)) { + result.add(g); } } + return result; + } - return findEntries(new TextMatcher(find), recursive); + @Override + public List findEntries(String find, boolean recursive) { + return findEntries(entry -> entry.match(find), recursive); } @Override - public List findEntries(Entry.Matcher matcher, boolean recursive) { - List result = new ArrayList<>(getEntries().size()); - for (E entry: getEntries()){ + public List findEntries(Entry.Matcher matcher, boolean recursive) { + List result = new ArrayList<>(getEntries().size()); + for (Entry entry: getEntries()){ if (entry.match(matcher)){ result.add(entry); } } if (recursive) { - for (G group : getGroups()) { + for (Group group: getGroups()) { // don't recurse into recycle bin if (group.isRecycleBin()) { continue; @@ -87,9 +86,9 @@ public List findEntries(Entry.Matcher matcher, boolean recursive) { } @Override - public void copy(Group parent) { - for (Group child : parent.getGroups()) { - G addedGroup = addGroup(this.getDatabase().newGroup(child)); + public void copy(Group parent) { + for (Group child : parent.getGroups()) { + Group addedGroup = addGroup(this.getDatabase().newGroup(child)); addedGroup.copy(child); } for (Entry entry : parent.getEntries()) { @@ -106,7 +105,7 @@ public String getPath() { parents.push(parent); } StringBuilder result = new StringBuilder("/"); - while (parents.size() > 0) { + while (!parents.isEmpty()) { result.append(parents.pop().getName()).append("/"); } return result.toString(); diff --git a/database/src/main/java/org/linguafranca/pwdb/abstractdb/package-info.java b/database/src/main/java/org/linguafranca/pwdb/abstractdb/package-info.java new file mode 100644 index 00000000..6aafe5e8 --- /dev/null +++ b/database/src/main/java/org/linguafranca/pwdb/abstractdb/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.abstractdb; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonRecycleBinTest.java b/database/src/main/java/org/linguafranca/pwdb/package-info.java similarity index 54% rename from jackson/src/test/java/org/linguafranca/pwdb/JacksonRecycleBinTest.java rename to database/src/main/java/org/linguafranca/pwdb/package-info.java index fc63749d..bf6b8ed1 100644 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonRecycleBinTest.java +++ b/database/src/main/java/org/linguafranca/pwdb/package-info.java @@ -1,31 +1,28 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; - -import org.linguafranca.pwdb.checks.RecycleBinChecks; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; -public class JacksonRecycleBinTest extends RecycleBinChecks { +/** + * This package contains the interfaces that define the org.linguafranca Database API. + *

+ * The API is designed to be a simple, low-level, read-write API for password databases. + *

+ * Class instances of the interfaces are contained in the sub-packages of this package. + */ +@NullMarked +package org.linguafranca.pwdb; - public JacksonRecycleBinTest() { - try { - database = new JacksonDatabase(); - } catch(Exception e) { - e.printStackTrace(); - } - - } -} +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/database/src/main/java/org/linguafranca/pwdb/protect/ProtectedDatabase.java b/database/src/main/java/org/linguafranca/pwdb/protect/ProtectedDatabase.java index 7c50e16c..200f6209 100644 --- a/database/src/main/java/org/linguafranca/pwdb/protect/ProtectedDatabase.java +++ b/database/src/main/java/org/linguafranca/pwdb/protect/ProtectedDatabase.java @@ -1,7 +1,25 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.protect; -import org.linguafranca.pwdb.*; -import org.linguafranca.pwdb.base.AbstractDatabase; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.PropertyValue; +import org.linguafranca.pwdb.abstractdb.AbstractDatabase; import java.util.Collections; import java.util.List; @@ -10,28 +28,30 @@ * Base class for Databases which support storage using {@link PropertyValue}s. *

* By default {@link Entry#STANDARD_PROPERTY_NAME_PASSWORD} is defined as protected and the property value strategy - * establishes {@link org.linguafranca.pwdb.PropertyValue.StringStore} storage for unprotected values and + * establishes {@link org.linguafranca.pwdb.PropertyValue.BytesStore} storage for unprotected values and * {@link org.linguafranca.pwdb.PropertyValue.SealedStore} for protected values. */ -public abstract class ProtectedDatabase, G extends Group, E extends Entry, I extends Icon> extends AbstractDatabase { +public abstract class ProtectedDatabase extends AbstractDatabase { private PropertyValue.Strategy valueStrategy = new PropertyValue.Strategy.Default(); @Override - public boolean shouldProtect(String propertyName){ + public boolean shouldProtect(String propertyName) { return valueStrategy.getProtectedProperties().contains(propertyName); } @Override - public void setShouldProtect(String propertyName, boolean protect){ + public void setShouldProtect(String propertyName, boolean protect) { if (protect) { - valueStrategy.getProtectedProperties().add(propertyName); + if (!valueStrategy.getProtectedProperties().contains(propertyName)) { + valueStrategy.getProtectedProperties().add(propertyName); + } } else { valueStrategy.getProtectedProperties().remove(propertyName); } } @Override - public List listShouldProtect(){ + public List listShouldProtect() { return Collections.unmodifiableList(valueStrategy.getProtectedProperties()); } @@ -39,20 +59,22 @@ public List listShouldProtect(){ * Get the default means of storage of unprotected and protected property values */ @Override - public PropertyValue.Strategy getPropertyValueStrategy(){ + public PropertyValue.Strategy getPropertyValueStrategy() { return this.valueStrategy; } + /** * Set the default means of storage of unprotected and protected property values + * * @param strategy a property value strategy */ @Override - public void setPropertyValueStrategy(PropertyValue.Strategy strategy){ + public void setPropertyValueStrategy(PropertyValue.Strategy strategy) { this.valueStrategy = strategy; } @Override - public boolean supportsPropertyValueStrategy(){ + public boolean supportsPropertyValueStrategy() { return true; } } diff --git a/database/src/main/java/org/linguafranca/pwdb/protect/package-info.java b/database/src/main/java/org/linguafranca/pwdb/protect/package-info.java new file mode 100644 index 00000000..acad64db --- /dev/null +++ b/database/src/main/java/org/linguafranca/pwdb/protect/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.protect; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/database/src/main/java/org/linguafranca/pwdb/security/Aes.java b/database/src/main/java/org/linguafranca/pwdb/security/Aes.java index 776cf39e..529b07c2 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/Aes.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/Aes.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; import org.bouncycastle.crypto.engines.AESEngine; @@ -22,7 +39,12 @@ * AES may be used for Key Derivation and also as the underlying stream cipher *

* The class is a singleton + * + * {@implNote This class is a singleton} + * {@implNote Warnings for the use of AES CBC etc. noted, however their continuing use is required for + * compatibility / interoperability with existing databases} */ +@SuppressWarnings({"deprecation"}) public class Aes implements CipherAlgorithm, KeyDerivationFunction { /** UUID specifying that AES is to be used as the Key Derivation Function in KDBX */ diff --git a/database/src/main/java/org/linguafranca/pwdb/security/Argon2.java b/database/src/main/java/org/linguafranca/pwdb/security/Argon2.java index a0ba2984..09d59494 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/Argon2.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/Argon2.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; import org.bouncycastle.crypto.generators.Argon2BytesGenerator; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/ChaCha.java b/database/src/main/java/org/linguafranca/pwdb/security/ChaCha.java index 65ad1f5b..fd281032 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/ChaCha.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/ChaCha.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; import org.bouncycastle.crypto.StreamCipher; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/CipherAlgorithm.java b/database/src/main/java/org/linguafranca/pwdb/security/CipherAlgorithm.java index bb1a1c0a..e1bc5524 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/CipherAlgorithm.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/CipherAlgorithm.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; import java.io.InputStream; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/Encryption.java b/database/src/main/java/org/linguafranca/pwdb/security/Encryption.java index 36d4a46e..32f9fdf8 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/Encryption.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/Encryption.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.security; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/KeyDerivationFunction.java b/database/src/main/java/org/linguafranca/pwdb/security/KeyDerivationFunction.java index 0e15663f..529dcfe8 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/KeyDerivationFunction.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/KeyDerivationFunction.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; import java.util.UUID; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/StreamEncryptor.java b/database/src/main/java/org/linguafranca/pwdb/security/StreamEncryptor.java index b3fc4a15..e8827590 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/StreamEncryptor.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/StreamEncryptor.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.security; diff --git a/database/src/main/java/org/linguafranca/pwdb/security/VariantDictionary.java b/database/src/main/java/org/linguafranca/pwdb/security/VariantDictionary.java index 0a95db47..1738fb3b 100644 --- a/database/src/main/java/org/linguafranca/pwdb/security/VariantDictionary.java +++ b/database/src/main/java/org/linguafranca/pwdb/security/VariantDictionary.java @@ -1,7 +1,23 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.security; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import javax.annotation.concurrent.Immutable; import java.nio.ByteBuffer; @@ -10,7 +26,7 @@ import java.util.Map; import java.util.UUID; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static org.linguafranca.pwdb.security.VariantDictionary.EntryType.*; /** @@ -62,16 +78,16 @@ public static EntryType get(byte type) { @Immutable public static class Entry { private final byte type; - private final byte @NotNull [] value; + private final byte [] value; private final ByteOrder byteOrder; - public Entry(EntryType entryType, byte @NotNull [] value) { + public Entry(EntryType entryType, byte[] value) { this(entryType, value, ByteOrder.LITTLE_ENDIAN); } - public Entry(EntryType entryType, byte @NotNull [] value, ByteOrder byteOrder) { + public Entry(EntryType entryType, byte[] value, ByteOrder byteOrder) { this.type = entryType.value; - this.value = checkNotNull(value, vnn); + this.value = requireNonNull(value, vnn); this.byteOrder = byteOrder; } @@ -101,7 +117,7 @@ public int asInteger() { return ByteBuffer.wrap(value).order(byteOrder).getInt(); } - public byte @NotNull [] asByteArray() { + public byte[] asByteArray() { return value; } } @@ -147,7 +163,7 @@ public short getVersion() { * @param key the key * @return an entry, or null if no such entry exists */ - public @Nullable Entry get(@NotNull String key) { + public @Nullable Entry get(String key) { return entries.get(key); } @@ -157,7 +173,7 @@ public short getVersion() { * @param key the key to get * @return the entry corresponding to the key */ - public @NotNull Entry mustGet(@NotNull String key) { + public Entry mustGet(String key) { Entry entry = entries.get(key); if (entry == null) { throw new IllegalArgumentException("There is no entry with key " + key); @@ -172,57 +188,57 @@ public short getVersion() { * @param type the data type of the entry * @param value a buffer containing an appropriate entry */ - public void put(@NotNull String key, EntryType type, byte @NotNull [] value) { - entries.put(checkNotNull(key), new Entry(type, checkNotNull(value))); + public void put(String key, EntryType type, byte[] value) { + entries.put(requireNonNull(key), new Entry(type, requireNonNull(value))); } /** * Put a UUID under the key defined */ - public void putUuid(@NotNull String key, UUID uuid) { + public void putUuid(String key, UUID uuid) { byte[] buf = new byte[16]; ByteBuffer bb = ByteBuffer.wrap(buf); bb.putLong(0, uuid.getMostSignificantBits()); bb.putLong(8, uuid.getLeastSignificantBits()); - entries.put(checkNotNull(key, knn), new Entry(ARRAY, buf)); + entries.put(requireNonNull(key, knn), new Entry(ARRAY, buf)); } /** * Put a byte array under the key defined */ - public void putByteArray(@NotNull String key, byte @NotNull [] value) { - entries.put(checkNotNull(key, knn), new Entry(ARRAY, value)); + public void putByteArray(String key, byte[] value) { + entries.put(requireNonNull(key, knn), new Entry(ARRAY, value)); } /** * Put a long as a signed64 under the key defined */ - public void putLong(@NotNull String key, long value) { + public void putLong(String key, long value) { byte[] buf = new byte[8]; ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); bb.putLong(value); - entries.put(checkNotNull(key, knn), new Entry(INT64, buf)); + entries.put(requireNonNull(key, knn), new Entry(INT64, buf)); } /** * Put a long as an unsigned64 under the key defined */ - public void putULong(@NotNull String key, long value) { + public void putULong(String key, long value) { byte[] buf = new byte[8]; ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); bb.putLong(value); - entries.put(checkNotNull(key, knn), new Entry(UINT64, buf)); + entries.put(requireNonNull(key, knn), new Entry(UINT64, buf)); } - public void putInt(@NotNull String key, int value) { + public void putInt(String key, int value) { byte[] buf = new byte[4]; ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); bb.putInt(value); - entries.put(checkNotNull(key, knn), new Entry(INT32, buf)); + entries.put(requireNonNull(key, knn), new Entry(INT32, buf)); } - public void putUInt(@NotNull String key, int value) { + public void putUInt(String key, int value) { byte[] buf = new byte[4]; ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); bb.putInt(value); - entries.put(checkNotNull(key, knn), new Entry(UINT32, buf)); + entries.put(requireNonNull(key, knn), new Entry(UINT32, buf)); } } diff --git a/database/src/main/java/org/linguafranca/pwdb/security/package-info.java b/database/src/main/java/org/linguafranca/pwdb/security/package-info.java new file mode 100644 index 00000000..61951d71 --- /dev/null +++ b/database/src/main/java/org/linguafranca/pwdb/security/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.security; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/database/src/main/java/org/linguafranca/util/MavenRunListener.java b/database/src/main/java/org/linguafranca/util/MavenRunListener.java deleted file mode 100644 index 1839a645..00000000 --- a/database/src/main/java/org/linguafranca/util/MavenRunListener.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.linguafranca.util; - -import org.junit.runner.Description; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; - -/** - * Helper class to identify the test being run by maven surefire in the log output. This - * is particularly useful if there is a lot of "chatty" testing going on. - *

- * See: Using Custom Listeners and Reporters - */ -@RunListener.ThreadSafe -public class MavenRunListener extends RunListener { - static Logger logger = LoggerFactory.getLogger("MavenRunListener"); - static Marker marker = MarkerFactory.getMarker("Starting Test"); - static Marker endMarker = MarkerFactory.getMarker("Finished Test"); - static Marker failMarker = MarkerFactory.getMarker("Failed Test"); - static Marker assumeFailMarker = MarkerFactory.getMarker("Assumption Failed"); - static Marker ignoredMarker = MarkerFactory.getMarker("Ignored Test"); - - @Override - public void testStarted(Description description) throws Exception { - super.testStarted(description); - logger.info(marker, "{}", description.getDisplayName()); - } - - @Override - public void testFailure(Failure failure) throws Exception { - super.testFailure(failure); - logger.error(failMarker, "{} {}", failure.getMessage(), failure.getDescription().getDisplayName()); - } - - @Override - public void testAssumptionFailure(Failure failure) { - super.testAssumptionFailure(failure); - logger.warn(assumeFailMarker, "{} {}", failure.getMessage(), failure.getDescription().getDisplayName()); - } - - @Override - public void testIgnored(Description description) throws Exception { - super.testIgnored(description); - logger.info(ignoredMarker, "{}", description.getDisplayName()); - } - - @Override - public void testFinished(Description description) throws Exception { - super.testFinished(description); - logger.info(endMarker, "Finished {}", description.getDisplayName()); - } -} diff --git a/database/src/test/java/org/linguafranca/pwdb/PropertyValueTest.java b/database/src/test/java/org/linguafranca/pwdb/PropertyValueTest.java index 21918401..1ba29d4f 100644 --- a/database/src/test/java/org/linguafranca/pwdb/PropertyValueTest.java +++ b/database/src/test/java/org/linguafranca/pwdb/PropertyValueTest.java @@ -1,86 +1,52 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.StandardCharsets; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.PropertyValueUtil.genericTest; + +@Execution(CONCURRENT) public class PropertyValueTest { - public static final String THIS_IS_A_SECRET = "This is a secret + לַחַיִּים"; public static final String ANOTHER_SECRET = "password with accents àéç"; - Logger logger = LoggerFactory.getLogger(PropertyValueTest.class); @Test public void bytesTest() { - PropertyValue.BytesStore.Factory factory = PropertyValue.BytesStore.getFactory(); - - // test as CharSequence - PropertyValue.BytesStore testValue = factory.of(THIS_IS_A_SECRET); - assertEquals(THIS_IS_A_SECRET, testValue.getValue().toString()); - assertEquals(THIS_IS_A_SECRET, testValue.getValueAsString()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), testValue.getValueAsChars()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8), testValue.getValueAsBytes()); - // test as char[] - testValue = factory.of(THIS_IS_A_SECRET.toCharArray()); - assertEquals(THIS_IS_A_SECRET, testValue.getValue().toString()); - assertEquals(THIS_IS_A_SECRET, testValue.getValueAsString()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), testValue.getValueAsChars()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8), testValue.getValueAsBytes()); - // test as byte[] - testValue = factory.of(THIS_IS_A_SECRET.getBytes()); - assertEquals(THIS_IS_A_SECRET, testValue.getValue().toString()); - assertEquals(THIS_IS_A_SECRET, testValue.getValueAsString()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), testValue.getValueAsChars()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8), testValue.getValueAsBytes()); + genericTest(PropertyValue.BytesStore.getFactory()); } @Test public void sealedObjectTest() { - PropertyValue.SealedStore.Factory factory = PropertyValue.SealedStore.getFactory(); - - PropertyValue.SealedStore sealed = factory.of(THIS_IS_A_SECRET); - assertEquals(THIS_IS_A_SECRET, sealed.getValue().toString()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), sealed.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), sealed.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, sealed.getValueAsString()); - - sealed = factory.of(THIS_IS_A_SECRET.getBytes()); - assertEquals(THIS_IS_A_SECRET, sealed.getValue().toString()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), sealed.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), sealed.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, sealed.getValueAsString()); - - sealed =factory.of(THIS_IS_A_SECRET.toCharArray()); - assertEquals(THIS_IS_A_SECRET, sealed.getValue().toString()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), sealed.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), sealed.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, sealed.getValueAsString()); + genericTest(PropertyValue.SealedStore.getFactory()); } + @Test public void stringTest() { - PropertyValue.Factory factory = PropertyValue.StringStore.getFactory(); - - PropertyValue.StringStore stringStore = factory.of(THIS_IS_A_SECRET); - assertEquals(THIS_IS_A_SECRET, stringStore.getValue()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), stringStore.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), stringStore.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, stringStore.getValueAsString()); - - stringStore = factory.of(THIS_IS_A_SECRET.getBytes()); - assertEquals(THIS_IS_A_SECRET, stringStore.getValue()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), stringStore.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), stringStore.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, stringStore.getValueAsString()); - - stringStore =factory.of(THIS_IS_A_SECRET.toCharArray()); - assertEquals(THIS_IS_A_SECRET, stringStore.getValue()); - assertArrayEquals(THIS_IS_A_SECRET.getBytes(), stringStore.getValueAsBytes()); - assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), stringStore.getValueAsChars()); - assertEquals(THIS_IS_A_SECRET, stringStore.getValueAsString()); + genericTest(PropertyValue.StringStore.getFactory()); } @Test @@ -88,31 +54,5 @@ public void sealedScriptTest(){ PropertyValue.SealedStore sealed = PropertyValue.SealedStore.getFactory().of(ANOTHER_SECRET); byte[] bytes = sealed.getValueAsBytes(); assertArrayEquals(ANOTHER_SECRET.getBytes(StandardCharsets.UTF_8), bytes); - - } - - @Test - public void getCharsTest(){ - PropertyValue.BytesStore cs = new PropertyValue.BytesStore("a test".toCharArray()); - char [] value = cs.getValueAsChars(); - value[0] = 'b'; - // changing the retrieved copy doesn't change the source - assertArrayEquals("a test".toCharArray(), cs.getValueAsChars()); - - byte [] bytes = cs.getValueAsBytes(); - bytes [0] = 0; - // same for bytes - assertArrayEquals("a test".getBytes(), cs.getValueAsBytes()); - } - - @Test - public void putCharsTest(){ - String testValue = "a test"; - char [] chars = testValue.toCharArray(); - PropertyValue.BytesStore cs = new PropertyValue.BytesStore(chars); - chars[0] = 'b'; - char [] value = cs.getValueAsChars(); - // changing the source doesn't change the copy - assertArrayEquals(testValue.toCharArray(), cs.getValueAsChars()); } } \ No newline at end of file diff --git a/database/src/test/java/org/linguafranca/pwdb/PropertyValueUtil.java b/database/src/test/java/org/linguafranca/pwdb/PropertyValueUtil.java new file mode 100644 index 00000000..ee85aae9 --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/PropertyValueUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Make available as a utility to provide for PropertyValue extension implementations + */ +public class PropertyValueUtil { + + public static final String THIS_IS_A_SECRET = "This is a secret + לַחַיִּים"; + + public static void genericTest(PropertyValue.Factory factory) { + genericTest(factory, true); + } + + public static void genericTest(PropertyValue.Factory factory, boolean testStringReturn) { + // test input CharSequence + PropertyValue testValue = factory.of(THIS_IS_A_SECRET); + test(testStringReturn, testValue); + + // test input char[] + testValue = factory.of(THIS_IS_A_SECRET.toCharArray()); + test(testStringReturn, testValue); + + + // test input byte[] + testValue = factory.of(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8)); + test(testStringReturn, testValue); + + } + + private static void test(boolean testStringReturn, PropertyValue testValue) { + if (testStringReturn) assertEquals(THIS_IS_A_SECRET, testValue.getValue().toString()); + assertEquals(THIS_IS_A_SECRET, testValue.getValueAsString()); + + // get value as chars + char[] chars = testValue.getValueAsChars(); + // return value is correct + assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), chars); + chars[0] = 'z'; + // return value is still correct + assertArrayEquals(THIS_IS_A_SECRET.toCharArray(), testValue.getValueAsChars()); + + + // get value as bytes + byte[] bytes = testValue.getValueAsBytes(); + // return value is correct + assertArrayEquals(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8), bytes); + bytes[0] = 'z'; + // return value is still correct + assertArrayEquals(THIS_IS_A_SECRET.getBytes(StandardCharsets.UTF_8), testValue.getValueAsBytes()); + } + +} diff --git a/database/src/test/java/org/linguafranca/pwdb/test/BinaryPropertiesTest.java b/database/src/test/java/org/linguafranca/pwdb/test/BinaryPropertiesTest.java new file mode 100644 index 00000000..049ec6f0 --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/BinaryPropertiesTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.KdbConsts; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + + +public interface BinaryPropertiesTest { + + Supplier getDbFunc(); + + /** + * Verify that the database correctly reports that its supports binary attachments - override for + * databases that don't + */ + @Test + default void checkSupported() { + DatabaseTestBase db = getDbFunc().get(); + + Database database = db.getDatabase(); + assertTrue(database.supportsBinaryProperties()); + } + + /** + * Retrieve and verify attachment "letter J" + */ + @Test + default void getBinaryProperty() throws Exception { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry = db.getDatabase().findEntries("Test attachment").get(0); + byte[] letterJ = entry.getBinaryProperty("letter J.jpeg"); + InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter J.jpeg"); + assert testFile != null; + byte[] original = IOUtils.toByteArray(testFile); + assertArrayEquals(original, letterJ); + } + + /** + * Retrieve and verify attachment "letter L" + */ + @Test + default void getAnotherBinaryProperty() throws Exception { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry = db.getDatabase().findEntries("Test 2 attachment").get(0); + byte[] letterL = entry.getBinaryProperty("letter L.jpeg"); + InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter L.jpeg"); + assert testFile != null; + byte[] original = IOUtils.toByteArray(testFile); + assertArrayEquals(original, letterL); + } + + /** + * Add the Letter L to the entry containing letter J + */ + @Test + default void setBinaryProperty() throws Exception { + DatabaseTestBase db = getDbFunc().get(); + + InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter L.jpeg"); + assert testFile != null; + byte[] original = IOUtils.toByteArray(testFile); + Entry entry = db.getDatabase().findEntries("Test attachment").get(0); + entry.setBinaryProperty("letter L.jpeg", original); + byte[] letterL = entry.getBinaryProperty("letter L.jpeg"); + assertArrayEquals(original, letterL); + assertArrayEquals(new String[]{"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); + assertTrue(db.getDatabase().isDirty()); + } + + /** + * Verify that the entries have the right attachments + */ + @Test + default void getBinaryPropertyNames() { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry = db.getDatabase().findEntries("Test attachment").get(0); + assertArrayEquals(new String[]{"letter J.jpeg"}, entry.getBinaryPropertyNames().toArray()); + + entry = db.getDatabase().findEntries("Test 2 attachment").get(0); + assertArrayEquals(new String[]{"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); + } + + /** + * Verify that the binary properties can be added and removed correctly + */ + @Test + default void checkAddChangeRemoveBinaryProperty() { + DatabaseTestBase db = getDbFunc().get(); + + byte[] test = new byte[]{0, 1, 2, 3}; + byte[] test2 = new byte[]{3, 2, 1, 0}; + Entry entry = db.getDatabase().findEntries("Test attachment").get(0); + assertEquals(1, entry.getBinaryPropertyNames().size()); + entry.setBinaryProperty("test", test); + assertArrayEquals(test, entry.getBinaryProperty("test")); + entry.setBinaryProperty("test", test2); + assertArrayEquals(test2, entry.getBinaryProperty("test")); + // true that property was removed + assertTrue(entry.removeBinaryProperty("test")); + // false that same property was removed + assertFalse(entry.removeBinaryProperty("test")); + // false that non-existent was removed + assertFalse(entry.removeBinaryProperty("test-test")); + // same number of properties as we started with + assertEquals(1, entry.getBinaryPropertyNames().size()); + } + + /** + * Checks that a database with binary properties saves and reloads correctly + */ + @Test + default void saveAndReloadCheck() throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Path file = Files.createTempFile("test8", "tmp"); + testBase.saveDatabase(testBase.getDatabase(), testBase.getCredentials(KdbConsts.PASSWORD_BYTES), Files.newOutputStream(file)); + + Database db = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), Files.newInputStream(file)); + Entry newEntry = db.findEntries("Test attachment").get(0); + assertArrayEquals(new String[]{"letter J.jpeg"}, newEntry.getBinaryPropertyNames().toArray()); + Entry oldEntry = testBase.getDatabase().findEntries("Test attachment").get(0); + assertArrayEquals(oldEntry.getBinaryProperty("letter J.jpeg"), newEntry.getBinaryProperty("letter J.jpeg")); + + newEntry = db.findEntries("Test 2 attachment").get(0); + assertArrayEquals(new String[]{"letter J.jpeg", "letter L.jpeg"}, newEntry.getBinaryPropertyNames().toArray()); + oldEntry = testBase.getDatabase().findEntries("Test 2 attachment").get(0); + assertArrayEquals(oldEntry.getBinaryProperty("letter J.jpeg"), newEntry.getBinaryProperty("letter J.jpeg")); + assertArrayEquals(oldEntry.getBinaryProperty("letter L.jpeg"), newEntry.getBinaryProperty("letter L.jpeg")); + + } + + /** + * Checks that a new database can add binary properties saves and reloads correctly + */ + @Test + default void createAndSaveCheck() throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Path file = Files.createTempFile("keepass", "tmp"); + Database database1 = testBase.createDatabase(); + Entry entry = database1.newEntry("Test attachment"); + database1.getRootGroup().addEntry(entry); + InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter J.jpeg"); + assert testFile != null; + byte[] letterJ = IOUtils.toByteArray(testFile); + entry.setBinaryProperty("letter J.jpeg", letterJ); + testBase.saveDatabase(database1, testBase.getCredentials(KdbConsts.PASSWORD_BYTES), Files.newOutputStream(file)); + + Database db = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), Files.newInputStream(file)); + Entry entry1 = db.findEntries("Test attachment").get(0); + // just one property + assertArrayEquals(new String[]{"letter J.jpeg"}, entry1.getBinaryPropertyNames().toArray()); + // content is correct + assertArrayEquals(letterJ, entry1.getBinaryProperty("letter J.jpeg")); + } +} diff --git a/database/src/test/java/org/linguafranca/pwdb/test/DatabaseTestBase.java b/database/src/test/java/org/linguafranca/pwdb/test/DatabaseTestBase.java new file mode 100644 index 00000000..b4ffdc9e --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/DatabaseTestBase.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.Database; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +public final class DatabaseTestBase { + + @FunctionalInterface + public interface TriConsumer { + + void accept(A a, B b, C c); + + default TriConsumer andThen(TriConsumer after) { + Objects.requireNonNull(after); + + return (l, r,c) -> { + accept(l, r, c); + after.accept(l, r, c); + }; + } + } + + public Database database; + public final Supplier creator; + public final BiFunction loader; + public final TriConsumer saver; + public final Function credentials; + + public DatabaseTestBase(Supplier creator, + BiFunction loader, + TriConsumer saver, + Function credentials) { + this.creator = creator; + this.loader = loader; + this.saver = saver; + this.credentials = credentials; + this.database = createDatabase(); + } + + /** + * Create a new database + */ + public Database createDatabase() { + return this.creator.get(); + } + + /** + * Get the current default database + */ + public Database getDatabase() { + return database; + } + + public Database loadDatabase(byte[] credentials, String resourceName) { + return loader.apply(getCredentials(credentials), getClass().getClassLoader().getResourceAsStream(resourceName)); + } + + public Database loadDatabase(Credentials credentials, InputStream inputStream) { + return loader.apply(credentials, inputStream); + } + + public Database loadAndInitDatabase(byte[] credentials, String resourceName) { + this.database = loader.apply(getCredentials(credentials), getClass().getClassLoader().getResourceAsStream(resourceName)); + return this.database; + } + + public Database loadAndInitDatabase(Credentials credentials, InputStream inputStream) { + this.database = loadDatabase(credentials, inputStream); + return this.database; + } + + + public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream){ + saver.accept(database, credentials, outputStream); + } + + public Credentials getCredentials(byte[] credentials){ + return this.credentials.apply(credentials); + } +} diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java b/database/src/test/java/org/linguafranca/pwdb/test/GroupsAndEntriesTest.java similarity index 58% rename from test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java rename to database/src/test/java/org/linguafranca/pwdb/test/GroupsAndEntriesTest.java index dd0d06b5..7adee132 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/BasicDatabaseChecks.java +++ b/database/src/test/java/org/linguafranca/pwdb/test/GroupsAndEntriesTest.java @@ -1,4 +1,21 @@ /* + * Copyright (c) 2025. Jo Rabin + * + * 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.test;/* * Copyright 2015 Jo Rabin * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,10 +31,7 @@ * limitations under the License. */ -package org.linguafranca.pwdb.checks; - -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.Entry; import org.linguafranca.pwdb.Group; @@ -28,74 +42,78 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.function.Supplier; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.PASSWORD; /** * @author Jo */ -public abstract class BasicDatabaseChecks , G extends Group, E extends Entry, I extends Icon> { +public interface GroupsAndEntriesTest { - protected Database database; - - public abstract Database createDatabase() throws IOException; + Supplier getDbFunc(); + + @Test + default void testEmptyDatabase() { + DatabaseTestBase db = getDbFunc().get(); - public BasicDatabaseChecks() throws IOException { - this.database = createDatabase(); + assertEquals("Root", db.getDatabase().getRootGroup().getName()); + assertEquals(0, db.getDatabase().getRootGroup().getEntries().size()); + assertEquals(0, db.getDatabase().getRootGroup().getGroups().size()); } @Test - public void testEmptyDatabase() { - assertEquals("Root", database.getRootGroup().getName()); - assertEquals(0, database.getRootGroup().getEntries().size()); - assertEquals(0, database.getRootGroup().getGroups().size()); - } + default void testAddGroup() { + DatabaseTestBase db = getDbFunc().get(); - @Test - public void testAddGroup() { - G g1 = database.getRootGroup().addGroup(database.newGroup("group1")); - assertEquals(1, database.getRootGroup().getGroups().size()); + Group g1 = db.getDatabase().getRootGroup().addGroup(db.getDatabase().newGroup("group1")); + assertEquals(1, db.getDatabase().getRootGroup().getGroups().size()); assertEquals("group1", g1.getName()); assertEquals(0, g1.getGroups().size()); assertEquals(0, g1.getEntries().size()); - assertEquals("root is not the parent of its child", g1.getParent(), database.getRootGroup()); + assertEquals(g1.getParent(), db.getDatabase().getRootGroup(), "root is not the parent of its child"); - G g2 = database.newGroup(); + Group g2 = db.getDatabase().newGroup(); assertEquals("", g2.getName()); - Assert.assertNotNull(g2.getUuid()); + assertNotNull(g2.getUuid()); assertEquals(0, g2.getIcon().getIndex()); assertEquals(0, g2.getGroups().size()); assertEquals(0, g2.getEntries().size()); // show that the list of groups is a copy - database.getRootGroup().getGroups().clear(); - assertEquals(1, database.getRootGroup().getGroups().size()); + db.getDatabase().getRootGroup().getGroups().clear(); + assertEquals(1, db.getDatabase().getRootGroup().getGroups().size()); assertEquals(0, g1.getEntries().size()); } @Test - public void testDeleteGroup () { - Group g1 = database.getRootGroup().addGroup(database.newGroup("group1")); - List l1 = database.getRootGroup().findGroups("group1"); + default void testDeleteGroupDetail () { + DatabaseTestBase db = getDbFunc().get(); + + Group g1 = db.getDatabase().getRootGroup().addGroup(db.getDatabase().newGroup("group1")); + List l1 = db.getDatabase().getRootGroup().findGroups("group1"); assertEquals(1, l1.size()); - G g2 = l1.get(0); + Group g2 = l1.get(0); assertEquals(g2, g1); - G g3 = database.getRootGroup().removeGroup(g2); + Group g3 = db.getDatabase().getRootGroup().removeGroup(g2); assertEquals(g3, g1); assertNull(g1.getParent()); - assertEquals(0, database.getRootGroup().getGroups().size()); - assertEquals(0, database.getRootGroup().findGroups("group1").size()); + assertEquals(0, db.getDatabase().getRootGroup().getGroups().size()); + assertEquals(0, db.getDatabase().getRootGroup().findGroups("group1").size()); } @Test - public void testAddRemoveEntry() { - E e1 = database.getRootGroup().addEntry(database.newEntry()); + default void testAddRemoveEntry() { + DatabaseTestBase db = getDbFunc().get(); + + Entry e1 = db.getDatabase().getRootGroup().addEntry(db.getDatabase().newEntry()); e1.setTitle("entry1"); - List l1 = database.findEntries("entry1"); + List l1 = db.getDatabase().findEntries("entry1"); assertEquals(1, l1.size()); - E e12 = database.getRootGroup().addEntry(database.newEntry("entry12")); - List l2 = database.findEntries("entry1"); + Entry e12 = db.getDatabase().getRootGroup().addEntry(db.getDatabase().newEntry("entry12")); + List l2 = db.getDatabase().findEntries("entry1"); assertEquals(2, l2.size()); // show that the entries are different @@ -103,34 +121,36 @@ public void testAddRemoveEntry() { // show that the list is a copy l2.clear(); - assertEquals(2, database.findEntries("entry1").size()); + assertEquals(2, db.getDatabase().findEntries("entry1").size()); // show that we get an equivalent entry when we remove to when we inserted - E e12b = database.getRootGroup().removeEntry(e12); + Entry e12b = db.getDatabase().getRootGroup().removeEntry(e12); assertEquals(e12b, e12); // has been unhooked from parent assertNull(e12.getParent()); - assertEquals(1, database.findEntries("entry1").size()); + assertEquals(1, db.getDatabase().findEntries("entry1").size()); } @Test - public void testSetFields () { - E e1 = database.newEntry("Entry 1"); + default void testSetFields () { + DatabaseTestBase db = getDbFunc().get(); + + Entry e1 = db.getDatabase().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", e1.getPassword()); + e1.setProperty(PASSWORD, "supercalifragelisticexpialidocious"); + assertEquals("supercalifragelisticexpialidocious", e1.getProperty(PASSWORD)); 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")); + assertTrue(e1.match("2")); + assertTrue(e1.matchTitle("1")); + assertFalse(e1.matchTitle("doggy")); - I ic1 = database.newIcon(27); + Icon ic1 = db.getDatabase().newIcon(27); e1.setIcon(ic1); assertEquals(e1.getIcon(), ic1); @@ -142,7 +162,7 @@ public void testSetFields () { e1.setProperty(Entry.STANDARD_PROPERTY_NAME_NOTES, "notes"); assertEquals("notes", e1.getNotes()); e1.setProperty(Entry.STANDARD_PROPERTY_NAME_PASSWORD, "password"); - assertEquals("password", e1.getPassword()); + assertEquals("password", e1.getProperty(PASSWORD)); e1.setProperty(Entry.STANDARD_PROPERTY_NAME_URL, "url"); assertEquals("url", e1.getUrl()); @@ -153,10 +173,10 @@ public void testSetFields () { 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()); + assertEquals(0, properties.size()); } catch (UnsupportedOperationException e) { // databases don't have to support arbitrary properties - assertFalse(database.supportsNonStandardPropertyNames()); + assertFalse(db.getDatabase().supportsNonStandardPropertyNames()); assertArrayEquals(e1.getPropertyNames().toArray(), Entry.STANDARD_PROPERTY_NAMES.toArray()); } @@ -168,9 +188,11 @@ public void testSetFields () { } @Test - public void testTimes() { + default void testTimes() { + DatabaseTestBase db = getDbFunc().get(); + long beforeSecond = Instant.now().toEpochMilli()/1000; - E entry = database.newEntry(); + Entry entry = db.getDatabase().newEntry(); long afterSecond = Instant.now().toEpochMilli()/1000; long createdSecond = entry.getCreationTime().getTime()/1000; @@ -189,17 +211,19 @@ public void testTimes() { } @Test - public void checkAddChangeRemoveProperty() { + default void checkAddChangeRemoveProperty() { + DatabaseTestBase db = getDbFunc().get(); + // only applies to databases that support arbitrary properties - E entry = database.newEntry(); + Entry entry = db.getDatabase().newEntry(); assertEquals(Entry.STANDARD_PROPERTY_NAMES.size(), entry.getPropertyNames().size()); try { entry.setProperty("test", "test1"); } catch (UnsupportedOperationException e) { - if (!database.supportsNonStandardPropertyNames()) { + if (!db.getDatabase().supportsNonStandardPropertyNames()) { return; } - fail("Database must report that it doesn't support non standrad properties"); + fail("Database must report that it doesn't support non standard properties"); } assertEquals("test1", entry.getProperty("test")); entry.setProperty("test", "test2"); @@ -218,49 +242,53 @@ public void checkAddChangeRemoveProperty() { @Test - public void testNewEntry() { - E e2 = database.newEntry(); - Assert.assertNull(e2.getParent()); - assertEquals("", e2.getPassword()); - Assert.assertNotNull(e2.getUuid()); + default void testNewEntryInit() { + DatabaseTestBase db = getDbFunc().get(); + + Entry e2 = db.getDatabase().newEntry(); + assertNull(e2.getParent()); + assertEquals("", e2.getProperty(PASSWORD)); + assertNotNull(e2.getUuid()); assertEquals("", e2.getUrl()); assertEquals("", e2.getNotes()); assertEquals("", e2.getUsername()); assertEquals("", e2.getTitle()); - Assert.assertNull(e2.getProperty("silly")); + assertNull(e2.getProperty("silly")); List l = e2.getPropertyNames(); l.removeAll(Entry.STANDARD_PROPERTY_NAMES); - Assert.assertEquals(0, l.size()); + assertEquals(0, l.size()); } @Test - public void testCopy() throws IOException { - E entry1 = database.newEntry(); + default void testCopy() throws IOException { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry1 = db.getDatabase().newEntry(); entry1.setTitle("Entry"); entry1.setUsername("Username"); - entry1.setPassword("Password"); + entry1.setProperty(PASSWORD, "Password"); entry1.setUrl("https://dont.follow.me"); entry1.setNotes("Notes"); - entry1.setIcon(database.newIcon(2)); + entry1.setIcon(db.getDatabase().newIcon(2)); // create a new Database - Database database2 = createDatabase(); + Database database2 = db.createDatabase(); // create a new Entry in new Database - E entry2 = database2.newEntry(entry1); + Entry entry2 = database2.newEntry(entry1); assertEquals(entry1.getTitle(), entry2.getTitle()); assertEquals(entry1.getUsername(), entry2.getUsername()); - assertEquals(entry1.getPassword(), entry2.getPassword()); + assertEquals(entry1.getProperty(PASSWORD), entry2.getProperty(PASSWORD)); assertEquals(entry1.getUrl(), entry2.getUrl()); assertEquals(entry1.getNotes(), entry2.getNotes()); assertEquals(entry1.getIcon(), entry2.getIcon()); assertNotEquals(entry1.getUuid(), entry2.getUuid()); - G group1 = database.newGroup(); + Group group1 = db.getDatabase().newGroup(); group1.setName("Group"); - group1.setIcon(database.newIcon(3)); + group1.setIcon(db.getDatabase().newIcon(3)); - G group2 = database2.newGroup(group1); + Group 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/SaveAndReloadChecks.java b/database/src/test/java/org/linguafranca/pwdb/test/KdbxFileSaveAndReloadTest.java similarity index 51% rename from test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java rename to database/src/test/java/org/linguafranca/pwdb/test/KdbxFileSaveAndReloadTest.java index 82c4733f..472f4b88 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/SaveAndReloadChecks.java +++ b/database/src/test/java/org/linguafranca/pwdb/test/KdbxFileSaveAndReloadTest.java @@ -1,110 +1,119 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner * * 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 - * + * + * 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.checks; +package org.linguafranca.pwdb.test; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.linguafranca.pwdb.*; import java.io.*; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; +import java.util.function.Supplier; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.*; import static org.linguafranca.util.TestUtil.getTestPrintStream; + /** * @author jo + * @author Sergio Lissner */ -public abstract class SaveAndReloadChecks , G extends Group, E extends Entry, I extends Icon>{ - - static PrintStream printStream = getTestPrintStream(); +public interface KdbxFileSaveAndReloadTest { - public abstract D getDatabase(); - public abstract D getDatabase(String name, Credentials credentials) throws IOException; + Supplier getDbFunc(); - public abstract void saveDatabase(D database, Credentials credentials, OutputStream outputStream) throws IOException; - public abstract D loadDatabase(Credentials credentials, InputStream inputStream) throws IOException; - public abstract Credentials getCreds(byte[] creds); - public abstract boolean verifyStreamFormat (StreamFormat s1, StreamFormat s2); - - @BeforeClass - public static void ensureOutputDir() throws IOException { - Files.createDirectories(Paths.get("testOutput")); - } + boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2); /** * Test verifies that entries contain the same content on reload as they did on save, * and also verifies that saving doesn't alter the contents */ @Test - public void saveAndReloadTest() throws IOException { + default void saveAndReloadTest(@TempDir Path tempPath) throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); long now = System.currentTimeMillis(); // create database with known content - D output = createNewDatabase(); + Database output = createNewDatabase(); verifyContents(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(); + Path dbPath = tempPath.resolve("test1.kdbx"); + try (OutputStream fos = Files.newOutputStream(dbPath)) { + testBase.saveDatabase(output, testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fos); + assertFalse(output.isDirty()); + fos.flush(); + fos.close(); + } // make sure that saving didn't mess up content verifyContents(output); //output.save(new StreamFormat.None(), new Credentials.None(), printStream); - - FileInputStream fis = new FileInputStream("testOutput/test1.kdbx"); - D input = loadDatabase(getCreds("123".getBytes()), fis); + Database input; + try (InputStream fis = Files.newInputStream(dbPath)) { + input = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fis); + } verifyContents(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 */ @Test - public void saveAndReloadTest2() throws IOException { - D attachment = getDatabase("Attachment.kdbx", getCreds("123".getBytes())); + default void saveAndReloadTest2(@TempDir Path tempDir) throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Database attachment; + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx")) { + assertNotNull(inputStream); + attachment = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), + inputStream); + } - E entry = attachment.findEntries("Test attachment").get(0); + Entry entry = attachment.findEntries("Test attachment").get(0); assertArrayEquals(new String[] {"letter J.jpeg"}, entry.getBinaryPropertyNames().toArray()); - E entry2 = attachment.findEntries("Test 2 attachment").get(0); + Entry entry2 = attachment.findEntries("Test 2 attachment").get(0); assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, entry2.getBinaryPropertyNames().toArray()); byte[] content = entry2.getBinaryProperty("letter L.jpeg"); + assertNotNull(content); entry.setBinaryProperty("letter L.jpeg", content); assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); - FileOutputStream fos = new FileOutputStream("testOutput/test2.kdbx"); - saveDatabase(attachment, getCreds("123".getBytes()), fos); - fos.flush(); - fos.close(); + Path dbPath = tempDir.resolve("test2.kdbx"); + try (OutputStream fos = Files.newOutputStream(dbPath)) { + testBase.saveDatabase(attachment, testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fos); + fos.flush(); + fos.close(); + } - FileInputStream fis = new FileInputStream("testOutput/test2.kdbx"); - D input = loadDatabase(getCreds("123".getBytes()), fis); + Database input; + try (InputStream fis = Files.newInputStream(dbPath)) { + input = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fis); + } entry = input.findEntries("Test attachment").get(0); assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); @@ -112,7 +121,7 @@ public void saveAndReloadTest2() throws IOException { //saveDatabase(input, new StreamFormat.None(), new Credentials.None(), printStream); } - String [] testFiles = {"V4-AES-AES.kdbx", + static String [] testFiles = {"V4-AES-AES.kdbx", "V4-AES-Argon2.kdbx", "V4-ChaCha20-AES.kdbx", "V4-ChaCha20-Argon2-Attachment.kdbx"}; @@ -121,25 +130,38 @@ public void saveAndReloadTest2() throws IOException { * Test verifies that database is saved with same encryption that it was loaded with */ @Test - public void saveAndReloadTest3() throws IOException { + default void saveAndReloadTest3(@TempDir Path tempPath) throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + for (String resource: testFiles) { - D database = getDatabase(resource, this.getCreds("123".getBytes())); + Database database; + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource)) { + assertNotNull(inputStream); + database = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } StreamFormat format1 = database.getStreamFormat(); - database.save(getCreds("123".getBytes()), Files.newOutputStream(Paths.get("testOutput/test3.kdbx"))); + Path dbPath = tempPath.resolve("test3.kdbx"); + try (OutputStream outputStream = Files.newOutputStream(dbPath)) { + database.save(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), outputStream); + } - FileInputStream fis = new FileInputStream("testOutput/test3.kdbx"); - D input = loadDatabase(getCreds("123".getBytes()), fis); + Database input; + try (InputStream fis = Files.newInputStream(dbPath)) { + input = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fis); + } StreamFormat format2 = input.getStreamFormat(); assertTrue(verifyStreamFormat(format1, format2)); } } - private D createNewDatabase() throws IOException { - D database = getDatabase(); + private Database createNewDatabase() throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Database database = testBase.createDatabase(); for (@SuppressWarnings("WrapperTypeMayBePrimitive") Integer g = 0; g < 5; g++){ - G group = database.getRootGroup().addGroup(database.newGroup(g.toString())); + Group group = database.getRootGroup().addGroup(database.newGroup(g.toString())); for (int e = 0; e <= g; e++) { group.addEntry(entryFactory(database, g.toString(), e)); } @@ -148,28 +170,28 @@ private D createNewDatabase() throws IOException { return database; } - private E entryFactory(D database, String g, int e) { - E result = database.newEntry(); - result.setTitle(g + "-" + e); - result.setUsername(g + " - un - " + e); - result.setPassword(g + "- p -" + e); - result.setUrl(g + "- url - " + e); - result.setNotes(g + "- n - " + e); + private static Entry entryFactory(Database database, String g, int e) { + Entry result = database.newEntry(); + result.addProperty(TITLE, g + "-" + e) + .addProperty(USER_NAME, g + " - un - " + e) + .addProperty(PASSWORD, g + "- p -" + e) + .addProperty(URL,g + "- url - " + e) + .addProperty(NOTES,g + "- n - " + e); return result; } - private void verifyContents(D database) { + private static void verifyContents(Database database) { for (Integer g = 0; g< 5; g++){ - G group = database.getRootGroup().getGroups().get(g); + Group 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); + Entry entry = group.getEntries().get(e); assertEquals(g + "-" + e, entry.getTitle()); assertEquals(g + " - un - " + e, entry.getUsername()); - assertEquals(g + "- p -" + e, entry.getPassword()); + assertEquals(g + "- p -" + e, entry.getProperty(PASSWORD)); assertEquals(g + "- url - " + e, entry.getUrl()); assertEquals(g + "- n - " + e, entry.getNotes()); assertEquals(group, entry.getParent()); @@ -182,38 +204,44 @@ private void verifyContents(D database) { * * @throws IOException when naughty */ - @Test @Ignore - public void saveNewDatabase () throws IOException { - D database = createNewDatabase(); + @Disabled + default void saveNewDatabase(@TempDir Path tempPath) throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Database database = createNewDatabase(); - FileOutputStream outputStream = new FileOutputStream("compatibility.kdbx"); - saveDatabase(database, getCreds("123".getBytes()), outputStream); + Path dbPath = tempPath.resolve("compatibility.kdbx"); + try (OutputStream outputStream = Files.newOutputStream(dbPath)) { + testBase.saveDatabase(database, testBase.getCredentials(KdbConsts.PASSWORD_BYTES), outputStream); + } } /** * Doesn't do anything other than output the database using default PrintVisitor * @throws IOException when naughty */ - @Test @Ignore - public void inspectNewDatabase () throws IOException { - D database = createNewDatabase(); + @Disabled + default void inspectNewDatabase () throws IOException { + Database database = createNewDatabase(); - database.visit(new Visitor.Print(printStream)); + database.visit(new Visitor.Print(getTestPrintStream())); } // create a new database for messing around with - // the assertions here somewhat duplicate those in BasicDatabaseChecks + // the assertions here somewhat duplicate those elsewhere @Test - public void testNewDatabase() throws IOException { - D database = getDatabase(); - G root = database.getRootGroup(); - Assert.assertTrue(root.isRootGroup()); + default void testNewDatabase() throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + Database database = testBase.createDatabase(); + Group root = database.getRootGroup(); + 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")); + assertTrue(database.shouldProtect("Password")); + assertFalse(database.shouldProtect("Title")); + assertFalse(database.shouldProtect("Bogus")); assertEquals("New Database", database.getName()); database.setName("Modified Database"); @@ -223,13 +251,13 @@ public void testNewDatabase() throws IOException { database.setDescription("Test Database"); assertEquals("Test Database", database.getDescription()); - G group1 = database.newGroup("Group 1"); + Group 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()); + assertFalse(group1.isRootGroup()); + assertTrue(root.isRootGroup()); assertEquals(1, root.getGroups().size()); assertEquals(newGroupUUID, root.getGroups().get(0).getUuid()); @@ -246,11 +274,11 @@ public void testNewDatabase() throws IOException { try { root.setParent(group1); - Assert.fail("Cannot add root group to another group"); + fail("Cannot add root group to another group"); } catch (Exception ignored) { } - G group2 = database.newGroup(); + Group group2 = database.newGroup(); group2.setName("Group 2"); group1.addGroup(group2); assertEquals(1, group1.getGroups().size()); @@ -260,7 +288,7 @@ public void testNewDatabase() throws IOException { assertEquals(0, group1.getGroups().size()); assertEquals(2, root.getGroups().size()); - E entry1 = database.newEntry(); + Entry entry1 = database.newEntry(); entry1.setTitle("A new entry"); assertEquals("A new entry", entry1.getTitle()); entry1.setUsername("user name"); @@ -274,12 +302,12 @@ public void testNewDatabase() throws IOException { group2.addEntry(entry1); assertEquals(1, group2.getEntries().size()); - entry1.setPassword("pass"); - assertEquals("pass", entry1.getPassword()); + entry1.addProperty(PASSWORD, "pass"); + assertEquals("pass", entry1.getProperty(PASSWORD)); - E entry2 = database.newEntry(entry1); - entry2.setPassword("pass2"); - assertEquals("pass2", entry2.getPassword()); + Entry entry2 = database.newEntry(entry1); + entry2.addProperty(PASSWORD,"pass2"); + assertEquals("pass2", entry2.getProperty(PASSWORD)); group2.addEntry(entry2); assertEquals(2, group2.getEntries().size()); diff --git a/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest.java b/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest.java new file mode 100644 index 00000000..7e7ae22f --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.*; + +import java.util.List; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.PASSWORD; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.USER_NAME; + +public interface ProtectedPropertyTest { + + Supplier getDbFunc(); + + @Test + default void testShouldProtect() { + DatabaseTestBase testBase = getDbFunc().get(); + + String propertyName = PASSWORD; + assertThrows(UnsupportedOperationException.class, + () -> testBase.getDatabase().setShouldProtect(propertyName, false), + "By default the ProtectionStrategy should be immutable"); + assertTrue(testBase.getDatabase().shouldProtect(propertyName)); + } + + @Test + default void testSetShouldProtect() { + DatabaseTestBase testBase = getDbFunc().get(); + + testBase.getDatabase().setPropertyValueStrategy(new PropertyValue.Strategy.MutableProtectionStrategy()); + String propertyName = PASSWORD; + testBase.getDatabase().setShouldProtect(propertyName, true); + assertTrue(testBase.getDatabase().shouldProtect(propertyName)); + + testBase.getDatabase().setShouldProtect(propertyName, false); + assertFalse(testBase.getDatabase().shouldProtect(propertyName)); + } + + @Test + default void testListShouldProtect() { + DatabaseTestBase testBase = getDbFunc().get(); + + testBase.getDatabase().setPropertyValueStrategy(new PropertyValue.Strategy.MutableProtectionStrategy()); + String propertyName1 = PASSWORD; + String propertyName2 = USER_NAME; + testBase.getDatabase().setShouldProtect(propertyName1, true); + testBase.getDatabase().setShouldProtect(propertyName2, true); + + List protectedProperties = testBase.getDatabase().listShouldProtect(); + assertTrue(protectedProperties.contains(propertyName1)); + assertTrue(protectedProperties.contains(propertyName2)); + } + + @Test + default void testGetPropertyValueStrategy() { + DatabaseTestBase testBase = getDbFunc().get(); + + PropertyValue.Strategy strategy = testBase.getDatabase().getPropertyValueStrategy(); + assertNotNull(strategy); + } + + @Test + default void testSetPropertyValueStrategy() { + DatabaseTestBase testBase = getDbFunc().get(); + + PropertyValue.Strategy newStrategy = new PropertyValue.Strategy.Default(); + testBase.getDatabase().setPropertyValueStrategy(newStrategy); + assertEquals(newStrategy, testBase.getDatabase().getPropertyValueStrategy()); + } + + @Test + default void testSupportsPropertyValueStrategy() { + DatabaseTestBase testBase = getDbFunc().get(); + + assertTrue(testBase.getDatabase().supportsPropertyValueStrategy()); + } +} \ No newline at end of file diff --git a/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest2.java b/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest2.java new file mode 100644 index 00000000..d0799918 --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/ProtectedPropertyTest2.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.PropertyValue; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +public interface ProtectedPropertyTest2 { + + Supplier getDbFunc(); + + /* + void newDatabase(); + + Database testBase.getDatabase(); + + void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException; + + Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException; + + Credentials getCredentials(byte[] credentials); + + @BeforeEach + default void ppt2SetUp() throws Exception { + newDatabase(); + } + +*/ + /** + * Catches expected thrown exception + * + * @param f a Consumer i.e. a function that does have output + * @param c the exception that is expected to be thrown when the function is executed + */ + private static void catchException(Consumer f, @SuppressWarnings("SameParameterValue") Class c) { + try { + f.accept(null); + } + catch (Exception e) { + if (c.equals(e.getClass())) { + return; + } + } + fail(c.getName() + " expected"); + } + + /** + * Check that unsupported operations throw exceptions + */ + @Test + default void noPropertyValueSupport() { + DatabaseTestBase testBase = getDbFunc().get(); + + if (testBase.getDatabase().supportsPropertyValueStrategy()) { + return; + } + + assertFalse(testBase.getDatabase().supportsPropertyValueStrategy()); + // test a sample of the methods that are supposed to thrown if used here + catchException((_) -> testBase.getDatabase().setPropertyValueStrategy(null), UnsupportedOperationException.class); + catchException((_) -> testBase.getDatabase().getPropertyValueStrategy(), UnsupportedOperationException.class); + catchException((_) -> testBase.getDatabase().setShouldProtect(null, true), UnsupportedOperationException.class); + catchException((_) -> testBase.getDatabase().listShouldProtect(), UnsupportedOperationException.class); + + Entry entry = testBase.getDatabase().newEntry(); + catchException((z) -> entry.getPropertyValue(null), UnsupportedOperationException.class); + catchException((z) -> entry.setPropertyValue(null, (PropertyValue) null), UnsupportedOperationException.class); + + } + + @Test + default void checkDefaults() { + DatabaseTestBase testBase = getDbFunc().get(); + + // these are the defaults established by createDatabase() + assertTrue(testBase.getDatabase().shouldProtect(Entry.STANDARD_PROPERTY_NAME_PASSWORD)); + assertFalse(testBase.getDatabase().shouldProtect(Entry.STANDARD_PROPERTY_NAME_USER_NAME)); + assertFalse(testBase.getDatabase().shouldProtect(Entry.STANDARD_PROPERTY_NAME_NOTES)); + assertFalse(testBase.getDatabase().shouldProtect(Entry.STANDARD_PROPERTY_NAME_URL)); + assertFalse(testBase.getDatabase().shouldProtect(Entry.STANDARD_PROPERTY_NAME_TITLE)); + } + + @Test + default void expectedStorageType(@TempDir Path tempPath) throws IOException { + DatabaseTestBase testBase = getDbFunc().get(); + + if (!testBase.getDatabase().supportsPropertyValueStrategy()) { + return; + } + + // Password is protected + PropertyValue.Strategy pvs = testBase.getDatabase().getPropertyValueStrategy(); + PropertyValue pv = pvs.getFactoryFor("Password").of("123"); + assertEquals(pv.getClass(), pvs.newProtected().of("a").getClass()); + assertTrue(pv.isProtected()); + + // Notes isn't protected + pv = pvs.getFactoryFor("Notes").of("123"); + assertEquals(pv.getClass(), pvs.newUnprotected().of("b").getClass()); + assertFalse(pv.isProtected()); + + // "random" is not protected + pv = pvs.getFactoryFor("random").of("123"); + assertEquals(pv.getClass(), pvs.newUnprotected().of("b").getClass()); + assertFalse(pv.isProtected()); + + // create a strategy that makes "random" protected + PropertyValue.Strategy newPvs = new PropertyValue.Strategy.MutableProtectionStrategy(); + testBase.getDatabase().setPropertyValueStrategy(newPvs); + testBase.getDatabase().setShouldProtect("random", true); + + // get a value for random and check it is protected + pv = newPvs.getFactoryFor("random").of("123"); + assertEquals(pv.getClass(), pvs.newProtected().of("b").getClass()); + assertTrue(pv.isProtected()); + + // create an entry with random property as protected value + Entry entry = testBase.getDatabase().newEntry("Test Random"); + entry.setPropertyValue("random", pv); + testBase.getDatabase().getRootGroup().addEntry(entry); + + // Save database + + Path dbPath = tempPath.resolve("test9.kdbx"); + try (OutputStream fos = Files.newOutputStream(dbPath)) { + testBase.saveDatabase(testBase.getDatabase(), testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fos); + fos.flush(); + fos.close(); + } + + // reload database, "random" is still protected even though it's not protected by default + Database input; + try (InputStream fis = Files.newInputStream(dbPath)) { + input = testBase.loadDatabase(testBase.getCredentials(KdbConsts.PASSWORD_BYTES), fis); + } + + List entries = input.findEntries("random"); + assertEquals(1, entries.size()); + assertTrue(entries.get(0).getPropertyValue("random").isProtected()); + assertFalse(input.getPropertyValueStrategy().getProtectedProperties().contains("random")); + } + +} + diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/RecycleBinChecks.java b/database/src/test/java/org/linguafranca/pwdb/test/RecycleBinTest.java similarity index 58% rename from test/src/main/java/org/linguafranca/pwdb/checks/RecycleBinChecks.java rename to database/src/test/java/org/linguafranca/pwdb/test/RecycleBinTest.java index 3fb007ae..3ca4731b 100644 --- a/test/src/main/java/org/linguafranca/pwdb/checks/RecycleBinChecks.java +++ b/database/src/test/java/org/linguafranca/pwdb/test/RecycleBinTest.java @@ -1,31 +1,55 @@ -package org.linguafranca.pwdb.checks; +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.Entry; import org.linguafranca.pwdb.Group; -import org.linguafranca.pwdb.Icon; -import static org.junit.Assert.*; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * @author jo */ -public abstract class RecycleBinChecks , G extends Group, E extends Entry, I extends Icon> { +public interface RecycleBinTest { - protected Database database; + Supplier getDbFunc(); @Test - public void recycleBinEntries() { + default void recycleBinEntries() { + DatabaseTestBase testBase = getDbFunc().get(); + + assumeTrue(testBase.getDatabase().supportsRecycleBin(), "Database does not support recycle bin, skipping test"); + Database database = testBase.getDatabase(); database.enableRecycleBin(false); assertFalse(database.isRecycleBinEnabled()); assertNull(database.getRecycleBin()); database.enableRecycleBin(true); assertTrue(database.isRecycleBinEnabled()); - G recycleBin = database.getRecycleBin(); + Group recycleBin = database.getRecycleBin(); assertNotNull(recycleBin); - E entry = database.newEntry(); + Entry entry = database.newEntry(); assertEquals(entry, database.getRootGroup().addEntry(entry)); assertEquals(1, database.getRootGroup().getEntriesCount()); @@ -48,13 +72,17 @@ public void recycleBinEntries() { } @Test - public void recycleBinGroups() { + default void recycleBinGroups() { + DatabaseTestBase testBase = getDbFunc().get(); + + assumeTrue(testBase.getDatabase().supportsRecycleBin(), "Database does not support recycle bin, skipping test"); + Database database = testBase.getDatabase(); database.enableRecycleBin(true); assertTrue(database.isRecycleBinEnabled()); - G recycleBin = database.getRecycleBin(); + Group recycleBin = database.getRecycleBin(); assertNotNull(recycleBin); - G group= database.newGroup(); + Group group= database.newGroup(); assertEquals(group, database.getRootGroup().addGroup(group)); assertEquals(2, database.getRootGroup().getGroupsCount()); diff --git a/database/src/test/java/org/linguafranca/pwdb/test/Test123Test.java b/database/src/test/java/org/linguafranca/pwdb/test/Test123Test.java new file mode 100644 index 00000000..364fe495 --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/Test123Test.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.*; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.PASSWORD; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +/** + * a test123 file for each format. Should contain the same thing. This is a basic sanity check. + *

+ * (i.e. there exist files called test123 for some formats and this test tests their contents. presumably + * back in the day, a database was created then saved in kdbx and kdb formats and when there was more than + * one KDBX implementation the test was created to test them all as well as the KDB implementation) + */ +public interface Test123Test { + + Supplier getDbFunc(); + + boolean getSkipDateCheck(); + String getFileName(); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssX"); + + @Test + default void test123File() throws ParseException, IOException { + DatabaseTestBase db = getDbFunc().get(); + + Database database; + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(getFileName())) { + assertNotNull(inputStream); + database = db.loadDatabase(db.getCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } + // visit all groups and entries and list them to console + database.visit(new Visitor.Print(getTestPrintStream())); + + // find all entries in the database + // the kdb version has three additional system related entries + List anything = database.findEntries(""); + 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) { + getTestPrintStream().println(tes.getTitle()); + } + assertEquals(4, tests.size()); + if (!tests.isEmpty()) { + // copy the password of the first entry + String pass = tests.get(0).getProperty(PASSWORD); + // all the relevant entries should have the password 123 + String pass2 = tests.get(0).getProperty(PASSWORD); + assertEquals(pass, pass2); + assertEquals("123", pass2); + } + + List passwords = database.findEntries("password"); + assertEquals(4, passwords.size()); + for (Entry passwordEntry : passwords) { + assertEquals(passwordEntry.getTitle(), passwordEntry.getProperty(PASSWORD)); + getTestPrintStream().println(passwordEntry.getTitle()); + } + + List entries = database.findEntries(entry -> entry.getTitle().equals("hello world")); + + assertEquals(1, entries.size()); + assertEquals("pass", entries.get(0).getProperty(PASSWORD)); + + // 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 (getSkipDateCheck()) { + return; + } + + Date c = entries.get(0).getCreationTime(); + Date expected = sdf.parse("2015-10-24T17:20:41Z"); + assertEquals(expected, c); + } +} diff --git a/database/src/test/java/org/linguafranca/pwdb/test/TrivialDatabaseTest.java b/database/src/test/java/org/linguafranca/pwdb/test/TrivialDatabaseTest.java new file mode 100644 index 00000000..88caea66 --- /dev/null +++ b/database/src/test/java/org/linguafranca/pwdb/test/TrivialDatabaseTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.test; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Group; +import org.linguafranca.pwdb.Icon; + +import java.util.UUID; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +public interface TrivialDatabaseTest { + + Supplier getDbFunc(); + + @Test + default void testGetRootGroup() { + DatabaseTestBase db = getDbFunc().get(); + + Group root = db.getDatabase().getRootGroup(); + assertNotNull(root); + assertEquals("Root", root.getName()); + } + + @Test + default void testNewGroup() { + DatabaseTestBase db = getDbFunc().get(); + + Group group = db.getDatabase().newGroup(); + assertNotNull(group); + } + + @Test + default void testNewEntry() { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry = db.getDatabase().newEntry(); + assertNotNull(entry); + } + + @Test + default void testNewIcon() { + DatabaseTestBase db = getDbFunc().get(); + + Icon icon = db.getDatabase().newIcon(); + assertNotNull(icon); + } + + @Test + default void testNewIconWithInteger() { + DatabaseTestBase db = getDbFunc().get(); + + Icon icon = db.getDatabase().newIcon(1); + assertNotNull(icon); + assertEquals(1, icon.getIndex()); + } + + @Test + default void testRecycleBin() { + DatabaseTestBase db = getDbFunc().get(); + + if (db.getDatabase().supportsRecycleBin()) { + assertTrue(db.getDatabase().isRecycleBinEnabled()); + Group recycleBin = db.getDatabase().getRecycleBin(); + assertNotNull(recycleBin); + } else { + assertFalse(db.getDatabase().isRecycleBinEnabled()); + assertThrows(UnsupportedOperationException.class, () -> db.getDatabase().enableRecycleBin(true)); + assertNull(db.getDatabase().getRecycleBin()); + } + } + + @Test + default void testGetSetName() { + DatabaseTestBase db = getDbFunc().get(); + + assertEquals("New Database", db.getDatabase().getName()); + db.getDatabase().setName("Test Database"); + assertEquals("Test Database", db.getDatabase().getName()); + } + + @Test + default void testGetSetDescription() { + DatabaseTestBase db = getDbFunc().get(); + + assertNotNull(db.getDatabase().getDescription()); + db.getDatabase().setDescription("Test Description"); + assertEquals("Test Description", db.getDatabase().getDescription()); + } + + @Test + default void testDeleteEntry() { + DatabaseTestBase db = getDbFunc().get(); + + Entry entry = db.getDatabase().newEntry(); + db.getDatabase().getRootGroup().addEntry(entry); + UUID uuid = entry.getUuid(); + assertTrue(db.getDatabase().deleteEntry(uuid)); + assertNull(db.getDatabase().findEntry(uuid)); + } + + @Test + default void testDeleteGroup() { + DatabaseTestBase db = getDbFunc().get(); + + Group group = db.getDatabase().newGroup(); + db.getDatabase().getRootGroup().addGroup(group); + UUID uuid = group.getUuid(); + assertTrue(db.getDatabase().deleteGroup(uuid)); + assertNull(db.getDatabase().findGroup(uuid)); + } + + @Test + default void testSupportsNonStandardPropertyNames() { + DatabaseTestBase db = getDbFunc().get(); + + // check standard names are there + Entry entry = db.getDatabase().newEntry(); + for (String name : Entry.STANDARD_PROPERTY_NAMES) { + assertNotNull(entry.getProperty(name)); + } + // add a random property + if (db.getDatabase().supportsNonStandardPropertyNames()) { + entry.setProperty("random", "you never know"); + assertEquals("you never know", entry.getProperty("random")); + } else { + assertThrows(UnsupportedOperationException.class, () -> entry.setProperty("random", "you never know")); + } + } + + @Test + default void testSupportsBinaryProperties() { + DatabaseTestBase db = getDbFunc().get(); + + if (db.getDatabase().supportsBinaryProperties()) { + Entry entry = db.getDatabase().newEntry(); + entry.setBinaryProperty("binary", new byte[]{1, 2, 3}); + assertArrayEquals(new byte[]{1, 2, 3}, entry.getBinaryProperty("binary")); + } else { + Entry entry = db.getDatabase().newEntry(); + assertThrows(UnsupportedOperationException.class, () -> entry.setBinaryProperty("binary", new byte[]{1, 2, 3})); + } + } +} \ No newline at end of file diff --git a/database/src/test/java/org/linguafranca/util/ExecutionListener.java b/database/src/test/java/org/linguafranca/util/ExecutionListener.java new file mode 100644 index 00000000..1c798048 --- /dev/null +++ b/database/src/test/java/org/linguafranca/util/ExecutionListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.util; + +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.slf4j.*; + +public class ExecutionListener implements TestExecutionListener { + + static Logger logger = LoggerFactory.getLogger("Test Execution Listener"); + static Marker marker = MarkerFactory.getMarker("Starting"); + static Marker endMarker = MarkerFactory.getMarker("Finished"); + static Marker failMarker = MarkerFactory.getMarker("Failed"); + static Marker assumeFailMarker = MarkerFactory.getMarker("Assumption Failed"); + static Marker ignoredMarker = MarkerFactory.getMarker("Ignored"); + static Marker skippedMarker = MarkerFactory.getMarker("Skipped"); + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + logger.info(ignoredMarker, "{} {}", testIdentifier.getDisplayName(), reason); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + logger.info(marker, "{} {}", testIdentifier.getType(), testIdentifier.getDisplayName()); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + MDC.put("result", testExecutionResult.getStatus().toString()); + switch (testExecutionResult.getStatus()) { + case FAILED: + logger.error(failMarker, "{}", testIdentifier.getDisplayName()); + break; + case ABORTED: + logger.warn(ignoredMarker, "{}", testIdentifier.getDisplayName()); + break; + case SUCCESSFUL: + logger.info(endMarker,"{}", testIdentifier.getDisplayName()); + } + MDC.clear(); + } +} diff --git a/database/src/test/java/org/linguafranca/util/TestUtil.java b/database/src/test/java/org/linguafranca/util/TestUtil.java new file mode 100644 index 00000000..7778f021 --- /dev/null +++ b/database/src/test/java/org/linguafranca/util/TestUtil.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +public class TestUtil { + + /** + * Database save closes output stream. If you want to output to console using KdbxFormat.None and System.out + * then use this class to wrap System.out, so that it doesn't get closed. + */ + public static class UncloseableOutputStream extends OutputStream { + + private final OutputStream outputStream; + + public UncloseableOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void write(int b) throws IOException { + outputStream.write(b); + } + + @Override + public void close() throws IOException { + // do nothing + } + } + + /** + * Null output stream, i.e. throw away everything that is written to it. Useful during builds + * when you don't want to see the output - e.g. CI builds. + */ + public static class NullOutputStream extends OutputStream { + + @Override + public void write(int b) throws IOException {} + } + + /** + * Get a print stream that will not close System.out, but will throw away all output + * if "enableConsoleOutput" system property has been not set - e.g. as a result of using + * mvn clean install -P enableConsoleOutput. + */ + public static PrintStream getTestPrintStream() { + return !Boolean.getBoolean("enableConsoleOutput") + ? new PrintStream(new UncloseableOutputStream(System.out)) + : new PrintStream(new NullOutputStream()); + } +} \ No newline at end of file diff --git a/database/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/database/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 00000000..c5b20de5 --- /dev/null +++ b/database/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1,17 @@ +# +# Copyright (c) 2025. Jo Rabin +# +# 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. +# +# +org.linguafranca.util.ExecutionListener \ No newline at end of file diff --git a/database/src/test/resources/junit-platform.properties b/database/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..2115a2f1 --- /dev/null +++ b/database/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = true diff --git a/dom/pom.xml b/dom/pom.xml deleted file mode 100644 index fa44bcb4..00000000 --- a/dom/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - ../pom.xml - - 4.0.0 - - KeePassJava2-dom - KeePassJava2 :: DOM - - - - org.linguafranca.pwdb - KeePassJava2-kdbx - ${project.version} - - - org.linguafranca.pwdb - test - ${project.version} - test - - - junit - junit - test - - - org.linguafranca.pwdb - test - 2.2.3-SNAPSHOT - compile - - - \ No newline at end of file diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapper.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapper.java deleted file mode 100644 index d6ba2513..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapper.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.StreamConfiguration; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.base.AbstractDatabase; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Objects; -import java.util.UUID; - -import static com.google.common.base.Preconditions.checkNotNull; -import static org.linguafranca.pwdb.kdbx.Helpers.base64FromUuid; -import static org.linguafranca.pwdb.kdbx.dom.DomHelper.*; - -/** - * The class wraps a {@link DomSerializableDatabase} as a {@link org.linguafranca.pwdb.Database}. - * - * @author jo - */ -public class DomDatabaseWrapper extends AbstractDatabase { - - private final DomSerializableDatabase domDatabase = DomSerializableDatabase.createEmptyDatabase(); - Element dbMeta; - private Document document; - private Element dbRootGroup; - - - private StreamFormat streamFormat; - - - public DomDatabaseWrapper() { - init(); - } - - /** - * load a database - * @param credentials credentials to use - * @param inputStream where to read from - * @return a database - */ - public DomDatabaseWrapper(Credentials credentials, InputStream inputStream) throws IOException { - this.streamFormat = new KdbxStreamFormat(); - streamFormat.load(domDatabase, credentials, inputStream); - init(); - } - - /** - * load a database - * @param credentials credentials to use - * @param inputStream where to read from - * @return a database - */ - public static DomDatabaseWrapper load(@NotNull Credentials credentials, - @NotNull InputStream inputStream) throws IOException { - return new DomDatabaseWrapper( - checkNotNull(credentials, "Credentials must not be null"), - checkNotNull(inputStream, "InputStream must not be null")); - } - - private void init() { - document = domDatabase.getDoc(); - try { - dbRootGroup = ((Element) DomHelper.xpath.evaluate("/KeePassFile/Root/Group", document, XPathConstants.NODE)); - dbMeta = ((Element) DomHelper.xpath.evaluate("/KeePassFile/Meta", document, XPathConstants.NODE)); - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - - /** - * Save database with same format/encryption settings as it was loaded with or V4 defaults if was not loaded - * @param credentials credentials to use - * @param outputStream where to write - */ - @Override - public void save(Credentials credentials, - OutputStream outputStream) throws IOException { - if (Objects.isNull(streamFormat)){ - streamFormat = new KdbxStreamFormat(new KdbxHeader(4)); - } - save(streamFormat, credentials, outputStream); - } - - /** - * Save database with same format/encryption settings as it was loaded with - * @param streamFormat format/config to use for saving - * @param credentials credentials to use - * @param outputStream where to write - */ - public void save(StreamFormat streamFormat, - Credentials credentials, - OutputStream outputStream) throws IOException { - DomHelper.getElement("//Generator", domDatabase.getDoc().getDocumentElement(), false).setTextContent("KeePassJava2-DOM"); - streamFormat.save(domDatabase, credentials, outputStream); - setDirty(false); - } - - public boolean shouldProtect(String name) { - Element protectionElement = DomHelper.getElement("MemoryProtection/Protect" + name, dbMeta, false); - if (protectionElement == null) { - return false; - } - return Boolean.parseBoolean(protectionElement.getTextContent()); - } - - @Override - public DomGroupWrapper getRootGroup() { - return new DomGroupWrapper(dbRootGroup, this, false); - } - - @Override - public DomGroupWrapper newGroup() { - return new DomGroupWrapper(document.createElement(DomHelper.GROUP_ELEMENT_NAME), this, true); - } - - @Override - public DomEntryWrapper newEntry() { - return new DomEntryWrapper(document.createElement(DomHelper.ENTRY_ELEMENT_NAME), this, true); - } - - @Override - public DomIconWrapper newIcon() { - return new DomIconWrapper(document.createElement(DomHelper.ICON_ELEMENT_NAME)); - } - - @Override - public DomIconWrapper newIcon(Integer i) { - DomIconWrapper icon = newIcon(); - icon.setIndex(i); - return icon; - } - - @Override - public DomGroupWrapper getRecycleBin() { - String UuidContent = getElementContent(RECYCLE_BIN_UUID_ELEMENT_NAME, dbMeta); - if (UuidContent != null) { - final UUID uuid = Helpers.uuidFromBase64(UuidContent); - if (uuid.getLeastSignificantBits() != 0 && uuid.getMostSignificantBits() != 0) { - for (DomGroupWrapper g : getRootGroup().getGroups()) { - if (g.getUuid().equals(uuid)) { - return g; - } - } - // the recycle bin seems to have been lost, better create another one - } - // uuid was 0 i.e. there isn't one - } - // no recycle bin group set up - if (!isRecycleBinEnabled()) { - return null; - } - - DomGroupWrapper g = newGroup(); - g.setName("Recycle Bin"); - getRootGroup().addGroup(g); - ensureElementContent(RECYCLE_BIN_UUID_ELEMENT_NAME, dbMeta, base64FromUuid(g.getUuid())); - touchElement(RECYCLE_BIN_CHANGED_ELEMENT_NAME, dbMeta); - return g; - } - - @Override - public boolean isRecycleBinEnabled() { - return Boolean.parseBoolean(getElementContent(RECYCLE_BIN_ENABLED_ELEMENT_NAME, dbMeta)); - } - - @Override - public void enableRecycleBin(boolean enable) { - setElementContent(RECYCLE_BIN_ENABLED_ELEMENT_NAME, dbMeta, ((Boolean) enable).toString()); - } - - public String getName() { - return DomHelper.getElementContent("DatabaseName", dbMeta); - } - - public void setName(String name) { - DomHelper.setElementContent("DatabaseName", dbMeta, name); - DomHelper.touchElement("DatabaseNameChanged", dbMeta); - setDirty(true); - } - - @Override - public String getDescription() { - return DomHelper.getElementContent("DatabaseDescription", dbMeta); - } - - @Override - public void setDescription(String description) { - DomHelper.setElementContent("DatabaseDescription", dbMeta, description); - DomHelper.touchElement("DatabaseDescriptionChanged", dbMeta); - setDirty(true); - } - public StreamFormat getStreamFormat() { - return streamFormat; - } -} 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 deleted file mode 100644 index 39575301..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomEntryWrapper.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.base.AbstractEntry; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.w3c.dom.Element; - -import java.util.*; - -/** - * Class wraps Entries from a {@link DomSerializableDatabase} as {@link org.linguafranca.pwdb.Entry} - * - * @author jo - */ -public class DomEntryWrapper extends AbstractEntry { - - private static Map mandatoryEntryElements = new HashMap() {{ - put(DomHelper.UUID_ELEMENT_NAME, new DomHelper.UuidValueCreator()); - put(DomHelper.ICON_ELEMENT_NAME, new DomHelper.ConstantValueCreator("2")); - put(DomHelper.TIMES_ELEMENT_NAME, new DomHelper.ConstantValueCreator("")); - put(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, new DomHelper.DateValueCreator()); - put(DomHelper.CREATION_TIME_ELEMENT_NAME, new DomHelper.DateValueCreator()); - put(DomHelper.LAST_ACCESS_TIME_ELEMENT_NAME, new DomHelper.DateValueCreator()); - put(DomHelper.EXPIRY_TIME_ELEMENT_NAME, new DomHelper.DateValueCreator()); - put(DomHelper.EXPIRES_ELEMENT_NAME, new DomHelper.ConstantValueCreator("False")); - put(DomHelper.USAGE_COUNT_ELEMENT_NAME, new DomHelper.ConstantValueCreator("0")); - put(DomHelper.LOCATION_CHANGED, new DomHelper.DateValueCreator()); - }}; - - final Element element; - private final DomDatabaseWrapper database; - - public DomEntryWrapper(Element element, DomDatabaseWrapper database, boolean newElement) { - this.element = element; - this.database = database; - if (newElement) { - DomHelper.ensureElements(element, mandatoryEntryElements); - ensureProperty("Notes"); - ensureProperty("Title"); - ensureProperty("URL"); - ensureProperty("UserName"); - ensureProperty("Password"); - } - } - - @Override - public String getProperty(String name) { - Element property = DomHelper.getElement(String.format(DomHelper.PROPERTY_ELEMENT_FORMAT, name), element, false); - if (property == null) { - return null; - } - return DomHelper.getElementContent(DomHelper.VALUE_ELEMENT_NAME, property); - } - - @Override - public void setProperty(String name, String value) { - Element property = DomHelper.getElement(String.format(DomHelper.PROPERTY_ELEMENT_FORMAT, name), element, false); - if (property == null) { - property = DomHelper.newElement("String", element); - DomHelper.setElementContent("Key", property, name); - } - DomHelper.setElementContent(DomHelper.VALUE_ELEMENT_NAME, property, value); - DomHelper.touchElement(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, element); - database.setDirty(true); - } - - @Override - public boolean removeProperty(String name) throws IllegalArgumentException { - if (STANDARD_PROPERTY_NAMES.contains(name)) throw new IllegalArgumentException("may not remove property: " + name); - boolean wasRemoved = DomHelper.removeElement(String.format(DomHelper.PROPERTY_ELEMENT_FORMAT, name), element); - if (wasRemoved) database.setDirty(true); - return wasRemoved; - } - - @Override - public List getPropertyNames() { - ArrayList result = new ArrayList<>(); - List list = DomHelper.getElements("String", element); - for (Element listElement: list) { - result.add(DomHelper.getElementContent("Key", listElement)); - } - return result; - } - - @Override - public byte[] getBinaryProperty(String name) { - Element property = DomHelper.getElement(String.format(DomHelper.BINARY_PROPERTY_ELEMENT_FORMAT, name), element, false); - if (property == null) { - return null; - } - return DomHelper.getBinaryElementContent(DomHelper.VALUE_ELEMENT_NAME, property); - } - - @Override - public void setBinaryProperty(String name, byte[] value) { - Element property = DomHelper.getElement(String.format(DomHelper.BINARY_PROPERTY_ELEMENT_FORMAT, name), element, false); - if (property == null) { - property = DomHelper.newElement("Binary", element); - DomHelper.setElementContent("Key", property, name); - } - DomHelper.setBinaryElementContent(DomHelper.VALUE_ELEMENT_NAME, property, value); - DomHelper.touchElement(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, element); - database.setDirty(true); - - } - - @Override - public boolean removeBinaryProperty(String name) { - boolean wasRemoved = DomHelper.removeElement(String.format(DomHelper.BINARY_PROPERTY_ELEMENT_FORMAT, name), element); - if (wasRemoved) database.setDirty(true); - return wasRemoved; - } - - @Override - public List getBinaryPropertyNames() { - ArrayList result = new ArrayList<>(); - List list = DomHelper.getElements("Binary", element); - for (Element listElement: list) { - result.add(DomHelper.getElementContent("Key", listElement)); - } - return result; - } - - private void ensureProperty(String name){ - Element property = DomHelper.getElement(String.format(DomHelper.PROPERTY_ELEMENT_FORMAT, name), element, false); - if (property == null) { - Element container = DomHelper.newElement("String", element); - DomHelper.setElementContent("Key", container, name); - DomHelper.getElement("Value", container, true); - } - } - - @Override - public DomGroupWrapper getParent() { - if (element.getParentNode() == null) { - return null; - } - return new DomGroupWrapper((Element) element.getParentNode(), database, false); - } - - @Override - public UUID getUuid() { - return Helpers.uuidFromBase64(DomHelper.getElementContent(DomHelper.UUID_ELEMENT_NAME, element)); - } - - @Override - public DomIconWrapper getIcon() { - return new DomIconWrapper(DomHelper.getElement(DomHelper.ICON_ELEMENT_NAME, element, false)); - } - - @Override - public void setIcon(DomIconWrapper icon) { - DomHelper.getElement(DomHelper.ICON_ELEMENT_NAME, element, true).setTextContent(String.valueOf(icon.getIndex())); - DomHelper.touchElement(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, element); - database.setDirty(true); - } - - @Override - public Date getLastAccessTime() { - return Helpers.toDate(DomHelper.getElementContent(DomHelper.LAST_ACCESS_TIME_ELEMENT_NAME, element)); - } - - @Override - public Date getCreationTime() { - return Helpers.toDate(DomHelper.getElementContent(DomHelper.CREATION_TIME_ELEMENT_NAME, element)); - } - - @Override - public boolean getExpires() { - String content = DomHelper.getElementContent(DomHelper.EXPIRES_ELEMENT_NAME, element); - return content != null && content.equalsIgnoreCase("true"); - } - - @Override - public void setExpires(boolean expires) { - DomHelper.setElementContent(DomHelper.EXPIRES_ELEMENT_NAME, element, expires ? "True" : "False"); - } - - @Override - public Date getExpiryTime() { - return Helpers.toDate(DomHelper.getElementContent(DomHelper.EXPIRY_TIME_ELEMENT_NAME, element)); - } - - @Override - public void setExpiryTime(Date expiryTime) throws IllegalArgumentException { - if (expiryTime == null) throw new IllegalArgumentException("expiryTime may not be null"); - String formatted = Helpers.fromDate(expiryTime); - DomHelper.setElementContent(DomHelper.EXPIRY_TIME_ELEMENT_NAME, element, formatted); - } - - @Override - public Date getLastModificationTime() { - return Helpers.toDate(DomHelper.getElementContent(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, element)); - } - - @Override - protected void touch() { - DomHelper.setElementContent(DomHelper.LAST_MODIFICATION_TIME_ELEMENT_NAME, element, Helpers.fromDate(new Date())); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DomEntryWrapper that = (DomEntryWrapper) o; - - return element.equals(that.element) && database.equals(that.database); - - } -} diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomGroupWrapper.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomGroupWrapper.java deleted file mode 100644 index 504cf848..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomGroupWrapper.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.linguafranca.pwdb.Icon; -import org.linguafranca.pwdb.base.AbstractGroup; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.w3c.dom.Element; - -import java.util.*; - -import static org.linguafranca.pwdb.kdbx.dom.DomHelper.*; - - -/** - * Class wraps Groups from a {@link DomSerializableDatabase} as {@link org.linguafranca.pwdb.Group} - * - * @author jo - */ -public class DomGroupWrapper extends AbstractGroup { - - static Map mandatoryGroupElements = new HashMap() {{ - put(UUID_ELEMENT_NAME, new UuidValueCreator()); - put(NAME_ELEMENT_NAME, new ConstantValueCreator("")); - put(NOTES_ELEMENT_NAME, new ConstantValueCreator("")); - put(ICON_ELEMENT_NAME, new ConstantValueCreator("0")); - put(TIMES_ELEMENT_NAME, new ConstantValueCreator("")); - put(LAST_MODIFICATION_TIME_ELEMENT_NAME, new DateValueCreator()); - put(CREATION_TIME_ELEMENT_NAME, new DateValueCreator()); - put(LAST_ACCESS_TIME_ELEMENT_NAME, new DateValueCreator()); - put(EXPIRY_TIME_ELEMENT_NAME, new DateValueCreator()); - put(EXPIRES_ELEMENT_NAME, new ConstantValueCreator("False")); - put(USAGE_COUNT_ELEMENT_NAME, new ConstantValueCreator("0")); - put(LOCATION_CHANGED, new DateValueCreator()); - }}; - - - private final Element element; - private final DomDatabaseWrapper database; - - public DomGroupWrapper(Element element, DomDatabaseWrapper database, boolean newGroup) { - this.database = database; - this.element = element; - if (newGroup) { - ensureElements(this.element, mandatoryGroupElements); - } - } - - @Override - public boolean isRootGroup() { - Element parent = ((Element) element.getParentNode()); - return parent != null && (parent.getTagName().equals("Root")); - } - - @Override - public boolean isRecycleBin() { - String UUIDcontent = getElementContent(RECYCLE_BIN_UUID_ELEMENT_NAME, database.dbMeta); - if (UUIDcontent != null){ - UUID uuid = Helpers.uuidFromBase64(UUIDcontent); - return uuid.equals(this.getUuid()); - } - return false; - } - - @Override - public @Nullable DomGroupWrapper getParent() { - Element parent = ((Element) element.getParentNode()); - if (parent == null) { - return null; - } - // if the element is the root group there is no parent - if (element == element.getOwnerDocument().getDocumentElement().getElementsByTagName(GROUP_ELEMENT_NAME).item(0)){ - return null; - } - return new DomGroupWrapper(parent, database, false); - } - - @Override - public void setParent(DomGroupWrapper parent) { - parent.addGroup(this); - } - - @Override - public List getGroups() { - List elements = getElements(GROUP_ELEMENT_NAME, this.element); - List result = new ArrayList<>(elements.size()); - for (Element e: elements){ - result.add(new DomGroupWrapper(e, database, false)); - } - return result; - } - - @Override - public int getGroupsCount() { - List elements = getElements(GROUP_ELEMENT_NAME, this.element); - return elements.size(); - } - - @Override - public DomGroupWrapper addGroup(DomGroupWrapper group) { - if (group.isRootGroup()) { - throw new IllegalStateException("Cannot set root group as child of another group"); - } - // skip if this is a new group with no parent - if (group.getParent() != null) { - group.getParent().touch(); - group.getParent().removeGroup(group); - } - element.appendChild(group.element); - touchElement("Times/LocationChanged", group.element); - touch(); - return group; - } - - @Override - public DomGroupWrapper removeGroup(DomGroupWrapper g1) { - element.removeChild(g1.element); - database.setDirty(true); - return g1; - } - - @Override - public List getEntries() { - List elements = getElements(ENTRY_ELEMENT_NAME, this.element); - List entries = new ArrayList<>(elements.size()); - for(Element e: elements) { - entries.add(new DomEntryWrapper(e, database, false)); - } - return entries; - } - - @Override - public int getEntriesCount() { - return getElementsCount(ENTRY_ELEMENT_NAME, this.element); - } - - @Override - public DomEntryWrapper addEntry(DomEntryWrapper entry) { - if (entry.getParent() != null) { - entry.element.getParentNode().removeChild(element); - } - element.appendChild(entry.element); - database.setDirty(true); - return entry; - } - - @Override - public DomEntryWrapper removeEntry(DomEntryWrapper e12) { - element.removeChild(e12.element); - database.setDirty(true); - return e12; - } - - @Override - public String getName() { - return getElementContent(NAME_ELEMENT_NAME, element); - } - - @Override - public void setName(String name) { - setElementContent(NAME_ELEMENT_NAME, element, name); - database.setDirty(true); - } - - @Override - public UUID getUuid() { - String encodedUuid = getElementContent(UUID_ELEMENT_NAME, element); - return Helpers.uuidFromBase64(encodedUuid); - } - - @Override - public DomIconWrapper getIcon() { - return new DomIconWrapper(getElement(ICON_ELEMENT_NAME, element, false)); - } - - @Override - public void setIcon(DomIconWrapper icon) { - setElementContent(ICON_ELEMENT_NAME, element, String.valueOf(icon.getIndex())); - database.setDirty(true); - } - - @NotNull - @Override - public DomDatabaseWrapper getDatabase() { - return database; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DomGroupWrapper that = (DomGroupWrapper) o; - - return element.equals(that.element) && database.equals(that.database); - - } - - private void touch() { - touchElement(LAST_MODIFICATION_TIME_ELEMENT_NAME, this.element); - this.database.setDirty(true); - } -} diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomIconWrapper.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomIconWrapper.java deleted file mode 100644 index b965e143..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomIconWrapper.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.Icon; -import org.w3c.dom.Element; - -/** - * Class wraps Icons from a {@link DomSerializableDatabase} as {@link Icon} - * - * @author jo - */ -public class DomIconWrapper implements Icon { - - private final Element element; - - public DomIconWrapper(Element element) { - this.element = element; - } - - @Override - public int getIndex() { - return Integer.parseInt(element.getTextContent()); - } - - @Override - public void setIndex(int index) { - element.setTextContent(String.valueOf(index)); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof Icon)) return false; - - Icon that = (Icon) o; - - return this.getIndex() == that.getIndex(); - - } -} diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomPropertyValueTest.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomPropertyValueTest.java deleted file mode 100644 index c695129a..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomPropertyValueTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.checks.PropertyValueChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class DomPropertyValueTest extends PropertyValueChecks { - - public DomPropertyValueTest() throws IOException { - super(false); - } - - - @Override - public Database createDatabase() throws IOException { - return new DomDatabaseWrapper(); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return DomDatabaseWrapper.load(credentials, inputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} diff --git a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomSerializableDatabase.java b/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomSerializableDatabase.java deleted file mode 100644 index 9bfe4fce..00000000 --- a/dom/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomSerializableDatabase.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - - -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.SerializableDatabase; -import org.linguafranca.pwdb.security.StreamEncryptor; -import org.apache.commons.codec.binary.Base64; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Date; - -/** - * This class is an XML DOM implementation of a KDBX database. The data is maintained as a DOM, - * despite the obvious inefficiency of doing do, in order to maintain transparency on loading and - * saving of elements and attributes this implementation knows nothing about. - * - *

Obviously, perhaps, if the database is added to, or under certain types of modification, - * those elements will be missing from a re-serialization. - * - * @author jo - */ -public class DomSerializableDatabase implements SerializableDatabase { - - private Document doc; - private StreamEncryptor encryption; - - private DomSerializableDatabase() {} - - public static DomSerializableDatabase createEmptyDatabase() { - DomSerializableDatabase result = new DomSerializableDatabase(); - // read in the template KeePass XML database - try { - result.load(result.getClass().getClassLoader().getResourceAsStream("base.kdbx.xml")); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - // set the root group UUID - Node uuid = (Node) DomHelper.xpath.evaluate("//"+ DomHelper.UUID_ELEMENT_NAME, result.doc.getDocumentElement(), XPathConstants.NODE); - uuid.setTextContent(DomHelper.base64RandomUuid()); - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - return result; - } - - @Override - public SerializableDatabase load(InputStream inputStream) throws IOException { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - try { - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(inputStream); - - // we need to decrypt all protected fields - NodeList protectedContent = (NodeList) DomHelper.xpath.evaluate("//*[@Protected='True']", doc, XPathConstants.NODESET); - for (int i = 0; i < protectedContent.getLength(); i++){ - Element element = ((Element) protectedContent.item(i)); - String base64 = DomHelper.getElementContent(".", element); - // Android compatibility - byte[] encrypted = Base64.decodeBase64(base64.getBytes()); - String decrypted = new String(encryption.decrypt(encrypted), StandardCharsets.UTF_8); - DomHelper.setElementContent(".", element, decrypted); - element.removeAttribute("Protected"); - element.setAttribute("kpj2-ProtectOnOutput", "True"); - } - return this; - - } catch (ParserConfigurationException e) { - throw new IllegalStateException("Instantiating Document Builder", e); - } catch (SAXException e) { - throw new IllegalStateException("Parsing exception", e); - } catch (XPathExpressionException e) { - throw new IllegalStateException("XPath Exception", e); - } - } - - @Override - public void addBinary(int index, byte[] payload) { - DomHelper.addBinary(doc.getDocumentElement(), Helpers.encodeBase64Content(payload, true),index); - } - - @Override - public byte[] getBinary(int index) { - return Helpers.decodeBase64Content(DomHelper.getBinary(doc.getDocumentElement(), index).getBytes(StandardCharsets.UTF_8), true); - } - - @Override - public int getBinaryCount() { - return DomHelper.getBinaryCount(doc.getDocumentElement()); - } - - @Override - public void save(OutputStream outputStream) { - // make a copy so we can mess with content - Document copyDoc = (Document) doc.cloneNode(true); - try { - // check whether protection is required by default and if so mark the element with @@kpj2-ProtectOnOutput='True' - prepareProtection(copyDoc, "Title"); - prepareProtection(copyDoc, "UserName"); - prepareProtection(copyDoc, "Password"); - prepareProtection(copyDoc, "Notes"); - prepareProtection(copyDoc, "URL"); - - // encrypt and base64 every element marked as protected - NodeList protectedContent = (NodeList) DomHelper.xpath.evaluate("//*[@kpj2-ProtectOnOutput='True']", copyDoc, XPathConstants.NODESET); - for (int i = 0; i < protectedContent.getLength(); i++){ - Element element = ((Element) protectedContent.item(i)); - - element.removeAttribute("kpj2-ProtectOnOutput"); - element.setAttribute("Protected", "True"); - - String decrypted = DomHelper.getElementContent(".", element); - if (decrypted == null) { - decrypted = ""; - } - byte[] encrypted = encryption.encrypt(decrypted.getBytes()); - // Android compatibility - String base64 = new String(Base64.encodeBase64(encrypted)); - DomHelper.setElementContent(".", element, base64); - } - - // we need to serialise dates according to the format, and - // we are going to say that anything that is a date is called *Changed or *Time - // because that does work in the schema, but won't work for custom values - NodeList timeBasedContent = (NodeList) DomHelper.xpath.evaluate( - //"//*[ends-with(name(), 'Changed')] | //*[ends-with(name(), 'Time')]", - // dealing with XPath1 - "//*[substring(name(),string-length(name())-6) = 'Changed'] | //*[substring(name(),string-length(name())-3) = 'Time']", - copyDoc, - XPathConstants.NODESET); - for (int i = 0; i < timeBasedContent.getLength(); i++){ - Element element = ((Element) timeBasedContent.item(i)); - String time = DomHelper.getElementContent(".", element); - Date date; - if (time.equals("${creationDate}")) { - date = Date.from(Instant.now()); - } else { - date = Helpers.toDate(time); - } - String encoded = Helpers.fromDate(date); - DomHelper.setElementContent(".", element, encoded); - } - - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - - Source xmlSource = new DOMSource(copyDoc); - try { - TransformerFactory factory = TransformerFactory.newInstance(); - //factory.setAttribute("indent-number", "4"); - Transformer transformer = factory.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - //noinspection HttpUrlsUsage - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - transformer.transform(xmlSource, new StreamResult(outputStream)); - } catch (TransformerException e) { - throw new IllegalStateException(e); - } - } - - private static final String protectQuery = "//Meta/MemoryProtection/Protect%s"; - private static final String pattern = "//String/Key[text()='%s']/following-sibling::Value"; - private void prepareProtection(Document doc, String protect) throws XPathExpressionException { - // does this require encryption - String query = String.format(protectQuery, protect); - if (!((String) DomHelper.xpath.evaluate(query, doc, XPathConstants.STRING)).equalsIgnoreCase("true")) { - return; - } - // mark the field as Protected but don't actually encrypt yet, that comes later - String path = String.format(pattern, protect); - NodeList nodelist = (NodeList) DomHelper.xpath.evaluate(path, doc, XPathConstants.NODESET); - for (int i = 0; i < nodelist.getLength(); i++) { - Element element = (Element) nodelist.item(i); - element.setAttribute("kpj2-ProtectOnOutput", "True"); - } - } - - @Override - public byte[] getHeaderHash() { - try { - String base64 = (String) DomHelper.xpath.evaluate("//HeaderHash", doc, XPathConstants.STRING); - // Android compatibility - return Base64.decodeBase64(base64.getBytes()); - } catch (XPathExpressionException e) { - throw new IllegalStateException("Can't get header hash", e); - } - } - - @Override - public void setHeaderHash(byte[] hash) { - // Android compatibility - String base64String = new String(Base64.encodeBase64(hash)); - DomHelper.ensureElementContent("Meta/HeaderHash", doc.getDocumentElement(), base64String); - //((Element) DomHelper.xpath.evaluate("//HeaderHash", doc, XPathConstants.NODE)).setTextContent(base64String); - } - - - @Override - public StreamEncryptor getEncryption() { - return encryption; - } - - @Override - public void setEncryption(StreamEncryptor encryption) { - this.encryption = encryption; - } - - public Document getDoc() { - return doc; - } -} diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV3Test.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV3Test.java deleted file mode 100644 index 58ccdebc..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV3Test.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class DomBinaryPropertyV3Test extends BinaryPropertyChecks { - - public DomBinaryPropertyV3Test() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx"); - database = DomDatabaseWrapper.load(new KdbxCreds("123".getBytes()), inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return DomDatabaseWrapper.load(credentials, inputStream); - } - - @Override - public Database newDatabase() { - return new DomDatabaseWrapper(); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} \ No newline at end of file diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV4Test.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV4Test.java deleted file mode 100644 index 97499e18..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomBinaryPropertyV4Test.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class DomBinaryPropertyV4Test extends BinaryPropertyChecks { - - public DomBinaryPropertyV4Test() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-Argon2-Attachment.kdbx"); - database = DomDatabaseWrapper.load(new KdbxCreds("123".getBytes()), inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - StreamFormat sf = new KdbxStreamFormat(new KdbxHeader(4)); - database.save(sf, credentials, outputStream); - } - - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return DomDatabaseWrapper.load(credentials, inputStream); - } - - @Override - public Database newDatabase() { - return new DomDatabaseWrapper(); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} \ No newline at end of file diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperLoaderTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperLoaderTest.java deleted file mode 100644 index e980f98a..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperLoaderTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.checks.DatabaseLoaderChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; - -import java.io.IOException; -import java.io.InputStream; - -/** - * @author jo - */ -public class DomDatabaseWrapperLoaderTest extends DatabaseLoaderChecks { - - public DomDatabaseWrapperLoaderTest() throws IOException { - // get an input stream from kdbx file - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - // file has password credentials - Credentials credentials = new KdbxCreds("123".getBytes()); - // open database. DomDatabaseWrapper is so-called, since it wraps - // a W3C DOM, populated from the KeePass XML, and presents it - // through a org.linguafranca.keepass.Database interface. - super.database = DomDatabaseWrapper.load(credentials, inputStream); - } -} diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperTest.java deleted file mode 100644 index aef78d3d..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomDatabaseWrapperTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; -import org.junit.Test; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.Credentials; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class DomDatabaseWrapperTest extends BasicDatabaseChecks { - - static PrintStream printStream = getTestPrintStream(); - - public DomDatabaseWrapperTest() throws IOException { - } - - @Test - public void inspectPasswordDatabase() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - DomDatabaseWrapper database = new DomDatabaseWrapper(new KdbxCreds("123".getBytes()), inputStream); - - database.save(new StreamFormat.None(), new Credentials.None(), printStream); - } - - @Test - public void inspectKeyfileDatabase() throws IOException { - InputStream keyFileInputStream = getClass().getClassLoader().getResourceAsStream("KeyFileDatabase.key"); - Credentials credentials = new KdbxCreds("123".getBytes(), keyFileInputStream); - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("KeyFileDatabase.kdbx"); - DomDatabaseWrapper database = new DomDatabaseWrapper(credentials, inputStream); - - database.save(new StreamFormat.None(), new Credentials.None(), printStream); - } - - @Override - public Database createDatabase() throws IOException { - return new DomDatabaseWrapper(); - } -} \ No newline at end of file diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomHelperTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomHelperTest.java deleted file mode 100644 index 227b5e3e..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomHelperTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.junit.Test; -import org.linguafranca.pwdb.kdbx.Helpers; - -import java.io.PrintStream; - -import static org.junit.Assert.assertEquals; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class DomHelperTest { - - static PrintStream printStream = getTestPrintStream(); - - @Test - public void testBase64RandomUuid() throws Exception { - // just check that it can do something - String uuid1 = DomHelper.base64RandomUuid(); - printStream.println(Helpers.uuidFromBase64(uuid1)); - String uuid2 = DomHelper.base64RandomUuid(); - printStream.println(Helpers.uuidFromBase64(uuid2)); - } -} \ No newline at end of file diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomRecycleBinTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomRecycleBinTest.java deleted file mode 100644 index 9502ff6e..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomRecycleBinTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.linguafranca.pwdb.kdbx.dom; - -import org.linguafranca.pwdb.checks.RecycleBinChecks; - -import java.io.IOException; - -/** - * @author jo - */ -public class DomRecycleBinTest extends RecycleBinChecks { - public DomRecycleBinTest() throws IOException { - database = new DomDatabaseWrapper(); - } -} diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomSaveAndReloadTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomSaveAndReloadTest.java deleted file mode 100644 index 704f0a72..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomSaveAndReloadTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.checks.SaveAndReloadChecks; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class DomSaveAndReloadTest extends SaveAndReloadChecks { - - @Override - public Database getDatabase() { - return new DomDatabaseWrapper(); - } - @Override - public Database getDatabase(String name, Credentials credentials) throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name); - return DomDatabaseWrapper.load(credentials, inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return DomDatabaseWrapper.load(credentials, inputStream); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds("123".getBytes()); - } - - @Override - public boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2) { - KdbxHeader h1 = (KdbxHeader) s1.getStreamConfiguration(); - KdbxHeader h2 = (KdbxHeader) s1.getStreamConfiguration(); - return (h1.getVersion() == h2.getVersion() && - h1.getProtectedStreamAlgorithm().equals(h2.getProtectedStreamAlgorithm()) && - h1.getKeyDerivationFunction().equals(h2.getKeyDerivationFunction()) && - h1.getCipherAlgorithm().equals(h2.getCipherAlgorithm())); - } -} diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomV4Test.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomV4Test.java deleted file mode 100644 index d3ad03a2..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/DomV4Test.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.linguafranca.pwdb.kdbx.dom; - -import org.junit.Test; -import org.linguafranca.pwdb.Visitor; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class DomV4Test { - - static PrintStream printStream = getTestPrintStream(); - - @Test - public void loadKdbxV4() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); - DomDatabaseWrapper database = DomDatabaseWrapper.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - // test what happens to dates in V4 - database.visit(new Visitor.Default(){ - - @Override - public void visit(DomEntryWrapper entry) { - printStream.println(entry.getCreationTime()); - } - }); - } - -} diff --git a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/VisitorTest.java b/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/VisitorTest.java deleted file mode 100644 index 199c78b9..00000000 --- a/dom/src/test/java/org/linguafranca/pwdb/kdbx/dom/VisitorTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.junit.Assert; -import org.junit.Test; -import org.linguafranca.pwdb.Group; -import org.linguafranca.pwdb.Entry; -import org.linguafranca.pwdb.Visitor; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; - -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertTrue; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author Jo - */ -public class VisitorTest { - - static PrintStream printStream = getTestPrintStream(); - - private List visitorList = new ArrayList<>(); - private Visitor visitor = new Visitor() { - StringBuffer indentation = new StringBuffer(); - @Override - public void startVisit(Group group) { - printStream.println(indentation.toString() + group.getName()); - indentation.append(" "); - } - - @Override - public void endVisit(Group group) { - indentation.setLength(indentation.length()-3); - } - - @Override - public boolean isEntriesFirst() { - return false; - } - - @Override - public void visit(Entry entry) { - printStream.println(indentation.toString() + "= " + entry.getTitle()); - visitorList.add(entry); - } - }; - - @Test - public void testLoadDB() { - try { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - DomDatabaseWrapper db = new DomDatabaseWrapper(new KdbxCreds("123".getBytes()), inputStream); - db.visit(visitor); - - //noinspection unchecked - List matched = db.findEntries(new Entry.Matcher() { - String matchee = ""; - @Override - public boolean matches(Entry entry) { - return entry.getTitle().toLowerCase().contains(matchee) || - entry.getNotes().toLowerCase().contains(matchee) || - entry.getUsername().toLowerCase().contains(matchee); - } - }); - - Assert.assertTrue(matched.size() == visitorList.size()); - - for (Entry e: matched) { - Assert.assertTrue(visitorList.contains(e)); - } - } catch (Exception e) { - Assert.assertTrue("Couldn\'t open test DB " + e.getMessage(), false); - } - } -} diff --git a/example/pom.xml b/example/pom.xml index 7cfde215..2bbba5a6 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -1,25 +1,26 @@ - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT + KeePassJava2.parent + mh.keepass + 1.0.0.1 ../pom.xml 4.0.0 @@ -34,19 +35,23 @@ - org.linguafranca.pwdb + mh.keepass KeePassJava2 ${project.version} - org.linguafranca.pwdb - test - ${project.version} + org.slf4j + slf4j-api - io.github.novacrypto - SecureString - 2022.01.17 + ch.qos.logback + logback-classic + + + mh.keepass + database + ${project.version} + test-jar diff --git a/example/src/main/java/org/linguafranca/pwdb/example/SecureCharBufferPropertyValue.java b/example/src/main/java/org/linguafranca/pwdb/example/SecureCharBufferPropertyValue.java deleted file mode 100644 index 0b84d4ae..00000000 --- a/example/src/main/java/org/linguafranca/pwdb/example/SecureCharBufferPropertyValue.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.linguafranca.pwdb.example; - -import io.github.novacrypto.SecureCharBuffer; -import org.linguafranca.pwdb.PropertyValue; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; - -/** - * SecureCharBufferPropertyValue uses {@link SecureCharBuffer} - */ -public class SecureCharBufferPropertyValue implements PropertyValue { - private final SecureCharBuffer value; - - /** - * Builder for {@link SecureCharBufferPropertyValue} - */ - static class Factory implements PropertyValue.Factory { - - @Override - public PropertyValue of(CharSequence aString) { - return new SecureCharBufferPropertyValue(aString); - } - - @Override - public PropertyValue of(char[] value) { - return new SecureCharBufferPropertyValue(value); - } - - @Override - public PropertyValue of(byte[] value) { - return new SecureCharBufferPropertyValue(value); - } - } - - public SecureCharBufferPropertyValue(CharSequence aString) { - this.value = SecureCharBuffer.withCapacity(aString.length()); - this.value.append(aString); - } - - public SecureCharBufferPropertyValue(char [] value) { - this.value = SecureCharBuffer.withCapacity(value.length); - this.value.append(CharBuffer.wrap(value)); - } - - public SecureCharBufferPropertyValue(byte [] value) { - this.value = SecureCharBuffer.withCapacity(value.length); - this.value.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(value))); - } - @Override - public String getValueAsString() { - return this.value.toStringAble().toString(); - } - - @Override - public CharSequence getValue() { - return this.value; - } - - @Override - public char [] getValueAsChars() { - return CharBuffer.wrap(value).array(); - } - - @Override - public byte [] getValueAsBytes() { - return StandardCharsets.UTF_8.encode(CharBuffer.wrap(value)).array(); - } - - @Override - public boolean isProtected() { - return true; - } -} - diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/ChooseFile.java b/example/src/main/java/org/linguafranca/pwdb/kdbx/ChooseFile.java index e0f4f266..3eb9a6e4 100644 --- a/example/src/main/java/org/linguafranca/pwdb/kdbx/ChooseFile.java +++ b/example/src/main/java/org/linguafranca/pwdb/kdbx/ChooseFile.java @@ -1,7 +1,25 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; -import com.google.common.base.Strings; -import org.linguafranca.util.HexViewer; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,15 +30,14 @@ import java.nio.file.Files; import java.nio.file.Paths; -import static org.linguafranca.util.TestUtil.getTestPrintStream; +import static org.linguafranca.pwdb.kdbx.jackson.util.Util.streamToString; + /** * Utility to allow browsing of database files and listing content to console */ public class ChooseFile { - OutputStream outputStream = getTestPrintStream(); - public static void main(String[] args) throws IOException { ChooseFile cf = new ChooseFile(); cf.choose(); @@ -63,13 +80,16 @@ public String getDescription() { null, null, "123"); - if (Strings.isNullOrEmpty(s)) { + if (s==null || s.isEmpty()) { return; } logger.info("Opening {}", fc.getSelectedFile().getPath()); - Util.listXml(fc.getSelectedFile().getName(), - new KdbxCreds(s.getBytes()), - new PrintWriter(outputStream)); + try (InputStream is = Files.newInputStream(Paths.get(fc.getSelectedFile().getPath()))) { + InputStream ss = KdbxSerializer.createUnencryptedInputStream(new KdbxCredentials(s.getBytes()), new KdbxHeader(), is); + System.out.println(streamToString(ss)); + System.out.println(); + System.out.flush(); + } /* try (InputStream is = Files.newInputStream(Paths.get(fc.getSelectedFile().getPath()))) { HexViewer.list(is); }*/ diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/Misc.java b/example/src/main/java/org/linguafranca/pwdb/kdbx/Misc.java new file mode 100644 index 00000000..e1058435 --- /dev/null +++ b/example/src/main/java/org/linguafranca/pwdb/kdbx/Misc.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +public class Misc { + + /** + * Useful for e.g. viewing the raw file contents + */ + public static class HexViewer { + + static PrintStream printStream = getTestPrintStream(); + + public static void list(InputStream is) throws IOException { + for (int i = 0; i < 32; i++) { + byte[] buf = new byte [16]; + is.read(buf); + StringBuilder sb = new StringBuilder(); + for (byte b: buf) { + sb.append(String.format("%02X ", b)); + } + sb.append(" "); + for (byte b : buf) { + sb.append(b < 0x20 || b > 0x7e ? (char) 0x00B7 : (char) b); + } + printStream.println(sb); + } + } + } + + + +} diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/OpenDbExample.java b/example/src/main/java/org/linguafranca/pwdb/kdbx/OpenDbExample.java index 8af6c6f3..d268c6bb 100644 --- a/example/src/main/java/org/linguafranca/pwdb/kdbx/OpenDbExample.java +++ b/example/src/main/java/org/linguafranca/pwdb/kdbx/OpenDbExample.java @@ -1,11 +1,27 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.KdbConsts; import org.linguafranca.pwdb.Visitor; -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; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; import java.io.InputStream; import java.io.PrintStream; @@ -14,6 +30,9 @@ /** * Example and naive speed test for various implementations. + *

+ * It was used to compare the various implementations of KDBX in V2. It isn't really very interesting in V3 but + * remains here in case new implementations are added. * * @author jo */ @@ -22,42 +41,21 @@ public class OpenDbExample { static PrintStream printStream = getTestPrintStream(); private interface DbLoader { - Database load(KdbxCreds creds, InputStream inputStream) throws Exception; - } - - private static class SimpleDbLoader implements DbLoader { - @Override - public Database load(KdbxCreds creds, InputStream inputStream) throws Exception { - return SimpleDatabase.load(creds, inputStream); - } - } - - private static class DomDbLoader implements DbLoader { - @Override - public Database load(KdbxCreds creds, InputStream inputStream) throws Exception { - return DomDatabaseWrapper.load(creds, inputStream); - } - } - - private static class JaxbDbLoader implements DbLoader { - @Override - public Database load(KdbxCreds creds, InputStream inputStream) throws Exception { - return JaxbDatabase.load(creds, inputStream); - } + Database load(KdbxCredentials creds, InputStream inputStream) throws Exception; } - private static class JacksonDbLoader implements DbLoader { + private static class KdbxLoader implements DbLoader { @Override - public Database load(KdbxCreds creds, InputStream inputStream) throws Exception { - return JacksonDatabase.load(creds, inputStream); + public Database load(KdbxCredentials creds, InputStream inputStream) throws Exception { + return KdbxDatabase.load(creds, inputStream); } } - public static void testDb (DbLoader loader, String label, int loads, int iterations) throws Exception { - KdbxCreds creds = new KdbxCreds("123".getBytes()); + private static void testDb (DbLoader loader, String label, int loads, int iterations) throws Exception { + KdbxCredentials creds = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); long start = System.currentTimeMillis(); for (int i=0; i < loads; i++) { - try (InputStream inputStream = OpenDbExample.class.getClassLoader().getResourceAsStream("test1.kdbx")) { + try (InputStream inputStream = OpenDbExample.class.getResourceAsStream("/test1.kdbx")) { Database database = loader.load(creds, inputStream); for (int j = 0; j < iterations; j++) { database.visit(new Visitor.Default() { @@ -71,36 +69,24 @@ public static void testDb (DbLoader loader, String label, int loads, int iterati public static void main(String[] args) throws Exception { printStream.println("Warming up JVM"); - testDb(new SimpleDbLoader(), "Simple", 5, 20); - testDb(new JaxbDbLoader(), "Jaxb", 5, 20); - testDb(new DomDbLoader(), "Dom", 5, 20); - testDb(new JacksonDbLoader(), "Jackson", 5, 20); + testDb(new KdbxLoader(), "Jackson", 5, 20); printStream.println("Sleeping"); System.gc(); Thread.sleep(2000); - testDb(new SimpleDbLoader(), "Simple", 5, 20); - testDb(new JaxbDbLoader(), "Jaxb", 5, 20); - testDb(new DomDbLoader(), "Dom", 5, 20); - testDb(new JacksonDbLoader(), "Jackson", 5, 20); + testDb(new KdbxLoader(), "Jackson", 5, 20); printStream.println("Sleeping"); System.gc(); Thread.sleep(2000); - testDb(new SimpleDbLoader(), "Simple", 10, 1); - testDb(new JaxbDbLoader(), "Jaxb", 10, 1); - testDb(new DomDbLoader(), "Dom", 10, 1); - testDb(new JacksonDbLoader(), "Jackson", 10, 1); + testDb(new KdbxLoader(), "Jackson", 10, 1); printStream.println("Sleeping"); System.gc(); Thread.sleep(2000); - testDb(new SimpleDbLoader(), "Simple", 1, 50); - testDb(new JaxbDbLoader(), "Jaxb", 1, 50); - testDb(new DomDbLoader(), "Dom", 1, 50); - testDb(new JacksonDbLoader(), "Jackson", 1, 50); + testDb(new KdbxLoader(), "Jackson", 1, 50); } } diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/QuickStart.java b/example/src/main/java/org/linguafranca/pwdb/kdbx/QuickStart.java index c68a93b3..be04fb46 100644 --- a/example/src/main/java/org/linguafranca/pwdb/kdbx/QuickStart.java +++ b/example/src/main/java/org/linguafranca/pwdb/kdbx/QuickStart.java @@ -1,35 +1,41 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.junit.BeforeClass; import org.linguafranca.pwdb.*; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxStreamFormat; import org.linguafranca.pwdb.kdb.KdbCredentials; import org.linguafranca.pwdb.kdb.KdbDatabase; -import org.linguafranca.pwdb.kdbx.dom.DomDatabaseWrapper; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; import org.linguafranca.pwdb.security.Encryption; +import org.linguafranca.util.TestUtil; import java.io.*; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.List; +import java.util.Objects; -import static org.junit.Assert.assertEquals; -import static org.linguafranca.util.TestUtil.getTestPrintStream; +import static java.nio.file.StandardOpenOption.*; +import static org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME.*; /** * Examples for QuickStart @@ -37,80 +43,128 @@ * @author jo */ @SuppressWarnings("WeakerAccess") -public abstract class QuickStart, G extends Group, E extends Entry, I extends Icon> { +public final class QuickStart { + + public static void canonicalQuickStart(Path tempPath) throws IOException { - static PrintStream printStream = getTestPrintStream(); + // V3 create a database, no need to specify what kind (e.g. JacksonDatabase) + // V3 Database no longer need to be generified + Database database = new KdbxDatabase(); + // V3 the property value strategy is set to PropertyValue.Strategy.Default(), anyway, but the Default property + // value strategy being to protect field "Password" and no other, all of which are stored as byte[] + database.setPropertyValueStrategy(new PropertyValue.Strategy.Default()); + // V3 Group is not generified. create a group + // V3 short-cut add a new group to another group with the name supplied + Group main = database.getRootGroup().addGroup("Main"); + // V3 fluent building of Entry (using PropertyValueStrategy as set in database to determine protection) + main.addEntry() + // V3 addProperty short-cut for setPropertyValue + // V3 abbreviated static import of standard field names + .addProperty(TITLE, "first entry") + .addProperty(USER_NAME, "Tom") + .addProperty(PASSWORD, KdbConsts.PASSWORD_BYTES) + .addProperty(URL, "http://localhost:8080/") + .addEntry("2nd Entry") // v3 addEntry on Entry adds Entry to parent Group if exists + .addProperty(USER_NAME, "Alice") + .addProperty(PASSWORD, KdbConsts.PASSWORD_BYTES); + // find entries just added, in V3 the findEntries return value is just List + List entries = database.findEntries("entry"); + + // V3 KdbxCreds now called KdbxCredentials + KdbxCredentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + try (OutputStream outputStream = Files.newOutputStream(tempPath.resolve("test.kdbx"))) { + database.save(credentials, outputStream); + } + } - public abstract D getDatabase(); - public abstract D loadDatabase(Credentials creds, InputStream inputStream); + /** + * Illustrates the fluent API for creating a database, as well as the various ways of setting property values. + */ + public static void fluentExample() { + Database database = new KdbxDatabase(); + database.getRootGroup().addGroup("Group 1") + .addEntry("as property values determined by the database property value strategy (preferred)") + .addProperty("prop1", "value1".getBytes()) + .addProperty("prop2", "value2") + .addProperty("prop3", "value3") + .addEntry("as non default storage type (according to PropertyValueStrategy) property values") + .setPropertyValue("prop1", database.getPropertyValueStrategy().newProtected().of("value1")) + .setPropertyValue("prop2", database.getPropertyValueStrategy().newUnprotected().of("value2")) + .addEntry("as explicit storage type property values") + .setPropertyValue("prop1", new PropertyValue.BytesStore("value1".getBytes())) + .setPropertyValue("prop2", new PropertyValue.SealedStore("value2")) + .addEntry("as strings") + .setProperty("prop1", "value1") + .setProperty("prop2", "value2") + .setProperty("prop3", "value3"); - @BeforeClass - public static void ensureOutputDir() throws IOException { - Files.createDirectories(Paths.get("testOutput")); + // find entries just added, in V3 the findEntries return value is just List + List entries = database.findEntries("prop"); } /** - * Load KDBX + * Load KDBX from file */ - public void loadKdbx() throws IOException { + public static void loadKdbx() throws IOException { // get an input stream - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx")) { + try (InputStream inputStream = QuickStart.class.getResourceAsStream("/test123.kdbx")) { // password credentials - Credentials credentials = new KdbxCreds("123".getBytes()); - // Jaxb implementation seems a lot faster than the DOM implementation - D database = loadDatabase(credentials, inputStream); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + // load the database from the input stream + Database database = KdbxDatabase.load(credentials, Objects.requireNonNull(inputStream)); // visit all groups and entries and list them to console - database.visit(new Visitor.Print(printStream)); + database.visit(new Visitor.Print(System.out)); } } /** - * Save KDBX + * Save KDBX to file */ - private E entryFactory(D database, String s, int e) { + private static Entry entryFactory(Database database, String s, int e) { return database.newEntry(String.format("Group %s Entry %d", s, e)); } - public void saveKdbx() throws IOException { + public static void saveKdbx(Path tempPath) throws IOException { // create an empty database - D database = getDatabase(); + Database database = new KdbxDatabase(); // add some groups and entries - for (Integer g = 0; g < 5; g++) { - G group = database.getRootGroup().addGroup(database.newGroup(g.toString())); + for (int g = 0; g < 5; g++) { + Group group = database.getRootGroup().addGroup(database.newGroup(Integer.toString(g))); for (int e = 0; e <= g; e++) { // entry factory is a local helper to populate an entry - group.addEntry(entryFactory(database, g.toString(), e)); + group.addEntry(entryFactory(database, Integer.toString(g), e)); } } // save to a file with password "123" - try (FileOutputStream outputStream = new FileOutputStream("testOutput/test.kdbx")) { - database.save(new KdbxCreds("123".getBytes()), outputStream); + try (OutputStream outputStream = Files.newOutputStream(tempPath.resolve( "test.kdbx"))) { + database.save(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), outputStream); } } /** * Splice - add a group from one database to a parent from another (or copy from the same db) */ - public void splice(G newParent, Group groupToSplice) { - G addedGroup = newParent.addGroup(newParent.getDatabase().newGroup(groupToSplice)); + public static void splice(Group newParent, Group groupToSplice) { + Group addedGroup = newParent.addGroup(newParent.getDatabase().newGroup(groupToSplice)); addedGroup.copy(groupToSplice); } /** * Group by title */ - public G groupByTitle(D database) { - List entries = database.findEntries(entry -> entry.getProperty(Entry.STANDARD_PROPERTY_NAME_TITLE).toLowerCase().contains("findme!")); + public static Group groupByTitle(Database database) { + List entries = + database.findEntries(entry -> entry.getProperty(Entry.STANDARD_PROPERTY_NAME_TITLE).toLowerCase().contains("findme!")); // create a new group using DB factory method - G newParent = database.newGroup("Found entries"); + Group newParent = database.newGroup("Found entries"); // iterate over the found entries - for (E entry : entries) { + for (Entry entry : entries) { // copy the entry using DB factory so that it remains where it was found as well as being in new group - E copy = database.newEntry(entry); + Entry copy = database.newEntry(entry); // add new entry to new group newParent.addEntry(copy); } @@ -120,82 +174,102 @@ public G groupByTitle(D database) { /** * Load KDB and save as KDBX */ - public void loadKdb() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdb"); - // password credentials - Credentials credentials = new KdbCredentials.Password("123".getBytes()); - // load KdbDatabase - KdbDatabase database = KdbDatabase.load(credentials, inputStream); - // visit all groups and entries and list them to console + public static void loadKdbSaveKdbx(Path tempPath) throws IOException { + KdbDatabase database; + try (InputStream inputStream = QuickStart.class.getResourceAsStream("/test123.kdb")) { + // password credentials + Credentials credentials = new KdbCredentials.Password(KdbConsts.PASSWORD_BYTES); + // load KdbDatabase + database = KdbDatabase.load(credentials, Objects.requireNonNull(inputStream)); + } // create a KDBX (database - D kdbxDatabase = getDatabase(); + Database kdbxDatabase = new KdbxDatabase(); kdbxDatabase.setName("New Database"); kdbxDatabase.setDescription("Migration of KDB Database to KDBX Database"); // deep copy from group (not including source group, KDB database has simulated root) kdbxDatabase.getRootGroup().copy(database.getRootGroup()); // save it - try (FileOutputStream f = new FileOutputStream("testOutput/migration.kdbx")) { - kdbxDatabase.save(new KdbxCreds("123".getBytes()), f); + try (OutputStream f = Files.newOutputStream(tempPath.resolve( "migration.kdbx"))) { + kdbxDatabase.save(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), f); } } /** * Load KDBX V3 save as KDBX V4 */ - public void loadKdbx3SaveKdbx4(String resourceName, byte[] password, OutputStream v4OutputStream) throws IOException { - DomDatabaseWrapper database; + public static void loadKdbx3SaveKdbx4(String v3ResourceName, byte[] password, Path v4Filename) throws IOException { + KdbxDatabase database; // password credentials - KdbxCreds credentials = new KdbxCreds(password); - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourceName)) { + KdbxCredentials credentials = new KdbxCredentials(password); + try (InputStream inputStream = QuickStart.class.getResourceAsStream(v3ResourceName)) { // load KdbDatabase - database = DomDatabaseWrapper.load(credentials, inputStream); + database = KdbxDatabase.load(credentials, Objects.requireNonNull(inputStream)); } // create a KDBX (database - D kdbxDatabase = getDatabase(); + Database kdbxDatabase = new KdbxDatabase(); kdbxDatabase.setName("New Database"); kdbxDatabase.setDescription("Migration of KDBX 3 Database to KDBX 4 Database"); - // deep copy from group (not including source group, KDB database has simulated root) + // deep copy from group (not including source group) kdbxDatabase.getRootGroup().copy(database.getRootGroup()); // choose a stream format - V4 Kdbx and choose some algorithms KdbxHeader kdbxHeader = new KdbxHeader(KdbxHeader.KdbxHeaderOpts.V4_AES_ARGON_CHA_CHA); - KdbxStreamFormat formatV4 = new KdbxStreamFormat(kdbxHeader); // change algorithm from those originally selected kdbxHeader.setCipherAlgorithm(Encryption.Cipher.CHA_CHA_20); kdbxHeader.setKeyDerivationFunction(Encryption.KeyDerivationFunction.ARGON2); kdbxHeader.setProtectedStreamAlgorithm(Encryption.ProtectedStreamAlgorithm.CHA_CHA_20); + KdbxStreamFormat formatV4 = new KdbxStreamFormat(kdbxHeader); + // save it with format options - kdbxDatabase.save(formatV4, credentials, v4OutputStream); + try (OutputStream v4OutputStream = Files.newOutputStream(v4Filename, CREATE_NEW, SYNC)) { + kdbxDatabase.save(formatV4, credentials, v4OutputStream); + } } - /** * Load KDBX V3 save as KDBX V4 */ - public void loadKdbx4SaveKdbx3(String resourceName, byte[] password, OutputStream v3OutputStream) throws IOException { - DomDatabaseWrapper database; + public static void loadKdbx4SaveKdbx3(String resourceName, byte[] password, Path v3Filename) throws IOException { + KdbxDatabase database; // password credentials - KdbxCreds credentials = new KdbxCreds(password); - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourceName)) { + KdbxCredentials credentials = new KdbxCredentials(password); + try (InputStream inputStream = QuickStart.class.getResourceAsStream(resourceName)) { // load KdbDatabase - database = DomDatabaseWrapper.load(credentials, inputStream); + database = KdbxDatabase.load(credentials, Objects.requireNonNull(inputStream)); } - // create a KDBX (database - D kdbxDatabase = getDatabase(); + // create a KDBX database + Database kdbxDatabase = new KdbxDatabase(); kdbxDatabase.setName("New Database"); kdbxDatabase.setDescription("Migration of KDBX 4 Database to KDBX 3 Database"); - // deep copy from group (not including source group, KDB database has simulated root) + // deep copy from group (not including source group kdbxDatabase.getRootGroup().copy(database.getRootGroup()); - // choose a stream format - V4 Kdbx and choose some algorithms + // choose a stream format - V3 KdbxHeader kdbxHeader = new KdbxHeader(KdbxHeader.KdbxHeaderOpts.V3_AES_SALSA_20); KdbxStreamFormat formatV3 = new KdbxStreamFormat(kdbxHeader); // save it with format options - kdbxDatabase.save(formatV3, credentials, v3OutputStream); + try (OutputStream v3OutputStream = Files.newOutputStream(v3Filename)) { + kdbxDatabase.save(formatV3, credentials, v3OutputStream); + } + } + + /** + * Load KDBX V3 save as KDBX V4 + */ + public static void loadKdbx4SaveToConsole(String resourceName, byte[] password, Path path) throws IOException { + KdbxDatabase database; + // password credentials + KdbxCredentials credentials = new KdbxCredentials(password); + try (InputStream inputStream = QuickStart.class.getResourceAsStream(resourceName)) { + // load KdbDatabase + database = KdbxDatabase.load(credentials, Objects.requireNonNull(inputStream)); + } + + database.save(new StreamFormat.None(), new Credentials.None(), TestUtil.getTestPrintStream()); } } diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/SaxParse.java b/example/src/main/java/org/linguafranca/pwdb/kdbx/SaxParse.java index 2128b9d2..864b0dd0 100644 --- a/example/src/main/java/org/linguafranca/pwdb/kdbx/SaxParse.java +++ b/example/src/main/java/org/linguafranca/pwdb/kdbx/SaxParse.java @@ -1,22 +1,26 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.format.Helpers; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; import org.linguafranca.pwdb.security.StreamEncryptor; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; @@ -29,6 +33,12 @@ import java.io.PrintWriter; /** + * NB if you want to examine the XML of a KeePass file it's easiest to use
+ * + * database.save(new KdbxFormat.None(), new Credentials.None(), new PrintWriter(System.out)); + * + *

+ * See {@link QuickStart#loadKdbx4SaveToConsole} * @author jo */ public class SaxParse { @@ -89,5 +99,6 @@ public void characters(char[] ch, int start, int length) { InputSource xmlInputSource = new InputSource(decryptedInputStream); xmlReader.parse(xmlInputSource); } + writer.flush(); } } diff --git a/example/src/test/java/org/linguafranca/pwdb/kdbx/SaxParseTest.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/SaxParseTest.java index 1d673046..f9c98e19 100644 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/SaxParseTest.java +++ b/example/src/test/java/org/linguafranca/pwdb/kdbx/SaxParseTest.java @@ -1,22 +1,26 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; @@ -24,22 +28,23 @@ import java.io.OutputStream; import java.io.PrintWriter; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** * @author jo */ - +@Execution(CONCURRENT) public class SaxParseTest extends SaxParse { - OutputStream outputStream = getTestPrintStream(); @Test public void exampleSaxParsingV3 () throws IOException, SAXException, ParserConfigurationException { - super.exampleSaxParsing("test123.kdbx", new KdbxCreds("123".getBytes()), new PrintWriter(outputStream)); + super.exampleSaxParsing("test123.kdbx", new KdbxCredentials(KdbConsts.PASSWORD_BYTES), new PrintWriter(System.out)); } + @Test public void exampleSaxParsingV4 () throws IOException, SAXException, ParserConfigurationException { - super.exampleSaxParsing("V4-AES-AES.kdbx", new KdbxCreds("123".getBytes()), new PrintWriter(outputStream)); + super.exampleSaxParsing("V4-AES-AES.kdbx", new KdbxCredentials(KdbConsts.PASSWORD_BYTES), new PrintWriter(System.out)); } } diff --git a/example/src/test/java/org/linguafranca/pwdb/kdbx/SimpleQuickStartTest.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/SimpleQuickStartTest.java index f9c8a71f..3e730e92 100644 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/SimpleQuickStartTest.java +++ b/example/src/test/java/org/linguafranca/pwdb/kdbx/SimpleQuickStartTest.java @@ -1,96 +1,110 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2025. Sergio Lissner * * 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 + * 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; -import org.junit.Test; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.kdbx.dom.DomDatabaseWrapper; -import org.linguafranca.pwdb.kdbx.dom.DomEntryWrapper; -import org.linguafranca.pwdb.kdbx.dom.DomGroupWrapper; -import org.linguafranca.pwdb.kdbx.dom.DomIconWrapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxStreamFormat; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; import org.linguafranca.pwdb.security.Encryption; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.kdbx.QuickStart.*; /** * Simple illustration of hooking a SAX parser up to process a KDBX file * * @author jo */ -public class SimpleQuickStartTest extends QuickStart { - +@Execution(CONCURRENT) +public class SimpleQuickStartTest { - @Override - public DomDatabaseWrapper getDatabase() { - return new DomDatabaseWrapper(); + @Test + public void canonicalQuickstartTest(@TempDir Path tempPath) throws IOException { + canonicalQuickStart(tempPath); } - @Override - public DomDatabaseWrapper loadDatabase(Credentials credentials, InputStream inputStream){ - try { - return DomDatabaseWrapper.load(credentials, inputStream); - } catch (Exception e) { - throw new IllegalStateException(e); - } + @Test + public void fluentExampleTest() { + fluentExample(); } @Test - public void saveTest() throws IOException { - super.saveKdbx(); + public void saveTest(@TempDir Path tempPath) throws IOException { + saveKdbx(tempPath); } @Test public void loadTest() throws IOException { - super.loadKdbx(); + QuickStart.loadKdbx(); } @Test - public void loadKdbSaveVernacular() throws IOException { - super.loadKdb(); + public void loadKdbSaveKdbx(@TempDir Path tempPath) throws IOException { + QuickStart.loadKdbSaveKdbx(tempPath); } @Test - public void loadSave() throws IOException { - Path path = Paths.get("testOutput/CHACHA-AES-CHACHA.kdbx"); + public void loadSave(@TempDir Path tempPath) throws IOException { + Path actualTempPath = Files.createTempDirectory("kbdx-"); + System.out.println("actualTempPath: " + actualTempPath.toAbsolutePath()); + Path path = actualTempPath.resolve("CHACHA-AES-CHACHA.kdbx"); - loadKdbx3SaveKdbx4("test123.kdbx","123".getBytes(), Files.newOutputStream(path)); + QuickStart.loadKdbx3SaveKdbx4("/test123.kdbx", KdbConsts.PASSWORD_BYTES, path); // load newly created V4 database - DomDatabaseWrapper db = DomDatabaseWrapper.load(new KdbxCreds("123".getBytes()), Files.newInputStream(path)); + KdbxDatabase db; + try (InputStream inputStream = Files.newInputStream(path)) { + db = KdbxDatabase.load(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } + catch (Throwable t) { + System.out.println(path.toAbsolutePath()); + int _=0; + throw new RuntimeException(t); + } KdbxStreamFormat streamFormat = (KdbxStreamFormat) db.getStreamFormat(); assertEquals(4, streamFormat.getStreamConfiguration().getVersion()); assertEquals(Encryption.Cipher.CHA_CHA_20, streamFormat.getStreamConfiguration().getCipherAlgorithm()); assertEquals(Encryption.KeyDerivationFunction.ARGON2, streamFormat.getStreamConfiguration().getKeyDerivationFunction()); assertEquals(Encryption.ProtectedStreamAlgorithm.CHA_CHA_20, streamFormat.getStreamConfiguration().getProtectedStreamAlgorithm()); } + @Test - public void loadSave2() throws IOException { - Path path = Paths.get("testOutput/CHACHA-AES-CHACHA.kdbx"); + public void loadSave2(@TempDir Path tempPath) throws IOException { + Path path = tempPath.resolve("CHACHA-AES-CHACHA.kdbx"); - loadKdbx4SaveKdbx3("V4-ChaCha20-Argon2-Attachment.kdbx","123".getBytes(), Files.newOutputStream(path)); + QuickStart.loadKdbx4SaveKdbx3("/V4-ChaCha20-Argon2-Attachment.kdbx", KdbConsts.PASSWORD_BYTES, path); // load newly created V4 database - DomDatabaseWrapper db = DomDatabaseWrapper.load(new KdbxCreds("123".getBytes()), Files.newInputStream(path)); + KdbxDatabase db; + try (InputStream inputStream = Files.newInputStream(path)) { + db = KdbxDatabase.load(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } KdbxStreamFormat streamFormat = (KdbxStreamFormat) db.getStreamFormat(); assertEquals(3, streamFormat.getStreamConfiguration().getVersion()); assertEquals(Encryption.Cipher.AES, streamFormat.getStreamConfiguration().getCipherAlgorithm()); diff --git a/example/src/test/java/org/linguafranca/pwdb/kdbx/UtilTest.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/UtilTest.java deleted file mode 100644 index bfda2c5b..00000000 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/UtilTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.linguafranca.pwdb.kdbx; - -import org.junit.Test; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; - -import static org.linguafranca.pwdb.kdbx.Util.*; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -public class UtilTest { - - OutputStream outputStream = getTestPrintStream(); - @Test public void listDatabaseTest() throws IOException { - listDatabase("V3-CustomIcon.kdbx", new KdbxCreds("123".getBytes()), outputStream); - } - - @Test - public void listXmlTest() throws IOException { - PrintWriter writer = new PrintWriter(outputStream); - listXml("V3-CustomIcon.kdbx", new KdbxCreds("123".getBytes()), writer); - writer.flush(); - } - - /** - * List Database Encryption Characteristics - */ - @Test - public void listKdbxHeaderParams () throws IOException { - PrintWriter writer = new PrintWriter(outputStream); - listKdbxHeaderProperties("test123.kdbx", writer); - listKdbxHeaderProperties("V4-AES-AES.kdbx", writer); - listKdbxHeaderProperties("V4-AES-Argon2.kdbx", writer); - listKdbxHeaderProperties("V4-ChaCha20-AES.kdbx", writer); - listKdbxHeaderProperties("V4-ChaCha20-Argon2-Attachment.kdbx", writer); - writer.flush(); - } - - @Test - public void listHeaderPropertiesAndXml() throws IOException { - PrintWriter writer = new PrintWriter(outputStream); - listKdbxHeaderProperties("V4-AES-Argon2-CustomIcon.kdbx", writer); - listXml("V4-AES-Argon2-CustomIcon.kdbx", new KdbxCreds("123".getBytes()), writer); - writer.flush(); - } -} diff --git a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue27Test.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue27Test.java deleted file mode 100644 index be04ad11..00000000 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue27Test.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.linguafranca.pwdb.kdbx.validation; - -import com.google.common.io.CharStreams; -import org.junit.Ignore; -import org.junit.Test; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxSerializer; -import org.linguafranca.pwdb.kdbx.jaxb.JaxbDatabase; -import org.linguafranca.pwdb.kdbx.jaxb.JaxbEntry; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -public class Issue27Test { - static PrintStream printStream = getTestPrintStream(); - /** - * Check load of problem file - */ - @Test - public void testIssue27() throws IOException { - InputStream is = this.getClass().getClassLoader().getResourceAsStream("issue-27/bogus-timestamp2.kdbx"); - KdbxCreds creds = new KdbxCreds("passwordless".getBytes()); - JaxbDatabase db = JaxbDatabase.load(creds, is); - List entries = db.findEntries("testtitle"); - - for (JaxbEntry entry: entries) { - printStream.println(Helpers.fromDateV3(entry.getCreationTime())); - assertEquals("2021-01-11T09:18:56Z", Helpers.fromDateV3(entry.getCreationTime())); - } - } - - /** - * Verify that V4 dates are still processed correctly - */ - @Test - public void testV4Date() throws IOException { - InputStream is = this.getClass().getClassLoader().getResourceAsStream("V4-AES-AES.kdbx"); - KdbxCreds creds = new KdbxCreds("123".getBytes()); - JaxbDatabase db = JaxbDatabase.load(creds, is); - List entries = db.findEntries("Sample Entry #2 - Copy"); - - for (JaxbEntry entry: entries) { - printStream.println(Helpers.fromDate(entry.getCreationTime())); - assertEquals("2018-01-26T13:20:58Z", Helpers.fromDateV3(entry.getCreationTime())); - } - } - - @Test @Ignore - public void testIssue27XML() throws IOException { - - InputStream is = this.getClass().getClassLoader().getResourceAsStream("issue-27/bogus-timestamp2.kdbx"); - KdbxCreds creds = new KdbxCreds("passwordless".getBytes()); - InputStream plainText = KdbxSerializer.createUnencryptedInputStream(creds,new KdbxHeader(), is); - printStream.println(CharStreams.toString(new InputStreamReader(plainText, StandardCharsets.UTF_8))); - } -} 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 deleted file mode 100644 index 322ee10a..00000000 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue33Test.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.linguafranca.pwdb.kdbx.validation; - - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.kdbx.Helpers; -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; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * Review Issue-33 https://github.com/jorabin/KeePassJava2/issues/33 - */ -public class Issue33Test { - - public static final String TEST_RESOURCE = "V4-AES-Argon2-CustomIcon.kdbx"; - public static final String TEST_OUTPUT_DIR = "testOutput"; - public static final File TEST_XML_FILE = Paths.get(TEST_OUTPUT_DIR,"Issue33Source.xml").toFile(); - public static final KdbxCreds CREDENTIALS = new KdbxCreds("123".getBytes()); - - - InputStream inputStream; - - @BeforeClass - public static void listXml() throws IOException { - Files.createDirectories(Paths.get(TEST_OUTPUT_DIR)); - Util.listXml(TEST_RESOURCE, CREDENTIALS, new PrintWriter(TEST_XML_FILE)); - Helpers.isV4.set(true); - } - - @Before - public void refreshInputStream() { - inputStream = this.getClass().getClassLoader().getResourceAsStream(TEST_RESOURCE); - } - - @Test - public void testDomDatabaseWrapper() throws IOException { - DomDatabaseWrapper database = DomDatabaseWrapper.load(CREDENTIALS, inputStream); - database.save(new StreamFormat.None(), new Credentials.None(), Files.newOutputStream(Paths.get(TEST_OUTPUT_DIR, "Issue33Dom.xml"))); - } - - @Test - public void testJaxbDatabase() throws IOException { - JaxbDatabase database = JaxbDatabase.load(CREDENTIALS, inputStream); - database.save(new StreamFormat.None(), new Credentials.None(), Files.newOutputStream(Paths.get(TEST_OUTPUT_DIR, "Issue33Jaxb.xml"))); - } - - @Test - 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/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue38Test.java b/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue38Test.java deleted file mode 100644 index 4684a712..00000000 --- a/example/src/test/java/org/linguafranca/pwdb/kdbx/validation/Issue38Test.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.linguafranca.pwdb.kdbx.validation; - -import org.junit.Test; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.dom.DomDatabaseWrapper; -import org.linguafranca.pwdb.kdbx.dom.DomEntryWrapper; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.List; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * Bug report on GitHub, the Keyfile is Version 2 (Hex) - */ -public class Issue38Test { - - static PrintStream printStream = getTestPrintStream(); - @Test - public void testV2Keyfile() throws IOException { - InputStream databaseStream = Issue38Test.class.getClassLoader().getResourceAsStream("issue-38/Database/Database.kdbx"); - InputStream keyStream = Issue38Test.class.getClassLoader().getResourceAsStream("issue-38/Database/Database.keyx"); - assert keyStream != null; - KdbxCreds creds = new KdbxCreds("MyPassword".getBytes(), keyStream); - assert databaseStream != null; - DomDatabaseWrapper database = DomDatabaseWrapper.load(creds, databaseStream); - List entries = database.findEntries("Sample Entry"); - DomEntryWrapper entry = entries.get(0); - printStream.println(entry.getTitle()); - } -} diff --git a/test/src/main/resources/V4-AES-AES.kdbx b/example/src/test/resources/V4-AES-AES.kdbx similarity index 100% rename from test/src/main/resources/V4-AES-AES.kdbx rename to example/src/test/resources/V4-AES-AES.kdbx diff --git a/test/src/main/resources/V4-ChaCha20-Argon2-Attachment.kdbx b/example/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx similarity index 100% rename from test/src/main/resources/V4-ChaCha20-Argon2-Attachment.kdbx rename to example/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx diff --git a/test/src/main/resources/test1.kdbx b/example/src/test/resources/test1.kdbx similarity index 100% rename from test/src/main/resources/test1.kdbx rename to example/src/test/resources/test1.kdbx diff --git a/test/src/main/resources/test123.kdb b/example/src/test/resources/test123.kdb similarity index 100% rename from test/src/main/resources/test123.kdb rename to example/src/test/resources/test123.kdb diff --git a/test/src/main/resources/test123.kdbx b/example/src/test/resources/test123.kdbx similarity index 100% rename from test/src/main/resources/test123.kdbx rename to example/src/test/resources/test123.kdbx diff --git a/jackson/pom.xml b/jackson/pom.xml deleted file mode 100644 index 9726b910..00000000 --- a/jackson/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - ../pom.xml - - 4.0.0 - - KeePassJava2-jackson - KeePassJava2 :: Jackson - Contains a Jackson XML implementation for KDBX. - - - - org.linguafranca.pwdb - KeePassJava2-kdbx - ${project.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.17.1 - - - com.fasterxml.woodstox - woodstox-core - 7.0.0 - - - org.linguafranca.pwdb - test - ${project.version} - test - - - diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV3Test.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV3Test.java deleted file mode 100644 index 41c14567..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV3Test.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; - -public class JacksonBinaryPropertyV3Test extends BinaryPropertyChecks { - - - public JacksonBinaryPropertyV3Test() { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx"); - try { - database = JacksonDatabase.load(new KdbxCreds("123".getBytes()),inputStream); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JacksonDatabase.load(credentials, inputStream); - } - - @Override - public Database newDatabase() { - Database result = null; - try { - result = new JacksonDatabase(); - } catch(Exception e) { - throw new RuntimeException(e); - } - return result; - - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV4Test.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV4Test.java deleted file mode 100644 index ec65bdc4..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonBinaryPropertyV4Test.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; - -public class JacksonBinaryPropertyV4Test extends BinaryPropertyChecks{ - - - public JacksonBinaryPropertyV4Test() throws Exception{ - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-Argon2-Attachment.kdbx"); - database = JacksonDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - } - - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return JacksonDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public Database newDatabase() { - Database result = null; - try { - result = new JacksonDatabase(); - } catch (Exception e) { - throw new RuntimeException(e); - } - return result; - - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoadTest.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoadTest.java deleted file mode 100644 index 2f0e4d2e..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoadTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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; - -import java.io.InputStream; -import java.io.PrintStream; - -import org.junit.Test; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -public class JacksonDatabaseLoadTest { - - static PrintStream printStream = getTestPrintStream(); - - - @Test - public void loadXml() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("ExampleDatabase.xml"); - JacksonDatabase database = JacksonDatabase.loadXml(inputStream); - database.visit(new Visitor.Print(printStream)); - } - @Test - public void loadKdbx() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - JacksonDatabase database = JacksonDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - } - - @Test - public void loadKdbxV4() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); - JacksonDatabase database = JacksonDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - // test what happens to dates in V4 - database.visit(new Visitor.Default(){ - @Override - public void visit(Entry entry) { - printStream.println(entry.getCreationTime()); - } - }); - } - - @Test - public void emptyDb() throws Exception { - JacksonDatabase database = new JacksonDatabase(); - printStream.println(database.getDescription()); - } - - @Test - public void dbWithDeleted() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("testDeleted.kdbx"); - JacksonDatabase database = JacksonDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - } - -} diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoaderTest.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoaderTest.java deleted file mode 100644 index 7c218fc3..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseLoaderTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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; - -import java.io.InputStream; - -import org.linguafranca.pwdb.checks.DatabaseLoaderChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; - -public class JacksonDatabaseLoaderTest extends DatabaseLoaderChecks{ - - - public JacksonDatabaseLoaderTest() throws Exception{ - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - // file has password credentials - Credentials credentials = new KdbxCreds("123".getBytes()); - super.database = JacksonDatabase.load(credentials, inputStream); - } - -} diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonPropertyValueTest.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonPropertyValueTest.java deleted file mode 100644 index 40571e53..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonPropertyValueTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 Jo Rabin - * - * 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; - -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; -import org.linguafranca.pwdb.checks.PropertyValueChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; -import org.linguafranca.pwdb.kdbx.jackson.JacksonEntry; -import org.linguafranca.pwdb.kdbx.jackson.JacksonGroup; -import org.linguafranca.pwdb.kdbx.jackson.JacksonIcon; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class JacksonPropertyValueTest extends PropertyValueChecks { - - public JacksonPropertyValueTest() throws IOException { - super(true); - } - - @Override - public void saveDatabase(JacksonDatabase database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public JacksonDatabase loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JacksonDatabase.load(credentials, inputStream); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - @Override - public JacksonDatabase createDatabase() throws IOException { - return new JacksonDatabase(); - } - -} diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonSaveAndReloadTest.java b/jackson/src/test/java/org/linguafranca/pwdb/JacksonSaveAndReloadTest.java deleted file mode 100644 index fcb2db76..00000000 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonSaveAndReloadTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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; - -import static org.junit.Assert.assertTrue; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintStream; - -import org.junit.Test; -import org.linguafranca.pwdb.checks.SaveAndReloadChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; - -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; -import org.linguafranca.pwdb.kdbx.jackson.JacksonEntry; -import org.linguafranca.pwdb.kdbx.jackson.JacksonGroup; -import org.linguafranca.pwdb.kdbx.jackson.JacksonIcon; - - -public class JacksonSaveAndReloadTest extends SaveAndReloadChecks{ - - @Override - public JacksonDatabase getDatabase() { - try { - return new JacksonDatabase(); - } catch(Exception e) { - return null; - } - - } - - @Override - public JacksonDatabase getDatabase(String name, Credentials credentials) throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name); - return JacksonDatabase.load(credentials, inputStream); - } - - @Override - public void saveDatabase(JacksonDatabase database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public JacksonDatabase loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JacksonDatabase.load(credentials, inputStream); - } - - - @Override - public boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2) { - KdbxHeader h1 = (KdbxHeader) s1.getStreamConfiguration(); - KdbxHeader h2 = (KdbxHeader) s1.getStreamConfiguration(); - return (h1.getVersion() == h2.getVersion() && - h1.getProtectedStreamAlgorithm().equals(h2.getProtectedStreamAlgorithm()) && - h1.getKeyDerivationFunction().equals(h2.getKeyDerivationFunction()) && - h1.getCipherAlgorithm().equals(h2.getCipherAlgorithm())); - } - - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} diff --git a/jaxb/bindings.xml b/jaxb/bindings.xml deleted file mode 100644 index c6907af8..00000000 --- a/jaxb/bindings.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - This class, while generated, is not used see {@link - org.linguafranca.pwdb.kdbx.jaxb.org.linguafranca.pwdb.base.AbstractJaxbParentedBinding} which is the - actual implementation class. - - - - - - - - - - - - \ No newline at end of file diff --git a/jaxb/gradle-source-sets.txt b/jaxb/gradle-source-sets.txt deleted file mode 100644 index 195f0d4f..00000000 --- a/jaxb/gradle-source-sets.txt +++ /dev/null @@ -1,18 +0,0 @@ -sourceSets { - main { - java { - srcDirs = ['src/main/java', 'src/generated/java'] - } - resources { - srcDirs = ['src/main/resources'] - } - } - test{ - java { - srcDirs = ['src/test/java'] - } - resources { - srcDirs = ['src/test/resources'] - } - } -} \ No newline at end of file diff --git a/jaxb/pom.xml b/jaxb/pom.xml deleted file mode 100644 index 535b53de..00000000 --- a/jaxb/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - ../pom.xml - - 4.0.0 - - KeePassJava2-jaxb - KeePassJava2 :: JAXB - Contains a JAXB implementation for KDBX. - - - - org.linguafranca.pwdb - KeePassJava2-kdbx - ${project.version} - - - - javax.xml.bind - jaxb-api - 2.3.1 - - - com.sun.xml.bind - jaxb-impl - 2.3.9 - - - org.linguafranca.pwdb - test - ${project.version} - test - - - junit - junit - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${java.version} - ${java.version} - src/generated/java - - - - - - - - generateJaxbSources - - - - - - org.codehaus.mojo - jaxb2-maven-plugin - 2.2 - - - xjc - - xjc - - - - - - ../KDBX.4.xsd - - - bindings.xml - - org.linguafranca.pwdb.kdbx.jaxb.binding - src/generated/java - - - - - - - - \ No newline at end of file diff --git a/jaxb/src/generated/java/META-INF/sun-jaxb.episode b/jaxb/src/generated/java/META-INF/sun-jaxb.episode deleted file mode 100644 index c585a686..00000000 --- a/jaxb/src/generated/java/META-INF/sun-jaxb.episode +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter1.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter1.java deleted file mode 100644 index 16005b3b..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter1.java +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.Date; -import javax.xml.bind.annotation.adapters.XmlAdapter; - -public class Adapter1 - extends XmlAdapter -{ - - - public Date unmarshal(String value) { - return (org.linguafranca.pwdb.kdbx.jaxb.util.DateAdapter.fromString(value)); - } - - public String marshal(Date value) { - return (org.linguafranca.pwdb.kdbx.jaxb.util.DateAdapter.toString(value)); - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter2.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter2.java deleted file mode 100644 index 530b9b09..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter2.java +++ /dev/null @@ -1,26 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.adapters.XmlAdapter; - -public class Adapter2 - extends XmlAdapter -{ - - - public Boolean unmarshal(String value) { - return (org.linguafranca.pwdb.kdbx.Helpers.toBoolean(value)); - } - - public String marshal(Boolean value) { - return (org.linguafranca.pwdb.kdbx.Helpers.fromBoolean(value)); - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter3.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter3.java deleted file mode 100644 index aa3295be..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Adapter3.java +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.UUID; -import javax.xml.bind.annotation.adapters.XmlAdapter; - -public class Adapter3 - extends XmlAdapter -{ - - - public UUID unmarshal(String value) { - return (org.linguafranca.pwdb.kdbx.Helpers.uuidFromBase64(value)); - } - - public String marshal(UUID value) { - return (org.linguafranca.pwdb.kdbx.Helpers.base64FromUuid(value)); - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/AutoType.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/AutoType.java deleted file mode 100644 index fe87bdea..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/AutoType.java +++ /dev/null @@ -1,239 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementRef; -import javax.xml.bind.annotation.XmlElementRefs; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - - -/** - * Stores everything related to auto-type for this wrappedEntry. - * http://keepass.info/help/base/autotype.html - * - * - *

Java class for autoType complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="autoType">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="Enabled" type="{}keepassBoolean"/>
- *         <element name="DataTransferObfuscation">
- *           <simpleType>
- *             <restriction base="{http://www.w3.org/2001/XMLSchema}int">
- *               <enumeration value="0"/>
- *               <enumeration value="1"/>
- *             </restriction>
- *           </simpleType>
- *         </element>
- *         <element name="DefaultSequence" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *         <element name="Association">
- *           <complexType>
- *             <complexContent>
- *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                 <sequence maxOccurs="unbounded" minOccurs="0">
- *                   <element name="Window" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="KeystrokeSequence" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                 </sequence>
- *               </restriction>
- *             </complexContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "autoType", propOrder = { - "enabled", - "dataTransferObfuscation", - "defaultSequence", - "association" -}) -public class AutoType { - - @XmlElement(name = "Enabled", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean enabled; - @XmlElement(name = "DataTransferObfuscation") - protected int dataTransferObfuscation; - @XmlElement(name = "DefaultSequence", required = true) - protected String defaultSequence; - @XmlElement(name = "Association", required = true) - protected AutoType.Association association; - - /** - * Gets the value of the enabled property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getEnabled() { - return enabled; - } - - /** - * Sets the value of the enabled property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setEnabled(Boolean value) { - this.enabled = value; - } - - /** - * Gets the value of the dataTransferObfuscation property. - * - */ - public int getDataTransferObfuscation() { - return dataTransferObfuscation; - } - - /** - * Sets the value of the dataTransferObfuscation property. - * - */ - public void setDataTransferObfuscation(int value) { - this.dataTransferObfuscation = value; - } - - /** - * Gets the value of the defaultSequence property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getDefaultSequence() { - return defaultSequence; - } - - /** - * Sets the value of the defaultSequence property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDefaultSequence(String value) { - this.defaultSequence = value; - } - - /** - * Gets the value of the association property. - * - * @return - * possible object is - * {@link AutoType.Association } - * - */ - public AutoType.Association getAssociation() { - return association; - } - - /** - * Sets the value of the association property. - * - * @param value - * allowed object is - * {@link AutoType.Association } - * - */ - public void setAssociation(AutoType.Association value) { - this.association = value; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <complexContent>
-     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *       <sequence maxOccurs="unbounded" minOccurs="0">
-     *         <element name="Window" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="KeystrokeSequence" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *       </sequence>
-     *     </restriction>
-     *   </complexContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "windowAndKeystrokeSequence" - }) - public static class Association { - - @XmlElementRefs({ - @XmlElementRef(name = "KeystrokeSequence", type = JAXBElement.class, required = false), - @XmlElementRef(name = "Window", type = JAXBElement.class, required = false) - }) - protected List> windowAndKeystrokeSequence; - - /** - * Gets the value of the windowAndKeystrokeSequence property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the windowAndKeystrokeSequence property. - * - *

- * For example, to add a new item, do as follows: - *

-         *    getWindowAndKeystrokeSequence().add(newItem);
-         * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link JAXBElement }{@code <}{@link String }{@code >} - * {@link JAXBElement }{@code <}{@link String }{@code >} - * - * - */ - public List> getWindowAndKeystrokeSequence() { - if (windowAndKeystrokeSequence == null) { - windowAndKeystrokeSequence = new ArrayList>(); - } - return this.windowAndKeystrokeSequence; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Binaries.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Binaries.java deleted file mode 100644 index 4090b908..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Binaries.java +++ /dev/null @@ -1,201 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlValue; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - - -/** - * Binary field for elements are centrally stored in this element. - * The same Binary element may be referenced from many Entries. - * - * - *

Java class for binaries complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="binaries">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence maxOccurs="unbounded" minOccurs="0">
- *         <element name="Binary">
- *           <complexType>
- *             <simpleContent>
- *               <extension base="<http://www.w3.org/2001/XMLSchema>base64Binary">
- *                 <attribute name="ID" type="{http://www.w3.org/2001/XMLSchema}int" />
- *                 <attribute name="Compressed" type="{}keepassBoolean" default="False" />
- *               </extension>
- *             </simpleContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "binaries", propOrder = { - "binary" -}) -public class Binaries { - - @XmlElement(name = "Binary") - protected List binary; - - /** - * Gets the value of the binary property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the binary property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getBinary().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link Binaries.Binary } - * - * - */ - public List getBinary() { - if (binary == null) { - binary = new ArrayList(); - } - return this.binary; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <simpleContent>
-     *     <extension base="<http://www.w3.org/2001/XMLSchema>base64Binary">
-     *       <attribute name="ID" type="{http://www.w3.org/2001/XMLSchema}int" />
-     *       <attribute name="Compressed" type="{}keepassBoolean" default="False" />
-     *     </extension>
-     *   </simpleContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "value" - }) - public static class Binary { - - @XmlValue - protected byte[] value; - @XmlAttribute(name = "ID") - protected Integer id; - @XmlAttribute(name = "Compressed") - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean compressed; - - /** - * Gets the value of the value property. - * - * @return - * possible object is - * byte[] - */ - public byte[] getValue() { - return value; - } - - /** - * Sets the value of the value property. - * - * @param value - * allowed object is - * byte[] - */ - public void setValue(byte[] value) { - this.value = value; - } - - /** - * Gets the value of the id property. - * - * @return - * possible object is - * {@link Integer } - * - */ - public Integer getID() { - return id; - } - - /** - * Sets the value of the id property. - * - * @param value - * allowed object is - * {@link Integer } - * - */ - public void setID(Integer value) { - this.id = value; - } - - /** - * Gets the value of the compressed property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getCompressed() { - if (compressed == null) { - return new Adapter2().unmarshal("False"); - } else { - return compressed; - } - } - - /** - * Sets the value of the compressed property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setCompressed(Boolean value) { - this.compressed = value; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/BinaryField.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/BinaryField.java deleted file mode 100644 index 50581d10..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/BinaryField.java +++ /dev/null @@ -1,164 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlType; - - -/** - * If derived from a file then the Key element conventionally contains the file name. - * I think it would be useful to add a mime type on this ... - * - * The Value element is the value of /./meta/binaries/binary/@ID that is referenced from this - * binary. - * - * - *

Java class for binaryField complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="binaryField">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="Key" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *         <element name="Value">
- *           <complexType>
- *             <complexContent>
- *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                 <attribute name="Ref" type="{http://www.w3.org/2001/XMLSchema}int" />
- *               </restriction>
- *             </complexContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "binaryField", propOrder = { - "key", - "value" -}) -public class BinaryField { - - @XmlElement(name = "Key", required = true) - protected String key; - @XmlElement(name = "Value", required = true) - protected BinaryField.Value value; - - /** - * Gets the value of the key property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getKey() { - return key; - } - - /** - * Sets the value of the key property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setKey(String value) { - this.key = value; - } - - /** - * Gets the value of the value property. - * - * @return - * possible object is - * {@link BinaryField.Value } - * - */ - public BinaryField.Value getValue() { - return value; - } - - /** - * Sets the value of the value property. - * - * @param value - * allowed object is - * {@link BinaryField.Value } - * - */ - public void setValue(BinaryField.Value value) { - this.value = value; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <complexContent>
-     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *       <attribute name="Ref" type="{http://www.w3.org/2001/XMLSchema}int" />
-     *     </restriction>
-     *   </complexContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "") - public static class Value { - - @XmlAttribute(name = "Ref") - protected Integer ref; - - /** - * Gets the value of the ref property. - * - * @return - * possible object is - * {@link Integer } - * - */ - public Integer getRef() { - return ref; - } - - /** - * Sets the value of the ref property. - * - * @param value - * allowed object is - * {@link Integer } - * - */ - public void setRef(Integer value) { - this.ref = value; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomData.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomData.java deleted file mode 100644 index cdd94c65..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomData.java +++ /dev/null @@ -1,80 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAnyElement; -import javax.xml.bind.annotation.XmlType; - - -/** - * Third party programs and plugins can put custom data here. - * Unique element names should be used, e.g. "PluginName_ItemName". - * - * - *

Java class for customData complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="customData">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <any maxOccurs="unbounded" minOccurs="0"/>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "customData", propOrder = { - "any" -}) -public class CustomData { - - @XmlAnyElement(lax = true) - protected List any; - - /** - * Gets the value of the any property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the any property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getAny().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link Object } - * - * - */ - public List getAny() { - if (any == null) { - any = new ArrayList(); - } - return this.any; - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomIcons.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomIcons.java deleted file mode 100644 index aa4b00d8..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/CustomIcons.java +++ /dev/null @@ -1,234 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - - -/** - *

Java class for customIcons complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="customIcons">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence maxOccurs="unbounded">
- *         <element name="Icon">
- *           <complexType>
- *             <complexContent>
- *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                 <sequence>
- *                   <element name="UUID" type="{}uuid"/>
- *                   <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="LastModificationTime" type="{}keepassDateTime"/>
- *                   <element name="Data" type="{http://www.w3.org/2001/XMLSchema}base64Binary"/>
- *                 </sequence>
- *               </restriction>
- *             </complexContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "customIcons", propOrder = { - "icon" -}) -public class CustomIcons { - - @XmlElement(name = "Icon", required = true) - protected List icon; - - /** - * Gets the value of the icon property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the icon property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getIcon().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link CustomIcons.Icon } - * - * - */ - public List getIcon() { - if (icon == null) { - icon = new ArrayList(); - } - return this.icon; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <complexContent>
-     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *       <sequence>
-     *         <element name="UUID" type="{}uuid"/>
-     *         <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="LastModificationTime" type="{}keepassDateTime"/>
-     *         <element name="Data" type="{http://www.w3.org/2001/XMLSchema}base64Binary"/>
-     *       </sequence>
-     *     </restriction>
-     *   </complexContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "uuid", - "name", - "lastModificationTime", - "data" - }) - public static class Icon { - - @XmlElement(name = "UUID", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID uuid; - @XmlElement(name = "Name", required = true) - protected String name; - @XmlElement(name = "LastModificationTime", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date lastModificationTime; - @XmlElement(name = "Data", required = true) - protected byte[] data; - - /** - * Gets the value of the uuid property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getUUID() { - return uuid; - } - - /** - * Sets the value of the uuid property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setUUID(UUID value) { - this.uuid = value; - } - - /** - * Gets the value of the name property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getName() { - return name; - } - - /** - * Sets the value of the name property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setName(String value) { - this.name = value; - } - - /** - * Gets the value of the lastModificationTime property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getLastModificationTime() { - return lastModificationTime; - } - - /** - * Sets the value of the lastModificationTime property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastModificationTime(Date value) { - this.lastModificationTime = value; - } - - /** - * Gets the value of the data property. - * - * @return - * possible object is - * byte[] - */ - public byte[] getData() { - return data; - } - - /** - * Sets the value of the data property. - * - * @param value - * allowed object is - * byte[] - */ - public void setData(byte[] value) { - this.data = value; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/History.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/History.java deleted file mode 100644 index d8110ad0..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/History.java +++ /dev/null @@ -1,71 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; - - -/** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType>
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element ref="{}Entry"/>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "entry" -}) -@XmlRootElement(name = "History") -public class History { - - @XmlElement(name = "Entry", required = true) - protected JaxbEntryBinding entry; - - /** - * Gets the value of the entry property. - * - * @return - * possible object is - * {@link JaxbEntryBinding } - * - */ - public JaxbEntryBinding getEntry() { - return entry; - } - - /** - * Sets the value of the entry property. - * - * @param value - * allowed object is - * {@link JaxbEntryBinding } - * - */ - public void setEntry(JaxbEntryBinding value) { - this.entry = value; - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbEntryBinding.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbEntryBinding.java deleted file mode 100644 index 0f6d2c88..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbEntryBinding.java +++ /dev/null @@ -1,423 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.linguafranca.pwdb.kdbx.jaxb.base.AbstractJaxbParentedBinding; - - -/** - *

Java class for Entry element declaration. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <element name="Entry">
- *   <complexType>
- *     <complexContent>
- *       <extension base="{}Parented">
- *         <sequence>
- *           <element name="UUID" type="{}uuid"/>
- *           <element name="IconID" type="{}iconId"/>
- *           <element name="CustomIconUUID" type="{}customIconUuidRef" minOccurs="0"/>
- *           <element name="ForegroundColor" type="{}color"/>
- *           <element name="BackgroundColor" type="{}color"/>
- *           <element name="OverrideURL" type="{}overrideUrl"/>
- *           <element name="Tags" type="{}commaSeparatedList"/>
- *           <element ref="{}Times"/>
- *           <element name="String" type="{}stringField" maxOccurs="unbounded" minOccurs="0"/>
- *           <element name="Binary" type="{}binaryField" maxOccurs="unbounded" minOccurs="0"/>
- *           <element name="AutoType" type="{}autoType"/>
- *           <element ref="{}History" minOccurs="0"/>
- *           <element name="CustomData" type="{}customData" minOccurs="0"/>
- *         </sequence>
- *       </extension>
- *     </complexContent>
- *   </complexType>
- * </element>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "uuid", - "iconID", - "customIconUUID", - "foregroundColor", - "backgroundColor", - "overrideURL", - "tags", - "times", - "string", - "binary", - "autoType", - "history", - "customData" -}) -@XmlRootElement(name = "Entry") -public class JaxbEntryBinding - extends AbstractJaxbParentedBinding -{ - - @XmlElement(name = "UUID", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID uuid; - @XmlElement(name = "IconID") - protected int iconID; - @XmlElement(name = "CustomIconUUID", type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID customIconUUID; - @XmlElement(name = "ForegroundColor", required = true) - protected String foregroundColor; - @XmlElement(name = "BackgroundColor", required = true) - protected String backgroundColor; - @XmlElement(name = "OverrideURL", required = true) - protected String overrideURL; - @XmlElement(name = "Tags", required = true) - protected String tags; - @XmlElement(name = "Times", required = true) - protected Times times; - @XmlElement(name = "String") - protected List string; - @XmlElement(name = "Binary") - protected List binary; - @XmlElement(name = "AutoType", required = true) - protected AutoType autoType; - @XmlElement(name = "History") - protected History history; - @XmlElement(name = "CustomData") - protected CustomData customData; - - /** - * Gets the value of the uuid property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getUUID() { - return uuid; - } - - /** - * Sets the value of the uuid property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setUUID(UUID value) { - this.uuid = value; - } - - /** - * Gets the value of the iconID property. - * - */ - public int getIconID() { - return iconID; - } - - /** - * Sets the value of the iconID property. - * - */ - public void setIconID(int value) { - this.iconID = value; - } - - /** - * Gets the value of the customIconUUID property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getCustomIconUUID() { - return customIconUUID; - } - - /** - * Sets the value of the customIconUUID property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setCustomIconUUID(UUID value) { - this.customIconUUID = value; - } - - /** - * Gets the value of the foregroundColor property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getForegroundColor() { - return foregroundColor; - } - - /** - * Sets the value of the foregroundColor property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setForegroundColor(String value) { - this.foregroundColor = value; - } - - /** - * Gets the value of the backgroundColor property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getBackgroundColor() { - return backgroundColor; - } - - /** - * Sets the value of the backgroundColor property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setBackgroundColor(String value) { - this.backgroundColor = value; - } - - /** - * Gets the value of the overrideURL property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getOverrideURL() { - return overrideURL; - } - - /** - * Sets the value of the overrideURL property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setOverrideURL(String value) { - this.overrideURL = value; - } - - /** - * Gets the value of the tags property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getTags() { - return tags; - } - - /** - * Sets the value of the tags property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setTags(String value) { - this.tags = value; - } - - /** - * Gets the value of the times property. - * - * @return - * possible object is - * {@link Times } - * - */ - public Times getTimes() { - return times; - } - - /** - * Sets the value of the times property. - * - * @param value - * allowed object is - * {@link Times } - * - */ - public void setTimes(Times value) { - this.times = value; - } - - /** - * Gets the value of the string property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the string property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getString().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link StringField } - * - * - */ - public List getString() { - if (string == null) { - string = new ArrayList(); - } - return this.string; - } - - /** - * Gets the value of the binary property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the binary property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getBinary().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link BinaryField } - * - * - */ - public List getBinary() { - if (binary == null) { - binary = new ArrayList(); - } - return this.binary; - } - - /** - * Gets the value of the autoType property. - * - * @return - * possible object is - * {@link AutoType } - * - */ - public AutoType getAutoType() { - return autoType; - } - - /** - * Sets the value of the autoType property. - * - * @param value - * allowed object is - * {@link AutoType } - * - */ - public void setAutoType(AutoType value) { - this.autoType = value; - } - - /** - * Gets the value of the history property. - * - * @return - * possible object is - * {@link History } - * - */ - public History getHistory() { - return history; - } - - /** - * Sets the value of the history property. - * - * @param value - * allowed object is - * {@link History } - * - */ - public void setHistory(History value) { - this.history = value; - } - - /** - * Gets the value of the customData property. - * - * @return - * possible object is - * {@link CustomData } - * - */ - public CustomData getCustomData() { - return customData; - } - - /** - * Sets the value of the customData property. - * - * @param value - * allowed object is - * {@link CustomData } - * - */ - public void setCustomData(CustomData value) { - this.customData = value; - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbGroupBinding.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbGroupBinding.java deleted file mode 100644 index 43c74da0..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/JaxbGroupBinding.java +++ /dev/null @@ -1,456 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.linguafranca.pwdb.kdbx.jaxb.base.AbstractJaxbParentedBinding; - - -/** - *

Java class for Group element declaration. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <element name="Group">
- *   <complexType>
- *     <complexContent>
- *       <extension base="{}Parented">
- *         <sequence>
- *           <element name="UUID" type="{}uuid"/>
- *           <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *           <element name="Notes" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *           <element name="IconID" type="{}iconId"/>
- *           <element name="CustomIconUUID" type="{}customIconUuidRef" minOccurs="0"/>
- *           <element ref="{}Times"/>
- *           <element name="IsExpanded" type="{}keepassBoolean"/>
- *           <element name="DefaultAutoTypeSequence" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *           <element name="EnableAutoType" type="{}keepassBoolean"/>
- *           <element name="EnableSearching" type="{}keepassBoolean"/>
- *           <element name="LastTopVisibleEntry" type="{}uuidRef"/>
- *           <element ref="{}Entry" maxOccurs="unbounded" minOccurs="0"/>
- *           <element ref="{}Group" maxOccurs="unbounded" minOccurs="0"/>
- *           <element name="CustomData" type="{}customData" minOccurs="0"/>
- *         </sequence>
- *       </extension>
- *     </complexContent>
- *   </complexType>
- * </element>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "uuid", - "name", - "notes", - "iconID", - "customIconUUID", - "times", - "isExpanded", - "defaultAutoTypeSequence", - "enableAutoType", - "enableSearching", - "lastTopVisibleEntry", - "entry", - "group", - "customData" -}) -@XmlRootElement(name = "Group") -public class JaxbGroupBinding - extends AbstractJaxbParentedBinding -{ - - @XmlElement(name = "UUID", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID uuid; - @XmlElement(name = "Name", required = true) - protected String name; - @XmlElement(name = "Notes", required = true) - protected String notes; - @XmlElement(name = "IconID") - protected int iconID; - @XmlElement(name = "CustomIconUUID", type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID customIconUUID; - @XmlElement(name = "Times", required = true) - protected Times times; - @XmlElement(name = "IsExpanded", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean isExpanded; - @XmlElement(name = "DefaultAutoTypeSequence", required = true) - protected String defaultAutoTypeSequence; - @XmlElement(name = "EnableAutoType", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean enableAutoType; - @XmlElement(name = "EnableSearching", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean enableSearching; - @XmlElement(name = "LastTopVisibleEntry", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID lastTopVisibleEntry; - @XmlElement(name = "Entry") - protected List entry; - @XmlElement(name = "Group") - protected List group; - @XmlElement(name = "CustomData") - protected CustomData customData; - - /** - * Gets the value of the uuid property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getUUID() { - return uuid; - } - - /** - * Sets the value of the uuid property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setUUID(UUID value) { - this.uuid = value; - } - - /** - * Gets the value of the name property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getName() { - return name; - } - - /** - * Sets the value of the name property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setName(String value) { - this.name = value; - } - - /** - * Gets the value of the notes property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getNotes() { - return notes; - } - - /** - * Sets the value of the notes property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setNotes(String value) { - this.notes = value; - } - - /** - * Gets the value of the iconID property. - * - */ - public int getIconID() { - return iconID; - } - - /** - * Sets the value of the iconID property. - * - */ - public void setIconID(int value) { - this.iconID = value; - } - - /** - * Gets the value of the customIconUUID property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getCustomIconUUID() { - return customIconUUID; - } - - /** - * Sets the value of the customIconUUID property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setCustomIconUUID(UUID value) { - this.customIconUUID = value; - } - - /** - * Gets the value of the times property. - * - * @return - * possible object is - * {@link Times } - * - */ - public Times getTimes() { - return times; - } - - /** - * Sets the value of the times property. - * - * @param value - * allowed object is - * {@link Times } - * - */ - public void setTimes(Times value) { - this.times = value; - } - - /** - * Gets the value of the isExpanded property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getIsExpanded() { - return isExpanded; - } - - /** - * Sets the value of the isExpanded property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setIsExpanded(Boolean value) { - this.isExpanded = value; - } - - /** - * Gets the value of the defaultAutoTypeSequence property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getDefaultAutoTypeSequence() { - return defaultAutoTypeSequence; - } - - /** - * Sets the value of the defaultAutoTypeSequence property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDefaultAutoTypeSequence(String value) { - this.defaultAutoTypeSequence = value; - } - - /** - * Gets the value of the enableAutoType property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getEnableAutoType() { - return enableAutoType; - } - - /** - * Sets the value of the enableAutoType property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setEnableAutoType(Boolean value) { - this.enableAutoType = value; - } - - /** - * Gets the value of the enableSearching property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getEnableSearching() { - return enableSearching; - } - - /** - * Sets the value of the enableSearching property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setEnableSearching(Boolean value) { - this.enableSearching = value; - } - - /** - * Gets the value of the lastTopVisibleEntry property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getLastTopVisibleEntry() { - return lastTopVisibleEntry; - } - - /** - * Sets the value of the lastTopVisibleEntry property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastTopVisibleEntry(UUID value) { - this.lastTopVisibleEntry = value; - } - - /** - * Gets the value of the entry property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the entry property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getEntry().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link JaxbEntryBinding } - * - * - */ - public List getEntry() { - if (entry == null) { - entry = new ArrayList(); - } - return this.entry; - } - - /** - * Gets the value of the group property. - * - *

- * This accessor method returns a reference to the live list, - * not a snapshot. Therefore any modification you make to the - * returned list will be present inside the JAXB object. - * This is why there is not a set method for the group property. - * - *

- * For example, to add a new item, do as follows: - *

-     *    getGroup().add(newItem);
-     * 
- * - * - *

- * Objects of the following type(s) are allowed in the list - * {@link JaxbGroupBinding } - * - * - */ - public List getGroup() { - if (group == null) { - group = new ArrayList(); - } - return this.group; - } - - /** - * Gets the value of the customData property. - * - * @return - * possible object is - * {@link CustomData } - * - */ - public CustomData getCustomData() { - return customData; - } - - /** - * Sets the value of the customData property. - * - * @param value - * allowed object is - * {@link CustomData } - * - */ - public void setCustomData(CustomData value) { - this.customData = value; - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/KeePassFile.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/KeePassFile.java deleted file mode 100644 index 7b2ec424..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/KeePassFile.java +++ /dev/null @@ -1,1173 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.Date; -import java.util.UUID; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - - -/** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType>
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="Meta">
- *           <complexType>
- *             <complexContent>
- *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                 <sequence>
- *                   <element name="Generator" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="HeaderHash" type="{http://www.w3.org/2001/XMLSchema}base64Binary" minOccurs="0"/>
- *                   <element name="DatabaseName" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="DatabaseNameChanged" type="{}keepassDateTime"/>
- *                   <element name="DatabaseDescription" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="DatabaseDescriptionChanged" type="{}keepassDateTime"/>
- *                   <element name="DefaultUserName" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *                   <element name="DefaultUserNameChanged" type="{}keepassDateTime"/>
- *                   <element name="MaintenanceHistoryDays" type="{http://www.w3.org/2001/XMLSchema}int"/>
- *                   <element name="Color" type="{}color"/>
- *                   <element name="MasterKeyChanged" type="{}keepassDateTime"/>
- *                   <element name="MasterKeyChangeRec" type="{}changeDays"/>
- *                   <element name="MasterKeyChangeForce" type="{}changeDays"/>
- *                   <element name="MemoryProtection">
- *                     <complexType>
- *                       <complexContent>
- *                         <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                           <sequence>
- *                             <element name="ProtectTitle" type="{}keepassBoolean"/>
- *                             <element name="ProtectUserName" type="{}keepassBoolean"/>
- *                             <element name="ProtectPassword" type="{}keepassBoolean"/>
- *                             <element name="ProtectURL" type="{}keepassBoolean"/>
- *                             <element name="ProtectNotes" type="{}keepassBoolean"/>
- *                           </sequence>
- *                         </restriction>
- *                       </complexContent>
- *                     </complexType>
- *                   </element>
- *                   <element name="CustomIcons" type="{}customIcons"/>
- *                   <element name="RecycleBinEnabled" type="{}keepassBoolean"/>
- *                   <element name="RecycleBinUUID" type="{}uuidRef"/>
- *                   <element name="RecycleBinChanged" type="{}keepassDateTime"/>
- *                   <element name="EntryTemplatesGroup" type="{}uuidRef"/>
- *                   <element name="EntryTemplatesGroupChanged" type="{}keepassDateTime"/>
- *                   <element name="LastSelectedGroup" type="{}uuidRef"/>
- *                   <element name="LastTopVisibleGroup" type="{}uuidRef"/>
- *                   <element name="HistoryMaxItems" type="{http://www.w3.org/2001/XMLSchema}int"/>
- *                   <element name="HistoryMaxSize" type="{http://www.w3.org/2001/XMLSchema}int"/>
- *                   <element name="Binaries" type="{}binaries" minOccurs="0"/>
- *                   <element name="CustomData" type="{}customData"/>
- *                 </sequence>
- *               </restriction>
- *             </complexContent>
- *           </complexType>
- *         </element>
- *         <element name="Root">
- *           <complexType>
- *             <complexContent>
- *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *                 <sequence>
- *                   <element ref="{}Group"/>
- *                   <element name="DeletedObjects" type="{http://www.w3.org/2001/XMLSchema}anyType"/>
- *                 </sequence>
- *               </restriction>
- *             </complexContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "meta", - "root" -}) -@XmlRootElement(name = "KeePassFile") -public class KeePassFile { - - @XmlElement(name = "Meta", required = true) - protected KeePassFile.Meta meta; - @XmlElement(name = "Root", required = true) - protected KeePassFile.Root root; - - /** - * Gets the value of the meta property. - * - * @return - * possible object is - * {@link KeePassFile.Meta } - * - */ - public KeePassFile.Meta getMeta() { - return meta; - } - - /** - * Sets the value of the meta property. - * - * @param value - * allowed object is - * {@link KeePassFile.Meta } - * - */ - public void setMeta(KeePassFile.Meta value) { - this.meta = value; - } - - /** - * Gets the value of the root property. - * - * @return - * possible object is - * {@link KeePassFile.Root } - * - */ - public KeePassFile.Root getRoot() { - return root; - } - - /** - * Sets the value of the root property. - * - * @param value - * allowed object is - * {@link KeePassFile.Root } - * - */ - public void setRoot(KeePassFile.Root value) { - this.root = value; - } - - - /** - * This contains database settings and also contains information - * about the last state of the program that used the database. Presumably assuming - * that the same program will be used to open it again, or that a different program - * would want/need the same settings. - * - * - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <complexContent>
-     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *       <sequence>
-     *         <element name="Generator" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="HeaderHash" type="{http://www.w3.org/2001/XMLSchema}base64Binary" minOccurs="0"/>
-     *         <element name="DatabaseName" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="DatabaseNameChanged" type="{}keepassDateTime"/>
-     *         <element name="DatabaseDescription" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="DatabaseDescriptionChanged" type="{}keepassDateTime"/>
-     *         <element name="DefaultUserName" type="{http://www.w3.org/2001/XMLSchema}string"/>
-     *         <element name="DefaultUserNameChanged" type="{}keepassDateTime"/>
-     *         <element name="MaintenanceHistoryDays" type="{http://www.w3.org/2001/XMLSchema}int"/>
-     *         <element name="Color" type="{}color"/>
-     *         <element name="MasterKeyChanged" type="{}keepassDateTime"/>
-     *         <element name="MasterKeyChangeRec" type="{}changeDays"/>
-     *         <element name="MasterKeyChangeForce" type="{}changeDays"/>
-     *         <element name="MemoryProtection">
-     *           <complexType>
-     *             <complexContent>
-     *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *                 <sequence>
-     *                   <element name="ProtectTitle" type="{}keepassBoolean"/>
-     *                   <element name="ProtectUserName" type="{}keepassBoolean"/>
-     *                   <element name="ProtectPassword" type="{}keepassBoolean"/>
-     *                   <element name="ProtectURL" type="{}keepassBoolean"/>
-     *                   <element name="ProtectNotes" type="{}keepassBoolean"/>
-     *                 </sequence>
-     *               </restriction>
-     *             </complexContent>
-     *           </complexType>
-     *         </element>
-     *         <element name="CustomIcons" type="{}customIcons"/>
-     *         <element name="RecycleBinEnabled" type="{}keepassBoolean"/>
-     *         <element name="RecycleBinUUID" type="{}uuidRef"/>
-     *         <element name="RecycleBinChanged" type="{}keepassDateTime"/>
-     *         <element name="EntryTemplatesGroup" type="{}uuidRef"/>
-     *         <element name="EntryTemplatesGroupChanged" type="{}keepassDateTime"/>
-     *         <element name="LastSelectedGroup" type="{}uuidRef"/>
-     *         <element name="LastTopVisibleGroup" type="{}uuidRef"/>
-     *         <element name="HistoryMaxItems" type="{http://www.w3.org/2001/XMLSchema}int"/>
-     *         <element name="HistoryMaxSize" type="{http://www.w3.org/2001/XMLSchema}int"/>
-     *         <element name="Binaries" type="{}binaries" minOccurs="0"/>
-     *         <element name="CustomData" type="{}customData"/>
-     *       </sequence>
-     *     </restriction>
-     *   </complexContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "generator", - "headerHash", - "databaseName", - "databaseNameChanged", - "databaseDescription", - "databaseDescriptionChanged", - "defaultUserName", - "defaultUserNameChanged", - "maintenanceHistoryDays", - "color", - "masterKeyChanged", - "masterKeyChangeRec", - "masterKeyChangeForce", - "memoryProtection", - "customIcons", - "recycleBinEnabled", - "recycleBinUUID", - "recycleBinChanged", - "entryTemplatesGroup", - "entryTemplatesGroupChanged", - "lastSelectedGroup", - "lastTopVisibleGroup", - "historyMaxItems", - "historyMaxSize", - "binaries", - "customData" - }) - public static class Meta { - - @XmlElement(name = "Generator", required = true) - protected String generator; - @XmlElement(name = "HeaderHash") - protected byte[] headerHash; - @XmlElement(name = "DatabaseName", required = true) - protected String databaseName; - @XmlElement(name = "DatabaseNameChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date databaseNameChanged; - @XmlElement(name = "DatabaseDescription", required = true) - protected String databaseDescription; - @XmlElement(name = "DatabaseDescriptionChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date databaseDescriptionChanged; - @XmlElement(name = "DefaultUserName", required = true) - protected String defaultUserName; - @XmlElement(name = "DefaultUserNameChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date defaultUserNameChanged; - @XmlElement(name = "MaintenanceHistoryDays") - protected int maintenanceHistoryDays; - @XmlElement(name = "Color", required = true) - protected String color; - @XmlElement(name = "MasterKeyChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date masterKeyChanged; - @XmlElement(name = "MasterKeyChangeRec") - protected int masterKeyChangeRec; - @XmlElement(name = "MasterKeyChangeForce") - protected int masterKeyChangeForce; - @XmlElement(name = "MemoryProtection", required = true) - protected KeePassFile.Meta.MemoryProtection memoryProtection; - @XmlElement(name = "CustomIcons", required = true) - protected CustomIcons customIcons; - @XmlElement(name = "RecycleBinEnabled", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean recycleBinEnabled; - @XmlElement(name = "RecycleBinUUID", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID recycleBinUUID; - @XmlElement(name = "RecycleBinChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date recycleBinChanged; - @XmlElement(name = "EntryTemplatesGroup", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID entryTemplatesGroup; - @XmlElement(name = "EntryTemplatesGroupChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date entryTemplatesGroupChanged; - @XmlElement(name = "LastSelectedGroup", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID lastSelectedGroup; - @XmlElement(name = "LastTopVisibleGroup", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter3 .class) - @XmlSchemaType(name = "base64Binary") - protected UUID lastTopVisibleGroup; - @XmlElement(name = "HistoryMaxItems") - protected int historyMaxItems; - @XmlElement(name = "HistoryMaxSize") - protected int historyMaxSize; - @XmlElement(name = "Binaries") - protected Binaries binaries; - @XmlElement(name = "CustomData", required = true) - protected CustomData customData; - - /** - * Gets the value of the generator property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getGenerator() { - return generator; - } - - /** - * Sets the value of the generator property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setGenerator(String value) { - this.generator = value; - } - - /** - * Gets the value of the headerHash property. - * - * @return - * possible object is - * byte[] - */ - public byte[] getHeaderHash() { - return headerHash; - } - - /** - * Sets the value of the headerHash property. - * - * @param value - * allowed object is - * byte[] - */ - public void setHeaderHash(byte[] value) { - this.headerHash = value; - } - - /** - * Gets the value of the databaseName property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getDatabaseName() { - return databaseName; - } - - /** - * Sets the value of the databaseName property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDatabaseName(String value) { - this.databaseName = value; - } - - /** - * Gets the value of the databaseNameChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getDatabaseNameChanged() { - return databaseNameChanged; - } - - /** - * Sets the value of the databaseNameChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDatabaseNameChanged(Date value) { - this.databaseNameChanged = value; - } - - /** - * Gets the value of the databaseDescription property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getDatabaseDescription() { - return databaseDescription; - } - - /** - * Sets the value of the databaseDescription property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDatabaseDescription(String value) { - this.databaseDescription = value; - } - - /** - * Gets the value of the databaseDescriptionChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getDatabaseDescriptionChanged() { - return databaseDescriptionChanged; - } - - /** - * Sets the value of the databaseDescriptionChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDatabaseDescriptionChanged(Date value) { - this.databaseDescriptionChanged = value; - } - - /** - * Gets the value of the defaultUserName property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getDefaultUserName() { - return defaultUserName; - } - - /** - * Sets the value of the defaultUserName property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDefaultUserName(String value) { - this.defaultUserName = value; - } - - /** - * Gets the value of the defaultUserNameChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getDefaultUserNameChanged() { - return defaultUserNameChanged; - } - - /** - * Sets the value of the defaultUserNameChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setDefaultUserNameChanged(Date value) { - this.defaultUserNameChanged = value; - } - - /** - * Gets the value of the maintenanceHistoryDays property. - * - */ - public int getMaintenanceHistoryDays() { - return maintenanceHistoryDays; - } - - /** - * Sets the value of the maintenanceHistoryDays property. - * - */ - public void setMaintenanceHistoryDays(int value) { - this.maintenanceHistoryDays = value; - } - - /** - * Gets the value of the color property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getColor() { - return color; - } - - /** - * Sets the value of the color property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setColor(String value) { - this.color = value; - } - - /** - * Gets the value of the masterKeyChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getMasterKeyChanged() { - return masterKeyChanged; - } - - /** - * Sets the value of the masterKeyChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setMasterKeyChanged(Date value) { - this.masterKeyChanged = value; - } - - /** - * Gets the value of the masterKeyChangeRec property. - * - */ - public int getMasterKeyChangeRec() { - return masterKeyChangeRec; - } - - /** - * Sets the value of the masterKeyChangeRec property. - * - */ - public void setMasterKeyChangeRec(int value) { - this.masterKeyChangeRec = value; - } - - /** - * Gets the value of the masterKeyChangeForce property. - * - */ - public int getMasterKeyChangeForce() { - return masterKeyChangeForce; - } - - /** - * Sets the value of the masterKeyChangeForce property. - * - */ - public void setMasterKeyChangeForce(int value) { - this.masterKeyChangeForce = value; - } - - /** - * Gets the value of the memoryProtection property. - * - * @return - * possible object is - * {@link KeePassFile.Meta.MemoryProtection } - * - */ - public KeePassFile.Meta.MemoryProtection getMemoryProtection() { - return memoryProtection; - } - - /** - * Sets the value of the memoryProtection property. - * - * @param value - * allowed object is - * {@link KeePassFile.Meta.MemoryProtection } - * - */ - public void setMemoryProtection(KeePassFile.Meta.MemoryProtection value) { - this.memoryProtection = value; - } - - /** - * Gets the value of the customIcons property. - * - * @return - * possible object is - * {@link CustomIcons } - * - */ - public CustomIcons getCustomIcons() { - return customIcons; - } - - /** - * Sets the value of the customIcons property. - * - * @param value - * allowed object is - * {@link CustomIcons } - * - */ - public void setCustomIcons(CustomIcons value) { - this.customIcons = value; - } - - /** - * Gets the value of the recycleBinEnabled property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getRecycleBinEnabled() { - return recycleBinEnabled; - } - - /** - * Sets the value of the recycleBinEnabled property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setRecycleBinEnabled(Boolean value) { - this.recycleBinEnabled = value; - } - - /** - * Gets the value of the recycleBinUUID property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getRecycleBinUUID() { - return recycleBinUUID; - } - - /** - * Sets the value of the recycleBinUUID property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setRecycleBinUUID(UUID value) { - this.recycleBinUUID = value; - } - - /** - * Gets the value of the recycleBinChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getRecycleBinChanged() { - return recycleBinChanged; - } - - /** - * Sets the value of the recycleBinChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setRecycleBinChanged(Date value) { - this.recycleBinChanged = value; - } - - /** - * Gets the value of the entryTemplatesGroup property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getEntryTemplatesGroup() { - return entryTemplatesGroup; - } - - /** - * Sets the value of the entryTemplatesGroup property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setEntryTemplatesGroup(UUID value) { - this.entryTemplatesGroup = value; - } - - /** - * Gets the value of the entryTemplatesGroupChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getEntryTemplatesGroupChanged() { - return entryTemplatesGroupChanged; - } - - /** - * Sets the value of the entryTemplatesGroupChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setEntryTemplatesGroupChanged(Date value) { - this.entryTemplatesGroupChanged = value; - } - - /** - * Gets the value of the lastSelectedGroup property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getLastSelectedGroup() { - return lastSelectedGroup; - } - - /** - * Sets the value of the lastSelectedGroup property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastSelectedGroup(UUID value) { - this.lastSelectedGroup = value; - } - - /** - * Gets the value of the lastTopVisibleGroup property. - * - * @return - * possible object is - * {@link String } - * - */ - public UUID getLastTopVisibleGroup() { - return lastTopVisibleGroup; - } - - /** - * Sets the value of the lastTopVisibleGroup property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastTopVisibleGroup(UUID value) { - this.lastTopVisibleGroup = value; - } - - /** - * Gets the value of the historyMaxItems property. - * - */ - public int getHistoryMaxItems() { - return historyMaxItems; - } - - /** - * Sets the value of the historyMaxItems property. - * - */ - public void setHistoryMaxItems(int value) { - this.historyMaxItems = value; - } - - /** - * Gets the value of the historyMaxSize property. - * - */ - public int getHistoryMaxSize() { - return historyMaxSize; - } - - /** - * Sets the value of the historyMaxSize property. - * - */ - public void setHistoryMaxSize(int value) { - this.historyMaxSize = value; - } - - /** - * Gets the value of the binaries property. - * - * @return - * possible object is - * {@link Binaries } - * - */ - public Binaries getBinaries() { - return binaries; - } - - /** - * Sets the value of the binaries property. - * - * @param value - * allowed object is - * {@link Binaries } - * - */ - public void setBinaries(Binaries value) { - this.binaries = value; - } - - /** - * Gets the value of the customData property. - * - * @return - * possible object is - * {@link CustomData } - * - */ - public CustomData getCustomData() { - return customData; - } - - /** - * Sets the value of the customData property. - * - * @param value - * allowed object is - * {@link CustomData } - * - */ - public void setCustomData(CustomData value) { - this.customData = value; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-         * <complexType>
-         *   <complexContent>
-         *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-         *       <sequence>
-         *         <element name="ProtectTitle" type="{}keepassBoolean"/>
-         *         <element name="ProtectUserName" type="{}keepassBoolean"/>
-         *         <element name="ProtectPassword" type="{}keepassBoolean"/>
-         *         <element name="ProtectURL" type="{}keepassBoolean"/>
-         *         <element name="ProtectNotes" type="{}keepassBoolean"/>
-         *       </sequence>
-         *     </restriction>
-         *   </complexContent>
-         * </complexType>
-         * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "protectTitle", - "protectUserName", - "protectPassword", - "protectURL", - "protectNotes" - }) - public static class MemoryProtection { - - @XmlElement(name = "ProtectTitle", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectTitle; - @XmlElement(name = "ProtectUserName", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectUserName; - @XmlElement(name = "ProtectPassword", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectPassword; - @XmlElement(name = "ProtectURL", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectURL; - @XmlElement(name = "ProtectNotes", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectNotes; - - /** - * Gets the value of the protectTitle property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectTitle() { - return protectTitle; - } - - /** - * Sets the value of the protectTitle property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectTitle(Boolean value) { - this.protectTitle = value; - } - - /** - * Gets the value of the protectUserName property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectUserName() { - return protectUserName; - } - - /** - * Sets the value of the protectUserName property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectUserName(Boolean value) { - this.protectUserName = value; - } - - /** - * Gets the value of the protectPassword property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectPassword() { - return protectPassword; - } - - /** - * Sets the value of the protectPassword property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectPassword(Boolean value) { - this.protectPassword = value; - } - - /** - * Gets the value of the protectURL property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectURL() { - return protectURL; - } - - /** - * Sets the value of the protectURL property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectURL(Boolean value) { - this.protectURL = value; - } - - /** - * Gets the value of the protectNotes property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectNotes() { - return protectNotes; - } - - /** - * Sets the value of the protectNotes property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectNotes(Boolean value) { - this.protectNotes = value; - } - - } - - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <complexContent>
-     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-     *       <sequence>
-     *         <element ref="{}Group"/>
-     *         <element name="DeletedObjects" type="{http://www.w3.org/2001/XMLSchema}anyType"/>
-     *       </sequence>
-     *     </restriction>
-     *   </complexContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "", propOrder = { - "group", - "deletedObjects" - }) - public static class Root { - - @XmlElement(name = "Group", required = true) - protected JaxbGroupBinding group; - @XmlElement(name = "DeletedObjects", required = true) - protected Object deletedObjects; - - /** - * This is the root group which contains everything. - * - * - * @return - * possible object is - * {@link JaxbGroupBinding } - * - */ - public JaxbGroupBinding getGroup() { - return group; - } - - /** - * Sets the value of the group property. - * - * @param value - * allowed object is - * {@link JaxbGroupBinding } - * - */ - public void setGroup(JaxbGroupBinding value) { - this.group = value; - } - - /** - * Gets the value of the deletedObjects property. - * - * @return - * possible object is - * {@link Object } - * - */ - public Object getDeletedObjects() { - return deletedObjects; - } - - /** - * Sets the value of the deletedObjects property. - * - * @param value - * allowed object is - * {@link Object } - * - */ - public void setDeletedObjects(Object value) { - this.deletedObjects = value; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ObjectFactory.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ObjectFactory.java deleted file mode 100644 index 0c3cf1e2..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ObjectFactory.java +++ /dev/null @@ -1,214 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.JAXBElement; -import javax.xml.bind.annotation.XmlElementDecl; -import javax.xml.bind.annotation.XmlRegistry; -import javax.xml.namespace.QName; - - -/** - * This object contains factory methods for each - * Java content interface and Java element interface - * generated in the org.linguafranca.pwdb.kdbx.jaxb.binding package. - *

An ObjectFactory allows you to programatically - * construct new instances of the Java representation - * for XML content. The Java representation of XML - * content can consist of schema derived interfaces - * and classes representing the binding of schema - * type definitions, element declarations and model - * groups. Factory methods for each of these are - * provided in this class. - * - */ -@XmlRegistry -public class ObjectFactory { - - private final static QName _AutoTypeAssociationWindow_QNAME = new QName("", "Window"); - private final static QName _AutoTypeAssociationKeystrokeSequence_QNAME = new QName("", "KeystrokeSequence"); - - /** - * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.linguafranca.pwdb.kdbx.jaxb.binding - * - */ - public ObjectFactory() { - } - - /** - * Create an instance of {@link KeePassFile } - * - */ - public KeePassFile createKeePassFile() { - return new KeePassFile(); - } - - /** - * Create an instance of {@link Binaries } - * - */ - public Binaries createBinaries() { - return new Binaries(); - } - - /** - * Create an instance of {@link StringField } - * - */ - public StringField createStringField() { - return new StringField(); - } - - /** - * Create an instance of {@link AutoType } - * - */ - public AutoType createAutoType() { - return new AutoType(); - } - - /** - * Create an instance of {@link BinaryField } - * - */ - public BinaryField createBinaryField() { - return new BinaryField(); - } - - /** - * Create an instance of {@link CustomIcons } - * - */ - public CustomIcons createCustomIcons() { - return new CustomIcons(); - } - - /** - * Create an instance of {@link KeePassFile.Meta } - * - */ - public KeePassFile.Meta createKeePassFileMeta() { - return new KeePassFile.Meta(); - } - - /** - * Create an instance of {@link JaxbGroupBinding } - * - */ - public JaxbGroupBinding createJaxbGroupBinding() { - return new JaxbGroupBinding(); - } - - /** - * Create an instance of {@link JaxbEntryBinding } - * - */ - public JaxbEntryBinding createJaxbEntryBinding() { - return new JaxbEntryBinding(); - } - - /** - * Create an instance of {@link Times } - * - */ - public Times createTimes() { - return new Times(); - } - - /** - * Create an instance of {@link History } - * - */ - public History createHistory() { - return new History(); - } - - /** - * Create an instance of {@link KeePassFile.Root } - * - */ - public KeePassFile.Root createKeePassFileRoot() { - return new KeePassFile.Root(); - } - - /** - * Create an instance of {@link CustomData } - * - */ - public CustomData createCustomData() { - return new CustomData(); - } - - /** - * Create an instance of {@link Binaries.Binary } - * - */ - public Binaries.Binary createBinariesBinary() { - return new Binaries.Binary(); - } - - /** - * Create an instance of {@link StringField.Value } - * - */ - public StringField.Value createStringFieldValue() { - return new StringField.Value(); - } - - /** - * Create an instance of {@link AutoType.Association } - * - */ - public AutoType.Association createAutoTypeAssociation() { - return new AutoType.Association(); - } - - /** - * Create an instance of {@link BinaryField.Value } - * - */ - public BinaryField.Value createBinaryFieldValue() { - return new BinaryField.Value(); - } - - /** - * Create an instance of {@link CustomIcons.Icon } - * - */ - public CustomIcons.Icon createCustomIconsIcon() { - return new CustomIcons.Icon(); - } - - /** - * Create an instance of {@link KeePassFile.Meta.MemoryProtection } - * - */ - public KeePassFile.Meta.MemoryProtection createKeePassFileMetaMemoryProtection() { - return new KeePassFile.Meta.MemoryProtection(); - } - - /** - * Create an instance of {@link JAXBElement }{@code <}{@link String }{@code >}} - * - */ - @XmlElementDecl(namespace = "", name = "Window", scope = AutoType.Association.class) - public JAXBElement createAutoTypeAssociationWindow(String value) { - return new JAXBElement(_AutoTypeAssociationWindow_QNAME, String.class, AutoType.Association.class, value); - } - - /** - * Create an instance of {@link JAXBElement }{@code <}{@link String }{@code >}} - * - */ - @XmlElementDecl(namespace = "", name = "KeystrokeSequence", scope = AutoType.Association.class) - public JAXBElement createAutoTypeAssociationKeystrokeSequence(String value) { - return new JAXBElement(_AutoTypeAssociationKeystrokeSequence_QNAME, String.class, AutoType.Association.class, value); - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Parented.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Parented.java deleted file mode 100644 index 331b6222..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Parented.java +++ /dev/null @@ -1,34 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlType; - - -/** - * This class, while generated, is not used see {@link - * org.linguafranca.pwdb.kdbx.jaxb.org.linguafranca.pwdb.base.AbstractJaxbParentedBinding} which is the - * actual implementation class. - * - * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "Parented") -@XmlSeeAlso({ - JaxbGroupBinding.class, - JaxbEntryBinding.class -}) -public abstract class Parented { - - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/StringField.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/StringField.java deleted file mode 100644 index 887ec91e..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/StringField.java +++ /dev/null @@ -1,205 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.linguafranca.pwdb.kdbx.jaxb.base.ValueBinding; - - -/** - * This is where the values of the database are actually stored. You can have String valued - * fields, and you can have Binary valued fields. There are "Default" String fields (username and so on) and - * there are custom string fields (custom only in that their names are not the names of default string - * fields). Not really clear whether the keys are case-sensitive. - * - * - *

Java class for stringField complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="stringField">
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="Key" type="{http://www.w3.org/2001/XMLSchema}string"/>
- *         <element name="Value">
- *           <complexType>
- *             <simpleContent>
- *               <extension base="<>ValueExtender">
- *                 <attribute name="Protected" type="{}keepassBoolean" default="False" />
- *                 <attribute name="ProtectInMemory" type="{}keepassBoolean" default="False" />
- *               </extension>
- *             </simpleContent>
- *           </complexType>
- *         </element>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "stringField", propOrder = { - "key", - "value" -}) -public class StringField { - - @XmlElement(name = "Key", required = true) - protected String key; - @XmlElement(name = "Value", required = true) - protected StringField.Value value; - - /** - * Gets the value of the key property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getKey() { - return key; - } - - /** - * Sets the value of the key property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setKey(String value) { - this.key = value; - } - - /** - * Gets the value of the value property. - * - * @return - * possible object is - * {@link StringField.Value } - * - */ - public StringField.Value getValue() { - return value; - } - - /** - * Sets the value of the value property. - * - * @param value - * allowed object is - * {@link StringField.Value } - * - */ - public void setValue(StringField.Value value) { - this.value = value; - } - - - /** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

-     * <complexType>
-     *   <simpleContent>
-     *     <extension base="<>ValueExtender">
-     *       <attribute name="Protected" type="{}keepassBoolean" default="False" />
-     *       <attribute name="ProtectInMemory" type="{}keepassBoolean" default="False" />
-     *     </extension>
-     *   </simpleContent>
-     * </complexType>
-     * 
- * - * - */ - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "") - public static class Value - extends ValueBinding - { - - @XmlAttribute(name = "Protected") - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean _protected; - @XmlAttribute(name = "ProtectInMemory") - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean protectInMemory; - - /** - * Gets the value of the protected property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtected() { - if (_protected == null) { - return new Adapter2().unmarshal("False"); - } else { - return _protected; - } - } - - /** - * Sets the value of the protected property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtected(Boolean value) { - this._protected = value; - } - - /** - * Gets the value of the protectInMemory property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getProtectInMemory() { - if (protectInMemory == null) { - return new Adapter2().unmarshal("False"); - } else { - return protectInMemory; - } - } - - /** - * Sets the value of the protectInMemory property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setProtectInMemory(Boolean value) { - this.protectInMemory = value; - } - - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Times.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Times.java deleted file mode 100644 index 2d371477..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/Times.java +++ /dev/null @@ -1,245 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import java.util.Date; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - - -/** - *

Java class for anonymous complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType>
- *   <complexContent>
- *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
- *       <sequence>
- *         <element name="LastModificationTime" type="{}keepassDateTime"/>
- *         <element name="CreationTime" type="{}keepassDateTime"/>
- *         <element name="LastAccessTime" type="{}keepassDateTime"/>
- *         <element name="ExpiryTime" type="{}keepassDateTime"/>
- *         <element name="Expires" type="{}keepassBoolean"/>
- *         <element name="UsageCount" type="{http://www.w3.org/2001/XMLSchema}int"/>
- *         <element name="LocationChanged" type="{}keepassDateTime"/>
- *       </sequence>
- *     </restriction>
- *   </complexContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "lastModificationTime", - "creationTime", - "lastAccessTime", - "expiryTime", - "expires", - "usageCount", - "locationChanged" -}) -@XmlRootElement(name = "Times") -public class Times { - - @XmlElement(name = "LastModificationTime", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date lastModificationTime; - @XmlElement(name = "CreationTime", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date creationTime; - @XmlElement(name = "LastAccessTime", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date lastAccessTime; - @XmlElement(name = "ExpiryTime", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date expiryTime; - @XmlElement(name = "Expires", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter2 .class) - protected Boolean expires; - @XmlElement(name = "UsageCount") - protected int usageCount; - @XmlElement(name = "LocationChanged", required = true, type = String.class) - @XmlJavaTypeAdapter(Adapter1 .class) - @XmlSchemaType(name = "dateTime") - protected Date locationChanged; - - /** - * Gets the value of the lastModificationTime property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getLastModificationTime() { - return lastModificationTime; - } - - /** - * Sets the value of the lastModificationTime property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastModificationTime(Date value) { - this.lastModificationTime = value; - } - - /** - * Gets the value of the creationTime property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getCreationTime() { - return creationTime; - } - - /** - * Sets the value of the creationTime property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setCreationTime(Date value) { - this.creationTime = value; - } - - /** - * Gets the value of the lastAccessTime property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getLastAccessTime() { - return lastAccessTime; - } - - /** - * Sets the value of the lastAccessTime property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLastAccessTime(Date value) { - this.lastAccessTime = value; - } - - /** - * Gets the value of the expiryTime property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getExpiryTime() { - return expiryTime; - } - - /** - * Sets the value of the expiryTime property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setExpiryTime(Date value) { - this.expiryTime = value; - } - - /** - * Gets the value of the expires property. - * - * @return - * possible object is - * {@link String } - * - */ - public Boolean getExpires() { - return expires; - } - - /** - * Sets the value of the expires property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setExpires(Boolean value) { - this.expires = value; - } - - /** - * Gets the value of the usageCount property. - * - */ - public int getUsageCount() { - return usageCount; - } - - /** - * Sets the value of the usageCount property. - * - */ - public void setUsageCount(int value) { - this.usageCount = value; - } - - /** - * Gets the value of the locationChanged property. - * - * @return - * possible object is - * {@link String } - * - */ - public Date getLocationChanged() { - return locationChanged; - } - - /** - * Sets the value of the locationChanged property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setLocationChanged(Date value) { - this.locationChanged = value; - } - -} diff --git a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ValueExtender.java b/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ValueExtender.java deleted file mode 100644 index df8407c8..00000000 --- a/jaxb/src/generated/java/org/linguafranca/pwdb/kdbx/jaxb/binding/ValueExtender.java +++ /dev/null @@ -1,75 +0,0 @@ -// -// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 -// See http://java.sun.com/xml/jaxb -// Any modifications to this file will be lost upon recompilation of the source schema. -// Generated on: 2023.05.17 at 11:23:19 AM BST -// - - -package org.linguafranca.pwdb.kdbx.jaxb.binding; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlValue; - - -/** - * Abstract parent for StringField/Value so that - * to allow creation of a custom mapping that contains extra - * non-serialised (transient) fields. - * - * - *

Java class for ValueExtender complex type. - * - *

The following schema fragment specifies the expected content contained within this class. - * - *

- * <complexType name="ValueExtender">
- *   <simpleContent>
- *     <extension base="<http://www.w3.org/2001/XMLSchema>string">
- *     </extension>
- *   </simpleContent>
- * </complexType>
- * 
- * - * - */ -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "ValueExtender", propOrder = { - "value" -}) -@XmlSeeAlso({ - org.linguafranca.pwdb.kdbx.jaxb.binding.StringField.Value.class -}) -public abstract class ValueExtender { - - @XmlValue - protected String value; - - /** - * Gets the value of the value property. - * - * @return - * possible object is - * {@link String } - * - */ - public String getValue() { - return value; - } - - /** - * Sets the value of the value property. - * - * @param value - * allowed object is - * {@link String } - * - */ - public void setValue(String value) { - this.value = value; - } - -} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbDatabase.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbDatabase.java deleted file mode 100644 index 1059d52f..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbDatabase.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.StreamConfiguration; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.base.AbstractDatabase; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; -import org.linguafranca.pwdb.kdbx.jaxb.binding.KeePassFile; -import org.linguafranca.pwdb.kdbx.jaxb.binding.ObjectFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Date; -import java.util.Objects; -import java.util.UUID; - -/** - * Implementation of {@link org.linguafranca.pwdb.Database} for JAXB. - * - * @author jo - */ - -public class JaxbDatabase extends AbstractDatabase { - - private final KeePassFile keePassFile; - private final ObjectFactory objectFactory = new ObjectFactory(); - private final JaxbGroup root; - private StreamFormat streamFormat; - - public JaxbDatabase() { - this(createEmptyDatabase().getKeePassFile(), null); - } - - private JaxbDatabase(KeePassFile keePassFile, StreamFormat streamFormat) { - this.keePassFile = keePassFile; - this.root = new JaxbGroup(this, keePassFile.getRoot().getGroup()); - this.streamFormat = streamFormat; - } - - public static JaxbDatabase createEmptyDatabase() { - InputStream inputStream = JaxbDatabase.class.getClassLoader().getResourceAsStream("base.kdbx.xml"); - KeePassFile keePassFile = new JaxbSerializableDatabase().load(inputStream).keePassFile; - keePassFile.getRoot().getGroup().setUUID(UUID.randomUUID()); - return new JaxbDatabase(keePassFile, null); - } - - public static JaxbDatabase load(Credentials creds, InputStream inputStream) throws IOException { - return load(new KdbxStreamFormat(), creds, inputStream); - } - - @NotNull - public static JaxbDatabase load(StreamFormat format, Credentials creds, InputStream inputStream) throws IOException { - JaxbSerializableDatabase db = new JaxbSerializableDatabase(); - format.load(db, creds, inputStream); - return new JaxbDatabase(db.getKeePassFile(), format); - } - - @Override - public void save(Credentials creds, OutputStream outputStream) throws IOException { - if (Objects.isNull(this.streamFormat)){ - this.streamFormat = new KdbxStreamFormat(new KdbxHeader(4)); - } - save(this.streamFormat, creds, outputStream); - } - - public void save(StreamFormat format, Credentials creds, OutputStream outputStream) throws IOException { - keePassFile.getMeta().setGenerator("KeePassJava2-JAXB"); - JaxbSerializableDatabase jsd = new JaxbSerializableDatabase(this.keePassFile); - format.save(jsd, creds, outputStream); - setDirty(false); - } - - @Override - public JaxbGroup getRootGroup() { - return root; - } - - @Override - public JaxbGroup getRecycleBin() { - UUID recycleBinUuid = this.keePassFile.getMeta().getRecycleBinUUID(); - JaxbGroup g = findGroup(recycleBinUuid); - if (g == null && !isRecycleBinEnabled()) { - return null; - } - if (g == null) { - g = newGroup("Recycle Bin"); - getRootGroup().addGroup(g); - this.keePassFile.getMeta().setRecycleBinUUID(g.getUuid()); - this.keePassFile.getMeta().setRecycleBinChanged(new Date()); - } - return g; - } - - @Override - public boolean isRecycleBinEnabled() { - return this.keePassFile.getMeta().getRecycleBinEnabled(); - } - - @Override - public void enableRecycleBin(boolean enable) { - this.keePassFile.getMeta().setRecycleBinEnabled(enable); - } - - @Override - public JaxbGroup newGroup() { - return new JaxbGroup(this); - } - - @Override - public JaxbEntry newEntry() { - return new JaxbEntry(this); - } - - @Override - public JaxbIcon newIcon() { - return new JaxbIcon(); - } - - @Override - public JaxbIcon newIcon(Integer i) { - return new JaxbIcon(i); - } - - @Override - public String getDescription() { - return keePassFile.getMeta().getDatabaseDescription(); - } - - @Override - public void setDescription(String description) { - keePassFile.getMeta().setDatabaseDescription(description); - } - - @Override - public boolean shouldProtect(String propertyName) { - switch (propertyName.toLowerCase()) { - case "title": - return keePassFile.getMeta().getMemoryProtection().getProtectTitle(); - case "username": - return keePassFile.getMeta().getMemoryProtection().getProtectUserName(); - case "password": - return keePassFile.getMeta().getMemoryProtection().getProtectPassword(); - case "url": - return keePassFile.getMeta().getMemoryProtection().getProtectURL(); - case "notes": - return keePassFile.getMeta().getMemoryProtection().getProtectNotes(); - default: - return false; - } - } - - @Override - public String getName() { - return keePassFile.getMeta().getDatabaseName(); - } - - @Override - public void setName(String s) { - keePassFile.getMeta().setDatabaseName(s); - keePassFile.getMeta().setDatabaseNameChanged(new Date()); - } - - public KeePassFile getKeePassFile() { - return keePassFile; - } - - ObjectFactory getObjectFactory() { - return objectFactory; - } - - public void createBinary(byte[] value, Integer index) { - JaxbSerializableDatabase.addBinary(getKeePassFile(), getObjectFactory(), index, value); - } - - @SuppressWarnings("unchecked") - @Override - public StreamFormat getStreamFormat(){ - return streamFormat; - } -} 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 deleted file mode 100644 index 1eafe10d..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbEntry.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.base.AbstractEntry; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.jaxb.binding.*; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -/** - * Implementation of {@link org.linguafranca.pwdb.Entry} for JAXB. - * - *

The class wraps an underlying JAXB generated delegate. - * - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class JaxbEntry extends AbstractEntry { - - protected JaxbDatabase database; - protected JaxbEntryBinding delegate; - - public JaxbEntry(JaxbDatabase jaxbDatabase) { - this.database = jaxbDatabase; - this.delegate = new JaxbEntryBinding(); - - for (String s: STANDARD_PROPERTY_NAMES) { - StringField field = jaxbDatabase.getObjectFactory().createStringField(); - field.setKey(s); - StringField.Value value = jaxbDatabase.getObjectFactory().createStringFieldValue(); - value.setValue(""); - field.setValue(value); - delegate.getString().add(field); - } - - Date now = new Date(System.currentTimeMillis() / 1000L * 1000L); // to nearest lower second - Times times = new Times(); - times.setLastModificationTime(now); - times.setCreationTime(now); - times.setLastAccessTime(now); - times.setExpiryTime(now); - times.setExpires(false); - times.setUsageCount(0); - times.setLocationChanged(now); - this.delegate.setTimes(times); - - delegate.setUUID(UUID.randomUUID()); - } - - public JaxbEntry(JaxbDatabase database, JaxbEntryBinding entry) { - this.database = database; - this.delegate = entry; - } - - @Override - public String getProperty(String name) { - for (StringField field: delegate.getString()){ - if (field.getKey().equals(name)){ - return field.getValue().getValue(); - } - } - return null; - } - - @Override - public void setProperty(String name, String value) { - StringField toRemove = null; - for (StringField field: delegate.getString()){ - if (field.getKey().equals(name)) { - toRemove = field; - break; - } - } - if (toRemove != null) { - delegate.getString().remove(toRemove); - } - - StringField.Value fieldValue = database.getObjectFactory().createStringFieldValue(); - fieldValue.setValue(value); - fieldValue.setProtected(false); - StringField field = database.getObjectFactory().createStringField(); - field.setKey(name); - field.setValue(fieldValue); - delegate.getString().add(field); - touch(); - } - - @Override - public boolean removeProperty(String name) throws IllegalArgumentException { - if (STANDARD_PROPERTY_NAMES.contains(name)) throw new IllegalArgumentException("may not remove property: " + name); - - StringField toRemove = null; - for (StringField field: delegate.getString()){ - if (field.getKey().equals(name)) { - toRemove = field; - break; - } - } - if (toRemove == null) { - return false; - } else { - delegate.getString().remove(toRemove); - touch(); - return true; - } - } - - @Override - public List getPropertyNames() { - List result = new ArrayList<>(); - for (StringField stringField : delegate.getString()) { - result.add(stringField.getKey()); - } - return result; - } - - @Override - public byte[] getBinaryProperty(String name) { - for (BinaryField binaryField : delegate.getBinary()){ - if (binaryField.getKey().equals(name)){ - Integer ref = binaryField.getValue().getRef(); - for (Binaries.Binary binary: database.getKeePassFile().getMeta().getBinaries().getBinary()){ - if (binary.getID().equals(ref)) { - if (binary.getCompressed()) { - return Helpers.unzipBinaryContent(binary.getValue()); - } - return binary.getValue(); - } - } - } - } - return null; - } - - @Override - public void setBinaryProperty(String name, byte[] value) { - // remove old binary property with same name - BinaryField toRemove = null; - for (BinaryField binaryField : delegate.getBinary()) { - if (binaryField.getKey().equals(name)) { - toRemove = binaryField; - break; - } - } - if (toRemove != null) { - delegate.getBinary().remove(toRemove); - } - - // what is the next free index in the binary store? - Integer max = -1; - List binaryList = database.getKeePassFile().getMeta().getBinaries().getBinary(); - for (Binaries.Binary binary: binaryList){ - if (binary.getID() > max) { - max = binary.getID(); - } - } - max++; - - database.createBinary(value, max); - - // make a reference to it from the entry - BinaryField binaryField = database.getObjectFactory().createBinaryField(); - binaryField.setKey(name); - BinaryField.Value fieldValue = database.getObjectFactory().createBinaryFieldValue(); - fieldValue.setRef(max); - binaryField.setValue(fieldValue); - delegate.getBinary().add(binaryField); - touch(); - } - - @Override - public boolean removeBinaryProperty(String name) throws UnsupportedOperationException { - BinaryField toRemove = null; - for (BinaryField binaryField : delegate.getBinary()) { - if (binaryField.getKey().equals(name)) { - toRemove = binaryField; - break; - } - } - - if (toRemove == null) { - return false; - } else { - delegate.getBinary().remove(toRemove); - touch(); - return true; - } - } - - @Override - public List getBinaryPropertyNames() { - List result = new ArrayList<>(); - for (BinaryField binaryField : delegate.getBinary()) { - result.add(binaryField.getKey()); - } - return result; - } - - @Override - public JaxbGroup getParent() { - if (delegate.parent == null) { - return null; - } - return new JaxbGroup(database, ((JaxbGroupBinding) delegate.parent)); - } - - @Override - public @NotNull UUID getUuid() { - return delegate.getUUID(); - } - - @Override - public JaxbIcon getIcon() { - return new JaxbIcon(delegate.getIconID()); - } - - @Override - public void setIcon(JaxbIcon icon) { - delegate.setIconID(icon.getIndex()); - touch(); - } - - @Override - public Date getLastAccessTime() { - return delegate.getTimes().getLastAccessTime(); - } - - @Override - public Date getCreationTime() { - return delegate.getTimes().getCreationTime(); - } - - @Override - public boolean getExpires() { - return delegate.getTimes().getExpires(); - } - - @Override - public void setExpires(boolean expires) { - delegate.getTimes().setExpires(true); - } - - @Override - public Date getExpiryTime() { - return delegate.getTimes().getExpiryTime(); - } - - @Override - public void setExpiryTime(Date expiryTime) throws IllegalArgumentException { - if (expiryTime == null) throw new IllegalArgumentException("expiryTime may not be null"); - delegate.getTimes().setExpiryTime(expiryTime); - } - - @Override - public Date getLastModificationTime() { - return delegate.getTimes().getLastModificationTime(); - } - - @Override - protected void touch() { - database.setDirty(true); - delegate.getTimes().setLastModificationTime(new Date()); - } -} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbGroup.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbGroup.java deleted file mode 100644 index 1ceddb9e..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbGroup.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.base.AbstractGroup; -import org.linguafranca.pwdb.kdbx.jaxb.binding.JaxbEntryBinding; -import org.linguafranca.pwdb.kdbx.jaxb.binding.JaxbGroupBinding; -import org.linguafranca.pwdb.kdbx.jaxb.binding.ObjectFactory; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -/** - * Implementation of {@link org.linguafranca.pwdb.Group} for JAXB. - * - *

The class wraps an underlying JAXB generated delegate. - * - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class JaxbGroup extends AbstractGroup { - - protected JaxbDatabase database; - protected JaxbGroupBinding delegate; - - /** - * Wrap an existing {@link JaxbGroupBinding} in this database - * @param database the database - * @param group a JaxbGroupBinding - */ - public JaxbGroup(JaxbDatabase database, JaxbGroupBinding group) { - this.delegate = group; - this.database = database; - } - - public JaxbGroup(JaxbDatabase database) { - this.database = database; - this.delegate = database.getObjectFactory().createJaxbGroupBinding(); - delegate.setTimes(new ObjectFactory().createTimes()); - delegate.setIconID(0); - delegate.setName(""); - delegate.setUUID(UUID.randomUUID()); - } - - @Override - public boolean isRootGroup() { - return database.getRootGroup().equals(this); - } - - @Override - public boolean isRecycleBin() { - return database.getKeePassFile().getMeta().getRecycleBinUUID().equals(this.getUuid()); - } - - @Override - public JaxbGroup getParent() { - if (delegate.parent == null) { - return null; - } - return new JaxbGroup(database, ((JaxbGroupBinding) delegate.parent)); - } - - @Override - public void setParent(JaxbGroup group) { - if (isRootGroup()) { - throw new IllegalStateException("Cannot add root group to another group"); - } - - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - - JaxbGroupBinding parent = (JaxbGroupBinding) group.delegate.parent; - - if (parent != null) { - parent.getGroup().remove(group.delegate); - parent.getTimes().setLastModificationTime(new Date()); - } - - this.delegate.parent = group.delegate; - ((JaxbGroupBinding) this.delegate.parent).getTimes().setLastModificationTime(new Date()); - touch(); - } - - @Override - public List getGroups() { - List result = new ArrayList<>(); - for (JaxbGroupBinding child : delegate.getGroup()) { - result.add(new JaxbGroup(database, child)); - } - return result; - } - - @Override - public int getGroupsCount() { - return delegate.getGroup().size(); - } - - @Override - public JaxbGroup addGroup(JaxbGroup group) { - if (group.isRootGroup()) { - throw new IllegalStateException("Cannot add root group to another group"); - } - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - - if (group.getParent() != null) { - group.getParent().delegate.getGroup().remove(group.delegate); - } - group.delegate.parent = this.delegate; - this.delegate.getGroup().add(group.delegate); - touch(); - return group; - } - - @Override - public JaxbGroup removeGroup(JaxbGroup group) { - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - delegate.getGroup().remove(group.delegate); - group.delegate.parent = null; - touch(); - return group; - } - - @Override - public List getEntries() { - List result = new ArrayList<>(); - for (JaxbEntryBinding entry : this.delegate.getEntry()) { - result.add(new JaxbEntry(database, entry)); - } - return result; - } - - @Override - public int getEntriesCount() { - return this.delegate.getEntry().size(); - } - - @Override - public JaxbEntry addEntry(JaxbEntry entry) { - if (this.database != entry.database) { - throw new IllegalStateException("Must be from same database"); - } - if (entry.getParent() != null) { - entry.getParent().removeEntry(entry); - } - delegate.getEntry().add(entry.delegate); - entry.delegate.parent = this.delegate; - touch(); - return entry; - } - - @Override - public JaxbEntry removeEntry(JaxbEntry entry) { - delegate.getEntry().remove(entry.delegate); - entry.delegate.parent = null; - return entry; - } - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public void setName(String name) { - this.delegate.setName(name); - } - - @Override - public UUID getUuid() { - return this.delegate.getUUID(); - } - - @Override - public JaxbIcon getIcon() { - return new JaxbIcon(this.delegate.getIconID()); - } - - @Override - public void setIcon(JaxbIcon icon) { - this.delegate.setIconID(icon.getIndex()); - } - - @NotNull - @Override - public JaxbDatabase getDatabase() { - return database; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - JaxbGroup that = (JaxbGroup) o; - - return database.equals(that.database) && delegate.equals(that.delegate); - } - - private void touch() { - this.delegate.getTimes().setLastModificationTime(new Date()); - this.database.setDirty(true); - } -} 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 deleted file mode 100644 index 188af88b..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabase.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import com.sun.xml.txw2.output.IndentingXMLStreamWriter; -import org.apache.commons.codec.binary.Base64; -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.SerializableDatabase; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.jaxb.base.ValueBinding; -import org.linguafranca.pwdb.kdbx.jaxb.binding.*; -import org.linguafranca.pwdb.security.StreamEncryptor; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class JaxbSerializableDatabase implements SerializableDatabase { - - private final ObjectFactory objectFactory = new ObjectFactory(); - protected KeePassFile keePassFile; - private StreamEncryptor encryption; - - public JaxbSerializableDatabase() { - - } - - public JaxbSerializableDatabase(KeePassFile keePassFile) { - this.keePassFile = keePassFile; - } - - public static void addBinary(KeePassFile keePassFile, ObjectFactory objectFactory, int index, byte[] value) { - // create a new binary to put in the store - Binaries.Binary newBin = objectFactory.createBinariesBinary(); - newBin.setID(index); - newBin.setValue(Helpers.zipBinaryContent(value)); - newBin.setCompressed(true); - if (keePassFile.getMeta().getBinaries() == null) { - keePassFile.getMeta().setBinaries(objectFactory.createBinaries()); - } - keePassFile.getMeta().getBinaries().getBinary().add(newBin); - } - - @Override - public JaxbSerializableDatabase load(InputStream inputStream) { - try { - JAXBContext jc = JAXBContext.newInstance(KeePassFile.class, ValueBinding.class); - Unmarshaller u = jc.createUnmarshaller(); - u.setListener(new Unmarshaller.Listener() { - @Override - public void afterUnmarshal(Object target, Object parent) { - if (target instanceof StringField.Value) { - StringField.Value value = (StringField.Value) target; - if (value.getProtected() != null && value.getProtected()) { - byte[] encrypted = Base64.decodeBase64(value.getValue().getBytes()); - String decrypted = new String(encryption.decrypt(encrypted), StandardCharsets.UTF_8); - value.setValue(decrypted); - value.setProtected(null); - value.protectOnOutput=true; - } - } - if (target instanceof JaxbGroupBinding && (parent instanceof JaxbGroupBinding)) { - ((JaxbGroupBinding) target).parent = ((JaxbGroupBinding) parent); - } - if (target instanceof JaxbEntryBinding && (parent instanceof JaxbGroupBinding)) { - ((JaxbEntryBinding) target).parent = ((JaxbGroupBinding) parent); - } - } - }); - keePassFile = (KeePassFile) u.unmarshal(inputStream); - return this; - } catch (JAXBException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void save(OutputStream outputStream) { - - try { - JAXBContext jc = JAXBContext.newInstance(KeePassFile.class); - Marshaller marshaller = jc.createMarshaller(); - marshaller.setListener(createMarshallerListener(getToEncrypt())); - - XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance(); - XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(outputStream); - - // this tidies up output of boolean ="False" attributes and indentation, which is incorrect - // in marshaller and also uses tabs rather than spaces, which is more economical - IndentingXMLStreamWriter writer = new IndentingXMLStreamWriter(xmlStreamWriter){ - - @Override - public void writeStartDocument() throws XMLStreamException { - setIndentStep("\t"); - super.writeStartDocument(); - } - - @Override - public void writeAttribute(String localName, String value) throws XMLStreamException { - if (localName.equals("ProtectInMemory")) { - return; - } - if (localName.equals("Protected")) { - if (!value.equalsIgnoreCase("true")){ - return; - } - } - super.writeAttribute(localName, value); - } - }; - marshaller.marshal(keePassFile, writer); - - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - /** - * Creates a listener for marshalling that will rewrite fields that need encryption - */ - private Marshaller.Listener createMarshallerListener(List toEncrypt) { - return new Marshaller.Listener() { - String savedValue=""; - - // this changes the content on save, so we need to change it back again, see comment below - @Override - public void beforeMarshal(Object source) { - if (source instanceof StringField) { - StringField field = (StringField) source; - if (toEncrypt.contains(field.getKey()) || field.getValue().protectOnOutput) { - savedValue = field.getValue().getValue(); - byte [] encrypted = encryption.encrypt(field.getValue().getValue().getBytes(StandardCharsets.UTF_8)); - byte [] base64Encoded = Base64.encodeBase64(encrypted); - field.getValue().setValue(new String(base64Encoded)); - field.getValue().setProtected(true); - } else { - field.getValue().setProtected(false); - } - field.getValue().setProtectInMemory(false); - } - } - - // turns out that undoing the content change we made in beforeMarshal is the easiest way of doing this - // after a couple of days of looking at it. Making a clone before serialization - // is not practical and creating an adapter is not practical either, believe me, I tried. - // That said, if you are a JAXB whizz, and you know better ... - @Override - public void afterMarshal(Object source) { - if (source instanceof StringField) { - StringField field = (StringField) source; - if (field.getValue().getProtected()) { - field.getValue().setValue(savedValue); - field.getValue().setProtected(false); - } - } - } - }; - } - - /** - * Create a list of names of properties that should be encrypted by default - */ - @NotNull - private List getToEncrypt() { - final List toEncrypt = new ArrayList<>(); - if (keePassFile.getMeta().getMemoryProtection().getProtectTitle()) { - toEncrypt.add(org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_TITLE); - } - if (keePassFile.getMeta().getMemoryProtection().getProtectURL()) { - toEncrypt.add(org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_URL); - } - if (keePassFile.getMeta().getMemoryProtection().getProtectUserName()) { - toEncrypt.add(org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_USER_NAME); - } - if (keePassFile.getMeta().getMemoryProtection().getProtectPassword()) { - toEncrypt.add(org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_PASSWORD); - } - if (keePassFile.getMeta().getMemoryProtection().getProtectNotes()) { - toEncrypt.add(org.linguafranca.pwdb.Entry.STANDARD_PROPERTY_NAME_NOTES); - } - return toEncrypt; - } - - - @Override - public StreamEncryptor getEncryption() { - return encryption; - } - - @Override - public void setEncryption(StreamEncryptor encryption) { - this.encryption = encryption; - } - - @Override - public byte[] getHeaderHash() { - return keePassFile.getMeta().getHeaderHash(); - } - - @Override - public void setHeaderHash(byte[] hash) { - keePassFile.getMeta().setHeaderHash(hash); - } - - @Override - public void addBinary(int index, byte[] value) { - addBinary(keePassFile, objectFactory, index, value); - } - - @Override - public byte[] getBinary(int index) { - return keePassFile.getMeta().getBinaries().getBinary().get(index).getValue(); - } - - @Override - public int getBinaryCount() { - if (Objects.isNull(keePassFile.getMeta().getBinaries())) { - return 0; - } - return keePassFile.getMeta().getBinaries().getBinary().size(); - } - - public KeePassFile getKeePassFile() { - return keePassFile; - } - - public void setKeePassFile(KeePassFile keypassFile) { - this.keePassFile = keypassFile; - } -} 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 deleted file mode 100644 index a89fc2a2..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/base/ValueBinding.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.linguafranca.pwdb.kdbx.jaxb.base; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlValue; - -/** - * Ancestor class of StringField.Value, providing a way of flagging protection - * separate from @Protected and @ProtectInMemory - */ -@XmlAccessorType(XmlAccessType.FIELD) -public abstract class ValueBinding { - - @XmlTransient - public boolean protectOnOutput; - - @XmlValue - public String value; - - public String getValue(){ - return value; - } - - public void setValue(String string){ - value = string; - } - -} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/DateAdapter.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/DateAdapter.java deleted file mode 100644 index da5f78b3..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/DateAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb.util; - -import org.linguafranca.pwdb.kdbx.Helpers; - -import java.util.Date; - -/** - * Used for loading the database template which contains placeholders. - * @author jo - */ -public class DateAdapter { - public static String toString(Date date) { - return Helpers.fromDate(date); - } - - public static Date fromString(String string) { - // this is found in the base template - if (string.equals("${creationDate}")) { - return new Date(); - } - return Helpers.toDate(string); - } -} diff --git a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/JaxbUtil.java b/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/JaxbUtil.java deleted file mode 100644 index 7eb700f1..00000000 --- a/jaxb/src/main/java/org/linguafranca/pwdb/kdbx/jaxb/util/JaxbUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.linguafranca.pwdb.kdbx.jaxb.util; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.util.JAXBSource; -import javax.xml.namespace.QName; - -public class JaxbUtil { - - // https://stackoverflow.com/questions/879453/how-to-make-a-deep-copy-of-jaxb-object-like-xmlbean-xmlobject-copy - // however it does not copy transients which we need it to, so we don't use it - public static T deepCopyJAXB(T object, Class clazz) { - try { - JAXBContext jaxbContext = JAXBContext.newInstance(clazz); - JAXBElement contentObject = new JAXBElement(new QName(clazz.getSimpleName()), clazz, object); - JAXBSource source = new JAXBSource(jaxbContext, contentObject); - return jaxbContext.createUnmarshaller().unmarshal(source, clazz).getValue(); - } catch (JAXBException e) { - throw new RuntimeException(e); - } - } - - public static T deepCopyJAXB(T object) { - if(object==null) throw new RuntimeException("Can't guess at class"); - return deepCopyJAXB(object, (Class) object.getClass()); - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV3Test.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV3Test.java deleted file mode 100644 index d014cc9b..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV3Test.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class JaxbBinaryPropertyV3Test extends BinaryPropertyChecks { - - public JaxbBinaryPropertyV3Test() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx"); - database = JaxbDatabase.load(new KdbxCreds("123".getBytes()),inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials,outputStream); - } - - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JaxbDatabase.load(credentials, inputStream); - } - - @Override - public Database newDatabase() { - return new JaxbDatabase(); - } - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV4Test.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV4Test.java deleted file mode 100644 index 9a9fcd26..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbBinaryPropertyV4Test.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.junit.Ignore; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -@Ignore -public class JaxbBinaryPropertyV4Test extends BinaryPropertyChecks { - - public JaxbBinaryPropertyV4Test() throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-Argon2-Attachment.kdbx"); - database = JaxbDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - StreamFormat sf = new KdbxStreamFormat(new KdbxHeader(4)); - database.save(sf, credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JaxbDatabase.load(credentials, inputStream); - } - - @Override - public Database newDatabase() { - return new JaxbDatabase(); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} \ No newline at end of file diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbLoaderTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbLoaderTest.java deleted file mode 100644 index c3a83ced..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbLoaderTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.linguafranca.pwdb.checks.DatabaseLoaderChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.IOException; -import java.io.InputStream; - -/** - * @author jo - */ -public class JaxbLoaderTest extends DatabaseLoaderChecks { - - public JaxbLoaderTest() throws IOException { - // get an input stream from kdbx file - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - // file has password credentials - KdbxCreds credentials = new KdbxCreds("123".getBytes()); - // open database. DomDatabaseWrapper is so-called, since it wraps - // a W3C DOM, populated from the KeePass XML, and presents it - // through a org.linguafranca.keepass.Database interface. - super.database = JaxbDatabase.load(credentials, inputStream); - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbPropertyValueTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbPropertyValueTest.java deleted file mode 100644 index 06f0d611..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbPropertyValueTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Jo Rabin - * - * 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.jaxb; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.checks.PropertyValueChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class JaxbPropertyValueTest extends PropertyValueChecks { - - public JaxbPropertyValueTest() throws IOException { - super(false); - } - - - @Override - public Database createDatabase() throws IOException { - return new JaxbDatabase(); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return JaxbDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbRecycleBinTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbRecycleBinTest.java deleted file mode 100644 index c837e3c6..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbRecycleBinTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.linguafranca.pwdb.kdbx.jaxb; - -import org.linguafranca.pwdb.checks.RecycleBinChecks; - -/** - * @author jo - */ -public class JaxbRecycleBinTest extends RecycleBinChecks { - public JaxbRecycleBinTest() { - database = new JaxbDatabase(); - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSaveAndReloadTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSaveAndReloadTest.java deleted file mode 100644 index 37eb4341..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSaveAndReloadTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.checks.SaveAndReloadChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class JaxbSaveAndReloadTest extends SaveAndReloadChecks { - @Override - public JaxbDatabase getDatabase() { - return new JaxbDatabase(); - } - @Override - public JaxbDatabase getDatabase(String name, Credentials credentials) throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream(name); - return JaxbDatabase.load(credentials, inputStream); - } - - @Override - public void saveDatabase(JaxbDatabase database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public JaxbDatabase loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - return JaxbDatabase.load(credentials, inputStream); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - - - @Override - public boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2) { - KdbxHeader h1 = (KdbxHeader) s1.getStreamConfiguration(); - KdbxHeader h2 = (KdbxHeader) s1.getStreamConfiguration(); - return (h1.getVersion() == h2.getVersion() && - h1.getProtectedStreamAlgorithm().equals(h2.getProtectedStreamAlgorithm()) && - h1.getKeyDerivationFunction().equals(h2.getKeyDerivationFunction()) && - h1.getCipherAlgorithm().equals(h2.getCipherAlgorithm())); - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabaseTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabaseTest.java deleted file mode 100644 index 97921e51..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbSerializableDatabaseTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.junit.Test; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.Visitor; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class JaxbSerializableDatabaseTest { - - static PrintStream printStream = getTestPrintStream(); - - @Test - public void createEmptyDatabase() throws Exception { - JaxbDatabase db = JaxbDatabase.createEmptyDatabase(); - db.save(new StreamFormat.None(), new KdbxCreds.None(), printStream); - } - @Test - public void loadXml() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - JaxbDatabase database = JaxbDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - database.save(new StreamFormat.None(), new KdbxCreds.None(), printStream); - } - -} \ No newline at end of file diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbTest.java deleted file mode 100644 index 60aa341d..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.jaxb; - -import org.junit.Test; -import org.linguafranca.pwdb.kdbx.jaxb.binding.KeePassFile; -import org.linguafranca.pwdb.kdbx.jaxb.binding.StringField; - -import javax.xml.bind.*; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class JaxbTest { - static PrintStream printStream = getTestPrintStream(); - - @Test - public void unmarshal() throws JAXBException { - JAXBContext jc = JAXBContext.newInstance(KeePassFile.class); - Unmarshaller u = jc.createUnmarshaller(); - u.setEventHandler(new ValidationEventHandler() { - @Override - public boolean handleEvent(ValidationEvent event) { - printStream.println(event.getLocator().getLineNumber() +": "+ event.getMessage()); - return true; - } - }); - u.setListener(new Unmarshaller.Listener() { - @Override - public void afterUnmarshal(Object target, Object parent) { - if (target instanceof StringField.Value) { - StringField.Value value = (StringField.Value) target; - printStream.println(value.getValue()); - } - super.afterUnmarshal(target, parent); - } - }); - KeePassFile kpf = (KeePassFile) u.unmarshal(getClass().getClassLoader().getResourceAsStream("ExampleDatabase.xml")); - printStream.println(kpf.getMeta().getDatabaseDescription()); - - } -} diff --git a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbV4LoadTest.java b/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbV4LoadTest.java deleted file mode 100644 index 66ecd37e..00000000 --- a/jaxb/src/test/java/org/linguafranca/pwdb/kdbx/jaxb/JaxbV4LoadTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.linguafranca.pwdb.kdbx.jaxb; - -import org.junit.Test; -import org.linguafranca.pwdb.Entry; -import org.linguafranca.pwdb.Visitor; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class JaxbV4LoadTest { - - static PrintStream printStream = getTestPrintStream(); - - @Test - public void loadKdbxV4() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); - JaxbDatabase database = JaxbDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - // test what happens to dates in V4 - database.visit(new Visitor.Default(){ - @Override - public void visit(Entry entry) { - printStream.println(entry.getCreationTime()); - } - }); - } - -} diff --git a/kdb/pom.xml b/kdb/pom.xml index f78d55f9..25668d44 100644 --- a/kdb/pom.xml +++ b/kdb/pom.xml @@ -1,32 +1,46 @@ + + - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT + KeePassJava2.parent + mh.keepass + 1.0.0.1 ../pom.xml 4.0.0 - KeePassJava2-kdb + KeePassJava2.kdb KeePassJava2 :: KDB - Contains an implementaion that can load KDB files. + KeePass KDB file support - org.linguafranca.pwdb + mh.keepass database ${project.version} + - org.jetbrains - annotations - 24.1.0 - - - org.linguafranca.pwdb - test + mh.keepass + database ${project.version} + test-jar test diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbCredentials.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbCredentials.java index b578d062..1e86f5c6 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbCredentials.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbCredentials.java @@ -1,22 +1,23 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; -import com.google.common.io.ByteStreams; +import org.apache.commons.io.IOUtils; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.security.Encryption; import org.bouncycastle.util.encoders.Hex; @@ -63,7 +64,7 @@ public KeyFile(byte[] password, InputStream inputStream) { md.update(pwKey); try { - byte [] keyFileData = ByteStreams.toByteArray(inputStream); + byte [] keyFileData = IOUtils.toByteArray(inputStream); if (keyFileData.length == 64) { keyFileData = Hex.decode(keyFileData); key = md.digest(keyFileData); diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbDatabase.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbDatabase.java index fbb8634e..7529dbee 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbDatabase.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbDatabase.java @@ -1,23 +1,24 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; import org.linguafranca.pwdb.*; -import org.linguafranca.pwdb.base.AbstractDatabase; +import org.linguafranca.pwdb.abstractdb.AbstractDatabase; import java.io.IOException; import java.io.InputStream; @@ -30,7 +31,7 @@ * * @author jo */ -public class KdbDatabase extends AbstractDatabase { +public class KdbDatabase extends AbstractDatabase { private String description; private final KdbGroup rootGroup; @@ -49,6 +50,14 @@ public static KdbDatabase load(Credentials credentials, InputStream inputStream) return KdbSerializer.createKdbDatabase(credentials, new KdbHeader(), inputStream); } + public static KdbDatabase loadNx(Credentials credentials, InputStream inputStream) { + try { + return KdbDatabase.load(credentials, inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** * Primarily intended for finding the parent of an Entry, when deserializing KDB data. * @@ -170,6 +179,7 @@ public boolean supportsBinaryProperties() { return false; } + @SuppressWarnings("RedundantMethodOverride") @Override public boolean supportsRecycleBin() { return false; 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..9b3a8603 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbEntry.java @@ -1,26 +1,27 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.linguafranca.pwdb.Entry; import org.linguafranca.pwdb.Icon; -import org.linguafranca.pwdb.base.AbstractEntry; +import org.linguafranca.pwdb.abstractdb.AbstractEntry; import java.util.ArrayList; import java.util.Date; @@ -32,13 +33,14 @@ * * @author jo */ -public class KdbEntry extends AbstractEntry { +public class KdbEntry extends AbstractEntry { + public KdbDatabase database; KdbGroup parent; private UUID uuid = UUID.randomUUID(); private String title = ""; private String url = ""; private String notes = ""; - private KdbIcon icon = new KdbIcon(0); + private Icon icon = new KdbIcon(0); private String username = ""; private String password = ""; private Date creationTime = new Date((System.currentTimeMillis())); // to the next lower second @@ -50,7 +52,7 @@ public class KdbEntry extends AbstractEntry { +public class KdbGroup extends AbstractGroup { private boolean root; private KdbGroup parent; protected KdbDatabase database; private UUID uuid = UUID.randomUUID(); private String name = ""; - private KdbIcon icon = new KdbIcon(0); - private List groups = new ArrayList<>(); - private List entries = new ArrayList<>(); + private Icon icon = new KdbIcon(0); + protected List groups = new ArrayList<>(); + protected List entries = new ArrayList<>(); private Date creationTime; private Date lastModificationTime; private Date lastAccessTime; @@ -55,7 +54,8 @@ public class KdbGroup extends AbstractGroup getEntries() { + public List getEntries() { return new ArrayList<>(entries); } @@ -82,7 +83,8 @@ public int getEntriesCount() { } @Override - public KdbEntry addEntry(KdbEntry entry) { + public KdbEntry addEntry(Entry e) { + KdbEntry entry = ((KdbEntry) e); KdbGroup entryParent = entry.parent; if (entryParent != null) { entryParent.removeEntry(entry); @@ -93,7 +95,8 @@ public KdbEntry addEntry(KdbEntry entry) { } @Override - public KdbEntry removeEntry(KdbEntry entry) { + public KdbEntry removeEntry(Entry e) { + KdbEntry entry = ((KdbEntry) e); entries.remove(entry); entry.parent = null; return entry; @@ -144,7 +147,7 @@ public KdbGroup getParent() { } @Override - public void setParent(KdbGroup parent) { + public void setParent(Group parent) { parent.addGroup(this); } @@ -158,17 +161,17 @@ void setUuid(UUID uuid) { } @Override - public KdbIcon getIcon() { + public Icon getIcon() { return icon; } @Override - public void setIcon(KdbIcon icon) { + public void setIcon(Icon icon) { this.icon = icon; } @Override - public List getGroups() { + public List getGroups() { return new ArrayList<>(groups); } @@ -210,7 +213,6 @@ public String toString() { return getPath() + String.format(" (%s) %s [%d]", uuid.toString(), time, flags); } - @NotNull public KdbDatabase getDatabase() { return database; } diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbHeader.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbHeader.java index 976bf318..b26384e4 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbHeader.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbHeader.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbIcon.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbIcon.java index 911be794..e2077b11 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbIcon.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbIcon.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; diff --git a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbSerializer.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbSerializer.java index 51e357de..a62207fa 100644 --- a/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbSerializer.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/KdbSerializer.java @@ -1,22 +1,23 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; -import com.google.common.io.LittleEndianDataInputStream; +import org.apache.commons.io.input.SwappedDataInputStream; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.Group; import org.linguafranca.pwdb.security.Encryption; @@ -65,7 +66,7 @@ private KdbSerializer() { */ public static KdbDatabase createKdbDatabase(Credentials credentials, KdbHeader kdbHeader, InputStream inputStream) throws IOException { // everything is little endian - DataInput dataInput = new LittleEndianDataInputStream(inputStream); + DataInput dataInput = new SwappedDataInputStream(inputStream); // check the magic values to verify file type checkSignature(dataInput); // load the header @@ -79,11 +80,11 @@ public static KdbDatabase createKdbDatabase(Credentials credentials, KdbHeader k DigestInputStream digestInputStream = new DigestInputStream(decryptedInputStream, digest); // Start the dataInput at wherever we have got to in the stream - dataInput = new LittleEndianDataInputStream(digestInputStream); + dataInput = new SwappedDataInputStream(digestInputStream); // read the decrypted serialized form of all groups KdbDatabase kdbDatabase = new KdbDatabase(); - KdbGroup lastGroup = (KdbGroup) kdbDatabase.getRootGroup(); + KdbGroup lastGroup = kdbDatabase.getRootGroup(); for (long group = 0; group < kdbHeader.getGroupCount(); group++) { lastGroup = deserializeGroup(lastGroup, dataInput); } @@ -105,8 +106,8 @@ public static KdbDatabase createKdbDatabase(Credentials credentials, KdbHeader k } private static void setDatabase(KdbDatabase kdbDatabase, KdbGroup group) { - for (KdbGroup child: group.getGroups()){ - ((KdbGroup) child).database = kdbDatabase; + for (KdbGroup child: group.groups){ + child.database = kdbDatabase; setDatabase(kdbDatabase, child); } } @@ -119,7 +120,7 @@ private static void setDatabase(KdbDatabase kdbDatabase, KdbGroup group) { * Check the signature of a data source * * @param dataInput the source to check - * @throws IOException + * @throws IOException if an integer could not be read from buffer * @throws IllegalStateException if the signature does not match */ public static void checkSignature(DataInput dataInput) throws IOException { @@ -133,7 +134,7 @@ public static void checkSignature(DataInput dataInput) throws IOException { * * @param kdbHeader a header to populate with relevant values * @param dataInput a source of data - * @throws IOException + * @throws IOException if there is a problem reading from the source */ private static void deserializeHeader(KdbHeader kdbHeader, DataInput dataInput) throws IOException { kdbHeader.setFlags(dataInput.readInt()); @@ -167,7 +168,7 @@ private static void deserializeHeader(KdbHeader kdbHeader, DataInput dataInput) * @param lastGroup the last group loaded from this source, or the root group if none * @param dataInput a source of data * @return a new KdbxGroup - * @throws IOException + * @throws IOException if there is a problem reading from the source */ private static KdbGroup deserializeGroup(KdbGroup lastGroup, DataInput dataInput) throws IOException { int fieldType; @@ -208,7 +209,7 @@ private static KdbGroup deserializeGroup(KdbGroup lastGroup, DataInput dataInput group.setFlags(readInt(dataInput)); break; default: - throw new IllegalStateException("Unknown field type " + String.valueOf(fieldType)); + throw new IllegalStateException("Unknown field type " + fieldType); } } dataInput.readInt(); @@ -220,11 +221,12 @@ private static KdbGroup deserializeGroup(KdbGroup lastGroup, DataInput dataInput * * @param database a database to insert the entry into * @param dataInput a source of data - * @throws IOException + * @throws IOException if there is a problem reading from the source */ private static void deserializeEntry(KdbDatabase database, DataInput dataInput) throws IOException { int fieldType; KdbEntry entry = new KdbEntry(); + entry.database = database; while ((fieldType = dataInput.readUnsignedShort()) != 0xFFFF) { switch (fieldType) { case 0x0000: @@ -317,10 +319,11 @@ private static KdbGroup computeParentGroup(KdbGroup lastGroup, int level) { throw new IllegalStateException("Could not determine parent group from level supplied"); } // working variable holding current candidate parent - KdbGroup candidateParent = (KdbGroup) lastGroup.getParent(); + KdbGroup candidateParent = lastGroup.getParent(); // work our way up through the parents till we find one + //noinspection DataFlowIssue - you know - if it's broken it's broken while (level <= candidateParent.computedLevel()) { - candidateParent = ((KdbGroup) candidateParent.getParent()); + candidateParent = (candidateParent.getParent()); } return candidateParent; } @@ -420,6 +423,7 @@ private static byte[] readBuffer(DataInput dataInput) throws IOException { return buffer; } + @SuppressWarnings("UnusedReturnValue") private static byte[] readExtData(DataInput dataInput) throws IOException { int size = dataInput.readInt(); byte[] buffer = new byte[size]; diff --git a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseTest.java b/kdb/src/main/java/org/linguafranca/pwdb/kdb/package-info.java similarity index 50% rename from jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseTest.java rename to kdb/src/main/java/org/linguafranca/pwdb/kdb/package-info.java index cbaaa291..02f1b8de 100644 --- a/jackson/src/test/java/org/linguafranca/pwdb/JacksonDatabaseTest.java +++ b/kdb/src/main/java/org/linguafranca/pwdb/kdb/package-info.java @@ -1,35 +1,28 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; - -import java.io.IOException; - -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; -import org.linguafranca.pwdb.kdbx.jackson.JacksonDatabase; - -public class JacksonDatabaseTest extends BasicDatabaseChecks { - - public JacksonDatabaseTest() throws IOException { - - } +/** + * This package contains the interfaces that define the org.linguafranca Database API. + *

+ * The API is designed to be a simple, low-level, read-write API for password databases. + *

+ * Class instances of the interfaces are contained in the sub-packages of this package. + */ +@NullMarked +package org.linguafranca.pwdb.kdb; - @Override - public Database createDatabase() throws IOException { - return new JacksonDatabase(); - } - -} +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseLoaderTest.java b/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseLoaderTest.java deleted file mode 100644 index d375e45f..00000000 --- a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseLoaderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.kdb; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.checks.DatabaseLoaderChecks; - -import java.io.IOException; -import java.io.InputStream; - -/** - * @author jo - */ -public class KdbDatabaseLoaderTest extends DatabaseLoaderChecks { - - public KdbDatabaseLoaderTest() throws IOException { - // get an input stream from KDB file - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdb"); - // file has password credentials - Credentials credentials = new KdbCredentials.Password("123".getBytes()); - // open database. - super.database = KdbDatabase.load(credentials, inputStream); - super.skipDateCheck = true; - } -} diff --git a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseTest.java b/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseTest.java index 84d08972..3929703e 100644 --- a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseTest.java +++ b/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbDatabaseTest.java @@ -1,46 +1,94 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.kdb; -import org.junit.Test; -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; +import org.junit.jupiter.api.parallel.Execution; import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.Visitor; +import org.linguafranca.pwdb.test.DatabaseTestBase; +import org.linguafranca.pwdb.test.GroupsAndEntriesTest; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.test.Test123Test; import java.io.IOException; +import java.io.InputStream; +import java.util.function.Supplier; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.util.TestUtil.getTestPrintStream; /** * @author Jo */ -public class KdbDatabaseTest extends BasicDatabaseChecks { +@Execution(CONCURRENT) +public class KdbDatabaseTest implements GroupsAndEntriesTest, Test123Test { + + public static final Supplier kdbxFunc = ()-> new DatabaseTestBase(KdbDatabase::new, KdbDatabase::loadNx, Database::saveNx, KdbCredentials.Password::new); + + public Supplier getDbFunc() { + return kdbxFunc; + } + @Override - public Database createDatabase() throws IOException { - return new KdbDatabase(); + public boolean getSkipDateCheck() { + return true; } - public KdbDatabaseTest() throws IOException { - super(); + @Override + public String getFileName(){ + return "test123.kdb"; } @Test public void supportedFunctionalityTest(){ - assertFalse(database.supportsBinaryProperties()); - assertFalse(database.supportsNonStandardPropertyNames()); - assertFalse(database.supportsRecycleBin()); + DatabaseTestBase testBase = getDbFunc().get(); + + assertFalse(testBase.getDatabase().supportsBinaryProperties()); + assertFalse(testBase.getDatabase().supportsNonStandardPropertyNames()); + assertFalse(testBase.getDatabase().supportsRecycleBin()); + assertFalse(testBase.getDatabase().supportsPropertyValueStrategy()); + } + + @Test + public void testCreateKdbDatabase() throws Exception { + Database database; + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdb")) { + assertNotNull(inputStream); + database = KdbDatabase.load(new KdbCredentials.Password(KdbConsts.PASSWORD_BYTES), inputStream); + } + database.visit(new Visitor.Print(getTestPrintStream())); + } + + @Test + public void openKdbWithKeyFile() throws IOException { + KdbCredentials creds; + try (InputStream key = getClass().getClassLoader().getResourceAsStream("kdb.key")) { + assertNotNull(key); + creds = new KdbCredentials.KeyFile(KdbConsts.PASSWORD_BYTES, key); + } + KdbDatabase db; + try (InputStream is = getClass().getClassLoader().getResourceAsStream("kdbwithkey.kdb")) { + assertNotNull(is); + db = KdbDatabase.load(creds, is); + } + assertEquals(1, db.getRootGroup().getGroupsCount()); + assertEquals("General", db.getRootGroup().getGroups().get(0).getName()); } } diff --git a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbSerializerTest.java b/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbSerializerTest.java deleted file mode 100644 index 06254062..00000000 --- a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KdbSerializerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.kdb; - -import org.junit.Test; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.Visitor; - -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class KdbSerializerTest { - - static PrintStream printStream = getTestPrintStream(); - - - @Test - public void testCreateKdbDatabase() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdb"); - Database database = KdbDatabase.load(new KdbCredentials.Password("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - } -} \ No newline at end of file diff --git a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KeyFileTest.java b/kdb/src/test/java/org/linguafranca/pwdb/kdb/KeyFileTest.java deleted file mode 100644 index c78ba10a..00000000 --- a/kdb/src/test/java/org/linguafranca/pwdb/kdb/KeyFileTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.linguafranca.pwdb.kdb; - -import org.junit.Test; - -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.assertEquals; - -/** - * @author jo - */ -public class KeyFileTest { - - @Test - public void openKdbWithKeyFile() throws IOException { - InputStream key = getClass().getClassLoader().getResourceAsStream("kdb.key"); - KdbCredentials creds = new KdbCredentials.KeyFile("123".getBytes(), key); - InputStream is = getClass().getClassLoader().getResourceAsStream("kdbwithkey.kdb"); - KdbDatabase db = KdbDatabase.load(creds, is); - assertEquals(1, db.getRootGroup().getGroupsCount()); - assertEquals("General", db.getRootGroup().getGroups().get(0).getName()); - } -} \ No newline at end of file diff --git a/kdb/src/test/resources/junit-platform.properties b/kdb/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..2115a2f1 --- /dev/null +++ b/kdb/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = true diff --git a/test/src/main/resources/kdb.key b/kdb/src/test/resources/kdb.key similarity index 100% rename from test/src/main/resources/kdb.key rename to kdb/src/test/resources/kdb.key diff --git a/test/src/main/resources/kdbwithkey.kdb b/kdb/src/test/resources/kdbwithkey.kdb similarity index 100% rename from test/src/main/resources/kdbwithkey.kdb rename to kdb/src/test/resources/kdbwithkey.kdb diff --git a/kdb/src/test/resources/test123.kdb b/kdb/src/test/resources/test123.kdb new file mode 100755 index 00000000..0cfad3b4 Binary files /dev/null and b/kdb/src/test/resources/test123.kdb differ diff --git a/kdbx-database/pom.xml b/kdbx-database/pom.xml new file mode 100644 index 00000000..62d20644 --- /dev/null +++ b/kdbx-database/pom.xml @@ -0,0 +1,51 @@ + + + + + KeePassJava2.parent + mh.keepass + 1.0.0.1 + ../pom.xml + + 4.0.0 + + KeePassJava2.kdbx.database + KeePassJava2 :: KDBX-Database + KeePass KDBX file support + + + + mh.keepass + KeePassJava2.kdbx.io + ${project.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.21.2 + + + mh.keepass + database + ${project.version} + test-jar + test + + + diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonDatabase.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxDatabase.java similarity index 58% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonDatabase.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxDatabase.java index d17615d5..0771e102 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonDatabase.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxDatabase.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -19,8 +20,8 @@ import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.StreamConfiguration; import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxStreamFormat; import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile; import org.linguafranca.pwdb.protect.ProtectedDatabase; @@ -32,23 +33,23 @@ import java.util.Objects; import java.util.UUID; -import static org.linguafranca.pwdb.kdbx.jackson.JacksonSerializableDatabase.createEmptyDatabase; +import static org.linguafranca.pwdb.kdbx.jackson.KdbxSerializableDatabase.createEmptyDatabase; -public class JacksonDatabase extends ProtectedDatabase { +public class KdbxDatabase extends ProtectedDatabase { KeePassFile keePassFile; StreamFormat streamFormat; - public JacksonDatabase() throws IOException { + public KdbxDatabase() { this(createEmptyDatabase(), null); } - public JacksonDatabase(KeePassFile file, StreamFormat streamFormat) { + public KdbxDatabase(KeePassFile file, StreamFormat streamFormat) { try { keePassFile = file; keePassFile.root.group.database = this; this.streamFormat = streamFormat; - JacksonSerializableDatabase.fixUp(keePassFile.root.group); + fixUp(keePassFile.root.group); } catch (Exception e) { throw new IllegalStateException(e); } @@ -61,11 +62,11 @@ public JacksonDatabase(KeePassFile file, StreamFormat streamFormat) { * @return a new Database * @throws Exception on load failure */ - public static JacksonDatabase loadXml(InputStream inputStream) throws Exception { + public static KdbxDatabase loadXml(InputStream inputStream) throws Exception { - KeePassFile keePassFile = new JacksonSerializableDatabase().load(inputStream).keePassFile; + KeePassFile keePassFile = new KdbxSerializableDatabase().load(inputStream).keePassFile; keePassFile.root.group.uuid = UUID.randomUUID(); - return new JacksonDatabase(keePassFile, null); + return new KdbxDatabase(keePassFile, null); } /** @@ -75,19 +76,30 @@ public static JacksonDatabase loadXml(InputStream inputStream) throws Exception * @param inputStream where to load from * @return a new database */ - public static JacksonDatabase load(Credentials credentials, InputStream inputStream) throws IOException { - JacksonSerializableDatabase jsd = new JacksonSerializableDatabase(); + public static KdbxDatabase load(Credentials credentials, InputStream inputStream) throws IOException { + KdbxSerializableDatabase jsd = new KdbxSerializableDatabase(); StreamFormat streamFormat = new KdbxStreamFormat(); streamFormat.load(jsd, credentials, inputStream); - return new JacksonDatabase(jsd.keePassFile, streamFormat); + return new KdbxDatabase(jsd.keePassFile, streamFormat); + } + + /** + * Load kdbx file - avoiding checked exceptions + */ + public static KdbxDatabase loadNx(Credentials credentials, InputStream inputStream) { + try { + return load(credentials, inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** * Save the database with the same stream format that it was loaded with, or V4 * default if none - * + * * @param credentials credentials to use - * @param outputStream where to write to + * @param outputStream where to write to - closes stream */ @Override public void save(Credentials credentials, OutputStream outputStream) throws IOException { @@ -99,44 +111,44 @@ public void save(Credentials credentials, OutputStream outputStream) throws IOEx /** * Save the database with a choice of stream format - * + * * @param streamFormat the format to use * @param credentials credentials to use - * @param outputStream where to write to + * @param outputStream where to write to - call closes output stream */ @Override public void save(StreamFormat streamFormat, Credentials credentials, - OutputStream outputStream) throws IOException { - keePassFile.meta.generator = "KeePassJava2-Jackson"; - JacksonSerializableDatabase jacksonSerializableDatabase = new JacksonSerializableDatabase(this.keePassFile); - jacksonSerializableDatabase.setPropertyValueStrategy(this.getPropertyValueStrategy()); - streamFormat.save(jacksonSerializableDatabase, credentials, outputStream); - setDirty(false); + OutputStream outputStream) throws IOException { + keePassFile.meta.generator = "KeePassJava2-V3-Jackson"; + KdbxSerializableDatabase kdbxSerializableDatabase = new KdbxSerializableDatabase(this.keePassFile); + kdbxSerializableDatabase.setPropertyValueStrategy(this.getPropertyValueStrategy()); + streamFormat.save(kdbxSerializableDatabase, credentials, outputStream); + setDirty(false); } @Override - public JacksonGroup getRootGroup() { + public KdbxGroup getRootGroup() { return keePassFile.root.group; } @Override - public JacksonGroup newGroup() { - return JacksonGroup.createGroup(this); + public KdbxGroup newGroup() { + return KdbxGroup.createGroup(this); } @Override - public JacksonEntry newEntry() { - return JacksonEntry.createEntry(this); + public KdbxEntry newEntry() { + return KdbxEntry.createEntry(this); } @Override - public JacksonIcon newIcon() { - return new JacksonIcon(); + public KdbxIcon newIcon() { + return new KdbxIcon(); } @Override - public JacksonIcon newIcon(Integer integer) { - JacksonIcon ic = newIcon(); + public KdbxIcon newIcon(Integer integer) { + KdbxIcon ic = newIcon(); ic.setIndex(integer); return ic; } @@ -152,11 +164,11 @@ public void enableRecycleBin(boolean enable) { } @Override - public JacksonGroup getRecycleBin() { + public KdbxGroup getRecycleBin() { UUID recycleBinUuid = this.keePassFile.meta.recycleBinUUID; - JacksonGroup g = findGroup(recycleBinUuid); + KdbxGroup g = (KdbxGroup) findGroup(recycleBinUuid); if (g == null && isRecycleBinEnabled()) { - g = newGroup("Recycle Bin"); + g = (KdbxGroup) newGroup("Recycle Bin"); getRootGroup().addGroup(g); this.keePassFile.meta.recycleBinUUID = g.getUuid(); this.keePassFile.meta.recycleBinChanged = new Date(); @@ -164,6 +176,11 @@ public JacksonGroup getRecycleBin() { return g; } + @Override + public boolean supportsRecycleBin() { + return true; + } + @Override public String getName() { return keePassFile.meta.databaseName; @@ -188,21 +205,40 @@ public void setDescription(String s) { setDirty(true); } - @Override - public boolean shouldProtect(String s) { - return keePassFile.meta.memoryProtection.shouldProtect(s); - } - public List getBinaries() { + if (keePassFile.meta.binaries == null) { + keePassFile.createBinaries(); + } return keePassFile.meta.binaries; } public void addBinary(byte[] bytes, int index) { - JacksonSerializableDatabase.addBinary(this.keePassFile, index, bytes); + KdbxSerializableDatabase.addBinary(this.keePassFile, index, bytes); } public StreamFormat getStreamFormat() { return streamFormat; } + + /** + * On load add parents + * + * @param parent a parent to recurse + */ + static void fixUp(KdbxGroup parent) { + + for (KdbxGroup group : parent.groups) { + group.parent = parent; + group.database = parent.database; + fixUp(group); + } + + for (KdbxEntry entry : parent.entries) { + entry.database = parent.database; + entry.parent = parent; + } + } + + } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxEntry.java similarity index 73% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxEntry.java index f6eb7c57..7a85e2b8 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonEntry.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxEntry.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -21,19 +22,18 @@ import java.util.List; import java.util.UUID; -import org.jetbrains.annotations.NotNull; - +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Icon; import org.linguafranca.pwdb.PropertyValue; -import org.linguafranca.pwdb.base.AbstractEntry; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.abstractdb.AbstractEntry; +import org.linguafranca.pwdb.format.Helpers; import org.linguafranca.pwdb.kdbx.jackson.converter.Base64ToUUIDConverter; +import org.linguafranca.pwdb.kdbx.jackson.converter.BooleanToStringConverter; +import org.linguafranca.pwdb.kdbx.jackson.converter.StringToBooleanConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.UUIDToBase64Converter; import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile; import org.linguafranca.pwdb.kdbx.jackson.model.Times; -import org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses.AutoType; -import org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses.BinaryProperty; -import org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses.StringProperty; import static org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses.*; @@ -54,17 +54,44 @@ "foregroundColor", "backgroundColor", "overrideURL", + "previousParentGroup", "tags", "times", "string", "binary", "autoType", + "customData", "history", }) @JsonIgnoreProperties({"path", "username", "title", "notes", "url", "password"}) -public class JacksonEntry extends AbstractEntry { +public class KdbxEntry extends AbstractEntry { + + @JsonIgnore + KdbxDatabase database; + + @JsonIgnore + KdbxGroup parent; + + protected KdbxEntry() { + string = new ArrayList<>(); + binary = new ArrayList<>(); + times = new Times(); + uuid = UUID.randomUUID(); + iconID = 0; + } + + public static KdbxEntry createEntry(KdbxDatabase database) { + KdbxEntry result = new KdbxEntry(); + result.database = database; + result.parent = null; + // avoiding setProperty as it does a touch(); + for (String p : STANDARD_PROPERTY_NAMES) { + result.string.add(new StringProperty(p, database.getPropertyValueStrategy().newUnprotected().of(""))); + } + return result; + } @JacksonXmlProperty(localName = "UUID") @@ -88,10 +115,21 @@ public class JacksonEntry extends AbstractEntry(); - binary = new ArrayList<>(); - times = new Times(); - uuid = UUID.randomUUID(); - iconID = 0; - } - - public static JacksonEntry createEntry(JacksonDatabase database) { - JacksonEntry result = new JacksonEntry(); - result.database = database; - result.parent = null; - // avoiding setProperty as it does a touch(); - for (String p : STANDARD_PROPERTY_NAMES) { - result.string.add(new StringProperty(p, database.getPropertyValueStrategy().newUnprotected().of(""))); - } - return result; - } + @JacksonXmlProperty(localName = "History") /* Workaround jackson */ + protected KdbxHistory history; @Override @JsonIgnore @@ -146,14 +162,15 @@ public String getProperty(String s) { @Override @JsonIgnore - public void setProperty(String s, String s1) { + public KdbxEntry setProperty(String s, String s1) { StringProperty sp = getStringProperty(s, string); if (sp != null) { sp.setValue(database.getPropertyValueStrategy().newUnprotected().of(s1)); - return; + return this; } string.add(new StringProperty(s, database.getPropertyValueStrategy().newUnprotected().of(s1))); touch(); + return this; } @Override @@ -165,14 +182,39 @@ public PropertyValue getPropertyValue(String name) { @Override @JsonIgnore - public void setPropertyValue(String name, PropertyValue value) { + public KdbxEntry setPropertyValue(String name, PropertyValue value) { StringProperty sp = getStringProperty(name, string); if (sp != null) { sp.setValue(value); - return; + return this; } string.add(new StringProperty(name, value)); touch(); + return this; + } + + @Override + @JsonIgnore + public Entry addProperty(String name, byte[] value){ + PropertyValue.Factory f = database.getPropertyValueStrategy().getFactoryFor(name); + this.setPropertyValue(name, f.of(value)); + return this; + } + + @Override + @JsonIgnore + public Entry addProperty(String name, char[] value){ + PropertyValue.Factory f = database.getPropertyValueStrategy().getFactoryFor(name); + this.setPropertyValue(name, f.of(value)); + return this; + } + + @Override + @JsonIgnore + public Entry addProperty(String name, CharSequence value){ + PropertyValue.Factory f = database.getPropertyValueStrategy().getFactoryFor(name); + this.setPropertyValue(name, f.of(value)); + return this; } @Override @@ -272,25 +314,53 @@ public List getBinaryPropertyNames() { } @Override - public JacksonGroup getParent() { + public KdbxDatabase getDatabase() { + return database; + } + + @Override + public KdbxGroup getParent() { return parent; } @Override - public @NotNull UUID getUuid() { + public UUID getUuid() { return uuid; } @Override - public JacksonIcon getIcon() { - return new JacksonIcon(iconID); + public KdbxIcon getIcon() { + return new KdbxIcon(iconID); } @Override - public void setIcon(JacksonIcon icon) { + public void setIcon(Icon icon) { iconID = icon.getIndex(); } + @JsonIgnore + public UUID getCustomIconUuid() { + return customIconUUID; + } + + @JsonIgnore + public void setCustomIconUUID(UUID uuid) { + customIconUUID = uuid; + } + + @JsonIgnore + public KeePassFile.Icon getCustomIcon() { + if (customIconUUID == null) { + throw new IllegalStateException("Custom icon has not been set"); + } + for (KeePassFile.Icon icon : database.keePassFile.meta.customIcons) { + if (icon.uuid.equals(customIconUUID)) { + return icon; + } + } + throw new IllegalStateException("Custom icon was not found in database for uuid: " + customIconUUID); + } + @Override @JsonIgnore public Date getLastAccessTime() { diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxGroup.java similarity index 80% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxGroup.java index f71ffb3d..c8a58f3c 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonGroup.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxGroup.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -23,12 +24,15 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.base.AbstractGroup; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.Group; +import org.linguafranca.pwdb.Icon; +import org.linguafranca.pwdb.abstractdb.AbstractGroup; import org.linguafranca.pwdb.kdbx.jackson.converter.Base64ToUUIDConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.BooleanToStringConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.StringToBooleanConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.UUIDToBase64Converter; +import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile; import org.linguafranca.pwdb.kdbx.jackson.model.Times; import java.util.ArrayList; @@ -48,11 +52,14 @@ "enableAutoType", "enableSearching", "lastTopVisibleEntry", +"previousParentGroup", +"tags", +"customData", "entry", "group", }) @JsonIgnoreProperties(ignoreUnknown=true) -public class JacksonGroup extends AbstractGroup { +public class KdbxGroup extends AbstractGroup{ @JacksonXmlProperty(localName = "UUID") @JsonDeserialize(converter = Base64ToUUIDConverter.class) @@ -99,29 +106,40 @@ public class JacksonGroup extends AbstractGroup entries; + protected List entries; @JacksonXmlProperty(localName = "Group") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) - protected List groups; + protected List groups; @JsonIgnore - protected JacksonDatabase database; + protected KdbxDatabase database; @JsonIgnore - protected JacksonGroup parent; + protected KdbxGroup parent; - protected JacksonGroup() { + public KdbxGroup() { entries = new ArrayList<>(); groups = new ArrayList<>(); times = new Times(); } - public static JacksonGroup createGroup(JacksonDatabase database) { - JacksonGroup group = new JacksonGroup(); + public static KdbxGroup createGroup(KdbxDatabase database) { + KdbxGroup group = new KdbxGroup(); group.database = database; group.iconID = 0; group.name = ""; @@ -140,12 +158,13 @@ public boolean isRecycleBin() { } @Override - public JacksonGroup getParent() { + public KdbxGroup getParent() { return parent; } @Override - public void setParent(JacksonGroup group) { + public void setParent(Group g) { + KdbxGroup group = (KdbxGroup) g; if (isRootGroup()) { throw new IllegalStateException("Cannot add root group to another group"); } @@ -162,7 +181,7 @@ public void setParent(JacksonGroup group) { } @Override - public List getGroups() { + public List getGroups() { return new ArrayList<>(groups); } @@ -173,7 +192,8 @@ public int getGroupsCount() { } @Override - public JacksonGroup addGroup(JacksonGroup group) { + public KdbxGroup addGroup(Group g) { + KdbxGroup group = (KdbxGroup) g; if (group.isRootGroup()) { throw new IllegalStateException("Cannot add root group to another group"); } @@ -190,7 +210,8 @@ public JacksonGroup addGroup(JacksonGroup group) { } @Override - public JacksonGroup removeGroup(JacksonGroup group) { + public KdbxGroup removeGroup(Group g) { + KdbxGroup group = (KdbxGroup) g; if (this.database != group.database) { throw new IllegalStateException("Must be from same database"); } @@ -201,7 +222,7 @@ public JacksonGroup removeGroup(JacksonGroup group) { } @Override - public List getEntries() { + public List getEntries() { return new ArrayList<>(this.entries); } @@ -212,7 +233,8 @@ public int getEntriesCount() { } @Override - public JacksonEntry addEntry(JacksonEntry entry) { + public KdbxEntry addEntry(Entry e) { + KdbxEntry entry = (KdbxEntry) e; if (this.database != entry.database) { throw new IllegalStateException("Must be from same database"); } @@ -226,7 +248,8 @@ public JacksonEntry addEntry(JacksonEntry entry) { } @Override - public JacksonEntry removeEntry(JacksonEntry entry) { + public KdbxEntry removeEntry(Entry e) { + KdbxEntry entry = (KdbxEntry) e; if (this.database != entry.database) { throw new IllegalStateException("Must be from same database"); } @@ -252,19 +275,18 @@ public UUID getUuid() { } @Override - public JacksonIcon getIcon() { - return new JacksonIcon(iconID); + public KdbxIcon getIcon() { + return new KdbxIcon(iconID); } @Override - public void setIcon(JacksonIcon icon) { + public void setIcon(Icon icon) { this.iconID = icon.getIndex(); touch(); } - @NotNull @Override - public JacksonDatabase getDatabase() { + public KdbxDatabase getDatabase() { return database; } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxHistory.java similarity index 79% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxHistory.java index c9ea238c..24797653 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonHistory.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxHistory.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -21,27 +22,27 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -public class JacksonHistory { +public class KdbxHistory { @JacksonXmlProperty(localName = "Entry") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) - private List entry; + private List entry; - public JacksonHistory() { + public KdbxHistory() { entry = new ArrayList<>(); } /** * @return the entry */ - public List getEntry() { + public List getEntry() { return entry; } /** * @param entry the entry to set */ - public void setEntry(List entry) { + public void setEntry(List entry) { this.entry = entry; } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonIcon.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxIcon.java similarity index 83% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonIcon.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxIcon.java index 7e3cb732..b419b044 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonIcon.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxIcon.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; @@ -21,15 +22,15 @@ /** * @author giusvale */ -public class JacksonIcon implements Icon { +public class KdbxIcon implements Icon { private int index; - public JacksonIcon(int iconID) { + public KdbxIcon(int iconID) { index = iconID; } - public JacksonIcon() { + public KdbxIcon() { this.index = 0; } @@ -50,7 +51,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - JacksonIcon that = (JacksonIcon) o; + KdbxIcon that = (KdbxIcon) o; return index == that.index; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxSerializableDatabase.java similarity index 77% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxSerializableDatabase.java index a3f9d10a..fad9e14d 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/JacksonSerializableDatabase.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/KdbxSerializableDatabase.java @@ -1,22 +1,25 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.annotation.JsonInclude; +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; @@ -24,7 +27,7 @@ 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.format.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; @@ -36,34 +39,47 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.Objects; -public class JacksonSerializableDatabase implements SerializableDatabase { +public class KdbxSerializableDatabase 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 static KeePassFile createEmptyDatabase() { + try (InputStream inputStream = KdbxSerializableDatabase.class.getClassLoader().getResourceAsStream("base.kdbx.xml")) { + XmlMapper mapper = new XmlMapper(); + return mapper.readValue(inputStream, KeePassFile.class); + } + catch (IOException e) { + throw new RuntimeException(e); + } } - public JacksonSerializableDatabase() { + public KdbxSerializableDatabase() { } - public JacksonSerializableDatabase(KeePassFile keePassFile) { + public KdbxSerializableDatabase(KeePassFile keePassFile) { this.keePassFile = keePassFile; } @Override - public JacksonSerializableDatabase load(InputStream inputStream) throws IOException { + public KdbxSerializableDatabase load(InputStream inputStream) throws IOException { XmlMapper mapper = new XmlMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + KdbxSerializableDatabase.FAIL_ON_UNKNOWN_PROPERTIES); + SimpleModule module = new SimpleModule(); module.addDeserializer(PropertyValue.class, new ValueDeserializer(encryptor, propertyValueStrategy)); mapper.registerModule(module); @@ -76,7 +92,7 @@ public JacksonSerializableDatabase load(InputStream inputStream) throws IOExcept public void save(OutputStream outputStream) { try { SimpleModule module = new SimpleModule(); - module.addSerializer(PropertyValue.class, new ValueSerializer(encryptor, propertyValueStrategy)); + module.addSerializer(PropertyValue.class, new ValueSerializer(encryptor)); // disable auto-detection, only use annotated values XmlMapper mapper = XmlMapper.builder() .disable(MapperFeature.AUTO_DETECT_CREATORS, @@ -89,6 +105,7 @@ public void save(OutputStream outputStream) { mapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY); // set the serializer to Woodstox System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); @@ -97,8 +114,9 @@ public void save(OutputStream outputStream) { xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false); xmlOutputFactory.setProperty(WstxInputProperties.P_RETURN_NULL_FOR_DEFAULT_NAMESPACE, true); - OutputStreamWriter osw = new OutputStreamWriter(outputStream); + OutputStreamWriter osw = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); XMLStreamWriter sw = xmlOutputFactory.createXMLStreamWriter(osw); + //noinspection TryFinallyCanBeTryWithResources - doesn't work with XMLStreamWriter try { sw.setPrefix("xml", "http://www.w3.org/XML/1998/namespace"); @@ -137,6 +155,7 @@ public static void addBinary(KeePassFile keePassFile, int index, byte[] payload) 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); @@ -152,24 +171,6 @@ public int getBinaryCount() { 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() { diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java similarity index 90% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java index 66eb416c..b4cac37e 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToByteConverter.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java similarity index 87% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java index b2b07d29..e83a1913 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/Base64ToUUIDConverter.java @@ -1,24 +1,25 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; import java.util.UUID; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java similarity index 86% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java index 4059b1c8..362352df 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/BooleanToStringConverter.java @@ -1,22 +1,23 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java similarity index 90% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java index fd613a9f..e09592d5 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ByteToBase64Converter.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java similarity index 86% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java index 752579c0..eadcde32 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/DateToStringConverter.java @@ -1,24 +1,25 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; import java.util.Date; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java similarity index 86% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java index b295edd9..bdfea657 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToBooleanConverter.java @@ -1,21 +1,22 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java similarity index 83% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java index c215f90e..72577c9b 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/StringToDateConverter.java @@ -1,31 +1,33 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; import java.util.Date; import java.util.Objects; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.jspecify.annotations.Nullable; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; public class StringToDateConverter extends StdConverter { @Override - public Date convert(String value) { + public @Nullable Date convert(String value) { // TODO: It would really be better if we could inhibit deserialize date elements that are not present if (Objects.isNull(value) || value.isEmpty()) { return null; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java similarity index 86% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java index 3564185e..1feb3c8d 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/UUIDToBase64Converter.java @@ -1,23 +1,24 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; import java.util.UUID; -import org.linguafranca.pwdb.kdbx.Helpers; +import org.linguafranca.pwdb.format.Helpers; import com.fasterxml.jackson.databind.util.StdConverter; diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java similarity index 90% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java index 3c9639b4..86311aa0 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueDeserializer.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; @@ -20,14 +21,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.apache.commons.codec.binary.Base64; -import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.PropertyValue; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.jackson.model.EntryClasses; +import org.linguafranca.pwdb.format.Helpers; import org.linguafranca.pwdb.security.StreamEncryptor; import java.io.IOException; -import java.nio.charset.StandardCharsets; public class ValueDeserializer extends StdDeserializer { diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java similarity index 87% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java index 3e25b25c..ab98e10e 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/converter/ValueSerializer.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.converter; @@ -29,12 +30,10 @@ public class ValueSerializer extends StdSerializer { private final StreamEncryptor encryptor; - private final PropertyValue.Strategy propertyValueStrategy; - public ValueSerializer(StreamEncryptor encryptor, PropertyValue.Strategy propertyValueStrategy) { + public ValueSerializer(StreamEncryptor encryptor) { super(ValueSerializer.class, false); this.encryptor = encryptor; - this.propertyValueStrategy = propertyValueStrategy; } private String encrypt(byte[] bytes) { diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java similarity index 93% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java index c768d4eb..44d26864 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/EntryClasses.java @@ -1,17 +1,18 @@ /* - * Copyright 2015 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.model; @@ -19,8 +20,9 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; import org.linguafranca.pwdb.PropertyValue; -import org.linguafranca.pwdb.kdbx.jackson.JacksonEntry; +import org.linguafranca.pwdb.kdbx.jackson.KdbxEntry; import org.linguafranca.pwdb.kdbx.jackson.converter.BooleanToStringConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.StringToBooleanConverter; @@ -72,6 +74,7 @@ public static class Association { @JacksonXmlProperty(localName = "Window") protected String window; @JacksonXmlProperty(localName = "KeystrokeSequence") + @JsonInclude(JsonInclude.Include.ALWAYS) protected String keystrokeSequence; protected List windowAndKeystrokeSequence; @@ -145,13 +148,13 @@ public static class History { @JacksonXmlProperty(localName = "Entry") /* Workaround jackson */ @JacksonXmlElementWrapper(useWrapping = false) - private List list; + private List list; public History() { list = new ArrayList<>(); } - public List getHistory() { + public List getHistory() { return list; } } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java similarity index 87% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java index 6c7691bd..0a49aed0 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/KeePassFile.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.model; @@ -22,7 +23,7 @@ import java.util.UUID; import org.linguafranca.pwdb.Entry; -import org.linguafranca.pwdb.kdbx.jackson.JacksonGroup; +import org.linguafranca.pwdb.kdbx.jackson.KdbxGroup; import org.linguafranca.pwdb.kdbx.jackson.converter.Base64ToByteConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.Base64ToUUIDConverter; import org.linguafranca.pwdb.kdbx.jackson.converter.BooleanToStringConverter; @@ -62,7 +63,7 @@ public Root() { } @JacksonXmlProperty(localName = "Group") - public JacksonGroup group; + public KdbxGroup group; @JacksonXmlProperty(localName = "DeletedObjects") protected Object deletedObject; @@ -71,6 +72,7 @@ public Root() { @JsonPropertyOrder({ "generator", + "settingsChanged", "headerHash", "databaseName", "databaseNameChanged", @@ -83,6 +85,7 @@ public Root() { "masterKeyChanged", "masterKeyChangeRec", "masterKeyChangeForce", + "masterKeyChangeForceOnce", "memoryProtection", "customIcons", "recycleBinEnabled", @@ -94,7 +97,7 @@ public Root() { "lastTopVisibleGroup", "historyMaxItems", "historyMaxSize", - "binaries", + "customData", }) public static class Meta { @@ -150,11 +153,17 @@ public Meta() { @JacksonXmlProperty(localName = "MasterKeyChangeForce") protected int masterKeyChangeForce; + @JacksonXmlProperty(localName = "MasterKeyChangeForceOnce") + @JsonDeserialize(converter = StringToBooleanConverter.class) + @JsonSerialize(converter = BooleanToStringConverter.class) + protected Boolean masterKeyChangeForceOnce; + @JacksonXmlProperty(localName = "MemoryProtection") public KeePassFile.MemoryProtection memoryProtection; @JacksonXmlElementWrapper(localName = "CustomIcons") - protected ArrayList customIcons; + @JacksonXmlProperty(localName = "Icon") + public ArrayList customIcons; @JacksonXmlProperty(localName = "RecycleBinEnabled") @JsonDeserialize(converter = StringToBooleanConverter.class) @@ -310,6 +319,12 @@ public void setCompressed(boolean compressed) { } } + @JsonPropertyOrder({ + "uuid", + "data", + "name", + "lastModificationTime", + }) public static class Icon { public Icon() { } @@ -317,39 +332,48 @@ public Icon() { @JacksonXmlProperty(localName = "UUID") @JsonDeserialize(converter = Base64ToUUIDConverter.class) @JsonSerialize(converter = UUIDToBase64Converter.class) - protected UUID uuid; + public UUID uuid; @JacksonXmlProperty(localName = "Name") - protected String name; + public String name; @JacksonXmlProperty(localName = "LastModificationTime") @JsonDeserialize(converter = StringToDateConverter.class) @JsonSerialize(converter = DateToStringConverter.class) - protected Date lastModificationTime; + public Date lastModificationTime; @JacksonXmlProperty(localName = "Data") @JsonDeserialize(converter = Base64ToByteConverter.class) @JsonSerialize(converter = ByteToBase64Converter.class) - protected byte[] data; - - public String getName() { - return uuid.toString(); - } + public byte[] data; } public static class CustomData { - public CustomData() { + public static class CustomDataItem { + @JacksonXmlProperty(localName = "Key") + public String key; + @JacksonXmlProperty(localName = "Value") + public String value; + @JacksonXmlProperty(localName = "LastModificationTime") + @JsonDeserialize(converter = StringToDateConverter.class) + @JsonSerialize(converter = DateToStringConverter.class) + public Date lastModificationTime; } - public List getAny() { - return any; + @JacksonXmlProperty(localName = "Item") + @JacksonXmlElementWrapper(useWrapping = false) + protected List items; + + public CustomData() { } - public void setAny(List any) { - this.any = any; + public List getItems() { + return items; } - protected List any; + public void setItems(List items) { + this.items = items; + } } } diff --git a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java similarity index 97% rename from jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java index 0e1f8ba2..6fea2303 100644 --- a/jackson/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/model/Times.java @@ -1,17 +1,18 @@ /* - * Copyright 2023 Giuseppe Valente + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.model; diff --git a/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/package-info.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/package-info.java new file mode 100644 index 00000000..fd08f4c3 --- /dev/null +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.kdbx.jackson; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/example/src/main/java/org/linguafranca/pwdb/kdbx/Util.java b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/util/Util.java similarity index 57% rename from example/src/main/java/org/linguafranca/pwdb/kdbx/Util.java rename to kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/util/Util.java index cfffbe96..4557abe0 100644 --- a/example/src/main/java/org/linguafranca/pwdb/kdbx/Util.java +++ b/kdbx-database/src/main/java/org/linguafranca/pwdb/kdbx/jackson/util/Util.java @@ -1,57 +1,81 @@ -package org.linguafranca.pwdb.kdbx; +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ -import com.google.common.io.CharStreams; +package org.linguafranca.pwdb.kdbx.jackson.util; + +import org.apache.commons.io.IOUtils; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.Database; import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.kdbx.dom.DomDatabaseWrapper; -import org.linguafranca.pwdb.kdbx.jaxb.JaxbDatabase; -import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.Objects; +@SuppressWarnings("resource") public class Util { - List implementations = new ArrayList<>(Arrays.asList(DomDatabaseWrapper.class, SimpleDatabase.class, JaxbDatabase.class)); + List> implementations = List.of(KdbxDatabase.class); @FunctionalInterface public interface DatabaseLoader { - Database load(Credentials c, InputStream i) throws IOException; + Database load(Credentials c, InputStream i) throws IOException; } - List dbLoader = Arrays.asList(DomDatabaseWrapper::load, SimpleDatabase::load, JaxbDatabase::load); + List dbLoader = List.of(KdbxDatabase::load); public static InputStream getDecryptedInputStream (String resourceName, Credentials credentials) throws IOException { return getDecryptedInputStream(resourceName, credentials, new KdbxHeader()); } public static InputStream getDecryptedInputStream (String resourceName, Credentials credentials, KdbxHeader header) throws IOException { - InputStream is = Util.class.getClassLoader().getResourceAsStream(resourceName); - return KdbxSerializer.createUnencryptedInputStream(credentials, header, is); + // Don't close inputStream here + InputStream is = Util.class.getResourceAsStream(resourceName); + return KdbxSerializer.createUnencryptedInputStream(credentials, header, Objects.requireNonNull(is, ()->"resource: "+resourceName)); } public static String streamToString(InputStream inputStream) throws IOException { - return CharStreams.toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + return IOUtils.toString(inputStream, StandardCharsets.UTF_8); } /** * Example shows how to list XML from a database (but not decrypted passwords) */ public static void listDatabase(String resourceName, Credentials creds, OutputStream outputStream) throws IOException { - SimpleDatabase database = SimpleDatabase.load(creds, Util.class.getClassLoader().getResourceAsStream(resourceName)); - database.save(new StreamFormat.None(), new KdbxCreds.None(), outputStream); + KdbxDatabase database; + InputStream inputStream = Util.class.getResourceAsStream(resourceName); + database = KdbxDatabase.load(creds, Objects.requireNonNull(inputStream, ()->"resource: "+resourceName)); + database.save(new StreamFormat.None(), new KdbxCredentials.None(), outputStream); } /** * Example shows how to list XML from a database using specified loader */ public static void listDatabase(DatabaseLoader loader, String resourceName, Credentials creds, OutputStream outputStream) throws IOException { - Database database = loader.load(creds, Util.class.getClassLoader().getResourceAsStream(resourceName)); - database.save(new StreamFormat.None(), new KdbxCreds.None(), outputStream); + Database database; + InputStream inputStream = Util.class.getResourceAsStream(resourceName); + database = loader.load(creds, Objects.requireNonNull(inputStream, ()->"resource: "+resourceName)); + database.save(new StreamFormat.None(), new KdbxCredentials.None(), outputStream); + outputStream.flush(); } @@ -77,8 +101,9 @@ public static void listXml(String resourceName, Credentials credentials, PrintWr public static void listKdbxHeaderProperties(String resourceName, PrintWriter printWriter) throws IOException { printWriter.format(resourceName + "\n"); KdbxHeader kdbxHeader = new KdbxHeader(); - InputStream is = Util.class.getClassLoader().getResourceAsStream(resourceName); - KdbxSerializer.readOuterHeader(is, kdbxHeader); + try (InputStream is = Util.class.getResourceAsStream(resourceName)) { + KdbxSerializer.readOuterHeader(Objects.requireNonNull(is, ()->"resource: "+resourceName), kdbxHeader); + } printWriter.format("Version: %d\n", kdbxHeader.getVersion()); printWriter.format("Cipher Algorithm: %s\n", kdbxHeader.getCipherAlgorithm().getName()); printWriter.format("Key Derivation Function: %s\n", kdbxHeader.getKeyDerivationFunction().getName()); diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/Kdbx123Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/Kdbx123Test.java new file mode 100644 index 00000000..17d234ce --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/Kdbx123Test.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; +import org.linguafranca.pwdb.test.Test123Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +/** + * @author jo + */ +@Execution(CONCURRENT) +public class Kdbx123Test extends KdbxTestBase implements Test123Test { + + @Override + public boolean getSkipDateCheck() { + return false; + } + + @Override + public String getFileName() { + return "test123.kdbx"; + } +} \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxBinariesTest.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxBinariesTest.java new file mode 100644 index 00000000..8bed2876 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxBinariesTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.test.BinaryPropertiesTest; +import org.linguafranca.pwdb.test.DatabaseTestBase; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +public class KdbxBinariesTest { + + @Execution(CONCURRENT) + public static class KdbxBinariesV4Test extends KdbxTestBase implements BinaryPropertiesTest { + KdbxBinariesV4Test() { + DatabaseTestBase db = getDbFunc().get(); + db.loadAndInitDatabase(KdbConsts.PASSWORD_BYTES, "V4-ChaCha20-Argon2-Attachment.kdbx"); + } + } + + @Execution(CONCURRENT) + public static class KdbxBinariesV3Test extends KdbxTestBase implements BinaryPropertiesTest { + KdbxBinariesV3Test() { + DatabaseTestBase db = getDbFunc().get(); + db.loadAndInitDatabase(KdbConsts.PASSWORD_BYTES, "Attachment.kdbx"); + } + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseLoadTest.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseLoadTest.java new file mode 100644 index 00000000..875bf946 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseLoadTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import java.io.InputStream; +import java.io.PrintStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.Visitor; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +@Execution(CONCURRENT) +public class KdbxDatabaseLoadTest { + + @Test + public void loadXml() throws Exception { + PrintStream printStream = getTestPrintStream(); + KdbxDatabase database; + try (InputStream inputStream = KdbxDatabaseLoadTest.class.getResourceAsStream("/ExampleDatabase.xml")) { + assertNotNull(inputStream); + database = KdbxDatabase.loadXml(inputStream); + } + database.visit(new Visitor.Print(printStream)); + } + @Test + public void loadKdbx() throws Exception { + PrintStream printStream = getTestPrintStream(); + KdbxDatabase database; + try (InputStream inputStream = KdbxDatabaseLoadTest.class.getResourceAsStream("/test123.kdbx")) { + assertNotNull(inputStream); + database = KdbxDatabase.load(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } + database.visit(new Visitor.Print(printStream)); + } + + @Test + public void loadKdbxV4() throws Exception { + PrintStream printStream = getTestPrintStream(); + KdbxDatabase database; + try (InputStream inputStream = KdbxDatabaseLoadTest.class.getResourceAsStream("/V4-AES-Argon2.kdbx")) { + assertNotNull(inputStream); + database = KdbxDatabase.load(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } + database.visit(new Visitor.Print(printStream)); + // test what happens to dates in V4 + database.visit(new Visitor.Default(){ + @Override + public void visit(Entry entry) { + printStream.println(entry.getCreationTime()); + } + }); + } + + @Test + public void emptyDb() throws Exception { + PrintStream printStream = getTestPrintStream(); + KdbxDatabase database = new KdbxDatabase(); + printStream.println(database.getDescription()); + } + + @Test + public void dbWithDeleted() throws Exception { + PrintStream printStream = getTestPrintStream(); + KdbxDatabase database; + try (InputStream inputStream = KdbxDatabaseLoadTest.class.getResourceAsStream("/testDeleted.kdbx")) { + assertNotNull(inputStream); + database = KdbxDatabase.load(new KdbxCredentials(KdbConsts.PASSWORD_BYTES), inputStream); + } + database.visit(new Visitor.Print(printStream)); + } + +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseTest.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseTest.java new file mode 100644 index 00000000..4dbdf590 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxDatabaseTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.test.*; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@Execution(CONCURRENT) +public class KdbxDatabaseTest + extends + KdbxTestBase + implements + TrivialDatabaseTest, + GroupsAndEntriesTest, + RecycleBinTest, + ProtectedPropertyTest, + ProtectedPropertyTest2 { + +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxSaveAndReloadTest.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxSaveAndReloadTest.java new file mode 100644 index 00000000..c5998d0f --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxSaveAndReloadTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.StreamFormat; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.test.*; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@Execution(CONCURRENT) +public class KdbxSaveAndReloadTest extends KdbxTestBase implements KdbxFileSaveAndReloadTest { + + + @Override + public boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2) { + KdbxHeader h1 = (KdbxHeader) s1.getStreamConfiguration(); + KdbxHeader h2 = (KdbxHeader) s1.getStreamConfiguration(); + return (h1.getVersion() == h2.getVersion() && + h1.getProtectedStreamAlgorithm().equals(h2.getProtectedStreamAlgorithm()) && + h1.getKeyDerivationFunction().equals(h2.getKeyDerivationFunction()) && + h1.getCipherAlgorithm().equals(h2.getCipherAlgorithm())); + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxTestBase.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxTestBase.java new file mode 100644 index 00000000..7c6d0d2f --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/KdbxTestBase.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database; + +import org.linguafranca.pwdb.Database; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; +import org.linguafranca.pwdb.test.*; + +import java.util.function.Supplier; + +public abstract class KdbxTestBase { + + public static final Supplier kdbxFunc = ()-> new DatabaseTestBase(KdbxDatabase::new, KdbxDatabase::loadNx, Database::saveNx, KdbxCredentials::new); + + public Supplier getDbFunc() { + return kdbxFunc; + } + +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/package-info.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/package-info.java new file mode 100644 index 00000000..6143e46b --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.kdbx.database; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/UtilTest.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/UtilTest.java new file mode 100644 index 00000000..5803b0e4 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/UtilTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database.util; + + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.kdbx.jackson.util.Util.*; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +@Execution(CONCURRENT) +public class UtilTest { + + @Test + public void listDatabaseTest() throws IOException { + OutputStream outputStream = getTestPrintStream(); + listDatabase("/V3-CustomIcon.kdbx", new KdbxCredentials(KdbConsts.PASSWORD_BYTES), outputStream); + } + + @Test + public void listXmlTest() throws IOException { + OutputStream outputStream = getTestPrintStream(); + PrintWriter writer = new PrintWriter(outputStream); + listXml("/Database-4.1-123.kdbx", new KdbxCredentials(KdbConsts.PASSWORD_BYTES), writer); + writer.flush(); + } + + @Test + public void listXmlTest2() throws IOException { + OutputStream outputStream = getTestPrintStream(); + PrintWriter writer = new PrintWriter(outputStream); + listXml("/issue-70/test2.kdbx", new KdbxCredentials("KeePassJava2".getBytes()), writer); + writer.flush(); + } + /** + * List Database Encryption Characteristics + */ + @Test + public void listKdbxHeaderParams () throws IOException { + OutputStream outputStream = getTestPrintStream(); + PrintWriter writer = new PrintWriter(outputStream); + listKdbxHeaderProperties("/test123.kdbx", writer); + listKdbxHeaderProperties("/V4-AES-AES.kdbx", writer); + listKdbxHeaderProperties("/V4-AES-Argon2.kdbx", writer); + listKdbxHeaderProperties("/V4-ChaCha20-AES.kdbx", writer); + listKdbxHeaderProperties("/V4-ChaCha20-Argon2-Attachment.kdbx", writer); + writer.flush(); + } + + @Test + public void listHeaderPropertiesAndXml() throws IOException { + OutputStream outputStream = getTestPrintStream(); + PrintWriter writer = new PrintWriter(outputStream); + listKdbxHeaderProperties("/V4-AES-Argon2-CustomIcon.kdbx", writer); + listXml("/V4-AES-Argon2-CustomIcon.kdbx", new KdbxCredentials(KdbConsts.PASSWORD_BYTES), writer); + writer.flush(); + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/package-info.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/package-info.java new file mode 100644 index 00000000..120221c1 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/util/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.kdbx.database.util; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue27Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue27Test.java new file mode 100644 index 00000000..4bc3d604 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue27Test.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database.validation; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.Helpers; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +@Execution(CONCURRENT) +public class Issue27Test { + + /** + * Check load of problem file + */ + @Test + public void testIssue27() throws IOException { + PrintStream printStream = getTestPrintStream(); + + KdbxDatabase db; + try (InputStream is = Issue27Test.class.getResourceAsStream("/issue-27/bogus-timestamp2.kdbx")) { + assertNotNull(is); + KdbxCredentials creds = new KdbxCredentials("passwordless".getBytes()); + db = KdbxDatabase.load(creds, is); + } + List entries = db.findEntries("testtitle"); + + for (Entry entry: entries) { + printStream.println(Helpers.fromDateV3(entry.getCreationTime())); + assertEquals("2021-01-11T09:18:56Z", Helpers.fromDateV3(entry.getCreationTime())); + } + } + + /** + * Verify that V4 dates are still processed correctly + */ + @Test + public void testV4Date() throws IOException { + PrintStream printStream = getTestPrintStream(); + + KdbxDatabase db; + try (InputStream is = Issue27Test.class.getResourceAsStream("/V4-AES-AES.kdbx")) { + assertNotNull(is); + KdbxCredentials creds = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + db = KdbxDatabase.load(creds, is); + } + List entries = db.findEntries("Sample Entry #2 - Copy"); + + for (Entry entry: entries) { + printStream.println(Helpers.fromDate(entry.getCreationTime())); + assertEquals("2018-01-26T13:20:58Z", Helpers.fromDateV3(entry.getCreationTime())); + } + } + + @Test + public void testIssue27XML() throws IOException { + PrintStream printStream = getTestPrintStream(); + + + InputStream plainText; + try (InputStream is = Issue27Test.class.getResourceAsStream("/issue-27/bogus-timestamp2.kdbx")) { + assertNotNull(is); + KdbxCredentials creds = new KdbxCredentials("passwordless".getBytes()); + plainText = KdbxSerializer.createUnencryptedInputStream(creds, new KdbxHeader(), is); + String string = IOUtils.toString(plainText, StandardCharsets.UTF_8); + printStream.println(string); + } + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue33Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue33Test.java new file mode 100644 index 00000000..5617785e --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue33Test.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio + * + * 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.database.validation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.StreamFormat; +import org.linguafranca.pwdb.format.Helpers; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; +import org.linguafranca.pwdb.kdbx.jackson.util.Util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +/** + * Review Issue-33 + */ +@Execution(CONCURRENT) +public class Issue33Test { + + public static final String TEST_RESOURCE = "/V4-AES-Argon2-CustomIcon.kdbx"; + public static final KdbxCredentials CREDENTIALS = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + + + public static void listXml(Path tempPath) throws IOException { + final Path TEST_XML_FILE = tempPath.resolve("Issue33Source.xml"); + + if (System.out==null) { + throw new IllegalStateException("(System.out==null)"); + } + PrintWriter printWriter = new PrintWriter(System.out); + Util.listXml(TEST_RESOURCE, CREDENTIALS, printWriter); + Helpers.isV4.set(true); + } + + @Test + public void testJacksonDatabase(@TempDir Path tempPath) throws IOException { + listXml(tempPath); + + KdbxDatabase database; + try (InputStream inputStream = Issue33Test.class.getResourceAsStream(TEST_RESOURCE)) { + assertNotNull(inputStream); + database = KdbxDatabase.load(CREDENTIALS, inputStream); + } + try (OutputStream outputStream = Files.newOutputStream(tempPath.resolve("Issue33Jackson.xml"))) { + database.save(new StreamFormat.None(), new Credentials.None(), outputStream); + } + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue38Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue38Test.java new file mode 100644 index 00000000..b7505730 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue38Test.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database.validation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.util.TestUtil.getTestPrintStream; + +/** + * Bug report on GitHub, the Keyfile is Version 2 (Hex) + */ +@Execution(CONCURRENT) +public class Issue38Test { + + @Test + public void testV2Keyfile() throws IOException { + PrintStream printStream = getTestPrintStream(); + + KdbxDatabase database; + try (InputStream databaseStream = Issue38Test.class.getResourceAsStream("/issue-38/Database.kdbx")) { + KdbxCredentials creds; + try (InputStream keyStream = Issue38Test.class.getResourceAsStream("/issue-38/Database.keyx")) { + assertNotNull(keyStream); + creds = new KdbxCredentials("MyPassword".getBytes(), keyStream); + } + assertNotNull(databaseStream); + database = KdbxDatabase.load(creds, databaseStream); + } + List entries = database.findEntries("Sample Entry"); + Entry entry = entries.get(0); + printStream.println(entry.getTitle()); + } +} diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue70Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue70Test.java new file mode 100644 index 00000000..51285e4f --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue70Test.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.database.validation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +/** + * Review Issue-70 + */ +@Execution(CONCURRENT) +public class Issue70Test { + + public static final String TEST_RESOURCE1 = "/issue-70/test2.kdbx"; + public static final KdbxCredentials CREDENTIALS1 = new KdbxCredentials("KeePassJava2".getBytes()); + public static final String TEST_RESOURCE2 = "/Database-4.1-123.kdbx"; + public static final KdbxCredentials CREDENTIALS2 = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + public static final String TEST_RESOURCE3 = "/issue-70/test-hugoo10.kdbx"; + public static final KdbxCredentials CREDENTIALS3 = new KdbxCredentials("test".getBytes()); + + + /* custom data missing from meta */ + @Test + public void testCustomData() throws IOException { + try (InputStream inputStream = Issue70Test.class.getResourceAsStream(TEST_RESOURCE1)) { + assertNotNull(inputStream); + KdbxDatabase database = KdbxDatabase.load(CREDENTIALS1, inputStream); + } + } + + @Test + public void testFileFormat_4_1() throws IOException { + try (InputStream inputStream = Issue70Test.class.getResourceAsStream(TEST_RESOURCE2)){ + assertNotNull(inputStream); + KdbxDatabase database = KdbxDatabase.load(CREDENTIALS2, inputStream); + } + } + + /* custom data missing from entry definition */ + @Test + public void testCustomData2() throws IOException { + try (InputStream inputStream = Issue70Test.class.getResourceAsStream(TEST_RESOURCE3)){ + assertNotNull(inputStream); + KdbxDatabase database = KdbxDatabase.load(CREDENTIALS3, inputStream); + } + } + +} \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue87Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue87Test.java new file mode 100644 index 00000000..3db11046 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue87Test.java @@ -0,0 +1,83 @@ +package org.linguafranca.pwdb.kdbx.database.validation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.Entry; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; +import org.linguafranca.pwdb.kdbx.jackson.KdbxEntry; +import org.linguafranca.pwdb.kdbx.jackson.model.KeePassFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.kdbx.jackson.util.Util.streamToString; + +/** + * Review Issue-87 ... + */ +@Execution(CONCURRENT) +public class Issue87Test { + + public static final String TEST_RESOURCE1 = "/issue-87/customIcon-123.kdbx"; + public static final KdbxCredentials CREDENTIALS1 = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + public static final String TEST_OUTPUT_ISSUE_87_KDBX = "Issue87.kdbx"; + + @Test + public void testCustomIcon(@TempDir Path tempPath) throws IOException { + KdbxDatabase database; + try (InputStream inputStream = Issue87Test.class.getResourceAsStream(TEST_RESOURCE1)) { + assertNotNull(inputStream); + database = KdbxDatabase.load(CREDENTIALS1, inputStream); + } + + List entries = database.findEntries("Michael321"); + assertEquals(1, entries.size()); + + KdbxEntry entry = (KdbxEntry) entries.get(0); + UUID customIcon = entry.getCustomIconUuid(); + assertNotNull(customIcon); + System.out.println("Custom icon id: " + customIcon); + KeePassFile.Icon icon = entry.getCustomIcon(); + + Path path = tempPath.resolve(TEST_OUTPUT_ISSUE_87_KDBX); + try (OutputStream outputStream = Files.newOutputStream(path)) { + database.save(CREDENTIALS1, outputStream); + } + + // output the XML of the file we just saved, to check that the custom icon is still there + try (InputStream is = Files.newInputStream(path); + InputStream ss = KdbxSerializer.createUnencryptedInputStream(CREDENTIALS1, new KdbxHeader(), is)) { + System.out.println(streamToString(ss)); + System.out.println(); + System.out.flush(); + } + + // now load the newly written database and check the custom icon is the same as the original + try (InputStream is = Files.newInputStream(path)) { + KdbxDatabase database1 = KdbxDatabase.load(CREDENTIALS1, is); + List entries1 = database1.findEntries("Michael321"); + assertEquals(1, entries1.size()); + KdbxEntry entry1 = (KdbxEntry) entries1.get(0); + UUID customIcon1 = entry1.getCustomIconUuid(); + assertNotNull(customIcon1); + assertEquals(customIcon, customIcon1); + KeePassFile.Icon icon1 = entry1.getCustomIcon(); + assertEquals(icon1.name, icon.name); + assertArrayEquals(icon1.data, icon.data); + assertEquals(icon1.uuid, icon.uuid); + } + } +} \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue88Test.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue88Test.java new file mode 100644 index 00000000..a451f1af --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/Issue88Test.java @@ -0,0 +1,54 @@ +package org.linguafranca.pwdb.kdbx.database.validation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; +import org.linguafranca.pwdb.kdbx.jackson.KdbxDatabase; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.linguafranca.pwdb.kdbx.jackson.util.Util.streamToString; + +/** + * Review Issue-88 ... + */ +@Execution(CONCURRENT) +public class Issue88Test { + + public static final String TEST_RESOURCE1 = "/issue-88/newDb-123.kdbx"; + public static final KdbxCredentials CREDENTIALS1 = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + public static final String TEST_OUTPUT_ISSUE_88_KDBX = "Issue88.kdbx"; + + @Test + public void testDefaultAutoType(@TempDir Path tempPath) throws IOException { + KdbxDatabase database; + try (InputStream inputStream = Issue88Test.class.getResourceAsStream(TEST_RESOURCE1)) { + assertNotNull(inputStream); + database = KdbxDatabase.load(CREDENTIALS1, inputStream); + } + + Path path = tempPath.resolve(TEST_OUTPUT_ISSUE_88_KDBX); + try (OutputStream outputStream = Files.newOutputStream(path)) { + database.save(CREDENTIALS1, outputStream); + } + + // output the XML of the file we just saved, to check that the empty element is serialized + try (InputStream is = Files.newInputStream(path); + InputStream ss = KdbxSerializer.createUnencryptedInputStream(CREDENTIALS1, new KdbxHeader(), is)) { + System.out.println(streamToString(ss)); + System.out.println(); + System.out.flush(); + } + } +} \ No newline at end of file diff --git a/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/package-info.java b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/package-info.java new file mode 100644 index 00000000..f297dc60 --- /dev/null +++ b/kdbx-database/src/test/java/org/linguafranca/pwdb/kdbx/database/validation/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.kdbx.database.validation; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/test/src/main/resources/Attachment.kdbx b/kdbx-database/src/test/resources/Attachment.kdbx similarity index 100% rename from test/src/main/resources/Attachment.kdbx rename to kdbx-database/src/test/resources/Attachment.kdbx diff --git a/test/src/main/resources/Attachment.xml b/kdbx-database/src/test/resources/Attachment.xml similarity index 96% rename from test/src/main/resources/Attachment.xml rename to kdbx-database/src/test/resources/Attachment.xml index e400d526..0bfa905f 100755 --- a/test/src/main/resources/Attachment.xml +++ b/kdbx-database/src/test/resources/Attachment.xml @@ -1,157 +1,174 @@ - - - - KeePass - - 2016-08-27T14:41:45Z - - 2016-08-27T14:41:45Z - - 2016-08-27T14:41:45Z - 365 - - 2016-08-27T14:41:58Z - -1 - -1 - - False - False - True - False - False - - True - AAAAAAAAAAAAAAAAAAAAAA== - 2016-08-27T14:41:45Z - AAAAAAAAAAAAAAAAAAAAAA== - 2016-08-27T14:41:45Z - 10 - 6291456 - AAAAAAAAAAAAAAAAAAAAAA== - AAAAAAAAAAAAAAAAAAAAAA== - - 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 - - - - - - TOEWAvT9GlIjOkSMv9F+aw== - Root - - 48 - - 2016-08-27T14:41:45Z - 2016-08-27T14:41:45Z - 2016-08-27T14:41:45Z - 2016-08-27T14:41:45Z - False - 0 - 2016-08-27T14:41:45Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - GPv+n0B4upj0sZd+nuuiBQ== - 0 - - - - - - 2016-08-27T14:42:09Z - 2016-08-27T14:43:51Z - 2016-09-11T11:11:59Z - 2016-08-27T14:42:09Z - False - 1 - 2016-08-27T14:43:51Z - - - Notes - - - - Password - - - - Title - Test attachment - - - URL - - - - UserName - - - - letter J.jpeg - - - - True - 0 - - - - - mwq3nFNpPrvjXlsWy/6FTQ== - 0 - - - - - - 2016-08-27T15:03:52Z - 2016-08-27T15:04:44Z - 2016-08-27T15:04:44Z - 2016-08-27T15:03:52Z - False - 0 - 2016-08-27T15:04:44Z - - - Notes - - - - Password - - - - Title - Test 2 Attachments - - - URL - - - - UserName - - - - letter J.jpeg - - - - letter L.jpeg - - - - True - 0 - - - - - - + + + + + + KeePass + + 2016-08-27T14:41:45Z + + 2016-08-27T14:41:45Z + + 2016-08-27T14:41:45Z + 365 + + 2016-08-27T14:41:58Z + -1 + -1 + + False + False + True + False + False + + True + AAAAAAAAAAAAAAAAAAAAAA== + 2016-08-27T14:41:45Z + AAAAAAAAAAAAAAAAAAAAAA== + 2016-08-27T14:41:45Z + 10 + 6291456 + AAAAAAAAAAAAAAAAAAAAAA== + AAAAAAAAAAAAAAAAAAAAAA== + + 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 + + + + + + TOEWAvT9GlIjOkSMv9F+aw== + Root + + 48 + + 2016-08-27T14:41:45Z + 2016-08-27T14:41:45Z + 2016-08-27T14:41:45Z + 2016-08-27T14:41:45Z + False + 0 + 2016-08-27T14:41:45Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + GPv+n0B4upj0sZd+nuuiBQ== + 0 + + + + + + 2016-08-27T14:42:09Z + 2016-08-27T14:43:51Z + 2016-09-11T11:11:59Z + 2016-08-27T14:42:09Z + False + 1 + 2016-08-27T14:43:51Z + + + Notes + + + + Password + + + + Title + Test attachment + + + URL + + + + UserName + + + + letter J.jpeg + + + + True + 0 + + + + + mwq3nFNpPrvjXlsWy/6FTQ== + 0 + + + + + + 2016-08-27T15:03:52Z + 2016-08-27T15:04:44Z + 2016-08-27T15:04:44Z + 2016-08-27T15:03:52Z + False + 0 + 2016-08-27T15:04:44Z + + + Notes + + + + Password + + + + Title + Test 2 Attachments + + + URL + + + + UserName + + + + letter J.jpeg + + + + letter L.jpeg + + + + True + 0 + + + + + + \ No newline at end of file diff --git a/kdbx-database/src/test/resources/Database-4.1-123.kdbx b/kdbx-database/src/test/resources/Database-4.1-123.kdbx new file mode 100644 index 00000000..024017e1 Binary files /dev/null and b/kdbx-database/src/test/resources/Database-4.1-123.kdbx differ diff --git a/test/src/main/resources/issue-38/Database/Database.kdbx b/kdbx-database/src/test/resources/Database/Database.kdbx similarity index 100% rename from test/src/main/resources/issue-38/Database/Database.kdbx rename to kdbx-database/src/test/resources/Database/Database.kdbx diff --git a/test/src/main/resources/issue-38/Database/Database.keyx b/kdbx-database/src/test/resources/Database/Database.keyx similarity index 100% rename from test/src/main/resources/issue-38/Database/Database.keyx rename to kdbx-database/src/test/resources/Database/Database.keyx diff --git a/test/src/main/resources/issue-38/Database/README.md b/kdbx-database/src/test/resources/Database/README.md similarity index 100% rename from test/src/main/resources/issue-38/Database/README.md rename to kdbx-database/src/test/resources/Database/README.md diff --git a/test/src/main/resources/ExampleDatabase.kdbx b/kdbx-database/src/test/resources/ExampleDatabase.kdbx similarity index 100% rename from test/src/main/resources/ExampleDatabase.kdbx rename to kdbx-database/src/test/resources/ExampleDatabase.kdbx diff --git a/test/src/main/resources/ExampleDatabase.xml b/kdbx-database/src/test/resources/ExampleDatabase.xml similarity index 99% rename from test/src/main/resources/ExampleDatabase.xml rename to kdbx-database/src/test/resources/ExampleDatabase.xml index c7367899..0f77e5cc 100755 --- a/test/src/main/resources/ExampleDatabase.xml +++ b/kdbx-database/src/test/resources/ExampleDatabase.xml @@ -1,583 +1,600 @@ - - - - KeePass - - 2016-09-10T16:29:17Z - - 2016-09-10T16:29:17Z - - 2016-09-10T16:29:17Z - 365 - #FF0000 - 2016-09-10T16:29:17Z - -1 - -1 - - False - False - True - False - False - - - - kJh5KNGNaU6SZIsfsAwx7g== - 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 - - - True - 4fkGUxKnlUae43TI8NsnLQ== - 2016-09-10T16:29:17Z - V/rJOkFjYUqrFCDdP8HeSw== - 2016-09-10T16:36:55Z - 10 - 6291456 - SkjqCvDUmU6y6k7onr6ewg== - oy863aK0cEKApjbJfGLHZQ== - - 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 - - - - - - oy863aK0cEKApjbJfGLHZQ== - ExampleDatabase - - 49 - - 2016-09-10T16:29:17Z - 2016-09-10T16:29:17Z - 2016-09-10T18:50:45Z - 2016-09-10T15:04:19Z - False - 19 - 2016-09-10T16:29:17Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - 9truGTw7i0+svm1EKjSCXQ== - 0 - - - - - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T15:04:19Z - False - 0 - 2016-09-10T16:29:32Z - - - Notes - Notes - - - Password - Password - - - Title - Sample Entry - - - URL - http://keepass.info/ - - - UserName - User Name - - - True - 0 - - Target Window - {USERNAME}{TAB}{PASSWORD}{TAB}{ENTER} - - - - - - qgRU1hFDE0az+mXL5mTrFg== - 0 - - - - - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T15:04:19Z - False - 0 - 2016-09-10T16:29:32Z - - - Password - 12345 - - - Title - Sample Entry #2 - - - URL - http://keepass.info/help/kb/testform.html - - - UserName - Michael321 - - - True - 0 - - *Test Form - KeePass* - - - - - - - y5JBCSQMd0qKhGu3M9XjIw== - General - - 48 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:41Z - 2016-09-10T15:04:19Z - False - 2 - 2016-09-10T16:29:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - 0w2ac9Zj6Uqp6Up46CyNhQ== - Windows - - 38 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:36Z - 2016-09-10T15:04:19Z - False - 2 - 2016-09-10T16:29:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - zlA7tzCMp0WLJD8Bjw+rCw== - Network - - 3 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:36Z - 2016-09-10T15:04:19Z - False - 1 - 2016-09-10T16:29:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - JATpnerLXU+0DtRco5cx6Q== - Internet - - 1 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:37Z - 2016-09-10T15:04:19Z - False - 2 - 2016-09-10T16:29:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - KS6v+Pw92EmIby9paykzpQ== - eMail - - 19 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:38Z - 2016-09-10T15:04:19Z - False - 1 - 2016-09-10T16:29:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - 4fkGUxKnlUae43TI8NsnLQ== - Recycle Bin - - 43 - - 2016-09-10T16:30:02Z - 2016-09-10T16:30:02Z - 2016-09-10T18:50:45Z - 2016-09-10T15:04:19Z - False - 7 - 2016-09-10T16:30:02Z - - True - - false - false - AAAAAAAAAAAAAAAAAAAAAA== - - SkjqCvDUmU6y6k7onr6ewg== - Homebanking - - 37 - - 2016-09-10T16:29:32Z - 2016-09-10T16:29:32Z - 2016-09-10T18:50:45Z - 2016-09-10T15:04:19Z - False - 8 - 2016-09-10T16:30:02Z - - True - - null - null - qYwf0cP0sU6mfMsfj+s9EA== - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - kJh5KNGNaU6SZIsfsAwx7g== - - - - c;b;a - - 2016-09-10T16:29:50Z - 2016-09-11T10:44:40Z - 2016-09-11T10:44:40Z - 2016-09-10T15:04:19Z - False - 9 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - BinaryField - - - URL - - - - UserName - - - - letter J.jpeg - - - - True - 0 - - - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - - - - - - 2016-09-10T16:29:50Z - 2016-09-10T16:29:56Z - 2016-09-10T16:29:56Z - 2016-09-10T15:04:19Z - False - 1 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - hhs - - - URL - - - - UserName - - - - True - 0 - - - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - kJh5KNGNaU6SZIsfsAwx7g== - - - - - - 2016-09-10T16:29:50Z - 2016-09-10T17:26:56Z - 2016-09-10T17:45:44Z - 2016-09-10T15:04:19Z - False - 3 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - hhs - - - URL - - - - UserName - - - - True - 0 - - - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - kJh5KNGNaU6SZIsfsAwx7g== - - - - - - 2016-09-10T16:29:50Z - 2016-09-10T18:39:40Z - 2016-09-10T18:40:02Z - 2016-09-10T15:04:19Z - False - 6 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - hhs - - - URL - - - - UserName - - - - letter J.jpeg - - - - True - 0 - - - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - kJh5KNGNaU6SZIsfsAwx7g== - - - - a b c - - 2016-09-10T16:29:50Z - 2016-09-10T18:50:20Z - 2016-09-10T18:50:20Z - 2016-09-10T15:04:19Z - False - 7 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - hhs - - - URL - - - - UserName - - - - letter J.jpeg - - - - True - 0 - - - - qYwf0cP0sU6mfMsfj+s9EA== - 37 - kJh5KNGNaU6SZIsfsAwx7g== - - - - c;b;a - - 2016-09-10T16:29:50Z - 2016-09-10T18:51:05Z - 2016-09-10T18:51:05Z - 2016-09-10T15:04:19Z - False - 8 - 2016-09-10T16:29:50Z - - - Notes - - - - Password - 1abEUk2xWlLtfDIvzwYJ - - - Title - hhs - - - URL - - - - UserName - - - - letter J.jpeg - - - - True - 0 - - - - - - - - V/rJOkFjYUqrFCDdP8HeSw== - Templates - - 48 - - 2016-09-10T16:36:24Z - 2016-09-10T16:36:29Z - 2016-09-10T18:50:40Z - 2016-09-09T23:00:00Z - False - 4 - 2016-09-10T16:36:32Z - - True - - null - null - AAAAAAAAAAAAAAAAAAAAAA== - - - - + + + + + + KeePass + + 2016-09-10T16:29:17Z + + 2016-09-10T16:29:17Z + + 2016-09-10T16:29:17Z + 365 + #FF0000 + 2016-09-10T16:29:17Z + -1 + -1 + + False + False + True + False + False + + + + kJh5KNGNaU6SZIsfsAwx7g== + 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 + + + True + 4fkGUxKnlUae43TI8NsnLQ== + 2016-09-10T16:29:17Z + V/rJOkFjYUqrFCDdP8HeSw== + 2016-09-10T16:36:55Z + 10 + 6291456 + SkjqCvDUmU6y6k7onr6ewg== + oy863aK0cEKApjbJfGLHZQ== + + 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 + + + + + + oy863aK0cEKApjbJfGLHZQ== + ExampleDatabase + + 49 + + 2016-09-10T16:29:17Z + 2016-09-10T16:29:17Z + 2016-09-10T18:50:45Z + 2016-09-10T15:04:19Z + False + 19 + 2016-09-10T16:29:17Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + 9truGTw7i0+svm1EKjSCXQ== + 0 + + + + + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T15:04:19Z + False + 0 + 2016-09-10T16:29:32Z + + + Notes + Notes + + + Password + Password + + + Title + Sample Entry + + + URL + http://keepass.info/ + + + UserName + User Name + + + True + 0 + + Target Window + {USERNAME}{TAB}{PASSWORD}{TAB}{ENTER} + + + + + + qgRU1hFDE0az+mXL5mTrFg== + 0 + + + + + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T15:04:19Z + False + 0 + 2016-09-10T16:29:32Z + + + Password + 12345 + + + Title + Sample Entry #2 + + + URL + http://keepass.info/help/kb/testform.html + + + UserName + Michael321 + + + True + 0 + + *Test Form - KeePass* + + + + + + + y5JBCSQMd0qKhGu3M9XjIw== + General + + 48 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:41Z + 2016-09-10T15:04:19Z + False + 2 + 2016-09-10T16:29:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + 0w2ac9Zj6Uqp6Up46CyNhQ== + Windows + + 38 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:36Z + 2016-09-10T15:04:19Z + False + 2 + 2016-09-10T16:29:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + zlA7tzCMp0WLJD8Bjw+rCw== + Network + + 3 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:36Z + 2016-09-10T15:04:19Z + False + 1 + 2016-09-10T16:29:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + JATpnerLXU+0DtRco5cx6Q== + Internet + + 1 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:37Z + 2016-09-10T15:04:19Z + False + 2 + 2016-09-10T16:29:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + KS6v+Pw92EmIby9paykzpQ== + eMail + + 19 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:38Z + 2016-09-10T15:04:19Z + False + 1 + 2016-09-10T16:29:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + 4fkGUxKnlUae43TI8NsnLQ== + Recycle Bin + + 43 + + 2016-09-10T16:30:02Z + 2016-09-10T16:30:02Z + 2016-09-10T18:50:45Z + 2016-09-10T15:04:19Z + False + 7 + 2016-09-10T16:30:02Z + + True + + false + false + AAAAAAAAAAAAAAAAAAAAAA== + + SkjqCvDUmU6y6k7onr6ewg== + Homebanking + + 37 + + 2016-09-10T16:29:32Z + 2016-09-10T16:29:32Z + 2016-09-10T18:50:45Z + 2016-09-10T15:04:19Z + False + 8 + 2016-09-10T16:30:02Z + + True + + null + null + qYwf0cP0sU6mfMsfj+s9EA== + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + kJh5KNGNaU6SZIsfsAwx7g== + + + + c;b;a + + 2016-09-10T16:29:50Z + 2016-09-11T10:44:40Z + 2016-09-11T10:44:40Z + 2016-09-10T15:04:19Z + False + 9 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + BinaryField + + + URL + + + + UserName + + + + letter J.jpeg + + + + True + 0 + + + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + + + + + + 2016-09-10T16:29:50Z + 2016-09-10T16:29:56Z + 2016-09-10T16:29:56Z + 2016-09-10T15:04:19Z + False + 1 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + hhs + + + URL + + + + UserName + + + + True + 0 + + + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + kJh5KNGNaU6SZIsfsAwx7g== + + + + + + 2016-09-10T16:29:50Z + 2016-09-10T17:26:56Z + 2016-09-10T17:45:44Z + 2016-09-10T15:04:19Z + False + 3 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + hhs + + + URL + + + + UserName + + + + True + 0 + + + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + kJh5KNGNaU6SZIsfsAwx7g== + + + + + + 2016-09-10T16:29:50Z + 2016-09-10T18:39:40Z + 2016-09-10T18:40:02Z + 2016-09-10T15:04:19Z + False + 6 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + hhs + + + URL + + + + UserName + + + + letter J.jpeg + + + + True + 0 + + + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + kJh5KNGNaU6SZIsfsAwx7g== + + + + a b c + + 2016-09-10T16:29:50Z + 2016-09-10T18:50:20Z + 2016-09-10T18:50:20Z + 2016-09-10T15:04:19Z + False + 7 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + hhs + + + URL + + + + UserName + + + + letter J.jpeg + + + + True + 0 + + + + qYwf0cP0sU6mfMsfj+s9EA== + 37 + kJh5KNGNaU6SZIsfsAwx7g== + + + + c;b;a + + 2016-09-10T16:29:50Z + 2016-09-10T18:51:05Z + 2016-09-10T18:51:05Z + 2016-09-10T15:04:19Z + False + 8 + 2016-09-10T16:29:50Z + + + Notes + + + + Password + 1abEUk2xWlLtfDIvzwYJ + + + Title + hhs + + + URL + + + + UserName + + + + letter J.jpeg + + + + True + 0 + + + + + + + + V/rJOkFjYUqrFCDdP8HeSw== + Templates + + 48 + + 2016-09-10T16:36:24Z + 2016-09-10T16:36:29Z + 2016-09-10T18:50:40Z + 2016-09-09T23:00:00Z + False + 4 + 2016-09-10T16:36:32Z + + True + + null + null + AAAAAAAAAAAAAAAAAAAAAA== + + + + \ No newline at end of file diff --git a/test/src/main/resources/KeyFileDatabase.kdbx b/kdbx-database/src/test/resources/KeyFileDatabase.kdbx similarity index 100% rename from test/src/main/resources/KeyFileDatabase.kdbx rename to kdbx-database/src/test/resources/KeyFileDatabase.kdbx diff --git a/test/src/main/resources/V3-CustomIcon.kdbx b/kdbx-database/src/test/resources/V3-CustomIcon.kdbx similarity index 100% rename from test/src/main/resources/V3-CustomIcon.kdbx rename to kdbx-database/src/test/resources/V3-CustomIcon.kdbx diff --git a/kdbx-database/src/test/resources/V4-AES-AES.kdbx b/kdbx-database/src/test/resources/V4-AES-AES.kdbx new file mode 100644 index 00000000..da60da3b Binary files /dev/null and b/kdbx-database/src/test/resources/V4-AES-AES.kdbx differ diff --git a/test/src/main/resources/V4-AES-Argon2-CustomIcon.kdbx b/kdbx-database/src/test/resources/V4-AES-Argon2-CustomIcon.kdbx similarity index 100% rename from test/src/main/resources/V4-AES-Argon2-CustomIcon.kdbx rename to kdbx-database/src/test/resources/V4-AES-Argon2-CustomIcon.kdbx diff --git a/test/src/main/resources/V4-AES-Argon2.kdbx b/kdbx-database/src/test/resources/V4-AES-Argon2.kdbx similarity index 100% rename from test/src/main/resources/V4-AES-Argon2.kdbx rename to kdbx-database/src/test/resources/V4-AES-Argon2.kdbx diff --git a/test/src/main/resources/V4-ChaCha20-AES.kdbx b/kdbx-database/src/test/resources/V4-ChaCha20-AES.kdbx similarity index 100% rename from test/src/main/resources/V4-ChaCha20-AES.kdbx rename to kdbx-database/src/test/resources/V4-ChaCha20-AES.kdbx diff --git a/kdbx-database/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx b/kdbx-database/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx new file mode 100755 index 00000000..59b73e50 Binary files /dev/null and b/kdbx-database/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx differ diff --git a/test/src/main/resources/V4-CustomIcon.kdbx b/kdbx-database/src/test/resources/V4-CustomIcon.kdbx similarity index 100% rename from test/src/main/resources/V4-CustomIcon.kdbx rename to kdbx-database/src/test/resources/V4-CustomIcon.kdbx diff --git a/test/src/main/resources/issue-27/bogus-timestamp.kdbx b/kdbx-database/src/test/resources/issue-27/bogus-timestamp.kdbx similarity index 100% rename from test/src/main/resources/issue-27/bogus-timestamp.kdbx rename to kdbx-database/src/test/resources/issue-27/bogus-timestamp.kdbx diff --git a/test/src/main/resources/issue-27/bogus-timestamp2.kdbx b/kdbx-database/src/test/resources/issue-27/bogus-timestamp2.kdbx similarity index 100% rename from test/src/main/resources/issue-27/bogus-timestamp2.kdbx rename to kdbx-database/src/test/resources/issue-27/bogus-timestamp2.kdbx diff --git a/kdbx-database/src/test/resources/issue-38/Database.kdbx b/kdbx-database/src/test/resources/issue-38/Database.kdbx new file mode 100644 index 00000000..b769f15a Binary files /dev/null and b/kdbx-database/src/test/resources/issue-38/Database.kdbx differ diff --git a/kdbx-database/src/test/resources/issue-38/Database.keyx b/kdbx-database/src/test/resources/issue-38/Database.keyx new file mode 100644 index 00000000..fe8be0de --- /dev/null +++ b/kdbx-database/src/test/resources/issue-38/Database.keyx @@ -0,0 +1,12 @@ + + + + 2.0 + + + + A700DD37 8EBAABE2 1936E1F9 9FED8DEE + D0F71664 B25B0DD2 961FFDA6 9E194E75 + + + \ No newline at end of file diff --git a/kdbx-database/src/test/resources/issue-38/README.md b/kdbx-database/src/test/resources/issue-38/README.md new file mode 100644 index 00000000..9098fa49 --- /dev/null +++ b/kdbx-database/src/test/resources/issue-38/README.md @@ -0,0 +1 @@ +This was rasied as Issue 38 - Keyfile is version 2, which was not supported. \ No newline at end of file diff --git a/kdbx-database/src/test/resources/issue-70/test-hugoo10.kdbx b/kdbx-database/src/test/resources/issue-70/test-hugoo10.kdbx new file mode 100644 index 00000000..1eb01859 Binary files /dev/null and b/kdbx-database/src/test/resources/issue-70/test-hugoo10.kdbx differ diff --git a/kdbx-database/src/test/resources/issue-70/test1.kdbx b/kdbx-database/src/test/resources/issue-70/test1.kdbx new file mode 100644 index 00000000..d27f37d1 Binary files /dev/null and b/kdbx-database/src/test/resources/issue-70/test1.kdbx differ diff --git a/kdbx-database/src/test/resources/issue-70/test2.kdbx b/kdbx-database/src/test/resources/issue-70/test2.kdbx new file mode 100644 index 00000000..6eda5aed Binary files /dev/null and b/kdbx-database/src/test/resources/issue-70/test2.kdbx differ diff --git a/kdbx-database/src/test/resources/issue-87/customIcon-123.kdbx b/kdbx-database/src/test/resources/issue-87/customIcon-123.kdbx new file mode 100644 index 00000000..0d46aab0 Binary files /dev/null and b/kdbx-database/src/test/resources/issue-87/customIcon-123.kdbx differ diff --git a/kdbx-database/src/test/resources/issue-88/newDb-123.kdbx b/kdbx-database/src/test/resources/issue-88/newDb-123.kdbx new file mode 100644 index 00000000..cdc7dbce Binary files /dev/null and b/kdbx-database/src/test/resources/issue-88/newDb-123.kdbx differ diff --git a/kdbx-database/src/test/resources/junit-platform.properties b/kdbx-database/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..2115a2f1 --- /dev/null +++ b/kdbx-database/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = true diff --git a/test/src/main/resources/kdbxSample.xml b/kdbx-database/src/test/resources/kdbxSample.xml similarity index 98% rename from test/src/main/resources/kdbxSample.xml rename to kdbx-database/src/test/resources/kdbxSample.xml index 9ff1df98..5a6f513a 100644 --- a/test/src/main/resources/kdbxSample.xml +++ b/kdbx-database/src/test/resources/kdbxSample.xml @@ -1,18 +1,19 @@ diff --git a/test/src/main/resources/letter J.jpeg b/kdbx-database/src/test/resources/letter J.jpeg similarity index 100% rename from test/src/main/resources/letter J.jpeg rename to kdbx-database/src/test/resources/letter J.jpeg diff --git a/test/src/main/resources/letter L.jpeg b/kdbx-database/src/test/resources/letter L.jpeg similarity index 100% rename from test/src/main/resources/letter L.jpeg rename to kdbx-database/src/test/resources/letter L.jpeg diff --git a/kdbx-database/src/test/resources/test1.kdbx b/kdbx-database/src/test/resources/test1.kdbx new file mode 100644 index 00000000..afda971d Binary files /dev/null and b/kdbx-database/src/test/resources/test1.kdbx differ diff --git a/test/src/main/resources/test123-AES-AES.kdbx b/kdbx-database/src/test/resources/test123-AES-AES.kdbx similarity index 100% rename from test/src/main/resources/test123-AES-AES.kdbx rename to kdbx-database/src/test/resources/test123-AES-AES.kdbx diff --git a/test/src/main/resources/test123-AES-Argon2.kdbx b/kdbx-database/src/test/resources/test123-AES-Argon2.kdbx similarity index 100% rename from test/src/main/resources/test123-AES-Argon2.kdbx rename to kdbx-database/src/test/resources/test123-AES-Argon2.kdbx diff --git a/test/src/main/resources/test123-ChaCha20-AES.kdbx b/kdbx-database/src/test/resources/test123-ChaCha20-AES.kdbx similarity index 100% rename from test/src/main/resources/test123-ChaCha20-AES.kdbx rename to kdbx-database/src/test/resources/test123-ChaCha20-AES.kdbx diff --git a/test/src/main/resources/test123-ChaCha20-Argon2.kdbx b/kdbx-database/src/test/resources/test123-ChaCha20-Argon2.kdbx similarity index 100% rename from test/src/main/resources/test123-ChaCha20-Argon2.kdbx rename to kdbx-database/src/test/resources/test123-ChaCha20-Argon2.kdbx diff --git a/kdbx-database/src/test/resources/test123.kdbx b/kdbx-database/src/test/resources/test123.kdbx new file mode 100644 index 00000000..0263111b Binary files /dev/null and b/kdbx-database/src/test/resources/test123.kdbx differ diff --git a/test/src/main/resources/testDeleted.kdbx b/kdbx-database/src/test/resources/testDeleted.kdbx similarity index 100% rename from test/src/main/resources/testDeleted.kdbx rename to kdbx-database/src/test/resources/testDeleted.kdbx diff --git a/test/src/main/resources/xml/V4-AES-AES.xml b/kdbx-database/src/test/resources/xml/V4-AES-AES.xml similarity index 95% rename from test/src/main/resources/xml/V4-AES-AES.xml rename to kdbx-database/src/test/resources/xml/V4-AES-AES.xml index a8cf1f9f..d312e5de 100644 --- a/test/src/main/resources/xml/V4-AES-AES.xml +++ b/kdbx-database/src/test/resources/xml/V4-AES-AES.xml @@ -1,4 +1,21 @@ + + KeePass diff --git a/test/src/main/resources/xml/V4-AES-Argon2.xml b/kdbx-database/src/test/resources/xml/V4-AES-Argon2.xml similarity index 95% rename from test/src/main/resources/xml/V4-AES-Argon2.xml rename to kdbx-database/src/test/resources/xml/V4-AES-Argon2.xml index 3ebf3a01..9fc08870 100644 --- a/test/src/main/resources/xml/V4-AES-Argon2.xml +++ b/kdbx-database/src/test/resources/xml/V4-AES-Argon2.xml @@ -1,4 +1,21 @@ + + KeePass diff --git a/test/src/main/resources/xml/V4-ChaCha20-AES.xml b/kdbx-database/src/test/resources/xml/V4-ChaCha20-AES.xml similarity index 95% rename from test/src/main/resources/xml/V4-ChaCha20-AES.xml rename to kdbx-database/src/test/resources/xml/V4-ChaCha20-AES.xml index 1b687758..8dee14dd 100644 --- a/test/src/main/resources/xml/V4-ChaCha20-AES.xml +++ b/kdbx-database/src/test/resources/xml/V4-ChaCha20-AES.xml @@ -1,4 +1,21 @@ + + KeePass diff --git a/test/src/main/resources/xml/V4-ChaCha20-Argon2-Attachment.xml b/kdbx-database/src/test/resources/xml/V4-ChaCha20-Argon2-Attachment.xml similarity index 90% rename from test/src/main/resources/xml/V4-ChaCha20-Argon2-Attachment.xml rename to kdbx-database/src/test/resources/xml/V4-ChaCha20-Argon2-Attachment.xml index 1a6574c2..d182609e 100644 --- a/test/src/main/resources/xml/V4-ChaCha20-Argon2-Attachment.xml +++ b/kdbx-database/src/test/resources/xml/V4-ChaCha20-Argon2-Attachment.xml @@ -1,4 +1,21 @@ + + KeePass diff --git a/kdbx/pom.xml b/kdbx-io/pom.xml similarity index 60% rename from kdbx/pom.xml rename to kdbx-io/pom.xml index 282eeed4..f1027a84 100644 --- a/kdbx/pom.xml +++ b/kdbx-io/pom.xml @@ -1,62 +1,55 @@ - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT + KeePassJava2.parent + mh.keepass + 1.0.0.1 ../pom.xml 4.0.0 - KeePassJava2-kdbx - KeePassJava2 :: KDBX - Base classes and resources for other KDBX modules. - KDBX Stream Support. - Security support. - + KeePassJava2.kdbx.io + KeePassJava2 :: KDBX-IO + KeePass KDBX File format support - org.linguafranca.pwdb + mh.keepass database ${project.version} commons-codec commons-codec - 1.17.0 - - - org.jetbrains - annotations - 24.1.0 - org.linguafranca.pwdb - test - ${project.version} - test + + commons-io + commons-io - junit - junit + mh.keepass + database + ${project.version} + test-jar test diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/Helpers.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/Helpers.java similarity index 97% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/Helpers.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/Helpers.java index d789bd15..1654fb3c 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/Helpers.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/Helpers.java @@ -1,24 +1,25 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; -import com.google.common.io.ByteStreams; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -155,7 +156,7 @@ public static byte[] unzipBinaryContent(byte[] content) { ByteArrayInputStream bais = new ByteArrayInputStream(content); try { GZIPInputStream g = new GZIPInputStream(bais); - return ByteStreams.toByteArray(g); + return IOUtils.toByteArray(g); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCreds.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCredentials.java similarity index 75% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCreds.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCredentials.java index dff3d242..ab893a76 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCreds.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCredentials.java @@ -1,22 +1,22 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; -import org.jetbrains.annotations.NotNull; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.security.Encryption; @@ -28,16 +28,16 @@ * * @author jo */ -public class KdbxCreds implements Credentials { +public class KdbxCredentials implements Credentials { private final byte[] key; /** * Constructor for password with KDBX Keyfile * @param password Master Password (new byte[0] if empty, not none) - * @param inputStream inputstream of the keyfile + * @param inputStream inputStream of the keyfile */ - public KdbxCreds(@NotNull byte[] password, @NotNull InputStream inputStream) { + public KdbxCredentials(byte[] password, InputStream inputStream) { MessageDigest md = Encryption.getSha256MessageDigestInstance(); byte[] pwKey = md.digest(password); md.update(pwKey); @@ -51,9 +51,9 @@ public KdbxCreds(@NotNull byte[] password, @NotNull InputStream inputStream) { /** * Constructor for KDBX Keyfile with no password - * @param inputStream inputstream of the keyfile + * @param inputStream inputStream of the keyfile */ - public KdbxCreds(@NotNull InputStream inputStream) { + public KdbxCredentials(InputStream inputStream) { MessageDigest md = Encryption.getSha256MessageDigestInstance(); byte[] keyFileData = KdbxKeyFile.load(inputStream); if (keyFileData == null) { @@ -63,7 +63,11 @@ public KdbxCreds(@NotNull InputStream inputStream) { } - public KdbxCreds(@NotNull byte[] password) { + /** + * Constructor for password only + * @param password Master Password for database (new byte[0] if empty, not none) + */ + public KdbxCredentials(byte[] password) { MessageDigest md = Encryption.getSha256MessageDigestInstance(); byte[] digest = md.digest(password); key = md.digest(digest); diff --git a/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCreds.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCreds.java new file mode 100644 index 00000000..34a1f8a5 --- /dev/null +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxCreds.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.format; + +import java.io.InputStream; + +/** + * Convenience wrapper to convert to KdbxCredentials - for helping with code migration to KeePassJava2 version 3. + * + * @deprecated use {@link KdbxCredentials} instead + */ +@Deprecated +public class KdbxCreds extends KdbxCredentials { + public KdbxCreds(byte[] password, InputStream inputStream) { + super(password, inputStream); + } + + public KdbxCreds(InputStream inputStream) { + super(inputStream); + } + + public KdbxCreds(byte[] password) { + super(password); + } +} diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxHeader.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxHeader.java similarity index 98% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxHeader.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxHeader.java index ecfdb585..ab372500 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxHeader.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxHeader.java @@ -1,20 +1,21 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.StreamConfiguration; @@ -125,7 +126,7 @@ interface KdbxHeaderOptions { /** * Default values for crypto options */ - enum KdbxHeaderOpts implements KdbxHeaderOptions{ + public enum KdbxHeaderOpts implements KdbxHeaderOptions{ V3_AES_SALSA_20(3, Encryption.Cipher.AES, Encryption.KeyDerivationFunction.AES, Encryption.ProtectedStreamAlgorithm.SALSA_20), V4_AES_ARGON_CHA_CHA (4, Encryption.Cipher.AES, Encryption.KeyDerivationFunction.ARGON2, Encryption.ProtectedStreamAlgorithm.CHA_CHA_20); @@ -241,9 +242,9 @@ public InputStream createDecryptedStream(byte[] digest, InputStream inputStream) return ca.getDecryptedInputStream(inputStream, finalKeyDigest, encryptionIv); } - public StreamEncryptor getInnerStreamEncryptor() { +/* public StreamEncryptor getInnerStreamEncryptor() { return Encryption.ProtectedStreamAlgorithm.getStreamEncryptor(getProtectedStreamAlgorithm(), getInnerRandomStreamKey()); - } + }*/ /** * Takes the composite credentials and transforms them according to the underlying KDF algorithm. @@ -339,7 +340,7 @@ public VariantDictionary getKdfParameters() { return kdfParameters; } - public StreamEncryptor getStreamEncryptor() { + public StreamEncryptor getInnerStreamEncryptor() { return Encryption.ProtectedStreamAlgorithm.getStreamEncryptor(getProtectedStreamAlgorithm(), this.innerRandomStreamKey); } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxKeyFile.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxKeyFile.java similarity index 98% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxKeyFile.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxKeyFile.java index c3f7004e..960572f1 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxKeyFile.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxKeyFile.java @@ -1,20 +1,21 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxSerializer.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxSerializer.java similarity index 96% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxSerializer.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxSerializer.java index 98f36eab..f3acccb7 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxSerializer.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxSerializer.java @@ -1,27 +1,28 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +import org.apache.commons.io.input.SwappedDataInputStream; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.hashedblock.*; import org.linguafranca.pwdb.security.Encryption; import org.linguafranca.pwdb.security.VariantDictionary; +import org.linguafranca.pwdb.utils.SwappedDataOutputStream; import javax.crypto.Mac; import java.io.*; @@ -82,7 +83,7 @@ * @see this diagram * @author jo */ -@SuppressWarnings({"WeakerAccess", "UnstableApiUsage"}) +@SuppressWarnings({"WeakerAccess"}) public class KdbxSerializer { // make entirely static @@ -179,7 +180,7 @@ public static OutputStream createEncryptedOutputStream(Credentials credentials, * @throws IOException if the stream cannot be read, etc. */ private static void checkStartBytes(KdbxHeader kdbxHeader, InputStream decryptedInputStream) throws IOException { - LittleEndianDataInputStream ledis = new LittleEndianDataInputStream(decryptedInputStream); + SwappedDataInputStream ledis = new SwappedDataInputStream(decryptedInputStream); byte [] startBytes = new byte[32]; ledis.readFully(startBytes); @@ -195,7 +196,7 @@ private static void checkStartBytes(KdbxHeader kdbxHeader, InputStream decrypted * @throws IOException if the stream cannot be written, etc. */ private static void writeStartBytes(KdbxHeader kdbxHeader, OutputStream encryptedOutputStream) throws IOException { - LittleEndianDataOutputStream ledos = new LittleEndianDataOutputStream(encryptedOutputStream); + SwappedDataOutputStream ledos = new SwappedDataOutputStream(encryptedOutputStream); ledos.write(kdbxHeader.getStreamStartBytes()); } @@ -236,7 +237,7 @@ public static KdbxHeader readOuterHeader(InputStream inputStream, KdbxHeader kdb // HMac calculation depends on having collected a couple of the header fields CollectingInputStream collectingInputStream = new CollectingInputStream(shaDigestInputStream, true); // make values available from LittleEndian - LittleEndianDataInputStream ledis = new LittleEndianDataInputStream(collectingInputStream); + SwappedDataInputStream ledis = new SwappedDataInputStream(collectingInputStream); // file starts with magic number if (!verifyMagicNumber(ledis)) { @@ -377,12 +378,11 @@ private static void getOuterHeaderFields(KdbxHeader kdbxHeader, DataInput input) * @param kdbxHeader the header to write and update * @throws IOException on error */ - @SuppressWarnings("UnstableApiUsage") public static void writeKdbxHeader(KdbxHeader kdbxHeader, OutputStream outputStream) throws IOException { MessageDigest messageDigest = Encryption.getSha256MessageDigestInstance(); DigestOutputStream digestOutputStream = new DigestOutputStream(outputStream, messageDigest); CollectingOutputStream collectingOutputStream = new CollectingOutputStream(digestOutputStream); - LittleEndianDataOutputStream ledos = new LittleEndianDataOutputStream(collectingOutputStream); + SwappedDataOutputStream ledos = new SwappedDataOutputStream(collectingOutputStream); // lengths are short in v3 int in v4 IntConsumer lengthWriter = (i -> { @@ -483,7 +483,7 @@ private static class InnerHeaderType { * @throws IOException on error */ private static void readInnerHeader(KdbxHeader kdbxHeader, InputStream plainTextStream) throws IOException { - DataInput input = new LittleEndianDataInputStream(plainTextStream); + DataInput input = new SwappedDataInputStream(plainTextStream); byte headerType; do { @@ -517,7 +517,7 @@ private static void readInnerHeader(KdbxHeader kdbxHeader, InputStream plainText } public static void writeInnerHeader(KdbxHeader kdbxHeader, OutputStream outputStream) throws IOException { - DataOutput output = new LittleEndianDataOutputStream(outputStream); + DataOutput output = new SwappedDataOutputStream(outputStream); output.writeByte(InnerHeaderType.INNER_RANDOM_STREAM_ID); output.writeInt(4); @@ -602,7 +602,7 @@ public static byte[] serializeVariantDictionary(VariantDictionary v) { * @return true if it looks like this is a kdbx file * @throws IOException on error */ - private static boolean verifyMagicNumber(LittleEndianDataInputStream ledis) throws IOException { + private static boolean verifyMagicNumber(SwappedDataInputStream ledis) throws IOException { int sig1 = ledis.readInt(); int sig2 = ledis.readInt(); return sig1 == SIG1 && sig2 == SIG2; diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxStreamFormat.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxStreamFormat.java similarity index 91% rename from kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxStreamFormat.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxStreamFormat.java index c6d3c730..221f19ad 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxStreamFormat.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/KdbxStreamFormat.java @@ -1,20 +1,21 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; +package org.linguafranca.pwdb.format; import org.linguafranca.pwdb.Credentials; import org.linguafranca.pwdb.SerializableDatabase; @@ -61,7 +62,7 @@ public KdbxStreamFormat(KdbxHeader kdbxHeader) { @Override public void load(SerializableDatabase serializableDatabase, Credentials credentials, InputStream encryptedInputStream) throws IOException { try (InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, kdbxHeader, encryptedInputStream)) { - serializableDatabase.setEncryption(kdbxHeader.getStreamEncryptor()); + serializableDatabase.setEncryption(kdbxHeader.getInnerStreamEncryptor()); serializableDatabase.load(decryptedInputStream); if (kdbxHeader.getVersion() == 3 && !Arrays.equals(serializableDatabase.getHeaderHash(), kdbxHeader.getHeaderHash())) { throw new IllegalStateException("Header hash does not match"); @@ -80,7 +81,6 @@ public void load(SerializableDatabase serializableDatabase, Credentials credenti public void save(SerializableDatabase serializableDatabase, Credentials credentials, OutputStream outputStream) throws IOException { Helpers.isV4.set(kdbxHeader.getVersion() == 4); if (kdbxHeader.getVersion() == 4) { - // TODO this assumes that the indexes start from 0 and are in sequence ... for (int a = 0; a < serializableDatabase.getBinaryCount(); a++) { int attachmentLength = serializableDatabase.getBinary(a).length; byte[] binary = new byte[attachmentLength + 1]; @@ -88,13 +88,14 @@ public void save(SerializableDatabase serializableDatabase, Credentials credenti System.arraycopy(serializableDatabase.getBinary(a),0, binary, 1, attachmentLength); kdbxHeader.addBinary(binary); } + // TODO the binaries should now be removed so they don't get serialized in XML } try (OutputStream encryptedOutputStream = KdbxSerializer.createEncryptedOutputStream(credentials, kdbxHeader, outputStream)) { if (kdbxHeader.getVersion() == 3) { serializableDatabase.setHeaderHash(kdbxHeader.getHeaderHash()); } - serializableDatabase.setEncryption(kdbxHeader.getStreamEncryptor()); + serializableDatabase.setEncryption(kdbxHeader.getInnerStreamEncryptor()); serializableDatabase.save(encryptedOutputStream); encryptedOutputStream.flush(); } diff --git a/kdbx-io/src/main/java/org/linguafranca/pwdb/format/package-info.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/package-info.java new file mode 100644 index 00000000..fd08e284 --- /dev/null +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/format/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ + +@NullMarked +package org.linguafranca.pwdb.format; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java similarity index 74% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java index e301e625..a8d85d55 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingInputStream.java @@ -1,6 +1,21 @@ -package org.linguafranca.pwdb.hashedblock; +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ -import org.jetbrains.annotations.NotNull; +package org.linguafranca.pwdb.hashedblock; import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; @@ -48,12 +63,12 @@ public int read() throws IOException { } @Override - public int read(@NotNull byte[] b) throws IOException { + public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override - public int read(@NotNull byte[] b, int off, int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { int result = super.read(b, off, len); if (collecting && result != -1) { collectedBytes.write(b, off, result); diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java similarity index 70% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java index 90919b20..634ec0b0 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/CollectingOutputStream.java @@ -1,6 +1,21 @@ -package org.linguafranca.pwdb.hashedblock; +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ -import org.jetbrains.annotations.NotNull; +package org.linguafranca.pwdb.hashedblock; import java.io.*; @@ -44,12 +59,12 @@ public void write(int toWrite) throws IOException { } @Override - public void write(byte @NotNull [] b) throws IOException { + public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override - public void write(byte @NotNull [] b, int off, int len) throws IOException { + public void write(byte[] b, int off, int len) throws IOException { super.write(b, off, len); } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java similarity index 96% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java index 47d96986..22379b96 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockInputStream.java @@ -1,23 +1,22 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.hashedblock; -import org.jetbrains.annotations.NotNull; - import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; @@ -56,7 +55,7 @@ public class HashedBlockInputStream extends InputStream { private long expectedSequenceNumber = 0; private boolean littleEndian = false; private boolean done = false; - private InputStream inputStream; + private final InputStream inputStream; private ByteArrayInputStream blockInputStream = new ByteArrayInputStream(new byte[0]); /** @@ -85,7 +84,7 @@ public HashedBlockInputStream(InputStream inputStream, boolean littleEndian) { } @Override - public int read(@NotNull byte[] b, int offset, int length) throws IOException { + public int read(byte[] b, int offset, int length) throws IOException { return get(b, offset, length); } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java similarity index 94% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java index e376e556..4367191a 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HashedBlockOutputStream.java @@ -1,23 +1,22 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.hashedblock; -import org.jetbrains.annotations.NotNull; - import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; @@ -54,8 +53,8 @@ public class HashedBlockOutputStream extends OutputStream { private final MessageDigest sha256; private int nextSequenceNumber = 0; private boolean littleEndian = false; - private OutputStream outputStream; - private ByteArrayOutputStream blockOutputStream = new ByteArrayOutputStream(); + private final OutputStream outputStream; + private final ByteArrayOutputStream blockOutputStream = new ByteArrayOutputStream(); private boolean isClosed = false; /** @@ -91,7 +90,7 @@ public void write(int i) throws IOException { } @Override - public void write(@NotNull byte[] b, int offset, int count) throws IOException { + public void write(byte[] b, int offset, int count) throws IOException { put(b, offset, count); } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java similarity index 79% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java index 9a7825df..de0a0bdf 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockInputStream.java @@ -1,14 +1,30 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.hashedblock; -import com.google.common.io.LittleEndianDataInputStream; -import org.jetbrains.annotations.NotNull; +import org.apache.commons.io.input.SwappedDataInputStream; +import org.linguafranca.pwdb.format.Helpers; import javax.crypto.Mac; import java.io.*; import java.nio.ByteOrder; import java.util.Arrays; -import static org.linguafranca.pwdb.kdbx.Helpers.toBytes; import static org.linguafranca.pwdb.security.Encryption.getHMacSha256Instance; import static org.linguafranca.pwdb.security.Encryption.transformHmacKey; @@ -62,7 +78,7 @@ public HmacBlockInputStream(byte[] key, InputStream inputStream, boolean littleE this.byteOrder = littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; this.key = key; if (littleEndian) { - input = new LittleEndianDataInputStream(in); + input = new SwappedDataInputStream(in); } else { input = new DataInputStream(in); } @@ -99,35 +115,22 @@ private void getBlock() throws IOException { * @param hmacSha256 the hmac to verify */ private void verifyHmac(byte[] buffer, long blockNumber, byte[] hmacSha256) { - final byte[] transformedKey = transformHmacKey(this.key, toBytes(blockNumber, byteOrder)); + final byte[] transformedKey = transformHmacKey(this.key, Helpers.toBytes(blockNumber, byteOrder)); final Mac mac = getHMacSha256Instance(transformedKey); - mac.update(toBytes(blockNumber, byteOrder)); - mac.update(toBytes(buffer.length, byteOrder)); + mac.update(Helpers.toBytes(blockNumber, byteOrder)); + mac.update(Helpers.toBytes(buffer.length, byteOrder)); if (!Arrays.equals(mac.doFinal(buffer), hmacSha256)) { throw new IllegalStateException("Block HMAC does not match"); } - -/* // using bouncy castle - HMac hmac = new HMac(new SHA256Digest()); - hmac.init(new KeyParameter(getHmacBlockKey(this.key, blockNumber, byteOrder))); - hmac.update(Helpers.toBytes(blockNumber, byteOrder), 0, 8); - hmac.update(Helpers.toBytes(buffer.length, byteOrder), 0, 4); - hmac.update(buffer, 0, buffer.length); - byte[] computedHmacSha256 = new byte[32]; - hmac.doFinal(computedHmacSha256, 0); - if (!Arrays.equals(computedHmacSha256, hmacSha256)) { - throw new IllegalStateException("Block HMAC does not match"); - } -*/ } @Override - public int read(@NotNull byte[] b) throws IOException { + public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override - public int read(@NotNull byte[] b, int off, int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { if (finished) { return -1; } diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java similarity index 81% rename from kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java rename to kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java index 5dd0d252..7051644c 100644 --- a/kdbx/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/HmacBlockOutputStream.java @@ -1,14 +1,30 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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.hashedblock; -import static org.linguafranca.pwdb.kdbx.Helpers.toBytes; import static org.linguafranca.pwdb.security.Encryption.getHMacSha256Instance; import static org.linguafranca.pwdb.security.Encryption.transformHmacKey; import java.io.*; import java.nio.ByteOrder; import javax.crypto.Mac; -import org.jetbrains.annotations.NotNull; -import com.google.common.io.LittleEndianDataOutputStream; +import org.linguafranca.pwdb.format.Helpers; +import org.linguafranca.pwdb.utils.SwappedDataOutputStream; /** * Takes a stream of (encrypted GZIPped) data and formats it as HMAC Hashed Blocks to the @@ -60,8 +76,7 @@ public HmacBlockOutputStream(byte [] key, OutputStream outputStream, boolean lit this.key = key; this.byteOrder = littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; if (this.byteOrder.equals(ByteOrder.LITTLE_ENDIAN)) { - //noinspection UnstableApiUsage - this.output = new LittleEndianDataOutputStream(out); + this.output = new SwappedDataOutputStream(out); } else { this.output = new DataOutputStream(out); } @@ -83,10 +98,10 @@ public void close() throws IOException { } private void writeBlock(byte [] buffer) throws IOException { - final byte[] transformedKey = transformHmacKey(this.key, toBytes(nextSequenceNumber, byteOrder)); + final byte[] transformedKey = transformHmacKey(this.key, Helpers.toBytes(nextSequenceNumber, byteOrder)); final Mac mac = getHMacSha256Instance(transformedKey); - mac.update(toBytes(nextSequenceNumber++, byteOrder)); - mac.update(toBytes(buffer.length, byteOrder)); + mac.update(Helpers.toBytes(nextSequenceNumber++, byteOrder)); + mac.update(Helpers.toBytes(buffer.length, byteOrder)); mac.update(buffer); byte[] finalMac = mac.doFinal(); output.write(finalMac); @@ -141,7 +156,7 @@ public void write(int i) throws IOException { } @Override - public void write(byte @NotNull [] b, int offset, int count) throws IOException { + public void write(byte[] b, int offset, int count) throws IOException { put(b, offset, count); } diff --git a/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/package-info.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/package-info.java new file mode 100644 index 00000000..ddf4ea51 --- /dev/null +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/hashedblock/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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. + * + */ +@NullMarked +package org.linguafranca.pwdb.hashedblock; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdbx-io/src/main/java/org/linguafranca/pwdb/utils/SwappedDataOutputStream.java b/kdbx-io/src/main/java/org/linguafranca/pwdb/utils/SwappedDataOutputStream.java new file mode 100644 index 00000000..8fd4fca6 --- /dev/null +++ b/kdbx-io/src/main/java/org/linguafranca/pwdb/utils/SwappedDataOutputStream.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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.utils; + +import org.apache.commons.io.EndianUtils; + +import java.io.*; + +/** + * DataOutput for systems relying on little-endian data formats. When written, values will be changed from big-endian to little-endian + * formats for external usage. + * + * @author github@Siggen + * Date: 5/8/2026 + * Time: 9:34 AM + */ +public final class SwappedDataOutputStream extends FilterOutputStream implements DataOutput { + + /** + * Creates a {@code SwappedDataOutputStream} that wraps the given stream. + * + * @param out the stream to delegate to + */ + public SwappedDataOutputStream(OutputStream out) { + super(new DataOutputStream(out)); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + ((DataOutputStream) out).writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + ((DataOutputStream) out).writeByte(v); + } + + @Deprecated + @Override + public void writeBytes(String s) throws IOException { + ((DataOutputStream) out).writeBytes(s); + } + + /** + * Writes a char as specified by {@link DataOutputStream#writeChar(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeChar(int v) throws IOException { + writeShort(v); + } + + /** + * Writes a {@code String} as specified by {@link DataOutputStream#writeChars(String)}, except + * each character is written using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeChars(String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + writeChar(s.charAt(i)); + } + } + + /** + * Writes a {@code double} as specified by {@link DataOutputStream#writeDouble(double)}, except + * using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeDouble(double v) throws IOException { + EndianUtils.writeSwappedDouble(out, v); + } + + /** + * Writes a {@code float} as specified by {@link DataOutputStream#writeFloat(float)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeFloat(float v) throws IOException { + EndianUtils.writeSwappedFloat(out, v); + } + + /** + * Writes an {@code int} as specified by {@link DataOutputStream#writeInt(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeInt(int v) throws IOException { + EndianUtils.writeSwappedInteger(out, v); + } + + /** + * Writes a {@code long} as specified by {@link DataOutputStream#writeLong(long)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeLong(long v) throws IOException { + EndianUtils.writeSwappedLong(out, v); + } + + /** + * Writes a {@code short} as specified by {@link DataOutputStream#writeShort(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeShort(int v) throws IOException { + EndianUtils.writeSwappedShort(out, (short) v); + } + + @Override + public void writeUTF(String str) throws IOException { + ((DataOutputStream) out).writeUTF(str); + } + + @Override + public void close() throws IOException { + out.close(); + } +} \ No newline at end of file diff --git a/kdbx/src/main/resources/base.kdbx.xml b/kdbx-io/src/main/resources/base.kdbx.xml similarity index 95% rename from kdbx/src/main/resources/base.kdbx.xml rename to kdbx-io/src/main/resources/base.kdbx.xml index 5a791ae4..577c732a 100644 --- a/kdbx/src/main/resources/base.kdbx.xml +++ b/kdbx-io/src/main/resources/base.kdbx.xml @@ -1,22 +1,23 @@ - Keepass-Java-2 + KeePassJava2 New Database ${creationDate} diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java similarity index 93% rename from kdbx/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java index 14d21611..b356e5e8 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/hashedblock/HashBlockTest.java @@ -1,27 +1,28 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.hashedblock; -import org.junit.Test; - +import org.junit.jupiter.api.Test; import java.io.*; import java.util.Arrays; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java similarity index 86% rename from kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java index 586bcf81..ddcc8ece 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelperTest.java @@ -1,27 +1,29 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.format.Helpers; import java.io.PrintStream; import java.util.UUID; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** @@ -40,7 +42,7 @@ public void testBase64() throws Exception { assertEquals(hexString, convertedString.toUpperCase()); UUID uuid = Helpers.uuidFromBase64(inputString); - printStream.println("UUID: " + uuid.toString()); + printStream.println("UUID: " + uuid); assertEquals("d2d53c5c-5454-5f64-c293-9b669abb21b9", uuid.toString()); String uuidHex = Helpers.hexStringFromUuid(uuid); diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java similarity index 71% rename from kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java index c4ad11aa..080b3151 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/HelpersTest.java @@ -1,16 +1,33 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.format.Helpers; import java.io.PrintStream; import java.text.ParseException; import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; -import static org.junit.Assert.*; -import static org.linguafranca.pwdb.kdbx.Helpers.dateTimeFormatter; +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.format.Helpers.dateTimeFormatter; import static org.linguafranca.util.TestUtil.getTestPrintStream; public class HelpersTest { diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java similarity index 59% rename from kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java index f6a3b440..c01be012 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxHeaderTest.java @@ -1,21 +1,39 @@ +/* + * Copyright (c) 2025. Jo Rabin + * + * 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; -import com.google.common.io.CharStreams; -import com.google.common.io.LittleEndianDataInputStream; -import org.junit.Ignore; -import org.junit.Test; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.SwappedDataInputStream; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; import org.linguafranca.pwdb.hashedblock.HmacBlockInputStream; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxSerializer; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.StandardCharsets; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** @@ -24,23 +42,24 @@ public class KdbxHeaderTest { static PrintStream printStream = getTestPrintStream(); - @Test @Ignore // can be used for detailed HMAC debugging + @Disabled // can be used for detailed HMAC debugging public void getHmacStream() throws IOException { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); + assertNotNull(inputStream); KdbxHeader header = KdbxSerializer.readOuterHeader(inputStream, new KdbxHeader()); printStream.println("Version " + header.getVersion()); - KdbxCreds creds = new KdbxCreds("123".getBytes()); - assert inputStream != null; - //noinspection UnstableApiUsage - KdbxSerializer.readOuterHeaderVerification(header, creds, new LittleEndianDataInputStream(inputStream)); + KdbxCredentials creds = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); + KdbxSerializer.readOuterHeaderVerification(header, creds, new SwappedDataInputStream(inputStream)); HmacBlockInputStream hmacBlockInputStream = new HmacBlockInputStream(header.getHmacKey(creds), inputStream, true); - printStream.println(CharStreams.toString(new InputStreamReader(hmacBlockInputStream, StandardCharsets.UTF_8))); + String string = IOUtils.toString(hmacBlockInputStream, StandardCharsets.UTF_8); + printStream.println(string); } // check the correct version @Test public void loadAesAesHeader() throws IOException { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-AES.kdbx"); + assertNotNull(inputStream); KdbxHeader header = KdbxSerializer.readOuterHeader(inputStream, new KdbxHeader()); assertEquals(4, header.getVersion()); } @@ -49,6 +68,7 @@ public void loadAesAesHeader() throws IOException { @Test public void loadAesArgonHeader() throws IOException { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); + assertNotNull(inputStream); KdbxHeader header = KdbxSerializer.readOuterHeader(inputStream, new KdbxHeader()); assertEquals(4, header.getVersion()); } @@ -56,12 +76,14 @@ public void loadAesArgonHeader() throws IOException { @Test public void loadChaChaHeader() throws IOException { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-AES.kdbx"); + assertNotNull(inputStream); KdbxHeader header = KdbxSerializer.readOuterHeader(inputStream, new KdbxHeader()); assertEquals(4, header.getVersion()); } @Test public void loadChaChaArgonHeader() throws IOException { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-Argon2-Attachment.kdbx"); + assertNotNull(inputStream); KdbxHeader header = KdbxSerializer.readOuterHeader(inputStream, new KdbxHeader()); assertEquals(4, header.getVersion()); } diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java similarity index 55% rename from kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java index 31b8801f..d7e2e54e 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxKeyFileTest.java @@ -1,33 +1,37 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import com.google.common.io.CharStreams; -import org.junit.Test; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxKeyFile; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.StandardCharsets; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.linguafranca.pwdb.format.KdbxSerializer.createUnencryptedInputStream; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** @@ -38,17 +42,20 @@ public class KdbxKeyFileTest { static PrintStream printStream = getTestPrintStream(); private static void toConsole(InputStream is) throws IOException { - printStream.println(CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8))); + String string = IOUtils.toString(is, StandardCharsets.UTF_8); + printStream.println(string); } + /** * Test that we can load a key file and get a 32 byte base64 encoded value back */ @Test - public void testLoad() throws Exception { + public void testLoad() { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("KeyFileDatabase.key"); + assertNotNull(inputStream); byte[] key = KdbxKeyFile.load(inputStream); assertNotNull(key); - assertEquals(32, key.length);; + assertEquals(32, key.length); } /** @@ -57,45 +64,49 @@ public void testLoad() throws Exception { @Test public void testEmptyPasswordCreds() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("EmptyPassword.kdbx"); - Credentials credentials = new KdbxCreds(new byte[0]); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + Credentials credentials = new KdbxCredentials(new byte[0]); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } /** - Test for empty password with key file + * Test for empty password with key file */ @Test public void testEmptyPasswordKeyCreds() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("EmptyPasswordWithKey.kdbx"); + assertNotNull(inputStream); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("EmptyPasswordWithKey.key"); - assert inputStreamKeyFile != null; - Credentials credentials = new KdbxCreds(new byte[0], inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(new byte[0], inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } /** - Test for no master password with key + * Test for no master password with key */ @Test public void testNoPasswordKeyCreds() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("NoPasswordWithKey.kdbx"); + assertNotNull(inputStream); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("NoPasswordWithKey.key"); - assert inputStreamKeyFile != null; - Credentials credentials = new KdbxCreds(inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } /** - Test for empty password + * Test for empty password */ @Test public void testEmptyPassword() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("EmptyPassword.kdbx"); - Credentials credentials = new KdbxCreds(new byte[0]); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + Credentials credentials = new KdbxCredentials(new byte[0]); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } @@ -106,21 +117,28 @@ public void testEmptyPassword() throws Exception { public void testSignedKeyFile() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("kdbx_hash_test.kdbx"); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("kdbx_hash_test.keyx"); - Credentials credentials = new KdbxCreds("123".getBytes(), inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES, inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } - /** + /** * Test hash fails in KeyFile (v2.0) */ - @Test(expected = RuntimeException.class) + @Test public void testSignatureFails() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("kdbx_hash_test.kdbx"); - InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("kdbx_hash_test_wrong_hash.keyx"); - Credentials credentials = new KdbxCreds("123".getBytes(), inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); - toConsole(decryptedInputStream); + InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("kdbx_hash_test_wrong_hash" + + ".keyx"); + assertNotNull(inputStream); + assertNotNull(inputStreamKeyFile); + Throwable throwable = assertThrows(RuntimeException.class, () -> { + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES, inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + toConsole(decryptedInputStream); + }); } /** @@ -130,8 +148,10 @@ public void testSignatureFails() throws Exception { public void testKeyFileRandom() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("kdb_with_random_file.kdbx"); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("random_file"); - Credentials credentials = new KdbxCreds("123".getBytes(), inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES, inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } @@ -142,8 +162,10 @@ public void testKeyFileRandom() throws Exception { public void testKeyFileHex64() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("kdbx_keyfile64.kdbx"); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("keyfile64"); - Credentials credentials = new KdbxCreds("123".getBytes(), inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES, inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } @@ -154,8 +176,10 @@ public void testKeyFileHex64() throws Exception { public void testKeyFile32() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("kdbx_keyfile32.kdbx"); InputStream inputStreamKeyFile = getClass().getClassLoader().getResourceAsStream("keyfile32"); - Credentials credentials = new KdbxCreds("123".getBytes(), inputStreamKeyFile); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); + assertNotNull(inputStream); + assertNotNull(inputStreamKeyFile); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES, inputStreamKeyFile); + InputStream decryptedInputStream = createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); toConsole(decryptedInputStream); } } diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java similarity index 78% rename from kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java index 27d8011d..ed43e01d 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/KdbxSerializerTest.java @@ -1,30 +1,35 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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; -import com.google.common.io.CharStreams; -import org.junit.Test; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; import org.linguafranca.pwdb.Credentials; +import org.linguafranca.pwdb.KdbConsts; +import org.linguafranca.pwdb.format.KdbxCredentials; +import org.linguafranca.pwdb.format.KdbxHeader; +import org.linguafranca.pwdb.format.KdbxSerializer; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Scanner; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** @@ -40,9 +45,10 @@ public class KdbxSerializerTest { @Test public void testGetPlainTextInputStream() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx"); - Credentials credentials = new KdbxCreds("123".getBytes()); + assertNotNull(inputStream); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); - printStream.println(CharStreams.toString(new InputStreamReader(decryptedInputStream, StandardCharsets.UTF_8))); + printStream.println(IOUtils.toString(decryptedInputStream, StandardCharsets.UTF_8)); } /** @@ -51,9 +57,10 @@ public void testGetPlainTextInputStream() throws Exception { @Test public void testGetPlainTextInputStream2() throws Exception { InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-AES.kdbx"); - Credentials credentials = new KdbxCreds("123".getBytes()); + assertNotNull(inputStream); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); - printStream.println(CharStreams.toString(new InputStreamReader(decryptedInputStream, StandardCharsets.UTF_8))); + printStream.println(IOUtils.toString(decryptedInputStream, StandardCharsets.UTF_8)); } @@ -65,7 +72,7 @@ public void testGetPlainTextInputStream2() throws Exception { public void testCypherTextOutputStream() throws Exception { File tempFile = File.createTempFile("test", "test"); OutputStream testStream = new FileOutputStream(tempFile); - Credentials credentials = new KdbxCreds("123".getBytes()); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); OutputStream outputStream = KdbxSerializer.createEncryptedOutputStream(credentials, new KdbxHeader(), testStream); outputStream.write("Hello World\n".getBytes()); @@ -85,7 +92,7 @@ public void testCypherTextOutputStream() throws Exception { public void testCypherTextOutputStream2() throws Exception { File tempFile = File.createTempFile("test", "test"); OutputStream testStream = new FileOutputStream(tempFile); - Credentials credentials = new KdbxCreds("123".getBytes()); + Credentials credentials = new KdbxCredentials(KdbConsts.PASSWORD_BYTES); KdbxHeader kdbxHeader = new KdbxHeader(4); OutputStream outputStream = KdbxSerializer.createEncryptedOutputStream(credentials, kdbxHeader, testStream); diff --git a/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/package-info.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/package-info.java new file mode 100644 index 00000000..37dff8e8 --- /dev/null +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/kdbx/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025. Jo Rabin + * Copyright (c) 2026. Sergio Lissner + * + * 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. + * + */ + +@NullMarked +package org.linguafranca.pwdb.kdbx; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/kdbx/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java b/kdbx-io/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java similarity index 90% rename from kdbx/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java rename to kdbx-io/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java index 30a77ed6..f7a455c4 100644 --- a/kdbx/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java +++ b/kdbx-io/src/test/java/org/linguafranca/pwdb/security/StreamEncryptionTest.java @@ -1,28 +1,29 @@ /* - * Copyright 2015 Jo Rabin + * Copyright (c) 2025. Jo Rabin * * 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 + * 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.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.linguafranca.pwdb.security.StreamEncryptor.*; import java.io.PrintStream; import java.security.SecureRandom; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.linguafranca.util.TestUtil.getTestPrintStream; /** diff --git a/kdbx-io/src/test/resources/Attachment.kdbx b/kdbx-io/src/test/resources/Attachment.kdbx new file mode 100644 index 00000000..12a1c1bb Binary files /dev/null and b/kdbx-io/src/test/resources/Attachment.kdbx differ diff --git a/test/src/main/resources/EmptyPassword.kdbx b/kdbx-io/src/test/resources/EmptyPassword.kdbx similarity index 100% rename from test/src/main/resources/EmptyPassword.kdbx rename to kdbx-io/src/test/resources/EmptyPassword.kdbx diff --git a/test/src/main/resources/EmptyPasswordWithKey.kdbx b/kdbx-io/src/test/resources/EmptyPasswordWithKey.kdbx similarity index 100% rename from test/src/main/resources/EmptyPasswordWithKey.kdbx rename to kdbx-io/src/test/resources/EmptyPasswordWithKey.kdbx diff --git a/test/src/main/resources/EmptyPasswordWithKey.key b/kdbx-io/src/test/resources/EmptyPasswordWithKey.key similarity index 100% rename from test/src/main/resources/EmptyPasswordWithKey.key rename to kdbx-io/src/test/resources/EmptyPasswordWithKey.key diff --git a/test/src/main/resources/KeyFileDatabase.key b/kdbx-io/src/test/resources/KeyFileDatabase.key similarity index 95% rename from test/src/main/resources/KeyFileDatabase.key rename to kdbx-io/src/test/resources/KeyFileDatabase.key index 6f63430b..aade8cbf 100755 --- a/test/src/main/resources/KeyFileDatabase.key +++ b/kdbx-io/src/test/resources/KeyFileDatabase.key @@ -1,9 +1,9 @@ - - - - 1.00 - - - uE9L7SGiT0+PrSvZ0oIzaLjtpsiu50ciR/JXc44Pw24= - - + + + + 1.00 + + + uE9L7SGiT0+PrSvZ0oIzaLjtpsiu50ciR/JXc44Pw24= + + diff --git a/test/src/main/resources/NoPasswordWithKey.kdbx b/kdbx-io/src/test/resources/NoPasswordWithKey.kdbx similarity index 100% rename from test/src/main/resources/NoPasswordWithKey.kdbx rename to kdbx-io/src/test/resources/NoPasswordWithKey.kdbx diff --git a/test/src/main/resources/NoPasswordWithKey.key b/kdbx-io/src/test/resources/NoPasswordWithKey.key similarity index 95% rename from test/src/main/resources/NoPasswordWithKey.key rename to kdbx-io/src/test/resources/NoPasswordWithKey.key index 6fdee6b8..300da4d5 100755 --- a/test/src/main/resources/NoPasswordWithKey.key +++ b/kdbx-io/src/test/resources/NoPasswordWithKey.key @@ -1,9 +1,9 @@ - - - - 1.00 - - - RJS69T/8tKYwO/fXPNGdtHlgLM5VfJmBDQLOlsg1AcE= - - + + + + 1.00 + + + RJS69T/8tKYwO/fXPNGdtHlgLM5VfJmBDQLOlsg1AcE= + + diff --git a/kdbx-io/src/test/resources/V4-AES-AES.kdbx b/kdbx-io/src/test/resources/V4-AES-AES.kdbx new file mode 100644 index 00000000..da60da3b Binary files /dev/null and b/kdbx-io/src/test/resources/V4-AES-AES.kdbx differ diff --git a/kdbx-io/src/test/resources/V4-AES-Argon2.kdbx b/kdbx-io/src/test/resources/V4-AES-Argon2.kdbx new file mode 100755 index 00000000..5d9e5794 Binary files /dev/null and b/kdbx-io/src/test/resources/V4-AES-Argon2.kdbx differ diff --git a/kdbx-io/src/test/resources/V4-ChaCha20-AES.kdbx b/kdbx-io/src/test/resources/V4-ChaCha20-AES.kdbx new file mode 100644 index 00000000..9f33a1f1 Binary files /dev/null and b/kdbx-io/src/test/resources/V4-ChaCha20-AES.kdbx differ diff --git a/kdbx-io/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx b/kdbx-io/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx new file mode 100755 index 00000000..59b73e50 Binary files /dev/null and b/kdbx-io/src/test/resources/V4-ChaCha20-Argon2-Attachment.kdbx differ diff --git a/kdbx-io/src/test/resources/junit-platform.properties b/kdbx-io/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..2115a2f1 --- /dev/null +++ b/kdbx-io/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled = true diff --git a/test/src/main/resources/kdb_with_random_file.kdbx b/kdbx-io/src/test/resources/kdb_with_random_file.kdbx similarity index 100% rename from test/src/main/resources/kdb_with_random_file.kdbx rename to kdbx-io/src/test/resources/kdb_with_random_file.kdbx diff --git a/test/src/main/resources/kdbx_hash_test.kdbx b/kdbx-io/src/test/resources/kdbx_hash_test.kdbx similarity index 100% rename from test/src/main/resources/kdbx_hash_test.kdbx rename to kdbx-io/src/test/resources/kdbx_hash_test.kdbx diff --git a/test/src/main/resources/kdbx_hash_test.keyx b/kdbx-io/src/test/resources/kdbx_hash_test.keyx similarity index 95% rename from test/src/main/resources/kdbx_hash_test.keyx rename to kdbx-io/src/test/resources/kdbx_hash_test.keyx index 31d047f3..e5254af2 100755 --- a/test/src/main/resources/kdbx_hash_test.keyx +++ b/kdbx-io/src/test/resources/kdbx_hash_test.keyx @@ -1,12 +1,12 @@ - - - - 2.0 - - - - 66F110CA E32995B5 EFA8E672 1E70C773 - 48F8E260 EEDD8744 93F41803 5BFDC27D - - + + + + 2.0 + + + + 66F110CA E32995B5 EFA8E672 1E70C773 + 48F8E260 EEDD8744 93F41803 5BFDC27D + + \ No newline at end of file diff --git a/test/src/main/resources/kdbx_hash_test_wrong_hash.keyx b/kdbx-io/src/test/resources/kdbx_hash_test_wrong_hash.keyx similarity index 95% rename from test/src/main/resources/kdbx_hash_test_wrong_hash.keyx rename to kdbx-io/src/test/resources/kdbx_hash_test_wrong_hash.keyx index 5c7b37e2..2bfa7c09 100755 --- a/test/src/main/resources/kdbx_hash_test_wrong_hash.keyx +++ b/kdbx-io/src/test/resources/kdbx_hash_test_wrong_hash.keyx @@ -1,12 +1,12 @@ - - - - 2.0 - - - - 66F110CA E32995B5 EFA8E672 1E70C773 - 48F8E260 EEDD8744 93F41803 5BFDC27D - - + + + + 2.0 + + + + 66F110CA E32995B5 EFA8E672 1E70C773 + 48F8E260 EEDD8744 93F41803 5BFDC27D + + \ No newline at end of file diff --git a/test/src/main/resources/kdbx_keyfile32.kdbx b/kdbx-io/src/test/resources/kdbx_keyfile32.kdbx similarity index 100% rename from test/src/main/resources/kdbx_keyfile32.kdbx rename to kdbx-io/src/test/resources/kdbx_keyfile32.kdbx diff --git a/test/src/main/resources/kdbx_keyfile64.kdbx b/kdbx-io/src/test/resources/kdbx_keyfile64.kdbx similarity index 100% rename from test/src/main/resources/kdbx_keyfile64.kdbx rename to kdbx-io/src/test/resources/kdbx_keyfile64.kdbx diff --git a/test/src/main/resources/keyfile32 b/kdbx-io/src/test/resources/keyfile32 similarity index 100% rename from test/src/main/resources/keyfile32 rename to kdbx-io/src/test/resources/keyfile32 diff --git a/test/src/main/resources/keyfile64 b/kdbx-io/src/test/resources/keyfile64 similarity index 100% rename from test/src/main/resources/keyfile64 rename to kdbx-io/src/test/resources/keyfile64 diff --git a/test/src/main/resources/random_file b/kdbx-io/src/test/resources/random_file similarity index 100% rename from test/src/main/resources/random_file rename to kdbx-io/src/test/resources/random_file diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCredentials.java b/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCredentials.java deleted file mode 100644 index 3863dbee..00000000 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/KdbxCredentials.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.security.Encryption; -import org.jetbrains.annotations.NotNull; - -import java.io.InputStream; -import java.security.MessageDigest; - -/** - * The class holds subclasses of {@link Credentials} appropriate to KDBX files. - * - * @author jo - * @deprecated use KdbxCreds instead - */ -@Deprecated -@SuppressWarnings("deprecation") -public interface KdbxCredentials extends Credentials { - - /** - * Class for KDBX key file with password credentials - */ - class KeyFile implements KdbxCredentials { - - private final byte[] key; - - /** - * Constructor for password with KDBX Keyfile - * @param password Master Password (new byte[0] if empty, not none) - * @param inputStream inputstream of the keyfile - */ - public KeyFile(@NotNull byte[] password, @NotNull InputStream inputStream) { - MessageDigest md = Encryption.getSha256MessageDigestInstance(); - byte[] pwKey = md.digest(password); - md.update(pwKey); - - byte[] keyFileData = KdbxKeyFile.load(inputStream); - if (keyFileData == null) { - throw new IllegalStateException("Could not read key file"); - } - this.key = md.digest(keyFileData); - } - - /** - * Constructor for KDBX Keyfile with no password - * @param inputStream inputstream of the keyfile - */ - public KeyFile(@NotNull InputStream inputStream) { - MessageDigest md = Encryption.getSha256MessageDigestInstance(); - - byte[] keyFileData = KdbxKeyFile.load(inputStream); - if (keyFileData == null) { - throw new IllegalStateException("Could not read key file"); - } - this.key = md.digest(keyFileData); - } - - @Override - public byte[] getKey() { - return key; - } - } - - /** - * Class for KDBX password only credentials - */ - class Password implements KdbxCredentials { - - private final byte[] key; - - public Password(@NotNull byte[] password) { - MessageDigest md = Encryption.getSha256MessageDigestInstance(); - byte[] digest = md.digest(password); - key = md.digest(digest); - } - - @Override - public byte[] getKey() { - return key; - } - } -} diff --git a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomHelper.java b/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomHelper.java deleted file mode 100644 index 7fcb9f70..00000000 --- a/kdbx/src/main/java/org/linguafranca/pwdb/kdbx/dom/DomHelper.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.dom; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import java.time.Instant; -import java.util.*; - -/** - * The class contains static helper methods for access to the underlying XML DOM - * - * @author jo - */ -public class DomHelper { - - public static XPath xpath = XPathFactory.newInstance().newXPath(); - - static final String GROUP_ELEMENT_NAME = "Group"; - static final String ENTRY_ELEMENT_NAME = "Entry"; - static final String ICON_ELEMENT_NAME = "IconID"; - static final String UUID_ELEMENT_NAME = "UUID"; - static final String NAME_ELEMENT_NAME = "Name"; - static final String NOTES_ELEMENT_NAME = "Notes"; - static final String TIMES_ELEMENT_NAME = "Times"; - static final String IS_EXPANDED = "IsExpanded"; - - static final String HISTORY_ELEMENT_NAME = "History"; - - static final String LAST_MODIFICATION_TIME_ELEMENT_NAME = "Times/LastModificationTime"; - static final String CREATION_TIME_ELEMENT_NAME = "Times/CreationTime"; - static final String LAST_ACCESS_TIME_ELEMENT_NAME = "Times/LastAccessTime"; - static final String EXPIRY_TIME_ELEMENT_NAME = "Times/ExpiryTime"; - static final String EXPIRES_ELEMENT_NAME = "Times/Expires"; - static final String USAGE_COUNT_ELEMENT_NAME = "Times/UsageCount"; - static final String LOCATION_CHANGED = "Times/LocationChanged"; - - static final String PROPERTY_ELEMENT_FORMAT = "String[Key/text()='%s']"; - static final String BINARY_PROPERTY_ELEMENT_FORMAT = "Binary[Key/text()='%s']"; - static final String VALUE_ELEMENT_NAME = "Value"; - - static final String RECYCLE_BIN_UUID_ELEMENT_NAME = "RecycleBinUuid"; - static final String RECYCLE_BIN_ENABLED_ELEMENT_NAME = "RecycleBinEnabled"; - static final String RECYCLE_BIN_CHANGED_ELEMENT_NAME = "RecycleBinChanged"; - - interface ValueCreator { - String getValue(); - } - - static class ConstantValueCreator implements ValueCreator { - String value; - ConstantValueCreator(String value) { - this.value = value; - } - @Override - public String getValue() { - return value; - } - } - - static class DateValueCreator implements ValueCreator { - @Override - public String getValue() { - return Helpers.fromDate(Date.from(Instant.now())); - } - } - - static class UuidValueCreator implements ValueCreator { - @Override - public String getValue() { - return base64RandomUuid(); - } - - } - - static void ensureElements (Element element, Map childElements) { - for (Map.Entry entry: childElements.entrySet()) { - ensureElementContent(entry.getKey(), element, entry.getValue().getValue()); - } - } - - - @Nullable @Contract("_,_,true -> !null") - static Element getElement(String elementPath, Element parentElement, boolean create) { - try { - Element result = (Element) xpath.evaluate(elementPath, parentElement, XPathConstants.NODE); - if (result == null && create) { - result = createHierarchically(elementPath, parentElement); - } - return result; - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - - static boolean removeElement(String elementPath, Element parentElement) { - Element toRemove = getElement(elementPath, parentElement, false); - if (toRemove == null) { - return false; - } else { - toRemove.getParentNode().removeChild(toRemove); - return true; - } - } - - static List getElements (String elementPath, Element parentElement) { - try { - NodeList nodes = (NodeList) xpath.evaluate(elementPath, parentElement, XPathConstants.NODESET); - ArrayList result = new ArrayList<>(nodes.getLength()); - for (int i = 0; i < nodes.getLength(); i++) { - result.add(((Element) nodes.item(i))); - } - return result; - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - - static int getElementsCount (String elementPath, Element parentElement) { - try { - NodeList nodes = (NodeList) xpath.evaluate(elementPath, parentElement, XPathConstants.NODESET); - return nodes.getLength(); - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - - @Nullable - static Element newElement(String elementName, Element parentElement) { - Element newElement = parentElement.getOwnerDocument().createElement(elementName); - parentElement.appendChild(newElement); - return newElement; - } - - @Nullable - public static String getElementContent(String elementPath, Element parentElement) { - Element result = getElement(elementPath, parentElement, false); - return (result == null) ? null : result.getTextContent(); - } - - @NotNull - public static String ensureElementContent(String elementPath, Element parentElement, @NotNull String value) { - Element result = getElement(elementPath, parentElement, false); - if (result == null) { - result = createHierarchically(elementPath, parentElement); - } - result.setTextContent(value); - return result.getTextContent(); - } - - @NotNull - public static Element setElementContent(String elementPath, Element parentElement, String value) { - Element result = getElement(elementPath, parentElement, true); - result.setTextContent(value); - return result; - } - - @Nullable - static byte[] getBinaryElementContent(String elementPath, Element parentElement) { - Element result = getElement(elementPath, parentElement, false); - if (result == null) { - return null; - } - String id = result.getAttribute("Ref"); - Element content = getElement("//Binaries/Binary[@ID=" + id + "]", parentElement.getOwnerDocument().getDocumentElement(),false); - if (content == null) { - throw new IllegalStateException("Could not find binary content with ID " + id); - } - return Helpers.decodeBase64Content(content.getTextContent().getBytes(), content.hasAttribute("Compressed")); - } - - @NotNull - static Element setBinaryElementContent(String elementPath, Element parentElement, byte[] value) { - try { - String b64 = Helpers.encodeBase64Content(value, true); - - //Find the highest numbered existing content - String max = xpath.evaluate("//Binaries/Binary/@ID[not(. < ../../Binary/@ID)][1]", parentElement.getOwnerDocument().getDocumentElement()); - if (max.trim().isEmpty()){ - max = "-1"; - } - Integer newIndex = Integer.valueOf(max) + 1; - - addBinary(parentElement.getOwnerDocument().getDocumentElement(), b64, newIndex); - - Element result = getElement(elementPath, parentElement, true); - result.setAttribute("Ref", newIndex.toString()); - - - return result; - - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - - /** - * Add a binary property value to the V3 Meta/Binaries element - * - * @param documentElement the document element - * @param b64 a base64 gzipped encoded representation of the binary content - * @param index the index by which it is known - */ - public static void addBinary(Element documentElement, String b64, Integer index) { - Element binaries = getElement("Meta/Binaries", documentElement,true); - Element binary = (Element) binaries.appendChild(binaries.getOwnerDocument().createElement("Binary")); - binary.setTextContent(b64); - binary.setAttribute("Compressed", "True"); - binary.setAttribute("ID", index.toString()); - } - - public static String getBinary(Element documentElement, Integer index) { - Element binaries = getElement("Meta/Binaries", documentElement,false); - if (Objects.isNull(binaries)){ - throw new IllegalArgumentException("No binaries found"); - } - for (int i = 0; i < binaries.getChildNodes().getLength(); i++){ - if (((Element) binaries.getChildNodes().item(i)).getAttribute("ID").equals(index.toString())) { - return ((Element) binaries.getChildNodes().item(i)).getTextContent(); - } - } - throw new IllegalArgumentException("No binary with that index found"); - } - - public static int getBinaryCount(Element documentElement) { - Element binaries = getElement("Meta/Binaries", documentElement,false); - if (Objects.isNull(binaries)) { - return 0; - } - return binaries.getChildNodes().getLength(); - } - - @NotNull - static Element touchElement(String elementPath, Element parentElement) { - return setElementContent(elementPath, parentElement, Helpers.fromDate(new Date())); - } - - private static Element createHierarchically(String elementPath, Element startElement) { - Element currentElement = startElement; - for (String elementName : elementPath.split("/")) { - try { - Element nextElement = (Element) xpath.evaluate(elementName, currentElement, XPathConstants.NODE); - if (nextElement == null) { - nextElement = (Element) currentElement.appendChild(currentElement.getOwnerDocument().createElement(elementName)); - } - currentElement = nextElement; - } catch (XPathExpressionException e) { - throw new IllegalStateException(e); - } - } - return currentElement; - } - - static String base64RandomUuid () { - return Helpers.base64FromUuid(UUID.randomUUID()); - } - -} diff --git a/logback.xml b/logback.xml index 972c5a5f..30f917a1 100644 --- a/logback.xml +++ b/logback.xml @@ -1,3 +1,20 @@ + + - -%highlight(%-5level) %d{HH:mm:ss.SSS} [%thread] %logger{36} - %cyan(%marker) %msg%n + -%highlight(%-5level) %d{HH:mm:ss.SSS} [%thread] %logger{36} - %cyan(%-8marker) %msg %highlight(%X{result}) %n diff --git a/pom.xml b/pom.xml index e74cd2b5..6f1c4b76 100644 --- a/pom.xml +++ b/pom.xml @@ -1,291 +1,375 @@ - - 4.0.0 + + 4.0.0 + + mh.keepass + KeePassJava2.parent + 1.0.0.1 + + database + kdb + kdbx-io + kdbx-database + all + example + basic + + pom - org.linguafranca.pwdb - KeePassJava2-parent - 2.2.3-SNAPSHOT - - database - test - kdb - kdbx - dom - simple - jaxb - jackson - all - example - - pom + KeePassJava2 + A Java 25 API for databases compatible with the renowned KeePass password safe for Windows + https://github.com/sergmain/KeePassJava2 - KeePassJava2 - A Java 8 API for databases compatible with the renowned KeePass password safe for Windows - https://github.com/jorabin/KeePassJava2 + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + scm:git:git://github.com/sergmain/KeePassJava2.git + scm:git:git@github.com:sergmain/KeePassJava2.git + https://github.com/sergmain/KeePassJava2 + KeePassJava2-3.0.0 + - - scm:git:git://github.com/jorabin/KeePassJava2.git - scm:git:git@github.com:jorabin/KeePassJava2.git - https://github.com/jorabin/KeePassJava2 - KeePassJava2-2.2.2 - + + + Jo Rabin + Europe/London + https://www.linkedin.com/in/jorabin + + Creator + + + + Sergio Lissner + + AI Supervising Engineer + + + - - - Jo Rabin - Europe/London - https://www.linkedin.com/in/jorabin - - Creator - - - + + UTF-8 + 25 - - UTF-8 - 1.8 + 3.15.0 + 3.5.2 + 1.7.0 + 3.3.1 + 3.11.2 + 3.2.7 + 3.1.1 + 3.4.2 + 3.8.1 + 3.4.0 + 2.18.0 + 3.5.0 - 3.11.0 - 3.0.0 - 1.6.13 - 3.2.1 - 3.5.0 - 3.0.1 - 3.0.0 - 3.0.2 - - false - + + false + - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - false - - - ${inhibitConsoleOutput} - - - - ${maven.multiModuleProjectDirectory}/logback.xml - - - - - listener - org.linguafranca.util.MavenRunListener - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin.version} - - - - ${project.groupId}.${project.artifactId} - - - - - - + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + false + + + ${inhibitConsoleOutput} + + + + ${maven.multiModuleProjectDirectory}/logback.xml + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + ${project.groupId}.${project.artifactId} + + + + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + + - + org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-release-plugin - ${maven-release-plugin.version} - - true - false - releaseToMaven - deploy + --enable-preview + -parameters + ${java.version} + ${project.build.sourceEncoding} + true + - org.apache.maven.plugins - maven-dependency-plugin - 3.6.0 - - - + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + true + false + releaseToMaven + deploy + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + maven-clean-plugin + ${maven-clean-plugin.version} + + + + testOutput + false + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-maven + + enforce + + + + + + 3.6.3 + + + + + + + + - - - junit - junit - - - org.slf4j - slf4j-api - 2.0.12 - - - ch.qos.logback - logback-classic - - + + + org.jspecify + jspecify + 1.0.0 + provided + + + + org.junit.platform + junit-platform-launcher + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.slf4j + slf4j-api + test + + + ch.qos.logback + logback-classic + test + + - - - - org.jetbrains - annotations - 24.0.1 - - - - com.google.guava - guava - 32.1.2-android - - - org.bouncycastle - bcpkix-jdk18on - 1.78.1 - - - org.slf4j - slf4j-api - 2.0.5 - - - - ch.qos.logback - logback-classic - 1.3.14 - - - commons-codec - commons-codec - 1.15 - + + + + org.jspecify + jspecify + 1.0.0 + provided + + + + org.bouncycastle + bcpkix-jdk18on + 1.84 + + + org.slf4j + slf4j-api + 2.0.17 + + + + ch.qos.logback + logback-classic + 1.5.32 + + + commons-codec + commons-codec + 1.21.0 + - junit - junit - 4.13.2 - + + commons-io + commons-io + 2.21.0 - - + + org.junit + junit-bom + 5.14.3 + pom + import + + + - - - inhibitConsoleOutput - - true - - - - releaseToMaven - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar-no-fork - - - - + + + + inhibitConsoleOutput + + true + + + + + releaseToMaven + + - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - html, syntax - - - - - - - - + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + html, syntax + + + + + + + + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - + + + central + https://central.sonatype.com/repository/maven-snapshots/ + + diff --git a/readme.md b/readme.md index fa4bcaca..19e87fdf 100644 --- a/readme.md +++ b/readme.md @@ -3,26 +3,29 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.linguafranca.pwdb/KeePassJava2-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.linguafranca.pwdb/KeePassJava2-parent) [![javadoc](https://javadoc.io/badge2/org.linguafranca.pwdb/KeePassJava2/javadoc.svg)](https://javadoc.io/doc/org.linguafranca.pwdb/KeePassJava2) -![alt text](https://badgen.net/badge/Build/2.2.2/blue?icon=github) -master [![CircleCI](https://dl.circleci.com/status-badge/img/gh/jorabin/KeePassJava2/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jorabin/KeePassJava2/tree/master) -develop [![CircleCI](https://dl.circleci.com/status-badge/img/gh/jorabin/KeePassJava2/tree/develop.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jorabin/KeePassJava2/tree/develop) +![alt text](https://badgen.net/badge/Branch/master/yellow?icon=github) ![alt text](https://badgen.net/badge/Build/2.2.4/blue?icon=github) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/jorabin/KeePassJava2/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jorabin/KeePassJava2/tree/master) +![alt text](https://badgen.net/badge/Branch/develop/yellow?icon=github) ![alt text](https://badgen.net/badge/Build/2.2.5-SNAPSHOT/blue?icon=github) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/jorabin/KeePassJava2/tree/develop.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jorabin/KeePassJava2/tree/develop) +![alt text](https://badgen.net/badge/Branch/v3/yellow?icon=github) ![alt text](https://badgen.net/badge/Build/3.0.0-SNAPSHOT/blue?icon=github) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/jorabin/KeePassJava2/tree/develop.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jorabin/KeePassJava2/tree/v3) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/fe9059ac4d384b929f452149b9246658)](https://app.codacy.com/gh/jorabin/KeePassJava2/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) -A Java 8 API for databases compatible with the renowned [KeePass](http://keepass.info) password + +Java 11 API (from version 3.0.0 upwards) for password databases compatible with the renowned [KeePass](http://keepass.info) password safe for Windows. This is a "headless" implementation - if you want something with a UI then [KeePassXC](https://keepassxc.org/) and [KeePassDX](https://www.keepassdx.com/) could be just the things for you. Features to date: -- Read and write KeePass 2.x format (KDBX file formats V3 and V4) +- Read and write KeePass 2.x format (KDBX file formats V3.1, V4 and V4.1) - Keepass 2.x Password and Keyfile Credentials -- Read KeePass 1.x format (Rijndael only) +- Pluggable memory storage and protection strategy +- Read KeePass 1.x format (KDB format, Rijndael only) - *No* requirement for JCE Policy Files - Android compatible - Interfaces for Database, Group and Entry allow compatible addition of other formats + It is licensed under the Apache 2 License and is currently usable. The work is provided on an "AS IS" BASIS, WITHOUT @@ -39,12 +42,20 @@ It is licensed under the Apache 2 License and is currently usable. ## Current Status -The current code is version 2.2.2 - released to Maven September 2024. This is on the main branch. See [Build from Source](#build-from-source) +This version is 3.0.0-SNAPSHOT. It is the intention that maintenance will cease +on version 2 at some point during 2025 once this version is released. +Upgrade to V3 requires minor changes to V2 code. + +The current released version is version 2.2.4 - released to Maven March 2025. +This is on the main branch. See [Build from Source](#build-from-source) -Key updates relative to 2.1: -- Java 8 (dependencies no longer support Java 7) +Key updates relative to 2.x +- Java 11 +- Pluggable (protected) data storage model - File format version 4 support - with Argon2 -- Inclusion of Jackson based KDBX support with a view to removing SimpleXML, JAXB and JAXB support +- Removal of SimpleXML, JAXB and JAXB database implementations +- Removed generics on database classes +- Refactor modules and packages - Updated keyfile support - Updated dependencies @@ -54,58 +65,53 @@ See the [changelog](CHANGELOG.md) for more details. ### Release -The composite POM for the last release (2.2.2), Java 8 compatible, is - - org.linguafranca.pwdb - KeePassJava2 - 2.2.2 - -at Maven Central. Note that the artifactId has become Camel Case from release 2.1.x onwards. +For the POM for the last release, see the [main branch](https://github.com/jorabin/KeePassJava2/tree/master). +As noted, Version 2.x will no longer be maintained once this version is released. ### Snapshot -Snapshot builds are erratically available at [Sonatype](https://oss.sonatype.org/content/repositories/snapshots/org/linguafranca/pwdb/): +Snapshot builds, such as this version 3.0.0, are available at [Sonatype](https://oss.sonatype.org/content/repositories/snapshots/org/linguafranca/pwdb/) e.g.: org.linguafranca.pwdb KeePassJava2 - 2.2.3-SNAPSHOT + 3.0.0-SNAPSHOT with appropriate `` entry, like: - oss.sonatype.org-snapshot - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - + oss.sonatype.org-snapshot + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + - + - There are also separate POMs for the various modules. The module structure is illustrated below + The module structure is illustrated below under [Build from Source](#build-from-source). ## Java Version -From release 2.2 it requires Java 1.8. Earlier versions require Java 1.7. +Versions 3.0.0 onwards require Java 11. From version 2.2 Java 1.8 is required. Earlier versions require Java 1.7. ## Quick Start Create credentials and an input stream for the password vault in question: - KdbxCreds creds = new KdbxCreds("123".getBytes()); + KdbxCredentials credentials = new KdbxCredentials("123".getBytes()); InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test1.kdbx"); -then choose the Jackson based database implementation, and load the database +then load the database: - Database database = JacksonDatabase.load(credentials, inputStream) + Database database = KdbxDatabase.load(credentials, inputStream) -See below for discussion of other database implementations. Note that they will not be -maintained in the future - and see the following regarding making the storage of -passwords more secure. +> In the past there were a number of different database implementations, at present there +are two, one for KDBX (`KdbxDatabase` - previously called `JacksonDatabase`, because it uses Jackson for XML serialization) +and one to support the KeePass V2 KDB format (`KdbDatabase`). ### Storing Passwords @@ -113,16 +119,18 @@ There are numerous well-understood problems with storing passwords as Strings in Java. See [this discussion](./PropertyValueProtection.md) about the KeePassJava2 approach to storing passwords. -> Available from release 2.2.3 the enhanced **Jackson implementation** is the only KeePassJava2 database implementation that provides a -means of storing passwords other than as String. - ### Discussion Password databases are modelled as a three layer abstraction. -A *Database* is a collection of records whose physical representation needs only to be capable of rendering as a stream. *Entries* hold the information of value in the database and *Groups* allow the structuring of entries into collections, just like a folder structure. +A *Database* is a collection of records whose physical representation needs only to be capable +of rendering as a stream. *Entries* hold the information of value in the database and *Groups* +allow the structuring of entries into collections, just like a folder structure. -The Database has a root group and by following subgroups of the root group the tree structure of the database can be navigated. Entries belong to groups. Entries can be moved between groups and groups can also be moved between groups. However, entries and groups created in one database cannot be moved to another database without being converted: +The Database has a root group and by following subgroups of the root group the tree structure of +the database can be navigated. Entries belong to groups. Entries can be moved between groups and groups +can also be moved between groups. However, entries and groups created in one database cannot be moved to +another database without being converted: database.newEntry(entryToCopy); database.newGroup(groupToCopy); @@ -133,7 +141,7 @@ The class Javadoc on Interface classes [Entry](http://javadoc.io/page/org.linguafranca.pwdb/database/latest/org/linguafranca/pwdb/Entry.html) describe how to use the methods of those classes to create and modify entries. These classes provide the basis of all implementations of the various database formats, -initially KDB, KDBX 3.1 and KDBX 4 (KeePass 2) file formats, subsequently, potentially, others. +KDBX 3.1, 4 and 4.1 (KeePass 2) as well as KDB (KeePass 1), file formats. The class [QuickStart.java](example/src/main/java/org/linguafranca/pwdb/kdbx/QuickStart.java) provides some illustrations of operations using the Database, Group and Entry interfaces. @@ -151,7 +159,8 @@ KeePass is in effect defined by the code that Dominik writes to create and maint [KDBX File Format Specification](https://keepass.info/help/kb/kdbx.html) describes the file format. There is also a discussion of the [differences between KDBX version 3.1 and version 4](https://keepass.info/help/kb/kdbx_4.html). Additionally, there is a discussion of the [enhancements in KDBX 4.1](https://keepass.info/help/kb/kdbx_4.1.html), as well -as a discussion of [Key Files](https://keepass.info/help/base/keys.html#keyfiles). +as a discussion of [Key Files](https://keepass.info/help/base/keys.html#keyfiles). While preparing release 2.2.3 I found [this XSD](https://keepass.info/help/download/KDBX_XML.xsd) at the +KeePass site. Massive credit also to the folks over at [KeePassXC](https://keepassxc.org/) who wrote some [documentation](https://github.com/keepassxreboot/keepassxc-specs) about their understanding of various format things. Also, this is a @@ -162,9 +171,8 @@ clarification and my own satisfaction I have written about my understanding of KeePass formats in the following locations: 1. The Javadoc header to [KdbxSerializer](http://javadoc.io/page/org.linguafranca.pwdb/KeePassJava2-kdbx/latest/org/linguafranca/pwdb/kdbx/stream_3_1/KdbxSerializer.html) describes KDBX stream formatting. -2. The XSD Schema [KDBX.4.xsd](KDBX.4.xsd) documents my understanding of the Keepass XML, and also my - lack of understanding, in parts. While preparing release 2.2.3 I found [this XSD](https://keepass.info/help/download/KDBX_XML.xsd) at the - KeePass site. I have not (so far) attempted to reconcile my documentation with it. +2. The XSD Schema [KDBX.4.1.xsd](./XSD/KDBX.4.1.xsd) documents my understanding of the Keepass XML, and also my + lack of understanding, in parts. 3. The following graphic illustrates KDBX 3.1 and 4 file formats: @@ -175,43 +183,22 @@ KeePass formats in the following locations: KeePass - or more specifically its file format KDBX - is an XML based format, so one of the main tasks is serializing and deserializing XML. Over time (KeePassJava2 was originally released in 2014) approaches to Java and XML have been a bit mysterious. However, Jackson has now been chosen as the -underlying framework for implementation of KeePassJava2. - -There are several other database implementations which will be maintained for bug-fix purposes -only, with a view to being withdrawn, since they perform badly and/or depend on obsolete technology. - -- `SimpleXML` - no longer maintained, does not work with Java 17 and up -- `JAXB` - this causes problems with `javax` and `jakarta` namespaces, it's not worth maintaining - as it offers no compelling performance or other advantage -- `DOM` the was the original implementation and validates the fact that DOM based implementations - are slow. That said, if you want to load a database and then save it while maintaining whatever quirks - existed in the original database then this is the one. +underlying framework for implementation of KeePassJava2. From 3.0.0 a single KDBX implementation is available. Aside from dependencies on underlying frameworks, different implementations have varying characteristics, primarily speed. This is assessed by [this test](https://github.com/jorabin/KeePassJava2/blob/master/example/src/main/java/org/linguafranca/pwdb/kdbx/OpenDbExample.java) in the module `examples`. ## Dependencies -Aside from the JRE, at release 2.2, the API depends on: +Aside from the JRE, at release 3.0.0, the API depends on: - [Google Guava](https://github.com/google/guava/wiki) ([Apache 2 license](https://github.com/google/guava/blob/master/COPYING)). - [Apache Commons Codec](https://commons.apache.org/proper/commons-codec/) ([Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)). - [Bouncy Castle](https://github.com/bcgit/bc-java/blob/master/LICENSE.html) ([MIT License](https://github.com/bcgit/bc-java/blob/master/LICENSE.html)). +- [Jackson XML](https://github.com/FasterXML/jackson-dataformat-xml) ([Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)) +- [Woodstox](https://github.com/FasterXML/woodstox) ([Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)) -The Jackson implementation depends on: - -- [Faster XML Jackson](https://github.com/FasterXML/jackson) - -The (historical) Simple XML implementation additionally depends on: - -- [Simple XML Serialisation Framework](http://simple.sourceforge.net/) ([Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0)). -- [Faster XML Aalto](https://github.com/FasterXML/aalto-xml) ([Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0.txt)). - -For Java 11 and later, Jaxb implementation depends on explicit inclusion [no longer provided by JDK](https://docs.oracle.com/en/java/javase/11/migrate/index.html#JSMIG-GUID-F640FA9D-FB66-4D85-AD2B-D931174C09A3) of: - -- [JAXB](https://javaee.github.io/jaxb-v2/) - -It also depends on SLF4J and Junit 4 for tests. +It also depends on SLF4J, logback and Junit 4 for tests. ## Build from Source @@ -233,68 +220,41 @@ Each module corresponds to a Maven artifact. The GroupId is `org.linguafranca.pw databasedatabase -Javadocs +Javadocs Base definition of the Database APIs. exampleexample -Javadocs +Javadocs Worked examples of loading, saving, splicing etc. using the APIs -testtest -Javadocs -Shared tests to assess the viability of the implementation. allKeePassJava2 (no JavaDoc) This is the main KeePassJava2 Maven dependency. Provides a route to all artifacts (other than test and examples) via transitive dependency. kdbKeePassJava2-kdb -Javadocs +Javadocs An implementation of the Database APIs supporting KeePass KDB format. -kdbxKeePassJava2-kdbx -Javadocs +kdbx-ioKeePassJava2-kdbx-io +Javadocs Provides support for KDBX streaming and security. -jacksonKeePassJava2-jackson -Javadocs -A Jackson based implementation of KDBX. Intended to provide the main support going forward, replacing trhe -modules below. - -simpleKeePassJava2-simple -Javadocs -A Simple XML Platform implementation of KDBX. Could be useful for Android. -Simple cannot be used with Java versions 17 and up. - -jaxbKeePassJava2-jaxb -Javadocs -A JAXB implementation of KDBX. Probably not useful for Android. The generated class -bindings might be useful for building other interfaces. - -domKeePassJava2-dom -Javadocs -A DOM based implementation of KDBX. Being DOM based it is rather slow, but -messes less with existing content than the other implementations. Known to work on Android. +kdbx-databaseKeePassJava2-kdbx-database +Javadocs +Provides support for KDBX data access and memory protection. +basicbasic + +Javadocs +A basic lightweight database implementation. Has memory protection. -Why are there so many implementations for KDBX? Well, the DOM implementation came first, because it can -load and save stuff that the implementation doesn't specifically know about. But it is very slow. - -Then came the JAXB implementation, but -belatedly it seems that Android support is in question. So latterly the Simple implementation. That was probably enough -KDBX implementations, however, the Simple XML library seems no longer to be maintained, -and along comes the Jackson Implementation. - -Jackson is the implementation going forward. - ### Gradle -If you prefer Gradle the automatic conversion `gradle init` has been known to convert the POM successfully, however you will -need to add something like [gradle-source-sets.txt](jaxb/gradle-source-sets.txt) to the `build.gradle` for the JAXB module, so that the generated sources - get compiled correctly. +If you prefer Gradle the automatic conversion `gradle init` has been known to convert the POM successfully. ## Change Log @@ -312,7 +272,7 @@ Thanks to other contributors and raisers of issues. ## License -Copyright (c) 2024 Jo Rabin +Copyright (c) 2025 Jo Rabin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/simple/pom.xml b/simple/pom.xml deleted file mode 100644 index 6e8e5078..00000000 --- a/simple/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - ../pom.xml - - 4.0.0 - - KeePassJava2-simple - KeePassJava2 :: Simple - Contains a Simple XML framework implementation of KDBX. - - - - org.linguafranca.pwdb - KeePassJava2-kdbx - ${project.version} - - - org.linguafranca.pwdb - test - ${project.version} - test - - - junit - junit - test - - - com.carrotsearch.thirdparty - simple-xml-safe - 2.7.1 - - - org.apache.httpcomponents - httpcore - 4.4.16 - - - com.fasterxml - aalto-xml - 1.3.2 - - - \ No newline at end of file diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabase.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabase.java deleted file mode 100644 index 7cafa52a..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabase.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.StreamConfiguration; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.base.AbstractDatabase; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxStreamFormat; -import org.linguafranca.pwdb.kdbx.simple.model.KeePassFile; -import org.linguafranca.pwdb.security.Encryption; -import org.linguafranca.pwdb.security.StreamEncryptor; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -import static org.linguafranca.pwdb.kdbx.simple.SimpleSerializableDatabase.createEmptyDatabase; -import static org.linguafranca.pwdb.kdbx.simple.SimpleSerializableDatabase.getSerializer; - -/** - * Implementation of {@link org.linguafranca.pwdb.Database} using the Simple XML framework. - * - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class SimpleDatabase extends AbstractDatabase{ - - KeePassFile keePassFile; - StreamFormat streamFormat; - - /** - * Create a new empty database - */ - public SimpleDatabase() { - this(createEmptyDatabase(), null); - } - - public SimpleDatabase(KeePassFile file, StreamFormat streamFormat) { - try { - keePassFile = file; - keePassFile.root.group.database = this; - this.streamFormat = streamFormat; - SimpleSerializableDatabase.fixUp(keePassFile.root.group); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - /** - * Load plaintext XML - * - * @param inputStream contains the XML - * @return a new Database - * @throws Exception on load failure - */ - public static SimpleDatabase loadXml(InputStream inputStream) throws Exception { - KeePassFile result = getSerializer(new StreamEncryptor.None()).read(KeePassFile.class, inputStream); - result.root.group.uuid = UUID.randomUUID(); - return new SimpleDatabase(result, null); - } - - /** - * Load kdbx file - * - * @param credentials credentials to use - * @param inputStream where to load from - * @return a new database - */ - public static SimpleDatabase load(Credentials credentials, InputStream inputStream) throws IOException { - SimpleSerializableDatabase simpleSerializableDatabase = new SimpleSerializableDatabase(); - StreamFormat streamFormat = new KdbxStreamFormat(); - streamFormat.load(simpleSerializableDatabase, credentials, inputStream); - return new SimpleDatabase(simpleSerializableDatabase.getKeePassFile(), streamFormat); - } - - /** - * Save the database with the same stream format that it was loaded with, or V4 default if none - * @param credentials credentials to use - * @param outputStream where to write to - */ - @Override - public void save(Credentials credentials, OutputStream outputStream) throws IOException { - // save with the stream format used to load if it exists, otherwise save V4 - if (Objects.isNull(streamFormat)) { - streamFormat = new KdbxStreamFormat(new KdbxHeader(4)); - } - save(streamFormat, credentials, outputStream); - } - - /** - * Save the database with a choice of stream format - * @param streamFormat the format to use - * @param credentials credentials to use - * @param outputStream where to write to - */ - @Override - public void save(StreamFormat streamFormat, Credentials credentials, - OutputStream outputStream) throws IOException{ - keePassFile.meta.generator = "KeePassJava2-Simple"; - SimpleSerializableDatabase simpleSerializableDatabase = new SimpleSerializableDatabase(this.keePassFile); - streamFormat.save(simpleSerializableDatabase, credentials, outputStream); - setDirty(false); - } - - - @Override - public SimpleGroup getRootGroup() { - return keePassFile.root.getGroup(); - } - - @Override - public SimpleGroup newGroup() { - return SimpleGroup.createGroup(this); - } - - @Override - public SimpleEntry newEntry() { - return SimpleEntry.createEntry(this); - } - - @Override - public SimpleIcon newIcon() { - return new SimpleIcon(); - } - - @Override - public SimpleIcon newIcon(Integer integer) { - SimpleIcon ic = newIcon(); - ic.setIndex(integer); - return ic; - } - - @Override - public boolean isRecycleBinEnabled() { - return this.keePassFile.meta.recycleBinEnabled; - } - - @Override - public void enableRecycleBin(boolean enable) { - this.keePassFile.meta.recycleBinEnabled = enable; - } - - @Override - public SimpleGroup getRecycleBin() { - UUID recycleBinUuid = this.keePassFile.meta.recycleBinUUID; - SimpleGroup g = findGroup(recycleBinUuid); - if (g == null && isRecycleBinEnabled()) { - g = newGroup("Recycle Bin"); - getRootGroup().addGroup(g); - this.keePassFile.meta.recycleBinUUID = g.getUuid(); - this.keePassFile.meta.recycleBinChanged = new Date(); - } - return g; - } - - @Override - public String getName() { - return keePassFile.meta.databaseName; - } - - @Override - public void setName(String s) { - keePassFile.meta.databaseName = s; - keePassFile.meta.databaseNameChanged = new Date(); - setDirty(true); - } - - @Override - public String getDescription() { - return keePassFile.meta.databaseDescription; - } - - @Override - public void setDescription(String s) { - keePassFile.meta.databaseDescription = s; - keePassFile.meta.databaseDescriptionChanged = new Date(); - setDirty(true); - } - - @Override - public boolean shouldProtect(String s) { - return keePassFile.meta.memoryProtection.shouldProtect(s); - } - - - public List getBinaries() { - return keePassFile.getBinaries(); - } - - public void addBinary(byte [] bytes, int index) { - SimpleSerializableDatabase.addBinary(this.keePassFile, index, bytes); - } - - public StreamFormat getStreamFormat() { - return streamFormat; - } -} \ No newline at end of file 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 deleted file mode 100644 index 93f4f105..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleEntry.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.base.AbstractEntry; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.simple.converter.UuidConverter; -import org.linguafranca.pwdb.kdbx.simple.model.EntryClasses; -import org.linguafranca.pwdb.kdbx.simple.model.KeePassFile; -import org.linguafranca.pwdb.kdbx.simple.model.Times; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Transient; -import org.simpleframework.xml.convert.Convert; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import static org.linguafranca.pwdb.kdbx.simple.model.EntryClasses.*; - -/** - * Implementation of {@link org.linguafranca.pwdb.Entry} for Simple XML framework - * - * @author jo - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@Root(name="Entry") -public class SimpleEntry extends AbstractEntry { - @Element(name = "UUID", type=UUID.class) - @Convert(UuidConverter.class) - protected UUID uuid; - @Element(name = "IconID") - protected int iconID; - @Element(name = "CustomIconUUID", type = UUID.class, required=false) - @Convert(UuidConverter.class) - protected UUID customIconUUID; - @Element(name = "ForegroundColor", required=false) - protected String foregroundColor; - @Element(name = "BackgroundColor", required = false) - protected String backgroundColor; - @Element(name = "OverrideURL", required = false) - protected String overrideURL; - @Element(name = "Tags", required = false) - protected String tags; - @Element(name = "Times") - protected Times times; - @ElementList(inline=true) - protected List string; - @ElementList(inline=true, required = false) - protected List binary; - @Element(name = "AutoType", required = false) - protected EntryClasses.AutoType autoType; - @ElementList(name = "History", required = false) - protected List history; - - @Transient - SimpleDatabase database; - @Transient - SimpleGroup parent; - - protected SimpleEntry() { - string = new ArrayList<>(); - binary = new ArrayList<>(); - times = new Times(); - uuid = UUID.randomUUID(); - iconID = 0; - } - - /** - * Factory to create a new {@link SimpleEntry} with no parent - * @param database in which this entry - * @return a detached Entry - */ - public static SimpleEntry createEntry(SimpleDatabase database) { - SimpleEntry result = new SimpleEntry(); - result.database = database; - result.parent = null; - // avoiding setProperty as it does a touch(); - for (String p: STANDARD_PROPERTY_NAMES) { - result.string.add(new EntryClasses.StringProperty(p, new EntryClasses.StringProperty.Value(""))); - } - return result; - } - - @Override - public String getProperty(String s) { - return getStringContent(getStringProperty(s, string)); - } - - @Override - public void setProperty(String s, String s1) { - EntryClasses.StringProperty sp; - if ((sp = getStringProperty(s, string)) != null) { - this.string.remove(sp); - } - this.string.add(new EntryClasses.StringProperty(s, new EntryClasses.StringProperty.Value(s1))); - touch(); - } - - @Override - public boolean removeProperty(String name) throws IllegalArgumentException { - if (STANDARD_PROPERTY_NAMES.contains(name)) throw new IllegalArgumentException("may not remove property: " + name); - - EntryClasses.StringProperty sp = getStringProperty(name, string); - if (sp == null) { - return false; - } else { - this.string.remove(sp); - touch(); - return true; - } - } - - @Override - public List getPropertyNames() { - List result = new ArrayList<>(); - for (EntryClasses.StringProperty property: this.string) { - result.add(property.getKey()); - } - return result; - } - - @Override - public byte[] getBinaryProperty(String s) { - BinaryProperty bp = getBinaryProp(s, binary); - if (bp == null) { - return null; - } - - KeePassFile.Binary binary = null; - for (KeePassFile.Binary b : database.getBinaries()) { - if (b.getId().equals(Integer.valueOf(getBinaryContent(bp)))) { - binary = b; - } - } - if (binary == null) { - return null; - } - return Helpers.decodeBase64Content(binary.getValue().getBytes(), binary.getCompressed()); - } - - @Override - public void setBinaryProperty(String s, byte[] bytes) { - // remove old binary property with same name - BinaryProperty bp = getBinaryProp(s, binary); - if (bp != null){ - binary.remove(bp); - } - - // what is the next free index in the binary store? - Integer max = -1; - for (KeePassFile.Binary binary: database.getBinaries()){ - if (binary.getId() > max) { - max = binary.getId(); - } - } - max++; - - database.addBinary(bytes, max); - - // make a reference to it from the entry - BinaryProperty binaryProperty = new BinaryProperty(); - binaryProperty.setKey(s); - BinaryProperty.Value fieldValue = new BinaryProperty.Value(); - fieldValue.setRef(String.valueOf(max)); - binaryProperty.setValue(fieldValue); - binary.add(binaryProperty); - touch(); - } - - @Override - public boolean removeBinaryProperty(String name) throws UnsupportedOperationException { - BinaryProperty bp = getBinaryProp(name, binary); - if (bp != null) { - binary.remove(bp); - touch(); - return true; - } - return false; - } - - @Override - public List getBinaryPropertyNames() { - List result = new ArrayList<>(); - for (EntryClasses.BinaryProperty property: this.binary) { - result.add(property.getKey()); - } - return result; - } - - @Override - public SimpleGroup getParent() { - return parent; - } - - @Override - public @NotNull UUID getUuid() { - return uuid; - } - - @Override - public SimpleIcon getIcon() { - return new SimpleIcon(iconID); - } - - @Override - public void setIcon(SimpleIcon icon) { - iconID = icon.getIndex(); - } - - @Override - public Date getLastAccessTime() { - return times.getLastAccessTime(); - } - - @Override - public Date getCreationTime() { - return times.getCreationTime(); - } - - @Override - public boolean getExpires() { - return times.getExpires(); - } - - @Override - public void setExpires(boolean expires) { - times.setExpires(expires); - } - - @Override - public Date getExpiryTime() { - return times.getExpiryTime(); - } - - @Override - public void setExpiryTime(Date expiryTime) throws IllegalArgumentException { - if (expiryTime == null) throw new IllegalArgumentException("expiryTime may not be null"); - times.setExpiryTime(expiryTime); - } - - @Override - public Date getLastModificationTime() { - return times.getLastModificationTime(); - } - - @Override - protected void touch() { - this.times.setLastModificationTime(new Date()); - this.database.setDirty(true); - } -} \ No newline at end of file diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleGroup.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleGroup.java deleted file mode 100644 index 5fb36258..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleGroup.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.jetbrains.annotations.NotNull; -import org.linguafranca.pwdb.Group; -import org.linguafranca.pwdb.kdbx.simple.converter.KeePassBooleanConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.UuidConverter; -import org.linguafranca.pwdb.kdbx.simple.model.Times; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Transient; -import org.simpleframework.xml.convert.Convert; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -/** - * Implementation of {@link Group} using the Simple XML framework. - * @author jo - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@Root(name = "Group") -public class SimpleGroup extends org.linguafranca.pwdb.base.AbstractGroup { - @Element(name = "UUID", type = UUID.class) - @Convert(UuidConverter.class) - protected UUID uuid; - @Element(name = "Name") - protected String name; - @Element(name = "Notes", required = false) - protected String notes; - @Element(name = "IconID") - protected int iconID; - @Element(name = "CustomIconUUID", type = UUID.class, required=false) - @Convert(UuidConverter.class) - protected UUID customIconUuid; - @Element(name = "Times") - protected Times times; - @Element(name = "IsExpanded", required = false, type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean isExpanded; - @Element(name = "DefaultAutoTypeSequence", required = false) - protected String defaultAutoTypeSequence; - @Element(name = "EnableAutoType", required = false, type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean enableAutoType; - @Element(name = "EnableSearching", required = false, type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean enableSearching; - @Element(name = "LastTopVisibleEntry", required = false, type = UUID.class) - @Convert(UuidConverter.class) - protected UUID lastTopVisibleEntry; - @ElementList(inline = true, required = false) - protected List entry; - @ElementList(inline = true, required = false) - protected List group; - - @Transient - protected SimpleDatabase database; - @Transient - protected SimpleGroup parent; - - protected SimpleGroup() { - entry = new ArrayList<>(); - group = new ArrayList<>(); - times = new Times(); - } - - public static SimpleGroup createGroup(SimpleDatabase database) { - SimpleGroup group = new SimpleGroup(); - group.database = database; - group.iconID = 0; - group.name = ""; - group.uuid = UUID.randomUUID(); - return group; - } - - @Override - public boolean isRootGroup() { - return database.getRootGroup().equals(this); - } - - @Override - public boolean isRecycleBin() { - return database.keePassFile.meta.recycleBinUUID.equals(this.uuid); - } - - @Override - public SimpleGroup getParent() { - return parent; - } - - @Override - public void setParent(SimpleGroup group) { - if (isRootGroup()) { - throw new IllegalStateException("Cannot add root group to another group"); - } - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - if (parent != null) { - parent.removeGroup(group); - parent.touch(); - } - parent = group; - parent.touch(); - touch(); - } - - @Override - public List getGroups() { - List result = new ArrayList<>(); - for (SimpleGroup aGroup : group) { - result.add(aGroup); - } - return result; - } - - @Override - public int getGroupsCount() { - return group.size(); - } - - @Override - public SimpleGroup addGroup(SimpleGroup group) { - if (group.isRootGroup()) { - throw new IllegalStateException("Cannot add root group to another group"); - } - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - if (group.getParent() != null) { - group.getParent().removeGroup(group); - } - group.parent = this; - this.group.add(group); - touch(); - return group; - } - - @Override - public SimpleGroup removeGroup(SimpleGroup group) { - if (this.database != group.database) { - throw new IllegalStateException("Must be from same database"); - } - this.group.remove(group); - group.parent = null; - touch(); - return group; - } - - @Override - public List getEntries() { - List result = new ArrayList<>(); - for (SimpleEntry entry: this.entry){ - result.add(entry); - } - return result; - } - - @Override - public int getEntriesCount() { - return this.entry.size(); - } - - @Override - public SimpleEntry addEntry(SimpleEntry entry) { - if (this.database != entry.database) { - throw new IllegalStateException("Must be from same database"); - } - if (entry.getParent() != null) { - entry.getParent().removeEntry(entry); - } - this.entry.add(entry); - entry.parent=this; - touch(); - return entry; - } - - @Override - public SimpleEntry removeEntry(SimpleEntry entry) { - if (this.database != entry.database) { - throw new IllegalStateException("Must be from same database"); - } - this.entry.remove(entry); - entry.parent = null; - return entry; - } - - @Override - public String getName() { - return name; - } - - @Override - public void setName(String s) { - this.name = s; - touch(); - } - - @Override - public UUID getUuid() { - return uuid; - } - - @Override - public SimpleIcon getIcon() { - return new SimpleIcon(iconID); - } - - @Override - public void setIcon(SimpleIcon icon) { - this.iconID = icon.getIndex(); - touch(); - } - - @NotNull - @Override - public SimpleDatabase getDatabase() { - return database; - } - - private void touch() { - this.times.setLastModificationTime(new Date()); - this.database.setDirty(true); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleIcon.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleIcon.java deleted file mode 100644 index 377b822e..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleIcon.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.Icon; - -/** - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class SimpleIcon implements Icon { - private int index; - - public SimpleIcon() { - } - - public SimpleIcon(int index) { - this.index = index; - } - - @Override - public int getIndex() { - return index; - } - - @Override - public void setIndex(int i) { - index = i; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SimpleIcon that = (SimpleIcon) o; - - return index == that.index; - - } - - @Override - public int hashCode() { - return index; - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleSerializableDatabase.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleSerializableDatabase.java deleted file mode 100644 index a9d4de14..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/SimpleSerializableDatabase.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.SerializableDatabase; -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.simple.converter.EmptyStringConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.ValueConverter; -import org.linguafranca.pwdb.kdbx.simple.model.EntryClasses; -import org.linguafranca.pwdb.kdbx.simple.model.KeePassFile; -import org.linguafranca.pwdb.security.StreamEncryptor; -import org.simpleframework.xml.Serializer; -import org.simpleframework.xml.convert.AnnotationStrategy; -import org.simpleframework.xml.convert.Registry; -import org.simpleframework.xml.convert.RegistryStrategy; -import org.simpleframework.xml.core.Persister; -import org.simpleframework.xml.strategy.Strategy; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Objects; - -/** - * @author jo - */ -@SuppressWarnings("WeakerAccess") -public class SimpleSerializableDatabase implements SerializableDatabase { - - public KeePassFile keePassFile; - private StreamEncryptor encryption; - - public SimpleSerializableDatabase(){ - - } - public SimpleSerializableDatabase(KeePassFile keePassFile) { - this.keePassFile = keePassFile; - } - - - /** - * Create an empty underlying KeePassFile instance - * - * @return a new database - */ - static KeePassFile createEmptyDatabase() { - InputStream inputStream = SimpleDatabase.class.getClassLoader().getResourceAsStream("base.kdbx.xml"); - try { - return getSerializer(new StreamEncryptor.None()).read(KeePassFile.class, inputStream); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public SimpleSerializableDatabase load(InputStream inputStream) { - try { - // decrypt the encrypted fields in the inner XML stream - // InputStream plainTextXmlStream = new XmlInputStreamFilter(inputStream, new KdbxInputTransformer(encryption)); - // read the now entirely decrypted stream into database - keePassFile = getSerializer(encryption).read(KeePassFile.class, inputStream); - // ensure that parent fields are set - fixUp(keePassFile.root.group); - return this; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @Override - public void save(OutputStream outputStream) throws IOException { - // encrypt the fields in the XML inner stream - // XmlOutputStreamFilter plainTextOutputStream = new XmlOutputStreamFilter(outputStream, new KdbxOutputTransformer(encryption)); - - // set up the "protected" attributes of fields that need inner stream encryption - prepareForSave(keePassFile.root.group); - - // and save the database out - try { - getSerializer(encryption).write(this.keePassFile, outputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - /** - * Utility to get a simple framework persister - * @return a persister - */ - public static Serializer getSerializer(StreamEncryptor encryption) { - Registry registry = new Registry(); - try { - registry.bind(String.class, EmptyStringConverter.class); - registry.bind(EntryClasses.StringProperty.Value.class, new ValueConverter(encryption)); - } catch (Exception e) { - throw new IllegalStateException(e); - } - Strategy strategy = new AnnotationStrategy(new RegistryStrategy(registry)); - return new Persister(strategy); - } - - /** - * Utility to mark fields that need to be encrypted and vice versa - * - * @param parent the group to start from - */ - private static void prepareForSave(SimpleGroup parent){ - for (SimpleGroup group: parent.group) { - prepareForSave(group); - } - for (SimpleEntry entry: parent.entry) { - 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 (SimpleEntry entry2 : entry.history) { - for (EntryClasses.StringProperty property : entry2.string) { - boolean shouldProtect = parent.database.shouldProtect(property.getKey()); - property.getValue().setProtectOnOutput(shouldProtect || property.getValue().getProtectOnOutput()); - } - } - } - } - } - - /** - * On load add parents - * @param parent a parent to recurse - */ - static void fixUp(SimpleGroup parent){ - for (SimpleGroup group: parent.group) { - group.parent = parent; - group.database = parent.database; - fixUp(group); - } - for (SimpleEntry entry: parent.entry) { - entry.database = parent.database; - entry.parent = parent; - } - } - - @Override - public StreamEncryptor getEncryption() { - return encryption; - } - - @Override - public void setEncryption(StreamEncryptor encryption) { - this.encryption = encryption; - } - - @Override - public byte[] getHeaderHash() { - return keePassFile.meta.headerHash.getContent(); - } - - @Override - public void setHeaderHash(byte[] hash) { - keePassFile.meta.headerHash = new KeePassFile.ByteArray(hash); - } - - @Override - public void addBinary(int index, byte[] value) { - addBinary(keePassFile, index, value); - } - - @Override - public byte[] getBinary(int index) { - KeePassFile.Binary binary = keePassFile.getBinaries().get(index); - String value = binary.getValue(); - return Helpers.decodeBase64Content(value.getBytes(), binary.getCompressed()); - } - - @Override - public int getBinaryCount() { - if (Objects.isNull(keePassFile.getBinaries())){ - return 0; - } - return keePassFile.getBinaries().size(); - } - - public static void addBinary(KeePassFile keePassFile, int index, byte[] value) { - // create a new binary to put in the store - KeePassFile.Binary newBin = new KeePassFile.Binary(); - newBin.setId(index); - newBin.setValue(Helpers.encodeBase64Content(value, true)); - newBin.setCompressed(true); - if (keePassFile.getBinaries() == null) { - keePassFile.createBinaries(); - } - keePassFile.getBinaries().add(newBin); - } - - - - public KeePassFile getKeePassFile() { - return keePassFile; - } - - public void setKeePassFile(KeePassFile keypassFile) { - this.keePassFile = keypassFile; - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/Base64ByteArrayConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/Base64ByteArrayConverter.java deleted file mode 100644 index f3fe3b33..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/Base64ByteArrayConverter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.converter; - -import org.linguafranca.pwdb.kdbx.Helpers; -import org.linguafranca.pwdb.kdbx.simple.model.KeePassFile; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** - * @author jo - */ -public class Base64ByteArrayConverter implements Converter{ - @Override - public KeePassFile.ByteArray read(InputNode inputNode) throws Exception { - String input = inputNode.getValue(); - byte[] value = input == null? new byte[0] : input.getBytes(); - return new KeePassFile.ByteArray(Helpers.decodeBase64Content(value, false)); - } - - @Override - public void write(OutputNode outputNode, KeePassFile.ByteArray bytes) throws Exception { - outputNode.setValue(Helpers.encodeBase64Content(bytes.getContent(), false)); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/EmptyStringConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/EmptyStringConverter.java deleted file mode 100644 index ab27c06d..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/EmptyStringConverter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.converter; - -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** - * @author jo - */ -public class EmptyStringConverter implements Converter { - @Override - public String read(InputNode inputNode) throws Exception { - String value = inputNode.getValue(); - return value == null ? "": value; - } - - @Override - public void write(OutputNode outputNode, String s) throws Exception { - if(s==null || s.equals("")) { - outputNode.setValue(null); - } else { - outputNode.setValue(s); - } - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/KeePassBooleanConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/KeePassBooleanConverter.java deleted file mode 100644 index ef7f084f..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/KeePassBooleanConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.converter; - -import org.linguafranca.pwdb.kdbx.Helpers; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -/** - * @author jo - */ -public class KeePassBooleanConverter implements Converter{ - @Override - public Boolean read(InputNode inputNode) throws Exception { - return Helpers.toBoolean(inputNode.getValue()); - } - - @Override - public void write(OutputNode outputNode, Boolean bool) throws Exception { - outputNode.setValue(Helpers.fromBoolean(bool)); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/TimeConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/TimeConverter.java deleted file mode 100644 index 87f7e06f..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/TimeConverter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.converter; - -import org.linguafranca.pwdb.kdbx.Helpers; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -import java.util.Date; - -/** - * @author jo - */ -public class TimeConverter implements Converter{ - @Override - public Date read(InputNode inputNode) throws Exception { - String value = inputNode.getValue(); - if (value.equals("${creationDate}")) { - return new Date(); - } - return Helpers.toDate(value); - } - - @Override - public void write(OutputNode outputNode, Date date) throws Exception { - outputNode.setValue(Helpers.fromDate(date)); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/UuidConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/UuidConverter.java deleted file mode 100644 index f211901a..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/UuidConverter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.converter; - -import org.linguafranca.pwdb.kdbx.Helpers; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -import java.util.UUID; - -/** - * @author jo - */ -public class UuidConverter implements Converter { - @Override - public UUID read(InputNode inputNode) throws Exception { - String value = inputNode.getValue(); - if (value == null || value.equals("")) { - return UUID.randomUUID(); - } - return Helpers.uuidFromBase64(value); - } - - @Override - public void write(OutputNode outputNode, UUID uuid) throws Exception { - outputNode.setValue(Helpers.base64FromUuid(uuid)); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/ValueConverter.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/ValueConverter.java deleted file mode 100644 index eb436fae..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/converter/ValueConverter.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.linguafranca.pwdb.kdbx.simple.converter; - -import org.apache.commons.codec.binary.Base64; -import org.linguafranca.pwdb.kdbx.simple.model.EntryClasses; -import org.linguafranca.pwdb.security.StreamEncryptor; -import org.simpleframework.xml.convert.Converter; -import org.simpleframework.xml.stream.InputNode; -import org.simpleframework.xml.stream.OutputNode; - -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -/** - * Value converter takes care of encryption and decryption of protected fields - */ -public class ValueConverter implements Converter { - - private final StreamEncryptor encryption; - - public ValueConverter(StreamEncryptor encryption) { - this.encryption = encryption; - } - - /** - * De-serialise (unmarshal) the input node - * @param node this is the node to deserialize the object from - * - * @return the de-serialised object - */ - @Override - public EntryClasses.StringProperty.Value read(InputNode node) throws Exception { - EntryClasses.StringProperty.Value value = new EntryClasses.StringProperty.Value(); - if (Objects.nonNull(node.getAttribute("Protected")) && node.getAttribute("Protected").getValue().equals("True")) { - byte[] encrypted = new byte[0]; - // you can only read the value once - String text = node.getValue(); - if (Objects.nonNull(text)) { - encrypted = Base64.decodeBase64(text.getBytes()); - } - String decrypted = new String(encryption.decrypt(encrypted), StandardCharsets.UTF_8); - value.setText(decrypted); - value.setProtected(null); - value.setProtectOnOutput(true); - } else { - value.setProtectOnOutput(false); - value.setText(node.getValue()); - } - return value; - } - - /** - * Serialise (marshal) the object - * @param node this is the node to serialized the object to - * @param value this is the value that is to be serialized - */ - @Override - public void write(OutputNode node, EntryClasses.StringProperty.Value value) throws Exception { - if (value.getProtectOnOutput()) { - byte [] encrypted = encryption.encrypt(value.getText().getBytes(StandardCharsets.UTF_8)); - byte [] base64Encoded = Base64.encodeBase64(encrypted); - node.setValue(new String(base64Encoded)); - node.getAttributes().put("Protected", "True"); - } else { - node.setValue(value.getText()); - } - //node.getAttributes().remove("protectInMemory"); - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/EntryClasses.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/EntryClasses.java deleted file mode 100644 index 02285fe5..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/EntryClasses.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.model; - -import org.linguafranca.pwdb.kdbx.simple.SimpleEntry; -import org.linguafranca.pwdb.kdbx.simple.converter.KeePassBooleanConverter; -import org.simpleframework.xml.*; -import org.simpleframework.xml.convert.Convert; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * @author jo - */ -@SuppressWarnings("unused") -public abstract class EntryClasses { - public static StringProperty getStringProperty(String name, List string) { - for (StringProperty property : string) { - if (property.key.equals(name)) { - return property; - } - } - return null; - } - - public static String getStringContent(StringProperty property) { - return property == null || property.value == null? null:property.value.text; - } - - public static BinaryProperty getBinaryProp(String name, List binary) { - for (BinaryProperty property : binary) { - if (property.key.equals(name)) { - return property; - } - } - return null; - } - - public static String getBinaryContent(BinaryProperty property) { - return property == null || property.value == null ? null : property.value.ref; - } - - @Root(name = "AutoType") - public static class AutoType { - @Element(name = "Enabled", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean enabled; - @Element(name = "DataTransferObfuscation") - protected int dataTransferObfuscation; - @Element(name = "DefaultSequence", required = false) - protected String defaultSequence; - @Element(name = "Association", required = false) - protected AutoType.Association association; - - public static class Association { - @Element(name = "Window") - protected String window; - @Element(name="KeystrokeSequence") - protected String keystrokeSequence; - protected List windowAndKeystrokeSequence; - } - } - - @Root(name="String") - public static class StringProperty implements org.simpleframework.xml.util.Entry { - - public StringProperty() { - this("",new Value()); - } - public StringProperty(String key, Value value) { - this.key = key; - this.value = value; - } - - @Element(name="Key") - String key; - - @Element(name="Value") - Value value; - - @Override - public String getName() { - return key; - } - - public String getKey() { - return key; - } - - public Value getValue() { - return value; - } - - @Root(name="Value") - public static class Value { - public Value(){ - this(""); - } - public Value(String text) { - this.text = text; - this._protected = false; - } - - public Value(String text, Boolean _protected) { - this._protected = _protected; - this.text = text; - } - - @Attribute(name = "ProtectInMemory", required = false) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectInMemory; - @Attribute(name = "Protected", required = false) - Boolean _protected; - @Attribute(name = "kpj2-ProtectOnOutput", required = false) - @Convert(KeePassBooleanConverter.class) - Boolean protectOnOutput; - @Text - String text; - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public void setProtectOnOutput(boolean aProtected) { - this.protectOnOutput = aProtected; - } - - public boolean getProtectOnOutput() { - return Objects.nonNull(this.protectOnOutput) && this.protectOnOutput; - } - - - public Boolean getProtected() { - return _protected; - } - - public void setProtected(Boolean _protected) { - this._protected = _protected; - } - } - } - - @Root(name="Binary") - public static class BinaryProperty implements org.simpleframework.xml.util.Entry { - - @Element(name="Key") - String key; - - @Element(name="Value") - Value value; - - public String getKey() { - return key; - } - - @Override - public String getName() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public void setValue(Value value) { - this.value = value; - } - - @Root(name="Value") - public static class Value { - @Attribute(name="Ref") - String ref; - - public void setRef(String ref) { - this.ref = ref; - } - } - } - - public static class History { - - @ElementList(entry = "SimpleEntry", inline = true) - private List list; - - public History(){ - list = new ArrayList<>(); - } - public List getHistory(){ - return list; - } - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/KeePassFile.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/KeePassFile.java deleted file mode 100644 index 298f512c..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/KeePassFile.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.model; - -import org.linguafranca.pwdb.Entry; -import org.linguafranca.pwdb.kdbx.simple.SimpleGroup; -import org.linguafranca.pwdb.kdbx.simple.converter.Base64ByteArrayConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.KeePassBooleanConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.TimeConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.UuidConverter; -import org.simpleframework.xml.*; -import org.simpleframework.xml.convert.Convert; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -/** - * @author jo - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@Root(name = "KeePassFile") -public class KeePassFile { - - @Element(name = "Meta") - public Meta meta; - @Element(name = "Root") - public Root root; - - public List getBinaries() { - return meta.binaries; - } - - public void createBinaries() { - meta.binaries = new ArrayList<>(); - } - - public static class Root { - @Element(name = "Group") - public SimpleGroup group; - @ElementList(name = "DeletedObjects", required = false) - protected ArrayList deletedObjects; - - public SimpleGroup getGroup() { - return group; - } - } - - @SuppressWarnings("unused") - public static class Meta { - @Element(name = "Generator") - public String generator; - @Element(name = "HeaderHash", required = false) - @Convert(Base64ByteArrayConverter.class) - public KeePassFile.ByteArray headerHash; - @Element(name = "DatabaseName") - public String databaseName; - @Element(name = "DatabaseNameChanged", type = Date.class) - @Convert(TimeConverter.class) - public Date databaseNameChanged; - @Element(name = "DatabaseDescription") - public String databaseDescription; - @Element(name = "DatabaseDescriptionChanged", type = Date.class) - @Convert(TimeConverter.class) - public Date databaseDescriptionChanged; - @Element(name = "DefaultUserName") - protected String defaultUserName; - @Element(name = "DefaultUserNameChanged", type = Date.class) - @Convert(TimeConverter.class) - protected Date defaultUserNameChanged; - @Element(name = "MaintenanceHistoryDays") - protected int maintenanceHistoryDays; - @Element(name = "Color") - protected String color; - @Element(name = "MasterKeyChanged", type = Date.class) - @Convert(TimeConverter.class) - protected Date masterKeyChanged; - @Element(name = "MasterKeyChangeRec") - protected int masterKeyChangeRec; - @Element(name = "MasterKeyChangeForce") - protected int masterKeyChangeForce; - @Element(name = "MemoryProtection") - public KeePassFile.MemoryProtection memoryProtection; - @ElementList(name = "CustomIcons", required = false) - protected ArrayList customIcons; - @Element(name = "RecycleBinEnabled", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - public Boolean recycleBinEnabled; - @Element(name = "RecycleBinUUID", type = UUID.class) - @Convert(UuidConverter.class) - public UUID recycleBinUUID; - @Element(name = "RecycleBinChanged", type = Date.class) - @Convert(TimeConverter.class) - public Date recycleBinChanged; - @Element(name = "EntryTemplatesGroup", type = UUID.class) - @Convert(UuidConverter.class) - protected UUID entryTemplatesGroup; - @Element(name = "EntryTemplatesGroupChanged", type = Date.class) - @Convert(TimeConverter.class) - protected Date entryTemplatesGroupChanged; - @Element(name = "LastSelectedGroup", type = UUID.class) - @Convert(UuidConverter.class) - protected UUID lastSelectedGroup; - @Element(name = "LastTopVisibleGroup", type = UUID.class) - @Convert(UuidConverter.class) - protected UUID lastTopVisibleGroup; - @Element(name = "HistoryMaxItems") - protected int historyMaxItems; - @Element(name = "HistoryMaxSize") - protected int historyMaxSize; - @ElementList(name = "Binaries", required = false) - protected List binaries; - @Element(name = "CustomData", required = false) - protected KeePassFile.CustomData customData; - - /* version 4 */ - - @Element(name = "SettingsChanged", required = false, type = Date.class) - @Convert(TimeConverter.class) - protected Date settingsChanged; - } - - - public static class MemoryProtection { - @Element(name = "ProtectTitle", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectTitle; - @Element(name = "ProtectUserName", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectUserName; - @Element(name = "ProtectPassword", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectPassword; - @Element(name = "ProtectURL", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectURL; - @Element(name = "ProtectNotes", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean protectNotes; - - public Boolean shouldProtect(String name) { - switch(name) { - case Entry.STANDARD_PROPERTY_NAME_TITLE: return protectTitle; - case Entry.STANDARD_PROPERTY_NAME_USER_NAME: return protectUserName; - case Entry.STANDARD_PROPERTY_NAME_PASSWORD: return protectPassword; - case Entry.STANDARD_PROPERTY_NAME_URL: return protectURL; - case Entry.STANDARD_PROPERTY_NAME_NOTES: return protectNotes; - } - return false; - } - } - - public static class Binaries { - - } - - @org.simpleframework.xml.Root(name = "Binary") - public static class Binary implements org.simpleframework.xml.util.Entry { - @Text - protected String value; - - @Attribute(name = "ID") - protected Integer id; - @Attribute(name = "Compressed") - @Convert(KeePassBooleanConverter.class) - protected Boolean compressed; - - @Override - public String getName() { - return String.valueOf(id); - } - - public String getValue() { - return value; - } - - public Boolean getCompressed() { - return compressed; - } - - public Integer getId() { - return id; - } - - public void setId(Integer Id) { - this.id = Id; - } - - public void setValue(String value) { - this.value = value; - } - - public void setCompressed(boolean compressed) { - this.compressed = compressed; - } - } - - @org.simpleframework.xml.Root(name = "Icon") - public static class Icon implements org.simpleframework.xml.util.Entry { - @Element(name = "UUID", type=UUID.class) - @Convert(UuidConverter.class) - protected UUID uuid; - @Element(name = "Name") - protected String name; - @Element(name = "LastModificationTime", type=Date.class) - @Convert(TimeConverter.class) - protected Date lastModificationTime; - @Element(name = "Data") - @Convert(Base64ByteArrayConverter.class) - protected ByteArray data; - - @Override - public String getName() { - return uuid.toString(); - } - } - - /** - * Work around problems deserializing byte[] - */ - public static class ByteArray { - private byte[] content; - - public ByteArray(byte[] content) { - this.content = content; - } - - public byte[] getContent() { - return content; - } - - public void setContent(byte[] content) { - this.content = content; - } - } - - public static class CustomData { - protected List any; - } - - @org.simpleframework.xml.Root(name = "DeletedObject") - public static class DeletedObject { - @Element(name = "UUID", type = UUID.class) - @Convert(UuidConverter.class) - protected UUID uuid; - @Element(name = "DeletionTime", type = Date.class) - @Convert(TimeConverter.class) - protected Date deletionTime; - } -} diff --git a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/Times.java b/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/Times.java deleted file mode 100644 index b96388e4..00000000 --- a/simple/src/main/java/org/linguafranca/pwdb/kdbx/simple/model/Times.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple.model; - -import org.linguafranca.pwdb.kdbx.simple.converter.KeePassBooleanConverter; -import org.linguafranca.pwdb.kdbx.simple.converter.TimeConverter; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.convert.Convert; - -import java.util.Date; - -/** - * @author jo - */ -@SuppressWarnings({"unused", "WeakerAccess"}) -@Root -public class Times { - @Element(name = "LastModificationTime", type = Date.class) - @Convert(TimeConverter.class) - protected Date lastModificationTime; - @Element(name = "CreationTime", type = Date.class) - @Convert(TimeConverter.class) - protected Date creationTime; - @Element(name = "LastAccessTime", type = Date.class) - @Convert(TimeConverter.class) - protected Date lastAccessTime; - @Element(name = "ExpiryTime", type = Date.class) - @Convert(TimeConverter.class) - protected Date expiryTime; - @Element(name = "Expires", type = Boolean.class) - @Convert(KeePassBooleanConverter.class) - protected Boolean expires; - @Element(name = "UsageCount") - protected int usageCount; - @Element(name = "LocationChanged", type = Date.class) - @Convert(TimeConverter.class) - protected Date locationChanged; - - public Date getLastModificationTime() { - return lastModificationTime; - } - - public void setLastModificationTime(Date lastModificationTime) { - this.lastModificationTime = lastModificationTime; - } - - public Date getCreationTime() { - return creationTime; - } - - public void setCreationTime(Date creationTime) { - this.creationTime = creationTime; - } - - public Date getLastAccessTime() { - return lastAccessTime; - } - - public void setLastAccessTime(Date lastAccessTime) { - this.lastAccessTime = lastAccessTime; - } - - public Date getExpiryTime() { - return expiryTime; - } - - public void setExpiryTime(Date expiryTime) { - this.expiryTime = expiryTime; - } - - public Boolean getExpires() { - return expires; - } - - public void setExpires(Boolean expires) { - this.expires = expires; - } - - public int getUsageCount() { - return usageCount; - } - - public void setUsageCount(int usageCount) { - this.usageCount = usageCount; - } - - public Date getLocationChanged() { - return locationChanged; - } - - public void setLocationChanged(Date locationChanged) { - this.locationChanged = locationChanged; - } - - public Times() { - this(new Date(System.currentTimeMillis()/1000*1000)); - } - public Times(Date date) { - lastModificationTime = date; - lastAccessTime = date; - locationChanged = date; - creationTime = date; - expiryTime = date; - expires = false; - usageCount = 0; - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV3Test.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV3Test.java deleted file mode 100644 index be8bde9b..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV3Test.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -public class SimpleBinaryPropertyV3Test extends BinaryPropertyChecks { - - public SimpleBinaryPropertyV3Test() { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Attachment.kdbx"); - try { - database = SimpleDatabase.load(new KdbxCreds("123".getBytes()),inputStream); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return SimpleDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - - @Override - public Database newDatabase() { - return new SimpleDatabase(); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV4Test.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV4Test.java deleted file mode 100644 index aca2335d..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleBinaryPropertyV4Test.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.junit.Ignore; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.checks.BinaryPropertyChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author jo - */ -@Ignore -public class SimpleBinaryPropertyV4Test extends BinaryPropertyChecks { - - public SimpleBinaryPropertyV4Test() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-ChaCha20-Argon2-Attachment.kdbx"); - database = SimpleDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return SimpleDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public Database newDatabase() { - return new SimpleDatabase(); - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } - -} \ No newline at end of file diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoadTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoadTest.java deleted file mode 100644 index 8feb77c5..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoadTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.junit.Ignore; -import org.junit.Test; -import org.linguafranca.pwdb.Entry; -import org.linguafranca.pwdb.Group; -import org.linguafranca.pwdb.Visitor; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class SimpleDatabaseLoadTest { - - static PrintStream printStream = getTestPrintStream(); - - @Test @Ignore(value = "Looks like this example is bogus") - public void loadXml() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("ExampleDatabase.xml"); - SimpleDatabase database = SimpleDatabase.loadXml(inputStream); - database.visit(new Visitor.Print(printStream)); - } - @Test - public void loadKdbx() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - SimpleDatabase database = SimpleDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - } - @Test - public void loadKdbxV4() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("V4-AES-Argon2.kdbx"); - SimpleDatabase database = SimpleDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - // test what happens to dates in V4 - database.visit(new Visitor.Default(){ - @Override - public void visit(Entry entry) { - printStream.println(entry.getCreationTime()); - } - }); - } - - @Test - public void emptyDb() throws Exception { - SimpleDatabase database = new SimpleDatabase(); - printStream.println(database.getDescription()); - } - - @Test - public void dbWithDeleted() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("testDeleted.kdbx"); - SimpleDatabase database = SimpleDatabase.load(new KdbxCreds("123".getBytes()), inputStream); - database.visit(new Visitor.Print(printStream)); - } - -} \ No newline at end of file diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoaderTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoaderTest.java deleted file mode 100644 index e3946932..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseLoaderTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.checks.DatabaseLoaderChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; - -import java.io.InputStream; - -/** - * @author jo - */ -public class SimpleDatabaseLoaderTest extends DatabaseLoaderChecks { - - public SimpleDatabaseLoaderTest() throws Exception { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test123.kdbx"); - // file has password credentials - Credentials credentials = new KdbxCreds("123".getBytes()); - super.database = SimpleDatabase.load(credentials, inputStream); - - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseTest.java deleted file mode 100644 index dea2d1ac..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleDatabaseTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.checks.BasicDatabaseChecks; -import org.linguafranca.pwdb.Database; - -import java.io.IOException; - -/** - * @author jo - */ -public class SimpleDatabaseTest extends BasicDatabaseChecks { - - public SimpleDatabaseTest() throws IOException { - } - - @Override - public Database createDatabase() { - try { - return new SimpleDatabase(); - } catch (Exception e) { - throw new IllegalStateException(); - } - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimplePropertyValueTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimplePropertyValueTest.java deleted file mode 100644 index bd81a9bc..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimplePropertyValueTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Jo Rabin - * - * 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.simple; - -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.checks.PropertyValueChecks; -import org.linguafranca.pwdb.kdbx.KdbxCreds; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class SimplePropertyValueTest extends PropertyValueChecks { - - public SimplePropertyValueTest() throws IOException { - super(false); - } - - - @Override - public Database createDatabase() throws IOException { - return new SimpleDatabase(); - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return SimpleDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - public Credentials getCreds(byte[] creds) { - return new KdbxCreds(creds); - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleRecycleBinTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleRecycleBinTest.java deleted file mode 100644 index 3d7a4895..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleRecycleBinTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.linguafranca.pwdb.kdbx.simple; - -import org.linguafranca.pwdb.checks.RecycleBinChecks; - -/** - * @author jo - */ -public class SimpleRecycleBinTest extends RecycleBinChecks { - public SimpleRecycleBinTest() { - database = new SimpleDatabase(); - } -} diff --git a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleSaveAndReloadTest.java b/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleSaveAndReloadTest.java deleted file mode 100644 index d848e759..00000000 --- a/simple/src/test/java/org/linguafranca/pwdb/kdbx/simple/SimpleSaveAndReloadTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.simple; - -import org.junit.Test; -import org.linguafranca.pwdb.StreamFormat; -import org.linguafranca.pwdb.checks.SaveAndReloadChecks; -import org.linguafranca.pwdb.Database; -import org.linguafranca.pwdb.kdbx.KdbxCreds; -import org.linguafranca.pwdb.Credentials; -import org.linguafranca.pwdb.kdbx.KdbxHeader; -import org.linguafranca.pwdb.kdbx.KdbxSerializer; - -import java.io.*; - -import static org.junit.Assert.assertTrue; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public class SimpleSaveAndReloadTest extends SaveAndReloadChecks { - - static PrintStream printStream = getTestPrintStream(); - - @Override - public Database getDatabase() { - return new SimpleDatabase(); - } - - @Override - public Database getDatabase(String s, Credentials credentials) throws IOException { - InputStream inputStream = getClass().getClassLoader().getResourceAsStream(s); - try { - return SimpleDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - public void saveDatabase(Database database, Credentials credentials, OutputStream outputStream) throws IOException { - database.save(credentials, outputStream); - } - - @Override - public Database loadDatabase(Credentials credentials, InputStream inputStream) throws IOException { - try { - return SimpleDatabase.load(credentials, inputStream); - } catch (Exception e) { - throw new IOException(e); - } - } - - - @Override - public boolean verifyStreamFormat(StreamFormat s1, StreamFormat s2) { - KdbxHeader h1 = (KdbxHeader) s1.getStreamConfiguration(); - KdbxHeader h2 = (KdbxHeader) s1.getStreamConfiguration(); - return (h1.getVersion() == h2.getVersion() && - h1.getProtectedStreamAlgorithm().equals(h2.getProtectedStreamAlgorithm()) && - h1.getKeyDerivationFunction().equals(h2.getKeyDerivationFunction()) && - h1.getCipherAlgorithm().equals(h2.getCipherAlgorithm())); - } - - - // check that boolean comes out in upper case - Simple Converters don't work on attributes - // so this is done in the output transformer - @Test - public void uppercaseBooleanTest() throws IOException { - SimpleDatabase s = new SimpleDatabase(); - SimpleEntry e = s.newEntry(); - e.setPassword("12345"); - s.getRootGroup().addEntry(e); - File file = File.createTempFile("kdbx", "kdbx"); - s.save(new KdbxCreds("123".getBytes()), new FileOutputStream(file)); - InputStream inputStream = new FileInputStream(file); - Credentials credentials = new KdbxCreds("123".getBytes()); - InputStream decryptedInputStream = KdbxSerializer.createUnencryptedInputStream(credentials, new KdbxHeader(), inputStream); - BufferedReader br = new BufferedReader(new InputStreamReader(decryptedInputStream)); - boolean foundValue = false; - while (br.ready()) { - String string = br.readLine(); - if (string.trim().startsWith(" - - - KeePassJava2-parent - org.linguafranca.pwdb - 2.2.3-SNAPSHOT - - 4.0.0 - - test - PWDB :: Test - Contains the shared test classes for exercising the functionality of a PWDB database implementation. - - - - org.linguafranca.pwdb - database - ${project.version} - - - - \ No newline at end of file diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/BinaryPropertyChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/BinaryPropertyChecks.java deleted file mode 100644 index 92998e9b..00000000 --- a/test/src/main/java/org/linguafranca/pwdb/checks/BinaryPropertyChecks.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.checks; - -import com.google.common.io.ByteStreams; -import org.junit.Assert; -import org.junit.Test; -import org.linguafranca.pwdb.*; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.Assert.*; - -/** - * Tests to verify that binary properties work correctly. Resources associated with this module contain files - * named "Attachment*.kdbx" which contain two entries: - * - *
- *
"Test Attachment"
- *
Contains an attachment "Letter J" which is also present in the resources directory
- *
"Test 2 Attachment"
- *
Contains an attachment "Letter L" which is also present in the resources directory
- *
- * - * When used as a test suite for a concrete implementation, subclass and name the class *Test etc. to conform - * with Junit rules - *

- * Subclasses should test both V3 KDBX files (Attachment.kdbx) and V4 (V4-ChaCha20-Argon2-Attachment.kdbx) since - * attachments are handled differently in the two versions. - * - * @author jo - */ -public abstract class BinaryPropertyChecks , G extends Group, E extends Entry, I extends Icon> { - - public D database; - - @SuppressWarnings("unused") - public abstract void saveDatabase(D database, Credentials credentials, OutputStream outputStream) throws IOException; - public abstract D loadDatabase(Credentials credentials, InputStream inputStream) throws IOException; - public abstract D newDatabase(); - public abstract Credentials getCreds(byte[] creds); - - /** - * Retrieve and verify attachment "letter J" - */ - @Test - public void getBinaryProperty() throws Exception { - E entry = database.findEntries("Test attachment").get(0); - byte [] letterJ = entry.getBinaryProperty("letter J.jpeg"); - InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter J.jpeg"); - assert testFile != null; - byte [] original = ByteStreams.toByteArray(testFile); - Assert.assertArrayEquals(original, letterJ); - } - - /** - * Retrieve and verify attachment "letter L" - */ - @Test - public void getAnotherBinaryProperty() throws Exception { - E entry = database.findEntries("Test 2 attachment").get(0); - byte [] letterL = entry.getBinaryProperty("letter L.jpeg"); - InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter L.jpeg"); - assert testFile != null; - byte [] original = ByteStreams.toByteArray(testFile); - Assert.assertArrayEquals(original, letterL); - } - - /** - * Add the Letter L to the entry containing letter J - */ - @Test - public void setBinaryProperty() throws Exception { - InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter L.jpeg"); - assert testFile != null; - byte [] original = ByteStreams.toByteArray(testFile); - E entry = database.findEntries("Test attachment").get(0); - entry.setBinaryProperty("letter L.jpeg", original); - byte [] letterL = entry.getBinaryProperty("letter L.jpeg"); - Assert.assertArrayEquals(original, letterL); - assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); - Assert.assertTrue(database.isDirty()); - } - - /** - * Verify that the entries have the right attachments - */ - @Test - public void getBinaryPropertyNames() { - E entry = database.findEntries("Test attachment").get(0); - assertArrayEquals(new String[] {"letter J.jpeg"}, entry.getBinaryPropertyNames().toArray()); - - entry = database.findEntries("Test 2 attachment").get(0); - assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, entry.getBinaryPropertyNames().toArray()); - } - - /** - * Verify that the database correctly reports that its supports binary attachments - override for - * databases that don't - */ - @Test - public void checkSupported(){ - assertTrue(database.supportsBinaryProperties()); - } - - /** - * Verify that the binary properties can be added and removed correctly - */ - @Test - public void checkAddChangeRemoveBinaryProperty() { - byte[] test = new byte[] {0, 1, 2 ,3}; - byte[] test2 = new byte[] {3, 2, 1, 0}; - E entry = database.findEntries("Test attachment").get(0); - assertEquals(1, entry.getBinaryPropertyNames().size()); - entry.setBinaryProperty("test", test); - assertArrayEquals(test, entry.getBinaryProperty("test")); - entry.setBinaryProperty("test", test2); - assertArrayEquals(test2, entry.getBinaryProperty("test")); - // true that property was removed - assertTrue(entry.removeBinaryProperty("test")); - // false that same property was removed - assertFalse(entry.removeBinaryProperty("test")); - // false that non-existent was removed - assertFalse(entry.removeBinaryProperty("test-test")); - // same number of properties as we started with - assertEquals(1, entry.getBinaryPropertyNames().size()); - } - - /** - * Checks that a database with binary properties saves and reloads correctly - */ - @Test - public void saveAndReloadCheck() throws IOException { - Path file = Files.createTempFile("keepass", "tmp"); - saveDatabase(database, getCreds("123".getBytes()), Files.newOutputStream(file)); - - D db = loadDatabase(getCreds("123".getBytes()),Files.newInputStream(file)); - E newEntry = db.findEntries("Test attachment").get(0); - assertArrayEquals(new String[] {"letter J.jpeg"}, newEntry.getBinaryPropertyNames().toArray()); - E oldEntry = database.findEntries("Test attachment").get(0); - assertArrayEquals(oldEntry.getBinaryProperty("letter J.jpeg"), newEntry.getBinaryProperty("letter J.jpeg")); - - newEntry = db.findEntries("Test 2 attachment").get(0); - assertArrayEquals(new String[] {"letter J.jpeg", "letter L.jpeg"}, newEntry.getBinaryPropertyNames().toArray()); - oldEntry = database.findEntries("Test 2 attachment").get(0); - assertArrayEquals(oldEntry.getBinaryProperty("letter J.jpeg"), newEntry.getBinaryProperty("letter J.jpeg")); - assertArrayEquals(oldEntry.getBinaryProperty("letter L.jpeg"), newEntry.getBinaryProperty("letter L.jpeg")); - - } - - /** - * Checks that a new database can add binary properties saves and reloads correctly - */ - @Test - public void createAndSaveCheck() throws IOException { - Path file = Files.createTempFile("keepass", "tmp"); - D database1 = newDatabase(); - E entry = database1.newEntry("Test attachment"); - database1.getRootGroup().addEntry(entry); - InputStream testFile = getClass().getClassLoader().getResourceAsStream("letter J.jpeg"); - assert testFile != null; - byte [] letterJ = ByteStreams.toByteArray(testFile); - entry.setBinaryProperty("letter J.jpeg", letterJ); - saveDatabase(database1, getCreds("123".getBytes()), Files.newOutputStream(file)); - - D db = loadDatabase(getCreds("123".getBytes()),Files.newInputStream(file)); - E entry1 = db.findEntries("Test attachment").get(0); - // just one property - assertArrayEquals(new String[] {"letter J.jpeg"}, entry1.getBinaryPropertyNames().toArray()); - // content is correct - assertArrayEquals(letterJ, entry1.getBinaryProperty("letter J.jpeg")); - } - - -} diff --git a/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java deleted file mode 100644 index 140ea1c1..00000000 --- a/test/src/main/java/org/linguafranca/pwdb/checks/DatabaseLoaderChecks.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.checks; - -import org.junit.Assert; -import org.junit.Test; -import org.linguafranca.pwdb.*; - -import java.io.PrintStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * @author jo - */ -public abstract class DatabaseLoaderChecks , G extends Group, E extends Entry, I extends Icon>{ - - static PrintStream printStream = getTestPrintStream(); - - protected Database database; - protected boolean skipDateCheck = false; - private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssX"); - /** - * a test123 file for each format. Should contain the same thing. This is a basic sanity check. - */ - @Test - public void test123File() 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 - String pass = tests.get(0).getPassword(); -/* - 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 - String pass2 = tests.get(0).getPassword(); - Assert.assertEquals(pass, pass2); - Assert.assertEquals("123", pass2); - } - - List passwords = database.findEntries("password"); - Assert.assertEquals(4, passwords.size()); - for (Entry passwordEntry : passwords) { - assertEquals(passwordEntry.getTitle(), passwordEntry.getPassword()); - 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", entries.get(0).getPassword()); - - // 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/PropertyValueChecks.java b/test/src/main/java/org/linguafranca/pwdb/checks/PropertyValueChecks.java deleted file mode 100644 index ec9a153a..00000000 --- a/test/src/main/java/org/linguafranca/pwdb/checks/PropertyValueChecks.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2015 Jo Rabin - * - * 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.checks; - -import org.junit.BeforeClass; -import org.junit.Test; -import org.linguafranca.pwdb.*; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import static org.junit.Assert.*; - -/** - * Testing the operation of PropertyValue mechanisms - */ -public abstract class PropertyValueChecks, G extends Group, - E extends Entry, I extends Icon> { - - public abstract void saveDatabase(D database, Credentials credentials, OutputStream outputStream) throws IOException; - public abstract D loadDatabase(Credentials credentials, InputStream inputStream) throws IOException; - public abstract Credentials getCreds(byte[] creds); - - private final boolean propertyValueSupported; - protected D database; - - @BeforeClass - public static void ensureOutputDir() throws IOException { - Files.createDirectories(Paths.get("testOutput")); - } - - public PropertyValueChecks(boolean propertyValueSupported) throws IOException { - this.database = createDatabase(); - this.propertyValueSupported = propertyValueSupported; - } - - public abstract D createDatabase() throws IOException; - - /** - * Catches expected thrown exception - * - * @param f a Consumer i.e. a function that does have output - * @param c the exception that is expected to be thrown when the function is executed - */ - private void catchException(Consumer f, @SuppressWarnings("SameParameterValue") Class c) { - try { - f.accept(null); - } catch (Exception e) { - if (c.equals(e.getClass())) { - return; - } - } - fail(c.getName() + " expected"); - } - - /** - * Check that unsupported operations throw exceptions - */ - @Test - public void noPropertyValueSupport() { - if (propertyValueSupported) return; - - assertFalse(database.supportsPropertyValueStrategy()); - // test a sample of the methods that are supposed to thrown if used here - catchException((z) -> database.setPropertyValueStrategy(null), UnsupportedOperationException.class); - catchException((z) -> database.getPropertyValueStrategy(), UnsupportedOperationException.class); - catchException((z) -> database.setShouldProtect(null, true), UnsupportedOperationException.class); - catchException((z) -> database.listShouldProtect(), UnsupportedOperationException.class); - - E entry = database.newEntry(); - catchException((z) -> entry.getPropertyValue(null), UnsupportedOperationException.class); - catchException((z) -> entry.setPropertyValue(null, (PropertyValue) null), UnsupportedOperationException.class); - - } - - @Test - public void checkDefaults(){ - // these are the defaults established by createDatabase() - assertTrue(database.shouldProtect(Entry.STANDARD_PROPERTY_NAME_PASSWORD)); - assertFalse(database.shouldProtect(Entry.STANDARD_PROPERTY_NAME_USER_NAME)); - assertFalse(database.shouldProtect(Entry.STANDARD_PROPERTY_NAME_NOTES)); - assertFalse(database.shouldProtect(Entry.STANDARD_PROPERTY_NAME_URL)); - assertFalse(database.shouldProtect(Entry.STANDARD_PROPERTY_NAME_TITLE)); - } - - @Test - public void expectedStorageType() throws IOException { - if (!propertyValueSupported) { - return; - } - - // Password is protected - PropertyValue.Strategy pvs = database.getPropertyValueStrategy(); - PropertyValue pv = pvs.getFactoryFor("Password").of("123"); - assertEquals(pv.getClass(), pvs.newProtected().of("a").getClass()); - assertTrue(pv.isProtected()); - - // Notes isn't protected - pv = pvs.getFactoryFor("Notes").of("123"); - assertEquals(pv.getClass(), pvs.newUnprotected().of("b").getClass()); - assertFalse(pv.isProtected()); - - // "random" is not protected - pv = pvs.getFactoryFor("random").of("123"); - assertEquals(pv.getClass(), pvs.newUnprotected().of("b").getClass()); - assertFalse(pv.isProtected()); - - // create a strategy that makes "random" protected - PropertyValue.Strategy newPvs = new PropertyValue.Strategy() { - @Override - public List getProtectedProperties() { - List pp = new ArrayList<>(pvs.getProtectedProperties()); - pp.add("random"); - return pp; - } - - @Override - public PropertyValue.Factory newProtected() { - return pvs.newProtected(); - } - - @Override - public PropertyValue.Factory newUnprotected() { - return pvs.newUnprotected(); - } - }; - database.setPropertyValueStrategy(newPvs); - - // get a value for random and check it is protected - pv = newPvs.getFactoryFor("random").of("123"); - assertEquals(pv.getClass(), pvs.newProtected().of("b").getClass()); - assertTrue(pv.isProtected()); - - // create an entry with random property as protected value - E entry = database.newEntry("Test Random"); - entry.setPropertyValue("random", pv); - database.getRootGroup().addEntry(entry); - - // Save database - - FileOutputStream fos = new FileOutputStream("testOutput/test9.kdbx"); - saveDatabase(database, getCreds("123".getBytes()), fos); - fos.flush(); - fos.close(); - - // reload database, "random" is still protected even though it's not protected by default - FileInputStream fis = new FileInputStream("testOutput/test9.kdbx"); - D input = loadDatabase(getCreds("123".getBytes()), fis); - - List entries = input.findEntries("random"); - assertEquals(1, entries.size()); - assertTrue(entries.get(0).getPropertyValue("random").isProtected()); - assertFalse(input.getPropertyValueStrategy().getProtectedProperties().contains("random")); -} - -} diff --git a/test/src/main/java/org/linguafranca/util/HexViewer.java b/test/src/main/java/org/linguafranca/util/HexViewer.java deleted file mode 100644 index 2ec49e0c..00000000 --- a/test/src/main/java/org/linguafranca/util/HexViewer.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.linguafranca.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; - -import static org.linguafranca.util.TestUtil.getTestPrintStream; - -/** - * Useful for e.g. viewing the raw file contents - */ -public class HexViewer { - - static PrintStream printStream = getTestPrintStream(); - - public static void main(String[] args) throws IOException { - - - try (InputStream is = HexViewer.class.getClassLoader().getResourceAsStream("V4-AES-Argon2-CustomIcon.kdbx");) { - list(is); - } - } - - public static void list(InputStream is) throws IOException { - for (int i = 0; i < 32; i++) { - byte[] buf = new byte [16]; - is.read(buf); - StringBuilder sb = new StringBuilder(); - for (byte b: buf) { - sb.append(String.format("%02X ", b)); - } - sb.append(" "); - for (byte b : buf) { - sb.append(b < 0x20 || b > 0x7e ? (char) 0x00B7 : (char) b); - } - printStream.println(sb); - } - } -} - - diff --git a/test/src/main/java/org/linguafranca/util/TestUtil.java b/test/src/main/java/org/linguafranca/util/TestUtil.java deleted file mode 100644 index c6433e42..00000000 --- a/test/src/main/java/org/linguafranca/util/TestUtil.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.linguafranca.util; - -import com.google.common.io.ByteStreams; - -import java.io.PrintStream; - -public class TestUtil { - /** - * set system property to suppress output from tests - * @return if "nullOutput has been set, e.g. in a profile - */ - public static PrintStream getTestPrintStream() { - return Boolean.getBoolean("inhibitConsoleOutput") ? - new PrintStream(ByteStreams.nullOutputStream()) : - new PrintStream(System.out); - } -} diff --git a/v3.md b/v3.md new file mode 100644 index 00000000..806d97a6 --- /dev/null +++ b/v3.md @@ -0,0 +1,28 @@ +``` java + public void canonicalQuickStart() throws IOException { + + // V3 create a database, no need to specify what kind, there is only one kind + // V3 Database no longer need to be generified + Database database = new KdbxDatabase(); + // V3 the property value strategy is set to this, anyway, but the Default property value strategy is to protect + // field "Password" and no other, all of which are stored as byte[] + database.setPropertyValueStrategy(new PropertyValue.Strategy.Default()); + // create a group (as before) + Group main = database.newGroup("Main"); + // add to root (as before) + database.getRootGroup().addGroup(main); + // V3 fluent building of Entry (using PropertyValueStrategy as set in database to determine protection) + main.addEntry("entry") + // V3 abbreviated static import of standard field names + .addProperty(USER_NAME, "Tom") + .addProperty(PASSWORD, "123".getBytes()) + .addProperty(URL, "http://localhost:8080/"); + + + // V3 KdbxCreds now called KdbxCredentials + KdbxCredentials credentials = new KdbxCredentials("123".getBytes()); + try (OutputStream outputStream = Files.newOutputStream(Path.of(TEST_OUTPUT_DIR,"test.kdbx"))) { + database.save(credentials, outputStream); + } + } + ``` \ No newline at end of file