Compare commits
87 Commits
fix/passwo
...
fix/snap-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddfc94de4b | ||
|
|
1239e59ee1 | ||
|
|
434f31cba2 | ||
|
|
32abcb0d52 | ||
|
|
edd921401d | ||
|
|
e98c98c6c3 | ||
|
|
faf45079ef | ||
|
|
63714a6f5f | ||
|
|
31c0b23890 | ||
|
|
33a3796074 | ||
|
|
1b1643b5d1 | ||
|
|
244ed42231 | ||
|
|
af2d0b1429 | ||
|
|
86f74a00d0 | ||
|
|
ab6b6f36a0 | ||
|
|
5a3289ee3c | ||
|
|
e4bb80b96c | ||
|
|
d6e726a9cf | ||
|
|
03855fc411 | ||
|
|
518dd71de6 | ||
|
|
41b6247178 | ||
|
|
2ea656bc27 | ||
|
|
253bb42ac0 | ||
|
|
0b5ae1775c | ||
|
|
9ba6ada266 | ||
|
|
51e8c042af | ||
|
|
c9a64be699 | ||
|
|
15ac8ac4f8 | ||
|
|
8ca90a070a | ||
|
|
811887e591 | ||
|
|
81fa8d5947 | ||
|
|
5ee5e4998a | ||
|
|
046cb9bd70 | ||
|
|
831704f3b2 | ||
|
|
4d7eae34c2 | ||
|
|
8d6d937b1b | ||
|
|
f5bb5985ee | ||
|
|
6494cdbb4c | ||
|
|
9de623120b | ||
|
|
f3b08102c4 | ||
|
|
29ac4da240 | ||
|
|
832340e209 | ||
|
|
dce34de875 | ||
|
|
40ee047ef0 | ||
|
|
17dc022e15 | ||
|
|
620abb96f2 | ||
|
|
edab0faa94 | ||
|
|
9e29b5c7b6 | ||
|
|
2afec91e87 | ||
|
|
fb022cb5e9 | ||
|
|
e76e9d42c7 | ||
|
|
9670a5e74e | ||
|
|
132ca42ec5 | ||
|
|
af0c1644a6 | ||
|
|
d60472328f | ||
|
|
5e61db630c | ||
|
|
0cb0373f85 | ||
|
|
6bbb7dcfac | ||
|
|
f2ed4e3840 | ||
|
|
cdbff32f25 | ||
|
|
f71cca4eba | ||
|
|
b1180b3341 | ||
|
|
9a63e80386 | ||
|
|
b29993abe5 | ||
|
|
1d581ee027 | ||
|
|
bf856d278d | ||
|
|
9b8163c3a4 | ||
|
|
b9c5869806 | ||
|
|
4acb3774e6 | ||
|
|
2fc24be331 | ||
|
|
bff0b93f5f | ||
|
|
ca9b88fae8 | ||
|
|
6e81451f64 | ||
|
|
d03ffc228c | ||
|
|
2738a72b43 | ||
|
|
5d24495704 | ||
|
|
34fe413dad | ||
|
|
feafceca57 | ||
|
|
8acc54225d | ||
|
|
6e0baf9f2c | ||
|
|
95bae8377c | ||
|
|
f07db033c6 | ||
|
|
4f8c204096 | ||
|
|
02881889d5 | ||
|
|
d57d167e9c | ||
|
|
740994ed48 | ||
|
|
dbf9587775 |
39
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: provide information about a problem
|
||||
title:
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
## Overview
|
||||
[TIP]: # ( DO NOT include screenshots of your actual database! )
|
||||
[NOTE]: # ( Give a BRIEF summary about your problem )
|
||||
|
||||
|
||||
## Steps to Reproduce
|
||||
[NOTE]: # ( Provide a simple set of steps to reproduce this bug. )
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected Behavior
|
||||
[NOTE]: # ( Tell us what you expected to happen )
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
[NOTE]: # ( Tell us what actually happens )
|
||||
|
||||
|
||||
## Context
|
||||
[NOTE]: # ( Give us any additional information you may have. )
|
||||
|
||||
|
||||
[NOTE]: # ( Paste debug info from Help → About here )
|
||||
KeePassXC - VERSION
|
||||
Revision: REVISION
|
||||
|
||||
[NOTE]: # ( Pick choices based on your environment )
|
||||
Operating System: Windows/Linux/macOS
|
||||
Desktop Env: Gnome/KDE/XFCE/Mate/Cinnamon
|
||||
Windowing System: X11/Wayland
|
||||
83
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Bug Report
|
||||
description: Provide information about a problem you are experiencing.
|
||||
type: Bug
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Have you searched for an existing issue?
|
||||
description: |
|
||||
Use the issue search box to see if one already exists for the bug you encountered.
|
||||
Also take a moment to review our pinned issues.
|
||||
options:
|
||||
- label: Yes, I tried searching and reviewed the pinned issues
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Brief Summary
|
||||
description: |
|
||||
Provide an overview of the problem, include any information that may help us triage this issue.
|
||||
Provide screenshots if possible, but do NOT show sensitive data (use View -> Allow Screen Capture).
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Provide a simple set of steps to reproduce this bug.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected_vs_actual
|
||||
attributes:
|
||||
label: Expected Versus Actual Behavior
|
||||
description: Tell us what you expected to happen and what actually happened.
|
||||
|
||||
- type: textarea
|
||||
id: debug_info
|
||||
attributes:
|
||||
label: KeePassXC Debug Information
|
||||
placeholder: "Paste the output of: Help -> About -> Debug Info"
|
||||
render: Text
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: Select your operating system.
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
- macOS
|
||||
- Other (BSD, Haiku, etc)
|
||||
|
||||
- type: dropdown
|
||||
id: desktop_env
|
||||
attributes:
|
||||
label: Linux Desktop Environment
|
||||
description: If on Linux, please select your desktop environment.
|
||||
options:
|
||||
- Gnome
|
||||
- KDE
|
||||
- XFCE
|
||||
- Mate / Cinnamon
|
||||
- Sway
|
||||
- i3
|
||||
- Other
|
||||
|
||||
- type: dropdown
|
||||
id: window_system
|
||||
attributes:
|
||||
label: Linux Windowing System
|
||||
description: If on Linux, please select your windowing system.
|
||||
options:
|
||||
- X11
|
||||
- Wayland
|
||||
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: tell us about a new feature you want
|
||||
title:
|
||||
labels: new feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
## Summary
|
||||
[TIP]: # ( DO NOT include screenshots of your actual database! )
|
||||
[NOTE]: # ( Provide a brief overview of what the new feature is all about )
|
||||
|
||||
|
||||
## Examples
|
||||
[NOTE]: # ( Show us a picture or mock-up of your proposal )
|
||||
|
||||
|
||||
## Context
|
||||
[NOTE]: # ( Why does this feature matter to you? What unique circumstances do you have? )
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Feature Request
|
||||
description: Tell us about a new feature you want.
|
||||
type: Feature
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Have you searched for an existing feature request?
|
||||
description: Use the issue search box to see if one already exists for the feature you want.
|
||||
options:
|
||||
- label: Yes, I tried searching
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Brief Summary
|
||||
description: |
|
||||
Provide an overview of the feature you are interested in adding.
|
||||
Provide screenshots if possible, but do NOT show sensitive data (use View -> Allow Screen Capture).
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: example
|
||||
attributes:
|
||||
label: Example
|
||||
description: Provide an example of how this feature would be used.
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Context
|
||||
description: Why does this feature matter to you? What unique circumstances do you have?
|
||||
85
.github/ISSUE_TEMPLATE/prerelease_bug_report.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Pre-Release Bug Report
|
||||
description: Report an issue with pre-release code (e.g. snapshot builds).
|
||||
type: Bug
|
||||
labels: PRE-RELEASE BUG
|
||||
assignees: droidmonkey
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Have you searched for an existing issue?
|
||||
description: |
|
||||
Use the issue search box to see if one already exists for the bug you encountered.
|
||||
Also take a moment to review our pinned issues.
|
||||
options:
|
||||
- label: Yes, I tried searching and reviewed the pinned issues
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Brief Summary
|
||||
description: |
|
||||
Provide an overview of the problem, include any information that may help us triage this issue.
|
||||
Provide screenshots if possible, but do NOT show sensitive data (use View -> Allow Screen Capture).
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Provide a simple set of steps to reproduce this bug.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected_vs_actual
|
||||
attributes:
|
||||
label: Expected Versus Actual Behavior
|
||||
description: Tell us what you expected to happen and what actually happened.
|
||||
|
||||
- type: textarea
|
||||
id: debug_info
|
||||
attributes:
|
||||
label: KeePassXC Debug Information
|
||||
placeholder: "Paste the output of: Help -> About -> Debug Info"
|
||||
render: Text
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: Select your operating system.
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
- macOS
|
||||
- Other (BSD, Haiku, etc)
|
||||
|
||||
- type: dropdown
|
||||
id: desktop_env
|
||||
attributes:
|
||||
label: Linux Desktop Environment
|
||||
description: If on Linux, please select your desktop environment.
|
||||
options:
|
||||
- Gnome
|
||||
- KDE
|
||||
- XFCE
|
||||
- Mate / Cinnamon
|
||||
- Sway
|
||||
- i3
|
||||
- Other
|
||||
|
||||
- type: dropdown
|
||||
id: window_system
|
||||
attributes:
|
||||
label: Linux Windowing System
|
||||
description: If on Linux, please select your windowing system.
|
||||
options:
|
||||
- X11
|
||||
- Wayland
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Release Preview Bug report
|
||||
about: report a bug with a release preview (e.g., 2.6.0-beta1)
|
||||
title:
|
||||
labels: PRE-RELEASE BUG
|
||||
assignees: droidmonkey
|
||||
|
||||
---
|
||||
## Overview
|
||||
[TIP]: # ( DO NOT include screenshots of your actual database! )
|
||||
[NOTE]: # ( Give a BRIEF summary about your problem )
|
||||
|
||||
|
||||
## Steps to Reproduce
|
||||
[NOTE]: # ( Provide a simple set of steps to reproduce this bug. )
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected Behavior
|
||||
[NOTE]: # ( Tell us what you expected to happen )
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
[NOTE]: # ( Tell us what actually happens )
|
||||
|
||||
|
||||
## Context
|
||||
[NOTE]: # ( Give us any additional information you may have. )
|
||||
|
||||
|
||||
[NOTE]: # ( Paste debug info from Help → About here )
|
||||
KeePassXC - VERSION
|
||||
Revision: REVISION
|
||||
|
||||
[NOTE]: # ( Pick choices based on your environment )
|
||||
Operating System: Windows/Linux/macOS
|
||||
Desktop Env: Gnome/KDE/XFCE/Mate/Cinnamon
|
||||
Windowing System: X11/Wayland
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,15 +1,15 @@
|
||||
[NOTE]: # ( Describe your changes in detail, why is this change required? )
|
||||
[NOTE]: # ( Explain large or complex code modifications. )
|
||||
[NOTE]: # ( If it fixes an open issue, please add "Fixes #XXX" )
|
||||
[NOTE]: # ( If it fixes an open issue, please add "Fixes #XXX". )
|
||||
|
||||
|
||||
## Screenshots
|
||||
[TIP]: # ( Do not include screenshots of your actual database! )
|
||||
|
||||
[NOTE]: # ( Do not include screenshots of your actual database! )
|
||||
[TIP]: # ( Use View -> Allow Screen Capture )
|
||||
|
||||
## Testing strategy
|
||||
[NOTE]: # ( Please describe in detail how you tested your changes. )
|
||||
[TIP]: # ( We expect new code to be covered by unit tests and documented with doc blocks! )
|
||||
[TIP]: # ( We expect new code to be covered by unit tests and include helpful comments. )
|
||||
|
||||
|
||||
## Type of change
|
||||
|
||||
31
CHANGELOG.md
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 2.8.0 (Pending)
|
||||
* Placeholder for future release notes
|
||||
|
||||
## 2.7.9 (2024-06-19)
|
||||
|
||||
### Changes
|
||||
* Passkeys: Ability to easily remove a passkey from an entry [#10777]
|
||||
* Snap: Use new desktop portal for native messaging integration [#10906]
|
||||
|
||||
### Fixes
|
||||
* Improve entry placeholder/reference feature [#10846]
|
||||
* Improve CSV importing when title field isn't specified [#10843]
|
||||
* Improve encrypted Bitwarden importing [#10800]
|
||||
* Improve database settings UX [#10821]
|
||||
* Improve handling of clipboard actions from entry preview [#10810]
|
||||
* Improve group/entry view resize behavior and set sensible defaults [#10641]
|
||||
* Passkeys: Fix incorrect username fill [#10874]
|
||||
* Passkeys: Return additional data to the extension [#10857]
|
||||
* Fix password clear timer inconsistency on unlock view [#10708]
|
||||
* Fix portability check [#10760]
|
||||
* Fix page overflow on HTML exports [#10735]
|
||||
* Fix broken builds when using system provided zxcvbn [#10717]
|
||||
* Fix copy password button when text is selected [#10853]
|
||||
* Fix tab ordering on application settings pages [#10907]
|
||||
* SSH Agent: Fix broken decrypt button [#10638]
|
||||
* Windows: Fix ALT Auto-Type modifier [#10795]
|
||||
* Windows: Fix wrong DACL memory size allocation [#10712]
|
||||
* macOS: Fix monospace font sizing [#10739]
|
||||
* Flatpak: Fix configuration settings off-by-one error [#10688]
|
||||
* BSD: Fix compiling with libusb implementation [#10736]
|
||||
|
||||
## 2.7.8 (2024-05-05)
|
||||
|
||||
### Changes
|
||||
|
||||
3
COPYING
@@ -137,10 +137,12 @@ Files: share/icons/badges/2_Expired.svg
|
||||
share/icons/database/C46_Help.svg
|
||||
share/icons/database/C53_Apply.svg
|
||||
share/icons/database/C61_Services.svg
|
||||
share/icons/application/scalable/actions/proton.svg
|
||||
Copyright: 2022 KeePassXC Team <team@keepassxc.org>
|
||||
License: MIT
|
||||
|
||||
Files: share/icons/application/scalable/actions/application-exit.svg
|
||||
share/icons/application/scalable/actions/arrow-collapse-down.svg
|
||||
share/icons/application/scalable/actions/attributes-copy.svg
|
||||
share/icons/application/scalable/actions/auto-type.svg
|
||||
share/icons/application/scalable/actions/bitwarden.svg
|
||||
@@ -176,6 +178,7 @@ Files: share/icons/application/scalable/actions/application-exit.svg
|
||||
share/icons/application/scalable/actions/entry-delete.svg
|
||||
share/icons/application/scalable/actions/entry-restore.svg
|
||||
share/icons/application/scalable/actions/entry-edit.svg
|
||||
share/icons/application/scalable/actions/entry-expire.svg
|
||||
share/icons/application/scalable/actions/entry-new.svg
|
||||
share/icons/application/scalable/actions/favicon-download.svg
|
||||
share/icons/application/scalable/actions/fingerprint.svg
|
||||
|
||||
@@ -22,12 +22,13 @@ KeePassXC has numerous features for novice and power users alike. Our goal is to
|
||||
* Password generator
|
||||
* Auto-Type passwords into applications
|
||||
* Browser integration with Google Chrome, Mozilla Firefox, Microsoft Edge, Chromium, Vivaldi, Brave, and Tor-Browser
|
||||
* Support for passkeys using the browser integration
|
||||
* Entry icon download
|
||||
* Import databases from CSV, 1Password, and KeePass1 formats
|
||||
* Import databases from CSV, 1Password, Bitwarden, Proton Pass, and KeePass1 formats
|
||||
|
||||
### Advanced
|
||||
* Database reports (password health, HIBP, and statistics)
|
||||
* Database export to CSV and HTML formats
|
||||
* Database export to CSV, XML, and HTML formats
|
||||
* TOTP storage and generation
|
||||
* Field references between entries
|
||||
* File attachments and custom attributes
|
||||
|
||||
46
SECURITY.md
Normal file
@@ -0,0 +1,46 @@
|
||||
### Reporting Security Issues
|
||||
|
||||
The KeePassXC team takes security vulnerabilities very seriously and appreciates your responsible disclosure efforts. We will make every effort to acknowledge your contributions and handle them promptly.
|
||||
|
||||
To report a security issue, please use one of the following methods:
|
||||
|
||||
- **GitHub Security Advisory:** Use the ["Report a Vulnerability"](https://github.com/keepassxreboot/keepassxc/security/advisories/new) tab on our GitHub repository.
|
||||
- **Private Matrix Message:** Contact any of the following KeePassXC team members privately (also encrypted):
|
||||
- [@droidmonkey_kpxc](https://matrix.to/#/@droidmonkey_kpxc:matrix.org)
|
||||
- [@varjolintu](https://matrix.to/#/@varjolintu:matrix.org)
|
||||
- [@phoerious](https://matrix.to/#/@phoerious:matrix.org)
|
||||
- **Send an Email:** Send your report to team@keepassxc.org. We recommend encrypting the email if possible.
|
||||
|
||||
Please **DO NOT** use public channels (e.g., GitHub issues, Matrix chat channels) for initial reporting of bona fide security vulnerabilities.
|
||||
|
||||
Once you report a security issue, our team will respond with the next steps. After our initial reply, we will keep you informed of the progress towards a fix and full announcement. We may ask for additional information or guidance during this process. If we disagree that your report constitutes a genuine security vulnerability, we will inform you and close the report. Your report may be turned into an issue for further tracking.
|
||||
|
||||
If you discover vulnerabilities in third-party modules used by KeePassXC, please report them to the maintainers of the respective modules. If the vulnerability impacts KeePassXC directly, we encourage you to notify us using the above methods. We will validate if the vulnerability is exploitable from KeePassXC code; please note that not all vulnerabilities are actually exploitable and do not constitute an immediate concern for the KeePassXC application.
|
||||
|
||||
### Example Security Vulnerabilities
|
||||
|
||||
When reporting, please ensure the issue falls under what can be considered a genuine security vulnerability for KeePassXC. Some examples include:
|
||||
|
||||
- Unauthorized access to sensitive user data (e.g., passwords).
|
||||
- Remote code execution or escalation of privileges.
|
||||
- Bypassing authentication or encryption mechanisms.
|
||||
- Broken or improperly implemented encryption methods.
|
||||
|
||||
### Counter Examples
|
||||
|
||||
The following issues are **not** considered security vulnerabilities:
|
||||
|
||||
- Bugs caused by locally modifying the application (e.g., injecting DLLs, altering code).
|
||||
- Crashes or misbehavior resulting from normal use (report this as a normal issue).
|
||||
- Vulnerabilities found in third-party modules (should be reported to the module’s maintainers).
|
||||
|
||||
### CVE Reporting Policy
|
||||
|
||||
Please **DO NOT** submit a report to a Common Vulnerabilities and Exposures (CVE) Numbering Authority (CNA) before confirming the security vulnerability with the KeePassXC team. If we do not respond to your report within 30 days, this restriction no longer applies.
|
||||
|
||||
|
||||
### Other Communication
|
||||
|
||||
For other inquiries (e.g., developer questions, user questions), please use the public channels on Matrix:
|
||||
- **User's Channel:** [#keepassxc:mozilla.org](https://matrix.to/#/#keepassxc:mozilla.org)
|
||||
- **Developer's Channel:** [#keepassxc-dev:mozilla.org](https://matrix.to/#/#keepassxc-dev:mozilla.org)
|
||||
@@ -21,16 +21,38 @@ endif()
|
||||
|
||||
if(NOT PCSC_FOUND)
|
||||
# Search for PC/SC headers on Mac and Windows
|
||||
|
||||
# Additional search paths for Windows if not running in Visual Studio environment
|
||||
if (WIN32)
|
||||
# Resolve the ambiguity of using two names for one architechture
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x64")
|
||||
set(ARCH_DIR "x64")
|
||||
else()
|
||||
set(ARCH_DIR "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
# Locate Windows SDK Paths
|
||||
if (CMAKE_WINDOWS_KITS_10_DIR)
|
||||
set(WINSDKROOTC_INCLUDE "${CMAKE_WINDOWS_KITS_10_DIR}/Include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/um")
|
||||
set(WINSDKROOTC_LIB "${CMAKE_WINDOWS_KITS_10_DIR}/LIB/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/um/${ARCH_DIR}")
|
||||
else()
|
||||
set(WINSDKROOTC_INCLUDE "$ENV{ProgramFiles\(x86\)}/Windows Kits/10/Include/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/um")
|
||||
set(WINSDKROOTC_LIB "$ENV{ProgramFiles\(x86\)}/Windows Kits/10/LIB/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/um/${ARCH_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_path(PCSC_INCLUDE_DIRS winscard.h
|
||||
HINTS
|
||||
${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}
|
||||
/usr/include/PCSC
|
||||
${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}
|
||||
/usr/include/PCSC
|
||||
${WINSDKROOTC_INCLUDE}
|
||||
PATH_SUFFIXES PCSC)
|
||||
|
||||
# MAC library is PCSC, Windows library is WinSCard
|
||||
find_library(PCSC_LIBRARIES NAMES pcsclite libpcsclite WinSCard PCSC
|
||||
HINTS
|
||||
${CMAKE_C_IMPLICIT_LINK_DIRECTORIES})
|
||||
${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}
|
||||
${WINSDKROOTC_LIB})
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
@@ -26,8 +26,8 @@ include::topics/DownloadInstall.adoc[tags=*;!advanced]
|
||||
|
||||
include::topics/UserInterface.adoc[tags=*;!advanced]
|
||||
|
||||
include::topics/PasswordGenerator.adoc[tags=*;!advanced]
|
||||
|
||||
include::topics/DatabaseOperations.adoc[tags=*;!advanced]
|
||||
|
||||
include::topics/BrowserPlugin.adoc[tags=*;!advanced]
|
||||
include::topics/PasswordGenerator.adoc[tags=*;!advanced]
|
||||
|
||||
include::topics/BrowserIntegration.adoc[tags=*;!advanced]
|
||||
|
||||
@@ -6,6 +6,7 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:imagesdir: images
|
||||
:stylesheet: styles/dark.css
|
||||
:toc: left
|
||||
:sectanchors:
|
||||
ifdef::backend-pdf[]
|
||||
:title-page:
|
||||
:title-logo-image: {imagesdir}/kpxc_logo.png
|
||||
@@ -23,18 +24,18 @@ include::topics/UserInterface.adoc[tags=*]
|
||||
|
||||
include::topics/DatabaseOperations.adoc[tags=*]
|
||||
|
||||
include::topics/ImportExport.adoc[tags=*]
|
||||
|
||||
include::topics/PasswordGenerator.adoc[tags=*]
|
||||
|
||||
include::topics/BrowserPlugin.adoc[tags=*]
|
||||
include::topics/ImportExport.adoc[tags=*]
|
||||
|
||||
include::topics/KeeShare.adoc[tags=*]
|
||||
|
||||
include::topics/BrowserIntegration.adoc[tags=*]
|
||||
|
||||
include::topics/Passkeys.adoc[tags=*]
|
||||
|
||||
include::topics/AutoType.adoc[tags=*]
|
||||
|
||||
include::topics/KeeShare.adoc[tags=*]
|
||||
|
||||
include::topics/SSHAgent.adoc[tags=*]
|
||||
|
||||
include::topics/Reference.adoc[tags=*]
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
@@ -58,6 +58,9 @@ Your database works offline and requires no internet connection.
|
||||
*--pw-stdin*::
|
||||
Reads password of the database from stdin.
|
||||
|
||||
*--minimized*::
|
||||
Starts KeePassXC minimized to the system tray.
|
||||
|
||||
*--debug-info*::
|
||||
Displays debugging information.
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ body.toc2.toc-right{padding-left:0;padding-right:20em}}
|
||||
.sect1{padding-bottom:1.25em}}
|
||||
.sect1:last-child{padding-bottom:0}
|
||||
.sect1+.sect1{border-top:1px solid #efefed}
|
||||
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
|
||||
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:2.0ex;margin-left:-1.8ex;margin-top:0.08ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
|
||||
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
|
||||
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
|
||||
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
|
||||
|
||||
@@ -37,7 +37,7 @@ TIP: You can use an asterisk (`\*`) to match any value (e.g., when a window titl
|
||||
.Auto-Type entry sequences
|
||||
image::autotype_entry_sequences.png[]
|
||||
|
||||
2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: xref:UserGuide.adoc#_auto_type_actions[Auto-Type Actions Reference] and xref:UserGuide.adoc#_entry_placeholders[Entry Placeholders Reference]. Action codes and placeholders are not case sensitive.
|
||||
2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: <<Auto-Type Actions, Auto-Type Actions Reference>> and <<Entry Placeholders, Entry Placeholders Reference>>. Action codes and placeholders are not case sensitive.
|
||||
+
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
|
||||
@@ -1,190 +1,201 @@
|
||||
= KeePassXC – Browser Plugin
|
||||
include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Setup Browser Integration
|
||||
The KeePassXC-Browser extension is installed within your web browser so that you can automatically pull usernames and passwords from KeePassXC and populate them directly into website fields. It is a very useful and secure extension that enhances your productivity while using KeePassXC. With this extension, you do not need to manually copy the data from your KeePassXC database and paste it into the website fields.
|
||||
|
||||
The KeePassXC-Browser extension is available on the following web browsers:
|
||||
|
||||
* Google Chrome, Vivaldi, and Brave
|
||||
* Mozilla Firefox and Tor-Browser
|
||||
* Microsoft Edge
|
||||
* Chromium
|
||||
|
||||
=== Install the Browser Extension
|
||||
You can download the KeePassXC-Browser extension from your web browser. To download the KeePassXC-Browser extension, perform the following steps:
|
||||
|
||||
1. Click the link corresponding to your browser:
|
||||
* https://chromewebstore.google.com/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk[Chrome, Chromium, Vivaldi, and Brave]
|
||||
* https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser[Mozilla Firefox and Tor-Browser]
|
||||
* https://microsoftedge.microsoft.com/addons/detail/keepassxcbrowser/pdffhmdngciaglkoonimfcmckehcpafo[Microsoft Edge]
|
||||
|
||||
2. Click the button to install/add the extension to the browser. Accept any confirmation dialogs.
|
||||
|
||||
TIP: For the most up-to-date troubleshooting advice on all platforms, please read our https://github.com/keepassxreboot/keepassxc-browser/wiki/Troubleshooting-guide[Troubleshooting Guide].
|
||||
|
||||
// tag::advanced[]
|
||||
NOTE: When Microsoft Edge is installed as a managed application, system administrators are required to deploy a custom native messaging configuration. Instructions for this are found in the advanced section below.
|
||||
// end::advanced[]
|
||||
|
||||
=== Configure KeePassXC-Browser
|
||||
To start using KeePassXC-Browser, you must configure it so that it can communicate with the KeePassXC application on your desktop.
|
||||
|
||||
To configure KeePassXC-Browser, perform the following steps:
|
||||
|
||||
1. Open the KeePassXC application on your desktop and navigate to Tools > Settings.
|
||||
|
||||
2. Click the Browser Integration option on the left-hand side *(1)*. The following screen appears:
|
||||
+
|
||||
.Browser Settings
|
||||
image::browser_settings.png[]
|
||||
|
||||
3. Click the _Enable browser integration_ checkbox *(2)*. Then select the browsers for which you have downloaded the KeePassXC-Browser extension *(3)* and click *OK*.
|
||||
|
||||
4. Ensure your database is unlocked, then open (or restart) your browser.
|
||||
|
||||
5. Click the KeePassXC-Browser extension icon *(A)* in your browser (see figure below). A pop-up window appears.
|
||||
+
|
||||
.Connect Extension to KeePassXC
|
||||
image::browser_extension_connect.png[,80%]
|
||||
|
||||
6. Click the _Connect_ button *(B)* in the pop-up window to complete integrating the KeePassXC-Browser extension with your KeePassXC desktop application.
|
||||
|
||||
7. You are now prompted to enter a unique name to identify the connection between this browser and your database. Enter a unique name in the field (e.g., firefox-laptop) and click the _Save and allow access_ button.
|
||||
+
|
||||
.Extension Association Dialog
|
||||
image::browser_extension_association.png[,80%]
|
||||
|
||||
WARNING: If you reuse a connection name in a database, the previous browser connection will be overwritten and prevent access.
|
||||
|
||||
=== Using the Browser Extension
|
||||
The KeePassXC-Browser extension lets you automatically populate the entries from your KeePassXC database into the fields on websites you visit. To do so, perform the following steps:
|
||||
|
||||
1. Open your KeePassXC desktop application and unlock your database.
|
||||
|
||||
2. Open your web browser. The KeePassXC-Browser extension icon in your browser window will change based on its connection state. The figure below shows the different states.
|
||||
+
|
||||
*(A)* KeePassXC is not running or is disconnected. +
|
||||
*(B)* KeePassXC is running, but KeePassXC Browser Extension is not connected to the current database. +
|
||||
*\(C)* Connected to KeePassXC, but database is locked. +
|
||||
*(D)* Connected to KeePassXC and ready to use. If the icon is shown with a number, it indicates the number of credentials found for the current site.
|
||||
+
|
||||
.Extension Icon States
|
||||
image::browser_extension_icons.png[,70%]
|
||||
|
||||
3. If the KeePassXC desktop application is not connected with the KeePassXC-Browser extension, click the extension icon in your web browser and click _Reload_ from the pop-up window as shown in the following screen.
|
||||
+
|
||||
.Reload Extension Connection
|
||||
image::browser_extension_reload.png[,80%]
|
||||
|
||||
4. Open the URL for which you want to use with your database. If you have previously created an entry in your database then the KeePassXC-Browser Confirm Access dialog may appear:
|
||||
+
|
||||
.Confirm Access Dialog
|
||||
image::browser_confirm_access_dialog.png[,80%]
|
||||
|
||||
5. Ensure the credentials you want to use are checked, then click *(A)* Remember _(optional)_, then click _Allow Selected_ *(B)*.
|
||||
|
||||
6. In your website, the KeePassXC icon will appear in the username field of the login form *(A)*. Click the icon to populate the field with your stored credentials. If you have more than one credential for this website, a dropdown will appear to choose the one to use.
|
||||
+
|
||||
.Fill Credentials
|
||||
image::browser_fill_credentials.png[,80%]
|
||||
|
||||
=== Generate Passwords
|
||||
|
||||
The KeePassXC-Browser Extension also lets you generate passwords directly in your browser.
|
||||
This feature can be used for websites with existing credentials as well as for new websites.
|
||||
You can then choose to update/add the credentials to your KeePassXC database directly from the Browser.
|
||||
|
||||
1. Ensure your database is unlocked and configured to use the Browser extension as shown above.
|
||||
|
||||
2. Right click on a password field and from the KeePassXC sub-menu choose _Show Password Generator_. The standard KeePassXC password generator will appear.
|
||||
|
||||
3. Configure the password generation options and click _Apply Password_ when done. The generated password will be filled into the previously selected field.
|
||||
|
||||
4. When you have successfully submitted the password on the website, a popup will appear asking you to either update an existing entry or add a new one.
|
||||
|
||||
// tag::advanced[]
|
||||
=== Browser statistics
|
||||
You can see a cross-section of all browser-related settings applied to entries within a database through the Browser Statistics report. To access these, use the _Database_ -> _Database reports..._ menu option then click on _Browser Statistics_ on the left-hand menu. From here you can see all entries with URLs applied to them, explicitly allowed and denied URLs, and any entries with custom browser settings.
|
||||
|
||||
.Browser statistics
|
||||
image::browser_statistics.png[]
|
||||
|
||||
=== Advanced Usage
|
||||
You can configure unique browser integration behavior for each entry. This allows you to add multiple URLs to an entry, hide an entry from the browser integration, and more. To access these settings, open an entry for editing then click on _Browser Integration_ option in the left-hand menu *(1)*.
|
||||
|
||||
After opening the settings you can add any number of additional URLs by clicking the _Add_ button *(2)* and typing the URL in the list to the left *(3)*.
|
||||
|
||||
.Entry browser settings
|
||||
image::browser_entry_settings.png[]
|
||||
|
||||
To set options for all entries within a group, edit the group and go to the browser integration section *(1)*. Here you can explicitly disable access to all entries under a group hierarchy to the browser extension. You can set other useful options for groups of entries as well.
|
||||
|
||||
.Group browser settings
|
||||
image::browser_group_settings.png[]
|
||||
|
||||
Database-wide operations are available in the database settings. To access these use the _Database_ -> _Database settings..._ menu option. Click on _Browser Integration_ on the left-hand menu. From here you can disconnect all browsers, convert legacy KeePass-HTTP settings, reset all entry-level settings, and refresh the database root group ID (useful when making copies of your database file).
|
||||
|
||||
.Database browser settings
|
||||
image::browser_database_settings.png[]
|
||||
|
||||
Finally, advanced application-wide settings are available in the Browser Integration tab of the application settings.
|
||||
|
||||
WARNING: We do not recommend changing any of these settings as they may break the browser integration plugin.
|
||||
|
||||
.Advanced browser settings
|
||||
image::browser_advanced_settings.png[]
|
||||
|
||||
=== Advanced Setup
|
||||
|
||||
==== Custom Browser option
|
||||
|
||||
It is possible to enable support for a custom browser (e.g. LibreWolf, WaterFox, Arc, beta and nightly browsers, etc.) using this feature.
|
||||
This feature is only available for Linux and macOS.
|
||||
|
||||
.Custom browser configuration
|
||||
image::browser_custom_browser_configuration.png[]
|
||||
|
||||
The native messaging script file needed for the custom browser depends on the browser type. For Firefox based browsers like Librefox the _Browser type_ must be _Firefox_. For Arc, Opera, etc. the type must be set to _Chromium_.
|
||||
|
||||
_Config location_ must have the exact path for the browser's _native-messaging-hosts_ folder. If you are unsure, refer to our https://github.com/keepassxreboot/keepassxc-browser/wiki/Troubleshooting-guide#1-after-enabling-browser-integration-and-support-for-your-browser[Troubleshooting Guide] for listing of the most common paths, and a few ways for finding a path when it's not known.
|
||||
|
||||
When a Custom Browser has been successfully set, KeePassXC will automatically write the needed native messaging script file to the folder.
|
||||
|
||||
If you wish to support multiple custom browsers, you can copy the native messaging script files manually to the _native-messaging-hosts_ folder from other browsers.
|
||||
|
||||
==== Managed Microsoft Edge on Windows
|
||||
1. Deploy *org.keepassxc.keepassxc_browser_edge.json* to, for example, `C:\ProgramData\KeepassXC` on all managed platforms.
|
||||
+
|
||||
----
|
||||
{
|
||||
"allowed_origins": [
|
||||
"chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"
|
||||
],
|
||||
"description": "KeePassXC integration with native messaging support",
|
||||
"name": "org.keepassxc.keepassxc_browser",
|
||||
"path": "C:\\Program Files\\KeePassXC\\keepassxc-proxy.exe",
|
||||
"type": "stdio"
|
||||
}
|
||||
----
|
||||
|
||||
2. Configure GPO options (registry result):
|
||||
+
|
||||
----
|
||||
Windows Registry Editor Version 5.00
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Edge\NativeMessagingHosts\org.keepassxc.keepassxc_browser]
|
||||
@="C:\ProgramData\KeepassXC\org.keepassxc.keepassxc_browser_edge.json"
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge]
|
||||
"NativeMessagingUserLevelHosts"=dword:00000000
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallAllowlist]
|
||||
"1"="pdffhmdngciaglkoonimfcmckehcpafo"
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\NativeMessagingAllowlist]
|
||||
"1"="org.keepassxc.keepassxc_browser"
|
||||
----
|
||||
// end::advanced[]
|
||||
// end::content[]
|
||||
= KeePassXC – Browser Plugin
|
||||
include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Browser Integration
|
||||
The KeePassXC-Browser extension is installed within your web browser so that you can automatically pull usernames and passwords from KeePassXC and populate them directly into website fields. It is a very useful and secure extension that enhances your productivity while using KeePassXC. With this extension, you do not need to manually copy the data from your KeePassXC database and paste it into the website fields.
|
||||
|
||||
The KeePassXC-Browser extension is available on the following web browsers:
|
||||
|
||||
* Google Chrome, Vivaldi, and Brave
|
||||
* Mozilla Firefox and Tor-Browser
|
||||
* Microsoft Edge
|
||||
* Chromium
|
||||
|
||||
NOTE: On Linux, Flatpak and Snap based browsers are generally not supported. Ubuntu's Firefox Snap is currently the only known exception.
|
||||
|
||||
=== Install the Browser Extension
|
||||
You can download the KeePassXC-Browser extension from your web browser. To download the KeePassXC-Browser extension, perform the following steps:
|
||||
|
||||
1. Click the link corresponding to your browser:
|
||||
* https://chromewebstore.google.com/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk[Chrome, Chromium, Vivaldi, and Brave]
|
||||
* https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser[Mozilla Firefox and Tor-Browser]
|
||||
* https://microsoftedge.microsoft.com/addons/detail/keepassxcbrowser/pdffhmdngciaglkoonimfcmckehcpafo[Microsoft Edge]
|
||||
|
||||
2. Click the button to install/add the extension to the browser. Accept any confirmation dialogs.
|
||||
|
||||
TIP: For the most up-to-date troubleshooting advice on all platforms, please read our https://github.com/keepassxreboot/keepassxc-browser/wiki/Troubleshooting-guide[Troubleshooting Guide].
|
||||
|
||||
// tag::advanced[]
|
||||
NOTE: When Microsoft Edge is installed as a managed application, system administrators are required to deploy a custom native messaging configuration. Instructions for this are found in the advanced section below.
|
||||
// end::advanced[]
|
||||
|
||||
=== Configure KeePassXC-Browser
|
||||
To start using KeePassXC-Browser, you must configure it so that it can communicate with the KeePassXC application on your desktop.
|
||||
|
||||
To configure KeePassXC-Browser, perform the following steps:
|
||||
|
||||
1. Open the KeePassXC application on your desktop and navigate to Tools > Settings.
|
||||
|
||||
2. Click the Browser Integration option on the left-hand side *(1)*. The following screen appears:
|
||||
+
|
||||
.Browser Settings
|
||||
image::browser_settings.png[]
|
||||
|
||||
3. Click the _Enable browser integration_ checkbox *(2)*. Then select the browsers for which you have downloaded the KeePassXC-Browser extension *(3)* and click *OK*.
|
||||
|
||||
4. Ensure your database is unlocked, then open (or restart) your browser.
|
||||
|
||||
5. Click the KeePassXC-Browser extension icon *(A)* in your browser (see figure below). A pop-up window appears.
|
||||
+
|
||||
.Connect Extension to KeePassXC
|
||||
image::browser_extension_connect.png[,80%]
|
||||
|
||||
6. Click the _Connect_ button *(B)* in the pop-up window to complete integrating the KeePassXC-Browser extension with your KeePassXC desktop application.
|
||||
|
||||
7. You are now prompted to enter a unique name to identify the connection between this browser and your database. Enter a unique name in the field (e.g., firefox-laptop) and click the _Save and allow access_ button.
|
||||
+
|
||||
.Extension Association Dialog
|
||||
image::browser_extension_association.png[,80%]
|
||||
|
||||
WARNING: If you reuse a connection name in a database, the previous browser connection will be overwritten and prevent access.
|
||||
|
||||
=== Using the Browser Extension
|
||||
The KeePassXC-Browser extension lets you automatically populate the entries from your KeePassXC database into the fields on websites you visit. To do so, perform the following steps:
|
||||
|
||||
1. Open your KeePassXC desktop application and unlock your database.
|
||||
|
||||
2. Open your web browser. The KeePassXC-Browser extension icon in your browser window will change based on its connection state. The figure below shows the different states.
|
||||
+
|
||||
*(A)* KeePassXC is not running or is disconnected. +
|
||||
*(B)* KeePassXC is running, but KeePassXC Browser Extension is not connected to the current database. +
|
||||
*\(C)* Connected to KeePassXC, but database is locked. +
|
||||
*(D)* Connected to KeePassXC and ready to use. If the icon is shown with a number, it indicates the number of credentials found for the current site.
|
||||
+
|
||||
.Extension Icon States
|
||||
image::browser_extension_icons.png[,70%]
|
||||
|
||||
3. If the KeePassXC desktop application is not connected with the KeePassXC-Browser extension, click the extension icon in your web browser and click _Reload_ from the pop-up window as shown in the following screen.
|
||||
+
|
||||
.Reload Extension Connection
|
||||
image::browser_extension_reload.png[,80%]
|
||||
|
||||
4. Open the URL for which you want to use with your database. If you have previously created an entry in your database then the KeePassXC-Browser Confirm Access dialog may appear:
|
||||
+
|
||||
.Confirm Access Dialog
|
||||
image::browser_confirm_access_dialog.png[,80%]
|
||||
|
||||
5. Ensure the credentials you want to use are checked, then click *(A)* Remember _(optional)_, then click _Allow Selected_ *(B)*.
|
||||
|
||||
6. In your website, the KeePassXC icon will appear in the username field of the login form *(A)*. Click the icon to populate the field with your stored credentials. If you have more than one credential for this website, a dropdown will appear to choose the one to use.
|
||||
+
|
||||
.Fill Credentials
|
||||
image::browser_fill_credentials.png[,80%]
|
||||
|
||||
=== Generate Passwords
|
||||
The KeePassXC-Browser Extension also lets you generate passwords directly in your browser.
|
||||
This feature can be used for websites with existing credentials as well as for new websites.
|
||||
You can then choose to update/add the credentials to your KeePassXC database directly from the Browser.
|
||||
|
||||
1. Ensure your database is unlocked and configured to use the Browser extension as shown above.
|
||||
|
||||
2. Right click on a password field and from the KeePassXC sub-menu choose _Show Password Generator_. The standard KeePassXC password generator will appear.
|
||||
|
||||
3. Configure the password generation options and click _Apply Password_ when done. The generated password will be filled into the previously selected field.
|
||||
|
||||
4. When you have successfully submitted the password on the website, a popup will appear asking you to either update an existing entry or add a new one.
|
||||
|
||||
// tag::advanced[]
|
||||
=== Browser statistics
|
||||
You can see a cross-section of all browser-related settings applied to entries within a database through the Browser Statistics report. To access these, use the _Database_ -> _Database reports..._ menu option then click on _Browser Statistics_ on the left-hand menu. From here you can see all entries with URLs applied to them, explicitly allowed and denied URLs, and any entries with custom browser settings.
|
||||
|
||||
.Browser statistics
|
||||
image::browser_statistics.png[]
|
||||
|
||||
=== Advanced Usage
|
||||
You can configure unique browser integration behavior for each entry. This allows you to add multiple URLs to an entry, hide an entry from the browser integration, and more. To access these settings, open an entry for editing then click on _Browser Integration_ option in the left-hand menu *(1)*.
|
||||
|
||||
After opening the settings you can add any number of additional URLs by clicking the _Add_ button *(2)* and typing the URL in the list to the left *(3)*.
|
||||
|
||||
Additional URLs also supports wildcards (with KeePassXC 2.7.10 and later). You can use URLs like:
|
||||
----
|
||||
https://*.example.com
|
||||
https://example.com/*/path
|
||||
https://sub.*.example.com/path/*
|
||||
----
|
||||
|
||||
.Entry browser settings
|
||||
image::browser_entry_settings.png[]
|
||||
|
||||
To set options for all entries within a group, edit the group and go to the browser integration section *(1)*. Here you can explicitly disable access to all entries under a group hierarchy to the browser extension. You can set other useful options for groups of entries as well.
|
||||
|
||||
.Group browser settings
|
||||
image::browser_group_settings.png[]
|
||||
|
||||
Database-wide operations are available in the database settings. To access these use the _Database_ -> _Database settings..._ menu option. Click on _Browser Integration_ on the left-hand menu. From here you can disconnect all browsers, convert legacy KeePass-HTTP settings, reset all entry-level settings, and refresh the database root group ID (useful when making copies of your database file).
|
||||
|
||||
.Database browser settings
|
||||
image::browser_database_settings.png[]
|
||||
|
||||
Finally, advanced application-wide settings are available in the Browser Integration tab of the application settings.
|
||||
|
||||
WARNING: We do not recommend changing any of these settings as they may break the browser integration plugin.
|
||||
|
||||
.Advanced browser settings
|
||||
image::browser_advanced_settings.png[]
|
||||
|
||||
=== Advanced Setup
|
||||
==== Custom Browser option
|
||||
It is possible to enable support for a custom browser (e.g. LibreWolf, WaterFox, Arc, beta and nightly browsers, etc.) using this feature.
|
||||
This feature is only available for Linux and macOS.
|
||||
|
||||
.Custom browser configuration
|
||||
image::browser_custom_browser_configuration.png[]
|
||||
|
||||
The native messaging script file needed for the custom browser depends on the browser type. For Firefox based browsers like Librefox the _Browser type_ must be _Firefox_. For Arc, Opera, etc. the type must be set to _Chromium_.
|
||||
|
||||
_Config location_ must have the exact path for the browser's _native-messaging-hosts_ folder. If you are unsure, refer to our https://github.com/keepassxreboot/keepassxc-browser/wiki/Troubleshooting-guide#1-after-enabling-browser-integration-and-support-for-your-browser[Troubleshooting Guide] for listing of the most common paths, and a few ways for finding a path when it's not known.
|
||||
|
||||
When a Custom Browser has been successfully set, KeePassXC will automatically write the needed native messaging script file to the folder.
|
||||
|
||||
If you wish to support multiple custom browsers, you can copy the native messaging script files manually to the _native-messaging-hosts_ folder from other browsers.
|
||||
|
||||
==== Managed Microsoft Edge on Windows
|
||||
1. Deploy *org.keepassxc.keepassxc_browser_edge.json* to, for example, `C:\ProgramData\KeePassXC\` on all managed platforms.
|
||||
+
|
||||
----
|
||||
{
|
||||
"allowed_origins": [
|
||||
"chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"
|
||||
],
|
||||
"description": "KeePassXC integration with native messaging support",
|
||||
"name": "org.keepassxc.keepassxc_browser",
|
||||
"path": "C:\\Program Files\\KeePassXC\\keepassxc-proxy.exe",
|
||||
"type": "stdio"
|
||||
}
|
||||
----
|
||||
|
||||
2. Configure GPO options (see https://learn.microsoft.com/en-us/deployedge/microsoft-edge-policies#native-messaging[Microsoft Edge Native Messaging Policies] for more information.):
|
||||
+
|
||||
----
|
||||
Windows Registry Editor Version 5.00
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Edge\NativeMessagingHosts\org.keepassxc.keepassxc_browser]
|
||||
@="C:\ProgramData\KeepassXC\org.keepassxc.keepassxc_browser_edge.json"
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge]
|
||||
"NativeMessagingUserLevelHosts"=dword:00000000
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallAllowlist]
|
||||
"1"="pdffhmdngciaglkoonimfcmckehcpafo"
|
||||
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\NativeMessagingAllowlist]
|
||||
"1"="org.keepassxc.keepassxc_browser"
|
||||
----
|
||||
|
||||
==== Managed Microsoft Edge on macOS
|
||||
1. Deploy *org.keepassxc.keepassxc_browser_edge.json* to `/Library/Microsoft/Edge/NativeMessagingHosts`.
|
||||
|
||||
2. You may need to configure Edge to allowlist the extension and native messaging host. See https://learn.microsoft.com/en-us/deployedge/microsoft-edge-policies#native-messaging[Microsoft Edge Native Messaging Policies] for more information.
|
||||
// end::advanced[]
|
||||
// end::content[]
|
||||
@@ -36,6 +36,13 @@ NOTE: Keep this password for your database safe. Either memorize it or note it d
|
||||
|
||||
5. Click Done. You will be prompted to select a location to save your database file. The database file is saved on to your computer with the default `.kdbx` extension. You can store your database wherever you wish, it is fully encrypted at all times preventing unauthorized access.
|
||||
|
||||
=== Storing Your Database
|
||||
The database file that you create might contain highly sensitive data and must be stored in a very secure way. You must make sure that the database is always protected with a strong and long password. The database file that is protected with a strong and long password is secure and encrypted while stored on your computer or cloud storage service.
|
||||
|
||||
Make sure that you or someone else does not accidentally delete the database file. Deletion of the database file will result in the total loss of all your information (including all your passwords!) and a lot of inconvenience to manually retrieve your logins for various web applications. Do not share the credentials to access your database file with anyone unless you absolutely trust them (spouse, child, etc.).
|
||||
|
||||
TIP: You can safely store your database file in the cloud (OneDrive, Dropbox, Google Drive, Nextcloud, Syncthing, etc.). The database file is always fully encrypted; unencrypted data is never written to disk and is never accessible to your cloud storage provider. We recommend using a storage service that keeps automatic backups (version history) of your database file in the event of corruption or accidental deletion.
|
||||
|
||||
=== Opening an Existing Database
|
||||
To open an existing database, perform the following steps:
|
||||
|
||||
@@ -51,9 +58,11 @@ image::unlock_database.png[]
|
||||
|
||||
3. Enter the password for your database.
|
||||
|
||||
4. _(Optional)_ Browse for the Key File if you have chosen it as an additional authentication factor while creating the database. Refer to the KeePassXC User Guide for more information on setting a Key File as an additional authentication factor.
|
||||
4. _(Optional)_ Click *I have a key file (A)* if you have one as an additional authentication factor for your database.
|
||||
|
||||
5. Click *OK*. The database opens and the following screen is displayed:
|
||||
5. _(Optional)_ Plug in your configured YubiKey or OnlyKey to use it as an additional authentication factor. If you don't see it listed, press the refresh button *(B)*.
|
||||
|
||||
6. Click *OK*. The database opens and the following screen is displayed:
|
||||
+
|
||||
.Unlocked database
|
||||
image::database_view.png[]
|
||||
@@ -72,24 +81,13 @@ When your database is locked, you will see the following unlock dialog. Simply p
|
||||
image::quick_unlock.png[]
|
||||
|
||||
// tag::advanced[]
|
||||
=== Expired Entries
|
||||
By default, KeePassXC will show entries that are expired or will be expiring within 3 days after unlocking the database. This feature allows you to change your passwords before they expire and be aware of passwords that are no longer valid. You can disable or change this feature in the Application Settings.
|
||||
NOTE: By default, KeePassXC will show entries that are expired or will be expiring within 3 days after unlocking the database. This feature allows you to change your passwords before they expire and be aware of passwords that are no longer valid. You can disable or change this feature in the Application Settings.
|
||||
|
||||
=== Advanced Save Options
|
||||
There are three ways that KeePassXC can handle database files. This behavior is set in the Application Settings under _File Operations_.
|
||||
|
||||
1. _(Default)_ *Safe saves* create a temporary database file alongside the existing one and atomically move it into place when all writing is complete. This prevents database corruption in the case of application crashes, loss of power, or other interruptions.
|
||||
|
||||
2. *Temporary file saves* create a database in the temporary files folder. This database is then moved into place overtop of the existing file. Although rare, interruptions in this move process could leave your database in an unknown state. This option is useful for overcoming poorly behaved cloud sync tools.
|
||||
|
||||
3. *Direct-write saves* write directly to the existing database file. This is an unsafe operation since any interruption can leave your entire database inaccessible. We only recommend using this option when interfacing with Linux GVFS services (e.g. Google Cloud on Gnome) and other types of storage services that host a virtual drive system.
|
||||
|
||||
In addition to these save options, KeePassXC can create a backup of your existing database file just prior to saving. This backup will be saved at the path specified in the *Backup destination* field. This path can be absolute or relative. The latter will be resolved according to the databases path. It is possible to specify a custom naming scheme with placeholders. See xref:UserGuide.adoc#_backup_path_placeholders[Backup Path Placeholders] for available placeholders and examples.
|
||||
|
||||
image::save_options.png[]
|
||||
// end::advanced[]
|
||||
=== Entry Handling
|
||||
Entries in KeePassXC are the fundamental units where all your sensitive information is stored. Each entry can contain various fields such as usernames, passwords, URLs, attachments, and notes. You can create, edit, clone, and delete entries as needed. Additionally, KeePassXC supports advanced features like TOTP for two-factor authentication, custom attributes, and entry history to track changes over time. Proper management of entries ensures that your data is organized, secure, and easily accessible when needed.
|
||||
|
||||
=== Adding an Entry
|
||||
==== Adding an Entry
|
||||
All the details such as usernames, passwords, URLs, attachments, notes, and so on are stored in database entries. You can create as many entries as you want in the database.
|
||||
|
||||
To add an entry, perform the following step:
|
||||
@@ -112,7 +110,7 @@ image::edit_entry.png[]
|
||||
|
||||
5. Click *OK* to add the entry to your database.
|
||||
|
||||
=== Editing an Entry
|
||||
==== Editing an Entry
|
||||
To edit the details in an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to edit.
|
||||
@@ -123,7 +121,7 @@ To edit the details in an entry, perform the following steps:
|
||||
|
||||
4. Click *OK*.
|
||||
|
||||
=== Adding TOTP to an Entry
|
||||
==== Adding TOTP to an Entry
|
||||
Timed One-Time Passwords (TOTP) are a popular choice for two-factor authentication methods. These codes are typically six digits long and change every 30 seconds. They are derived from a shared secret value and the current time. Once set up, KeePassXC can calculate TOTP codes like any authenticator app, such as Google Authenticator. The codes can be used with copy/paste, browser extension, and Auto-Type.
|
||||
|
||||
TIP: Your computer time must be synchronized with an internet time source to generate valid TOTP codes, https://www.nist.gov/pml/time-and-frequency-division/time-distribution/internet-time-service-its[read more here].
|
||||
@@ -145,7 +143,17 @@ After an entry is configured with TOTP, you will see a clock icon in that entry'
|
||||
.TOTP Usage
|
||||
image::totp_usage_examples.png[]
|
||||
|
||||
=== Deleting an Entry
|
||||
==== Entry Icons
|
||||
You can select an icon to be displayed with each entry for easy identification. KeePassXC comes with a set of default icons that you can use or you can use your own custom icons. If you defined a URL with an entry, you can also download the favorite icon for that particular website.
|
||||
|
||||
NOTE: To delete a custom icon, go to <<Database Maintenance>> where you can purge unused icons and delete one or more icons at a time.
|
||||
|
||||
.Entry icon selection
|
||||
image::edit_entry_icons.png[]
|
||||
|
||||
TIP: Each KeePass application has different default icons. If you use a mobile app or KeePass2, be aware that the default icons may not be exactly correspond to the KeePassXC icons.
|
||||
|
||||
==== Deleting an Entry
|
||||
To delete an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
@@ -157,7 +165,7 @@ NOTE: You can disable the recycle bin within the Database Settings. If the recyc
|
||||
3. To permanently delete the entry, navigate to the Recycle Bin, select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
|
||||
// tag::advanced[]
|
||||
=== Clone an Entry
|
||||
==== Clone an Entry
|
||||
Creating a clone of an entry provides you a ready-to-use template for creating new entries with similar details of a master entry.
|
||||
|
||||
To create a clone of an existing entry, perform the following steps:
|
||||
@@ -180,12 +188,73 @@ image::clone_entry_dialog.png[,50%]
|
||||
.References in a cloned entry
|
||||
image::clone_entry_references.png[]
|
||||
|
||||
4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference Syntax]
|
||||
4. You can create your own references using the <<Entry Cross-Reference, Entry Reference Syntax>>
|
||||
|
||||
== Searching the Database
|
||||
KeePassXC provides an enhanced and granular search features the enables you to search for specific entries in the databases using the different modifiers, wild card characters, and logical operators.
|
||||
==== Entry URL Handling
|
||||
KeePassXC can handle URLs in various ways. Standard URLs will be opened in your default browser. URLs that start with schemas handled by your Operating System will launch the associated application, for example `ftp://` or `ssh://`. You can also use the following URL schemas to perform specific actions:
|
||||
|
||||
=== Modifiers and Fields
|
||||
|===
|
||||
|Schema | Example | Description
|
||||
|
||||
|cmd://
|
||||
|`cmd://ssh {USERNAME}@example.com -p 2222`
|
||||
|Launches the specified command line executable with the specified arguments. The executable must be present on your PATH or an absolute path must be specified.
|
||||
|
||||
|kdbx://
|
||||
|`kdbx://~/dbs/passwords.kdbx`
|
||||
|Opens the specified database file. Set the entry's username to the keyfile path (if required) and password to the database password. The database will open in a new tab.
|
||||
|
||||
|===
|
||||
|
||||
=== Advanced Entry Handling
|
||||
KeePassXC offers several advanced options for managing your database entries. Additional Attributes allow you to store extra information required by some applications and websites. Attachments enable you to attach files to entries, stored as encrypted binaries, which can be previewed directly in the application (text and images). Icons can be selected or downloaded for easy identification of entries. The Properties section lets you view basic properties such as creation, modification, and last accessed times, and retrieve an entry's UUID for references. KeePassXC also maintains a history of changes to entries, allowing you to view, restore, or delete previous versions of an entry.
|
||||
|
||||
==== Additional Attributes
|
||||
A lot of applications and web sites now require providing additional information when you create accounts. The additional information is used to block hackers if any suspicious activity is detected. In addition, the additional information you provide can be used to reset passwords if you forget them. You can also store arbitrary information here that can be copied to the clipboard or Auto-Typed using the `{S:<ATTR_NAME>}` action code.
|
||||
|
||||
To protect an attribute from being displayed by default, activate the _Protect_ checkbox *(A)*. To show the contents of the attribute while keeping it protected, press the _Reveal_ button *(B)*.
|
||||
|
||||
.Additional attributes example
|
||||
image::edit_entry_attributes.png[]
|
||||
|
||||
==== Attachments
|
||||
You can attach files to any entry in your database by pressing the _Add_ button *(A)*. These files are added to the database and stored as encrypted binaries. You can open, save, or delete attachments from this interface *(B)*.
|
||||
|
||||
NOTE: When you try to open the attached file, KeePassXC extracts the attachment to a temporary file and opens it using the default application associated with the file type. After finishing viewing or editing the file, you can choose between importing or discarding the changes that you made to the temporary file. KeePassXC securely deletes the temporary file by overwriting it.
|
||||
|
||||
.Attachments interface
|
||||
image::edit_entry_attachments.png[]
|
||||
|
||||
==== Foreground and Background Color
|
||||
You can change the foreground *(A)* and/or background *(B)* color that this entry will use in the entry lists. Click the corresponding box to open the color picker dialog.
|
||||
|
||||
.Color picker dialog
|
||||
image::edit_entry_colors.png[]
|
||||
|
||||
==== Properties
|
||||
KeePassXC lets you view the basic properties such as date and time of creation, modification, and when last accessed. This is also where you can retrieve an entry's UUID for use in references.
|
||||
|
||||
.Entry properties view
|
||||
image::edit_entry_properties.png[]
|
||||
|
||||
==== History
|
||||
KeePassXC maintains a history of changes you make to your entries. Each time you change an entry, KeePassXC automatically creates a backup copy of the current, non-modified entry before saving the new values. You can view the changes you made previously, restore, and delete the history of changes you made. The age of the history item, the changes that were made, and the entry's size are shown in the table view.
|
||||
|
||||
* Show: Display this history item for review, a read-only copy of the entry will be shown.
|
||||
* Restore: Reinstate the selected history item as the active entry details.
|
||||
* Delete: Delete the selected history item.
|
||||
* Delete All: Delete the entire history for this entry.
|
||||
|
||||
.Entry history view
|
||||
image::edit_entry_history.png[]
|
||||
|
||||
NOTE: Restoring an old history item will store the current entry settings as a new history item.
|
||||
|
||||
// end::advanced[]
|
||||
=== Search
|
||||
KeePassXC provides a robust search that enables you to find specific entries in the databases using different modifiers, wild card characters, and logical operators. By default, search considers the following fields when matching your query: Title, Username, URL, Tags, and Notes. To include other fields and/or narrow your search to specific fields, you can use the search syntax described below.
|
||||
|
||||
==== Modifiers and Fields
|
||||
[grid=rows, frame=none, width=70%]
|
||||
|===
|
||||
|Modifier |Description
|
||||
@@ -201,14 +270,15 @@ The following fields can be searched along with their abbreviated name in parent
|
||||
* Title (t)
|
||||
* Username (u)
|
||||
* Password (p, pw)
|
||||
* URL
|
||||
* URL (url)
|
||||
* Notes (n)
|
||||
* Attribute names and values (attr)
|
||||
* Attachment (attach)
|
||||
* Group (g)
|
||||
* Tags (tag)
|
||||
* Entry State (is:expired, is:weak)
|
||||
|
||||
=== Wild Card Characters and Logical Operators
|
||||
==== Wild Card Characters and Logical Operators
|
||||
[grid=rows, frame=none, width=70%]
|
||||
|===
|
||||
|Wild Card Character |Description
|
||||
@@ -218,7 +288,7 @@ The following fields can be searched along with their abbreviated name in parent
|
||||
|\| |Logical OR
|
||||
|===
|
||||
|
||||
=== Sample Search Queries
|
||||
==== Sample Search Queries
|
||||
The following tables lists a few samples search queries for your reference:
|
||||
|
||||
|===
|
||||
@@ -236,63 +306,39 @@ The following tables lists a few samples search queries for your reference:
|
||||
|`+attr:mystring123`
|
||||
|Searches all additional attributes for any name OR value equal to mystring123.
|
||||
|
||||
|`+tag:personal`
|
||||
| Search exactly for the 'personal' tag and do not include tags such as 'my personal'.
|
||||
|
||||
|`is:expired is:weak`
|
||||
|Searches for all expired entries with weak passwords.
|
||||
|===
|
||||
|
||||
== Advanced Entry Options
|
||||
=== Additional Attributes
|
||||
A lot of applications and web sites now require providing additional information when you create accounts. The additional information is used to block hackers if any suspicious activity is detected. In addition, the additional information you provide can be used to reset passwords if you forget them. You can also store arbitrary information here that can be copied to the clipboard or Auto-Typed using the `{S:<ATTR_NAME>}` action code.
|
||||
// tag::advanced[]
|
||||
=== Merging Databases
|
||||
KeePassXC allows you to merge entries from one database into another through the _Database_ -> _Merge From Database_ menu item. When merging, entries from the specified database will be imported into your currently open database. The merge process compares entries based on their unique identifiers (UUIDs) and modified timestamp. When an entry UUID matches, no matter which group it is in, the most recently modified version will be made the current and the previous version will be placed into the entry's history. Any new entries and/or groups will be added to the open database. This feature is useful for consolidating multiple databases or synchronizing databases from conflict files in a cloud storage system.
|
||||
|
||||
To protect an attribute from being displayed by default, activate the _Protect_ checkbox *(A)*. To show the contents of the attribute while keeping it protected, press the _Reveal_ button *(B)*.
|
||||
NOTE: When you delete entries, a record of that deletion (the entry UUID) is stored to prevent that entry from reappearing from a merge operation. An existing entry that has the same UUID as a deleted item will be removed from the database without prompt.
|
||||
|
||||
.Additional attributes example
|
||||
image::edit_entry_attributes.png[]
|
||||
=== Advanced Save Options
|
||||
There are three ways that KeePassXC can handle database files. This behavior is set in the Application Settings under _File Operations_.
|
||||
|
||||
=== Attachments
|
||||
You can attach files to any entry in your database by pressing the _Add_ button *(A)*. These files are added to the database and stored as encrypted binaries. You can open, save, or delete attachments from this interface *(B)*.
|
||||
1. _(Default)_ *Safe saves* create a temporary database file alongside the existing one and atomically move it into place when all writing is complete. This prevents database corruption in the case of application crashes, loss of power, or other interruptions.
|
||||
|
||||
NOTE: When you try to open the attached file, KeePassXC extracts the attachment to a temporary file and opens it using the default application associated with the file type. After finishing viewing or editing the file, you can choose between importing or discarding the changes that you made to the temporary file. KeePassXC securely deletes the temporary file by overwriting it.
|
||||
2. *Temporary file saves* create a database in the temporary files folder. This database is then moved into place overtop of the existing file. Although rare, interruptions in this move process could leave your database in an unknown state. This option is useful for overcoming poorly behaved cloud sync tools.
|
||||
|
||||
.Attachments interface
|
||||
image::edit_entry_attachments.png[]
|
||||
3. *Direct-write saves* write directly to the existing database file. This is an unsafe operation since any interruption can leave your entire database inaccessible. We only recommend using this option when interfacing with Linux GVFS services (e.g. Google Cloud on Gnome) and other types of storage services that host a virtual drive system.
|
||||
|
||||
=== Foreground and Background Color
|
||||
You can change the foreground *(A)* and/or background *(B)* color that this entry will use in the entry lists. Click the corresponding box to open the color picker dialog.
|
||||
=== Database Backup Options
|
||||
In addition to these save options, KeePassXC can create a backup of your existing database file just prior to saving. This backup will be saved at the path specified in the *Backup destination* field. This path can be absolute or relative. The latter will be resolved according to the databases path. It is possible to specify a custom naming scheme with placeholders. See <<Backup Path Placeholders, Backup Path Placeholders>> for available placeholders and examples.
|
||||
|
||||
.Color picker dialog
|
||||
image::edit_entry_colors.png[]
|
||||
image::save_options.png[]
|
||||
|
||||
=== Icons
|
||||
You can select an icon to be displayed with each entry for easy identification. KeePassXC comes with a set of default icons that you can use or you can use your own custom icons. If you defined a URL with an entry, you can also download the favorite icon for that particular website.
|
||||
Alternatively, backups can be created on-demand using the _Database_ -> _Save Database Backup..._ menu feature.
|
||||
|
||||
NOTE: To delete a custom icon, go to xref:UserGuide.adoc#_database_maintenance[Database Maintenance] where you can purge unused icons and delete one or more icons at a time.
|
||||
.Saving a database backup
|
||||
image::save_database_backup.png[,40%]
|
||||
|
||||
.Entry icon selection
|
||||
image::edit_entry_icons.png[]
|
||||
|
||||
TIP: Each KeePass application has different default icons. If you use a mobile app or KeePass2, be aware that the default icons may not be exactly correspond to the KeePassXC icons.
|
||||
|
||||
=== Properties
|
||||
KeePassXC lets you view the basic properties such as date and time of creation, modification, and when last accessed. This is also where you can retrieve an entry's UUID for use in references.
|
||||
|
||||
.Entry properties view
|
||||
image::edit_entry_properties.png[]
|
||||
|
||||
=== History
|
||||
KeePassXC maintains a history of changes you make to your entries. Each time you change an entry, KeePassXC automatically creates a backup copy of the current, non-modified entry before saving the new values. You can view the changes you made previously, restore, and delete the history of changes you made. The age of the history item, the changes that were made, and the entry's size are shown in the table view.
|
||||
|
||||
* Show: Display this history item for review, a read-only copy of the entry will be shown.
|
||||
* Restore: Reinstate the selected history item as the active entry details.
|
||||
* Delete: Delete the selected history item.
|
||||
* Delete All: Delete the entire history for this entry.
|
||||
|
||||
.Entry history view
|
||||
image::edit_entry_history.png[]
|
||||
|
||||
NOTE: Restoring an old history item will store the current entry settings as a new history item.
|
||||
|
||||
== Automatic Database Opening
|
||||
=== Automatic Database Opening
|
||||
You can setup one or more databases to open automatically when you unlock a single database. This is done by *(1)* defining a special group named `AutoOpen` with *(2)* entries that contain the file path and credentials for each database that should be opened. There is no limit to the number of databases that can be opened.
|
||||
|
||||
TIP: Case matters with auto open, the group name must be exactly `AutoOpen` and it must be a child of the root group.
|
||||
@@ -329,6 +375,7 @@ image::database_settings.png[]
|
||||
* *Database name:* This is the default identifier for your database and is shown in the tab bar and title bar (when active). You can change this name as desired.
|
||||
* *Database description:* Provide some meaningful description for your database.
|
||||
* *Default username:* Provide a default username for all new entries that you create in this database.
|
||||
* *Public Databse Metadata:* Here you can set a public (unencrypted) name, icon, and color for your database. This is used on the database unlock screen to help distinguish multiple databases from each other.
|
||||
* *Max history items:* This is the maximum number of history items that are stored for each entry. When you set this to 0, no history will be saved. Set this value to a low value to prevent the database from getting too large (we recommend no more than 10).
|
||||
* *Max. history size:* When the history of an entry gets above this size, it is truncated. For example, this happens when entries have large attachments. Set this value small to prevent the database from getting too large (we recommend 6 MiB).
|
||||
* *Use recycle bin:* Select this check-box if you want deleted entries to move to the recycle bin instead of being permanently removed. The recycle bin will be created if it does not already exist after your first deletion. To delete entries permanently, you must empty the recycle bin manually.
|
||||
@@ -365,31 +412,11 @@ The following key derivation functions are supported:
|
||||
|
||||
* Argon2 (KDBX 4 – recommended): KDBX 4, the Argon2 key derivation function can be used for transforming the composite master key (as protection against dictionary attacks). The main advantage of Argon2 over AES-KDF is that it provides a better resistance against GPU/ASIC attacks (due to being a memory-hard function). The number of iterations scales linearly with the required time. By increasing the memory parameter, GPU/ASIC attacks become harder and the required time increases. The parallelism parameter can be used to specify how many threads should be used. We recommend using Argon2id to prevent against timing-based attacks. Argon2d offers maximum compatibility with other KeePass-based apps, the default settings provide sufficient protection against any known attacks.
|
||||
|
||||
== Database Maintenance
|
||||
=== Database Maintenance
|
||||
KeePassXC offers some maintenance features that can be applied to clean up your database. Navigate to _Database_ -> _Database settings_ then click on _Maintenance_ on the left hand panel. The following screen appears. On this screen you can delete multiple icons at once and purge any unused icons in your database.
|
||||
|
||||
image::database_maintenance.png[]
|
||||
|
||||
=== Creating a YubiKey backup
|
||||
It is advisable to have a backup replica YubiKey In case your main YubiKey gets damaged, lost, or stolen. The same HMAC key will need to be written to both keys. To do this you can either use the YubiKey Personalization Tool GUI or the ykpersonalize CLI tool. The steps for the CLI tool are shown:
|
||||
|
||||
1. Create a 20 byte HMAC key:
|
||||
+
|
||||
```
|
||||
dd status=none if=/dev/random bs=20 count=1 | xxd -p -c 40
|
||||
```
|
||||
|
||||
2. Write the HMAC key to slot 2 _(Set through the first switch. Out of the box the YubiKey OTP resides in slot 1)_:
|
||||
+
|
||||
```
|
||||
ykpersonalize -2 -a -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible -oallow-update
|
||||
```
|
||||
|
||||
You will be asked to enter the HMAC key you created earlier, copy/paste they key output in the first step. Repeat step 2 for your second YubiKey using the same HMAC key from before. We recommend storing your HMAC key in a safe place (e.g., printed on paper) in case you need to recreate another key.
|
||||
|
||||
== Command Line Tool
|
||||
KeePassXC comes with the command line tool *keepassxc-cli* to access, view, and manipulate your database directly from a terminal window. The tool is documented through a separate man page, which can be shown using `man keepassxc-cli`, or through the on-demand help using `keepassxc-cli [command] -h`. An online version of the man page is https://github.com/keepassxreboot/keepassxc/blob/master/docs/man/keepassxc-cli.1.adoc[available on GitHub].
|
||||
|
||||
== Remote database support
|
||||
KeePassXC provides support for syncing database files that reside in a remote location. If you can download/upload the database file via a commandline tool (e.g. rsync, ssh, scp etc.) KeePassXC offers easy to use functionality to sync the remote database.
|
||||
|
||||
@@ -408,17 +435,4 @@ Select the remote sync command from the _Database > Remote Sync…_ menu to star
|
||||
WARNING: In case the remote database is changed by another user/process after the downloading command finishes and before uploading again, those changes will be overwritten. Syncing is not an atomic operation.
|
||||
|
||||
// end::advanced[]
|
||||
|
||||
== Storing a Database File
|
||||
The database file that you create might contain highly sensitive data and must be stored in a very secure way. You must make sure that the database is always protected with a strong and long password. The database file that is protected with a strong and long password is secure and encrypted while stored on your computer or cloud storage service.
|
||||
|
||||
Make sure that you or someone else does not accidentally delete the database file. Deletion of the database file will result in the total loss of all your information (including all your passwords!) and a lot of inconvenience to manually retrieve your logins for various web applications. Do not share the credentials to access your database file with anyone unless you absolutely trust them (spouse, child, etc.).
|
||||
|
||||
TIP: You can safely store your database file in the cloud (OneDrive, Dropbox, Google Drive, Nextcloud, Syncthing, etc.). The database file is always fully encrypted; unencrypted data is never written to disk and is never accessible to your cloud storage provider. We recommend using a storage service that keeps automatic backups (version history) of your database file in the event of corruption or accidental deletion.
|
||||
|
||||
== Backing up a Database File
|
||||
It is a good practice to create copies of your database file and store the copies of your database on a different computer, smart phone, or cloud storage space such a Google Drive or Microsoft OneDrive. Backups can be created automatically by selecting the _Backup database file before saving_ option in the application settings. Additionally, you can create a backup on-demand using the _Database_ -> _Save Database Backup..._ menu feature.
|
||||
|
||||
.Saving a database backup
|
||||
image::save_database_backup.png[,40%]
|
||||
// end::content[]
|
||||
|
||||
@@ -3,13 +3,14 @@ include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Importing External Databases
|
||||
== Importing Databases
|
||||
KeePassXC allows you to import external databases from the following options:
|
||||
|
||||
* Comma Separated Values (.csv)
|
||||
* 1Password Export (.1pux)
|
||||
* 1Password Vault (.opvault)
|
||||
* Bitwarden (.json)
|
||||
* Proton Pass (.json)
|
||||
* KeePass 1 Database (.kdb)
|
||||
* Remote database (.kdbx)
|
||||
|
||||
@@ -32,14 +33,17 @@ image::csv_import.png[]
|
||||
|
||||
3. Click `Done` to complete the import. If you chose to create a new database, the New Database dialog will appear. Otherwise your entries will be nested under the group you chose for the existing database.
|
||||
|
||||
=== Importing 1Password Export
|
||||
=== Importing from Other Applications
|
||||
KeePassXC allows you to import databases from various applications including 1Password (1PUX and OPVault), Bitwarden, and Proton Pass. Each import option involves selecting the file, providing necessary credentials (if required), and choosing to import into a new or existing database. Note that CSV, 1Password Export, Bitwarden, and Proton Pass files are unencrypted and should be securely deleted after import.
|
||||
|
||||
==== 1Password Export
|
||||
WARNING: A 1Password Export file is unencrypted and you should securely delete this file after successfully importing it into KeePassXC.
|
||||
|
||||
1. Open the Import Wizard as shown above. Select the 1Password Export option.
|
||||
|
||||
2. Click `Continue` to unlock and preview the import. Click `Done` to complete the import.
|
||||
|
||||
=== Importing 1Password OPVault
|
||||
==== 1Password OPVault
|
||||
NOTE: You must have 1Password version 7 or 8 to export your data to an OPVault. If you are using a newer version of 1Password, you should use the 1Password Export (1PUX) format instead.
|
||||
|
||||
Save your 1Password Vault locally to create an OPVault directory. Please see 1Password instructions on how to do this. Once an OPVault is created, perform the following steps:
|
||||
@@ -48,7 +52,7 @@ Save your 1Password Vault locally to create an OPVault directory. Please see 1Pa
|
||||
|
||||
2. Enter the password for your vault and click `Continue` to unlock and preview the import. Click `Done` to complete the import.
|
||||
|
||||
=== Importing Bitwarden
|
||||
==== Bitwarden
|
||||
WARNING: A Bitwarden Export file may be unencrypted and you should securely delete this file after successfully importing it into KeePassXC.
|
||||
|
||||
1. Open the Import Wizard as shown above. Select the Bitwarden option.
|
||||
@@ -57,6 +61,13 @@ WARNING: A Bitwarden Export file may be unencrypted and you should securely dele
|
||||
|
||||
3. Click `Continue` to unlock and preview the import. Click `Done` to complete the import.
|
||||
|
||||
==== Proton Pass
|
||||
WARNING: A Proton Pass Export file is unencrypted and you should securely delete this file after successfully importing it into KeePassXC.
|
||||
|
||||
1. Open the Import Wizard as shown above. Select the Proton Pass option.
|
||||
|
||||
2. Click `Continue` to preview the import. Click `Done` to complete the import.
|
||||
|
||||
=== Importing KeePass 1 Database
|
||||
KeePass 1 database is an older format of the database created using a legacy version of KeePass. KeePassXC lets your import this older format of the database and you can seamlessly start using this database in your new KeePassXC application.
|
||||
|
||||
@@ -86,6 +97,8 @@ Opening without importing a remote database is possible by selecting Temporary D
|
||||
== Exporting Databases
|
||||
KeePassXC supports multiple ways to export your database for transfer to another program or to print out and archive.
|
||||
|
||||
WARNING: These exports do not contain all the information in your database due to various limitations in the export format. For example, the CSV export does not support attachments, advanced attributes, Auto-Type settings, or custom icons. The XML export does not support attachments. The HTML export is mainly for printing and does not support attachments and some custom data fields.
|
||||
|
||||
WARNING: Exporting your database will result in all of your passwords and sensitive information being stored in an unencrypted format. We do not recommend saving your exported database for long periods of time as that can cause a compromise of sensitive information.
|
||||
|
||||
.Database export menu
|
||||
|
||||
@@ -16,7 +16,7 @@ To use sharing, you need to enable it for the application.
|
||||
.KeeShare Application Settings
|
||||
image::keeshare_application_settings.png[]
|
||||
|
||||
=== Sharing Credentials
|
||||
=== Setup a Shared Group
|
||||
If you checked _Allow export_ in the Sharing settings you can now share a group of passwords. Sharing is always defined on a particular group. If you enable sharing on a group, every entry under this group, and its children, are shared. If you enable sharing on the root node, **every password** inside your database gets shared!
|
||||
|
||||
NOTE: KeeShare does not synchronize group structure after the initial share is created. At this time, KeeShare operates at the entry level; shared entries moved outside of a shared group are still synchronized.
|
||||
|
||||
@@ -15,7 +15,8 @@ NOTE: On macOS please substitute `Ctrl` with `Cmd` (aka `⌘`).
|
||||
|Save Database As | Ctrl + Shift + S
|
||||
|New Database | Ctrl + Shift + N
|
||||
|Close Database | Ctrl + W ; Ctrl + F4
|
||||
|Lock All Databases | Ctrl + L
|
||||
|Lock Current Database | Ctrl + L
|
||||
|Lock All Databases | Ctrl + Shift + L
|
||||
|Database Settings | Ctrl + Shift + ,
|
||||
|Database Reports | Ctrl + Shift + R
|
||||
|Quit | Ctrl + Q
|
||||
|
||||
@@ -5,56 +5,56 @@ include::.sharedheader[]
|
||||
// tag::content[]
|
||||
== Passkeys
|
||||
|
||||
Passkeys are a secure way for replacing passwords that is supported by all major browser vendors and an increasing number of websites. For more information on what Passkeys are and how they work, please go to the FIDO Alliance's documentation: https://fidoalliance.org/passkeys/
|
||||
Passkeys are a secure way for replacing passwords that is supported by all major browser vendors and an increasing number of websites. For more information on what passkeys are and how they work, please go to the FIDO Alliance's documentation: https://fidoalliance.org/passkeys/
|
||||
|
||||
=== Enabling Passkey Support
|
||||
=== Browser Passkey Support
|
||||
|
||||
KeePassXC supports Passkeys directly through the Browser Integration service. Passkeys are only supported with the use of the KeePassXC Browser Extension and a properly connected database. To enable Passkey support on the extension, you must check the _Enable Passkeys_ option in the extension settings page.
|
||||
KeePassXC supports passkeys directly through the Browser Integration service. Passkeys are only supported with the use of the KeePassXC Browser Extension and a properly connected database. To enable passkey support on the extension, you must check the _Enable Passkeys_ option in the extension settings page.
|
||||
|
||||
.Enable Passkey Support in the KeePassXC Browser Extension
|
||||
image::passkeys_enable_from_extension.png[,75%]
|
||||
|
||||
Optionally, you can disable falling back to the built-in Passkey support from your browser and operating system. If left enabled, the extension will show the default Passkey dialogs if KeePassXC cannot handle the request or the request is canceled.
|
||||
Optionally, you can disable falling back to the built-in passkey support from your browser and operating system. If left enabled, the extension will show the default passkey dialogs if KeePassXC cannot handle the request or the request is canceled.
|
||||
|
||||
=== Create a New Passkey
|
||||
|
||||
Creating a new Passkey and authenticating with it is a simple process. This workflow will be demonstrated using GitHub as an example site. Please note that GitHub allows two use cases for Passkeys, one for 2FA only and the other for replacement of username and password entirely. We will be configuring the latter use case in this example.
|
||||
Creating a new passkey and authenticating with it is a simple process. This workflow will be demonstrated using GitHub as an example site. Please note that GitHub allows two use cases for passkeys, one for 2FA only and the other for replacement of username and password entirely. We will be configuring the latter use case in this example.
|
||||
|
||||
After navigating to GitHub's _Settings_ -> _Password and authentication_, there is a separate section shown for Passkeys.
|
||||
After navigating to GitHub's _Settings_ -> _Password and authentication_, there is a separate section shown for passkeys.
|
||||
|
||||
.GitHub's Passkey Registration
|
||||
image::passkeys_github_1.png[]
|
||||
|
||||
After clicking the _Add a Passkey_ button, the user is redirected to another page showing the actual configuration option.
|
||||
After clicking the _Add a passkey_ button, the user is redirected to another page showing the actual configuration option.
|
||||
|
||||
.Configure Passwordless Authentication
|
||||
image::passkeys_github_2.png[,50%]
|
||||
|
||||
Clicking the _Add Passkey_ button now shows the following popup dialog for the user, asking confirmation for creating a new Passkey.
|
||||
Clicking the _Add passkey_ button now shows the following popup dialog for the user, asking confirmation for creating a new passkey.
|
||||
|
||||
.Passkey Registration Confirmation Dialog
|
||||
image::passkeys_register_dialog.png[,30%]
|
||||
|
||||
After the Passkey has been registered, a new entry is created to the database under _KeePassXC-Browser Passwords_ with _(Passkey)_ added to the entry title. The entry holds additional attributes that are used for authenticating the Passkey.
|
||||
After the passkey has been registered, a new entry is created to the database under _KeePassXC-Browser Passwords_ with _(passkey)_ added to the entry title. The entry holds additional attributes that are used for authenticating the passkey.
|
||||
|
||||
After registration, GitHub will ask a name for the Passkey. This is only relevant for the server.
|
||||
After registration, GitHub will ask a name for the passkey. This is only relevant for the server.
|
||||
|
||||
.GitHub's Passkey Nickname
|
||||
image::passkeys_github_3.png[,50%]
|
||||
|
||||
Now the Passkey should be shown on the GitHub's Passkey section.
|
||||
Now the passkey should be shown on the GitHub's passkey section.
|
||||
|
||||
.Registered Passkeys on GitHub
|
||||
image::passkeys_github_4.png[]
|
||||
|
||||
=== Login With a Passkey
|
||||
|
||||
The Passkey created in the previous section can now be used to login to GitHub. Instead of logging in with normal credentials, choose _Sign in with a passkey_ at the bottom of GitHub's login page.
|
||||
The passkey created in the previous section can now be used to login to GitHub. Instead of logging in with normal credentials, choose _Sign in with a passkey_ at the bottom of GitHub's login page.
|
||||
|
||||
.GitHub's login page with a Passkey option
|
||||
image::passkeys_github_5.png[,50%]
|
||||
|
||||
After clicking the button, KeePassXC-Browser detects the Passkeys authentication and KeePassXC shows the following dialog for confirmation.
|
||||
After clicking the button, KeePassXC-Browser detects the passkeys authentication and KeePassXC shows the following dialog for confirmation.
|
||||
|
||||
.Passkey authentication confirmation dialog
|
||||
image::passkeys_authentication_dialog.png[,50%]
|
||||
@@ -66,36 +66,36 @@ After confirmation user is now authenticated and logged into GitHub.
|
||||
|
||||
==== Multiple Passkeys for a Site
|
||||
|
||||
Multiple Passkeys can be created for a single site. When registering a new Passkey with a different username, KeePassXC shows an option to register a new Passkey or update the previous one. Updating a Passkey will override the existing entry, so this option should be only used when actually needed.
|
||||
Multiple passkeys can be created for a single site. When registering a new passkey with a different username, KeePassXC shows an option to register a new passkey or update the previous one. Updating a passkey will override the existing entry, so this option should be only used when actually needed.
|
||||
|
||||
.Passkey authentication confirmation dialog
|
||||
image::passkeys_update_dialog.png[,50%]
|
||||
|
||||
==== Exporting Passkeys
|
||||
|
||||
All Passkeys in a database can be viewed and accessed from the _Database_ -> _Passkeys..._ menu item. The page shows both _Import_ and _Export_ buttons for Passkeys.
|
||||
All passkeys in a database can be viewed and accessed from the _Database_ -> _Passkeys..._ menu item. The page shows both _Import_ and _Export_ buttons for passkeys.
|
||||
|
||||
.Passkeys Overview
|
||||
image::passkeys_all_passkeys.png[]
|
||||
|
||||
After selecting one or more entries, the following dialog is shown. One or multiple Passkeys can be selected for export from the previously selected list of entries.
|
||||
After selecting one or more entries, the following dialog is shown. One or multiple passkeys can be selected for export from the previously selected list of entries.
|
||||
|
||||
.Passkeys Export Dialog
|
||||
image::passkeys_export_dialog.png[,65%]
|
||||
|
||||
Exported Passkeys are stored in JSON format using the `.passkey` file extension. The file includes all relevant information for importing a Passkey to another database or saving a backup.
|
||||
Exported passkeys are stored in JSON format using the `.passkey` file extension. The file includes all relevant information for importing a passkey to another database or saving a backup.
|
||||
|
||||
WARNING: The exported Passkey file is unencrypted and should be securely stored.
|
||||
WARNING: The exported passkey file is unencrypted and should be securely stored.
|
||||
|
||||
==== Importing Passkeys
|
||||
|
||||
An exported Passkey can be imported directly to a database or to an entry. To import directly, use the _Database_ -> _Import Passkey_ menu item.
|
||||
When right-clicking an entry, a separate menu item for _Import Passkey_ is shown. This is useful if user wants to import a previously created Passkey to an existing entry.
|
||||
An exported passkey can be imported directly to a database or to an entry. To import directly, use the _Database_ -> _Import Passkey_ menu item.
|
||||
When right-clicking an entry, a separate menu item for _Import Passkey_ is shown. This is useful if user wants to import a previously created passkey to an existing entry.
|
||||
|
||||
.Import Passkey to an Entry
|
||||
image::passkeys_import_passkey_to_entry.png[,50%]
|
||||
|
||||
After selecting a Passkey file to import, a separate dialog is shown where you can select which database, group, and entry to target. By default, the group is set to _Imported Passkeys_. The default action is to create a new entry that contains the imported Passkey.
|
||||
After selecting a passkey file to import, a separate dialog is shown where you can select which database, group, and entry to target. By default, the group is set to _Imported Passkeys_. The default action is to create a new entry that contains the imported passkey.
|
||||
|
||||
.Passkey import dialog
|
||||
image::passkeys_import_dialog.png[,65%]
|
||||
|
||||
@@ -21,7 +21,6 @@ image::password_generator.png[]
|
||||
4. Select the character-sets that you want to include in your password.
|
||||
5. Use the regenerate button (Ctrl + R) to make a new password using the chosen options.
|
||||
6. Use the clipboard button (Ctrl + C) to copy the generated password to the clipboard.
|
||||
// tag::advanced[]
|
||||
7. Click the Advanced button to specify additional conditions for your desired password.
|
||||
+
|
||||
.Advanced Password Generator Options
|
||||
@@ -42,5 +41,4 @@ Word Count slider.
|
||||
5. _(Optional)_ You can also load your own custom word lists. Click the plus sign button to the right of the wordlist selection dialog to choose a custom word list. You can download alternative lists from the https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases[EFF's Website] or from https://github.com/redacted/XKCD-password-generator#additional-languages[GitHub].
|
||||
6. Click the Regenerate button (Ctrl + R) to generate a new random passphrase.
|
||||
7. Click the Clipboard button (Ctrl + C) to copy the passphrase to the clipboard.
|
||||
// end::advanced[]
|
||||
// end::content[]
|
||||
|
||||
@@ -18,6 +18,8 @@ This section contains full details on advanced features available in KeePassXC.
|
||||
|{NOTES} |Notes
|
||||
|{TOTP} |Current TOTP value (if configured)
|
||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|
||||
|{T-CONV:/<PLACEHOLDER>/<METHOD>/} |Text conversion for resolved placeholder (eg, {USERNAME}) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC
|
||||
|{T-REPLACE-RX:/<PLACEHOLDER>/<REGEX>/<REPLACE>/} |Use a regular expression to find and replace data from a resolved placeholder (eg, {USERNAME}). Refer to match groups using $1, $2, etc.
|
||||
|{URL:RMVSCM} |URL without scheme (e.g., https)
|
||||
|{URL:WITHOUTSCHEME} |URL without scheme
|
||||
|{URL:SCM} |URL Scheme
|
||||
@@ -124,5 +126,21 @@ Use regular expressions to find and replace data from a resolved placeholder. Re
|
||||
`C:\Backups\MyDatabase\01-05-2022.kdbx`
|
||||
|===
|
||||
|
||||
=== Creating a YubiKey backup
|
||||
It is advisable to have a backup replica YubiKey In case your main YubiKey gets damaged, lost, or stolen. The same HMAC key will need to be written to both keys. To do this you can either use the YubiKey Personalization Tool GUI or the ykpersonalize CLI tool. The steps for the CLI tool are shown:
|
||||
|
||||
1. Create a 20 byte HMAC key:
|
||||
+
|
||||
```
|
||||
dd status=none if=/dev/random bs=20 count=1 | xxd -p -c 40
|
||||
```
|
||||
|
||||
2. Write the HMAC key to slot 2 _(Set through the first switch. Out of the box the YubiKey OTP resides in slot 1)_:
|
||||
+
|
||||
```
|
||||
ykpersonalize -2 -a -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible -oallow-update
|
||||
```
|
||||
|
||||
You will be asked to enter the HMAC key you created earlier, copy/paste they key output in the first step. Repeat step 2 for your second YubiKey using the same HMAC key from before. We recommend storing your HMAC key in a safe place (e.g., printed on paper) in case you need to recreate another key.
|
||||
|
||||
// end::content[]
|
||||
|
||||
@@ -3,12 +3,12 @@ include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== SSH Agent integration
|
||||
== SSH Agent Integration
|
||||
SSH (Secure Shell) is a widely used remote secure shell protocol and is considered an industry standard for secure remote access to UNIX-like systems including Linux, BSDs, macOS and more recently even Windows received native support. SSH supports multiple types of authentication and the most widely used ones are either interactive keyboard input with a password or a public-key cryptography pair of keys.
|
||||
|
||||
KeePassXC SSH Agent integration is built to manage SSH keys in a secure manner by either storing them completely within your KeePassXC database or by having only the decryption key of a key file that is stored elsewhere. SSH Agent integration _does not_ provide an agent itself but works as a client for any agent implementation that is OpenSSH compatible.
|
||||
|
||||
=== OpenSSH agent on Linux
|
||||
=== OpenSSH Agent on Linux
|
||||
If you are using a modern desktop Linux distribution it is very likely the OpenSSH agent is already configured and running when you have logged in to a graphical desktop session.
|
||||
This should be true for distributions like Debian, Ubuntu (including Kubuntu, Xubuntu and Lubuntu), Linux Mint, Fedora, ElementaryOS and Manjaro.
|
||||
|
||||
@@ -32,10 +32,10 @@ WARNING: _GNOME Keyring_ prior to release 3.27.92 had its own custom implementat
|
||||
It does not support any constraints you may want to configure for an added key.
|
||||
If you are running a modern distribution the custom agent has been removed and replaced with the stock OpenSSH agent which is feature complete.
|
||||
|
||||
=== OpenSSH agent on macOS
|
||||
=== OpenSSH Agent on macOS
|
||||
Apple has made OpenSSH an integrated part of macOS with automatic agent startup when it is first used. No further configuration is needed.
|
||||
|
||||
=== OpenSSH agent and Pageant on Windows
|
||||
=== OpenSSH Agent and Pageant on Windows
|
||||
The SSH Agent integration on Windows supports both _PuTTY Pageant_ and _OpenSSH for Windows 10_.
|
||||
Since Pageant is currently still the most widely used implementation and is easily installable on any version of Windows, it is the default on KeePassXC.
|
||||
However, Microsoft includes a native OpenSSH client implementation with Windows 10 since autumn 2018 that can be used instead. If you would like to self-manage your OpenSSH version you can use the builds offered via their official https://github.com/powershell/Win32-OpenSSH[GitHub repository].
|
||||
@@ -61,7 +61,7 @@ Alternatively, you can use a _Windows PowerShell_ running as _Administrator_ to
|
||||
|
||||
KeePassXC and other compatible tools can now use the Windows OpenSSH agent. To use it with KeePassXC, update the settings explained in <<Setting up SSH Agent integration>>.
|
||||
|
||||
=== Setting up SSH Agent integration
|
||||
=== Setup SSH Agent Integration
|
||||
By default the SSH Agent integration plugin is disabled.
|
||||
To enable integration, follow the steps below to access the settings:
|
||||
|
||||
@@ -78,10 +78,10 @@ On Windows, you have the option to select _Pageant_ and/or _OpenSSH for Windows_
|
||||
|
||||
If the value of _SSH_AUTH_SOCK_ is empty it means the agent is not properly configured and KeePassXC will be unable to connect to it unless you provide a static override path to the socket.
|
||||
|
||||
=== Generating a key to use with KeePassXC
|
||||
=== Generating an SSH Key
|
||||
KeePassXC only supports keys in the _OpenSSH_ format. On Windows, _PuTTYgen_ saves keys in its own format by default and you will need to convert them to OpenSSH format before being used. In this guide we are going to generate a standard RSA key in the default size.
|
||||
|
||||
==== Generating a key on Linux or macOS with _ssh-keygen_
|
||||
==== Generating a key on Linux or macOS
|
||||
Open a terminal window and type the following command to generate a key:
|
||||
|
||||
$ ssh-keygen -o -f keepassxc -C johndoe@example
|
||||
@@ -116,13 +116,13 @@ With KeePassXC you only need the first file listed.
|
||||
==== Generating a key on Windows
|
||||
On Windows you can generate key pairs with _PuTTYgen_ and with _ssh-keygen_, depending on whether you installed PuTTY and your Windows version.
|
||||
|
||||
===== Using _PuTTYgen_
|
||||
===== Using PuTTYgen
|
||||
Please read the manual on how to use _PuTTYgen_ for details on generate a key: https://the.earth.li/~sgtatham/putty/0.74/htmldoc/Chapter8.html#pubkey-puttygen. Once generated, you must save the key in the new OpenSSH format, see image below.
|
||||
|
||||
.Generating a key with _PuTTYgen_
|
||||
image::sshagent_puttygen.png[,70%]
|
||||
|
||||
===== Using _ssh-keygen_
|
||||
===== Using ssh-keygen
|
||||
Open _Command Prompt_ or _Windows PowerShell_ and type the following command to generate a key:
|
||||
|
||||
PS C:\Users\user> ssh-keygen.exe -o -f keepassxc -C johndoe@example
|
||||
@@ -159,7 +159,7 @@ Now we can see two files were generated:
|
||||
|
||||
With KeePassXC you only need the first file listed.
|
||||
|
||||
=== Configuring an entry to use SSH Agent
|
||||
=== Adding SSH Key to an Entry
|
||||
The last step is to setup an entry to contain the SSH Agent settings and key file you generated.
|
||||
|
||||
1. Create a new entry, or open an existing entry in edit mode.
|
||||
|
||||
@@ -12,11 +12,11 @@ image::main_interface.png[]
|
||||
|
||||
*(A) Groups* – Organize your entries into discrete groups to bring order to all of your sensitive information. Groups can be nested under each other to create a hierarchy. Settings from parent groups get applied to their children. You can hide this panel on the View menu.
|
||||
|
||||
*(B) Tags* – Dynamic groups of entries that can be quickly displayed with one click. Any number of custom tags can be added when editing an entry. This panel also includes useful pre-defined searches, such as finding expired and weak passwords.
|
||||
*(B) Searches and Tags* – Dynamic groups of entries that can be quickly displayed with one click. Any number of custom tags can be added when editing an entry. This panel also includes useful pre-defined and custom saved searches, such as finding expired and weak passwords.
|
||||
|
||||
*\(C) Entries* – Entries contain all the information you want to store for a website or application you are storing in KeePassXC. This view shows all the entries in the selected group. Each column can be resized, reordered, and shown or hidden based on your preference. Right-click the header row to see all available options.
|
||||
*\(C) Entries* – Entries contain all the information for a website or application you are storing in KeePassXC. This view shows all the entries in the selected group. Each column can be resized, reordered, and shown or hidden based on your preference. Right-click the header row to see all available options.
|
||||
|
||||
*(D) Preview* – Shows a preview of the selected group or entry. You can temporarily hide this preview using the close button on the right hand side or completely disabled in the application settings.
|
||||
*(D) Preview* – Shows a preview of the selected group or entry. You can interact with most information stored in an entry from here without opening the entry for editing. You can temporarily hide this preview using the down-arrow button on the right hand side or completely disable it from the View menu.
|
||||
|
||||
TIP: You can enable double-click copying of entry username and password in the Application Security Settings. This is turned off by default starting with version 2.7.0.
|
||||
|
||||
@@ -29,13 +29,17 @@ image::toolbar.png[]
|
||||
*(A) Database* – Open Database, Save Database, Lock Database +
|
||||
*(B) Entries* – Create Entry, Edit Entry, Delete Selected Entries +
|
||||
*\(C) Entry Data* – Copy Username, Copy Password, Copy URL, Perform Auto-Type +
|
||||
*(D) Tools* – Password Generator, Application Settings +
|
||||
*(D) Tools* – Database Settings, Reports, Password Generator, Application Settings +
|
||||
*(E) Search*
|
||||
|
||||
=== Application Settings
|
||||
Users can configure KeePassXC to their personal tastes with a wide variety of general and security settings that apply to the whole application. These settings are accessible from _Tools_ -> _Settings_ or the cog wheel icon from the toolbar. Settings include: startup options, file management, entry management, user interface, language, security timeouts, and convenience.
|
||||
=== Screenshot Security
|
||||
By default, KeePassXC prevents recordings and screenshots of the application window on Windows and macOS. This prevents inadvertent spillage of information during meetings and disallows other applications to capture the window contents. If you would like to enable screen capture temporarily, navigate to _View_ menu and select _Allow Screen Capture_. Alternatively, you can start the application with the `--allow-screencapture` command line flag.
|
||||
|
||||
==== Setting the Theme
|
||||
|
||||
=== View Options
|
||||
You can customize the appearance of KeePassXC to your liking. The following options are available in the _View_ menu:
|
||||
|
||||
==== Themes
|
||||
KeePassXC ships with light and dark themes specifically designed to meet accessibility standards. In most cases, the appropriate theme for your system will be determined automatically, but you can always set a specific theme by using the _View_ menu. When a new theme is selected you will be prompted to restart KeePassXC to apply the theme immediately.
|
||||
|
||||
.Setting the theme
|
||||
@@ -47,8 +51,8 @@ For users with smaller screens or those who desire seeing more entries at once,
|
||||
.Compact mode comparison
|
||||
image::compact_mode_comparison.png[]
|
||||
|
||||
=== Screenshot Security
|
||||
By default, KeePassXC prevents recordings and screenshots of the application window on Windows and macOS. This prevents inadvertent spillage of information during meetings and disallows other applications to capture the window contents. If you would like to enable screen capture, you must start the application with the `--allow-screencapture` command line flag.
|
||||
=== Application Settings
|
||||
Users can configure KeePassXC to their personal tastes with a wide variety of general and security settings that apply to the whole application. These settings are accessible from _Tools_ -> _Settings_ or the cog wheel icon from the toolbar. Settings include: startup options, file management, entry management, user interface, language, security controls, and integration settings (Auto-Type, Browser, etc).
|
||||
|
||||
=== Keyboard Shortcuts
|
||||
include::KeyboardShortcuts.adoc[tag=content, leveloffset=+1]
|
||||
@@ -77,6 +81,7 @@ Arguments:
|
||||
filename(s) filenames of the password databases to open (*.kdbx)
|
||||
----
|
||||
|
||||
=== Environment Variables
|
||||
Additionally, the following environment variables may be useful when running the application:
|
||||
|
||||
[grid=rows, frame=none, width=75%]
|
||||
@@ -91,5 +96,17 @@ Additionally, the following environment variables may be useful when running the
|
||||
|QT_SCREEN_SCALE_FACTORS [list] | Specifies scale factors for each screen. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt
|
||||
|QT_SCALE_FACTOR_ROUNDING_POLICY | Control device pixel ratio rounding to the nearest integer. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt
|
||||
|===
|
||||
|
||||
=== Installer Options
|
||||
The following options can be set when running the Windows Installer MSI in an unattended installation:
|
||||
|
||||
* *LAUNCHAPPONEXIT* – Launch KeePassXC after install (default ON)
|
||||
* *AUTOSTARTPROGRAM* – KeePassXC will auto-start on login (default ON)
|
||||
* *INSTALLDESKTOPSHORTCUT* – A desktop icon will be installed (default OFF)
|
||||
|
||||
Example: `msiexec.exe /q /i KeePassXC-Y.Y.Y-WinZZ.msi AUTOSTARTPROGRAM=0`
|
||||
|
||||
== Command Line Tool
|
||||
KeePassXC comes with the command line tool *keepassxc-cli* to access, view, and manipulate your database directly from a terminal window. The tool is documented through a separate man page, which can be shown using `man keepassxc-cli`, or through the on-demand help using `keepassxc-cli [command] -h`. An online version of the man page is https://github.com/keepassxreboot/keepassxc/blob/latest/docs/man/keepassxc-cli.1.adoc[available on GitHub].
|
||||
// end::advanced[]
|
||||
// end::content[]
|
||||
|
||||
@@ -26,12 +26,13 @@ KeePassXC has numerous features for novice and power users alike. This guide wil
|
||||
** Password generator
|
||||
** Auto-Type passwords into applications
|
||||
** Browser integration with Google Chrome, Mozilla Firefox, Microsoft Edge, Chromium, Vivaldi, Brave, and Tor-Browser
|
||||
** Support for passkeys using the browser integration
|
||||
** Entry icon download
|
||||
** Import databases from CSV, 1Password, and KeePass1 formats
|
||||
** Import databases from CSV, 1Password, Bitwarden, Proton Pass, and KeePass1 formats
|
||||
|
||||
* Advanced Features
|
||||
** Database reports (password health, HIBP, and statistics)
|
||||
** Database export to CSV and HTML formats
|
||||
** Database export to CSV, XML, and HTML formats
|
||||
** TOTP storage and generation
|
||||
** Field references between entries
|
||||
** File attachments and custom attributes
|
||||
|
||||
BIN
share/demo.kdbx
@@ -1 +0,0 @@
|
||||
secret
|
||||
3
share/demo_readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
This is a demo database to showcase some of the features of KeePassXC
|
||||
|
||||
The password to unlock demo.kdbx is: secret
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19.92,12.08L12,20L4.08,12.08L5.5,10.67L11,16.17V2H13V16.17L18.5,10.66L19.92,12.08M12,20H2V22H22V20H12Z" /></svg>
|
||||
|
After Width: | Height: | Size: 182 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 8H11V14H9V8M13 1H7V3H13V1M17.03 7.39C18.26 8.93 19 10.88 19 13C19 17.97 15 22 10 22C5.03 22 1 17.97 1 13S5.03 4 10 4C12.12 4 14.07 4.74 15.62 6L17.04 4.56C17.55 5 18 5.46 18.45 5.97L17.03 7.39M17 13C17 9.13 13.87 6 10 6S3 9.13 3 13 6.13 20 10 20 17 16.87 17 13M21 7V13H23V7H21M21 17H23V15H21V17Z" /></svg>
|
||||
|
After Width: | Height: | Size: 377 B |
1
share/icons/application/scalable/actions/proton.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M22.5 101.382V88.3094c0-9.1756 7.4383-16.6154 16.6154-16.6154l11.528.1398c12.5786 0 21.5115-2.3424 26.7973-7.0258 5.2858-4.6835 7.9851-8.4576 7.9851-16.9566 0-6.1551-1.6287-11.224-4.7734-15.5733-3.0775-4.4165-7.1586-7.3271-12.2445-8.7317-3.2789-.8707-9.334-1.3047-18.1656-1.3047l-9.1856-.1284v28.1362H22.5V4.75l26.1364.1284c9.768 0 17.2292.4682 22.3808 1.4046 7.2271 1.2048 13.2823 3.513 18.167 6.926 4.8847 3.3445 8.7988 8.0622 11.7422 14.1516 3.0119 6.088 4.5735 12.5958 4.5735 19.89 0 12.5115-4.0382 20.301-12.0005 28.9998-7.9623 8.6303-22.9161 12.4345-43.7268 12.4345 0 0-7.5968.1384-8.716.1384-8.4061 0-15.6061 5.2016-18.5566 12.5586ZM22.5 124.2501c0-13.2305 8.192-24.0806 18.5567-24.9993v24.902L22.5 124.25Z"/></svg>
|
||||
|
After Width: | Height: | Size: 900 B |
@@ -6,6 +6,7 @@
|
||||
<file>application/256x256/apps/keepassxc.png</file>
|
||||
|
||||
<file>application/scalable/actions/application-exit.svg</file>
|
||||
<file>application/scalable/actions/arrow-collapse-down.svg</file>
|
||||
<file>application/scalable/actions/attributes-copy.svg</file>
|
||||
<file>application/scalable/actions/auto-type.svg</file>
|
||||
<file>application/scalable/actions/bitwarden.svg</file>
|
||||
@@ -39,6 +40,7 @@
|
||||
<file>application/scalable/actions/edit-clear-locationbar-rtl.svg</file>
|
||||
<file>application/scalable/actions/entry-clone.svg</file>
|
||||
<file>application/scalable/actions/entry-delete.svg</file>
|
||||
<file>application/scalable/actions/entry-expire.svg</file>
|
||||
<file>application/scalable/actions/entry-restore.svg</file>
|
||||
<file>application/scalable/actions/entry-edit.svg</file>
|
||||
<file>application/scalable/actions/entry-new.svg</file>
|
||||
@@ -71,6 +73,7 @@
|
||||
<file>application/scalable/actions/password-generator.svg</file>
|
||||
<file>application/scalable/actions/password-show-off.svg</file>
|
||||
<file>application/scalable/actions/password-show-on.svg</file>
|
||||
<file>application/scalable/actions/proton.svg</file>
|
||||
<file>application/scalable/actions/qrcode.svg</file>
|
||||
<file>application/scalable/actions/refresh.svg</file>
|
||||
<file>application/scalable/actions/remote-sync.svg</file>
|
||||
|
||||
@@ -52,6 +52,41 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.8.0" date="2025-01-01">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Placeholder for future release notes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.7.9" date="2024-06-19">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Passkeys: Ability to easily remove a passkey from an entry [#10777]</li>
|
||||
<li>Snap: Use new desktop portal for native messaging integration [#10906]</li>
|
||||
<li>Improve entry placeholder/reference feature [#10846]</li>
|
||||
<li>Improve CSV importing when title field isn't specified [#10843]</li>
|
||||
<li>Improve encrypted Bitwarden importing [#10800]</li>
|
||||
<li>Improve database settings UX [#10821]</li>
|
||||
<li>Improve handling of clipboard actions from entry preview [#10810]</li>
|
||||
<li>Improve group/entry view resize behavior and set sensible defaults [#10641]</li>
|
||||
<li>Passkeys: Fix incorrect username fill [#10874]</li>
|
||||
<li>Passkeys: Return additional data to the extension [#10857]</li>
|
||||
<li>Fix password clear timer inconsistency on unlock view [#10708]</li>
|
||||
<li>Fix portability check [#10760]</li>
|
||||
<li>Fix page overflow on HTML exports [#10735]</li>
|
||||
<li>Fix broken builds when using system provided zxcvbn [#10717]</li>
|
||||
<li>Fix copy password button when text is selected [#10853]</li>
|
||||
<li>Fix tab ordering on application settings pages [#10907]</li>
|
||||
<li>SSH Agent: Fix broken decrypt button [#10638]</li>
|
||||
<li>Windows: Fix ALT Auto-Type modifier [#10795]</li>
|
||||
<li>Windows: Fix wrong DACL memory size allocation [#10712]</li>
|
||||
<li>macOS: Fix monospace font sizing [#10739]</li>
|
||||
<li>Flatpak: Fix configuration settings off-by-one error [#10688]</li>
|
||||
<li>BSD: Fix compiling with libusb implementation [#10736]</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.7.8" date="2024-05-05">
|
||||
<description>
|
||||
<ul>
|
||||
|
||||
@@ -243,6 +243,26 @@
|
||||
<source>Export KeePassXC Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Small</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Normal</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Medium</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Large</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ApplicationSettingsWidgetGeneral</name>
|
||||
@@ -541,6 +561,18 @@
|
||||
<source>Export settings…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open browser on double clicking URL field in entry view</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Font size:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Font size selection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ApplicationSettingsWidgetSecurity</name>
|
||||
@@ -677,19 +709,6 @@
|
||||
<source>Entry does not have attribute for PICKCHARS: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid conversion type: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid conversion syntax: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid regular expression syntax %1
|
||||
%2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid placeholder: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1045,10 +1064,6 @@ Do you want to overwrite the passkey in %1 - %2?</source>
|
||||
<source>General</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browsers installed as snaps are currently not supported.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable integration for these browsers:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1272,6 +1287,10 @@ Do you want to overwrite the passkey in %1 - %2?</source>
|
||||
<source>KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2 and %3.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browsers installed using Snap or Flatpak are not supported with exception to Firefox installed using Snap.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>CloneDialog</name>
|
||||
@@ -1426,6 +1445,10 @@ Do you want to overwrite the passkey in %1 - %2?</source>
|
||||
Are you sure you want to import?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tags</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>CsvParserModel</name>
|
||||
@@ -1494,6 +1517,14 @@ Backup database located at %2</source>
|
||||
<source>Recycle Bin</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database file read error.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No file path was provided.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseOpenDialog</name>
|
||||
@@ -1673,6 +1704,10 @@ Are you sure you want to continue with this file?.</source>
|
||||
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware keys found, but no slots are configured.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@@ -2194,6 +2229,50 @@ removed from the database.</source>
|
||||
<source>Autosave delay since last change checkbox</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Public Database Metadata</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Warning: the following settings are not encrypted.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display name:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Publically visible display name used on the unlock dialog</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database public display name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Publically visible color used on the unlock dialog</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database public display color chooser</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display icon:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Database Icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingsWidgetKeeShare</name>
|
||||
@@ -2616,24 +2695,6 @@ Save changes?</source>
|
||||
<source>File has changed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed. Do you want to load the changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge Request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed and you have unsaved changes.
|
||||
Do you want to merge your changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not open the new database file while attempting to autoreload.
|
||||
Error: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disable safe saves?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -2720,6 +2781,50 @@ Disable safe saves and try again?</source>
|
||||
<source>Do you want to remove the passkey from this entry?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file "%1" was modified externally</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you want to load the changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reload database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reloading database…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reload canceled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reload successful</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reload pending user action…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file "%1" was modified externally.<br>How would you like to proceed?<br><br>Merge all changes<br>Ignore the changes on disk until save<br>Discard unsaved changes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file "%1" was modified externally.<br>How would you like to proceed?<br><br>Merge all changes then save<br>Overwrite the changes on disk<br>Discard unsaved changes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database file overwritten.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database file on disk cannot be unlocked with current credentials.<br>Enter new credentials and/or present hardware key to continue.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidget</name>
|
||||
@@ -3313,6 +3418,10 @@ Would you like to correct it?</source>
|
||||
<source> seconds</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear agent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditGroupWidget</name>
|
||||
@@ -3766,6 +3875,19 @@ This may cause the affected plugins to malfunction.</source>
|
||||
<source>Passkey</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid conversion type: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid conversion syntax: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid regular expression syntax %1
|
||||
%2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttachments</name>
|
||||
@@ -3774,6 +3896,21 @@ This may cause the affected plugins to malfunction.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttachmentsDialog</name>
|
||||
<message>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File contents...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttachmentsModel</name>
|
||||
<message>
|
||||
@@ -3811,14 +3948,6 @@ This may cause the affected plugins to malfunction.</source>
|
||||
<source>Remove</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Rename selected attachment</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Rename</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open selected attachment</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -3928,6 +4057,18 @@ Error: %1</source>
|
||||
Would you like to overwrite the existing attachment?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Preview</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to preview an attachment: Attachment not found</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttributesModel</name>
|
||||
@@ -4126,6 +4267,10 @@ Would you like to overwrite the existing attachment?</source>
|
||||
<source>Background Color</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Group Path</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryPreviewWidget</name>
|
||||
@@ -4635,6 +4780,14 @@ You can enable the DuckDuckGo website icon service in the security section of th
|
||||
<source>KeePass1 Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Proton Pass (.json)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Proton Pass JSON Export</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Temporary Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -4651,10 +4804,6 @@ You can enable the DuckDuckGo website icon service in the security section of th
|
||||
<source>Input:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote Database (.kdbx)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>e.g.:
|
||||
get DatabaseOnRemote.kdbx {TEMP_DATABASE}
|
||||
@@ -4665,6 +4814,10 @@ The command has to exit. In case of `sftp` as last commend `exit` has to be sent
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote Database (.kdbx)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>KMessageWidget</name>
|
||||
@@ -5871,6 +6024,10 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<source>Import Passkey</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote S&ync…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit Application</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -5975,6 +6132,10 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<source>Show Password Generator</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove Passkey From Entry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Perform Auto-Type: {USERNAME}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -6120,17 +6281,33 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remote S&ync…</source>
|
||||
<source>Show Group Panel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove Passkey From Entry</source>
|
||||
<source>Toggle Show Group Panel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Setup Remote Sync…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Generator</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>E&xpire Entry…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear SSH Agent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear all identities in ssh-agent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ManageDatabase</name>
|
||||
@@ -6281,6 +6458,25 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NewEntryAttachmentsDialog</name>
|
||||
<message>
|
||||
<source>Attachment name cannot be empty</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attachment with the same name already exists</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save attachment</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New entry attachment</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NixUtils</name>
|
||||
<message>
|
||||
@@ -6846,10 +7042,6 @@ The following data is missing:
|
||||
<source>Word Count:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Character Count:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Case:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -6862,10 +7054,6 @@ The following data is missing:
|
||||
<source>Add custom wordlist</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>character</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -6979,6 +7167,10 @@ Do you want to overwrite it?</source>
|
||||
<source>Characters: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MIXED case</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Excluded characters: "0", "1", "l", "I", "O", "|", "﹒", "B", "8", "G", "6"</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -7050,6 +7242,21 @@ Do you want to overwrite it?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PreviewEntryAttachmentsDialog</name>
|
||||
<message>
|
||||
<source>Preview entry attachment</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No preview available</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Image format not supported</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QMessageBox</name>
|
||||
<message>
|
||||
@@ -7877,10 +8084,6 @@ Do you want to overwrite it?</source>
|
||||
<source>Exit interactive mode.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Format to use when exporting. Available choices are 'xml' or 'csv'. Defaults to 'xml'.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Exports the content of a database to standard output in the specified format.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -8483,18 +8686,6 @@ Kernel: %3 %4</source>
|
||||
<source>file empty</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>malformed string</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>missing closing quote</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1: (row, col) %2,%3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AES 256-bit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -9002,6 +9193,14 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Cannot generate valid passphrases because the wordlist is too short</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Encrypted files are not supported.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Proton Pass Import</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete plugin data?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -9013,6 +9212,34 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Passkey</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Format to use when exporting. Available choices are 'xml', 'csv' or 'html'. Defaults to 'xml'.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>start minimized to the system tray</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>malformed string, possible unescaped delimiter</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>missing closing delimiter</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1, row: %2, column: %3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tags</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtIOCompressor</name>
|
||||
@@ -9148,6 +9375,13 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Exclude from reports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Expire Entry(s)…</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Only show entries that have a URL</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -9174,6 +9408,14 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
</context>
|
||||
<context>
|
||||
<name>ReportsWidgetHealthcheck</name>
|
||||
<message>
|
||||
<source>Show expired entries</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> (Expired)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hover over reason to show additional details. Double-click entries to edit.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -9237,18 +9479,17 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Exclude from reports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show expired entries</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<message numerus="yes">
|
||||
<source>Expire Entry(s)…</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show entries that have been excluded from reports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> (Expired)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ReportsWidgetHibp</name>
|
||||
@@ -9347,6 +9588,13 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Exclude from reports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Expire Entry(s)…</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ReportsWidgetPasskeys</name>
|
||||
@@ -9591,6 +9839,14 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>No agent running, cannot list identities.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to remove all SSH identities from agent.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>All SSH identities removed from agent.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SearchHelpWidget</name>
|
||||
@@ -10174,6 +10430,10 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source><p>If you own a <a href="https://www.yubico.com/">YubiKey</a> or <a href="https://onlykey.io">OnlyKey</a>, you can use it for additional security.</p><p>The key requires one of its slots to be programmed with <a href="https://keepassxc.org/docs/#faq-yubikey-howto">Challenge-Response</a>.</p></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware keys found, but no slots are configured</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyInterfacePCSC</name>
|
||||
|
||||
@@ -115,6 +115,14 @@
|
||||
<ComponentRef Id="DesktopShortcut" />
|
||||
</FeatureRef>
|
||||
|
||||
<!-- Detect VCRedist Version -->
|
||||
<Property Id="VCREDISTINSTALLED">
|
||||
<RegistrySearch Id="SearchVCRedist" Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" Name="Version" Type="raw" Win64="yes"/>
|
||||
</Property>
|
||||
<Condition Message="The installed version of Visual Studio Redistributable is too old. Please install the latest version from: https://aka.ms/vs/17/release/vc_redist.x64.exe">
|
||||
<![CDATA[VCREDISTINSTALLED AND VCREDISTINSTALLED > "14.32.31332.0"]]>
|
||||
</Condition>
|
||||
|
||||
<!-- Action to launch application after installer exits -->
|
||||
<Property Id="WixShellExecTarget" Value="[#CM_FP_KeePassXC.exe]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||
|
||||
@@ -2006,7 +2006,6 @@ drizzly
|
||||
drone
|
||||
drool
|
||||
droop
|
||||
drop-in
|
||||
dropforge
|
||||
dropkick
|
||||
droplet
|
||||
@@ -2525,7 +2524,6 @@ feed
|
||||
feel
|
||||
feisty
|
||||
feline
|
||||
felt-tip
|
||||
feminine
|
||||
feminism
|
||||
feminist
|
||||
@@ -6637,7 +6635,6 @@ synthesis
|
||||
synthetic
|
||||
syrup
|
||||
system
|
||||
t-shirt
|
||||
tabasco
|
||||
tabby
|
||||
tableful
|
||||
@@ -7745,7 +7742,6 @@ yiddish
|
||||
yield
|
||||
yin
|
||||
yippee
|
||||
yo-yo
|
||||
yodel
|
||||
yoga
|
||||
yogurt
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
name: keepassxc
|
||||
summary: "KeePassXC: Secure, Community-Driven Password Management"
|
||||
description: |
|
||||
KeePassXC is an advanced password manager that offers secure storage
|
||||
in an encrypted database, with a modern, user-friendly experience that
|
||||
adapts to your desktop environment.
|
||||
adopt-info: keepassxc
|
||||
grade: stable
|
||||
base: core22
|
||||
@@ -7,19 +12,30 @@ compression: lzo
|
||||
|
||||
apps:
|
||||
keepassxc:
|
||||
command: usr/bin/keepassxc
|
||||
common-id: org.keepassxc.KeePassXC.desktop
|
||||
# Use desktop-launch to improve integration
|
||||
command: desktop-launch usr/bin/keepassxc
|
||||
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||
extensions: [kde-neon]
|
||||
plugs: [home, unity7, network, network-bind, removable-media, raw-usb, password-manager-service, browser-native-messaging]
|
||||
autostart: org.keepassxc.KeePassXC.desktop
|
||||
plugs:
|
||||
- home
|
||||
- unity7
|
||||
- network
|
||||
- network-bind
|
||||
- removable-media
|
||||
- raw-usb
|
||||
- password-manager-service
|
||||
- browser-native-messaging
|
||||
|
||||
cli:
|
||||
command: usr/bin/keepassxc-cli
|
||||
extensions: [kde-neon]
|
||||
plugs: [home, removable-media, raw-usb]
|
||||
|
||||
proxy:
|
||||
command: usr/bin/keepassxc-proxy
|
||||
extensions: [kde-neon]
|
||||
|
||||
# Enable direct access to the native messaging host configuration files
|
||||
plugs:
|
||||
browser-native-messaging:
|
||||
interface: personal-files
|
||||
@@ -78,8 +94,8 @@ parts:
|
||||
- libfreetype6
|
||||
- xclip
|
||||
- libkeyutils1
|
||||
|
||||
lint:
|
||||
ignore:
|
||||
- library:
|
||||
- lib/**/libhistory.so*
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ set(core_SOURCES
|
||||
format/BitwardenReader.cpp
|
||||
format/CsvExporter.cpp
|
||||
format/CsvParser.cpp
|
||||
format/HtmlExporter.cpp
|
||||
format/KeePass1Reader.cpp
|
||||
format/KeePass2.cpp
|
||||
format/KeePass2RandomStream.cpp
|
||||
@@ -90,11 +91,13 @@ set(core_SOURCES
|
||||
format/OpVaultReaderAttachments.cpp
|
||||
format/OpVaultReaderBandEntry.cpp
|
||||
format/OpVaultReaderSections.cpp
|
||||
format/ProtonPassReader.cpp
|
||||
keys/CompositeKey.cpp
|
||||
keys/FileKey.cpp
|
||||
keys/PasswordKey.cpp
|
||||
keys/ChallengeResponseKey.cpp
|
||||
streams/HashedBlockStream.cpp
|
||||
streams/HashingStream.cpp
|
||||
streams/HmacBlockStream.cpp
|
||||
streams/LayeredStream.cpp
|
||||
streams/qtiocompressor.cpp
|
||||
@@ -127,7 +130,7 @@ set(gui_SOURCES
|
||||
gui/FileDialog.cpp
|
||||
gui/Font.cpp
|
||||
gui/GuiTools.cpp
|
||||
gui/HtmlExporter.cpp
|
||||
gui/HtmlGuiExporter.cpp
|
||||
gui/IconModels.cpp
|
||||
gui/KMessageWidget.cpp
|
||||
gui/MainWindow.cpp
|
||||
@@ -156,6 +159,8 @@ set(gui_SOURCES
|
||||
gui/entry/EntryAttachmentsModel.cpp
|
||||
gui/entry/EntryAttachmentsWidget.cpp
|
||||
gui/entry/EntryAttributesModel.cpp
|
||||
gui/entry/NewEntryAttachmentsDialog.cpp
|
||||
gui/entry/PreviewEntryAttachmentsDialog.cpp
|
||||
gui/entry/EntryHistoryModel.cpp
|
||||
gui/entry/EntryModel.cpp
|
||||
gui/entry/EntryView.cpp
|
||||
@@ -386,7 +391,7 @@ target_link_libraries(keepassxc_gui
|
||||
${sshagent_LIB})
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(keepassxc_gui "-framework Foundation -framework AppKit -framework Carbon -framework Security -framework LocalAuthentication")
|
||||
target_link_libraries(keepassxc_gui "-framework Foundation -framework AppKit -framework Carbon -framework Security -framework LocalAuthentication -framework ScreenCaptureKit")
|
||||
if(Qt5MacExtras_FOUND)
|
||||
target_link_libraries(keepassxc_gui Qt5::MacExtras)
|
||||
endif()
|
||||
|
||||
@@ -153,6 +153,7 @@ AutoType::AutoType(QObject* parent, bool test)
|
||||
#endif
|
||||
}
|
||||
|
||||
connect(this, SIGNAL(autotypeFinished()), SLOT(resetAutoTypeState()));
|
||||
connect(qApp, SIGNAL(aboutToQuit()), SLOT(unloadPlugin()));
|
||||
}
|
||||
|
||||
@@ -353,7 +354,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry,
|
||||
}
|
||||
}
|
||||
|
||||
resetAutoTypeState();
|
||||
m_inAutoType.unlock();
|
||||
emit autotypeFinished();
|
||||
}
|
||||
@@ -434,38 +434,33 @@ void AutoType::startGlobalAutoType(const QString& search)
|
||||
*/
|
||||
void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbList, const QString& search)
|
||||
{
|
||||
if (!m_plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_inGlobalAutoTypeDialog.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_windowTitleForGlobal.isEmpty()) {
|
||||
m_inGlobalAutoTypeDialog.unlock();
|
||||
if (!m_plugin || !m_inGlobalAutoTypeDialog.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<AutoTypeMatch> matchList;
|
||||
bool hideExpired = config()->get(Config::AutoTypeHideExpiredEntry).toBool();
|
||||
// Generate entry/sequence match list if there is a valid window title
|
||||
if (!m_windowTitleForGlobal.isEmpty()) {
|
||||
bool hideExpired = config()->get(Config::AutoTypeHideExpiredEntry).toBool();
|
||||
for (const auto& db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
for (auto entry : dbEntries) {
|
||||
auto group = entry->group();
|
||||
if (!group || !group->resolveAutoTypeEnabled() || !entry->autoTypeEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
for (auto entry : dbEntries) {
|
||||
auto group = entry->group();
|
||||
if (!group || !group->resolveAutoTypeEnabled() || !entry->autoTypeEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hideExpired && entry->isExpired()) {
|
||||
continue;
|
||||
}
|
||||
const QSet<QString> sequences = Tools::asSet(entry->autoTypeSequences(m_windowTitleForGlobal));
|
||||
for (const auto& sequence : sequences) {
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
if (hideExpired && entry->isExpired()) {
|
||||
continue;
|
||||
}
|
||||
const QSet<QString> sequences = Tools::asSet(entry->autoTypeSequences(m_windowTitleForGlobal));
|
||||
for (const auto& sequence : sequences) {
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Auto-Type: Window title was empty from the operating system";
|
||||
}
|
||||
|
||||
// Show the selection dialog if we always ask, have multiple matches, or no matches
|
||||
@@ -493,11 +488,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
m_windowForGlobal,
|
||||
virtualMode ? AutoTypeExecutor::Mode::VIRTUAL
|
||||
: AutoTypeExecutor::Mode::NORMAL);
|
||||
resetAutoTypeState();
|
||||
});
|
||||
connect(selectDialog, &QDialog::rejected, this, [this] {
|
||||
restoreWindowState();
|
||||
resetAutoTypeState();
|
||||
emit autotypeFinished();
|
||||
});
|
||||
|
||||
@@ -511,10 +504,8 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
} else if (!matchList.isEmpty()) {
|
||||
// Only one match and not asking, do it!
|
||||
executeAutoTypeActions(matchList.first().first, matchList.first().second, m_windowForGlobal);
|
||||
resetAutoTypeState();
|
||||
} else {
|
||||
// We should never get here
|
||||
resetAutoTypeState();
|
||||
emit autotypeFinished();
|
||||
}
|
||||
}
|
||||
@@ -689,74 +680,23 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
} else if (placeholder.startsWith("t-conv:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else {
|
||||
error = tr("Invalid conversion type: %1").arg(type);
|
||||
return {};
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
auto resolved = entry->resolveConversionPlaceholder(placeholder, &error);
|
||||
if (!error.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (placeholder.startsWith("t-replace-rx:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \\<num> to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
|
||||
|
||||
auto searchRegex = QRegularExpression(resolvedSearch);
|
||||
if (!searchRegex.isValid()) {
|
||||
error = tr("Invalid regular expression syntax %1\n%2")
|
||||
.arg(resolvedSearch, searchRegex.errorString());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto resolved = resolvedText.replace(searchRegex, resolvedReplace);
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
auto resolved = entry->resolveRegexPlaceholder(placeholder, &error);
|
||||
if (!error.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (placeholder.startsWith("mode=")) {
|
||||
auto mode = AutoTypeExecutor::Mode::NORMAL;
|
||||
if (placeholder.endsWith("virtual")) {
|
||||
|
||||
@@ -66,6 +66,7 @@ signals:
|
||||
private slots:
|
||||
void startGlobalAutoType(const QString& search);
|
||||
void unloadPlugin();
|
||||
void resetAutoTypeState();
|
||||
|
||||
private:
|
||||
enum WindowState
|
||||
@@ -83,7 +84,6 @@ private:
|
||||
WId window = 0,
|
||||
AutoTypeExecutor::Mode mode = AutoTypeExecutor::Mode::NORMAL);
|
||||
void restoreWindowState();
|
||||
void resetAutoTypeState();
|
||||
|
||||
static QList<QSharedPointer<AutoTypeAction>>
|
||||
parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly = false);
|
||||
|
||||
@@ -85,6 +85,10 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
connect(m_ui->action, &QToolButton::clicked, this, &AutoTypeSelectDialog::activateCurrentMatch);
|
||||
|
||||
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject()));
|
||||
|
||||
auto sortColumn = config()->get(Config::AutoTypeDialogSortColumn).toInt();
|
||||
auto sortOrder = config()->get(Config::AutoTypeDialogSortOrder).toInt();
|
||||
m_ui->view->sortByColumn(sortColumn, sortOrder == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
|
||||
}
|
||||
|
||||
// Required for QScopedPointer
|
||||
@@ -330,15 +334,7 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
});
|
||||
#endif
|
||||
|
||||
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
|
||||
// Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
|
||||
typeUsernameAction->setShortcutVisibleInContextMenu(true);
|
||||
typePasswordAction->setShortcutVisibleInContextMenu(true);
|
||||
typeTotpAction->setShortcutVisibleInContextMenu(true);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
typeVirtualAction->setShortcutVisibleInContextMenu(true);
|
||||
#endif
|
||||
|
||||
copyUsernameAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_1);
|
||||
copyUsernameAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::USERNAME);
|
||||
connect(copyUsernameAction, &QAction::triggered, this, [&] {
|
||||
auto entry = m_ui->view->currentMatch().first;
|
||||
@@ -348,6 +344,7 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
}
|
||||
});
|
||||
|
||||
copyPasswordAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_2);
|
||||
copyPasswordAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::PASSWORD);
|
||||
connect(copyPasswordAction, &QAction::triggered, this, [&] {
|
||||
auto entry = m_ui->view->currentMatch().first;
|
||||
@@ -357,6 +354,7 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
}
|
||||
});
|
||||
|
||||
copyTotpAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_3);
|
||||
copyTotpAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::TOTP);
|
||||
connect(copyTotpAction, &QAction::triggered, this, [&] {
|
||||
auto entry = m_ui->view->currentMatch().first;
|
||||
@@ -365,6 +363,18 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
reject();
|
||||
}
|
||||
});
|
||||
|
||||
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
|
||||
// Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
|
||||
typeUsernameAction->setShortcutVisibleInContextMenu(true);
|
||||
typePasswordAction->setShortcutVisibleInContextMenu(true);
|
||||
typeTotpAction->setShortcutVisibleInContextMenu(true);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
typeVirtualAction->setShortcutVisibleInContextMenu(true);
|
||||
#endif
|
||||
copyUsernameAction->setShortcutVisibleInContextMenu(true);
|
||||
copyPasswordAction->setShortcutVisibleInContextMenu(true);
|
||||
copyTotpAction->setShortcutVisibleInContextMenu(true);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::showEvent(QShowEvent* event)
|
||||
@@ -391,6 +401,8 @@ void AutoTypeSelectDialog::showEvent(QShowEvent* event)
|
||||
void AutoTypeSelectDialog::hideEvent(QHideEvent* event)
|
||||
{
|
||||
config()->set(Config::GUI_AutoTypeSelectDialogSize, size());
|
||||
config()->set(Config::AutoTypeDialogSortColumn, m_ui->view->horizontalHeader()->sortIndicatorSection());
|
||||
config()->set(Config::AutoTypeDialogSortOrder, m_ui->view->horizontalHeader()->sortIndicatorOrder());
|
||||
if (!m_accepted) {
|
||||
emit rejected();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
set(autotype_mac_SOURCES AutoTypeMac.cpp)
|
||||
|
||||
add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
||||
set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||
set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon -framework ScreenCaptureKit")
|
||||
target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||
|
||||
install(TARGETS keepassxc-autotype-cocoa
|
||||
|
||||
@@ -58,17 +58,6 @@ const QString BrowserPasskeys::REQUIREMENT_REQUIRED = QStringLiteral("required")
|
||||
const QString BrowserPasskeys::PASSKEYS_ATTESTATION_DIRECT = QStringLiteral("direct");
|
||||
const QString BrowserPasskeys::PASSKEYS_ATTESTATION_NONE = QStringLiteral("none");
|
||||
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_USERNAME = QStringLiteral("KPEX_PASSKEY_USERNAME");
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID = QStringLiteral("KPEX_PASSKEY_CREDENTIAL_ID");
|
||||
|
||||
// For compatibility with StrongBox
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID = QStringLiteral("KPEX_PASSKEY_GENERATED_USER_ID");
|
||||
const QString BrowserPasskeys::KPXC_PASSKEY_USERNAME = QStringLiteral("KPXC_PASSKEY_USERNAME");
|
||||
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM = QStringLiteral("KPEX_PASSKEY_PRIVATE_KEY_PEM");
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY = QStringLiteral("KPEX_PASSKEY_RELYING_PARTY");
|
||||
const QString BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE = QStringLiteral("KPEX_PASSKEY_USER_HANDLE");
|
||||
|
||||
BrowserPasskeys* BrowserPasskeys::instance()
|
||||
{
|
||||
return s_browserPasskeys;
|
||||
|
||||
@@ -105,14 +105,6 @@ public:
|
||||
static const QString PASSKEYS_ATTESTATION_DIRECT;
|
||||
static const QString PASSKEYS_ATTESTATION_NONE;
|
||||
|
||||
static const QString KPXC_PASSKEY_USERNAME;
|
||||
static const QString KPEX_PASSKEY_USERNAME;
|
||||
static const QString KPEX_PASSKEY_CREDENTIAL_ID;
|
||||
static const QString KPEX_PASSKEY_GENERATED_USER_ID;
|
||||
static const QString KPEX_PASSKEY_PRIVATE_KEY_PEM;
|
||||
static const QString KPEX_PASSKEY_RELYING_PARTY;
|
||||
static const QString KPEX_PASSKEY_USER_HANDLE;
|
||||
|
||||
private:
|
||||
QByteArray buildAttestationObject(const QJsonObject& credentialCreationOptions,
|
||||
const QString& extensions,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
*
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "BrowserHost.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
@@ -50,6 +51,7 @@
|
||||
#include <QLocalSocket>
|
||||
#include <QLocale>
|
||||
#include <QProgressDialog>
|
||||
#include <QStringView>
|
||||
#include <QUrl>
|
||||
|
||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||
@@ -85,6 +87,10 @@ BrowserService::BrowserService()
|
||||
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
||||
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
||||
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
||||
connect(getMainWindow(),
|
||||
&MainWindow::databaseUnlockDialogFinished,
|
||||
this,
|
||||
&BrowserService::handleDatabaseUnlockDialogFinished);
|
||||
|
||||
setEnabled(browserSettings()->isEnabled());
|
||||
}
|
||||
@@ -597,7 +603,8 @@ QString BrowserService::storeKey(const QString& key)
|
||||
return {};
|
||||
}
|
||||
|
||||
contains = db->metadata()->customData()->contains(CustomData::BrowserKeyPrefix + id);
|
||||
contains =
|
||||
db->metadata()->customData()->contains(CustomData::getKeyWithPrefix(CustomData::BrowserKeyPrefix, id));
|
||||
if (contains) {
|
||||
dialogResult = MessageBox::warning(m_currentDatabaseWidget,
|
||||
tr("KeePassXC - Overwrite existing key?"),
|
||||
@@ -610,8 +617,8 @@ QString BrowserService::storeKey(const QString& key)
|
||||
} while (contains && dialogResult == MessageBox::Cancel);
|
||||
|
||||
hideWindow();
|
||||
db->metadata()->customData()->set(CustomData::BrowserKeyPrefix + id, key);
|
||||
db->metadata()->customData()->set(QString("%1_%2").arg(CustomData::Created, id),
|
||||
db->metadata()->customData()->set(CustomData::getKeyWithPrefix(CustomData::BrowserKeyPrefix, id), key);
|
||||
db->metadata()->customData()->set(CustomData::getKeyWithPrefix(CustomData::Created, id),
|
||||
QLocale::system().toString(Clock::currentDateTime(), QLocale::ShortFormat));
|
||||
return id;
|
||||
}
|
||||
@@ -623,7 +630,7 @@ QString BrowserService::getKey(const QString& id)
|
||||
return {};
|
||||
}
|
||||
|
||||
return db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + id);
|
||||
return db->metadata()->customData()->value(CustomData::getKeyWithPrefix(CustomData::BrowserKeyPrefix, id));
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
@@ -763,9 +770,9 @@ QJsonObject BrowserService::showPasskeysAuthenticationPrompt(const QJsonObject&
|
||||
return getPasskeyError(ERROR_PASSKEYS_UNKNOWN_ERROR);
|
||||
}
|
||||
|
||||
const auto privateKeyPem = selectedEntry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM);
|
||||
const auto privateKeyPem = selectedEntry->attributes()->value(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM);
|
||||
const auto credentialId = passkeyUtils()->getCredentialIdFromEntry(selectedEntry);
|
||||
const auto userHandle = selectedEntry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE);
|
||||
const auto userHandle = selectedEntry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE);
|
||||
|
||||
auto publicKeyCredential =
|
||||
browserPasskeys()->buildGetPublicKeyCredential(assertionOptions, credentialId, userHandle, privateKeyPem);
|
||||
@@ -843,11 +850,11 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
|
||||
|
||||
entry->beginUpdate();
|
||||
|
||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
|
||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
|
||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
|
||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY, rpId);
|
||||
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE, userHandle, true);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USERNAME, username);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY, rpId);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USER_HANDLE, userHandle, true);
|
||||
entry->addTag(tr("Passkey"));
|
||||
|
||||
entry->endUpdate();
|
||||
@@ -1042,7 +1049,7 @@ QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
// With Passkeys, check for the Relying Party instead of URL
|
||||
if (passkey && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
|
||||
if (passkey && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
@@ -1065,7 +1072,8 @@ QList<Entry*> BrowserService::searchEntries(const QString& siteUrl,
|
||||
// Check if database is connected with KeePassXC-Browser. If so, return browser key (otherwise empty)
|
||||
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
|
||||
for (const StringPair& keyPair : keyList) {
|
||||
QString key = db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + keyPair.first);
|
||||
const auto key = db->metadata()->customData()->value(
|
||||
CustomData::getKeyWithPrefix(CustomData::BrowserKeyPrefix, keyPair.first));
|
||||
if (!key.isEmpty() && keyPair.second == key) {
|
||||
return keyPair.first;
|
||||
}
|
||||
@@ -1368,9 +1376,15 @@ bool BrowserService::shouldIncludeEntry(Entry* entry,
|
||||
return url.endsWith("by-path/" + entry->path());
|
||||
}
|
||||
|
||||
const auto allEntryUrls = entry->getAllUrls();
|
||||
for (const auto& entryUrl : allEntryUrls) {
|
||||
if (handleURL(entryUrl, url, submitUrl, omitWwwSubdomain)) {
|
||||
// Handle the entry URL
|
||||
if (handleURL(entry->resolveUrl(), url, submitUrl, omitWwwSubdomain)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle additional URLs
|
||||
const auto additionalUrls = entry->getAdditionalUrls();
|
||||
for (const auto& additionalUrl : additionalUrls) {
|
||||
if (handleURL(additionalUrl, url, submitUrl, omitWwwSubdomain, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1384,7 +1398,7 @@ QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const Strin
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
||||
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
||||
if (entry->hasPasskey() && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
||||
entries << entry;
|
||||
}
|
||||
}
|
||||
@@ -1399,8 +1413,8 @@ QList<Entry*> BrowserService::getPasskeyEntriesWithUserHandle(const QString& rpI
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
||||
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId
|
||||
&& entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE) == userId) {
|
||||
if (entry->hasPasskey() && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) == rpId
|
||||
&& entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE) == userId) {
|
||||
entries << entry;
|
||||
}
|
||||
}
|
||||
@@ -1425,7 +1439,7 @@ QList<Entry*> BrowserService::getPasskeyAllowedEntries(const QJsonObject& assert
|
||||
// See: https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-userhandle
|
||||
if (allowedCredentials.contains(passkeyUtils()->getCredentialIdFromEntry(entry))
|
||||
|| (allowedCredentials.isEmpty()
|
||||
&& entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE))) {
|
||||
&& entry->attributes()->hasKey(EntryAttributes::KPEX_PASSKEY_USER_HANDLE))) {
|
||||
entries << entry;
|
||||
}
|
||||
}
|
||||
@@ -1458,17 +1472,35 @@ QJsonObject BrowserService::getPasskeyError(int errorCode) const
|
||||
bool BrowserService::handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const bool omitWwwSubdomain)
|
||||
const bool omitWwwSubdomain,
|
||||
const bool allowWildcards)
|
||||
{
|
||||
if (entryUrl.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isWildcardUrl = false;
|
||||
auto tempUrl = entryUrl;
|
||||
|
||||
// Allows matching with exact URL and wildcards
|
||||
if (allowWildcards) {
|
||||
// Exact match where URL is wrapped inside " characters
|
||||
if (entryUrl.startsWith("\"") && entryUrl.endsWith("\"")) {
|
||||
return QStringView{entryUrl}.mid(1, entryUrl.length() - 2) == siteUrl;
|
||||
}
|
||||
|
||||
// Replace wildcards
|
||||
isWildcardUrl = entryUrl.contains("*");
|
||||
if (isWildcardUrl) {
|
||||
tempUrl = tempUrl.replace("*", UrlTools::URL_WILDCARD);
|
||||
}
|
||||
}
|
||||
|
||||
QUrl entryQUrl;
|
||||
if (entryUrl.contains("://")) {
|
||||
entryQUrl = entryUrl;
|
||||
entryQUrl = tempUrl;
|
||||
} else {
|
||||
entryQUrl = QUrl::fromUserInput(entryUrl);
|
||||
entryQUrl = QUrl::fromUserInput(tempUrl);
|
||||
|
||||
if (browserSettings()->matchUrlScheme()) {
|
||||
entryQUrl.setScheme("https");
|
||||
@@ -1508,6 +1540,11 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use wildcard matching instead
|
||||
if (isWildcardUrl) {
|
||||
return handleURLWithWildcards(entryQUrl, siteUrl);
|
||||
}
|
||||
|
||||
// Match the base domain
|
||||
if (urlTools()->getBaseDomainFromUrl(siteQUrl.host()) != urlTools()->getBaseDomainFromUrl(entryQUrl.host())) {
|
||||
return false;
|
||||
@@ -1521,6 +1558,46 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BrowserService::handleURLWithWildcards(const QUrl& entryQUrl, const QString& siteUrl)
|
||||
{
|
||||
auto matchWithRegex = [&](QString firstPart, const QString& secondPart, bool hostnameUsed = false) {
|
||||
if (firstPart == secondPart) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there's no wildcard with hostname, just compare directly
|
||||
if (hostnameUsed && !firstPart.contains(UrlTools::URL_WILDCARD) && firstPart != secondPart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Escape illegal characters
|
||||
auto re = firstPart.replace(QRegularExpression(R"(([!\^\$\+\-\(\)@<>]))"), "\\\\1");
|
||||
|
||||
if (hostnameUsed) {
|
||||
// Replace all host parts with wildcards
|
||||
re = re.replace(QString("%1.").arg(UrlTools::URL_WILDCARD), "(.*?)");
|
||||
}
|
||||
|
||||
// Append a + to the end of regex to match all paths after the last asterisk
|
||||
if (re.endsWith(UrlTools::URL_WILDCARD)) {
|
||||
re.append("+");
|
||||
}
|
||||
|
||||
// Replace any remaining wildcards for paths
|
||||
re = re.replace(UrlTools::URL_WILDCARD, "(.*?)");
|
||||
return QRegularExpression(re).match(secondPart).hasMatch();
|
||||
};
|
||||
|
||||
// Match hostname and path
|
||||
QUrl siteQUrl = siteUrl;
|
||||
if (!matchWithRegex(entryQUrl.host(), siteQUrl.host(), true)
|
||||
|| !matchWithRegex(entryQUrl.path(), siteQUrl.path())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
|
||||
{
|
||||
if (!rootGroupUuid.isNull()) {
|
||||
@@ -1677,6 +1754,15 @@ void BrowserService::activeDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
m_currentDatabaseWidget = dbWidget;
|
||||
}
|
||||
|
||||
void BrowserService::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
|
||||
{
|
||||
// User canceled the database open dialog
|
||||
if (dbWidget && !accepted && m_bringToFrontRequested) {
|
||||
m_bringToFrontRequested = false;
|
||||
hideWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
||||
{
|
||||
auto clientID = message["clientID"].toString();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
*
|
||||
@@ -132,6 +132,7 @@ public:
|
||||
static const QString OPTION_ONLY_HTTP_AUTH;
|
||||
static const QString OPTION_NOT_HTTP_AUTH;
|
||||
static const QString OPTION_OMIT_WWW;
|
||||
static const QString ADDITIONAL_URL;
|
||||
static const QString OPTION_RESTRICT_KEY;
|
||||
|
||||
signals:
|
||||
@@ -145,6 +146,7 @@ public slots:
|
||||
|
||||
private slots:
|
||||
void processClientMessage(QLocalSocket* socket, const QJsonObject& message);
|
||||
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||
|
||||
private:
|
||||
enum Access
|
||||
@@ -198,7 +200,9 @@ private:
|
||||
bool handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const bool omitWwwSubdomain = false);
|
||||
const bool omitWwwSubdomain = false,
|
||||
const bool allowWildcards = false);
|
||||
bool handleURLWithWildcards(const QUrl& entryQUrl, const QString& siteUrl);
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
void hideWindow() const;
|
||||
|
||||
@@ -178,7 +178,7 @@ void BrowserSettingsWidget::loadSettings()
|
||||
QString BrowserSettingsWidget::resolveCustomProxyLocation()
|
||||
{
|
||||
auto settings = browserSettings();
|
||||
auto proxyLocation = m_ui->customProxyLocation->text();
|
||||
auto proxyLocation = m_ui->customProxyLocation->text().trimmed();
|
||||
proxyLocation = settings->replaceTildeHomePath(proxyLocation);
|
||||
return proxyLocation;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="snapWarningLabel">
|
||||
<property name="text">
|
||||
<string>Browsers installed as snaps are currently not supported.</string>
|
||||
<string>Browsers installed using Snap or Flatpak are not supported with exception to Firefox installed using Snap.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "PasskeyUtils.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#include "BrowserPasskeys.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/UrlTools.h"
|
||||
|
||||
@@ -389,9 +390,9 @@ QString PasskeyUtils::getCredentialIdFromEntry(const Entry* entry) const
|
||||
return {};
|
||||
}
|
||||
|
||||
return entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID)
|
||||
? entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID)
|
||||
: entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID);
|
||||
return entry->attributes()->hasKey(EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID)
|
||||
? entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID)
|
||||
: entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID);
|
||||
}
|
||||
|
||||
// For compatibility with StrongBox (and other possible clients in the future)
|
||||
@@ -401,7 +402,7 @@ QString PasskeyUtils::getUsernameFromEntry(const Entry* entry) const
|
||||
return {};
|
||||
}
|
||||
|
||||
return entry->attributes()->hasKey(BrowserPasskeys::KPXC_PASSKEY_USERNAME)
|
||||
? entry->attributes()->value(BrowserPasskeys::KPXC_PASSKEY_USERNAME)
|
||||
: entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME);
|
||||
return entry->attributes()->hasKey(EntryAttributes::KPXC_PASSKEY_USERNAME)
|
||||
? entry->attributes()->value(EntryAttributes::KPXC_PASSKEY_USERNAME)
|
||||
: entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USERNAME);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
#include "Utils.h"
|
||||
#include "core/Global.h"
|
||||
#include "format/CsvExporter.h"
|
||||
#include "format/HtmlExporter.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
|
||||
const QCommandLineOption Export::FormatOption = QCommandLineOption(
|
||||
QStringList() << "f" << "format",
|
||||
QObject::tr("Format to use when exporting. Available choices are 'xml' or 'csv'. Defaults to 'xml'."),
|
||||
QStringLiteral("xml|csv"));
|
||||
QObject::tr("Format to use when exporting. Available choices are 'xml', 'csv' or 'html'. Defaults to 'xml'."),
|
||||
QStringLiteral("xml|csv|html"));
|
||||
|
||||
Export::Export()
|
||||
{
|
||||
@@ -53,6 +54,9 @@ int Export::executeWithDatabase(QSharedPointer<Database> database, QSharedPointe
|
||||
} else if (format.startsWith(QStringLiteral("csv"), Qt::CaseInsensitive)) {
|
||||
CsvExporter csvExporter;
|
||||
out << csvExporter.exportDatabase(database);
|
||||
} else if (format.startsWith(QStringLiteral("html"), Qt::CaseInsensitive)) {
|
||||
HtmlExporter htmlExporter;
|
||||
out << htmlExporter.exportDatabase(database);
|
||||
} else {
|
||||
err << QObject::tr("Unsupported format %1").arg(format) << Qt::endl;
|
||||
return EXIT_FAILURE;
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace Utils
|
||||
QTextStream STDIN;
|
||||
QTextStream DEVNULL;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
UINT origCodePage;
|
||||
UINT origOutputCodePage;
|
||||
#endif
|
||||
|
||||
void setDefaultTextStreams()
|
||||
{
|
||||
auto fd = new QFile();
|
||||
@@ -66,6 +71,9 @@ namespace Utils
|
||||
DEVNULL.setDevice(fd);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
origCodePage = GetConsoleCP();
|
||||
origOutputCodePage = GetConsoleOutputCP();
|
||||
|
||||
// On Windows, we ask via keepassxc-cli.exe.manifest to use UTF-8,
|
||||
// but the console code-page isn't automatically changed to match.
|
||||
SetConsoleCP(GetACP());
|
||||
@@ -73,6 +81,14 @@ namespace Utils
|
||||
#endif
|
||||
}
|
||||
|
||||
void resetTextStreams()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
SetConsoleCP(origCodePage);
|
||||
SetConsoleOutputCP(origOutputCodePage);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setStdinEcho(bool enable = true)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -210,7 +226,7 @@ namespace Utils
|
||||
#ifdef __AFL_COMPILER
|
||||
// Fuzz test build takes password from environment variable to
|
||||
// allow non-interactive operation
|
||||
const auto env = getenv("KEYPASSXC_AFL_PASSWORD");
|
||||
const auto env = getenv("KEEPASSXC_AFL_PASSWORD");
|
||||
return env ? env : "";
|
||||
#else
|
||||
auto& in = STDIN;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Utils
|
||||
static const QStringList EntryFieldNames(QStringList() << UuidFieldName << TagsFieldName);
|
||||
|
||||
void setDefaultTextStreams();
|
||||
void resetTextStreams();
|
||||
|
||||
void setStdinEcho(bool enable);
|
||||
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey);
|
||||
|
||||
@@ -181,6 +181,8 @@ int main(int argc, char** argv)
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION);
|
||||
// Cleanup code pages after cli exits
|
||||
QObject::connect(&app, &QCoreApplication::destroyed, &app, [] { Utils::resetTextStreams(); });
|
||||
|
||||
Bootstrap::bootstrap(config()->get(Config::GUI_Language).toString());
|
||||
Utils::setDefaultTextStreams();
|
||||
@@ -218,7 +220,9 @@ int main(int argc, char** argv)
|
||||
// Switch to parser.showVersion() when available (QT 5.4).
|
||||
out << KEEPASSXC_VERSION << Qt::endl;
|
||||
return EXIT_SUCCESS;
|
||||
} else if (parser.isSet(debugInfoOption)) {
|
||||
}
|
||||
|
||||
if (parser.isSet(debugInfoOption)) {
|
||||
QString debugInfo = Tools::debugInfo().append("\n").append(Crypto::debugInfo());
|
||||
out << debugInfo << Qt::endl;
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@@ -66,6 +66,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::UseDirectWriteSaves,{QS("UseDirectWriteSaves"), Local, false}},
|
||||
{Config::SearchLimitGroup,{QS("SearchLimitGroup"), Roaming, false}},
|
||||
{Config::MinimizeOnOpenUrl,{QS("MinimizeOnOpenUrl"), Roaming, false}},
|
||||
{Config::OpenURLOnDoubleClick, {QS("OpenURLOnDoubleClick"), Roaming, true}},
|
||||
{Config::HideWindowOnCopy,{QS("HideWindowOnCopy"), Roaming, false}},
|
||||
{Config::MinimizeOnCopy,{QS("MinimizeOnCopy"), Roaming, true}},
|
||||
{Config::MinimizeAfterUnlock,{QS("MinimizeAfterUnlock"), Roaming, false}},
|
||||
@@ -76,6 +77,8 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::AutoTypeDelay,{QS("AutoTypeDelay"), Roaming, 25}},
|
||||
{Config::AutoTypeStartDelay,{QS("AutoTypeStartDelay"), Roaming, 500}},
|
||||
{Config::AutoTypeHideExpiredEntry,{QS("AutoTypeHideExpiredEntry"), Roaming, false}},
|
||||
{Config::AutoTypeDialogSortColumn,{QS("AutoTypeDialogSortColumn"), Roaming, 0}},
|
||||
{Config::AutoTypeDialogSortOrder,{QS("AutoTypeDialogSortOrder"), Roaming, Qt::AscendingOrder}},
|
||||
{Config::GlobalAutoTypeKey,{QS("GlobalAutoTypeKey"), Roaming, 0}},
|
||||
{Config::GlobalAutoTypeModifiers,{QS("GlobalAutoTypeModifiers"), Roaming, 0}},
|
||||
{Config::GlobalAutoTypeRetypeTime,{QS("GlobalAutoTypeRetypeTime"), Roaming, 15}},
|
||||
@@ -95,6 +98,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::GUI_HideMenubar, {QS("GUI/HideMenubar"), Roaming, false}},
|
||||
{Config::GUI_HideToolbar, {QS("GUI/HideToolbar"), Roaming, false}},
|
||||
{Config::GUI_MovableToolbar, {QS("GUI/MovableToolbar"), Roaming, false}},
|
||||
{Config::GUI_HideGroupPanel, {QS("GUI/HideGroupPanel"), Roaming, false}},
|
||||
{Config::GUI_HidePreviewPanel, {QS("GUI/HidePreviewPanel"), Roaming, false}},
|
||||
{Config::GUI_AlwaysOnTop, {QS("GUI/GUI_AlwaysOnTop"), Local, false}},
|
||||
{Config::GUI_ToolButtonStyle, {QS("GUI/ToolButtonStyle"), Roaming, Qt::ToolButtonIconOnly}},
|
||||
@@ -115,6 +119,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::GUI_CheckForUpdatesIncludeBetas, {QS("GUI/CheckForUpdatesIncludeBetas"), Roaming, false}},
|
||||
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlock, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlock"), Roaming, true}},
|
||||
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlockOffsetDays"), Roaming, 3}},
|
||||
{Config::GUI_FontSizeOffset, {QS("GUI/FontSizeOffset"), Local, 0}},
|
||||
|
||||
{Config::GUI_MainWindowGeometry, {QS("GUI/MainWindowGeometry"), Local, {}}},
|
||||
{Config::GUI_MainWindowState, {QS("GUI/MainWindowState"), Local, {}}},
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
UseDirectWriteSaves,
|
||||
SearchLimitGroup,
|
||||
MinimizeOnOpenUrl,
|
||||
OpenURLOnDoubleClick,
|
||||
HideWindowOnCopy,
|
||||
MinimizeOnCopy,
|
||||
MinimizeAfterUnlock,
|
||||
@@ -59,6 +60,8 @@ public:
|
||||
AutoTypeDelay,
|
||||
AutoTypeStartDelay,
|
||||
AutoTypeHideExpiredEntry,
|
||||
AutoTypeDialogSortColumn,
|
||||
AutoTypeDialogSortOrder,
|
||||
GlobalAutoTypeKey,
|
||||
GlobalAutoTypeModifiers,
|
||||
GlobalAutoTypeRetypeTime,
|
||||
@@ -77,6 +80,7 @@ public:
|
||||
GUI_HideMenubar,
|
||||
GUI_HideToolbar,
|
||||
GUI_MovableToolbar,
|
||||
GUI_HideGroupPanel,
|
||||
GUI_HidePreviewPanel,
|
||||
GUI_AlwaysOnTop,
|
||||
GUI_ToolButtonStyle,
|
||||
@@ -96,6 +100,7 @@ public:
|
||||
GUI_CheckForUpdatesIncludeBetas,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlock,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
|
||||
GUI_FontSizeOffset,
|
||||
|
||||
GUI_MainWindowGeometry,
|
||||
GUI_MainWindowState,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,9 +21,8 @@
|
||||
#include "core/Global.h"
|
||||
|
||||
const QString CustomData::LastModified = QStringLiteral("_LAST_MODIFIED");
|
||||
const QString CustomData::Created = QStringLiteral("_CREATED");
|
||||
const QString CustomData::Created = QStringLiteral("_CREATED_");
|
||||
const QString CustomData::BrowserKeyPrefix = QStringLiteral("KPXC_BROWSER_");
|
||||
const QString CustomData::BrowserLegacyKeyPrefix = QStringLiteral("Public Key: ");
|
||||
const QString CustomData::ExcludeFromReportsLegacy = QStringLiteral("KnownBad");
|
||||
const QString CustomData::FdoSecretsExposedGroup = QStringLiteral("FDO_SECRETS_EXPOSED_GROUP");
|
||||
const QString CustomData::RandomSlug = QStringLiteral("KPXC_RANDOM_SLUG");
|
||||
@@ -52,6 +51,15 @@ QString CustomData::value(const QString& key) const
|
||||
return m_data.value(key).value;
|
||||
}
|
||||
|
||||
QString CustomData::getKeyWithPrefix(const QString& prefix, const QString& key)
|
||||
{
|
||||
QString keyWithPrefix;
|
||||
keyWithPrefix.reserve(prefix.length() + key.length());
|
||||
keyWithPrefix.append(prefix);
|
||||
keyWithPrefix.append(key);
|
||||
return keyWithPrefix;
|
||||
}
|
||||
|
||||
const CustomData::CustomDataItem& CustomData::item(const QString& key) const
|
||||
{
|
||||
auto item = m_data.find(key);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -64,11 +64,12 @@ public:
|
||||
bool operator==(const CustomData& other) const;
|
||||
bool operator!=(const CustomData& other) const;
|
||||
|
||||
static QString getKeyWithPrefix(const QString& prefix, const QString& key);
|
||||
|
||||
// Pre-defined keys
|
||||
static const QString LastModified;
|
||||
static const QString Created;
|
||||
static const QString BrowserKeyPrefix;
|
||||
static const QString BrowserLegacyKeyPrefix;
|
||||
static const QString FdoSecretsExposedGroup;
|
||||
static const QString RandomSlug;
|
||||
static const QString RemoteProgramSettings;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "format/KdbxXmlReader.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "streams/HashingStream.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QJsonObject>
|
||||
@@ -62,8 +63,8 @@ Database::Database()
|
||||
updateTagList();
|
||||
});
|
||||
connect(this, &Database::modified, this, [this] { updateTagList(); });
|
||||
connect(this, &Database::databaseSaved, this, [this]() { updateCommonUsernames(); });
|
||||
connect(m_fileWatcher, &FileWatcher::fileChanged, this, &Database::databaseFileChanged);
|
||||
connect(this, &Database::databaseSaved, this, [this] { updateCommonUsernames(); });
|
||||
connect(m_fileWatcher, &FileWatcher::fileChanged, this, [this] { emit databaseFileChanged(false); });
|
||||
|
||||
// static uuid map
|
||||
s_uuidMap.insert(m_uuid, this);
|
||||
@@ -106,10 +107,6 @@ QUuid Database::uuid() const
|
||||
*/
|
||||
bool Database::open(QSharedPointer<const CompositeKey> key, QString* error)
|
||||
{
|
||||
Q_ASSERT(!m_data.filePath.isEmpty());
|
||||
if (m_data.filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return open(m_data.filePath, std::move(key), error);
|
||||
}
|
||||
|
||||
@@ -127,6 +124,13 @@ bool Database::open(QSharedPointer<const CompositeKey> key, QString* error)
|
||||
*/
|
||||
bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey> key, QString* error)
|
||||
{
|
||||
if (filePath.isEmpty()) {
|
||||
if (error) {
|
||||
*error = tr("No file path was provided.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile dbFile(filePath);
|
||||
if (!dbFile.exists()) {
|
||||
if (error) {
|
||||
@@ -151,6 +155,20 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
|
||||
|
||||
setEmitModified(false);
|
||||
|
||||
// update the hash of the first block
|
||||
m_fileBlockHash.clear();
|
||||
auto fileBlockData = dbFile.peek(kFileBlockToHashSizeBytes);
|
||||
if (fileBlockData.size() != kFileBlockToHashSizeBytes) {
|
||||
if (dbFile.size() >= kFileBlockToHashSizeBytes) {
|
||||
if (error) {
|
||||
*error = tr("Database file read error.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_fileBlockHash = QCryptographicHash::hash(fileBlockData, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
if (!reader.readDatabase(&dbFile, std::move(key), this)) {
|
||||
if (error) {
|
||||
@@ -260,14 +278,33 @@ bool Database::saveAs(const QString& filePath, SaveAction action, const QString&
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filePath == m_data.filePath) {
|
||||
// Fail-safe check to make sure we don't overwrite underlying file changes
|
||||
// that have not yet triggered a file reload/merge operation.
|
||||
if (!m_fileWatcher->hasSameFileChecksum()) {
|
||||
if (error) {
|
||||
*error = tr("Database file has unmerged changes.");
|
||||
// Make sure we don't overwrite external modifications unless explicitly allowed
|
||||
if (!m_ignoreFileChangesUntilSaved && !m_fileBlockHash.isEmpty() && filePath == m_data.filePath) {
|
||||
QFile dbFile(filePath);
|
||||
if (dbFile.exists()) {
|
||||
if (!dbFile.open(QIODevice::ReadOnly)) {
|
||||
if (error) {
|
||||
*error = tr("Unable to open file %1.").arg(filePath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto fileBlockData = dbFile.read(kFileBlockToHashSizeBytes);
|
||||
if (fileBlockData.size() == kFileBlockToHashSizeBytes) {
|
||||
auto hash = QCryptographicHash::hash(fileBlockData, QCryptographicHash::Md5);
|
||||
if (m_fileBlockHash != hash) {
|
||||
if (error) {
|
||||
*error = tr("Database file has unmerged changes.");
|
||||
}
|
||||
// emit the databaseFileChanged(true) signal async
|
||||
QMetaObject::invokeMethod(this, "databaseFileChanged", Qt::QueuedConnection, Q_ARG(bool, true));
|
||||
return false;
|
||||
}
|
||||
} else if (dbFile.size() >= kFileBlockToHashSizeBytes) {
|
||||
if (error) {
|
||||
*error = tr("Database file read error.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +339,7 @@ bool Database::saveAs(const QString& filePath, SaveAction action, const QString&
|
||||
SetFileAttributes(realFilePath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_ignoreFileChangesUntilSaved = false;
|
||||
m_fileWatcher->start(realFilePath, 30, 1);
|
||||
} else {
|
||||
// Saving failed, don't rewatch file since it does not represent our database
|
||||
@@ -325,8 +362,12 @@ bool Database::performSave(const QString& filePath, SaveAction action, const QSt
|
||||
case Atomic: {
|
||||
QSaveFile saveFile(filePath);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
HashingStream hashingStream(&saveFile, QCryptographicHash::Md5, kFileBlockToHashSizeBytes);
|
||||
if (!hashingStream.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
// write the database to the file
|
||||
if (!writeDatabase(&saveFile, error)) {
|
||||
if (!writeDatabase(&hashingStream, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -334,6 +375,9 @@ bool Database::performSave(const QString& filePath, SaveAction action, const QSt
|
||||
saveFile.setFileTime(createTime, QFile::FileBirthTime);
|
||||
|
||||
if (saveFile.commit()) {
|
||||
// store the new hash
|
||||
m_fileBlockHash = hashingStream.hashingResult();
|
||||
|
||||
// successfully saved database file
|
||||
return true;
|
||||
}
|
||||
@@ -347,8 +391,12 @@ bool Database::performSave(const QString& filePath, SaveAction action, const QSt
|
||||
case TempFile: {
|
||||
QTemporaryFile tempFile;
|
||||
if (tempFile.open()) {
|
||||
HashingStream hashingStream(&tempFile, QCryptographicHash::Md5, kFileBlockToHashSizeBytes);
|
||||
if (!hashingStream.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
// write the database to the file
|
||||
if (!writeDatabase(&tempFile, error)) {
|
||||
if (!writeDatabase(&hashingStream, error)) {
|
||||
return false;
|
||||
}
|
||||
tempFile.close(); // flush to disk
|
||||
@@ -366,6 +414,8 @@ bool Database::performSave(const QString& filePath, SaveAction action, const QSt
|
||||
QFile::setPermissions(filePath, perms);
|
||||
// Retain original creation time
|
||||
tempFile.setFileTime(createTime, QFile::FileBirthTime);
|
||||
// store the new hash
|
||||
m_fileBlockHash = hashingStream.hashingResult();
|
||||
return true;
|
||||
} else if (backupFilePath.isEmpty() || !restoreDatabase(filePath, backupFilePath)) {
|
||||
// Failed to copy new database in place, and
|
||||
@@ -387,10 +437,16 @@ bool Database::performSave(const QString& filePath, SaveAction action, const QSt
|
||||
// Open the original database file for direct-write
|
||||
QFile dbFile(filePath);
|
||||
if (dbFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
if (!writeDatabase(&dbFile, error)) {
|
||||
HashingStream hashingStream(&dbFile, QCryptographicHash::Md5, kFileBlockToHashSizeBytes);
|
||||
if (!hashingStream.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
if (!writeDatabase(&hashingStream, error)) {
|
||||
return false;
|
||||
}
|
||||
dbFile.close();
|
||||
// store the new hash
|
||||
m_fileBlockHash = hashingStream.hashingResult();
|
||||
return true;
|
||||
}
|
||||
if (error) {
|
||||
@@ -508,6 +564,9 @@ void Database::releaseData()
|
||||
m_deletedObjects.clear();
|
||||
m_commonUsernames.clear();
|
||||
m_tagList.clear();
|
||||
|
||||
m_fileBlockHash.clear();
|
||||
m_ignoreFileChangesUntilSaved = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,10 +703,33 @@ void Database::setFilePath(const QString& filePath)
|
||||
m_data.filePath = filePath;
|
||||
// Don't watch for changes until the next open or save operation
|
||||
m_fileWatcher->stop();
|
||||
m_ignoreFileChangesUntilSaved = false;
|
||||
emit filePathChanged(oldPath, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const QByteArray& Database::fileBlockHash() const
|
||||
{
|
||||
return m_fileBlockHash;
|
||||
}
|
||||
|
||||
void Database::setIgnoreFileChangesUntilSaved(bool ignore)
|
||||
{
|
||||
if (m_ignoreFileChangesUntilSaved != ignore) {
|
||||
m_ignoreFileChangesUntilSaved = ignore;
|
||||
if (ignore) {
|
||||
m_fileWatcher->pause();
|
||||
} else {
|
||||
m_fileWatcher->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Database::ignoreFileChangesUntilSaved() const
|
||||
{
|
||||
return m_ignoreFileChangesUntilSaved;
|
||||
}
|
||||
|
||||
QList<DeletedObject> Database::deletedObjects()
|
||||
{
|
||||
return m_deletedObjects;
|
||||
@@ -1051,6 +1133,54 @@ QUuid Database::publicUuid()
|
||||
return QUuid::fromRfc4122(publicCustomData()["KPXC_PUBLIC_UUID"].toByteArray());
|
||||
}
|
||||
|
||||
QString Database::publicName()
|
||||
{
|
||||
return publicCustomData().value("KPXC_PUBLIC_NAME").toString();
|
||||
}
|
||||
|
||||
void Database::setPublicName(const QString& name)
|
||||
{
|
||||
if (name.isEmpty()) {
|
||||
publicCustomData().remove("KPXC_PUBLIC_NAME");
|
||||
} else {
|
||||
publicCustomData().insert("KPXC_PUBLIC_NAME", name);
|
||||
}
|
||||
markAsModified();
|
||||
}
|
||||
|
||||
QString Database::publicColor()
|
||||
{
|
||||
return publicCustomData().value("KPXC_PUBLIC_COLOR").toString();
|
||||
}
|
||||
|
||||
void Database::setPublicColor(const QString& color)
|
||||
{
|
||||
if (color.isEmpty()) {
|
||||
publicCustomData().remove("KPXC_PUBLIC_COLOR");
|
||||
} else {
|
||||
publicCustomData().insert("KPXC_PUBLIC_COLOR", color);
|
||||
}
|
||||
markAsModified();
|
||||
}
|
||||
|
||||
int Database::publicIcon()
|
||||
{
|
||||
if (publicCustomData().contains("KPXC_PUBLIC_ICON")) {
|
||||
return publicCustomData().value("KPXC_PUBLIC_ICON").toInt();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Database::setPublicIcon(int iconIndex)
|
||||
{
|
||||
if (iconIndex < 0) {
|
||||
publicCustomData().remove("KPXC_PUBLIC_ICON");
|
||||
} else {
|
||||
publicCustomData().insert("KPXC_PUBLIC_ICON", iconIndex);
|
||||
}
|
||||
markAsModified();
|
||||
}
|
||||
|
||||
void Database::markAsTemporaryDatabase()
|
||||
{
|
||||
m_isTemporaryDatabase = true;
|
||||
|
||||
@@ -75,6 +75,9 @@ public:
|
||||
~Database() override;
|
||||
|
||||
private:
|
||||
// size of the block of file data to hash for detecting external changes
|
||||
static const quint32 kFileBlockToHashSizeBytes = 1024; // 1 KiB
|
||||
|
||||
bool writeDatabase(QIODevice* device, QString* error = nullptr);
|
||||
bool backupDatabase(const QString& filePath, const QString& destinationFilePath);
|
||||
bool restoreDatabase(const QString& filePath, const QString& fromBackupFilePath);
|
||||
@@ -108,6 +111,17 @@ public:
|
||||
QString canonicalFilePath() const;
|
||||
void setFilePath(const QString& filePath);
|
||||
|
||||
const QByteArray& fileBlockHash() const;
|
||||
void setIgnoreFileChangesUntilSaved(bool ignore);
|
||||
bool ignoreFileChangesUntilSaved() const;
|
||||
|
||||
QString publicName();
|
||||
void setPublicName(const QString& name);
|
||||
QString publicColor();
|
||||
void setPublicColor(const QString& color);
|
||||
int publicIcon();
|
||||
void setPublicIcon(int iconIndex);
|
||||
|
||||
Metadata* metadata();
|
||||
const Metadata* metadata() const;
|
||||
Group* rootGroup();
|
||||
@@ -174,7 +188,7 @@ signals:
|
||||
void databaseOpened();
|
||||
void databaseSaved();
|
||||
void databaseDiscarded();
|
||||
void databaseFileChanged();
|
||||
void databaseFileChanged(bool triggeredBySave);
|
||||
void databaseNonDataChanged();
|
||||
void tagListUpdated();
|
||||
|
||||
@@ -226,6 +240,8 @@ private:
|
||||
void startModifiedTimer();
|
||||
void stopModifiedTimer();
|
||||
|
||||
QByteArray m_fileBlockHash;
|
||||
bool m_ignoreFileChangesUntilSaved;
|
||||
QPointer<Metadata> const m_metadata;
|
||||
DatabaseData m_data;
|
||||
QPointer<Group> m_rootGroup;
|
||||
|
||||
@@ -381,16 +381,32 @@ QString Entry::url() const
|
||||
return m_attributes->value(EntryAttributes::URLKey);
|
||||
}
|
||||
|
||||
QString Entry::resolveUrl() const
|
||||
{
|
||||
const auto entryUrl = url();
|
||||
if (entryUrl.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return EntryAttributes::matchReference(entryUrl).hasMatch() ? resolveMultiplePlaceholders(entryUrl) : entryUrl;
|
||||
}
|
||||
|
||||
QStringList Entry::getAllUrls() const
|
||||
{
|
||||
QStringList urlList;
|
||||
auto entryUrl = url();
|
||||
|
||||
const auto entryUrl = resolveUrl();
|
||||
if (!entryUrl.isEmpty()) {
|
||||
urlList << (EntryAttributes::matchReference(entryUrl).hasMatch() ? resolveMultiplePlaceholders(entryUrl)
|
||||
: entryUrl);
|
||||
urlList << entryUrl;
|
||||
}
|
||||
|
||||
return urlList << getAdditionalUrls();
|
||||
}
|
||||
|
||||
QStringList Entry::getAdditionalUrls() const
|
||||
{
|
||||
QStringList urlList;
|
||||
|
||||
for (const auto& key : m_attributes->keys()) {
|
||||
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|
||||
|| key == QString("%1_RELYING_PARTY").arg(EntryAttributes::PasskeyAttribute)) {
|
||||
@@ -460,6 +476,12 @@ bool Entry::willExpireInDays(int days) const
|
||||
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTime().addDays(days);
|
||||
}
|
||||
|
||||
void Entry::expireNow()
|
||||
{
|
||||
setExpiryTime(Clock::currentDateTimeUtc());
|
||||
setExpires(true);
|
||||
}
|
||||
|
||||
bool Entry::isRecycled() const
|
||||
{
|
||||
const Database* db = database();
|
||||
@@ -1030,9 +1052,9 @@ void Entry::updateModifiedSinceBegin()
|
||||
|
||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
||||
{
|
||||
static const QRegularExpression placeholderRegEx(R"(\{[^}]+\})");
|
||||
static const QRegularExpression placeholderRegEx("({(?>[^{}]+?|(?1))+?})");
|
||||
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return str;
|
||||
}
|
||||
@@ -1043,7 +1065,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
|
||||
while (matches.hasNext()) {
|
||||
const auto match = matches.next();
|
||||
result += str.midRef(capEnd, match.capturedStart() - capEnd);
|
||||
result += resolvePlaceholderRecursive(match.captured(), maxDepth - 1);
|
||||
result += resolvePlaceholderRecursive(match.captured(), maxDepth);
|
||||
capEnd = match.capturedEnd();
|
||||
}
|
||||
result += str.rightRef(str.length() - capEnd);
|
||||
@@ -1052,7 +1074,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
|
||||
|
||||
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return placeholder;
|
||||
}
|
||||
@@ -1060,21 +1082,20 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
|
||||
switch (typeOfPlaceholder) {
|
||||
case PlaceholderType::NotPlaceholder:
|
||||
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth);
|
||||
case PlaceholderType::Unknown: {
|
||||
return "{" % resolveMultiplePlaceholdersRecursive(placeholder.mid(1, placeholder.length() - 2), maxDepth - 1)
|
||||
% "}";
|
||||
return "{" % resolveMultiplePlaceholdersRecursive(placeholder.mid(1, placeholder.length() - 2), maxDepth) % "}";
|
||||
}
|
||||
case PlaceholderType::Title:
|
||||
return resolveMultiplePlaceholdersRecursive(title(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(title(), maxDepth);
|
||||
case PlaceholderType::UserName:
|
||||
return resolveMultiplePlaceholdersRecursive(username(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(username(), maxDepth);
|
||||
case PlaceholderType::Password:
|
||||
return resolveMultiplePlaceholdersRecursive(password(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(password(), maxDepth);
|
||||
case PlaceholderType::Notes:
|
||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth);
|
||||
case PlaceholderType::Url:
|
||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||
case PlaceholderType::DbDir: {
|
||||
QFileInfo fileInfo(database()->filePath());
|
||||
return fileInfo.absoluteDir().absolutePath();
|
||||
@@ -1089,7 +1110,7 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
case PlaceholderType::UrlUserInfo:
|
||||
case PlaceholderType::UrlUserName:
|
||||
case PlaceholderType::UrlPassword: {
|
||||
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
||||
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
|
||||
}
|
||||
case PlaceholderType::Totp:
|
||||
@@ -1097,10 +1118,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
return totp();
|
||||
case PlaceholderType::CustomAttribute: {
|
||||
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
|
||||
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
|
||||
return attributes()->hasKey(key) ? resolveMultiplePlaceholdersRecursive(attributes()->value(key), maxDepth)
|
||||
: QString();
|
||||
}
|
||||
case PlaceholderType::Reference:
|
||||
return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
|
||||
return resolveReferencePlaceholderRecursive(placeholder, ++maxDepth);
|
||||
case PlaceholderType::DateTimeSimple:
|
||||
case PlaceholderType::DateTimeYear:
|
||||
case PlaceholderType::DateTimeMonth:
|
||||
@@ -1115,7 +1137,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
case PlaceholderType::DateTimeUtcHour:
|
||||
case PlaceholderType::DateTimeUtcMinute:
|
||||
case PlaceholderType::DateTimeUtcSecond:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveDateTimePlaceholder(typeOfPlaceholder), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(resolveDateTimePlaceholder(typeOfPlaceholder), maxDepth);
|
||||
case PlaceholderType::Conversion:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveConversionPlaceholder(placeholder), maxDepth);
|
||||
case PlaceholderType::Regex:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveRegexPlaceholder(placeholder), maxDepth);
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
@@ -1164,9 +1190,93 @@ QString Entry::resolveDateTimePlaceholder(Entry::PlaceholderType placeholderType
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveConversionPlaceholder(const QString& str, QString* error) const
|
||||
{
|
||||
if (error) {
|
||||
error->clear();
|
||||
}
|
||||
|
||||
// Extract the inner conversion from the placeholder
|
||||
QRegularExpression conversionRegEx("^{?t-conv:(.*)}?$", QRegularExpression::CaseInsensitiveOption);
|
||||
auto placeholder = conversionRegEx.match(str).captured(1);
|
||||
if (!placeholder.isEmpty()) {
|
||||
// Determine the separator character and split, include empty groups
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else {
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion type: %1").arg(type);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(str);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveRegexPlaceholder(const QString& str, QString* error) const
|
||||
{
|
||||
if (error) {
|
||||
error->clear();
|
||||
}
|
||||
|
||||
// Extract the inner regex from the placeholder
|
||||
QRegularExpression conversionRegEx("^{?t-replace-rx:(.*)}?$", QRegularExpression::CaseInsensitiveOption);
|
||||
auto placeholder = conversionRegEx.match(str).captured(1);
|
||||
if (!placeholder.isEmpty()) {
|
||||
// Determine the separator character and split, include empty groups
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \\<num> to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
|
||||
|
||||
auto searchRegex = QRegularExpression(resolvedSearch);
|
||||
if (!searchRegex.isValid()) {
|
||||
if (error) {
|
||||
*error =
|
||||
tr("Invalid regular expression syntax %1\n%2").arg(resolvedSearch, searchRegex.errorString());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
return resolvedText.replace(searchRegex, resolvedReplace);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(str);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return placeholder;
|
||||
}
|
||||
@@ -1194,7 +1304,7 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
|
||||
// Referencing fields of other entries only works with standard fields, not with custom user strings.
|
||||
// If you want to reference a custom user string, you need to place a redirection in a standard field
|
||||
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1369,15 +1479,21 @@ QString Entry::resolveUrlPlaceholder(const QString& str, Entry::PlaceholderType
|
||||
|
||||
Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
|
||||
{
|
||||
if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
|
||||
if (!placeholder.startsWith(QStringLiteral("{")) || !placeholder.endsWith(QStringLiteral("}"))) {
|
||||
return PlaceholderType::NotPlaceholder;
|
||||
}
|
||||
if (placeholder.startsWith(QLatin1Literal("{S:"))) {
|
||||
if (placeholder.startsWith(QStringLiteral("{S:"))) {
|
||||
return PlaceholderType::CustomAttribute;
|
||||
}
|
||||
if (placeholder.startsWith(QLatin1Literal("{REF:"))) {
|
||||
if (placeholder.startsWith(QStringLiteral("{REF:"))) {
|
||||
return PlaceholderType::Reference;
|
||||
}
|
||||
if (placeholder.startsWith(QStringLiteral("{T-CONV:"), Qt::CaseInsensitive)) {
|
||||
return PlaceholderType::Conversion;
|
||||
}
|
||||
if (placeholder.startsWith(QStringLiteral("{T-REPLACE-RX:"), Qt::CaseInsensitive)) {
|
||||
return PlaceholderType::Regex;
|
||||
}
|
||||
|
||||
static const QMap<QString, PlaceholderType> placeholders{
|
||||
{QStringLiteral("{TITLE}"), PlaceholderType::Title},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -100,7 +100,9 @@ public:
|
||||
const AutoTypeAssociations* autoTypeAssociations() const;
|
||||
QString title() const;
|
||||
QString url() const;
|
||||
QString resolveUrl() const;
|
||||
QStringList getAllUrls() const;
|
||||
QStringList getAdditionalUrls() const;
|
||||
QString webUrl() const;
|
||||
QString displayUrl() const;
|
||||
QString username() const;
|
||||
@@ -126,6 +128,7 @@ public:
|
||||
bool hasTotp() const;
|
||||
bool isExpired() const;
|
||||
bool willExpireInDays(int days) const;
|
||||
void expireNow();
|
||||
bool isRecycled() const;
|
||||
bool isAttributeReference(const QString& key) const;
|
||||
bool isAttributeReferenceOf(const QString& key, const QUuid& uuid) const;
|
||||
@@ -225,7 +228,9 @@ public:
|
||||
DateTimeUtcHour,
|
||||
DateTimeUtcMinute,
|
||||
DateTimeUtcSecond,
|
||||
DbDir
|
||||
DbDir,
|
||||
Conversion,
|
||||
Regex
|
||||
};
|
||||
|
||||
static const int DefaultIconNumber;
|
||||
@@ -244,6 +249,8 @@ public:
|
||||
QString resolvePlaceholder(const QString& str) const;
|
||||
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
||||
QString resolveDateTimePlaceholder(PlaceholderType placeholderType) const;
|
||||
QString resolveConversionPlaceholder(const QString& str, QString* error = nullptr) const;
|
||||
QString resolveRegexPlaceholder(const QString& str, QString* error = nullptr) const;
|
||||
PlaceholderType placeholderType(const QString& placeholder) const;
|
||||
QString resolveUrl(const QString& url) const;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QScopeGuard>
|
||||
#include <QSet>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUrl>
|
||||
@@ -236,8 +237,10 @@ bool EntryAttachments::openAttachment(const QString& key, QString* errorMessage)
|
||||
const bool saveOk = tmpFile.open() && tmpFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner)
|
||||
&& tmpFile.write(attachmentData) == attachmentData.size() && tmpFile.flush();
|
||||
|
||||
if (!saveOk && errorMessage) {
|
||||
*errorMessage = QString("%1 - %2").arg(key, tmpFile.errorString());
|
||||
if (!saveOk) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QString("%1 - %2").arg(key, tmpFile.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -250,15 +253,35 @@ bool EntryAttachments::openAttachment(const QString& key, QString* errorMessage)
|
||||
watcher->start(tmpFile.fileName(), 5);
|
||||
connect(watcher.data(), &FileWatcher::fileChanged, this, &EntryAttachments::attachmentFileModified);
|
||||
m_attachmentFileWatchers.insert(tmpFile.fileName(), watcher);
|
||||
} else if (auto path = m_openedAttachments.value(key); m_attachmentFileWatchers.contains(path)) {
|
||||
// If we are already watching an open attachment file, overwrite it with the information from the entry
|
||||
auto watcher = m_attachmentFileWatchers.value(path);
|
||||
watcher->stop();
|
||||
|
||||
QFile file(path);
|
||||
auto finally = qScopeGuard([&file, &watcher, &path] {
|
||||
file.close();
|
||||
watcher->start(path, 5);
|
||||
});
|
||||
|
||||
const auto attachmentData = value(key);
|
||||
const bool saveOk = file.open(QIODevice::WriteOnly) && file.setPermissions(QFile::ReadOwner | QFile::WriteOwner)
|
||||
&& file.write(attachmentData) == attachmentData.size() && file.flush();
|
||||
|
||||
if (!saveOk) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QString("%1 - %2").arg(key, file.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool openOk = QDesktopServices::openUrl(QUrl::fromLocalFile(m_openedAttachments.value(key)));
|
||||
if (!openOk && errorMessage) {
|
||||
*errorMessage = tr("Cannot open file \"%1\"").arg(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return openOk;
|
||||
}
|
||||
|
||||
void EntryAttachments::attachmentFileModified(const QString& path)
|
||||
|
||||
@@ -36,7 +36,20 @@ const QString EntryAttributes::SearchTextGroupName = "SearchText";
|
||||
|
||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||
const QString EntryAttributes::AdditionalUrlAttribute = "KP2A_URL";
|
||||
|
||||
// Passkey related attributes
|
||||
const QString EntryAttributes::PasskeyAttribute = "KPEX_PASSKEY";
|
||||
const QString EntryAttributes::KPEX_PASSKEY_USERNAME = QStringLiteral("KPEX_PASSKEY_USERNAME");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID = QStringLiteral("KPEX_PASSKEY_CREDENTIAL_ID");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM = QStringLiteral("KPEX_PASSKEY_PRIVATE_KEY_PEM");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_RELYING_PARTY = QStringLiteral("KPEX_PASSKEY_RELYING_PARTY");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_USER_HANDLE = QStringLiteral("KPEX_PASSKEY_USER_HANDLE");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START = QStringLiteral("-----BEGIN PRIVATE KEY-----");
|
||||
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END = QStringLiteral("-----END PRIVATE KEY-----");
|
||||
|
||||
// For compatibility with StrongBox
|
||||
const QString EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID = QStringLiteral("KPEX_PASSKEY_GENERATED_USER_ID");
|
||||
const QString EntryAttributes::KPXC_PASSKEY_USERNAME = QStringLiteral("KPXC_PASSKEY_USERNAME");
|
||||
|
||||
EntryAttributes::EntryAttributes(QObject* parent)
|
||||
: ModifiableObject(parent)
|
||||
|
||||
@@ -64,7 +64,18 @@ public:
|
||||
static const QStringList DefaultAttributes;
|
||||
static const QString RememberCmdExecAttr;
|
||||
static const QString AdditionalUrlAttribute;
|
||||
|
||||
static const QString PasskeyAttribute;
|
||||
static const QString KPXC_PASSKEY_USERNAME;
|
||||
static const QString KPEX_PASSKEY_USERNAME;
|
||||
static const QString KPEX_PASSKEY_CREDENTIAL_ID;
|
||||
static const QString KPEX_PASSKEY_GENERATED_USER_ID;
|
||||
static const QString KPEX_PASSKEY_PRIVATE_KEY_PEM;
|
||||
static const QString KPEX_PASSKEY_RELYING_PARTY;
|
||||
static const QString KPEX_PASSKEY_USER_HANDLE;
|
||||
static const QString KPEX_PASSKEY_PRIVATE_KEY_START;
|
||||
static const QString KPEX_PASSKEY_PRIVATE_KEY_END;
|
||||
|
||||
static bool isDefaultAttribute(const QString& key);
|
||||
static bool isPasskeyAttribute(const QString& key);
|
||||
|
||||
|
||||
@@ -79,17 +79,18 @@ void FileWatcher::stop()
|
||||
m_fileChecksum.clear();
|
||||
m_fileChecksumTimer.stop();
|
||||
m_fileChangeDelayTimer.stop();
|
||||
m_paused = false;
|
||||
}
|
||||
|
||||
void FileWatcher::pause()
|
||||
{
|
||||
m_ignoreFileChange = true;
|
||||
m_paused = true;
|
||||
m_fileChangeDelayTimer.stop();
|
||||
}
|
||||
|
||||
void FileWatcher::resume()
|
||||
{
|
||||
m_ignoreFileChange = false;
|
||||
m_paused = false;
|
||||
// Add a short delay to start in the next event loop
|
||||
if (!m_fileIgnoreDelayTimer.isActive()) {
|
||||
m_fileIgnoreDelayTimer.start(0);
|
||||
@@ -98,7 +99,7 @@ void FileWatcher::resume()
|
||||
|
||||
bool FileWatcher::shouldIgnoreChanges()
|
||||
{
|
||||
return m_filePath.isEmpty() || m_ignoreFileChange || m_fileIgnoreDelayTimer.isActive()
|
||||
return m_filePath.isEmpty() || m_ignoreFileChange || m_paused || m_fileIgnoreDelayTimer.isActive()
|
||||
|| m_fileChangeDelayTimer.isActive();
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ void FileWatcher::checkFileChanged()
|
||||
|
||||
AsyncTask::runThenCallback([this] { return calculateChecksum(); },
|
||||
this,
|
||||
[this](QByteArray checksum) {
|
||||
[this](const QByteArray& checksum) {
|
||||
if (checksum != m_fileChecksum) {
|
||||
m_fileChecksum = checksum;
|
||||
m_fileChangeDelayTimer.start(0);
|
||||
|
||||
@@ -56,6 +56,7 @@ private:
|
||||
QTimer m_fileChecksumTimer;
|
||||
int m_fileChecksumSizeBytes = -1;
|
||||
bool m_ignoreFileChange = false;
|
||||
bool m_paused = false;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_FILEWATCHER_H
|
||||
|
||||
@@ -72,6 +72,7 @@ void PassphraseGenerator::setWordList(const QString& path)
|
||||
}
|
||||
|
||||
QTextStream in(&file);
|
||||
in.setCodec("UTF-8");
|
||||
QString line = in.readLine();
|
||||
bool isSigned = line.startsWith("-----BEGIN PGP SIGNED MESSAGE-----");
|
||||
if (isSigned) {
|
||||
@@ -122,6 +123,7 @@ QString PassphraseGenerator::generatePassphrase() const
|
||||
}
|
||||
|
||||
QStringList words;
|
||||
int randomIndex = randomGen()->randomUInt(static_cast<quint32>(m_wordCount));
|
||||
for (int i = 0; i < m_wordCount; ++i) {
|
||||
int wordIndex = randomGen()->randomUInt(static_cast<quint32>(m_wordlist.size()));
|
||||
auto tmpWord = m_wordlist.at(wordIndex);
|
||||
@@ -134,6 +136,9 @@ QString PassphraseGenerator::generatePassphrase() const
|
||||
case TITLECASE:
|
||||
tmpWord = tmpWord.replace(0, 1, tmpWord.left(1).toUpper());
|
||||
break;
|
||||
case MIXEDCASE:
|
||||
tmpWord = i == randomIndex ? tmpWord.toUpper() : tmpWord.toLower();
|
||||
break;
|
||||
case LOWERCASE:
|
||||
tmpWord = tmpWord.toLower();
|
||||
break;
|
||||
|
||||
@@ -30,7 +30,8 @@ public:
|
||||
{
|
||||
LOWERCASE,
|
||||
UPPERCASE,
|
||||
TITLECASE
|
||||
TITLECASE,
|
||||
MIXEDCASE
|
||||
};
|
||||
|
||||
double estimateEntropy(int wordCount = 0);
|
||||
|
||||
@@ -475,4 +475,32 @@ namespace Tools
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
MimeType toMimeType(const QString& mimeName)
|
||||
{
|
||||
static QStringList textFormats = {
|
||||
"text/",
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"application/soap+xml",
|
||||
"application/x-yaml",
|
||||
"application/protobuf",
|
||||
};
|
||||
static QStringList imageFormats = {"image/"};
|
||||
|
||||
static auto isCompatible = [](const QString& format, const QStringList& list) {
|
||||
return std::any_of(
|
||||
list.cbegin(), list.cend(), [&format](const auto& item) { return format.startsWith(item); });
|
||||
};
|
||||
|
||||
if (isCompatible(mimeName, imageFormats)) {
|
||||
return MimeType::Image;
|
||||
}
|
||||
|
||||
if (isCompatible(mimeName, textFormats)) {
|
||||
return MimeType::PlainText;
|
||||
}
|
||||
|
||||
return MimeType::Unknown;
|
||||
}
|
||||
} // namespace Tools
|
||||
|
||||
@@ -114,6 +114,15 @@ namespace Tools
|
||||
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"});
|
||||
|
||||
QString substituteBackupFilePath(QString pattern, const QString& databasePath);
|
||||
|
||||
enum class MimeType : uint8_t
|
||||
{
|
||||
Image,
|
||||
PlainText,
|
||||
Unknown
|
||||
};
|
||||
|
||||
MimeType toMimeType(const QString& mimeName);
|
||||
} // namespace Tools
|
||||
|
||||
#endif // KEEPASSX_TOOLS_H
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -81,6 +81,43 @@ namespace
|
||||
entry->setTotp(Totp::parseSettings(totp));
|
||||
}
|
||||
|
||||
// Parse passkey
|
||||
if (loginMap.contains("fido2Credentials")) {
|
||||
const auto fido2CredentialsMap = loginMap.value("fido2Credentials").toList();
|
||||
for (const auto& fido2Credentials : fido2CredentialsMap) {
|
||||
const auto passkey = fido2Credentials.toMap();
|
||||
|
||||
// Change from UUID to base64 byte array
|
||||
const auto credentialIdValue = passkey.value("credentialId").toString();
|
||||
if (!credentialIdValue.isEmpty()) {
|
||||
const auto credentialUuid = Tools::uuidToHex(credentialIdValue);
|
||||
const auto credentialIdArray = QByteArray::fromHex(credentialUuid.toUtf8());
|
||||
const auto credentialId =
|
||||
credentialIdArray.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
|
||||
}
|
||||
|
||||
// Base64 needs to be changed from URL encoding back to normal, and the result as PEM string
|
||||
const auto keyValue = passkey.value("keyValue").toString();
|
||||
if (!keyValue.isEmpty()) {
|
||||
const auto keyValueArray =
|
||||
QByteArray::fromBase64(keyValue.toUtf8(), QByteArray::Base64UrlEncoding);
|
||||
auto privateKey = keyValueArray.toBase64(QByteArray::Base64Encoding);
|
||||
privateKey.insert(0, EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START.toUtf8());
|
||||
privateKey.append(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END.toUtf8());
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
|
||||
}
|
||||
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USERNAME,
|
||||
passkey.value("userName").toString());
|
||||
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY,
|
||||
passkey.value("rpId").toString());
|
||||
entry->attributes()->set(
|
||||
EntryAttributes::KPEX_PASSKEY_USER_HANDLE, passkey.value("userHandle").toString(), true);
|
||||
entry->addTag(QObject::tr("Passkey"));
|
||||
}
|
||||
}
|
||||
|
||||
// Set the entry url(s)
|
||||
int i = 1;
|
||||
for (const auto& urlObj : loginMap.value("uris").toList()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
@@ -53,7 +53,7 @@ bool CsvParser::reparse()
|
||||
return parseFile();
|
||||
}
|
||||
|
||||
bool CsvParser::parse(QFile* device)
|
||||
bool CsvParser::parse(QIODevice* device)
|
||||
{
|
||||
clear();
|
||||
if (!device) {
|
||||
@@ -66,7 +66,7 @@ bool CsvParser::parse(QFile* device)
|
||||
return parseFile();
|
||||
}
|
||||
|
||||
bool CsvParser::readFile(QFile* device)
|
||||
bool CsvParser::readFile(QIODevice* device)
|
||||
{
|
||||
if (device->isOpen()) {
|
||||
device->close();
|
||||
@@ -79,6 +79,7 @@ bool CsvParser::readFile(QFile* device)
|
||||
} else {
|
||||
device->close();
|
||||
|
||||
// Normalize on newline endings
|
||||
m_array.replace("\r\n", "\n");
|
||||
m_array.replace("\r", "\n");
|
||||
if (m_array.isEmpty()) {
|
||||
@@ -121,7 +122,7 @@ bool CsvParser::parseFile()
|
||||
parseRecord();
|
||||
while (!m_isEof) {
|
||||
if (!skipEndline()) {
|
||||
appendStatusMsg(QObject::tr("malformed string"), true);
|
||||
appendStatusMsg(QObject::tr("malformed string, possible unescaped delimiter"), true);
|
||||
}
|
||||
m_currRow++;
|
||||
m_currCol = 1;
|
||||
@@ -161,7 +162,7 @@ void CsvParser::parseField(CsvRow& row)
|
||||
{
|
||||
QString field;
|
||||
peek(m_ch);
|
||||
if (m_ch != m_separator && m_ch != '\n' && m_ch != '\r') {
|
||||
if (m_ch != m_separator && m_ch != '\n') {
|
||||
if (isQualifier(m_ch)) {
|
||||
parseQuoted(field);
|
||||
} else {
|
||||
@@ -190,7 +191,7 @@ void CsvParser::parseQuoted(QString& s)
|
||||
getChar(m_ch);
|
||||
parseEscaped(s);
|
||||
if (!isQualifier(m_ch)) {
|
||||
appendStatusMsg(QObject::tr("missing closing quote"), true);
|
||||
appendStatusMsg(QObject::tr("missing closing delimiter"), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +392,12 @@ int CsvParser::getCsvRows() const
|
||||
|
||||
void CsvParser::appendStatusMsg(const QString& s, bool isCritical)
|
||||
{
|
||||
m_statusMsg += QObject::tr("%1: (row, col) %2,%3").arg(s, m_currRow, m_currCol).append("\n");
|
||||
if (!m_statusMsg.isEmpty()) {
|
||||
m_statusMsg.append("\n");
|
||||
}
|
||||
|
||||
m_statusMsg +=
|
||||
QObject::tr("%1, row: %2, column: %3").arg(s, QString::number(m_currRow), QString::number(m_currCol));
|
||||
|
||||
m_isGood = !isCritical;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <QBuffer>
|
||||
#include <QTextStream>
|
||||
|
||||
class QFile;
|
||||
class QIODevice;
|
||||
|
||||
typedef QStringList CsvRow;
|
||||
typedef QList<CsvRow> CsvTable;
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
CsvParser();
|
||||
~CsvParser();
|
||||
// read data from device and parse it
|
||||
bool parse(QFile* device);
|
||||
bool parse(QIODevice* device);
|
||||
bool isFileLoaded();
|
||||
// reparse the same buffer (device is not opened again)
|
||||
bool reparse();
|
||||
@@ -85,7 +85,7 @@ private:
|
||||
void parseQuoted(QString& s);
|
||||
void parseEscaped(QString& s);
|
||||
void parseEscapedText(QString& s);
|
||||
bool readFile(QFile* device);
|
||||
bool readFile(QIODevice* device);
|
||||
void reset();
|
||||
void clear();
|
||||
bool skipEndline();
|
||||
|
||||
@@ -17,28 +17,13 @@
|
||||
|
||||
#include "HtmlExporter.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "gui/Icons.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QString PixmapToHTML(const QPixmap& pixmap)
|
||||
{
|
||||
if (pixmap.isNull()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/a/6621278
|
||||
QByteArray a;
|
||||
QBuffer buffer(&a);
|
||||
pixmap.save(&buffer, "PNG");
|
||||
return QString("<img src=\"data:image/png;base64,") + a.toBase64() + "\"/>";
|
||||
}
|
||||
|
||||
QString formatEntry(const Entry& entry)
|
||||
{
|
||||
// Here we collect the table rows with this entry's data fields
|
||||
@@ -127,15 +112,62 @@ QString HtmlExporter::errorString() const
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString HtmlExporter::groupIconToHtml(const Group* /* group */)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString HtmlExporter::entryIconToHtml(const Entry* /* entry */)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
bool HtmlExporter::exportDatabase(QIODevice* device,
|
||||
const QSharedPointer<const Database>& db,
|
||||
bool sorted,
|
||||
bool ascending)
|
||||
{
|
||||
if (device->write(exportHeader(db).toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (db->rootGroup()) {
|
||||
if (device->write(exportGroup(*db->rootGroup(), QString(), sorted, ascending).toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (device->write(exportFooter().toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString HtmlExporter::exportDatabase(const QSharedPointer<const Database>& db, bool sorted, bool ascending)
|
||||
{
|
||||
QString response;
|
||||
|
||||
response = exportHeader(db);
|
||||
if (!response.isEmpty()) {
|
||||
if (db->rootGroup()) {
|
||||
response.append(exportGroup(*db->rootGroup(), QString(), sorted, ascending));
|
||||
}
|
||||
response.append(exportFooter());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QString HtmlExporter::exportHeader(const QSharedPointer<const Database>& db)
|
||||
{
|
||||
const auto meta = db->metadata();
|
||||
if (!meta) {
|
||||
m_error = "Internal error: metadata is NULL";
|
||||
return false;
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto header = QString("<html>"
|
||||
@@ -171,33 +203,23 @@ bool HtmlExporter::exportDatabase(QIODevice* device,
|
||||
+ "</p>"
|
||||
"<p><code>"
|
||||
+ db->filePath().toHtmlEscaped() + "</code></p>");
|
||||
const auto footer = QString("</body>"
|
||||
"</html>");
|
||||
|
||||
if (device->write(header.toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (db->rootGroup()) {
|
||||
if (!writeGroup(*device, *db->rootGroup(), QString(), sorted, ascending)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (device->write(footer.toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return header;
|
||||
}
|
||||
|
||||
bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString path, bool sorted, bool ascending)
|
||||
QString HtmlExporter::exportFooter()
|
||||
{
|
||||
const auto footer = QString("</body>"
|
||||
"</html>");
|
||||
return footer;
|
||||
}
|
||||
|
||||
QString HtmlExporter::exportGroup(const Group& group, QString path, bool sorted, bool ascending)
|
||||
{
|
||||
QString response = "";
|
||||
|
||||
// Don't output the recycle bin
|
||||
if (&group == group.database()->metadata()->recycleBin()) {
|
||||
return true;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
@@ -212,8 +234,11 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
if (!group.entries().empty() || !notes.isEmpty()) {
|
||||
// Header line
|
||||
auto header = QString("<hr><h2>");
|
||||
header.append(PixmapToHTML(Icons::groupIconPixmap(&group, IconSize::Medium)));
|
||||
header.append(" ");
|
||||
auto groupIcon = this->groupIconToHtml(&group);
|
||||
if (!groupIcon.isEmpty()) {
|
||||
header.append(groupIcon);
|
||||
header.append(" ");
|
||||
}
|
||||
header.append(path);
|
||||
header.append("</h2>\n");
|
||||
|
||||
@@ -224,11 +249,8 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
header.append("</p>");
|
||||
}
|
||||
|
||||
// Output it
|
||||
if (device.write(header.toUtf8()) == -1) {
|
||||
m_error = device.errorString();
|
||||
return false;
|
||||
}
|
||||
// Append it to the output
|
||||
response.append(header);
|
||||
}
|
||||
|
||||
// Begin the table for the entries in this group
|
||||
@@ -242,7 +264,7 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
});
|
||||
}
|
||||
|
||||
// Output the entries in this group
|
||||
// Append to the output the entries in this group
|
||||
for (const auto* entry : entries) {
|
||||
auto formatted_entry = formatEntry(*entry);
|
||||
|
||||
@@ -252,7 +274,10 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
// Output it into our table. First the left side with
|
||||
// icon and entry title ...
|
||||
table += "<tr>";
|
||||
table += "<td width=\"1%\">" + PixmapToHTML(Icons::entryIconPixmap(entry, IconSize::Medium)) + "</td>";
|
||||
auto entryIcon = this->entryIconToHtml(entry);
|
||||
if (!entryIcon.isEmpty()) {
|
||||
table += "<td width=\"1%\">" + entryIcon + "</td>";
|
||||
}
|
||||
auto caption = "<caption>" + entry->title().toHtmlEscaped() + "</caption>";
|
||||
|
||||
// ... then the right side with the data fields
|
||||
@@ -261,12 +286,9 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
table += "</tr>";
|
||||
}
|
||||
|
||||
// Output the complete table of this group
|
||||
// Append the complete table of this group to the output
|
||||
table.append("</table>\n");
|
||||
if (device.write(table.toUtf8()) == -1) {
|
||||
m_error = device.errorString();
|
||||
return false;
|
||||
}
|
||||
response.append(table);
|
||||
|
||||
auto children = group.children();
|
||||
if (sorted) {
|
||||
@@ -276,12 +298,12 @@ bool HtmlExporter::writeGroup(QIODevice& device, const Group& group, QString pat
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively output the child groups
|
||||
// Recursively append to the output the child groups
|
||||
for (const auto* child : children) {
|
||||
if (child && !writeGroup(device, *child, path, sorted, ascending)) {
|
||||
return false;
|
||||
if (child) {
|
||||
response.append(exportGroup(*child, path, sorted, ascending));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return response;
|
||||
}
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/Group.h"
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
class QIODevice;
|
||||
@@ -32,18 +34,23 @@ public:
|
||||
const QSharedPointer<const Database>& db,
|
||||
bool sorted = true,
|
||||
bool ascending = true);
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
bool exportDatabase(QIODevice* device,
|
||||
const QSharedPointer<const Database>& db,
|
||||
bool sorted = true,
|
||||
bool ascending = true);
|
||||
bool writeGroup(QIODevice& device,
|
||||
const Group& group,
|
||||
QString path = QString(),
|
||||
bool sorted = true,
|
||||
bool ascending = true);
|
||||
QString exportDatabase(const QSharedPointer<const Database>& db, bool sorted = true, bool ascending = true);
|
||||
QString errorString() const;
|
||||
|
||||
virtual ~HtmlExporter() = default;
|
||||
|
||||
protected:
|
||||
virtual QString groupIconToHtml(const Group* group);
|
||||
virtual QString entryIconToHtml(const Entry* entry);
|
||||
|
||||
private:
|
||||
QString exportGroup(const Group& group, QString path = QString(), bool sorted = true, bool ascending = true);
|
||||
QString exportHeader(const QSharedPointer<const Database>& db);
|
||||
QString exportFooter();
|
||||
|
||||
QString m_error;
|
||||
};
|
||||
@@ -71,7 +71,8 @@ void KdbxWriter::extractDatabase(QByteArray& xmlOutput, Database* db)
|
||||
QBuffer buffer;
|
||||
buffer.setBuffer(&xmlOutput);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
KdbxXmlWriter writer(db->formatVersion());
|
||||
KdbxXmlWriter::BinaryIdxMap idxMap;
|
||||
KdbxXmlWriter writer(db->formatVersion(), idxMap);
|
||||
writer.disableInnerStreamProtection(true);
|
||||
writer.writeDatabase(&buffer, db);
|
||||
}
|
||||
|
||||
221
src/format/ProtonPassReader.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ProtonPassReader.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/Totp.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QMap>
|
||||
#include <QScopedPointer>
|
||||
#include <QUrl>
|
||||
|
||||
namespace
|
||||
{
|
||||
Entry* readItem(const QJsonObject& item)
|
||||
{
|
||||
const auto itemMap = item.toVariantMap();
|
||||
const auto dataMap = itemMap.value("data").toMap();
|
||||
const auto metadataMap = dataMap.value("metadata").toMap();
|
||||
|
||||
// Create entry and assign basic values
|
||||
QScopedPointer<Entry> entry(new Entry());
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(metadataMap.value("name").toString());
|
||||
entry->setNotes(metadataMap.value("note").toString());
|
||||
|
||||
if (itemMap.value("pinned").toBool()) {
|
||||
entry->addTag(QObject::tr("Favorite", "Tag for favorite entries"));
|
||||
}
|
||||
|
||||
// Handle specific item types
|
||||
auto type = dataMap.value("type").toString();
|
||||
|
||||
// Login
|
||||
if (type.compare("login", Qt::CaseInsensitive) == 0) {
|
||||
const auto loginMap = dataMap.value("content").toMap();
|
||||
entry->setUsername(loginMap.value("itemUsername").toString());
|
||||
entry->setPassword(loginMap.value("password").toString());
|
||||
if (loginMap.contains("totpUri")) {
|
||||
auto totp = loginMap.value("totpUri").toString();
|
||||
if (!totp.startsWith("otpauth://")) {
|
||||
QUrl url(QString("otpauth://totp/%1:%2?secret=%3")
|
||||
.arg(QString(QUrl::toPercentEncoding(entry->title())),
|
||||
QString(QUrl::toPercentEncoding(entry->username())),
|
||||
QString(QUrl::toPercentEncoding(totp))));
|
||||
totp = url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
entry->setTotp(Totp::parseSettings(totp));
|
||||
}
|
||||
|
||||
if (loginMap.contains("itemEmail")) {
|
||||
entry->attributes()->set("login_email", loginMap.value("itemEmail").toString());
|
||||
}
|
||||
|
||||
// Set the entry url(s)
|
||||
int i = 1;
|
||||
for (const auto& urlObj : loginMap.value("urls").toList()) {
|
||||
const auto url = urlObj.toString();
|
||||
if (entry->url().isEmpty()) {
|
||||
// First url encountered is set as the primary url
|
||||
entry->setUrl(url);
|
||||
} else {
|
||||
// Subsequent urls
|
||||
entry->attributes()->set(
|
||||
QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i)), url);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Credit Card
|
||||
else if (type.compare("creditCard", Qt::CaseInsensitive) == 0) {
|
||||
const auto cardMap = dataMap.value("content").toMap();
|
||||
entry->setUsername(cardMap.value("number").toString());
|
||||
entry->setPassword(cardMap.value("verificationNumber").toString());
|
||||
const QStringList attrs({"cardholderName", "pin", "expirationDate"});
|
||||
const QStringList sensitive({"pin"});
|
||||
for (const auto& attr : attrs) {
|
||||
auto value = cardMap.value(attr).toString();
|
||||
if (!value.isEmpty()) {
|
||||
entry->attributes()->set("card_" + attr, value, sensitive.contains(attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse extra fields
|
||||
for (const auto& field : dataMap.value("extraFields").toList()) {
|
||||
// Derive a prefix for attribute names using the title or uuid if missing
|
||||
const auto fieldMap = field.toMap();
|
||||
auto name = fieldMap.value("fieldName").toString();
|
||||
if (entry->attributes()->hasKey(name)) {
|
||||
name = QString("%1_%2").arg(name, QUuid::createUuid().toString().mid(1, 5));
|
||||
}
|
||||
|
||||
QString value;
|
||||
const auto fieldType = fieldMap.value("type").toString();
|
||||
if (fieldType.compare("totp", Qt::CaseInsensitive) == 0) {
|
||||
value = fieldMap.value("data").toJsonObject().value("totpUri").toString();
|
||||
} else {
|
||||
value = fieldMap.value("data").toJsonObject().value("content").toString();
|
||||
}
|
||||
|
||||
entry->attributes()->set(name, value, fieldType.compare("hidden", Qt::CaseInsensitive) == 0);
|
||||
}
|
||||
|
||||
// Checked expired/deleted state
|
||||
if (itemMap.value("state").toInt() == 2) {
|
||||
entry->setExpires(true);
|
||||
entry->setExpiryTime(QDateTime::currentDateTimeUtc());
|
||||
}
|
||||
|
||||
// Collapse any accumulated history
|
||||
entry->removeHistoryItems(entry->historyItems());
|
||||
|
||||
// Adjust the created and modified times
|
||||
auto timeInfo = entry->timeInfo();
|
||||
const auto createdTime = QDateTime::fromSecsSinceEpoch(itemMap.value("createTime").toULongLong(), Qt::UTC);
|
||||
const auto modifiedTime = QDateTime::fromSecsSinceEpoch(itemMap.value("modifyTime").toULongLong(), Qt::UTC);
|
||||
timeInfo.setCreationTime(createdTime);
|
||||
timeInfo.setLastModificationTime(modifiedTime);
|
||||
timeInfo.setLastAccessTime(modifiedTime);
|
||||
entry->setTimeInfo(timeInfo);
|
||||
|
||||
return entry.take();
|
||||
}
|
||||
|
||||
void writeVaultToDatabase(const QJsonObject& vault, QSharedPointer<Database> db)
|
||||
{
|
||||
// Create groups from vaults and store a temporary map of id -> uuid
|
||||
const auto vaults = vault.value("vaults").toObject().toVariantMap();
|
||||
for (const auto& vaultId : vaults.keys()) {
|
||||
auto vaultObj = vaults.value(vaultId).toJsonObject();
|
||||
auto group = new Group();
|
||||
group->setUuid(QUuid::createUuid());
|
||||
group->setName(vaultObj.value("name").toString());
|
||||
group->setNotes(vaultObj.value("description").toString());
|
||||
group->setParent(db->rootGroup());
|
||||
|
||||
const auto items = vaultObj.value("items").toArray();
|
||||
for (const auto& item : items) {
|
||||
auto entry = readItem(item.toObject());
|
||||
if (entry) {
|
||||
entry->setGroup(group, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ProtonPassReader::hasError()
|
||||
{
|
||||
return !m_error.isEmpty();
|
||||
}
|
||||
|
||||
QString ProtonPassReader::errorString()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QSharedPointer<Database> ProtonPassReader::convert(const QString& path)
|
||||
{
|
||||
m_error.clear();
|
||||
|
||||
QFileInfo fileinfo(path);
|
||||
if (!fileinfo.exists()) {
|
||||
m_error = QObject::tr("File does not exist.").arg(path);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Bitwarden uses a json file format
|
||||
QFile file(fileinfo.absoluteFilePath());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
m_error = QObject::tr("Cannot open file: %1").arg(file.errorString());
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
auto json = QJsonDocument::fromJson(file.readAll(), &error).object();
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
m_error =
|
||||
QObject::tr("Cannot parse file: %1 at position %2").arg(error.errorString(), QString::number(error.offset));
|
||||
return {};
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (json.value("encrypted").toBool()) {
|
||||
m_error = QObject::tr("Encrypted files are not supported.");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
db->rootGroup()->setName(QObject::tr("Proton Pass Import"));
|
||||
|
||||
writeVaultToDatabase(json, db);
|
||||
|
||||
return db;
|
||||
}
|
||||
43
src/format/ProtonPassReader.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PROTONPASS_READER_H
|
||||
#define PROTONPASS_READER_H
|
||||
|
||||
#include <QSharedPointer>
|
||||
|
||||
class Database;
|
||||
|
||||
/*!
|
||||
* Imports a Proton Pass vault in JSON format: https://proton.me/support/pass-export
|
||||
*/
|
||||
class ProtonPassReader
|
||||
{
|
||||
public:
|
||||
explicit ProtonPassReader() = default;
|
||||
~ProtonPassReader() = default;
|
||||
|
||||
QSharedPointer<Database> convert(const QString& path);
|
||||
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
private:
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
#endif // PROTONPASS_READER_H
|
||||
@@ -44,6 +44,7 @@ namespace
|
||||
{
|
||||
constexpr int WaitTimeoutMSec = 150;
|
||||
const char BlockSizeProperty[] = "blockSize";
|
||||
int g_OriginalFontSize = 0;
|
||||
} // namespace
|
||||
|
||||
Application::Application(int& argc, char** argv)
|
||||
@@ -151,12 +152,7 @@ void Application::bootstrap(const QString& uiLanguage)
|
||||
{
|
||||
Bootstrap::bootstrap(uiLanguage);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Qt on Windows uses "MS Shell Dlg 2" as the default font for many widgets, which resolves
|
||||
// to Tahoma 8pt, whereas the correct font would be "Segoe UI" 9pt.
|
||||
// Apparently, some widgets are already using the correct font. Thanks, MuseScore for this neat fix!
|
||||
QApplication::setFont(QApplication::font("QMessageBox"));
|
||||
#endif
|
||||
applyFontSize();
|
||||
|
||||
osUtils->registerNativeEventFilter();
|
||||
MessageBox::initializeButtonDefs();
|
||||
@@ -205,6 +201,28 @@ void Application::applyTheme()
|
||||
}
|
||||
}
|
||||
|
||||
void Application::applyFontSize()
|
||||
{
|
||||
auto font = QApplication::font();
|
||||
|
||||
// Store the original font size on first call
|
||||
if (g_OriginalFontSize <= 0) {
|
||||
#ifdef Q_OS_WIN
|
||||
// Qt on Windows uses "MS Shell Dlg 2" as the default font for many widgets, which resolves
|
||||
// to Tahoma 8pt, whereas the correct font would be "Segoe UI" 9pt.
|
||||
// Apparently, some widgets are already using the correct font. Thanks, MuseScore for this neat fix!
|
||||
font = QApplication::font("QMessageBox");
|
||||
#endif
|
||||
g_OriginalFontSize = font.pointSize();
|
||||
}
|
||||
|
||||
// Adjust application wide default font size
|
||||
auto newSize = g_OriginalFontSize + qBound(-2, config()->get(Config::GUI_FontSizeOffset).toInt(), 4);
|
||||
font.setPointSize(newSize);
|
||||
QApplication::setFont(font);
|
||||
QApplication::setFont(font, "QWidget");
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event)
|
||||
{
|
||||
// Handle Apple QFileOpenEvent from finder (double click on .kdbx file)
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
~Application() override;
|
||||
|
||||
static void bootstrap(const QString& uiLanguage = "system");
|
||||
static void applyFontSize();
|
||||
|
||||
void applyTheme();
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->toolButtonStyleComboBox->installEventFilter(mouseWheelFilter);
|
||||
m_generalUi->languageComboBox->installEventFilter(mouseWheelFilter);
|
||||
m_generalUi->trayIconAppearance->installEventFilter(mouseWheelFilter);
|
||||
m_generalUi->fontSizeComboBox->installEventFilter(mouseWheelFilter);
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
connect(m_generalUi->checkForUpdatesOnStartupCheckBox, SIGNAL(toggled(bool)), SLOT(checkUpdatesToggled(bool)));
|
||||
@@ -225,6 +226,7 @@ void ApplicationSettingsWidget::loadSettings()
|
||||
m_generalUi->autoReloadOnChangeCheckBox->setChecked(config()->get(Config::AutoReloadOnChange).toBool());
|
||||
m_generalUi->minimizeAfterUnlockCheckBox->setChecked(config()->get(Config::MinimizeAfterUnlock).toBool());
|
||||
m_generalUi->minimizeOnOpenUrlCheckBox->setChecked(config()->get(Config::MinimizeOnOpenUrl).toBool());
|
||||
m_generalUi->openUrlOnDoubleClick->setChecked(config()->get(Config::OpenURLOnDoubleClick).toBool());
|
||||
m_generalUi->hideWindowOnCopyCheckBox->setChecked(config()->get(Config::HideWindowOnCopy).toBool());
|
||||
hideWindowOnCopyCheckBoxToggled(m_generalUi->hideWindowOnCopyCheckBox->isChecked());
|
||||
m_generalUi->minimizeOnCopyRadioButton->setChecked(config()->get(Config::MinimizeOnCopy).toBool());
|
||||
@@ -264,10 +266,25 @@ void ApplicationSettingsWidget::loadSettings()
|
||||
m_generalUi->toolButtonStyleComboBox->addItem(tr("Follow style"), Qt::ToolButtonFollowStyle);
|
||||
int toolButtonStyleIndex =
|
||||
m_generalUi->toolButtonStyleComboBox->findData(config()->get(Config::GUI_ToolButtonStyle));
|
||||
if (toolButtonStyleIndex > 0) {
|
||||
if (toolButtonStyleIndex >= 0) {
|
||||
m_generalUi->toolButtonStyleComboBox->setCurrentIndex(toolButtonStyleIndex);
|
||||
}
|
||||
|
||||
m_generalUi->fontSizeComboBox->clear();
|
||||
m_generalUi->fontSizeComboBox->addItem(tr("Small"), -1);
|
||||
m_generalUi->fontSizeComboBox->addItem(tr("Normal"), 0);
|
||||
m_generalUi->fontSizeComboBox->addItem(tr("Medium"), 1);
|
||||
m_generalUi->fontSizeComboBox->addItem(tr("Large"), 2);
|
||||
|
||||
int fontSizeIndex = m_generalUi->fontSizeComboBox->findData(config()->get(Config::GUI_FontSizeOffset));
|
||||
if (fontSizeIndex >= 0) {
|
||||
m_generalUi->fontSizeComboBox->setCurrentIndex(fontSizeIndex);
|
||||
} else {
|
||||
// Custom value entered into config file, add it to the list and select it
|
||||
m_generalUi->fontSizeComboBox->addItem(tr("Custom"), config()->get(Config::GUI_FontSizeOffset).toInt());
|
||||
m_generalUi->fontSizeComboBox->setCurrentIndex(m_generalUi->fontSizeComboBox->count() - 1);
|
||||
}
|
||||
|
||||
m_generalUi->systrayShowCheckBox->setChecked(config()->get(Config::GUI_ShowTrayIcon).toBool());
|
||||
systrayToggled(m_generalUi->systrayShowCheckBox->isChecked());
|
||||
m_generalUi->systrayMinimizeToTrayCheckBox->setChecked(config()->get(Config::GUI_MinimizeToTray).toBool());
|
||||
@@ -382,6 +399,7 @@ void ApplicationSettingsWidget::saveSettings()
|
||||
config()->set(Config::AutoReloadOnChange, m_generalUi->autoReloadOnChangeCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeAfterUnlock, m_generalUi->minimizeAfterUnlockCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeOnOpenUrl, m_generalUi->minimizeOnOpenUrlCheckBox->isChecked());
|
||||
config()->set(Config::OpenURLOnDoubleClick, m_generalUi->openUrlOnDoubleClick->isChecked());
|
||||
config()->set(Config::HideWindowOnCopy, m_generalUi->hideWindowOnCopyCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeOnCopy, m_generalUi->minimizeOnCopyRadioButton->isChecked());
|
||||
config()->set(Config::DropToBackgroundOnCopy, m_generalUi->dropToBackgroundOnCopyRadioButton->isChecked());
|
||||
@@ -410,6 +428,7 @@ void ApplicationSettingsWidget::saveSettings()
|
||||
config()->set(Config::GUI_ColorPasswords, m_generalUi->colorPasswordsCheckBox->isChecked());
|
||||
|
||||
config()->set(Config::GUI_ToolButtonStyle, m_generalUi->toolButtonStyleComboBox->currentData().toString());
|
||||
config()->set(Config::GUI_FontSizeOffset, m_generalUi->fontSizeComboBox->currentData().toInt());
|
||||
|
||||
config()->set(Config::GUI_ShowTrayIcon, m_generalUi->systrayShowCheckBox->isChecked());
|
||||
config()->set(Config::GUI_TrayIconAppearance, m_generalUi->trayIconAppearance->currentData().toString());
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>568</width>
|
||||
<height>1153</height>
|
||||
<height>1202</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
@@ -493,6 +493,9 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Temporary file moved into place</string>
|
||||
@@ -543,6 +546,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="openUrlOnDoubleClick">
|
||||
<property name="text">
|
||||
<string>Open browser on double clicking URL field in entry view</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useGroupIconOnEntryCreationCheckBox">
|
||||
<property name="text">
|
||||
@@ -699,22 +712,6 @@
|
||||
<property name="horizontalSpacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="toolbarMovableCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Movable toolbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
@@ -728,66 +725,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="toolButtonStyleComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Toolbar button style</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="toolButtonStyleLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-right: 5px</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Toolbar button style:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>toolButtonStyleComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="languageComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Language selection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="languageLabel_2">
|
||||
<property name="sizePolicy">
|
||||
@@ -807,6 +744,82 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="toolbarMovableCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Movable toolbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="toolButtonStyleComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Toolbar button style</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="languageComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Language selection</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="toolButtonStyleLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Toolbar button style:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>toolButtonStyleComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="languageLabel_3">
|
||||
<property name="text">
|
||||
@@ -814,6 +827,32 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="fontSizeLabel">
|
||||
<property name="text">
|
||||
<string>Font size:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>fontSizeComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="fontSizeComboBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Font size selection</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@@ -902,6 +941,9 @@
|
||||
<property name="accessibleName">
|
||||
<string>Tray icon type</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
@@ -1351,6 +1393,7 @@
|
||||
<tabstop>alternativeSaveComboBox</tabstop>
|
||||
<tabstop>ConfirmMoveEntryToRecycleBinCheckBox</tabstop>
|
||||
<tabstop>EnableCopyOnDoubleClickCheckBox</tabstop>
|
||||
<tabstop>openUrlOnDoubleClick</tabstop>
|
||||
<tabstop>useGroupIconOnEntryCreationCheckBox</tabstop>
|
||||
<tabstop>minimizeOnOpenUrlCheckBox</tabstop>
|
||||
<tabstop>hideWindowOnCopyCheckBox</tabstop>
|
||||
|
||||
@@ -60,6 +60,8 @@ void Clipboard::setText(const QString& text, bool clear)
|
||||
mime->setData("x-kde-passwordManagerHint", QByteArrayLiteral("secret"));
|
||||
#elif defined(Q_OS_WIN)
|
||||
mime->setData("ExcludeClipboardContentFromMonitorProcessing", QByteArrayLiteral("1"));
|
||||
mime->setData("CanIncludeInClipboardHistory", {4, '\0'});
|
||||
mime->setData("CanUploadToCloudClipboard ", {4, '\0'});
|
||||
#endif
|
||||
|
||||
if (clipboard->supportsSelection()) {
|
||||
|
||||