Compare commits

..
142 Commits
Author SHA1 Message Date
Armin Schrenk 7ff2e22f17 Merge branch 'develop' into release/1.11.0 2023-10-30 10:41:11 +01:00
CryptobotandGitHub 4eee66b0ef New Crowdin updates (#3128)
New translations strings.properties

Arabic; Belarusian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Czech; Danish; Dutch; Filipino; Finnish; French; German; Greek; Hebrew; Hungarian; Italian; Japanese; Korean; Norwegian Bokmal; Polish; Portuguese; Portuguese, Brazilian; Romanian; Russian; Slovak; Spanish; Swahili, Tanzania; Swedish; Turkish; Ukrainian; Vietnamese; 

[ci skip]
2023-10-30 10:39:04 +01:00
Sebastian Stenzel 19fe7ba6bf updated to tiny-oauth2-client 0.8.0 2023-10-27 19:54:58 +02:00
Armin SchrenkandGitHub 7b522582fb Merge pull request #3163 from cryptomator/feature/3075-fix-ci-jdk-version
Feature: Use fixed JDK version for releases
2023-10-27 12:51:49 +02:00
Armin SchrenkandGitHub 7594e51e2b Update debian.yml
Remove architecture in apt install dependency
2023-10-27 12:30:37 +02:00
Armin Schrenk b3f0ff3662 use coffeelibs JDK for release builds 2023-10-27 12:10:37 +02:00
Armin SchrenkandGitHub 8ce304a0c3 Add link to how-to-jdk-update in notification 2023-10-26 16:21:22 +02:00
Armin SchrenkandGitHub f893b2b5be Merge pull request #3026 from cryptomator/feature/uninstall-old-winfsp
Feature: Update to WinFsp 2.x and uninstall old winfsp in Windows EXE installer
2023-10-23 12:26:28 +02:00
Armin Schrenk db868c6df7 move worklfow to correct directory 2023-10-20 16:22:45 +02:00
Sebastian Stenzel 6225d8d1f0 Merge branch 'develop' into release/1.11.0 2023-10-20 14:00:42 +02:00
Sebastian Stenzel ed975b459e bump dependencies 2023-10-20 14:00:08 +02:00
Armin Schrenk 652a6f5c4d use environment variables and update current jdk 2023-10-20 13:42:00 +02:00
Armin Schrenk a7a94099a8 Closes #3075 2023-10-20 13:35:03 +02:00
Armin Schrenk 71a3cbc334 add workflow to check for jdk updates 2023-10-20 13:29:43 +02:00
Armin SchrenkandGitHub 800b2440b3 Merge pull request #3157 from cryptomator/feature/3113-network-timeout
Time restrict network requests
2023-10-20 12:58:08 +02:00
Sebastian Stenzel f50a49e7a3 prepare 1.11.0 2023-10-20 12:11:06 +02:00
Sebastian Stenzel 92ece4dfb6 increased request timeout to 10s 2023-10-20 12:06:10 +02:00
Sebastian Stenzel 174225c60e add timeout to OAuth2 requests 2023-10-20 12:03:07 +02:00
Sebastian Stenzel 3071410a22 Merge branch 'develop' into feature/3113-network-timeout 2023-10-20 12:00:16 +02:00
Sebastian StenzelandGitHub 5c5777ffc5 Merge pull request #3091 from cryptomator/feature/simplify-out-of-bounds
simplify out-of-bounds check
2023-10-20 11:56:36 +02:00
Sebastian StenzelandGitHub 8dd8b93656 Merge pull request #3041 from cryptomator/feature/new-hub-keyloading
Adjusted to Hub 1.3.x API
2023-10-20 11:55:28 +02:00
Armin Schrenk 3b2d455b4a Update dependencies 2023-10-19 17:31:52 +02:00
Sebastian Stenzel b0dfa22903 removed unused class 2023-10-19 12:49:19 +02:00
Sebastian Stenzel 88b1c28d88 Merge branch 'develop' into feature/new-hub-keyloading 2023-10-19 11:33:13 +02:00
Sebastian Stenzel 5b85d7859a bump webdav-nio-adapter version 2023-10-19 11:28:54 +02:00
Sebastian Stenzel 468eed58d5 cleanup 2023-10-19 10:00:14 +02:00
Sebastian Stenzel 34c17be474 add request timeouts 2023-10-19 10:00:03 +02:00
Sebastian Stenzel 5dedd6b1c1 renamed class again 2023-10-18 11:30:22 +02:00
Sebastian Stenzel 25e8e81686 support apiBaseUrl in hub config 2023-10-18 11:08:42 +02:00
Sebastian Stenzel 1f7ab03bbb undo JEP 443 changes due to bug in javac
leads to invalid class files when built via Maven due to specific combination of javac arguments
2023-10-18 11:01:26 +02:00
Sebastian Stenzel 86f3cb7288 applied suggestions from code review 2023-10-18 09:40:34 +02:00
Sebastian Stenzel a1b8bf23b4 Merge branch 'develop' into feature/new-hub-keyloading 2023-10-18 09:40:03 +02:00
Sebastian StenzelandGitHub 1f21d5ea4c Merge pull request #3129 from cryptomator/feature/jdk21
JDK 21
2023-10-18 09:39:12 +02:00
Sebastian Stenzel f8ff7201d7 Merge branch 'develop' into feature/new-hub-keyloading 2023-10-18 08:50:34 +02:00
Armin Schrenk 3567c036c3 remove unused imports 2023-10-17 17:25:38 +02:00
Armin Schrenk 0cf1f087ad Closes #3113 2023-10-17 11:36:50 +02:00
Armin Schrenk 209f60727e fix f4ad7aa43d 2023-10-16 16:38:12 +02:00
Armin Schrenk 56624fc079 Replace URL files by hardcoded links in build scripts 2023-10-16 16:35:17 +02:00
Armin Schrenk 218c5243e3 Merge branch 'develop' into feature/uninstall-old-winfsp 2023-10-16 16:29:57 +02:00
Armin SchrenkandGitHub 560a979e0e Merge pull request #3131 from cryptomator/feature/sign-all-dlls
Feature: Sign all dlls to pass Microsoft Windows Smart App control
2023-10-16 16:21:08 +02:00
Sebastian Stenzel 1debe4c7c8 explicitly set Logback ConfiguratorRank 2023-10-16 16:20:03 +02:00
Sebastian Stenzel 2e2aa77727 update dependencies 2023-10-16 16:19:11 +02:00
Sebastian Stenzel 162ebf6545 Merge branch 'develop' into feature/jdk21 2023-10-16 16:12:10 +02:00
Armin Schrenk cf09eff640 Use new version of uninstaller:
* logging
* correct reboot
2023-10-13 17:49:48 +02:00
Armin SchrenkandGitHub 2d503a0edb Merge pull request #3148 from cryptomator/feature/propagate-deleted-discussions
Propagate deleted discussions to the error database
2023-10-13 17:01:45 +02:00
Julian Raufelder 7f7f0a099a Propagate deleted discussions to the error database 2023-10-12 14:11:54 +02:00
Julian Raufelder 109f5d1faa Update the error-db when a discussion is deleted 2023-10-12 10:56:14 +02:00
Armin Schrenk 38a5d40b64 Merge branch 'develop' into feature/uninstall-old-winfsp 2023-10-11 16:14:26 +02:00
Armin Schrenk 9b55f6fc56 Refactor extraction and singing in seperate steps 2023-10-11 12:55:05 +02:00
Sebastian Stenzel 83879f5cfe revert 8e9d54b & 6c5ee14 due to bug in JDK 21
see https://bugs.openjdk.org/browse/JDK-8313323
2023-10-09 16:19:56 +02:00
Armin SchrenkandGitHub 083ac5a7e0 Merge pull request #3133 from purejava/build-arch
Improve AppImage build script
2023-10-09 13:10:05 +02:00
dependabot[bot]andGitHub a7eba377ba Bump org.mockito:mockito-core from 5.5.0 to 5.6.0 (#3142) 2023-10-09 09:58:47 +00:00
dependabot[bot]andGitHub 4ee1e6d9f1 Bump the github-actions group with 1 update (#3135) 2023-10-04 20:24:43 +00:00
Ralph Plawetzki 24a63c10d0 Build for aarch64 too 2023-09-30 14:54:15 +02:00
Armin Schrenk 0d805b2d43 clean up 2023-09-28 18:34:38 +02:00
Armin Schrenk 7bc47fe6d7 Really fixes #3130 2023-09-28 18:33:26 +02:00
Armin Schrenk ae50846257 Revert ce466e7715 2023-09-28 18:33:01 +02:00
Armin Schrenk ce466e7715 fixes #3130 2023-09-28 13:47:51 +02:00
Armin Schrenk 15885545b5 supress some output 2023-09-27 11:13:52 +02:00
Armin Schrenk 29fedcd390 integrate code from test script into github workflow 2023-09-27 10:24:59 +02:00
Armin Schrenk fadd6b761f add cert import 2023-09-26 17:30:18 +02:00
Armin Schrenk d857335901 update test script:
* use variables
* use signtool
* find newest signtool version
2023-09-26 16:43:00 +02:00
Armin Schrenk 93b4cbfb2c add test script 2023-09-26 13:42:18 +02:00
Armin Schrenk 625334c6c8 fix windows build script 2023-09-25 16:02:31 +02:00
Sebastian Stenzel 943374856e build using JDK 21 GA 2023-09-25 12:18:01 +02:00
Sebastian Stenzel e1d7fd92ad Merge branch 'develop' into feature/jdk21
# Conflicts:
#	pom.xml
2023-09-25 12:15:14 +02:00
Sebastian Stenzel c10dc74818 update integrations-linux to 1.4.0-beta1 2023-09-25 12:12:28 +02:00
Tobias Hagemann 92d9f2c18d try to fix mac build script to be compatible with create-dmg 1.2.0 2023-09-20 15:27:17 +02:00
Armin SchrenkandGitHub d7488b6984 fix wrong pom version 2023-09-20 15:16:18 +02:00
Tobias Hagemann 88ad3cd724 Merge branch 'develop' into feature/new-hub-keyloading 2023-09-20 14:38:13 +02:00
Armin Schrenk ba9e0b0c12 Merge branch 'main' into develop 2023-09-20 11:34:37 +02:00
Armin Schrenk bbe7255901 Merge branch 'release/1.10.1' 2023-09-20 11:33:39 +02:00
Armin Schrenk 51e55d3e3b finalize 1.10.1 2023-09-20 11:09:37 +02:00
Armin Schrenk f704dc0de9 prepare 1.10.1 2023-09-20 11:07:50 +02:00
CryptobotandGitHub e858280f30 New Crowdin updates (#3110)
New Crowdin updates (#3110)

New translations strings.properties

Bulgarian; Chinese Traditional; 

[ci skip]
2023-09-20 11:05:33 +02:00
Armin SchrenkandGitHub f9b170204a Merge pull request #3118 from cryptomator/feature/3117-disable-update-check
Disable Update Check
2023-09-20 11:02:14 +02:00
Armin SchrenkandGitHub 0ed7415a0d Merge pull request #3109 from cryptomator/feature/error-dialog-request-with-errorcode
Enhance user agent string when downloading error code database
2023-09-20 11:02:01 +02:00
Armin Schrenk 7a0d50ecca removed unnecessary import 2023-09-20 10:48:26 +02:00
Armin SchrenkandGitHub 19e035aa33 Merge pull request #3123 from cryptomator/feature/context-menu-focus
Fix addVaultButton misbehavior
2023-09-19 20:56:56 +02:00
Jan-Peter Klein 830970cb75 improved format and naming 2023-09-19 16:29:12 +02:00
Jan-Peter Klein 9b2987d0a2 improved function naming 2023-09-19 16:26:55 +02:00
Armin Schrenk 76a4062f8b update org.cryptomator.integrations:integrations-win from 1.2.2 to 1.2.3 2023-09-19 15:12:45 +02:00
Jan-Peter Klein 113717f955 addVaultButton context menu event filter removed 2023-09-19 15:12:40 +02:00
Jan-Peter Klein c40ad58028 detached context menu from button to fix misbehavior 2023-09-19 15:03:22 +02:00
Jan-Peter Klein d6a7efcb7f added OS_NAME, OS_VERSION and OS_ARCH to user-agent 2023-09-19 14:00:45 +02:00
Jan-Peter Klein 910b89d6ce Merge branch 'develop' into feature/error-dialog-request-with-errorcode 2023-09-19 13:19:40 +02:00
Tobias Hagemann 5554dfdd89 fixes #3121 2023-09-19 12:22:24 +02:00
Tobias Hagemann 1657cd50fc fixes #3112 2023-09-19 12:12:27 +02:00
Sebastian Stenzel 57bf5e11b5 Merge branch 'develop' into feature/simplify-out-of-bounds 2023-09-18 19:33:25 +02:00
Sebastian Stenzel 594ca47c85 Merge branch 'develop' into feature/jdk21
# Conflicts:
#	.github/workflows/appimage.yml
#	.github/workflows/mac-dmg.yml
2023-09-18 19:33:06 +02:00
Sebastian Stenzel 6c5ee14c73 rename unused var 2023-09-18 19:29:35 +02:00
Sebastian Stenzel 8e9d54b44b rename unused var 2023-09-18 19:27:28 +02:00
Sebastian Stenzel 70805665bf use features of Java 21 with --enable-preview 2023-09-18 19:21:56 +02:00
Sebastian Stenzel ac243a706e improve readability 2023-09-18 19:13:03 +02:00
Sebastian Stenzel aa382ce80d update coffeelibs jdk to 21 2023-09-18 19:10:54 +02:00
b7da508ccc Update src/main/java/org/cryptomator/ui/mainwindow/ResizeController.java
Co-authored-by: Bas Ruigrok <Rexbas@proton.me>
2023-09-18 13:19:58 +02:00
Sebastian Stenzel 47bd0ca647 disable update check for PPA builds 2023-09-18 13:10:19 +02:00
Sebastian Stenzel c5d6c0ce98 add cryptomator.disableUpdateCheck property 2023-09-18 13:03:09 +02:00
Sebastian Stenzel 6017d6b7a9 bump webdav-nio-adapter
containing fixes for CVE-2023-40167, CVE-2023-2976, CVE-2023-37895
2023-09-18 12:34:49 +02:00
Sebastian Stenzel e31e06b288 added @VisibleForTesting 2023-09-18 10:13:51 +02:00
Sebastian Stenzel 84ac803a7d reordered properties
[ci skip]
2023-09-18 10:10:50 +02:00
Armin SchrenkandGitHub bcb970afb1 Update bug.yml
Add missing FUSE option in volume type selection
2023-09-13 13:59:50 +02:00
Jan-Peter Klein 9d640b57ce added build number to user-agent header 2023-09-13 12:31:53 +02:00
Jan-Peter Klein 4c83617847 fixed build error 2023-09-12 18:07:30 +02:00
Jan-Peter Klein 5d7906972b added user-agent header with <product> / <product-version> to HttpRequest 2023-09-12 17:59:08 +02:00
Jan-Peter Klein 666cd4a4f0 enhanced request URI with URLEncoder for errorCode 2023-09-12 17:14:45 +02:00
Jan-Peter Klein eb4d39e8b4 introduce 'errorCode' parameter in request 2023-09-12 13:34:55 +02:00
Armin Schrenk a1ad89a2e2 Merge branch 'main' into develop [ci skip] 2023-09-11 09:56:35 +02:00
Sebastian Stenzel 4043e3f71f build using JDK 21 2023-09-06 16:22:41 +02:00
Tobias Hagemann 08887f1147 Merge branch 'develop' into feature/new-hub-keyloading 2023-09-06 14:18:11 +02:00
Armin Schrenk 06f288ef76 only execute winfsp-uninstaller on install or repair 2023-08-29 17:51:05 +02:00
Armin Schrenk ffcad9f1ec reformat 2023-08-29 17:32:49 +02:00
Armin Schrenk 031dc8a31a bump exe uninstaller to new version and skip uninstaller on bundle uninstall 2023-08-29 17:25:43 +02:00
Sebastian Stenzel cfd433b328 revert to simple of of bounds check
as suggested in https://github.com/cryptomator/cryptomator/pull/3017#discussion_r1264527894 with minor adjustments
2023-08-29 07:26:46 +02:00
Armin Schrenk 422efcc89f [ci skip] use new version of winfsp-uninstaller 2023-08-23 15:43:57 +02:00
Armin Schrenk c1c0c2e82f [ci skip] use new version of winfsp-uninstaller 2023-08-22 10:47:18 +02:00
Armin Schrenk c6790fec70 [ci skip] use new version of winfsp-uninstaller 2023-08-17 12:01:58 +02:00
Armin Schrenk e9ef122e7f Merge branch 'develop' into feature/uninstall-old-winfsp 2023-08-14 15:13:11 +02:00
Sebastian Stenzel f48963a6d0 Merge branch 'develop' into feature/new-hub-keyloading
# Conflicts:
#	src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java
2023-08-09 15:26:03 +02:00
Armin Schrenk 40611b4ebe improve wording 2023-08-01 10:48:42 +02:00
Sebastian Stenzel 6ce34efb52 Merge branch 'develop' into feature/new-hub-keyloading 2023-07-28 15:23:00 +02:00
Armin Schrenk 0800c53aac update to uninstaller-beta4 2023-07-24 10:36:23 +02:00
Armin Schrenk c6ab05979c cleanup [ci skip] 2023-07-19 16:33:40 +02:00
Armin Schrenk 2c8d5d3d5d update ci script 2023-07-19 16:30:08 +02:00
Armin Schrenk b76a311ddc Replace winfsp-uninstaller by downloadlink 2023-07-19 16:24:44 +02:00
Armin Schrenk 315bf0d51f Format confirmation dialog 2023-07-14 16:00:49 +02:00
Armin Schrenk 4cdbe50eb6 adjust to new winfsp-uninstaller exe 2023-07-13 17:29:50 +02:00
Armin Schrenk ead0f0fae1 add exe for winfsp uninstallation and include in burn bundle 2023-07-11 09:41:54 +02:00
Sebastian Stenzel cd10d38990 adjusted to latest API-changes 2023-07-06 18:57:10 +02:00
Sebastian Stenzel 8cb21c97e3 add mark for future improvement 2023-07-04 16:58:37 +02:00
Sebastian Stenzel 0ad8ce77ef handle "invalid setup code" error properly 2023-07-04 16:55:42 +02:00
Sebastian Stenzel e358ffd666 added tests 2023-07-04 16:47:54 +02:00
Sebastian Stenzel 496f9a9cd5 docs
[ci skip]
2023-07-04 15:05:22 +02:00
Sebastian Stenzel 61025de0d6 renamed var
[ci skip]
2023-07-04 14:43:56 +02:00
Sebastian Stenzel f08049b960 fix "register device" on legacy hub instances 2023-07-04 14:34:36 +02:00
Sebastian Stenzel 746c8f54eb Merge branch 'develop' into feature/new-hub-keyloading
# Conflicts:
#	src/main/java/org/cryptomator/ui/keyloading/hub/CreateDeviceDto.java
#	src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java
2023-06-30 16:20:59 +02:00
Sebastian Stenzel 9fc1efa005 adjust labels 2023-06-29 10:38:31 +02:00
Sebastian Stenzel 448eac8ff5 cleanup 2023-06-23 12:06:06 +02:00
Sebastian Stenzel 5a18d086e0 register device using a setup code 2023-06-22 17:16:00 +02:00
Sebastian Stenzel 918ace2eb6 Merge branch 'develop' into feature/new-hub-keyloading 2023-06-14 10:31:58 +02:00
Sebastian Stenzel fe733967dc send device type in device registration request 2023-06-01 15:06:21 +02:00
Sebastian Stenzel 59f5c0cb12 started new unlock workflow using user-specific private key 2023-05-09 17:09:49 +02:00
95 changed files with 1891 additions and 423 deletions
+2 -1
View File
@@ -43,6 +43,7 @@ body:
- WinFsp (Local Drive) - WinFsp (Local Drive)
- FUSE-T - FUSE-T
- macFUSE - macFUSE
- FUSE
- WebDAV (Windows Explorer) - WebDAV (Windows Explorer)
- WebDAV (AppleScript) - WebDAV (AppleScript)
- WebDAV (gio) - WebDAV (gio)
@@ -95,4 +96,4 @@ body:
id: further-info id: further-info
attributes: attributes:
label: Anything else? label: Anything else?
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue? description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
+3 -3
View File
@@ -10,8 +10,8 @@ on:
required: false required: false
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: '21.0.1+12'
jobs: jobs:
get-version: get-version:
@@ -36,7 +36,7 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca' openjfx-sha: 'c0d80ebbe0aab404ef9ad8b46c05bf533a1e40b39b2720eebd9238d81f6326ca'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
+3 -3
View File
@@ -6,8 +6,8 @@ on:
types: [labeled] types: [labeled]
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: 21
defaults: defaults:
run: run:
@@ -18,7 +18,7 @@ jobs:
name: Compile and Test name: Compile and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
+64
View File
@@ -0,0 +1,64 @@
name: Checks JDK version for minor updates
on:
schedule:
- cron: '0 0 1 * *' # run once a month at the first day of month
env:
JDK_VERSION: '21.0.1+12'
JDK_VENDOR: zulu
jobs:
jdk-current:
name: Check out current version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
steps:
- uses: actions/setup-java@v3
with:
java-version: ${{ env.JDK_VERSION }}
distribution: ${{ env.JDK_VENDOR }}
check-latest: false
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
jdk-latest:
name: Checkout latest jdk version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
steps:
- uses: actions/setup-java@v3
with:
java-version: 21
distribution: ${{ env.JDK_VENDOR }}
check-latest: true
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-version=${version}" >> "$GITHUB_OUTPUT"
notify:
name: Notifies for jdk update
runs-on: ubuntu-latest
needs: [jdk-current, jdk-latest]
if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }}
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "JDK update available"
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions."
SLACK_FOOTER: false
MSG_MINIMAL: true
+9 -6
View File
@@ -16,8 +16,10 @@ on:
type: boolean type: boolean
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: '21.0.1+12'
COFFEELIBS_JDK: 21
COFFEELIBS_JDK_VERSION: '21.0.1+12-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c' OPENJFX_JMODS_AMD64_HASH: 'f522ac2ae4bdd61f0219b7b8d2058ff72a22f36a44378453bcfdcd82f8f5e08c'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip' OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_linux-aarch64_bin-jmods.zip'
@@ -28,7 +30,7 @@ jobs:
name: Build Debian Package name: Build Debian Package
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- id: versions - id: versions
name: Get version information name: Get version information
run: | run: |
@@ -42,7 +44,7 @@ jobs:
run: | run: |
sudo add-apt-repository ppa:coffeelibs/openjdk sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.JAVA_VERSION }} libgtk2.0-0 sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@@ -97,7 +99,8 @@ jobs:
run: | run: |
cp -r dist/linux/debian/ pkgdir cp -r dist/linux/debian/ pkgdir
export RFC2822_TIMESTAMP=`date --rfc-2822` export RFC2822_TIMESTAMP=`date --rfc-2822`
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules export DISABLE_UPDATE_CHECK=${{ inputs.dput }}
envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM} ${DISABLE_UPDATE_CHECK}' < dist/linux/debian/rules > pkgdir/debian/rules
envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
find . -name "*.jar" >> pkgdir/debian/source/include-binaries find . -name "*.jar" >> pkgdir/debian/source/include-binaries
mv pkgdir cryptomator_${{ inputs.ppaver }} mv pkgdir cryptomator_${{ inputs.ppaver }}
@@ -147,4 +150,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
run: | run: |
artifacts=$(ls | grep cryptomator*.deb) artifacts=$(ls | grep cryptomator*.deb)
gh release upload ${{ github.ref_name }} $artifacts gh release upload ${{ github.ref_name }} $artifacts
+9 -3
View File
@@ -2,7 +2,7 @@ name: Update Error Database
on: on:
discussion: discussion:
types: [created, edited, category_changed, answered, unanswered] types: [created, edited, deleted, category_changed, answered, unanswered]
discussion_comment: discussion_comment:
types: [created, edited, deleted] types: [created, edited, deleted]
@@ -12,6 +12,7 @@ jobs:
if: github.event.discussion.category.name == 'Errors' if: github.event.discussion.category.name == 'Errors'
steps: steps:
- name: Query Discussion Data - name: Query Discussion Data
if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted'
id: query-data id: query-data
uses: actions/github-script@v6 uses: actions/github-script@v6
with: with:
@@ -47,8 +48,13 @@ jobs:
- name: Merge Error Code Data - name: Merge Error Code Data
run: | run: |
jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json jq -c '.' ${{ steps.get-gist.outputs.file }} > original.json
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json if [ ! -z "$DISCUSSION" ]
jq -s '.[0] * .[1]' original.json new.json > merged.json then
echo $DISCUSSION | jq -c '.repository.discussion | .comments = .comments.totalCount | {(.id|tostring) : .}' > new.json
jq -s '.[0] * .[1]' original.json new.json > merged.json
else
cat original.json | jq 'del(.[] | select(.url=="https://github.com/cryptomator/cryptomator/discussions/${{ github.event.discussion.number }}"))' > merged.json
fi
env: env:
DISCUSSION: ${{ steps.query-data.outputs.result }} DISCUSSION: ${{ steps.query-data.outputs.result }}
- name: Patch Gist - name: Patch Gist
+3 -3
View File
@@ -22,8 +22,8 @@ on:
value: ${{ jobs.determine-version.outputs.type }} value: ${{ jobs.determine-version.outputs.type }}
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: 21
jobs: jobs:
determine-version: determine-version:
@@ -35,7 +35,7 @@ jobs:
revNum: ${{ steps.versions.outputs.revNum }} revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}} type: ${{ steps.versions.outputs.type}}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Java - name: Setup Java
+5 -6
View File
@@ -15,8 +15,8 @@ on:
type: boolean type: boolean
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: '21.0.1+12'
jobs: jobs:
get-version: get-version:
@@ -47,7 +47,7 @@ jobs:
openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip' openjfx-url: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf' openjfx-sha: 'c60f5f19aa847e0e620e0b011e5de68f2c6755641c2141cec27a0b89f612beaf'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@@ -62,7 +62,7 @@ jobs:
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check
mkdir -p openjfx-jmods/ mkdir -p openjfx-jmods/
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
- name: Ensure major jfx version in pom and in jmods is the same - name: Ensure major jfx version in pom and in jmods is the same
run: | run: |
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
@@ -72,7 +72,7 @@ jobs:
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
exit 1 exit 1
fi fi
@@ -222,7 +222,6 @@ jobs:
--app-drop-link 512 245 --app-drop-link 512 245
--eula "dist/mac/dmg/resources/license.rtf" --eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758 --icon ".background" 128 758
--icon ".fseventsd" 320 758
--icon ".VolumeIcon.icns" 512 758 --icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env: env:
+3 -3
View File
@@ -4,8 +4,8 @@ on:
pull_request: pull_request:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: 21
defaults: defaults:
run: run:
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')" if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: ${{ env.JAVA_DIST }} distribution: ${{ env.JAVA_DIST }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
name: Validate commits pushed to release/hotfix branch to fulfill release requirements name: Validate commits pushed to release/hotfix branch to fulfill release requirements
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- id: validate-pom-version - id: validate-pom-version
name: Validate POM version name: Validate POM version
run: | run: |
+47 -11
View File
@@ -14,10 +14,12 @@ on:
env: env:
JAVA_DIST: 'temurin' JAVA_DIST: 'zulu'
JAVA_VERSION: 20 JAVA_VERSION: '21.0.1+12'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/20.0.2/openjfx-20.0.2_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77' OPENJFX_JMODS_AMD64_HASH: '18625bbc13c57dbf802486564247a8d8cab72ec558c240a401bf6440384ebd77'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
defaults: defaults:
run: run:
@@ -37,7 +39,7 @@ jobs:
LOOPBACK_ALIAS: 'cryptomator-vault' LOOPBACK_ALIAS: 'cryptomator-vault'
WIN_CONSOLE_FLAG: '' WIN_CONSOLE_FLAG: ''
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@@ -143,9 +145,29 @@ jobs:
- name: Fix permissions - name: Fix permissions
run: attrib -r appdir/Cryptomator/Cryptomator.exe run: attrib -r appdir/Cryptomator/Cryptomator.exe
shell: pwsh shell: pwsh
- name: Extract integrations DLL for code signing - name: Extract jars with DLLs for Codesigning
shell: pwsh shell: pwsh
run: gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --extract integrations.dll } run: |
Add-Type -AssemblyName "System.io.compression.filesystem"
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
$jarExtractDir = New-Item -Path ".\appdir\jar-extract" -ItemType Directory
#for all jars inspect
Get-ChildItem -Path $jarFolder -Filter "*.jar" | ForEach-Object {
$jar = [Io.compression.zipfile]::OpenRead($_.FullName)
if (@($jar.Entries | Where-Object {$_.Name.ToString().EndsWith(".dll")} | Select-Object -First 1).Count -gt 0) {
#jars containing dlls extract
Set-Location $jarExtractDir
Expand-Archive -Path $_.FullName
}
$jar.Dispose()
}
- name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130
shell: pwsh
run: |
New-Item -Path appdir/jpackage-jmod -ItemType Directory
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Codesign - name: Codesign
uses: skymatic/code-sign-action@v2 uses: skymatic/code-sign-action@v2
with: with:
@@ -154,12 +176,22 @@ jobs:
certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A
description: Cryptomator description: Cryptomator
timestampUrl: 'http://timestamp.digicert.com' timestampUrl: 'http://timestamp.digicert.com'
folder: appdir/Cryptomator folder: appdir
recursive: true recursive: true
- name: Repack signed DLL into jar - name: Replace DLLs inside jars with signed ones
shell: pwsh shell: pwsh
run: | run: |
gci ./appdir/Cryptomator/app/mods/ -File integrations-win-*.jar | ForEach-Object {Set-Location -Path $_.Directory; jar --file=$($_.FullName) --update integrations.dll; Remove-Item integrations.dll} $jarExtractDir = Resolve-Path ".\appdir\jar-extract"
$jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods"
Get-ChildItem -Path $jarExtractDir | ForEach-Object {
$jarName = $_.Name
$jarFile = "${jarFolder}\${jarName}.jar"
Set-Location $_
Get-ChildItem -Path $_ -Recurse -File "*.dll" | ForEach-Object {
# update jar with signed dll
jar --file="$jarFile" --update $(Resolve-Path -Relative -Path $_)
}
}
- name: Generate license for MSI - name: Generate license for MSI
run: > run: >
mvn -B license:add-third-party mvn -B license:add-third-party
@@ -193,6 +225,7 @@ jobs:
--file-associations dist/win/resources/FAvaultFile.properties --file-associations dist/win/resources/FAvaultFile.properties
env: env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Codesign MSI - name: Codesign MSI
uses: skymatic/code-sign-action@v2 uses: skymatic/code-sign-action@v2
with: with:
@@ -234,7 +267,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
needs: [get-version, build-msi] needs: [get-version, build-msi]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Download .msi - name: Download .msi
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -261,8 +294,11 @@ jobs:
shell: pwsh shell: pwsh
- name: Download WinFsp - name: Download WinFsp
run: | run: |
$winfspUrl = (Select-String -Path ".\dist\win\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }}
curl --output dist/win/bundle/resources/winfsp.msi -L $winfspUrl shell: pwsh
- name: Download Legacy-WinFsp uninstaller
run: |
curl --output dist/win/bundle/resources/winfsp-uninstaller.exe -L ${{ env.WINFSP_UNINSTALLER }}
shell: pwsh shell: pwsh
- name: Compile to wixObj file - name: Compile to wixObj file
run: > run: >
+11 -12
View File
@@ -14,10 +14,10 @@
<option name="dagger.fastInit" value="enabled" /> <option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" /> <option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false"> <processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.45/dagger-compiler-2.45.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.48.1/dagger-compiler-2.48.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.45/dagger-2.45.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.48.1/dagger-2.48.1.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" /> <entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.45/dagger-producers-2.45.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.48.1/dagger-producers-2.48.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
@@ -26,20 +26,19 @@
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" /> <entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.45/dagger-spi-2.45.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.48.1/dagger-spi-2.48.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.7.0-1.0.6/symbol-processing-api-1.7.0-1.0.6.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.0-1.0.12/symbol-processing-api-1.9.0-1.0.12.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" /> <entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" /> <entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" /> <entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" /> <entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.5.0/kotlinx-metadata-jvm-0.5.0.jar" />
</processorPath> </processorPath>
<module name="cryptomator" /> <module name="cryptomator" />
</profile> </profile>
+1 -1
View File
@@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="20" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21_PREVIEW" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+6 -4
View File
@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
set -e
cd $(dirname $0) cd $(dirname $0)
REVISION_NO=`git rev-list --count HEAD` REVISION_NO=`git rev-list --count HEAD`
@@ -10,6 +11,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; }
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
SEMVER_STR=${VERSION} SEMVER_STR=${VERSION}
MACHINE_TYPE=$(uname -m)
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
@@ -83,17 +85,17 @@ ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.App
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
# load AppImageTool # load AppImageTool
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o /tmp/appimagetool.AppImage curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${MACHINE_TYPE}.AppImage -o /tmp/appimagetool.AppImage
chmod +x /tmp/appimagetool.AppImage chmod +x /tmp/appimagetool.AppImage
# create AppImage # create AppImage
/tmp/appimagetool.AppImage \ /tmp/appimagetool.AppImage \
Cryptomator.AppDir \ Cryptomator.AppDir \
cryptomator-${SEMVER_STR}-x86_64.AppImage \ cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage \
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${MACHINE_TYPE}.AppImage.zsync'
echo "" echo ""
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-x86_64.AppImage" echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
echo "" echo ""
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage" echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir jni runtime squashfs-root; rm launcher-gtk2.properties /tmp/appimagetool.AppImage"
echo "" echo ""
@@ -66,6 +66,7 @@
</content_rating> </content_rating>
<releases> <releases>
<release date="2023-09-20" version="1.10.1"/>
<release date="2023-09-11" version="1.10.0"/> <release date="2023-09-11" version="1.10.0"/>
<release date="2023-08-11" version="1.9.4"/> <release date="2023-08-11" version="1.9.4"/>
<release date="2023-08-07" version="1.9.3"/> <release date="2023-08-07" version="1.9.3"/>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org> Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils Section: utils
Priority: optional Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-20, libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1 Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (= 21.0.1+12-0ppa1), libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
Standards-Version: 4.5.0 Standards-Version: 4.5.0
Homepage: https://cryptomator.org Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git Vcs-Git: https://github.com/cryptomator/cryptomator.git
+2 -1
View File
@@ -4,7 +4,7 @@
# Uncomment this to turn on verbose mode. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-20-coffeelibs JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH) DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64) ifeq ($(DEB_BUILD_ARCH),amd64)
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
@@ -59,6 +59,7 @@ override_dh_auto_build:
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \ --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"/usr/share/icons/hicolor/symbolic/apps\"" \
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \ --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
--java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \
--app-version "${VERSION_NUM}.${REVISION_NUM}" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \
--resource-dir resources \ --resource-dir resources \
--verbose --verbose
+6 -6
View File
@@ -49,21 +49,22 @@ fi
# download and check jmods # download and check jmods
curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip
mkdir -p openjfx-jmods/ mkdir -p openjfx-jmods/
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods/ unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@} JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*} JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=$(mvn -f../../../pom.xml help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*} POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})" >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
exit 1 exit 1
fi fi
# compile # compile
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
cp ../../../LICENSE.txt ../../../target
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
# add runtime # add runtime
@@ -168,6 +169,5 @@ create-dmg \
--app-drop-link 512 245 \ --app-drop-link 512 245 \
--eula "resources/license.rtf" \ --eula "resources/license.rtf" \
--icon ".background" 128 758 \ --icon ".background" 128 758 \
--icon ".fseventsd" 320 758 \
--icon ".VolumeIcon.icns" 512 758 \ --icon ".VolumeIcon.icns" 512 758 \
${APP_NAME}-${VERSION_NO}.dmg dmg ${APP_NAME}-${VERSION_NO}.dmg dmg
+2
View File
@@ -4,4 +4,6 @@ installer
*.wixobj *.wixobj
*.pdb *.pdb
*.msi *.msi
*.exe
*.jmod
license.rtf license.rtf
+10 -3
View File
@@ -63,9 +63,10 @@ if( !(Test-Path -Path $jfxJmodsZip) ) {
$jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash $jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash
if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) { if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) {
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual" Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
exit 1; exit 1;
} }
Expand-Archive -Path $jfxJmodsZip -DestinationPath ".\resources\" Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
@@ -143,6 +144,7 @@ try {
# create .msi # create .msi
$Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
$Env:JP_WIXHELPER_DIR = "."
& "$Env:JAVA_HOME\bin\jpackage" ` & "$Env:JAVA_HOME\bin\jpackage" `
--verbose ` --verbose `
--type msi ` --type msi `
@@ -174,10 +176,15 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources"
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" "-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges"
# download Winfsp # download Winfsp
$winfspMsiUrl= (Select-String -Path ".\bundle\resources\winFspMetaData.wxi" -Pattern '<\?define BundledWinFspDownloadLink="(.+)".*?>').Matches.Groups[1].Value $winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
Write-Output "Downloading ${winfspMsiUrl}..." Write-Output "Downloading ${winfspMsiUrl}..."
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
# download legacy-winfsp uninstaller
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0-beta9/winfsp-uninstaller.exe'
Write-Output "Downloading ${winfspUninstaller}..."
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
# copy MSI to bundle resources # copy MSI to bundle resources
Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi"
+25 -20
View File
@@ -1,5 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- For Built in variables, see https://wixtoolset.org/docs/tools/burn/builtin-variables/-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html--> <!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html-->
<!-- Attributes explicitly not used: <!-- Attributes explicitly not used:
@@ -10,21 +11,10 @@
AboutUrl="$(var.AboutUrl)" HelpUrl="$(var.HelpUrl)" UpdateUrl="$(var.UpdateUrl)" Copyright="$(var.BundleCopyright)" IconSourceFile="bundle\resources\Cryptomator.ico"> AboutUrl="$(var.AboutUrl)" HelpUrl="$(var.HelpUrl)" UpdateUrl="$(var.UpdateUrl)" Copyright="$(var.BundleCopyright)" IconSourceFile="bundle\resources\Cryptomator.ico">
<!-- detect outdated WinFsp installations --> <!-- detect outdated WinFsp installations -->
<?include "resources\winFspMetaData.wxi" ?>
<util:ProductSearch <util:ProductSearch
Variable="InstalledWinFspVersion" Variable="InstalledLegacyWinFspVersion"
Result="version" Result="version"
UpgradeCode="82F812D9-4083-4EF1-8BC8-0F1EDA05B46B" UpgradeCode="82F812D9-4083-4EF1-8BC8-0F1EDA05B46B"/>
/>
<!-- Note: The bundle engine takes the Message format literaly -->
<bal:Condition Message=
"The WinFsp driver used by Cryptomator is outdated and must be removed before the installation.
1. Open the view of installed apps
2. Search for &quot;WinFsp&quot;
3. Uninstall the listed application
4. Reboot your device
5. Restart this installer">(InstalledWinFspVersion = v0.0.0.0) OR ($(var.BundledWinFspVersion) &lt;= InstalledWinFspVersion)</bal:Condition>
<!-- for definition of the standard themes, see https://github.com/wixtoolset/wix3/blob/master/src/ext/BalExtension/wixstdba/Resources/--> <!-- for definition of the standard themes, see https://github.com/wixtoolset/wix3/blob/master/src/ext/BalExtension/wixstdba/Resources/-->
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLargeLicense"> <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLargeLicense">
@@ -36,26 +26,41 @@
SuppressOptionsUI="yes" SuppressOptionsUI="yes"
ThemeFile="bundle\customBootstrapperTheme.xml" ThemeFile="bundle\customBootstrapperTheme.xml"
LocalizationFile="bundle\customBootstrapperTheme.wxl" LocalizationFile="bundle\customBootstrapperTheme.wxl"
LogoFile="bundle\resources\logo.png" LogoFile="bundle\resources\logo.png"/>
/>
<Payload SourceFile="bundle\resources\logoSide.png" /> <Payload SourceFile="bundle\resources\logoSide.png" />
</BootstrapperApplicationRef> </BootstrapperApplicationRef>
<Chain> <Chain>
<ExePackage Cache="yes" PerMachine="yes" Permanent="no"
SourceFile="resources\winfsp-uninstaller.exe"
DisplayName="Removing outdated WinFsp Driver"
Description="Executable to remove old winfsp"
DetectCondition="false"
InstallCondition="(InstalledLegacyWinFspVersion &lt;&gt; v0.0.0.0) AND ((WixBundleAction = 7) OR (WixBundleAction = 5))">
<CommandLine Condition="WixBundleUILevel &lt;= 3" InstallArgument="-q -l &quot;[WixBundleLog].winfsp-uninstaller.log&quot;" RepairArgument="-q" UninstallArgument="-s" />
<!-- XML allows line breaks in attributes, hence keep the line breaks here -->
<CommandLine Condition="WixBundleUILevel &gt; 3" InstallArgument="-l &quot;[WixBundleLog].winfsp-uninstaller.log&quot; -t &quot;Cryptomator Installer&quot; -m &quot;Cryptomator requires a newer version of the WinFsp driver. The installer will now uninstall WinFsp, possibly reboot, and afterwards proceed with the installation.
Do you want to continue?&quot;" RepairArgument="-q" UninstallArgument="-s" />
<ExitCode Behavior="success" Value="0"/>
<ExitCode Behavior="success" Value="1"/>
<ExitCode Behavior="error" Value="2"/>
<ExitCode Behavior="error" Value="3"/>
<ExitCode Behavior="forceReboot" Value="4"/>
<ExitCode Behavior="success" Value="5"/>
</ExePackage>
<!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html--> <!-- see https://wixtoolset.org/documentation/manual/v3/xsd/wix/msipackage.html-->
<MsiPackage <MsiPackage
SourceFile="resources\Cryptomator.msi" SourceFile="resources\Cryptomator.msi"
CacheId="cryptomator-bundle-cryptomator" CacheId="cryptomator-bundle-cryptomator"
DisplayInternalUI="no" DisplayInternalUI="no"
Visible="no" Visible="no"/>
/>
<MsiPackage <MsiPackage
SourceFile="resources\winfsp.msi" SourceFile="resources\winfsp.msi"
CacheId="cryptomator-bundle-winfsp" CacheId="cryptomator-bundle-winfsp"
Visible="yes" Visible="yes"
DisplayInternalUI="no" DisplayInternalUI="no"
Vital="no" Permanent="yes"/>
Permanent="yes"
/>
</Chain> </Chain>
</Bundle> </Bundle>
</Wix> </Wix>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Include xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- A version number MUST be prefixed with letter "v", otherwise it is considered a normal string -->
<?define BundledWinFspVersion="v1.12.22339" ?>
<?define BundledWinFspDownloadLink="https://github.com/winfsp/winfsp/releases/download/v1.12.22339/winfsp-1.12.22339.msi" ?> <!-- Only used by external build scripts -->
</Include>
+1 -1
View File
@@ -70,7 +70,7 @@
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" /> <CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?> <?endif?>
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/> <Binary Id="JpCaDll" SourceFile="$(env.JP_WIXHELPER_DIR)\wixhelper.dll"/>
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" /> <CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
<?ifndef SkipCryptomatorLegacyCheck ?> <?ifndef SkipCryptomatorLegacyCheck ?>
+14 -14
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId> <artifactId>cryptomator</artifactId>
<version>1.10.0</version> <version>1.11.0</version>
<name>Cryptomator Desktop App</name> <name>Cryptomator Desktop App</name>
<organization> <organization>
@@ -26,7 +26,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>20</project.jdk.version> <project.jdk.version>21</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now --> <!-- Group IDs of jars that need to stay on the class path for now -->
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) --> <!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
@@ -35,36 +35,36 @@
<!-- cryptomator dependencies --> <!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version> <cryptomator.cryptofs.version>2.6.7</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version> <cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.2.2</cryptomator.integrations.win.version> <cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.1</cryptomator.integrations.mac.version> <cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.3.0-beta6</cryptomator.integrations.linux.version> <cryptomator.integrations.linux.version>1.4.0-beta2</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>3.0.0</cryptomator.fuse.version> <cryptomator.fuse.version>4.0.0-beta4</cryptomator.fuse.version>
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version> <cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
<cryptomator.webdav.version>2.0.3</cryptomator.webdav.version> <cryptomator.webdav.version>2.0.5</cryptomator.webdav.version>
<!-- 3rd party dependencies --> <!-- 3rd party dependencies -->
<commons-lang3.version>3.13.0</commons-lang3.version> <commons-lang3.version>3.13.0</commons-lang3.version>
<dagger.version>2.48</dagger.version> <dagger.version>2.48.1</dagger.version>
<easybind.version>2.2</easybind.version> <easybind.version>2.2</easybind.version>
<guava.version>32.1.2-jre</guava.version> <guava.version>32.1.3-jre</guava.version>
<jackson.version>2.15.2</jackson.version> <jackson.version>2.15.3</jackson.version>
<javafx.version>20.0.2</javafx.version> <javafx.version>20.0.2</javafx.version>
<jwt.version>4.4.0</jwt.version> <jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.31</nimbus-jose.version> <nimbus-jose.version>9.37</nimbus-jose.version>
<logback.version>1.4.11</logback.version> <logback.version>1.4.11</logback.version>
<slf4j.version>2.0.9</slf4j.version> <slf4j.version>2.0.9</slf4j.version>
<tinyoauth2.version>0.6.0</tinyoauth2.version> <tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.8.2</zxcvbn.version> <zxcvbn.version>1.8.2</zxcvbn.version>
<!-- test dependencies --> <!-- test dependencies -->
<junit.jupiter.version>5.10.0</junit.jupiter.version> <junit.jupiter.version>5.10.0</junit.jupiter.version>
<mockito.version>5.5.0</mockito.version> <mockito.version>5.6.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version> <hamcrest.version>2.2</hamcrest.version>
<!-- build-time dependencies --> <!-- build-time dependencies -->
<jetbrains.annotations.version>24.0.1</jetbrains.annotations.version> <jetbrains.annotations.version>24.0.1</jetbrains.annotations.version>
<dependency-check.version>8.4.0</dependency-check.version> <dependency-check.version>8.4.0</dependency-check.version>
<jacoco.version>0.8.10</jacoco.version> <jacoco.version>0.8.11</jacoco.version>
<license-generator.version>2.2.0</license-generator.version> <license-generator.version>2.2.0</license-generator.version>
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version> <junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
<mvn-compiler.version>3.11.0</mvn-compiler.version> <mvn-compiler.version>3.11.0</mvn-compiler.version>
@@ -2,6 +2,7 @@ package org.cryptomator.common;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -31,6 +32,7 @@ public class Environment {
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber"; private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir"; private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon"; private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
private static final String DISABLE_UPDATE_CHECK_PROP_NAME = "cryptomator.disableUpdateCheck";
private Environment() {} private Environment() {}
@@ -43,15 +45,16 @@ public class Environment {
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME); logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME); logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME); logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME); logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME); logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME); logCryptomatorSystemProperty(MOUNTPOINT_DIR_PROP_NAME);
logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME); logCryptomatorSystemProperty(MIN_PW_LENGTH_PROP_NAME);
logCryptomatorSystemProperty(APP_VERSION_PROP_NAME); logCryptomatorSystemProperty(APP_VERSION_PROP_NAME);
logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME); logCryptomatorSystemProperty(BUILD_NUMBER_PROP_NAME);
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME); logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
logCryptomatorSystemProperty(P12_PATH_PROP_NAME); logCryptomatorSystemProperty(DISABLE_UPDATE_CHECK_PROP_NAME);
} }
public static Environment getInstance() { public static Environment getInstance() {
@@ -74,10 +77,6 @@ public class Environment {
return getPaths(SETTINGS_PATH_PROP_NAME); return getPaths(SETTINGS_PATH_PROP_NAME);
} }
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}
public Stream<Path> getIpcSocketPath() { public Stream<Path> getIpcSocketPath() {
return getPaths(IPC_SOCKET_PATH_PROP_NAME); return getPaths(IPC_SOCKET_PATH_PROP_NAME);
} }
@@ -86,6 +85,10 @@ public class Environment {
return getPaths(KEYCHAIN_PATHS_PROP_NAME); return getPaths(KEYCHAIN_PATHS_PROP_NAME);
} }
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}
public Optional<Path> getLogDir() { public Optional<Path> getLogDir() {
return getPath(LOG_DIR_PROP_NAME); return getPath(LOG_DIR_PROP_NAME);
} }
@@ -94,14 +97,14 @@ public class Environment {
return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME)); return Optional.ofNullable(System.getProperty(LOOPBACK_ALIAS_PROP_NAME));
} }
public Optional<Path> getPluginDir() {
return getPath(PLUGIN_DIR_PROP_NAME);
}
public Optional<Path> getMountPointsDir() { public Optional<Path> getMountPointsDir() {
return getPath(MOUNTPOINT_DIR_PROP_NAME); return getPath(MOUNTPOINT_DIR_PROP_NAME);
} }
public int getMinPwLength() {
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH);
}
/** /**
* Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT". * Returns the app version defined in the {@value APP_VERSION_PROP_NAME} property or returns "SNAPSHOT".
* *
@@ -115,20 +118,24 @@ public class Environment {
return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME)); return Optional.ofNullable(System.getProperty(BUILD_NUMBER_PROP_NAME));
} }
public int getMinPwLength() { public Optional<Path> getPluginDir() {
return Integer.getInteger(MIN_PW_LENGTH_PROP_NAME, DEFAULT_MIN_PW_LENGTH); return getPath(PLUGIN_DIR_PROP_NAME);
} }
public boolean showTrayIcon() { public boolean showTrayIcon() {
return Boolean.getBoolean(TRAY_ICON_PROP_NAME); return Boolean.getBoolean(TRAY_ICON_PROP_NAME);
} }
public boolean disableUpdateCheck() {
return Boolean.getBoolean(DISABLE_UPDATE_CHECK_PROP_NAME);
}
private Optional<Path> getPath(String propertyName) { private Optional<Path> getPath(String propertyName) {
String value = System.getProperty(propertyName); String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get); return Optional.ofNullable(value).map(Paths::get);
} }
// visible for testing @VisibleForTesting
Stream<Path> getPaths(String propertyName) { Stream<Path> getPaths(String propertyName) {
Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0)); Stream<String> rawSettingsPaths = getRawList(propertyName, System.getProperty("path.separator").charAt(0));
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of); return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Path::of);
@@ -3,6 +3,7 @@ package org.cryptomator.common;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@@ -114,7 +115,7 @@ public class ErrorCode {
* @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code> * @param bottomFrames Other stack frames, potentially forming the bottom of the stack of <code>allFrames</code>
* @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size. * @return The number of additional frames in <code>allFrames</code>. In most cases this should be equal to the difference in size.
*/ */
// visible for testing @VisibleForTesting
static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) { static int countTopmostFrames(StackTraceElement[] allFrames, StackTraceElement[] bottomFrames) {
if (allFrames.length < bottomFrames.length) { if (allFrames.length < bottomFrames.length) {
// if frames had been stacked on top of bottomFrames, allFrames would be larger // if frames had been stacked on top of bottomFrames, allFrames would be larger
@@ -124,7 +125,7 @@ public class ErrorCode {
} }
} }
// visible for testing @VisibleForTesting
static <T> int commonSuffixLength(T[] set, T[] subset) { static <T> int commonSuffixLength(T[] set, T[] subset) {
Preconditions.checkArgument(set.length >= subset.length); Preconditions.checkArgument(set.length >= subset.length);
// iterate items backwards as long as they are identical // iterate items backwards as long as they are identical
@@ -62,7 +62,7 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre
ProcessBuilder command = new ProcessBuilder(args); ProcessBuilder command = new ProcessBuilder(args);
Process p = command.start(); Process p = command.start();
waitForSuccess(p, 3, "`reg query`"); waitForSuccess(p, 3, "`reg query`");
return p.inputReader(StandardCharsets.UTF_8).lines().filter(outputFilter); return p.inputReader(StandardCharsets.ISO_8859_1).lines().filter(outputFilter);
} }
@@ -1,6 +1,7 @@
package org.cryptomator.common.mount; package org.cryptomator.common.mount;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -66,7 +67,7 @@ public final class MountWithinParentUtil {
} }
} }
//visible for testing @VisibleForTesting
static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException { static MountPointState getMountPointState(Path path) throws IOException, IllegalMountPointException {
if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) { if (Files.notExists(path, LinkOption.NOFOLLOW_LINKS)) {
return MountPointState.NOT_EXISTING; return MountPointState.NOT_EXISTING;
@@ -82,7 +83,7 @@ public final class MountWithinParentUtil {
return MountPointState.BROKEN_JUNCTION; return MountPointState.BROKEN_JUNCTION;
} }
//visible for testing @VisibleForTesting
enum MountPointState { enum MountPointState {
NOT_EXISTING, NOT_EXISTING,
@@ -93,7 +94,7 @@ public final class MountWithinParentUtil {
} }
//visible for testing @VisibleForTesting
static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException { static void removeResidualHideaway(Path mountPoint, Path hideaway) throws IOException {
checkIsHideawayDirectory(mountPoint, hideaway); checkIsHideawayDirectory(mountPoint, hideaway);
Files.delete(hideaway); //Fails if not empty Files.delete(hideaway); //Fails if not empty
@@ -155,7 +156,7 @@ public final class MountWithinParentUtil {
} }
} }
//visible for testing @VisibleForTesting
static Path getHideaway(Path mountPoint) { static Path getHideaway(Path mountPoint) {
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX); return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
} }
@@ -9,6 +9,7 @@ import com.google.common.base.CharMatcher;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.annotations.VisibleForTesting;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
@@ -126,7 +127,7 @@ public class VaultSettings {
return json; return json;
} }
//visible for testing @VisibleForTesting
static String normalizeDisplayName(String original) { static String normalizeDisplayName(String original) {
if (original.isBlank() || ".".equals(original) || "..".equals(original)) { if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
return "_"; return "_";
@@ -6,6 +6,7 @@
*******************************************************************************/ *******************************************************************************/
package org.cryptomator.launcher; package org.cryptomator.launcher;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -48,7 +49,7 @@ class FileOpenRequestHandler {
handleLaunchArgs(FileSystems.getDefault(), args); handleLaunchArgs(FileSystems.getDefault(), args);
} }
// visible for testing @VisibleForTesting
void handleLaunchArgs(FileSystem fs, List<String> args) { void handleLaunchArgs(FileSystem fs, List<String> args) {
Collection<Path> pathsToOpen = args.stream().map(str -> { Collection<Path> pathsToOpen = args.stream().map(str -> {
try { try {
@@ -5,6 +5,7 @@ import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.Configurator; import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.classic.spi.ConfiguratorRank;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender; import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.ConsoleAppender;
@@ -19,6 +20,7 @@ import org.cryptomator.common.Environment;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map; import java.util.Map;
@ConfiguratorRank(ConfiguratorRank.CUSTOM_NORMAL_PRIORITY)
public class LogbackConfigurator extends ContextAwareBase implements Configurator { public class LogbackConfigurator extends ContextAwareBase implements Configurator {
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
@@ -1,5 +1,7 @@
package org.cryptomator.ui.addvaultwizard; package org.cryptomator.ui.addvaultwizard;
import org.jetbrains.annotations.VisibleForTesting;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@@ -51,7 +53,7 @@ public class ReadmeGenerator {
resourceBundle.getString("addvault.new.readme.accessLocation.4"))); resourceBundle.getString("addvault.new.readme.accessLocation.4")));
} }
// visible for testing @VisibleForTesting
String createDocument(Iterable<String> paragraphs) { String createDocument(Iterable<String> paragraphs) {
StringBuilder sb = new StringBuilder(RTF_HEADER); StringBuilder sb = new StringBuilder(RTF_HEADER);
for (String p : paragraphs) { for (String p : paragraphs) {
@@ -63,7 +65,7 @@ public class ReadmeGenerator {
return sb.toString(); return sb.toString();
} }
// visible for testing @VisibleForTesting
String escapeNonAsciiChars(CharSequence input) { String escapeNonAsciiChars(CharSequence input) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
appendEscaped(sb, input); appendEscaped(sb, input);
@@ -20,9 +20,10 @@ public enum FxmlFile {
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), // HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), // HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), // HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), // HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), // HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), // HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), // LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), // LOCK_FAILED("/fxml/lock_failed.fxml"), //
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -116,7 +117,7 @@ public class HubToPasswordConvertController implements FxController {
}, Platform::runLater); // }, Platform::runLater); //
} }
//visible for testing @VisibleForTesting
void convertInternal() throws CompletionException, IllegalArgumentException { void convertInternal() throws CompletionException, IllegalArgumentException {
var passphrase = newPasswordController.getNewPassword(); var passphrase = newPasswordController.getNewPassword();
var vaultPath = vault.getPath(); var vaultPath = vault.getPath();
@@ -141,7 +142,7 @@ public class HubToPasswordConvertController implements FxController {
} }
} }
//visible for testing @VisibleForTesting
void backupHubConfig(Path hubConfigPath) throws IOException { void backupHubConfig(Path hubConfigPath) throws IOException {
byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath); byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath);
Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX); Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX);
@@ -149,7 +150,7 @@ public class HubToPasswordConvertController implements FxController {
LOG.debug("Successfully created hub config backup {}", backupPath.getFileName()); LOG.debug("Successfully created hub config backup {}", backupPath.getFileName());
} }
//visible for testing @VisibleForTesting
Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException { Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException {
var unverifiedVaultConfig = vault.getVaultConfigCache().get(); var unverifiedVaultConfig = vault.getVaultConfigCache().get();
try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) {
@@ -31,6 +31,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -42,7 +43,8 @@ public class ErrorController implements FxController {
private static final ObjectMapper JSON = new ObjectMapper(); private static final ObjectMapper JSON = new ObjectMapper();
private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class); private static final Logger LOG = LoggerFactory.getLogger(ErrorController.class);
private static final String ERROR_CODES_URL = "https://api.cryptomator.org/desktop/error-codes.json"; private static final String USER_AGENT_FORMAT = "Cryptomator/%s (Build %s) (%s %s %s)";
private static final String ERROR_CODES_URL_FORMAT = "https://api.cryptomator.org/desktop/error-codes.json?error-code=%s";
private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s"; private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s";
private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s"; private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s";
private static final String SEARCH_ERRORCODE_DELIM = " OR "; private static final String SEARCH_ERRORCODE_DELIM = " OR ";
@@ -142,11 +144,19 @@ public class ErrorController implements FxController {
@FXML @FXML
public void lookUpSolution() { public void lookUpSolution() {
String userAgent = USER_AGENT_FORMAT.formatted( //
environment.getAppVersion(), //
environment.getBuildNumber().orElse("undefined"), //
System.getProperty("os.name"), //
System.getProperty("os.version"), //
System.getProperty("os.arch"));
isLoadingHttpResponse.set(true); isLoadingHttpResponse.set(true);
askedForLookupDatabasePermission.set(true); askedForLookupDatabasePermission.set(true);
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpRequest httpRequest = HttpRequest.newBuilder()// HttpRequest httpRequest = HttpRequest.newBuilder()//
.uri(URI.create(ERROR_CODES_URL))// .header("User-Agent", userAgent)
.timeout(Duration.ofSeconds(10))
.uri(URI.create(ERROR_CODES_URL_FORMAT.formatted(URLEncoder.encode(errorCode.toString(),StandardCharsets.UTF_8))))//
.build(); .build();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())// httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())//
.thenAcceptAsync(this::loadHttpResponse, executorService)// .thenAcceptAsync(this::loadHttpResponse, executorService)//
@@ -1,6 +1,7 @@
package org.cryptomator.ui.fxapp; package org.cryptomator.ui.fxapp;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -17,6 +18,7 @@ public class FxApplication {
private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class);
private final long startupTime; private final long startupTime;
private final Environment environment;
private final Settings settings; private final Settings settings;
private final AppLaunchEventHandler launchEventHandler; private final AppLaunchEventHandler launchEventHandler;
private final Lazy<TrayMenuComponent> trayMenu; private final Lazy<TrayMenuComponent> trayMenu;
@@ -26,8 +28,9 @@ public class FxApplication {
private final AutoUnlocker autoUnlocker; private final AutoUnlocker autoUnlocker;
@Inject @Inject
FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { FxApplication(@Named("startupTime") long startupTime, Environment environment, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy<TrayMenuComponent> trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) {
this.startupTime = startupTime; this.startupTime = startupTime;
this.environment = environment;
this.settings = settings; this.settings = settings;
this.launchEventHandler = launchEventHandler; this.launchEventHandler = launchEventHandler;
this.trayMenu = trayMenu; this.trayMenu = trayMenu;
@@ -68,7 +71,9 @@ public class FxApplication {
return null; return null;
}); });
appWindows.checkAndShowUpdateReminderWindow(); if (!environment.disableUpdateCheck()) {
appWindows.checkAndShowUpdateReminderWindow();
}
launchEventHandler.startHandlingLaunchEvents(); launchEventHandler.startHandlingLaunchEvents();
autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES); autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
@@ -22,23 +22,23 @@ public class UpdateChecker {
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class); private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
private static final Duration AUTOCHECK_DELAY = Duration.seconds(5); private static final Duration AUTOCHECK_DELAY = Duration.seconds(5);
private final Environment env;
private final Settings settings; private final Settings settings;
private final String currentVersion;
private final StringProperty latestVersionProperty; private final StringProperty latestVersionProperty;
private final Comparator<String> semVerComparator; private final Comparator<String> semVerComparator;
private final ScheduledService<String> updateCheckerService; private final ScheduledService<String> updateCheckerService;
@Inject @Inject
UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) { UpdateChecker(Settings settings, Environment env, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
this.env = env;
this.settings = settings; this.settings = settings;
this.latestVersionProperty = latestVersionProperty; this.latestVersionProperty = latestVersionProperty;
this.semVerComparator = semVerComparator; this.semVerComparator = semVerComparator;
this.updateCheckerService = updateCheckerService; this.updateCheckerService = updateCheckerService;
this.currentVersion = env.getAppVersion();
} }
public void automaticallyCheckForUpdatesIfEnabled() { public void automaticallyCheckForUpdatesIfEnabled() {
if (settings.checkForUpdates.get()) { if (!env.disableUpdateCheck() && settings.checkForUpdates.get()) {
startCheckingForUpdates(AUTOCHECK_DELAY); startCheckingForUpdates(AUTOCHECK_DELAY);
} }
} }
@@ -63,9 +63,9 @@ public class UpdateChecker {
private void checkSucceeded(WorkerStateEvent event) { private void checkSucceeded(WorkerStateEvent event) {
String latestVersion = updateCheckerService.getValue(); String latestVersion = updateCheckerService.getValue();
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion); LOG.info("Current version: {}, lastest version: {}", getCurrentVersion(), latestVersion);
if (semVerComparator.compare(currentVersion, latestVersion) < 0) { if (semVerComparator.compare(getCurrentVersion(), latestVersion) < 0) {
// update is available // update is available
latestVersionProperty.set(latestVersion); latestVersionProperty.set(latestVersion);
} else { } else {
@@ -88,7 +88,7 @@ public class UpdateChecker {
} }
public String getCurrentVersion() { public String getCurrentVersion() {
return currentVersion; return env.getAppVersion();
} }
} }
@@ -63,6 +63,7 @@ public abstract class UpdateCheckerModule {
return HttpRequest.newBuilder() // return HttpRequest.newBuilder() //
.uri(LATEST_VERSION_URI) // .uri(LATEST_VERSION_URI) //
.header("User-Agent", userAgent) // .header("User-Agent", userAgent) //
.timeout(java.time.Duration.ofSeconds(10))
.build(); .build();
} }
@@ -101,16 +101,16 @@ public class StartController implements FxController {
} }
} }
private void loadingKeyFailed(Throwable e) { private void loadingKeyFailed(Throwable t) {
switch (e) { switch (t) {
case UnlockCancelledException uce -> {} //ok case UnlockCancelledException e -> {} // ok // TODO: rename to _ with JEP 443
case VaultKeyInvalidException vkie -> { case VaultKeyInvalidException e -> { // TODO: rename to _ with JEP 443
LOG.error("Invalid key"); //TODO: specific error screen LOG.error("Invalid key"); // TODO: specific error screen
appWindows.showErrorWindow(e, window, null); appWindows.showErrorWindow(e, window, null);
} }
default -> { default -> {
LOG.error("Failed to load key.", e); LOG.error("Failed to load key.", t);
appWindows.showErrorWindow(e, window, null); appWindows.showErrorWindow(t, window, null);
} }
} }
} }
@@ -35,13 +35,13 @@ public class AuthFlowController implements FxController {
private final String deviceId; private final String deviceId;
private final HubConfig hubConfig; private final HubConfig hubConfig;
private final AtomicReference<String> tokenRef; private final AtomicReference<String> tokenRef;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> receiveKeyScene; private final Lazy<Scene> receiveKeyScene;
private final ObjectProperty<URI> authUri; private final ObjectProperty<URI> authUri;
private AuthFlowTask task; private AuthFlowTask task;
@Inject @Inject
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) { public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
this.application = application; this.application = application;
this.window = window; this.window = window;
this.executor = executor; this.executor = executor;
@@ -1,13 +1,14 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.coffeelibs.tinyoauth2client.AuthFlow;
import io.github.coffeelibs.tinyoauth2client.TinyOAuth2; import io.github.coffeelibs.tinyoauth2client.TinyOAuth2;
import io.github.coffeelibs.tinyoauth2client.http.response.Response; import io.github.coffeelibs.tinyoauth2client.http.response.Response;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.function.Consumer; import java.util.function.Consumer;
class AuthFlowTask extends Task<String> { class AuthFlowTask extends Task<String> {
@@ -21,7 +22,7 @@ class AuthFlowTask extends Task<String> {
/** /**
* Spawns a server and waits for the redirectUri to be called. * Spawns a server and waits for the redirectUri to be called.
* *
* @param hubConfig Configuration object holding parameters required by {@link AuthFlow} * @param hubConfig Configuration object holding parameters required by {@link io.github.coffeelibs.tinyoauth2client.AuthorizationCodeGrant}
* @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started * @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started
*/ */
public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer<URI> redirectUriConsumer) { public AuthFlowTask(HubConfig hubConfig, AuthFlowContext authFlowContext, Consumer<URI> redirectUriConsumer) {
@@ -34,10 +35,11 @@ class AuthFlowTask extends Task<String> {
protected String call() throws IOException, InterruptedException { protected String call() throws IOException, InterruptedException {
var response = TinyOAuth2.client(hubConfig.clientId) // var response = TinyOAuth2.client(hubConfig.clientId) //
.withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) // .withTokenEndpoint(URI.create(hubConfig.tokenEndpoint)) //
.authFlow(URI.create(hubConfig.authEndpoint)) // .withRequestTimeout(Duration.ofSeconds(10)) //
.authorizationCodeGrant(URI.create(hubConfig.authEndpoint)) //
.setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) // .setSuccessResponse(Response.redirect(URI.create(hubConfig.authSuccessUrl + "&device=" + authFlowContext.deviceId()))) //
.setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) // .setErrorResponse(Response.redirect(URI.create(hubConfig.authErrorUrl + "&device=" + authFlowContext.deviceId()))) //
.authorize(redirectUriConsumer); .authorize(HttpClient.newHttpClient(), redirectUriConsumer);
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
throw new NotOkResponseException("Authorization returned status code " + response.statusCode()); throw new NotOkResponseException("Authorization returned status code " + response.statusCode());
} }
@@ -1,5 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
record CreateDeviceDto(String id, String name, String publicKey) {
}
@@ -1,19 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
class HttpHelper {
public static String readBody(HttpResponse<InputStream> response) throws IOException {
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return CharStreams.toString(reader);
}
}
}
@@ -1,6 +1,10 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
// needs to be accessible by JSON decoder // needs to be accessible by JSON decoder
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@@ -9,8 +13,19 @@ public class HubConfig {
public String clientId; public String clientId;
public String authEndpoint; public String authEndpoint;
public String tokenEndpoint; public String tokenEndpoint;
public String devicesResourceUrl;
public String authSuccessUrl; public String authSuccessUrl;
public String authErrorUrl; public String authErrorUrl;
public @Nullable String apiBaseUrl;
@Deprecated // use apiBaseUrl + "/devices/"
public String devicesResourceUrl;
public URI getApiBaseUrl() {
if (apiBaseUrl != null) {
return URI.create(apiBaseUrl);
} else {
// legacy approach
assert devicesResourceUrl != null;
return URI.create(devicesResourceUrl + "/..").normalize();
}
}
} }
@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.JWEObject;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -69,7 +68,7 @@ public abstract class HubKeyLoadingModule {
@Provides @Provides
@KeyLoadingScoped @KeyLoadingScoped
static CompletableFuture<JWEObject> provideResult() { static CompletableFuture<ReceivedKey> provideResult() {
return new CompletableFuture<>(); return new CompletableFuture<>();
} }
@@ -114,10 +113,10 @@ public abstract class HubKeyLoadingModule {
} }
@Provides @Provides
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE)
@KeyLoadingScoped @KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { static Scene provideHubLegacyRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE); return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
} }
@Provides @Provides
@@ -134,6 +133,13 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED); return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED);
} }
@Provides
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
}
@Provides @Provides
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE)
@KeyLoadingScoped @KeyLoadingScoped
@@ -166,6 +172,11 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(RegisterDeviceController.class) @FxControllerKey(RegisterDeviceController.class)
abstract FxController bindRegisterDeviceController(RegisterDeviceController controller); abstract FxController bindRegisterDeviceController(RegisterDeviceController controller);
@Binds
@IntoMap
@FxControllerKey(LegacyRegisterDeviceController.class)
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(RegisterSuccessController.class) @FxControllerKey(RegisterSuccessController.class)
@@ -36,11 +36,11 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
private final KeychainManager keychainManager; private final KeychainManager keychainManager;
private final Lazy<Scene> authFlowScene; private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene; private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
private final DeviceKey deviceKey; private final DeviceKey deviceKey;
@Inject @Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) { public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
this.window = window; this.window = window;
this.keychainManager = keychainManager; this.keychainManager = keychainManager;
window.setTitle(windowTitle); window.setTitle(windowTitle);
@@ -60,7 +60,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
var keypair = deviceKey.get(); var keypair = deviceKey.get();
showWindow(authFlowScene); showWindow(authFlowScene);
var jwe = result.get(); var jwe = result.get();
return JWEHelper.decrypt(jwe, keypair.getPrivate()); return jwe.decryptMasterkey(keypair.getPrivate());
} catch (NoKeychainAccessProviderException e) { } catch (NoKeychainAccessProviderException e) {
showWindow(noKeychainScene); showWindow(noKeychainScene);
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e); throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
@@ -2,35 +2,103 @@ package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDHDecrypter; import com.nimbusds.jose.crypto.ECDHDecrypter;
import com.nimbusds.jose.crypto.ECDHEncrypter;
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.JWKGenerator;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.function.Function;
class JWEHelper { class JWEHelper {
private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class); private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key"; private static final String JWE_PAYLOAD_KEY_FIELD = "key";
private static final String EC_ALG = "EC";
private JWEHelper(){} private JWEHelper(){}
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException {
try { try {
jwe.decrypt(new ECDHDecrypter(privateKey)); var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
return readKey(jwe); var keyGen = new ECKeyGenerator(Curve.P_384);
var ephemeralKeyPair = keyGen.generate();
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
var jwe = new JWEObject(header, payload);
jwe.encrypt(new ECDHEncrypter(deviceKey));
return jwe;
} catch (JOSEException e) { } catch (JOSEException e) {
LOG.warn("Failed to decrypt JWE: {}", jwe); throw new RuntimeException(e);
throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e);
} }
} }
private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException { public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
try {
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(deviceKey));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
try {
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
var factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(keySpec);
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
return ecPrivateKey;
} else {
throw new IllegalStateException(EC_ALG + " key factory not generating ECPrivateKeys");
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
}
}
public static Masterkey decryptVaultKey(JWEObject jwe, ECPrivateKey privateKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(privateKey));
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, Masterkey::new);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}
private static <T> T readKey(JWEObject jwe, String keyField, Function<byte[], T> rawKeyFactory) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED); Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
var fields = jwe.getPayload().toJSONObject(); var fields = jwe.getPayload().toJSONObject();
if (fields == null) { if (fields == null) {
@@ -39,11 +107,11 @@ class JWEHelper {
} }
var keyBytes = new byte[0]; var keyBytes = new byte[0];
try { try {
if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) { if (fields.get(keyField) instanceof String key) {
keyBytes = BaseEncoding.base64().decode(key); keyBytes = BaseEncoding.base64().decode(key);
return new Masterkey(keyBytes); return rawKeyFactory.apply(keyBytes);
} else { } else {
throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD); throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOG.error("Unexpected JWE payload: {}", jwe.getPayload()); LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
@@ -52,4 +120,11 @@ class JWEHelper {
Arrays.fill(keyBytes, (byte) 0x00); Arrays.fill(keyBytes, (byte) 0x00);
} }
} }
public static class InvalidJweKeyException extends MasterkeyLoadingFailedException {
public InvalidJweKeyException(Throwable cause) {
super("Invalid key", cause);
}
}
} }
@@ -0,0 +1,191 @@
package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dagger.Lazy;
import org.cryptomator.common.settings.DeviceKey;
import org.cryptomator.cryptolib.common.P384KeyPair;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class LegacyRegisterDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(LegacyRegisterDeviceController.class);
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409);
private final Stage window;
private final HubConfig hubConfig;
private final String bearerToken;
private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene;
private final String deviceId;
private final P384KeyPair keyPair;
private final CompletableFuture<ReceivedKey> result;
private final DecodedJWT jwt;
private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
public TextField deviceNameField;
public Button registerBtn;
@Inject
public LegacyRegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId;
this.keyPair = Objects.requireNonNull(deviceKey.get());
this.result = result;
this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene;
this.jwt = JWT.decode(this.bearerToken);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
}
public void initialize() {
deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
}
private String determineHostname() {
try {
var hostName = InetAddress.getLocalHost().getHostName();
return Objects.requireNonNullElse(hostName, "");
} catch (IOException e) {
return "";
}
}
@FXML
public void register() {
deviceNameAlreadyExists.set(false);
registerBtn.setContentDisplay(ContentDisplay.LEFT);
registerBtn.setDisable(true);
var deviceUri = URI.create(hubConfig.devicesResourceUrl + deviceId);
var deviceKey = keyPair.getPublic().getEncoded();
var dto = new CreateDeviceDto();
dto.id = deviceId;
dto.name = deviceNameField.getText();
dto.publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(deviceKey);
var json = toJson(dto);
var request = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json") //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
.thenApply(response -> {
if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) {
return response;
} else {
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
}
}).handleAsync((response, throwable) -> {
if (response != null) {
this.handleResponse(response);
} else {
this.registrationFailed(throwable);
}
return null;
}, Platform::runLater);
}
private String toJson(CreateDeviceDto dto) {
try {
return JSON.writer().writeValueAsString(dto);
} catch (JacksonException e) {
throw new IllegalStateException("Failed to serialize DTO", e);
}
}
private void handleResponse(HttpResponse<Void> voidHttpResponse) {
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode());
if (voidHttpResponse.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
registerBtn.setDisable(false);
} else {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get());
}
}
private void registrationFailed(Throwable cause) {
LOG.warn("Device registration failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
@FXML
public void close() {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
result.cancel(true);
}
/* Getter */
public String getUserName() {
return jwt.getClaim("email").asString();
}
//--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() {
return deviceNameAlreadyExists;
}
public boolean getDeviceNameAlreadyExists() {
return deviceNameAlreadyExists.get();
}
private static class CreateDeviceDto {
public String id;
public String name;
public final String type = "DESKTOP";
public String publicKey;
}
}
@@ -1,5 +1,8 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWEObject;
import dagger.Lazy; import dagger.Lazy;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
@@ -8,6 +11,9 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -17,14 +23,16 @@ import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -33,25 +41,32 @@ import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped @KeyLoadingScoped
public class ReceiveKeyController implements FxController { public class ReceiveKeyController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
private static final String SCHEME_PREFIX = "hub+"; private static final String SCHEME_PREFIX = "hub+";
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
private final Stage window; private final Stage window;
private final HubConfig hubConfig;
private final String deviceId; private final String deviceId;
private final String bearerToken; private final String bearerToken;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> registerDeviceScene; private final Lazy<Scene> setupDeviceScene;
private final Lazy<Scene> legacyRegisterDeviceScene;
private final Lazy<Scene> unauthorizedScene; private final Lazy<Scene> unauthorizedScene;
private final URI vaultBaseUri; private final URI vaultBaseUri;
private final Lazy<Scene> invalidLicenseScene; private final Lazy<Scene> invalidLicenseScene;
private final HttpClient httpClient; private final HttpClient httpClient;
@Inject @Inject
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) { public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_SETUP_DEVICE) Lazy<Scene> setupDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy<Scene> legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy<Scene> invalidLicenseScene) {
this.window = window; this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId; this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.result = result; this.result = result;
this.registerDeviceScene = registerDeviceScene; this.setupDeviceScene = setupDeviceScene;
this.legacyRegisterDeviceScene = legacyRegisterDeviceScene;
this.unauthorizedScene = unauthorizedScene; this.unauthorizedScene = unauthorizedScene;
this.vaultBaseUri = getVaultBaseUri(vault); this.vaultBaseUri = getVaultBaseUri(vault);
this.invalidLicenseScene = invalidLicenseScene; this.invalidLicenseScene = invalidLicenseScene;
@@ -61,23 +76,120 @@ public class ReceiveKeyController implements FxController {
@FXML @FXML
public void initialize() { public void initialize() {
var keyUri = appendPath(vaultBaseUri, "/keys/" + deviceId); requestVaultMasterkey();
var request = HttpRequest.newBuilder(keyUri) // }
/**
* STEP 1 (Request): GET vault key for this user
*/
private void requestVaultMasterkey() {
var accessTokenUri = appendPath(vaultBaseUri, "/access-token");
var request = HttpRequest.newBuilder(accessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) // .header("Authorization", "Bearer " + bearerToken) //
.GET() // .GET() //
.timeout(REQ_TIMEOUT) //
.build(); .build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) // httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(this::loadedExistingKey, Platform::runLater) // .thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) //
.exceptionally(this::retrievalFailed); .exceptionally(this::retrievalFailed);
} }
private void loadedExistingKey(HttpResponse<InputStream> response) { /**
* STEP 1 (Response): GET vault key for this user
*
* @param response Response
*/
private void receivedVaultMasterkey(HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
switch (response.statusCode()) {
case 200 -> requestUserKey(response.body());
case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog?
case 404 -> requestLegacyAccessToken();
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
}
/**
* STEP 2 (Request): GET user key for this device
*/
private void requestUserKey(String encryptedVaultKey) {
var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId);
var request = HttpRequest.newBuilder(deviceTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* STEP 2 (Response): GET user key for this device
*
* @param response Response
*/
private void receivedUserKey(String encryptedVaultKey, HttpResponse<String> response) {
LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode());
try { try {
switch (response.statusCode()) { switch (response.statusCode()) {
case 200 -> retrievalSucceeded(response); case 200 -> {
var device = JSON.reader().readValue(response.body(), DeviceDto.class);
receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey);
}
case 404 -> needsDeviceSetup(); // TODO: using the setup code, we can theoretically immediately unlock
default -> throw new IllegalStateException("Unexpected response " + response.statusCode());
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void needsDeviceSetup() {
window.setScene(setupDeviceScene.get());
}
private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException {
try {
var vaultKeyJwe = JWEObject.parse(encryptedVaultKey);
var userKeyJwe = JWEObject.parse(encryptedUserKey);
result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe));
window.close();
} catch (ParseException e) {
throw new IOException("Failed to parse JWE", e);
}
}
/**
* LEGACY FALLBACK (Request): GET the legacy access token from Hub 1.x
*/
@Deprecated
private void requestLegacyAccessToken() {
var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId);
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
.timeout(REQ_TIMEOUT) //
.build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) //
.thenAcceptAsync(this::receivedLegacyAccessTokenResponse, Platform::runLater) //
.exceptionally(this::retrievalFailed);
}
/**
* LEGACY FALLBACK (Response)
*
* @param response Response
*/
@Deprecated
private void receivedLegacyAccessTokenResponse(HttpResponse<String> response) {
try {
switch (response.statusCode()) {
case 200 -> receivedLegacyAccessTokenSuccess(response.body());
case 402 -> licenseExceeded(); case 402 -> licenseExceeded();
case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access
case 404 -> needsDeviceRegistration(); case 404 -> needsLegacyDeviceRegistration();
default -> throw new IOException("Unexpected response " + response.statusCode()); default -> throw new IOException("Unexpected response " + response.statusCode());
} }
} catch (IOException e) { } catch (IOException e) {
@@ -85,10 +197,11 @@ public class ReceiveKeyController implements FxController {
} }
} }
private void retrievalSucceeded(HttpResponse<InputStream> response) throws IOException { @Deprecated
private void receivedLegacyAccessTokenSuccess(String rawToken) throws IOException {
try { try {
var string = HttpHelper.readBody(response); var token = JWEObject.parse(rawToken);
result.complete(JWEObject.parse(string)); result.complete(ReceivedKey.legacyDeviceKey(token));
window.close(); window.close();
} catch (ParseException e) { } catch (ParseException e) {
throw new IOException("Failed to parse JWE", e); throw new IOException("Failed to parse JWE", e);
@@ -99,8 +212,9 @@ public class ReceiveKeyController implements FxController {
window.setScene(invalidLicenseScene.get()); window.setScene(invalidLicenseScene.get());
} }
private void needsDeviceRegistration() { @Deprecated
window.setScene(registerDeviceScene.get()); private void needsLegacyDeviceRegistration() {
window.setScene(legacyRegisterDeviceScene.get());
} }
private void accessNotGranted() { private void accessNotGranted() {
@@ -132,14 +246,17 @@ public class ReceiveKeyController implements FxController {
private static URI getVaultBaseUri(Vault vault) { private static URI getVaultBaseUri(Vault vault) {
try { try {
var kid = vault.getVaultConfigCache().get().getKeyId(); var url = vault.getVaultConfigCache().get().getKeyId();
assert kid.getScheme().startsWith(SCHEME_PREFIX); assert url.getScheme().startsWith(SCHEME_PREFIX);
var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length()); var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length());
return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment()); return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment());
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e); throw new IllegalStateException("URI constructed from params known to be valid", e);
} }
} }
@JsonIgnoreProperties(ignoreUnknown = true)
private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {}
} }
@@ -0,0 +1,45 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.cryptolib.api.Masterkey;
import java.security.interfaces.ECPrivateKey;
@FunctionalInterface
interface ReceivedKey {
/**
* Decrypts the vault key.
*
* @param deviceKey This device's private key.
* @return The decrypted vault key
*/
Masterkey decryptMasterkey(ECPrivateKey deviceKey);
/**
* Creates an unlock response object from the user key + vault key.
*
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device's user.
* @param userKeyJwe a JWE containing the user's private key, encrypted for this device.
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
*/
static ReceivedKey vaultKeyAndUserKey(JWEObject vaultKeyJwe, JWEObject userKeyJwe) {
return deviceKey -> {
var userKey = JWEHelper.decryptUserKey(userKeyJwe, deviceKey);
return JWEHelper.decryptVaultKey(vaultKeyJwe, userKey);
};
}
/**
* Creates an unlock response object from the received legacy "access token" JWE.
*
* @param vaultKeyJwe a JWE containing the symmetric vault key, encrypted for this device.
* @return Ciphertext received by Hub, which can be decrypted using this device's private key.
* @deprecated Only for compatibility with Hub 1.0 - 1.2
*/
@Deprecated
static ReceivedKey legacyDeviceKey(JWEObject vaultKeyJwe) {
return deviceKey -> JWEHelper.decryptVaultKey(vaultKeyJwe, deviceKey);
}
}
@@ -1,7 +1,7 @@
package org.cryptomator.ui.keyloading.hub; package org.cryptomator.ui.keyloading.hub;
import com.auth0.jwt.JWT; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
@@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -31,14 +32,16 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -47,7 +50,7 @@ public class RegisterDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class); private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true); private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
private static final List<Integer> EXPECTED_RESPONSE_CODES = List.of(201, 409); private static final Duration REQ_TIMEOUT = Duration.ofSeconds(10);
private final Stage window; private final Stage window;
private final HubConfig hubConfig; private final HubConfig hubConfig;
@@ -55,26 +58,27 @@ public class RegisterDeviceController implements FxController {
private final Lazy<Scene> registerSuccessScene; private final Lazy<Scene> registerSuccessScene;
private final Lazy<Scene> registerFailedScene; private final Lazy<Scene> registerFailedScene;
private final String deviceId; private final String deviceId;
private final P384KeyPair keyPair; private final P384KeyPair deviceKeyPair;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
private final DecodedJWT jwt;
private final HttpClient httpClient; private final HttpClient httpClient;
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
private final BooleanProperty deviceNameAlreadyExists = new SimpleBooleanProperty(false);
private final BooleanProperty invalidSetupCode = new SimpleBooleanProperty(false);
private final BooleanProperty workInProgress = new SimpleBooleanProperty(false);
public TextField setupCodeField;
public TextField deviceNameField; public TextField deviceNameField;
public Button registerBtn; public Button registerBtn;
@Inject @Inject
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<JWEObject> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) { public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<ReceivedKey> result, @Named("bearerToken") AtomicReference<String> bearerToken, @FxmlScene(FxmlFile.HUB_REGISTER_SUCCESS) Lazy<Scene> registerSuccessScene, @FxmlScene(FxmlFile.HUB_REGISTER_FAILED) Lazy<Scene> registerFailedScene) {
this.window = window; this.window = window;
this.hubConfig = hubConfig; this.hubConfig = hubConfig;
this.deviceId = deviceId; this.deviceId = deviceId;
this.keyPair = Objects.requireNonNull(deviceKey.get()); this.deviceKeyPair = Objects.requireNonNull(deviceKey.get());
this.result = result; this.result = result;
this.bearerToken = Objects.requireNonNull(bearerToken.get()); this.bearerToken = Objects.requireNonNull(bearerToken.get());
this.registerSuccessScene = registerSuccessScene; this.registerSuccessScene = registerSuccessScene;
this.registerFailedScene = registerFailedScene; this.registerFailedScene = registerFailedScene;
this.jwt = JWT.decode(this.bearerToken);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
} }
@@ -82,6 +86,13 @@ public class RegisterDeviceController implements FxController {
public void initialize() { public void initialize() {
deviceNameField.setText(determineHostname()); deviceNameField.setText(determineHostname());
deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false)); deviceNameField.textProperty().addListener(observable -> deviceNameAlreadyExists.set(false));
deviceNameField.disableProperty().bind(workInProgress);
setupCodeField.textProperty().addListener(observable -> invalidSetupCode.set(false));
setupCodeField.disableProperty().bind(workInProgress);
var missingSetupCode = setupCodeField.textProperty().isEmpty();
var missingDeviceName = deviceNameField.textProperty().isEmpty();
registerBtn.disableProperty().bind(workInProgress.or(missingSetupCode).or(missingDeviceName));
registerBtn.contentDisplayProperty().bind(Bindings.when(workInProgress).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY));
} }
private String determineHostname() { private String determineHostname() {
@@ -95,35 +106,62 @@ public class RegisterDeviceController implements FxController {
@FXML @FXML
public void register() { public void register() {
deviceNameAlreadyExists.set(false); workInProgress.set(true);
registerBtn.setContentDisplay(ContentDisplay.LEFT);
registerBtn.setDisable(true);
var keyUri = URI.create(hubConfig.devicesResourceUrl + deviceId); var apiRootUrl = hubConfig.getApiBaseUrl();
var deviceKey = keyPair.getPublic().getEncoded();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64Url().omitPadding().encode(deviceKey)); var userReq = HttpRequest.newBuilder(apiRootUrl.resolve("users/me")) //
var json = toJson(dto); .GET() //
var request = HttpRequest.newBuilder(keyUri) // .timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) // .header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) // .header("Content-Type", "application/json") //
.build(); .build();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) // httpClient.sendAsync(userReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) //
.thenApply(response -> { .thenApply(response -> {
if (EXPECTED_RESPONSE_CODES.contains(response.statusCode())) { if (response.statusCode() == 200) {
return response; var dto = fromJson(response.body());
return Objects.requireNonNull(dto, "null or empty response body");
} else { } else {
throw new RuntimeException("Server answered with unexpected status code " + response.statusCode()); throw new RuntimeException("Server answered with unexpected status code " + response.statusCode());
} }
}).handleAsync((response, throwable) -> { }).thenApply(user -> {
try {
assert user.privateKey != null; // api/vaults/{v}/user-tokens/me would have returned 403, if user wasn't fully set up yet
var userKey = JWEHelper.decryptUserKey(JWEObject.parse(user.privateKey), setupCodeField.getText());
return JWEHelper.encryptUserKey(userKey, deviceKeyPair.getPublic());
} catch (ParseException e) {
throw new RuntimeException("Server answered with unparsable user key", e);
}
}).thenCompose(jwe -> {
var now = Instant.now().toString();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
var json = toJson(dto);
var deviceUri = apiRootUrl.resolve("devices/" + deviceId);
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) //
.header("Content-Type", "application/json") //
.build();
return httpClient.sendAsync(putDeviceReq, HttpResponse.BodyHandlers.discarding());
}).whenCompleteAsync((response, throwable) -> {
if (response != null) { if (response != null) {
this.handleResponse(response); this.handleResponse(response);
} else { } else {
this.registrationFailed(throwable); this.setupFailed(throwable);
} }
return null; workInProgress.set(false);
}, Platform::runLater); }, Platform::runLater);
} }
private UserDto fromJson(String json) {
try {
return JSON.reader().readValue(json, UserDto.class);
} catch (IOException e) {
throw new IllegalStateException("Failed to deserialize DTO", e);
}
}
private String toJson(CreateDeviceDto dto) { private String toJson(CreateDeviceDto dto) {
try { try {
return JSON.writer().writeValueAsString(dto); return JSON.writer().writeValueAsString(dto);
@@ -132,23 +170,26 @@ public class RegisterDeviceController implements FxController {
} }
} }
private void handleResponse(HttpResponse<Void> voidHttpResponse) { private void handleResponse(HttpResponse<Void> response) {
assert EXPECTED_RESPONSE_CODES.contains(voidHttpResponse.statusCode()); if (response.statusCode() == 201) {
if (voidHttpResponse.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
registerBtn.setContentDisplay(ContentDisplay.TEXT_ONLY);
registerBtn.setDisable(false);
} else {
LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl); LOG.debug("Device registration for hub instance {} successful.", hubConfig.authSuccessUrl);
window.setScene(registerSuccessScene.get()); window.setScene(registerSuccessScene.get());
} else if (response.statusCode() == 409) {
deviceNameAlreadyExists.set(true);
} else {
setupFailed(new IllegalStateException("Unexpected http status code " + response.statusCode()));
} }
} }
private void registrationFailed(Throwable cause) { private void setupFailed(Throwable cause) {
LOG.warn("Device registration failed.", cause); switch (cause) {
window.setScene(registerFailedScene.get()); case CompletionException e when e.getCause() instanceof JWEHelper.InvalidJweKeyException -> invalidSetupCode.set(true);
result.completeExceptionally(cause); default -> {
LOG.warn("Device setup failed.", cause);
window.setScene(registerFailedScene.get());
result.completeExceptionally(cause);
}
}
} }
@FXML @FXML
@@ -160,13 +201,6 @@ public class RegisterDeviceController implements FxController {
result.cancel(true); result.cancel(true);
} }
/* Getter */
public String getUserName() {
return jwt.getClaim("email").asString();
}
//--- Getters & Setters //--- Getters & Setters
public BooleanProperty deviceNameAlreadyExistsProperty() { public BooleanProperty deviceNameAlreadyExistsProperty() {
@@ -177,5 +211,21 @@ public class RegisterDeviceController implements FxController {
return deviceNameAlreadyExists.get(); return deviceNameAlreadyExists.get();
} }
public BooleanProperty invalidSetupCodeProperty() {
return invalidSetupCode;
}
public boolean isInvalidSetupCode() {
return invalidSetupCode.get();
}
@JsonIgnoreProperties(ignoreUnknown = true)
private record UserDto(String id, String name, String publicKey, String privateKey, String setupCode) {}
private record CreateDeviceDto(@JsonProperty(required = true) String id, //
@JsonProperty(required = true) String name, //
@JsonProperty(required = true) String publicKey, //
@JsonProperty(required = true, defaultValue = "DESKTOP") String type, //
@JsonProperty(required = true) String userPrivateKey, //
@JsonProperty(required = true) String creationTime) {}
} }
@@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture;
public class RegisterFailedController implements FxController { public class RegisterFailedController implements FxController {
private final Stage window; private final Stage window;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
@Inject @Inject
public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) { public RegisterFailedController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
this.window = window; this.window = window;
this.result = result; this.result = result;
} }
@@ -15,10 +15,10 @@ import java.util.concurrent.CompletableFuture;
public class UnauthorizedDeviceController implements FxController { public class UnauthorizedDeviceController implements FxController {
private final Stage window; private final Stage window;
private final CompletableFuture<JWEObject> result; private final CompletableFuture<ReceivedKey> result;
@Inject @Inject
public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) { public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
this.window = window; this.window = window;
this.result = result; this.result = result;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
@@ -67,37 +66,7 @@ public class ResizeController implements FxController {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0); return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
} }
private boolean isWithinDisplayBounds() {
// (x1, y1) is the top left corner of the window, (x2, y2) is the bottom right corner
final double slack = 10;
final double width = window.getWidth() - 2 * slack;
final double height = window.getHeight() - 2 * slack;
final double x1 = window.getX() + slack;
final double y1 = window.getY() + slack;
final double x2 = x1 + width;
final double y2 = y1 + height;
final ObservableList<Screen> screens = Screen.getScreensForRectangle(x1, y1, width, height);
// Find the total visible area of the window
double visibleArea = 0;
for (Screen screen : screens) {
Rectangle2D bounds = screen.getVisualBounds();
double xOverlap = Math.min(x2, bounds.getMaxX()) - Math.max(x1, bounds.getMinX());
double yOverlap = Math.min(y2, bounds.getMaxY()) - Math.max(y1, bounds.getMinY());
visibleArea += xOverlap * yOverlap;
}
final double windowArea = width * height;
// Within bounds if the visible area matches the window area
return visibleArea == windowArea;
}
private void checkDisplayBounds(WindowEvent evt) { private void checkDisplayBounds(WindowEvent evt) {
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000) // Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453 // See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
// If the position is (-32000, -32000), restore to the last saved position // If the position is (-32000, -32000), restore to the last saved position
@@ -108,8 +77,9 @@ public class ResizeController implements FxController {
window.setHeight(settings.windowHeight.get()); window.setHeight(settings.windowHeight.get());
} }
if (!isWithinDisplayBounds()) { if (isOutOfDisplayBounds()) {
// If the position is illegal, then the window appears on the main screen in the middle of the window. // If the position is illegal, then the window appears on the main screen in the middle of the window.
LOG.debug("Resetting window position due to insufficient screen overlap");
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds(); Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2); window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2); window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
@@ -119,6 +89,22 @@ public class ResizeController implements FxController {
} }
} }
private boolean isOutOfDisplayBounds() {
// define a rect which is inset on all sides from the window's rect:
final double x = window.getX() + 20; // 20px left
final double y = window.getY() + 5; // 5px top
final double w = window.getWidth() - 40; // 20px left + 20px right
final double h = window.getHeight() - 25; // 5px top + 20px bottom
return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column
|| isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column
|| isRectangleOutOfScreen(x, y, w, 0) // Top pixel row
|| isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row
}
private boolean isRectangleOutOfScreen(double x, double y, double width, double height) {
return Screen.getScreensForRectangle(x, y, width, height).isEmpty();
}
private void startResize(MouseEvent evt) { private void startResize(MouseEvent evt) {
origX = window.getX(); origX = window.getX();
origY = window.getY(); origY = window.getY();
@@ -20,9 +20,10 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent; import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.DragEvent; import javafx.scene.input.DragEvent;
@@ -67,6 +68,8 @@ public class VaultListController implements FxController {
public ListView<Vault> vaultList; public ListView<Vault> vaultList;
public StackPane root; public StackPane root;
public Button addVaultBtn; public Button addVaultBtn;
@FXML
private ContextMenu addVaultContextMenu;
@Inject @Inject
VaultListController(@MainWindow Stage mainWindow, // VaultListController(@MainWindow Stage mainWindow, //
@@ -140,15 +143,15 @@ public class VaultListController implements FxController {
root.setOnDragOver(this::handleDragEvent); root.setOnDragOver(this::handleDragEvent);
root.setOnDragDropped(this::handleDragEvent); root.setOnDragDropped(this::handleDragEvent);
root.setOnDragExited(this::handleDragEvent); root.setOnDragExited(this::handleDragEvent);
addVaultBtn.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, Event::consume);
} }
@FXML @FXML
private void showMenu() { private void toggleMenu() {
double screenX = addVaultBtn.localToScreen(addVaultBtn.getBoundsInLocal()).getMinX(); if (addVaultContextMenu.isShowing()) {
double screenY = addVaultBtn.localToScreen(addVaultBtn.getBoundsInLocal()).getMaxY(); addVaultContextMenu.hide();
addVaultBtn.getContextMenu().show(addVaultBtn, screenX, screenY); } else {
addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0);
}
} }
private void deselect(MouseEvent released) { private void deselect(MouseEvent released) {
@@ -1,5 +1,6 @@
package org.cryptomator.ui.preferences; package org.cryptomator.ui.preferences;
import org.cryptomator.common.Environment;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker; import org.cryptomator.ui.fxapp.UpdateChecker;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -19,6 +20,7 @@ public class PreferencesController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class); private static final Logger LOG = LoggerFactory.getLogger(PreferencesController.class);
private final Environment env;
private final Stage window; private final Stage window;
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty; private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
private final BooleanBinding updateAvailable; private final BooleanBinding updateAvailable;
@@ -31,7 +33,8 @@ public class PreferencesController implements FxController {
public Tab aboutTab; public Tab aboutTab;
@Inject @Inject
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) { public PreferencesController(Environment env, @PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
this.env = env;
this.window = window; this.window = window;
this.selectedTabProperty = selectedTabProperty; this.selectedTabProperty = selectedTabProperty;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull(); this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
@@ -42,6 +45,9 @@ public class PreferencesController implements FxController {
window.setOnShowing(this::windowWillAppear); window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab()); selectedTabProperty.addListener(observable -> this.selectChosenTab());
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged()); tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
if (env.disableUpdateCheck()) {
tabPane.getTabs().remove(updatesTab);
}
} }
private void selectChosenTab() { private void selectChosenTab() {
@@ -8,6 +8,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -58,7 +59,7 @@ public class RecoveryKeyFactory {
} }
} }
// visible for testing @VisibleForTesting
String createRecoveryKey(byte[] rawKey) { String createRecoveryKey(byte[] rawKey) {
Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes"); Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes");
byte[] paddedKey = Arrays.copyOf(rawKey, 66); byte[] paddedKey = Arrays.copyOf(rawKey, 66);
@@ -15,7 +15,7 @@
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?> <?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<HBox xmlns:fx="http://javafx.com/fxml" <HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterDeviceController" fx:controller="org.cryptomator.ui.keyloading.hub.LegacyRegisterDeviceController"
minWidth="400" minWidth="400"
maxWidth="400" maxWidth="400"
minHeight="145" minHeight="145"
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.RegisterDeviceController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="INFO" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%hub.register.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.register.description" wrapText="true"/>
<HBox spacing="6" alignment="CENTER_LEFT">
<padding>
<Insets top="12"/>
</padding>
<Label text="%hub.register.setupCodeLabel" labelFor="$setupCodeField"/>
<TextField fx:id="setupCodeField" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox spacing="6" alignment="CENTER_LEFT">
<padding>
<Insets top="12"/>
</padding>
<Label text="%hub.register.nameLabel" labelFor="$deviceNameField"/>
<TextField fx:id="deviceNameField" HBox.hgrow="ALWAYS"/>
</HBox>
<HBox alignment="TOP_RIGHT">
<Label text="%hub.register.occupiedMsg" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.deviceNameAlreadyExists}" managed="${controller.deviceNameAlreadyExists}" graphicTextGap="6">
<padding>
<Insets top="6"/>
</padding>
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
<Label text="%hub.register.invalidSetupCode" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.invalidSetupCode}" managed="${controller.invalidSetupCode}" graphicTextGap="6">
<padding>
<Insets top="6"/>
</padding>
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
</HBox>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CU">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="registerBtn" text="%hub.register.registerBtn" ButtonBar.buttonData="OTHER" defaultButton="true" onAction="#register" contentDisplay="TEXT_ONLY" >
<graphic>
<FontAwesome5Spinner glyphSize="12" />
</graphic>
</Button>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>
+17 -17
View File
@@ -28,27 +28,27 @@
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/> <Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
</VBox> </VBox>
</StackPane> </StackPane>
<Button fx:id="addVaultBtn" onAction="#showMenu" styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" alignment="BASELINE_CENTER" maxWidth="Infinity" contentDisplay="RIGHT"> <Button fx:id="addVaultBtn" onAction="#toggleMenu" styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" alignment="BASELINE_CENTER" maxWidth="Infinity" contentDisplay="RIGHT">
<graphic> <graphic>
<FontAwesome5IconView glyph="CARET_DOWN"/> <FontAwesome5IconView glyph="CARET_DOWN"/>
</graphic> </graphic>
<contextMenu>
<ContextMenu>
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</contextMenu>
</Button> </Button>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
</VBox> </VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/> <Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
</StackPane> </StackPane>
+4 -2
View File
@@ -154,9 +154,11 @@ hub.auth.loginLink=Not redirected? Click here to open it.
hub.receive.message=Processing response… hub.receive.message=Processing response…
hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait. hub.receive.description=Cryptomator is receiving and processing the response from Hub. Please wait.
### Register Device ### Register Device
hub.register.message=Device name required hub.register.message=New Device
hub.register.description=This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device. hub.register.description=This is the first Hub access from this device. Please authorize it using your setup code.
hub.register.nameLabel=Device Name hub.register.nameLabel=Device Name
hub.register.setupCodeLabel=Setup Code
hub.register.invalidSetupCode=Invalid Setup Code
hub.register.occupiedMsg=Name already in use hub.register.occupiedMsg=Name already in use
hub.register.registerBtn=Confirm hub.register.registerBtn=Confirm
### Registration Success ### Registration Success
@@ -153,8 +153,6 @@ hub.auth.loginLink=لم يتم إعادة توجيهك؟ انقر هنا لفت
hub.receive.message=معالجة الاستجابة… hub.receive.message=معالجة الاستجابة…
hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار. hub.receive.description=Cryptomator يتلقى ويعالج الاستجابة من المركز. الرجاء الانتظار.
### Register Device ### Register Device
hub.register.message=اسم الجهاز متطلب
hub.register.description=يبدو أن هذا هو أول وصول للمركز من هذا الجهاز. من أجل تحديده للحصول على إذن الوصول، تحتاج إلى تسمية هذا الجهاز.
hub.register.nameLabel=اسم الجهاز hub.register.nameLabel=اسم الجهاز
hub.register.occupiedMsg=الاسم مستخدم مسبقاً hub.register.occupiedMsg=الاسم مستخدم مسبقاً
hub.register.registerBtn=تأكيد hub.register.registerBtn=تأكيد
@@ -138,8 +138,6 @@ hub.auth.loginLink=Не перанакіраваўся? Пстрыкні сюд
hub.receive.message=Апрацоўка адказу… hub.receive.message=Апрацоўка адказу…
hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай. hub.receive.description=Cryptomator атрымлівае ды апрацоўвае адказ ад Hub. Калі ласка, пачакай.
### Register Device ### Register Device
hub.register.message=Патрабуецца назва прылады
hub.register.description=Здаецца, што ты ў першы раз увайшла/-оў у Hub з гэтай прылады. Каб ідэнтыфікаваць яе для спраўджання доступу, табе трэба назваць гэтую прыладу.
hub.register.nameLabel=Назва прылады hub.register.nameLabel=Назва прылады
hub.register.occupiedMsg=Назва ўжо ўжытая hub.register.occupiedMsg=Назва ўжо ўжытая
hub.register.registerBtn=Пацвердзіць hub.register.registerBtn=Пацвердзіць
+264 -6
View File
@@ -20,6 +20,11 @@ error.description=Това е неочаквано за Криптоматор.
error.hyperlink.lookup=Търсене на грешката error.hyperlink.lookup=Търсене на грешката
error.hyperlink.report=Докладване на грешката error.hyperlink.report=Докладване на грешката
error.technicalDetails=Подробности: error.technicalDetails=Подробности:
error.existingSolutionDescription=Това е неочаквано за Криптоматор, но открихме съществуващо решение. Прочетете повече на следната препратка.
error.hyperlink.solution=Преглеждане на решението
error.lookupPermissionMessage=Криптоматор може да потърси решение за проблема. За целта ще направи заявка към базата от знания от вашия IP адрес.
error.dismiss=Отхвърляне
error.lookUpSolution=Търсене на решение
# Defaults # Defaults
defaults.vault.vaultName=Хранилище defaults.vault.vaultName=Хранилище
@@ -36,6 +41,7 @@ traymenu.vault.reveal=Разкриване
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Добавяне на хранилище addvaultwizard.title=Добавяне на хранилище
## New ## New
addvaultwizard.new.title=Добавяне на хранилище
### Name ### Name
addvaultwizard.new.nameInstruction=Изберете име на хранилището addvaultwizard.new.nameInstruction=Изберете име на хранилището
addvaultwizard.new.namePrompt=Наименование addvaultwizard.new.namePrompt=Наименование
@@ -57,12 +63,18 @@ addvaultwizard.new.validCharacters.chars=Букви (напр. a, ж или 수)
addvaultwizard.new.validCharacters.numbers=Числа addvaultwizard.new.validCharacters.numbers=Числа
addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s) addvaultwizard.new.validCharacters.dashes=Тире (%s) или долна черта (%s)
### Expert Settings ### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Разширени настройки
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Въведете стойност между 36 и 220 (по подразбиране 200)
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Прочетете повече в документацията.
addvaultwizard.new.expertSettings.shorteningThreshold.title=Максимална дължина на шифрованите имена на файлове
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Валидно
### Password ### Password
addvaultwizard.new.createVaultBtn=Създаване addvaultwizard.new.createVaultBtn=Създаване
addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си? addvaultwizard.new.generateRecoveryKeyChoice=Без парола няма да имате достъп до данните си. Желаете ли да бъде създаден ключ за възстановяване, в случай че загубите паролата си?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, нека имам за всеки случай addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, нека имам за всеки случай
addvaultwizard.new.generateRecoveryKeyChoice.no=Не, няма да загубя паролата си addvaultwizard.new.generateRecoveryKeyChoice.no=Не, няма да загубя паролата си
### Information ### Information
addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛОВЕ НА ХРАНИЛИЩЕ ⚠️ addvault.new.readme.storageLocation.1=⚠️ ФАЙЛОВЕ НА ХРАНИЛИЩЕ ⚠️
addvault.new.readme.storageLocation.2=Това е местоположението на хранилището. addvault.new.readme.storageLocation.2=Това е местоположението на хранилището.
addvault.new.readme.storageLocation.3=НЕДЕЙТЕ addvault.new.readme.storageLocation.3=НЕДЕЙТЕ
@@ -73,12 +85,14 @@ addvault.new.readme.storageLocation.7=1. Добавете това хранил
addvault.new.readme.storageLocation.8=2. Отключете хранилището в Криптоматор. addvault.new.readme.storageLocation.8=2. Отключете хранилището в Криптоматор.
addvault.new.readme.storageLocation.9=3. Отворете местоположението на съдържанието чрез бутона „Разкриване“. addvault.new.readme.storageLocation.9=3. Отворете местоположението на съдържанието чрез бутона „Разкриване“.
addvault.new.readme.storageLocation.10=Ако имате нужда от помощ, посетете документацията: %s addvault.new.readme.storageLocation.10=Ако имате нужда от помощ, посетете документацията: %s
addvault.new.readme.accessLocation.fileName=ДОБРЕ ДОШЛИ.rtf
addvault.new.readme.accessLocation.1=🔐️ ШИФРОВАН ДЯЛ 🔐️ addvault.new.readme.accessLocation.1=🔐️ ШИФРОВАН ДЯЛ 🔐️
addvault.new.readme.accessLocation.2=Това е местоположението на съдържанието на хранилището. addvault.new.readme.accessLocation.2=Това е местоположението на съдържанието на хранилището.
addvault.new.readme.accessLocation.3=Файловете, в този дял са шифроване от Криптоматор. Можете да работите с тях както с всеки друг диск или папка. Това е само разшифрован вариант на съдуржанието. Файловете остават шифровани на твърдия диск през цялото време. addvault.new.readme.accessLocation.3=Файловете, в този дял са шифроване от Криптоматор. Можете да работите с тях както с всеки друг диск или папка. Това е само разшифрован вариант на съдуржанието. Файловете остават шифровани на твърдия диск през цялото време.
addvault.new.readme.accessLocation.4=При желание можете да премахнете този файл. addvault.new.readme.accessLocation.4=При желание можете да премахнете този файл.
## Existing ## Existing
addvaultwizard.existing.instruction=Изберете файла „vault.cryptomator“ от съществуващото хранилище. Но ако съществува файл „masterkey.cryptomator“, изберете него. addvaultwizard.existing.title=Добавяне на съществуващо хранилище
addvaultwizard.existing.instruction=Изберете файла „vault.cryptomator“ от съществуващото хранилище, но ако има само файл „masterkey.cryptomator“, изберете него.
addvaultwizard.existing.chooseBtn=Избиране… addvaultwizard.existing.chooseBtn=Избиране…
addvaultwizard.existing.filePickerTitle=Избор на файл на хранилището addvaultwizard.existing.filePickerTitle=Избор на файл на хранилището
addvaultwizard.existing.filePickerMimeDesc=Хранилище на Криптоматор addvaultwizard.existing.filePickerMimeDesc=Хранилище на Криптоматор
@@ -122,6 +136,11 @@ unlock.success.revealBtn=Разкриване на диска
unlock.error.customPath.message=Хранилището не може да бъде монтирано в потребителския път unlock.error.customPath.message=Хранилището не може да бъде монтирано в потребителския път
unlock.error.customPath.description.notSupported=Ако искате да продължите да използвате потребитрлския път, отидете в настройките и изберете вид на дял, който поддържа потребителски пътища. В противен случай отидете в настройките на хранилището и изберете поддържана точка за монтиране. unlock.error.customPath.description.notSupported=Ако искате да продължите да използвате потребитрлския път, отидете в настройките и изберете вид на дял, който поддържа потребителски пътища. В противен случай отидете в настройките на хранилището и изберете поддържана точка за монтиране.
unlock.error.customPath.description.notExists=Потребителският път на монтиране не съществува. Създайте го в местната файлова система или го променете в настройките на хранилището. unlock.error.customPath.description.notExists=Потребителският път на монтиране не съществува. Създайте го в местната файлова система или го променете в настройките на хранилището.
unlock.error.customPath.description.inUse=Буквата на диска или потребителският път на монтиране „%s“ е зает.
unlock.error.customPath.description.hideawayNotDir=Временният, скрит файл „%3$s“, използван за отключване не може да бъде премахнат. Проверете файла и го изтройте ръчно.
unlock.error.customPath.description.couldNotBeCleaned=Хранилището не може да бъде монтирано на „%s“. Опитайте отново или изберете друг път.
unlock.error.customPath.description.notEmptyDir=Потребителският път на монтиране „%s“ не е празна папка. Изберете празна папка и опитайте отново.
unlock.error.customPath.description.generic=Избрали сте потребителски път за монтиране на това хранилище, но при използването му възникна следната грешка: %2$s
## Hub ## Hub
hub.noKeychain.message=Няма достъп до ключа на устройството hub.noKeychain.message=Няма достъп до ключа на устройството
hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките. hub.noKeychain.description=За да отключите хранилищата в Hub е необходим ключ за устройството, който се защитава с помощта на ключодържател. За да продължите, разрешете „%s“ и изберете ключодържателя в настройките.
@@ -134,9 +153,11 @@ hub.auth.loginLink=Не сте пренасочени? Щракнете тук,
hub.receive.message=Обработване на отговора… hub.receive.message=Обработване на отговора…
hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте. hub.receive.description=Криптоматор получава и обработва отговора от Hub. Изчакайте.
### Register Device ### Register Device
hub.register.message=Изисква се име на устройство hub.register.message=Ново устройство
hub.register.description=Изглежда, че това е първи достъп до Hub от това устройство. За да го разпознаете при разрешаване на достъпа, трябва да му дадете име. hub.register.description=За пръв път посещавате Hub от това устройство. Удостоверете го, като използвате кода за настройка.
hub.register.nameLabel=Име на устройството hub.register.nameLabel=Име на устройството
hub.register.setupCodeLabel=Код за настройка
hub.register.invalidSetupCode=Неприемлив код за настройка
hub.register.occupiedMsg=Това име вече е заето hub.register.occupiedMsg=Това име вече е заето
hub.register.registerBtn=Потвърждаване hub.register.registerBtn=Потвърждаване
### Registration Success ### Registration Success
@@ -150,28 +171,75 @@ hub.unauthorized.message=Отказан достъп
hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика. hub.unauthorized.description=Устройството не е упълномощено за достъп до това хранилище. Поискайте достъп от собственика.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Лиценза за Hub е недействителен hub.invalidLicense.message=Лиценза за Hub е недействителен
hub.invalidLicense.description=Лиценза на екземпляра на Концентратора на Криптоматор който вие използвате е лиценз. Информирайте администратора на Концентратора, за да поднови или надгради лиценза.
# Lock # Lock
## Force ## Force
lock.forced.message=Грешка при заключване
lock.forced.description=Хранилището „%s“ не може да бъде заключено, поради извършващо се действие или отворен файл. Можете принудително да го заключите, но прекъсването на действия на четене/писане в хранилището може да доведе до загуба на незапазени данни.
lock.forced.retryBtn=Повторен опит
lock.forced.forceBtn=Принудително заключване
## Failure ## Failure
lock.fail.message=Грешка при заключване на хранилището
lock.fail.description=Хранилището „%s“ не може да бъде заключено. Уверете се, че разполагате другаде с назапазената информация и че действията на четене/писане в хранилището са завършили. За да бъде затворено хранилището, трябва процесът на Криптоматор да бъда прекъснат.
# Migration # Migration
migration.title=Надграждане на хранилище
## Start ## Start
migration.start.header=Надграждане на хранилището
migration.start.text=За да може това ново издание на Криптоматор да отвори хранилището „%s“, то трябва да бъде надстроено до новия формат. Преди да го надстроите, трябва да знаете следното:
migration.start.remarkUndone=Надграждането е еднопосочен процес.
migration.start.remarkVersions=По-ранните издания на Криптоматор няма да могат да отварят надградените хранилища.
migration.start.remarkCanRun=Трябва да сте сигурни, че всички устройства от които достъпвате хранилището могат да работят с това издание на Криптоматор.
migration.start.remarkSynced=Трябва да сте сигурни, че хранилището е напълно синхронизирано между всички устройства, преди да пристъпите към надграждане.
migration.start.confirm=Прочетох и разбрах гореизложената информация
## Run ## Run
migration.run.enterPassword=Въведете паролата за „%s“
migration.run.startMigrationBtn=Надграждане на хранилището
migration.run.progressHint=Ще отнеме малко време…
## Success ## Success
migration.success.nextStepsInstructions=Хранилището „%s“ е надградено.\nМожете да го отключите.
migration.success.unlockNow=Отключване сега migration.success.unlockNow=Отключване сега
## Missing file system capabilities ## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Неподдържана файлова система migration.error.missingFileSystemCapabilities.title=Неподдържана файлова система
migration.error.missingFileSystemCapabilities.description=Не е започнато надграждане, защото хранилището се намира на неподдържана файлова система.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловата система не поддържа дълги имена на файлове.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловата система не поддържа дълги пътища.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файловата система не дава права за четене.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Файловата система не дава права за писане.
## Impossible ## Impossible
migration.impossible.heading=Хранилището не е надградено
migration.impossible.reason=Хранилището не може да бъде надградено автоматично, защото местоположението или точката за достъп са несъвместими.
migration.impossible.moreInfo=Хранилището миже да бъде отворено с по-ранно издание на Криптоматор. За инструкции как ръчно да надстроите хранилището постете
# Health Check # Health Check
## Start ## Start
health.title=Проверка на състоянието на „%s“ health.title=Проверка на състоянието на „%s“
health.intro.header=Проверка на състоянието health.intro.header=Проверка на състоянието
health.intro.text=Проверката на състоянието е набор от проверки и евентуално поправки за проблеми във вътрешната структура на хранилището. Имайте предвид, че: health.intro.text=Проверката на състоянието е набор от проверки и евентуално поправки за проблеми във вътрешната структура на хранилището. Имайте предвид, че:
health.intro.remarkSync=Трябва да сте сигурни, че хранилището е напълно синхронизирано между всички устройства, по този начин ще резрешите повечето проблеми.
health.intro.remarkFix=Не всички проблеми могат да бъдат разрешени.
health.intro.remarkBackup=Ако данните са повредени, само наличие на резервно копие ще помогне.
health.intro.affirmation=Прочетох и разбрах гореизложената информация
## Start Failure ## Start Failure
health.fail.header=Грешка при зареждане на настройките на хранилището
health.fail.ioError=Грешка при достъп или четене на файла с настройките.
health.fail.parseError=Грешка при разбор на файла с настройките на хранилището.
health.fail.moreInfo=Повече информация
## Check Selection ## Check Selection
health.checkList.description=Използвайте отметките отляво или бутоните отдолу.
health.checkList.selectAllButton=Избор всичко
health.checkList.deselectAllButton=Избор нищо
health.check.runBatchBtn=Извършване на избраните
## Detail view ## Detail view
health.check.detail.noSelectedCheck=За резултати изберете завършила проверка от списъка от ляво.
health.check.detail.checkScheduled=Извършването на проверката предстои.
health.check.detail.checkRunning=Проверката в момента се извършва…
health.check.detail.checkSkipped=Проверката не е избрана за извършване.
health.check.detail.checkFinished=Проверката е завършена.
health.check.detail.checkFinishedAndFound=Проверката е извършена. Прегледайте резултатите.
health.check.detail.checkFailed=Проверката не е завършена поради грешка.
health.check.detail.checkCancelled=Проверката е прекъсната.
health.check.detail.listFilters.label=Филтър health.check.detail.listFilters.label=Филтър
health.check.detail.fixAllSpecificBtn=Поправка всички от вида health.check.detail.fixAllSpecificBtn=Поправка всички от вида
health.check.exportBtn=Отчет health.check.exportBtn=Отчет
@@ -180,79 +248,269 @@ health.result.severityFilter.all=Сериозност - Всички
health.result.severityFilter.good=Добре health.result.severityFilter.good=Добре
health.result.severityFilter.info=Информация health.result.severityFilter.info=Информация
health.result.severityFilter.warn=Предупреждение health.result.severityFilter.warn=Предупреждение
health.result.severityFilter.crit=Криточно health.result.severityFilter.crit=Критично
health.result.severityTip.good=Сериозност: Добре\nСтруктурата на хранилището е наред.
health.result.severityTip.info=Сериозност: Информация\nСтруктурата на хранилището е непокътната, препоръчва се поправка.
health.result.severityTip.warn=Сериозност: Предупреждение\nСтруктурата на хранилището е нарушена, поправката е силно препоръчителна.
health.result.severityTip.crit=Сериозност: Критично\nСтруктурата на хранилището е нарушена, установена е загуба на информация.
health.result.fixStateFilter.all=Поправимост - Всички
health.result.fixStateFilter.fixable=Поправими
health.result.fixStateFilter.notFixable=Непоправими
health.result.fixStateFilter.fixing=Поправяне…
health.result.fixStateFilter.fixed=Поправени
health.result.fixStateFilter.fixFailed=Грешка при поправка
## Fix Application ## Fix Application
health.fix.fixBtn=Поправяне
health.fix.successTip=Поправката е успешна
health.fix.failTip=Грешка при поправката, вижте дневника за подробности
# Preferences # Preferences
preferences.title=Настройки preferences.title=Настройки
## General ## General
preferences.general=Общи
preferences.general.startHidden=Скриване на прозореца при отваряне на Криптоматор
preferences.general.autoCloseVaults=Заключване на хранилищата при затваряне на приложението
preferences.general.debugLogging=Дневник за отстраняване на дефекти
preferences.general.debugDirectory=Файлове на дневниците
preferences.general.autoStart=Отваряне на Криптоматор при старт на системата
preferences.general.keychainBackend=Съхраняванеа паролите в
## Interface ## Interface
preferences.interface.theme=Оформление preferences.interface=Външен вид
preferences.interface.theme=Тема
preferences.interface.theme.automatic=Автоматично preferences.interface.theme.automatic=Автоматично
preferences.interface.theme.dark=Тъмна
preferences.interface.theme.light=Светла
preferences.interface.unlockThemes=Отключване на тъмната тема
preferences.interface.language=Език (изисква рестарт)
preferences.interface.language.auto=Подразбиран
preferences.interface.interfaceOrientation=Посока на текста
preferences.interface.interfaceOrientation.ltr=От ляво на дясно
preferences.interface.interfaceOrientation.rtl=От дясно на ляво
preferences.interface.showMinimizeButton=Бутон за скриване
preferences.interface.showTrayIcon=Икона в областта за известия (изисква рестарт)
## Volume ## Volume
preferences.volume=Виртуален диск
preferences.volume.type=Вид на тома
preferences.volume.type.automatic=Автоматично preferences.volume.type.automatic=Автоматично
preferences.volume.docsTooltip=Вижте документацията относно видовете томове.
preferences.volume.fuseRestartRequired=За да бъдат приложени промените, Криптоматор трябва да бъде рестартиран.
preferences.volume.tcp.port=Порт на TCP
preferences.volume.supportedFeatures=Избрания вид на тома има следните възможности:
preferences.volume.feature.mountAuto=Автоматичен избор на точка за монтиране
preferences.volume.feature.mountToDir=Папка по избор като точка за монтиране
preferences.volume.feature.mountToDriveLetter=Буква на диск като точка за монтиране
preferences.volume.feature.mountFlags=Потребителски настройки на монтиране
preferences.volume.feature.readOnly=Монтиране само за четене
## Updates ## Updates
preferences.updates=Издания
preferences.updates.currentVersion=Текущо издание: %s
preferences.updates.autoUpdateCheck=Автоматична проверка за издания
preferences.updates.checkNowBtn=Проверяване
preferences.updates.updateAvailable=Налично е ново издание %s.
## Contribution ## Contribution
preferences.contribute=Подкрепете ни
preferences.contribute.registeredFor=Сертификатът за дарение е регистриран на %s
preferences.contribute.noCertificate=Ако подкрепите Криптоматор ще получите сертификат за дарение. Това е като лицензен ключ, но за готини хора, които използват безплатен софтуер. ;-)
preferences.contribute.getCertificate=Все още нямате? Научете как да се сдобиете.
preferences.contribute.promptText=Поставете тук кода на сертификата за дарение
#<-- Add entries for donations and code/translation/documentation contribution --> #<-- Add entries for donations and code/translation/documentation contribution -->
## About ## About
preferences.about=Относно
# Vault Statistics # Vault Statistics
stats.title=Статистика на %s
stats.cacheHitRate=Съотношения на попадения
## Read ## Read
stats.read.throughput.idle=Четене: бездействие
stats.read.throughput.kibs=Четене: %.2f КБ/с
stats.read.throughput.mibs=Четене: %.2f МБ/с
stats.read.total.data.none=Прочетени данни: -
stats.read.total.data.kib=Прочетени данни: %.1f КБ
stats.read.total.data.mib=Прочетени данни: %.1f МБ
stats.read.total.data.gib=Прочетени данни: %.1f ГБ
stats.decr.total.data.none=Разшифровани данни: -
stats.decr.total.data.kib=Разшифровани данни: %.1f КБ
stats.decr.total.data.mib=Разшифровани данни: %.1f МБ
stats.decr.total.data.gib=Разшифровани данни: %.1f ГБ
stats.read.accessCount=Общо прочитания: %d
## Write ## Write
stats.write.throughput.idle=Писане: бездействие
stats.write.throughput.kibs=Писане: %.2f КБ/с
stats.write.throughput.mibs=Писане: %.2f МБ/с
stats.write.total.data.none=Записани данни: -
stats.write.total.data.kib=Записани данни: %.1f КБ
stats.write.total.data.mib=Записани данни: %.1f МБ
stats.write.total.data.gib=Записани данни: %.1f ГБ
stats.encr.total.data.none=Шифровани данни: -
stats.encr.total.data.kib=Шифровани данни: %.1f КБ
stats.encr.total.data.mib=Шифровани данни: %.1f МБ
stats.encr.total.data.gib=Шифровани данни: %.1f ГБ
stats.write.accessCount=Общо записвания: %d
## Accesses ## Accesses
stats.access.current=Достъпване: %d
stats.access.total=Общо достъпвания: %d
# Main Window # Main Window
main.closeBtn.tooltip=Затваряне main.closeBtn.tooltip=Затваряне
main.minimizeBtn.tooltip=Скриване
main.preferencesBtn.tooltip=Настройки main.preferencesBtn.tooltip=Настройки
main.debugModeEnabled.tooltip=Режимът за премахване на дефекти е включен
main.supporterCertificateMissing.tooltip=Обмислете дарение
## Vault List ## Vault List
main.vaultlist.emptyList.onboardingInstruction=Щракнете, за да добавите хранилище
main.vaultlist.contextMenu.remove=Премахване…
main.vaultlist.contextMenu.lock=Заключване main.vaultlist.contextMenu.lock=Заключване
main.vaultlist.contextMenu.unlock=Отключване… main.vaultlist.contextMenu.unlock=Отключване…
main.vaultlist.contextMenu.unlockNow=Отключване сега main.vaultlist.contextMenu.unlockNow=Отключване сега
main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището main.vaultlist.contextMenu.vaultoptions=Настройки на хранилището
main.vaultlist.contextMenu.reveal=Разкриване на диска main.vaultlist.contextMenu.reveal=Разкриване на диска
main.vaultlist.addVaultBtn=Добавяне
main.vaultlist.addVaultBtn.menuItemNew=Ново хранилище…
main.vaultlist.addVaultBtn.menuItemExisting=Съществуващо хранилище…
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Благодарим ви, че избрахте Криптоматор, за да предпазвате файловете си. Ако имате нужда от съдействие прочетете ръководствата за започване на работа с приложението:
### Locked ### Locked
main.vaultDetail.lockedStatus=ЗАКЛЮЧЕНО
main.vaultDetail.unlockBtn=Отключване… main.vaultDetail.unlockBtn=Отключване…
main.vaultDetail.unlockNowBtn=Отключване сега main.vaultDetail.unlockNowBtn=Отключване сега
main.vaultDetail.optionsBtn=Настройки на хранилището
main.vaultDetail.passwordSavedInKeychain=Паролата е запазена
### Unlocked ### Unlocked
main.vaultDetail.unlockedStatus=ОТКЛЮЧЕНО main.vaultDetail.unlockedStatus=ОТКЛЮЧЕНО
main.vaultDetail.accessLocation=Съдържанието на хранилището е достъпно тук:
main.vaultDetail.revealBtn=Разкриване на диска main.vaultDetail.revealBtn=Разкриване на диска
main.vaultDetail.copyUri=Копиране на URI
main.vaultDetail.lockBtn=Заключване main.vaultDetail.lockBtn=Заключване
main.vaultDetail.bytesPerSecondRead=Четене:
main.vaultDetail.bytesPerSecondWritten=Писане:
main.vaultDetail.throughput.idle=бездействие
main.vaultDetail.throughput.kbps=%.1f КБ/с
main.vaultDetail.throughput.mbps=%.1f МБ/с
main.vaultDetail.stats=Статистики на хранилището
main.vaultDetail.locateEncryptedFileBtn=Намиране на шифровани файлове
main.vaultDetail.locateEncryptedFileBtn.tooltip=Изберете файл от хранилището, за да бъде намерено шифрованото му копие
main.vaultDetail.encryptedPathsCopied=Пътищата са копирани!
main.vaultDetail.filePickerTitle=Изберете файл от хранилището
### Missing ### Missing
main.vaultDetail.missing.info=Криптоматор не намира хранилище на това място.
main.vaultDetail.missing.recheck=Повторен опит
main.vaultDetail.missing.remove=Премахване от списъка с хранилищата…
main.vaultDetail.missing.changeLocation=Смяна на мястото на хранилището…
### Needs Migration ### Needs Migration
main.vaultDetail.migrateButton=Надграждане на хранилище
main.vaultDetail.migratePrompt=Преди да можете да го достъпвате, хранилището трябва да бъде надградено до новия формат
### Error ### Error
main.vaultDetail.error.info=Грешка при зареждане на хранилището от диска.
main.vaultDetail.error.reload=Презареждане
main.vaultDetail.error.windowTitle=Грешка при зареждане на хранилще
# Wrong File Alert # Wrong File Alert
wrongFileAlert.title=Как да шифровате файлове
wrongFileAlert.message=Пробвахте ли да шифровате файловете?
wrongFileAlert.description=За тази цел Криптоматор създава том, достъпен от приложението за управление на файлове.
wrongFileAlert.instruction.0=За да шифровета файлове следвайте тези стъпки:
wrongFileAlert.instruction.1=1. Отключете хранилището.
wrongFileAlert.instruction.2=2. Щракнете върху „Разкриване“, за да се покаже хранилището.
wrongFileAlert.instruction.3=3. Добавете файловете в тома.
wrongFileAlert.link=За допълнителна помощ, посетете
# Vault Options # Vault Options
## General ## General
vaultOptions.general=Общи
vaultOptions.general.vaultName=Наименование vaultOptions.general.vaultName=Наименование
vaultOptions.general.autoLock.lockAfterTimePart1=Заключване при бездействие от
vaultOptions.general.autoLock.lockAfterTimePart2=минути
vaultOptions.general.unlockAfterStartup=Отключване на хранилището при старт на Криптоматор
vaultOptions.general.actionAfterUnlock=След отключване
vaultOptions.general.actionAfterUnlock.ignore=Без действие
vaultOptions.general.actionAfterUnlock.reveal=Разкриване на диска vaultOptions.general.actionAfterUnlock.reveal=Разкриване на диска
vaultOptions.general.actionAfterUnlock.ask=Запитване vaultOptions.general.actionAfterUnlock.ask=Запитване
vaultOptions.general.startHealthCheckBtn=Проверка на състоянието
## Mount ## Mount
vaultOptions.mount=Монтиране
vaultOptions.mount.info=Възможностите зависят от вида на избрания том.
vaultOptions.mount.linkToPreferences=Настройки на виртуалния диск
vaultOptions.mount.readonly=Само за четене
vaultOptions.mount.customMountFlags=Флагове на монтиране
vaultOptions.mount.winDriveLetterOccupied=използвана
vaultOptions.mount.mountPoint=Точка на монтиране
vaultOptions.mount.mountPoint.auto=Автоматично избиране на подходящо място
vaultOptions.mount.mountPoint.driveLetter=Използване на определената буква на диска
vaultOptions.mount.mountPoint.custom=Използване на избрана папка
vaultOptions.mount.mountPoint.directoryPickerButton=Избиране… vaultOptions.mount.mountPoint.directoryPickerButton=Избиране…
vaultOptions.mount.mountPoint.directoryPickerTitle=Избиране на папка
## Master Key ## Master Key
vaultOptions.masterkey=Парола
vaultOptions.masterkey.changePasswordBtn=Промяна на парола vaultOptions.masterkey.changePasswordBtn=Промяна на парола
vaultOptions.masterkey.forgetSavedPasswordBtn=Забравяне на запазената парола
vaultOptions.masterkey.recoveryKeyExplanation=Ключът за възстановяване е единствения начин да си върнете достъпа да хранилището ако загубите паролата.
vaultOptions.masterkey.showRecoveryKeyBtn=Показване на ключ за възстановяване
vaultOptions.masterkey.recoverPasswordBtn=Нулиране на паролата
## Hub ## Hub
vaultOptions.hub=Възстановяване
vaultOptions.hub.convertInfo=В случай на спешност можете да използвате ключа за възстановяване, за да преобразувате това хранилище от хранилище от концентратор в хранилище с парола.
vaultOptions.hub.convertBtn=Преобразуване в хранилище с парола
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
recoveryKey.display.title=Показване на ключ за възстановяване
recoveryKey.create.message=Изисква се парола
recoveryKey.create.description=За да видите ключа за възстановяване въведете паролата за „%s“.
recoveryKey.display.description=Следния ключ за възстановязане може да бъде използван да си върнете достъпа до „%s“:
recoveryKey.display.StorageHints=Пазете го на много сигурно място, например:\n • Пазете го в софтуер за управление на пароли\n • Запишете го на USB памет\n • Отпечатайте го на хартия
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Нулиране на паролата
recoveryKey.recover.prompt=Въведете ключа за възстановяване на „%s“:
recoveryKey.recover.correctKey=Верен ключ за възстановяване
recoveryKey.recover.wrongKey=Този ключ за възстановяване принадлежи на друго хранилище
recoveryKey.recover.invalidKey=Неприемлив ключ за възстановяване
recoveryKey.printout.heading=Ключ за възстановяване на Криптоматор\n„%s“\n
### Reset Password ### Reset Password
recoveryKey.recover.resetBtn=Нулиране
### Recovery Key Password Reset Success ### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Паролата е променена
recoveryKey.recover.resetSuccess.description=Можете да отключите хранилището с новата парола.
# Convert Vault # Convert Vault
convertVault.title=Преобразуване на хранилище
convertVault.convert.convertBtn.before=Преобразуване
convertVault.convert.convertBtn.processing=Преобразуване…
convertVault.success.message=Преобразуване е успешно
convertVault.hubToPassword.success.description=Можете да отключите хранилището с избраната парола без необходимост от достъп до Концентратор.
# New Password # New Password
newPassword.promptText=Въведете нова парола
newPassword.reenterPassword=Потвърдете новата парола
newPassword.passwordsMatch=Паролите съвпадат!
newPassword.passwordsDoNotMatch=Паролите не съвпадат
passwordStrength.messageLabel.tooShort=Използвайте минимум %d знака
passwordStrength.messageLabel.0=Много слаба
passwordStrength.messageLabel.1=Слаба
passwordStrength.messageLabel.2=Задоволителна
passwordStrength.messageLabel.3=Добра
passwordStrength.messageLabel.4=Много добра
# Quit # Quit
quit.title=Изход от приложението
quit.message=Има отключени хранилища
quit.description=Потвърдете излизане от приложението. За да предотврати загуба на и формация Криптоматор ще заключи отворените хранилища.
quit.lockAndQuitBtn=Заключване и изход
# Forced Quit # Forced Quit
quit.forced.message=Някои от хранилищата не могат да бъдат заключени
quit.forced.description=Хранилището не може да бъде заключено, поради извършващо се действие или отворен файл. Можете принудително да заключите останалите хранилища, но прекъсването на входно-изходни процеси може да доведе до загуба на незапазени данни.
quit.forced.forceAndQuitBtn=Прекъсване и изход
# Update Reminder # Update Reminder
updateReminder.title=Проверка за обновяване
updateReminder.message=Проверяване за обновяване?
updateReminder.description=Получавайте нови възможности на приложението, поправки на дефекти и подобрения на сигурността. Препоръчваме ви автоматичното обновяване.
updateReminder.notNow=Не сега
updateReminder.yesOnce=Да, веднъж
updateReminder.yesAutomatically=Да, автоматично
+39 -3
View File
@@ -22,6 +22,9 @@ error.hyperlink.report=Notifica aquest error
error.technicalDetails=Detalls: error.technicalDetails=Detalls:
error.existingSolutionDescription=Cryptomator no esperava que això ocorreguera. Però hem trobat una solució per a aquest error. Per favor, done una ullada al següent enllaç. error.existingSolutionDescription=Cryptomator no esperava que això ocorreguera. Però hem trobat una solució per a aquest error. Per favor, done una ullada al següent enllaç.
error.hyperlink.solution=Buscar la solució error.hyperlink.solution=Buscar la solució
error.lookupPermissionMessage=Cryptomator pot cercar una solució a aquest problema en línia. S'enviarà una petició a la nostra base de dades de problemes des de la teva adreça IP.
error.dismiss=Descartar
error.lookUpSolution=Buscar Solució
# Defaults # Defaults
defaults.vault.vaultName=Caixa forta defaults.vault.vaultName=Caixa forta
@@ -38,6 +41,7 @@ traymenu.vault.reveal=Mostra
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Afegir una caixa forta addvaultwizard.title=Afegir una caixa forta
## New ## New
addvaultwizard.new.title=Afegeix nova caixa forta
### Name ### Name
addvaultwizard.new.nameInstruction=Introduiu el nom de la caixa forta addvaultwizard.new.nameInstruction=Introduiu el nom de la caixa forta
addvaultwizard.new.namePrompt=Nom de la caixa forta addvaultwizard.new.namePrompt=Nom de la caixa forta
@@ -59,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=Caràcters que formen paraules (ex.: a,
addvaultwizard.new.validCharacters.numbers=Nombres addvaultwizard.new.validCharacters.numbers=Nombres
addvaultwizard.new.validCharacters.dashes=Guionet (%s) o guió baix (%s) addvaultwizard.new.validCharacters.dashes=Guionet (%s) o guió baix (%s)
### Expert Settings ### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=Habilitar mode expert
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=Introduir un valor entre 36 i 220 (per defecte 220)
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=Obre la documentació per saber-ne més.
addvaultwizard.new.expertSettings.shorteningThreshold.title=Llargària màxima dels fitxers xifrats
addvaultwizard.new.expertSettings.shorteningThreshold.valid=Vàlid
### Password ### Password
addvaultwizard.new.createVaultBtn=Crea la caixa forta addvaultwizard.new.createVaultBtn=Crea la caixa forta
addvaultwizard.new.generateRecoveryKeyChoice=No podreu accedir a les vostres dades sense la contrasenya. Voleu crear una clau de recuperació en cas perdre la vostra contrasenya? addvaultwizard.new.generateRecoveryKeyChoice=No podreu accedir a les vostres dades sense la contrasenya. Voleu crear una clau de recuperació en cas perdre la vostra contrasenya?
@@ -82,6 +91,7 @@ addvault.new.readme.accessLocation.2=Aquesta és la ubicació d'accès de la vos
addvault.new.readme.accessLocation.3=Cryptomator xifra tots els fitxers afegits a aquest volum. Podeu treballar en aquest volum com en qualsevol altra unitat o carpeta. La vista mostra el contingut desxifrat però els fitxers sempre estan xifrats en el vostre disc dur. addvault.new.readme.accessLocation.3=Cryptomator xifra tots els fitxers afegits a aquest volum. Podeu treballar en aquest volum com en qualsevol altra unitat o carpeta. La vista mostra el contingut desxifrat però els fitxers sempre estan xifrats en el vostre disc dur.
addvault.new.readme.accessLocation.4=Pots esborrar aquest fitxer si vols. addvault.new.readme.accessLocation.4=Pots esborrar aquest fitxer si vols.
## Existing ## Existing
addvaultwizard.existing.title=Afegeix una caixa forta existent
addvaultwizard.existing.instruction=Selecciona el fitxer "vault.cryptomator" de la teva caixa forta. Si només existeix un fitxer anomenat "masterkey.cryptomator", selecciona aquest. addvaultwizard.existing.instruction=Selecciona el fitxer "vault.cryptomator" de la teva caixa forta. Si només existeix un fitxer anomenat "masterkey.cryptomator", selecciona aquest.
addvaultwizard.existing.chooseBtn=Trieu… addvaultwizard.existing.chooseBtn=Trieu…
addvaultwizard.existing.filePickerTitle=Selecciona el fitxer de la Caixa forta addvaultwizard.existing.filePickerTitle=Selecciona el fitxer de la Caixa forta
@@ -126,6 +136,11 @@ unlock.success.revealBtn=Mostra la unitat
unlock.error.customPath.message=No es pot muntar la caixa forta en la ruta personalitzada unlock.error.customPath.message=No es pot muntar la caixa forta en la ruta personalitzada
unlock.error.customPath.description.notSupported=Si vol continuar fent servir una ruta personalitzada, vagi si us plau a Preferències i seleccioni un tipus de volum que la suporti. Altrament, vagi a les opcions de la caixa forta i escolli un punt de muntatge suportat. unlock.error.customPath.description.notSupported=Si vol continuar fent servir una ruta personalitzada, vagi si us plau a Preferències i seleccioni un tipus de volum que la suporti. Altrament, vagi a les opcions de la caixa forta i escolli un punt de muntatge suportat.
unlock.error.customPath.description.notExists=La ruta de muntatge personalitzada no existeix. Creï-la o canviï-la en les opcions de la caixa forta. unlock.error.customPath.description.notExists=La ruta de muntatge personalitzada no existeix. Creï-la o canviï-la en les opcions de la caixa forta.
unlock.error.customPath.description.inUse=La unitat o el punt de muntatge "%s" ja és en ús.
unlock.error.customPath.description.hideawayNotDir=El fitxer temporal "%3$s" utilitzat per desbloquejar no es pot esborrar. Revisi si us plau aquest fitxer i esborri'l manualment.
unlock.error.customPath.description.couldNotBeCleaned=La caixa forta no es pot muntar en "%s". Intenteu-ho de nou o escolliu un destí diferent, si us plau.
unlock.error.customPath.description.notEmptyDir=El punt de muntatge "%s" no és una carpeta buida. Trieu una carpeta buida i proveu-ho de nou.
unlock.error.customPath.description.generic=Al anar a desar la caixa forta en el lloc indicat ha fallat amb el missatge: %2$s
## Hub ## Hub
hub.noKeychain.message=No es pot accedir a la clau del dispositiu hub.noKeychain.message=No es pot accedir a la clau del dispositiu
hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències. hub.noKeychain.description=Per poder desblocar caixes fortes del Hub es requereix la clau d'un dispositiu, que s'emmagatzema de forma segura en un clauer. Per continuar, habiliti "%s" i seleccioni un clauer en les Preferències.
@@ -138,9 +153,11 @@ hub.auth.loginLink=No heu estat redirigit? Feu clic aquí per a obrir-la.
hub.receive.message=S'està processant la resposta… hub.receive.message=S'està processant la resposta…
hub.receive.description=Cryptomator està rebent i processant la resposta del Hub. Espereu, si us plau. hub.receive.description=Cryptomator està rebent i processant la resposta del Hub. Espereu, si us plau.
### Register Device ### Register Device
hub.register.message=Cal un nom de dispositiu hub.register.message=Nou dispositiu
hub.register.description=Sembla que és el primer accés al Hub des d'aquest dispositiu. Per identificar-lo i poder autoritzar l'accés necessiteu anomenar aquest dispositiu. hub.register.description=Aquest és el primer accés al Hub des d'aquest dispositiu. Cal autoritzar-lo amb el codi de configuració.
hub.register.nameLabel=Nom del dispositiu hub.register.nameLabel=Nom del dispositiu
hub.register.setupCodeLabel=Codi de configuració
hub.register.invalidSetupCode=El codi de configuració és invàlid
hub.register.occupiedMsg=El nom ja està en ús hub.register.occupiedMsg=El nom ja està en ús
hub.register.registerBtn=Confirma hub.register.registerBtn=Confirma
### Registration Success ### Registration Success
@@ -273,8 +290,10 @@ preferences.interface.showMinimizeButton=Mostra el botó 'minimitzar'
preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar) preferences.interface.showTrayIcon=Mostra la icona en la barra (cal reiniciar)
## Volume ## Volume
preferences.volume=Unitat virtual preferences.volume=Unitat virtual
preferences.volume.type=Tipus de volum
preferences.volume.type.automatic=Automàtic preferences.volume.type.automatic=Automàtic
preferences.volume.docsTooltip=Obre la documentació per aprendre més sobre els diferents tipus de volums. preferences.volume.docsTooltip=Obre la documentació per aprendre més sobre els diferents tipus de volums.
preferences.volume.fuseRestartRequired=Per aplicar els canvis Cryptomator necessita reiniciar-se.
preferences.volume.tcp.port=Port TCP preferences.volume.tcp.port=Port TCP
preferences.volume.supportedFeatures=El tipus de volum escollit suporta les següents característiques: preferences.volume.supportedFeatures=El tipus de volum escollit suporta les següents característiques:
preferences.volume.feature.mountAuto=Selecció automàtica del punt de muntatge preferences.volume.feature.mountAuto=Selecció automàtica del punt de muntatge
@@ -349,6 +368,8 @@ main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
main.vaultlist.contextMenu.reveal=Mostra la unitat main.vaultlist.contextMenu.reveal=Mostra la unitat
main.vaultlist.addVaultBtn=Afegir main.vaultlist.addVaultBtn=Afegir
main.vaultlist.addVaultBtn.menuItemNew=Nova caixa forta...
main.vaultlist.addVaultBtn.menuItemExisting=Caixa forta existent...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos: main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
@@ -431,6 +452,9 @@ vaultOptions.masterkey.recoveryKeyExplanation=La clau de recuperació és l'unic
vaultOptions.masterkey.showRecoveryKeyBtn=Mostra la clau de recuperació vaultOptions.masterkey.showRecoveryKeyBtn=Mostra la clau de recuperació
vaultOptions.masterkey.recoverPasswordBtn=Canviar contrasenya vaultOptions.masterkey.recoverPasswordBtn=Canviar contrasenya
## Hub ## Hub
vaultOptions.hub=Recuperació
vaultOptions.hub.convertInfo=En cas d'emergència pot fer servir la clau de recuperació per convertir aquesta caixa forta de Hub a una caixa forta amb paraula clau.
vaultOptions.hub.convertBtn=Convertir a una caixa forta amb paraula clau
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
@@ -442,6 +466,7 @@ recoveryKey.display.StorageHints=Conserveu-la en un lloc molt segur. P. ex.:\n
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=Canviar contrasenya recoveryKey.recover.title=Canviar contrasenya
recoveryKey.recover.prompt=Introduir la clau de recuperació per "%s":
recoveryKey.recover.correctKey=La clau de recuperació és vàlida recoveryKey.recover.correctKey=La clau de recuperació és vàlida
recoveryKey.recover.wrongKey=Aquesta clau de recuperació pertany a una caixa forta diferent recoveryKey.recover.wrongKey=Aquesta clau de recuperació pertany a una caixa forta diferent
recoveryKey.recover.invalidKey=Aquesta clau de recuperació no és vàlida recoveryKey.recover.invalidKey=Aquesta clau de recuperació no és vàlida
@@ -453,6 +478,11 @@ recoveryKey.recover.resetSuccess.message=S'ha modificat la contrasenya correctam
recoveryKey.recover.resetSuccess.description=Pots desbloquejar la caixa forta amb la nova contrasenya. recoveryKey.recover.resetSuccess.description=Pots desbloquejar la caixa forta amb la nova contrasenya.
# Convert Vault # Convert Vault
convertVault.title=Convertir la caixa forta
convertVault.convert.convertBtn.before=Convertir
convertVault.convert.convertBtn.processing=Convertint…
convertVault.success.message=Conversió correcta
convertVault.hubToPassword.success.description=Ara pot desbloquejar la caixa forta amb la paraula clau sense haver d'accedir al Hub.
# New Password # New Password
newPassword.promptText=Introdueix una contrasenya nova newPassword.promptText=Introdueix una contrasenya nova
@@ -477,4 +507,10 @@ quit.forced.message=Algunes caixes fortes no s'han pogut bloquejar
quit.forced.description=No s'ha pogut blocar la caixa forta perquè hi ha operacions pendents o fitxers oberts. Podeu forçar-ne el blocatge, però heu de saber que interrompre l'entrada/sortida pot produir la pèrdua de dades. quit.forced.description=No s'ha pogut blocar la caixa forta perquè hi ha operacions pendents o fitxers oberts. Podeu forçar-ne el blocatge, però heu de saber que interrompre l'entrada/sortida pot produir la pèrdua de dades.
quit.forced.forceAndQuitBtn=Forçar i sortir quit.forced.forceAndQuitBtn=Forçar i sortir
# Update Reminder # Update Reminder
updateReminder.title=Comprovar actualitzacions
updateReminder.message=Comprova si hi ha actualitzacions?
updateReminder.description=Per estar actualitzat amb funcions noves, correccions d'errors i millores de seguretat recomanem comprovar les actualitzacions automàticament.
updateReminder.notNow=Ara no
updateReminder.yesOnce=Sí, una vegada
updateReminder.yesAutomatically=Sí, automàticament
@@ -142,7 +142,6 @@ hub.auth.loginLink=Nebyli jste přesměrováni? Klikněte zde pro otevření.
### Receive Key ### Receive Key
hub.receive.message=Zpracovávání odezvy… hub.receive.message=Zpracovávání odezvy…
### Register Device ### Register Device
hub.register.message=Je vyžadován název zařízení
hub.register.nameLabel=Název zařízení hub.register.nameLabel=Název zařízení
hub.register.occupiedMsg=Jméno je již používáno hub.register.occupiedMsg=Jméno je již používáno
hub.register.registerBtn=Potvrdit hub.register.registerBtn=Potvrdit
+16 -2
View File
@@ -22,6 +22,9 @@ error.hyperlink.report=Rapportér denne fejl
error.technicalDetails=Detaljer: error.technicalDetails=Detaljer:
error.existingSolutionDescription=Cryptomator forventede ikke, at dette ville ske. Men vi har fundet en eksisterende løsning på denne fejl. Tag et kig på følgende link. error.existingSolutionDescription=Cryptomator forventede ikke, at dette ville ske. Men vi har fundet en eksisterende løsning på denne fejl. Tag et kig på følgende link.
error.hyperlink.solution=Tjek løsningen error.hyperlink.solution=Tjek løsningen
error.lookupPermissionMessage=Cryptomator kan finde en løsning på dette problem online. Dette vil sende en anmodning til vores problemdatabase fra din IP-adresse.
error.dismiss=Luk
error.lookUpSolution=Slå løsning op
# Defaults # Defaults
defaults.vault.vaultName=Boks defaults.vault.vaultName=Boks
@@ -38,6 +41,7 @@ traymenu.vault.reveal=Vis
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Tilføj boks addvaultwizard.title=Tilføj boks
## New ## New
addvaultwizard.new.title=Tilføj Ny Boks
### Name ### Name
addvaultwizard.new.nameInstruction=Vælg et navn til boksen addvaultwizard.new.nameInstruction=Vælg et navn til boksen
addvaultwizard.new.namePrompt=Boks-navn addvaultwizard.new.namePrompt=Boks-navn
@@ -87,6 +91,7 @@ addvault.new.readme.accessLocation.2=Det er her indholdet af din boks tilgås.
addvault.new.readme.accessLocation.3=Filer du tilføjer til dette drev vil blive krypteret af Cryptomator. Du har arbejde med filerne ligesom enhver anden fil/mappe. Dette er blot en dekrypteret visning af indholdet. Dine filer er stadig krypterede på din harddisk hele tiden. addvault.new.readme.accessLocation.3=Filer du tilføjer til dette drev vil blive krypteret af Cryptomator. Du har arbejde med filerne ligesom enhver anden fil/mappe. Dette er blot en dekrypteret visning af indholdet. Dine filer er stadig krypterede på din harddisk hele tiden.
addvault.new.readme.accessLocation.4=Fjern denne fil hvis du har lyst. addvault.new.readme.accessLocation.4=Fjern denne fil hvis du har lyst.
## Existing ## Existing
addvaultwizard.existing.title=Tilføj Eksisterende Boks
addvaultwizard.existing.instruction=Vælgt filen "vault.cryptomator" i mappen med dine boks-filer. Hvis der kun findes en fil med navnet "masterkey.cryptomator", skal du vælge den i stedet. addvaultwizard.existing.instruction=Vælgt filen "vault.cryptomator" i mappen med dine boks-filer. Hvis der kun findes en fil med navnet "masterkey.cryptomator", skal du vælge den i stedet.
addvaultwizard.existing.chooseBtn=Vælg… addvaultwizard.existing.chooseBtn=Vælg…
addvaultwizard.existing.filePickerTitle=Vælg boks-fil addvaultwizard.existing.filePickerTitle=Vælg boks-fil
@@ -131,6 +136,11 @@ unlock.success.revealBtn=Vis drev
unlock.error.customPath.message=Kan ikke montere boks til brugerdefineret sti unlock.error.customPath.message=Kan ikke montere boks til brugerdefineret sti
unlock.error.customPath.description.notSupported=Hvis du ønsker at fortsætte med at bruge den brugerdefinerede sti, skal du gå til præferencer og vælge en type drev der understøtter det. Hvis ikke, skal du gå til boksens indstillinger og vælge et understøttet monteringspunkt. unlock.error.customPath.description.notSupported=Hvis du ønsker at fortsætte med at bruge den brugerdefinerede sti, skal du gå til præferencer og vælge en type drev der understøtter det. Hvis ikke, skal du gå til boksens indstillinger og vælge et understøttet monteringspunkt.
unlock.error.customPath.description.notExists=Den brugerdefinerede monteringssti eksisterer ikke. Opret den enten i dit lokale filsystem eller skift monteringssti i boksens indstillinger. unlock.error.customPath.description.notExists=Den brugerdefinerede monteringssti eksisterer ikke. Opret den enten i dit lokale filsystem eller skift monteringssti i boksens indstillinger.
unlock.error.customPath.description.inUse=Drevbogstavet eller den brugerdefinerede monteringssti "%s" er allerede i brug.
unlock.error.customPath.description.hideawayNotDir=Den midlertidige, skjulte fil "%3$s", der blev brugt til oplåsning, kunne ikke fjernes. Kontroller filen og slet den manuelt.
unlock.error.customPath.description.couldNotBeCleaned=Din boks kunne ikke monteres på stien "%s". Prøv venligst igen eller vælg en anden sti.
unlock.error.customPath.description.notEmptyDir=Den brugerdefinerede monteringssti "%s" er ikke en tom mappe. Vælg venligst en tom mappe og prøv igen.
unlock.error.customPath.description.generic=Du har valgt en brugerdefineret monteringssti til denne boks, men det mislykkedes med følgende besked: %2$s
## Hub ## Hub
hub.noKeychain.message=Kan ikke tilgå enhedsnøgle hub.noKeychain.message=Kan ikke tilgå enhedsnøgle
hub.noKeychain.description=En enhedsnøgle er påkrævet for at låse Hub bokse op. Enhedsnøglen er sikret i en nøglering. For at fortsætte, aktivér “%s” og vælg en nøglering i indstillingerne. hub.noKeychain.description=En enhedsnøgle er påkrævet for at låse Hub bokse op. Enhedsnøglen er sikret i en nøglering. For at fortsætte, aktivér “%s” og vælg en nøglering i indstillingerne.
@@ -143,9 +153,11 @@ hub.auth.loginLink=Ingen omdirigering? Klik her for at åbne den.
hub.receive.message=Behandler svar… hub.receive.message=Behandler svar…
hub.receive.description=Cryptomator modtager og behandler svaret fra hubben. Vent venligst. hub.receive.description=Cryptomator modtager og behandler svaret fra hubben. Vent venligst.
### Register Device ### Register Device
hub.register.message=Enheds-navn krævet hub.register.message=Ny Enhed
hub.register.description=Det ser ud til at dette er første gang denne enhed tilgår hubben. For at identificere denne enhed, skal du navngive den. hub.register.description=Dette er den første Hub adgang fra denne enhed. Godkend den med din opsætningskode.
hub.register.nameLabel=Enheds-navn hub.register.nameLabel=Enheds-navn
hub.register.setupCodeLabel=Opsætningskode
hub.register.invalidSetupCode=Ugyldig Opsætningskode
hub.register.occupiedMsg=Navnet er allerede i brug hub.register.occupiedMsg=Navnet er allerede i brug
hub.register.registerBtn=Bekræft hub.register.registerBtn=Bekræft
### Registration Success ### Registration Success
@@ -356,6 +368,8 @@ main.vaultlist.contextMenu.unlockNow=Lås op nu
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
main.vaultlist.contextMenu.reveal=Vis drev main.vaultlist.contextMenu.reveal=Vis drev
main.vaultlist.addVaultBtn=Tilføj main.vaultlist.addVaultBtn=Tilføj
main.vaultlist.addVaultBtn.menuItemNew=Ny Boks...
main.vaultlist.addVaultBtn.menuItemExisting=Eksisterende Boks...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang: main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang:
@@ -153,9 +153,11 @@ hub.auth.loginLink=Keine Weiterleitung? Zum Öffnen hier anklicken.
hub.receive.message=Antwort wird verarbeitet … hub.receive.message=Antwort wird verarbeitet …
hub.receive.description=Cryptomator empfängt und verarbeitet gerade die Antwort vom Hub. Bitte warten. hub.receive.description=Cryptomator empfängt und verarbeitet gerade die Antwort vom Hub. Bitte warten.
### Register Device ### Register Device
hub.register.message=Gerätename erforderlich hub.register.message=Neues Gerät
hub.register.description=Dies scheint der erste Hub-Zugriff von diesem Gerät zu sein. Um es für die Zugriffsberechtigung zu identifizieren, mussst du diesem Gerät einen Namen geben. hub.register.description=Dies ist der erste Hub Zugriff von diesem Gerät. Bitte autorisiere ihn mit deinem Setup Code.
hub.register.nameLabel=Gerätename hub.register.nameLabel=Gerätename
hub.register.setupCodeLabel=Setup Code
hub.register.invalidSetupCode=Ungültiger Setup Code
hub.register.occupiedMsg=Name bereits in Verwendung hub.register.occupiedMsg=Name bereits in Verwendung
hub.register.registerBtn=Bestätigen hub.register.registerBtn=Bestätigen
### Registration Success ### Registration Success
@@ -153,9 +153,11 @@ hub.auth.loginLink=Δεν έγινε ανακατεύθυνση; Κάντε κλ
hub.receive.message=Επεξεργασία απάντησης… hub.receive.message=Επεξεργασία απάντησης…
hub.receive.description=Το Cryptomator λαμβάνει και επεξεργάζεται την απάντηση από το Hub. Παρακαλώ περιμένετε. hub.receive.description=Το Cryptomator λαμβάνει και επεξεργάζεται την απάντηση από το Hub. Παρακαλώ περιμένετε.
### Register Device ### Register Device
hub.register.message=Απαιτείται όνομα συσκευής hub.register.message=Νέα Συσκευή
hub.register.description=Αυτή φαίνεται να είναι η πρώτη πρόσβαση στο Hub από αυτήν τη συσκευή. Για να την αναγνωρίσετε για εξουσιοδότηση πρόσβασης, πρέπει να ονομάσετε αυτήν τη συσκευή. hub.register.description=Αυτή είναι η πρώτη πρόσβαση Hub από αυτήν τη συσκευή. Παρακαλούμε εξουσιοδοτήστε την χρησιμοποιώντας τον κωδικό εγκατάστασης σας.
hub.register.nameLabel=Όνομα Συσκευής hub.register.nameLabel=Όνομα Συσκευής
hub.register.setupCodeLabel=Κωδικός Εγκατάστασης
hub.register.invalidSetupCode=Μη έγκυρος Κωδικός Εγκατάστασης
hub.register.occupiedMsg=Το όνομα χρησιμοποιείται ήδη hub.register.occupiedMsg=Το όνομα χρησιμοποιείται ήδη
hub.register.registerBtn=Επιβεβαίωση hub.register.registerBtn=Επιβεβαίωση
### Registration Success ### Registration Success
@@ -153,9 +153,11 @@ hub.auth.loginLink=¿No se ha redireccionado? Haga clic aquí para abrirla.
hub.receive.message=Procesando la respuesta… hub.receive.message=Procesando la respuesta…
hub.receive.description=Cryptomator está recibiendo y procesando la respuesta del Hub. Por favor espere. hub.receive.description=Cryptomator está recibiendo y procesando la respuesta del Hub. Por favor espere.
### Register Device ### Register Device
hub.register.message=Nombre del dispositivo requerido hub.register.message=Nuevo dispositivo
hub.register.description=Este parece ser el primer acceso al Hub desde este dispositivo. Para identificarlo y autorizar el acceso, necesita nombrar este dispositivo. hub.register.description=Este es el primer acceso a Hub desde este dispositivo. Por favor, autorízalo usando tu código de configuración.
hub.register.nameLabel=Nombre del dispositivo hub.register.nameLabel=Nombre del dispositivo
hub.register.setupCodeLabel=Código de configuración
hub.register.invalidSetupCode=Código de configuración incorrecto
hub.register.occupiedMsg=Nombre ya en uso hub.register.occupiedMsg=Nombre ya en uso
hub.register.registerBtn=Confirmar hub.register.registerBtn=Confirmar
### Registration Success ### Registration Success
@@ -138,8 +138,6 @@ hub.auth.loginLink=Uudelleenohjaus epäonnistui? Avaa tästä manuaalisesti.
hub.receive.message=Odotetaan vastausta… hub.receive.message=Odotetaan vastausta…
hub.receive.description=Cryptomator yhdistää Hub:iin. Ole hyvä ja odota. hub.receive.description=Cryptomator yhdistää Hub:iin. Ole hyvä ja odota.
### Register Device ### Register Device
hub.register.message=Laitteen nimi vaaditaan
hub.register.description=Et ole ilmeisesti ennen yhdistänyt Hub:iin tältä laitteelta. Jotta pääsyoikeus voidaan todentaa, sinun täytyy nimetä tämä laite.
hub.register.nameLabel=Laitteen Nimi hub.register.nameLabel=Laitteen Nimi
hub.register.occupiedMsg=Tämä nimi on jo käytössä hub.register.occupiedMsg=Tämä nimi on jo käytössä
hub.register.registerBtn=Vahvista hub.register.registerBtn=Vahvista
+288 -1
View File
@@ -124,106 +124,393 @@ unlock.savePassword=Maalala ang password
unlock.unlockBtn=I-unlock unlock.unlockBtn=I-unlock
## Select ## Select
unlock.chooseMasterkey.message=Hindi nahanap ang masterkey file unlock.chooseMasterkey.message=Hindi nahanap ang masterkey file
unlock.chooseMasterkey.description=Hindi mahanap ng Cryptomator ang masterkey file para sa vault na "%s". Mangyaring piliin ang key file nang manu-mano.
unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file unlock.chooseMasterkey.filePickerTitle=Piliin ang masterkey file
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success ## Success
unlock.success.message=Matagumpay ang pag-unlock
unlock.success.description=Ang nilalaman sa vault na "%s" ay naa-access na ngayon sa mount point nito.
unlock.success.rememberChoice=Paalala ang pinili ko, huwag mag tanong ulit unlock.success.rememberChoice=Paalala ang pinili ko, huwag mag tanong ulit
unlock.success.revealBtn=Ibunyag ang Drive
## Failure ## Failure
unlock.error.customPath.message=Hindi ma-mount ang vault sa custom na path
unlock.error.customPath.description.notSupported=Kung gusto mong patuloy na gamitin ang custom na path, mangyaring pumunta sa mga kagustuhan at pumili ng uri ng volume na sumusuporta dito. Kung hindi, pumunta sa mga opsyon sa vault at pumili ng sinusuportahang mount point.
unlock.error.customPath.description.notExists=Ang custom na mount path ay hindi umiiral. Alinman sa lumikha ito sa iyong lokal na filesystem o baguhin ito sa mga pagpipilian sa vault.
unlock.error.customPath.description.inUse=Ang drive letter o custom na mount path na "%s" ay ginagamit na.
unlock.error.customPath.description.hideawayNotDir=Ang pansamantalang nakatagong file na "%3$s" na ginamit para sa pag-unlock ay hindi maalis. Pakisuri ang file at pagkatapos ay tanggalin ito nang manu-mano.
unlock.error.customPath.description.couldNotBeCleaned=Hindi ma-mount ang iyong vault sa path na "%s". Pakisubukang muli o pumili ng ibang landas.
unlock.error.customPath.description.notEmptyDir=Ang custom na mount path na "%s" ay hindi isang walang laman na folder. Mangyaring pumili ng isang walang laman na folder at subukang muli.
unlock.error.customPath.description.generic=Pumili ka ng custom na mount path para sa vault na ito, ngunit nabigo ang paggamit nito sa mensaheng: %2$s
## Hub ## Hub
hub.noKeychain.message=Hindi ma-access ang key ng device
hub.noKeychain.description=Para ma-unlock ang mga Hub vault, kailangan ng device key, na sini-secure gamit ang keychain. Upang magpatuloy, paganahin ang “%s” at pumili ng keychain sa mga kagustuhan.
hub.noKeychain.openBtn=Buksan ang Mga Kagustuhan
### Waiting ### Waiting
hub.auth.message=Naghihintay para sa pagpapatunay…
hub.auth.description=Dapat kang awtomatikong mai-redirect sa pahina ng pag-login.
hub.auth.loginLink=Hindi na-redirect? Mag-click dito upang buksan ito.
### Receive Key ### Receive Key
hub.receive.message=Pinoproseso ang tugon…
hub.receive.description=Ang Cryptomator ay tumatanggap at nagpoproseso ng tugon mula sa Hub. Mangyaring maghintay.
### Register Device ### Register Device
hub.register.message=Bagong Device
hub.register.description=Ito ang unang Hub access mula sa device na ito. Mangyaring pahintulutan ito gamit ang iyong setup code.
hub.register.nameLabel=Pangalan ng device
hub.register.setupCodeLabel=Setup Code
hub.register.invalidSetupCode=Di-wastong Setup Code
hub.register.occupiedMsg=Ang pangalan ay nagamit na hub.register.occupiedMsg=Ang pangalan ay nagamit na
hub.register.registerBtn=Kumpirmahin hub.register.registerBtn=Kumpirmahin
### Registration Success ### Registration Success
hub.registerSuccess.message=Pinangalanan ang device
hub.registerSuccess.description=Para ma-access ang vault, kailangang pahintulutan ng may-ari ng vault ang iyong device.
### Registration Failed ### Registration Failed
hub.registerFailed.message=Nabigo ang pagpapangalan ng device
hub.registerFailed.description=Nagkaroon ng error sa proseso ng pagbibigay ng pangalan. Para sa higit pang mga detalye, tingnan ang log ng aplikasyon.
### Unauthorized ### Unauthorized
hub.unauthorized.message=Walang pahintulot
hub.unauthorized.description=Hindi pa pinahihintulutan ang iyong device na i-access ang vault na ito. Hilingin sa may-ari ng vault na pahintulutan ito.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Di-wasto ang Lisensya ng Hub
hub.invalidLicense.description=Ang iyong Cryptomator Hub instance ay may di-wastong lisensya. Mangyaring ipagbigay-alam sa administrator ng Hub na mag-upgrade o mag-renew ng lisensya.
# Lock # Lock
## Force ## Force
lock.forced.message=Nabigo ang pag-lock
lock.forced.description=Ang pag-lock ng "%s" ay na-block ng mga nakabinbing operasyon o mga bukas na file. Maaari mong pilitin na i-lock ang vault na ito, gayunpaman ang pagkagambala sa I/O ay maaaring magresulta sa pagkawala ng hindi na-save na data.
lock.forced.retryBtn=Subukan muli lock.forced.retryBtn=Subukan muli
lock.forced.forceBtn=Force Lock
## Failure ## Failure
lock.fail.message=Nabigo ang pag-lock ng vault
lock.fail.description=Hindi ma-lock ang Vault "%s". Tiyaking nai-save ang hindi na-save na gawain sa ibang lugar at natapos ang mahahalagang operasyon ng Read/Write. Upang isara ang vault, patayin ang proseso ng Cryptomator.
# Migration # Migration
migration.title=I-upgrade ang Vault
## Start ## Start
migration.start.header=I-upgrade ang Vault
migration.start.text=Upang mabuksan ang iyong vault na "%s" sa bagong bersyon na ito ng Cryptomator, kailangang i-upgrade ang vault sa mas bagong format. Bago gawin ito, dapat mong malaman ang mga sumusunod:
migration.start.remarkUndone=Hindi maa-undo ang pag-upgrade na ito.
migration.start.remarkVersions=Hindi mabubuksan ng mga lumang bersyon ng Cryptomator ang na-upgrade na vault.
migration.start.remarkCanRun=Dapat mong tiyakin na ang bawat device kung saan mo ina-access ang vault ay maaaring magpatakbo ng bersyong ito ng Cryptomator.
migration.start.remarkSynced=Dapat mong tiyakin na ang iyong vault ay ganap na naka-sync sa device na ito, at sa iyong iba pang mga device, bago ito i-upgrade.
migration.start.confirm=Nabasa at naunawaan ko ang impormasyon sa itaas
## Run ## Run
migration.run.enterPassword=Ilagay ang password para sa "%s"
migration.run.startMigrationBtn=I-migrate ang Vault
migration.run.progressHint=Maaaring tumagal ito ng ilang oras…
## Success ## Success
migration.success.nextStepsInstructions=Matagumpay na nailipat ang "%s".\nMaaari mo na ngayong i-unlock ang iyong vault.
migration.success.unlockNow=I-unlock Ngayon migration.success.unlockNow=I-unlock Ngayon
## Missing file system capabilities ## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Hindi sinusuportahang File System
migration.error.missingFileSystemCapabilities.description=Hindi nasimulan ang paglipat, dahil ang iyong vault ay matatagpuan sa isang hindi sapat na file system.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Hindi sinusuportahan ng file system ang mahabang pangalan ng file.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Hindi sinusuportahan ng file system ang mahahabang landas.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Hindi pinapayagan ng file system na basahin.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Ang file system ay hindi pinapayagang isulat sa.
## Impossible ## Impossible
migration.impossible.heading=Hindi ma-migrate ang vault
migration.impossible.reason=Hindi maaaring awtomatikong i-migrate ang vault dahil hindi tugma ang lokasyon ng storage o access point nito.
migration.impossible.moreInfo=Mabubuksan pa rin ang vault gamit ang mas lumang bersyon. Para sa mga tagubilin kung paano manu-manong mag-migrate ng vault, bisitahin ang
# Health Check # Health Check
## Start ## Start
health.title=Health Check ng "%s"
health.intro.header=Health Check
health.intro.text=Ang Health Check ay isang koleksyon ng mga pagsusuri upang makita at posibleng ayusin ang mga problema sa panloob na istraktura ng iyong vault. Mangyaring tandaan:
health.intro.remarkSync=Tiyaking ganap na naka-sync ang lahat ng device, nalulutas nito ang karamihan sa mga problema.
health.intro.remarkFix=Hindi lahat ng problema ay kayang ayusin.
health.intro.remarkBackup=Kung nasira ang data, isang backup lamang ang makakatulong.
health.intro.affirmation=Nabasa at naunawaan ko ang impormasyon sa itaas
## Start Failure ## Start Failure
health.fail.header=Error sa pag-load ng Vault Configuration
health.fail.ioError=Nagkaroon ng error habang ina-access at binabasa ang config file.
health.fail.parseError=Nagkaroon ng error habang pina-parse ang config ng vault.
health.fail.moreInfo=Karagdagang impormasyon
## Check Selection ## Check Selection
health.checkList.description=Pumili ng mga tseke sa kaliwang listahan o gamitin ang mga button sa ibaba.
health.checkList.selectAllButton=Piliin ang Lahat ng Pagsusuri
health.checkList.deselectAllButton=Alisin sa pagkakapili ang Lahat ng Pagsusuri
health.check.runBatchBtn=Patakbuhin ang Mga Napiling Pagsusuri
## Detail view ## Detail view
health.check.detail.noSelectedCheck=Para sa mga resulta pumili ng natapos na pagsusuri sa kalusugan sa kaliwang listahan.
health.check.detail.checkScheduled=The check is scheduled.
health.check.detail.checkRunning=The check is currently running…
health.check.detail.checkSkipped=Ang tseke ay hindi napiling tumakbo.
health.check.detail.checkFinished=Matagumpay na natapos ang tseke.
health.check.detail.checkFinishedAndFound=Ang tseke ay tapos nang tumakbo. Mangyaring suriin ang mga resulta.
health.check.detail.checkFailed=Ang tseke ay lumabas dahil sa isang error.
health.check.detail.checkCancelled=Kinansela ang tseke.
health.check.detail.listFilters.label=Filter
health.check.detail.fixAllSpecificBtn=Ayusin ang lahat ng uri
health.check.exportBtn=I-export ang Ulat
## Result view ## Result view
health.result.severityFilter.all=Kalubhaan - Lahat
health.result.severityFilter.good=Mabuti
health.result.severityFilter.info=Impormasyon
health.result.severityFilter.warn=Babala
health.result.severityFilter.crit=Mapanganib
health.result.severityTip.good=Seryoso: Mabuti\nNormal na istraktura ng vault.
health.result.severityTip.info=Kalubhaan: Impormasyon\nBuo ang istraktura ng Vault, iminungkahing ayusin.
health.result.severityTip.warn=Kalubhaan: Babala\nNasira ang istraktura ng Vault, lubos na pinapayuhan ang pag-aayos.
health.result.severityTip.crit=Kalubhaan: Kritikal\nNasira ang istraktura ng Vault, natukoy ang pagkawala ng data.
health.result.fixStateFilter.all=Ayusin ang estado - Lahat
health.result.fixStateFilter.fixable=Naaayos
health.result.fixStateFilter.notFixable=Hindi naaayos
health.result.fixStateFilter.fixing=Inaayos…
health.result.fixStateFilter.fixed=Nakapirming
health.result.fixStateFilter.fixFailed=Nabigo ang pag-aayos
## Fix Application ## Fix Application
health.fix.fixBtn=Ayusin
health.fix.successTip=Ayusin ang matagumpay
health.fix.failTip=Nabigo ang pag-aayos, tingnan ang log para sa mga detalye
# Preferences # Preferences
preferences.title=Mga Kagustuhan preferences.title=Mga Kagustuhan
## General ## General
preferences.general=Heneral
preferences.general.startHidden=Itago ang window kapag sinimulan ang Cryptomator
preferences.general.autoCloseVaults=Awtomatikong i-lock ang mga bukas na vault kapag huminto sa aplikasyon
preferences.general.debugLogging=Paganahin ang pag-log ng debug
preferences.general.debugDirectory=Magbunyag ng mga log file
preferences.general.autoStart=Ilunsad ang Cryptomator sa pagsisimula ng system
preferences.general.keychainBackend=Mag-imbak ng mga password gamit ang
## Interface ## Interface
preferences.interface=Interface
preferences.interface.theme=Tingnan at Pakiramdam
preferences.interface.theme.automatic=Awtomatiko
preferences.interface.theme.dark=Dark preferences.interface.theme.dark=Dark
preferences.interface.theme.light=Light preferences.interface.theme.light=Light
preferences.interface.unlockThemes=I-unlock ang dark mode
preferences.interface.language=Wika (nangangailangan ng pag-restart)
preferences.interface.language.auto=System Default
preferences.interface.interfaceOrientation=Oryentasyon ng Interface
preferences.interface.interfaceOrientation.ltr=Kaliwa hanggang Kanan
preferences.interface.interfaceOrientation.rtl=Kanan papuntang Kaliwa
preferences.interface.showMinimizeButton=Ipakita ang pindutan ng minimize
preferences.interface.showTrayIcon=Ipakita ang icon ng tray (kailangan i-restart)
## Volume ## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Uri ng Dami
preferences.volume.type.automatic=Awtomatiko
preferences.volume.docsTooltip=Buksan ang dokumentasyon para matuto pa tungkol sa iba't ibang uri ng volume.
preferences.volume.fuseRestartRequired=Upang mailapat ang mga pagbabago, kailangang i-restart ang Cryptomator.
preferences.volume.tcp.port=TCP Port
preferences.volume.supportedFeatures=Sinusuportahan ng napiling uri ng volume ang mga sumusunod na tampok:
preferences.volume.feature.mountAuto=Awtomatikong pagpili ng mount point
preferences.volume.feature.mountToDir=Custom na direktoryo bilang mount point
preferences.volume.feature.mountToDriveLetter=Drive letter bilang mount point
preferences.volume.feature.mountFlags=Mga pagpipilian sa custom na pag-mount
preferences.volume.feature.readOnly=Read-only mount
## Updates ## Updates
preferences.updates=Mga update
preferences.updates.currentVersion=Kasalukuyang Bersyon: %s
preferences.updates.autoUpdateCheck=Awtomatikong suriin ang mga update
preferences.updates.checkNowBtn=Tingnan ngayon
preferences.updates.updateAvailable=Available ang update sa bersyong %s.
## Contribution ## Contribution
preferences.contribute=Suportahan Kami
preferences.contribute.registeredFor=Nakarehistro ang sertipiko ng tagasuporta para sa %s
preferences.contribute.noCertificate=Suportahan ang Cryptomator at tumanggap ng sertipiko ng tagasuporta. Ito ay tulad ng isang susi ng lisensya ngunit para sa mga kahanga-hangang tao na gumagamit ng libreng software. ;-)
preferences.contribute.getCertificate=Wala ka na ba? Alamin kung paano mo ito makukuha.
preferences.contribute.promptText=I-paste ang code ng certificate ng tagasuporta dito
#<-- Add entries for donations and code/translation/documentation contribution --> #<-- Add entries for donations and code/translation/documentation contribution -->
## About ## About
preferences.about=Tungkol sa
# Vault Statistics # Vault Statistics
stats.title=Mga istatistika para sa %s
stats.cacheHitRate=Rate ng Cache Hit
## Read ## Read
stats.read.throughput.idle=Basahin: walang ginagawa
stats.read.throughput.kibs=Read: %.2f KiB/s
stats.read.throughput.mibs=Basahin: %.2f MiB/s
stats.read.total.data.none=Nabasa ang data: -
stats.read.total.data.kib=Nabasa ang data: %.1f KiB
stats.read.total.data.mib=Nabasa ang data: %.1f MiB
stats.read.total.data.gib=Nabasa ang data: %.1f GiB
stats.decr.total.data.none=Na-decrypt ang data: -
stats.decr.total.data.kib=Na-decrypt ang data: %.1f KiB
stats.decr.total.data.mib=Na-decrypt ang data: %.1f MiB
stats.decr.total.data.gib=Na-decrypt ang data: %.1f GiB
stats.read.accessCount=Kabuuang nabasa: %d
## Write ## Write
stats.write.throughput.idle=Sumulat: walang ginagawa
stats.write.throughput.kibs=Sumulat: %.2f KiB/s
stats.write.throughput.mibs=Sumulat: %.2f MiB/s
stats.write.total.data.none=Nakasulat na datos: -
stats.write.total.data.kib=Nakasulat na datos: %.1f KiB
stats.write.total.data.mib=Data na nakasulat: %.1f MiB
stats.write.total.data.gib=Nakasulat na datos: %.1f GiB
stats.encr.total.data.none=Naka-encrypt na data: -
stats.encr.total.data.kib=Naka-encrypt na data: %.1f KiB
stats.encr.total.data.mib=Naka-encrypt na data: %.1f MiB
stats.encr.total.data.gib=Naka-encrypt na data: %.1f GiB
stats.write.accessCount=Kabuuang pagsusulat: %d
## Accesses ## Accesses
stats.access.current=Access: %d
stats.access.total=Kabuuang mga access: %d
# Main Window # Main Window
main.closeBtn.tooltip=Isara main.closeBtn.tooltip=Isara
main.minimizeBtn.tooltip=I-minimize
main.preferencesBtn.tooltip=Mga Kagustuhan main.preferencesBtn.tooltip=Mga Kagustuhan
main.debugModeEnabled.tooltip=Naka-enable ang debug mode
main.supporterCertificateMissing.tooltip=Mangyaring isaalang-alang ang pagbibigay ng donasyon
## Vault List ## Vault List
main.vaultlist.emptyList.onboardingInstruction=Mag-click dito para magdagdag ng vault
main.vaultlist.contextMenu.remove=Alisin…
main.vaultlist.contextMenu.lock=I-lock main.vaultlist.contextMenu.lock=I-lock
main.vaultlist.contextMenu.unlock=I-unlock…
main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon main.vaultlist.contextMenu.unlockNow=I-unlock Ngayon
main.vaultlist.contextMenu.vaultoptions=Ipakita ang Mga Opsyon sa Vault
main.vaultlist.contextMenu.reveal=Ibunyag ang Drive
main.vaultlist.addVaultBtn=Idagdag main.vaultlist.addVaultBtn=Idagdag
main.vaultlist.addVaultBtn.menuItemNew=Bagong Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Kasalukuyang Vault...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Salamat sa pagpili sa Cryptomator para protektahan ang iyong mga file. Kung kailangan mo ng anumang tulong, tingnan ang aming mga gabay sa pagsisimula:
### Locked ### Locked
main.vaultDetail.lockedStatus=NAKA-LOCK
main.vaultDetail.unlockBtn=I-unlock…
main.vaultDetail.unlockNowBtn=I-unlock Ngayon main.vaultDetail.unlockNowBtn=I-unlock Ngayon
main.vaultDetail.optionsBtn=Mga Pagpipilian sa Vault
main.vaultDetail.passwordSavedInKeychain=Na-save ang password
### Unlocked ### Unlocked
main.vaultDetail.unlockedStatus=NAKA-unlock
main.vaultDetail.accessLocation=Ang mga nilalaman ng iyong vault ay maa-access dito:
main.vaultDetail.revealBtn=Ibunyag ang Drive
main.vaultDetail.copyUri=Kopyahin ang URI
main.vaultDetail.lockBtn=I-lock main.vaultDetail.lockBtn=I-lock
main.vaultDetail.bytesPerSecondRead=Basahin:
main.vaultDetail.bytesPerSecondWritten=Sumulat:
main.vaultDetail.throughput.idle=walang ginagawa
main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Mga Istatistika ng Vault
main.vaultDetail.locateEncryptedFileBtn=Hanapin ang Naka-encrypt na File
main.vaultDetail.locateEncryptedFileBtn.tooltip=Pumili ng file mula sa iyong vault upang mahanap ang naka-encrypt na katapat nito
main.vaultDetail.encryptedPathsCopied=Mga Path na Nakopya sa Clipboard!
main.vaultDetail.filePickerTitle=Piliin ang File Inside Vault
### Missing ### Missing
main.vaultDetail.missing.info=Hindi makahanap ng vault ang Cryptomator sa landas na ito.
main.vaultDetail.missing.recheck=Suriin muli
main.vaultDetail.missing.remove=Alisin sa Listahan ng Vault…
main.vaultDetail.missing.changeLocation=Baguhin ang Lokasyon ng Vault…
### Needs Migration ### Needs Migration
main.vaultDetail.migrateButton=I-upgrade ang Vault
main.vaultDetail.migratePrompt=Kailangang i-upgrade ang iyong vault sa bagong format, bago mo ito ma-access
### Error ### Error
main.vaultDetail.error.info=Nagkaroon ng error sa paglo-load ng vault mula sa disk.
main.vaultDetail.error.reload=Reload
main.vaultDetail.error.windowTitle=Error sa paglo-load ng vault
# Wrong File Alert # Wrong File Alert
wrongFileAlert.title=Paano Mag-encrypt ng mga File
wrongFileAlert.message=Sinubukan mo bang i-encrypt ang mga file na ito?
wrongFileAlert.description=Para sa layuning ito, nagbibigay ang Cryptomator ng volume sa iyong system file manager.
wrongFileAlert.instruction.0=Upang i-encrypt ang mga file, sundin ang mga hakbang na ito:
wrongFileAlert.instruction.1=1. I-unlock ang iyong vault.
wrongFileAlert.instruction.2=2. Mag-click sa "Reveal" para buksan ang volume sa iyong file manager.
wrongFileAlert.instruction.3=3. Idagdag ang iyong mga file sa volume na ito.
wrongFileAlert.link=Para sa karagdagang tulong, bisitahin ang
# Vault Options # Vault Options
## General ## General
vaultOptions.general=Heneral
vaultOptions.general.vaultName=Pangalan ng Vault vaultOptions.general.vaultName=Pangalan ng Vault
vaultOptions.general.autoLock.lockAfterTimePart1=I-lock kapag idle para sa
vaultOptions.general.autoLock.lockAfterTimePart2=minuto
vaultOptions.general.unlockAfterStartup=I-unlock ang vault kapag sinimulan ang Cryptomator
vaultOptions.general.actionAfterUnlock=Pagkatapos ng matagumpay na pag-unlock
vaultOptions.general.actionAfterUnlock.ignore=walang gawin
vaultOptions.general.actionAfterUnlock.reveal=Ibunyag ang Drive
vaultOptions.general.actionAfterUnlock.ask=Magtanong
vaultOptions.general.startHealthCheckBtn=Simulan ang Health Check
## Mount ## Mount
vaultOptions.mount=Pag-mount
vaultOptions.mount.info=Ang mga opsyon ay depende sa napiling uri ng volume.
vaultOptions.mount.linkToPreferences=Buksan ang mga kagustuhan sa virtual drive
vaultOptions.mount.readonly=Basahin lamang
vaultOptions.mount.customMountFlags=Mga custom na naka-mount na flag
vaultOptions.mount.winDriveLetterOccupied=inookupahan
vaultOptions.mount.mountPoint=Mount point
vaultOptions.mount.mountPoint.auto=Awtomatikong pumili ng angkop na lokasyon
vaultOptions.mount.mountPoint.driveLetter=Gumamit ng nakatalagang drive letter
vaultOptions.mount.mountPoint.custom=Gamitin ang napiling direktoryo
vaultOptions.mount.mountPoint.directoryPickerButton=Mamili… vaultOptions.mount.mountPoint.directoryPickerButton=Mamili…
vaultOptions.mount.mountPoint.directoryPickerTitle=Pumili ng isang direktoryo
## Master Key ## Master Key
vaultOptions.masterkey=Password
vaultOptions.masterkey.changePasswordBtn=Palitan ANG password vaultOptions.masterkey.changePasswordBtn=Palitan ANG password
vaultOptions.masterkey.forgetSavedPasswordBtn=Kalimutan ang Naka-save na Password
vaultOptions.masterkey.recoveryKeyExplanation=Ang recovery key ay ang tanging paraan mo upang maibalik ang access sa isang vault kung mawala mo ang iyong password.
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
vaultOptions.masterkey.recoverPasswordBtn=I-reset ang Password
## Hub ## Hub
vaultOptions.hub=Pagbawi
vaultOptions.hub.convertInfo=Maaari mong gamitin ang recovery key upang i-convert ang Hub vault na ito sa isang password-based na vault sa isang emergency.
vaultOptions.hub.convertBtn=I-convert sa Password-Based Vault
# Recovery Key # Recovery Key
## Display Recovery Key ## Display Recovery Key
recoveryKey.display.title=Ipakita ang Recovery Key
recoveryKey.create.message=Kailangan ng password
recoveryKey.create.description=Ilagay ang password para sa "%s" upang ipakita ang recovery key nito.
recoveryKey.display.description=Ang sumusunod na recovery key ay maaaring gamitin upang ibalik ang access sa "%s":
recoveryKey.display.StorageHints=Itago ito sa isang lugar na napaka-secure, hal.:\n • I-store ito gamit ang isang password manager\n • I-save ito sa isang USB flash drive\n • I-print ito sa papel
## Reset Password ## Reset Password
### Enter Recovery Key ### Enter Recovery Key
recoveryKey.recover.title=I-reset ang Password
recoveryKey.recover.prompt=Ilagay ang recovery key para sa "%s":
recoveryKey.recover.correctKey=Tama ang recovery key na ito
recoveryKey.recover.wrongKey=Ang recovery key na ito ay kabilang sa ibang vault
recoveryKey.recover.invalidKey=Hindi wasto ang recovery key na ito
recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n
### Reset Password ### Reset Password
recoveryKey.recover.resetBtn=I-reset
### Recovery Key Password Reset Success ### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Matagumpay ang pag-reset ng password
recoveryKey.recover.resetSuccess.description=Maaari mong i-unlock ang iyong vault gamit ang bagong password.
# Convert Vault # Convert Vault
convertVault.title=I-convert ang Vault
convertVault.convert.convertBtn.before=Magbalik-loob
convertVault.convert.convertBtn.processing=Kino-convert…
convertVault.success.message=Matagumpay ang conversion
convertVault.hubToPassword.success.description=Maaari mo na ngayong i-unlock ang vault gamit ang napiling password nang hindi nangangailangan ng access sa Hub.
# New Password # New Password
newPassword.promptText=Maglagay ng bagong password
newPassword.reenterPassword=Kumpirmahin ang bagong password
newPassword.passwordsMatch=Tugma ang mga password!
newPassword.passwordsDoNotMatch=Hindi tugma ang mga password
passwordStrength.messageLabel.tooShort=Gumamit ng hindi bababa sa %d character
passwordStrength.messageLabel.0=Napakahina
passwordStrength.messageLabel.1=Mahina
passwordStrength.messageLabel.2=Patas
passwordStrength.messageLabel.3=Malakas
passwordStrength.messageLabel.4=Napakalakas
# Quit # Quit
quit.title=Ihinto ang Application
quit.message=May mga naka-unlock na vault
quit.description=Pakikumpirma na gusto mong umalis. Ila-lock ng Cryptomator ang lahat ng naka-unlock na vault para maiwasan ang pagkawala ng data.
quit.lockAndQuitBtn=Lock at Quit
# Forced Quit # Forced Quit
quit.forced.message=Hindi ma-lock ang ilang vault
quit.forced.description=Ang pag-lock ng mga vault ay na-block ng mga nakabinbing operasyon o bukas na mga file. Maaari mong pilitin na i-lock ang mga natitirang vault, gayunpaman ang pagkagambala sa I/O ay maaaring magresulta sa pagkawala ng hindi na-save na data.
quit.forced.forceAndQuitBtn=Puwersa at Umalis
# Update Reminder # Update Reminder
updateReminder.message=I-tsek kung may bagong update? updateReminder.title=Muling iwasto
updateReminder.message=I-tsek kung may bagong update?
updateReminder.description=Manatiling updated sa mga bagong feature, pag-aayos ng bug, at pagpapahusay sa seguridad. Inirerekomenda naming awtomatikong suriin ang mga update.
updateReminder.notNow=Hindi ngayon
updateReminder.yesOnce=Oo, Minsan
updateReminder.yesAutomatically=Oo, Awtomatiko
@@ -153,9 +153,11 @@ hub.auth.loginLink=Vous n'avez pas été redirigé(e) ? Cliquez ici pour l'ouvri
hub.receive.message=Traitement de la réponse… hub.receive.message=Traitement de la réponse…
hub.receive.description=Cryptomator est en train de recevoir et de traiter la réponse de Hub. Veuillez patienter. hub.receive.description=Cryptomator est en train de recevoir et de traiter la réponse de Hub. Veuillez patienter.
### Register Device ### Register Device
hub.register.message=Le nom de l'appareil est requis hub.register.message=Nouvel Appareil
hub.register.description=Il semble que ce soit le premier accès à Hub depuis cet appareil. Afin de l'identifier pour l'autorisation d'accès, vous devez nommer cet appareil. hub.register.description=Il s'agit du premier accès Hub de cet appareil. Veuillez l'autoriser en utilisant votre code d'installation.
hub.register.nameLabel=Nom de l'appareil hub.register.nameLabel=Nom de l'appareil
hub.register.setupCodeLabel=Code de configuration
hub.register.invalidSetupCode=Code de configuration invalide
hub.register.occupiedMsg=Nom déjà utilisé hub.register.occupiedMsg=Nom déjà utilisé
hub.register.registerBtn=Confirmer hub.register.registerBtn=Confirmer
### Registration Success ### Registration Success
+22 -3
View File
@@ -20,6 +20,11 @@ error.description=Cryptomator לא ציפתה שזה יקרה. ניתן לחפש
error.hyperlink.lookup=חיפוש שגיאה error.hyperlink.lookup=חיפוש שגיאה
error.hyperlink.report=דיווח על שגיאה error.hyperlink.report=דיווח על שגיאה
error.technicalDetails=פרטים: error.technicalDetails=פרטים:
error.existingSolutionDescription=Cryptomator לא ציפה שזה יקרה. אך מצאנו פתרון קיים לשגיאה זו. בבקשה לבדוק את הקישור הבא.
error.hyperlink.solution=חפש את הפתרון
error.lookupPermissionMessage=Cryptomator יכול לחפש פתרון לבעיה זו באינטרנט. זה ישלח בקשה למסד הנתונים של הבעיה מכתובת ה-IP שלך.
error.dismiss=שחרור
error.lookUpSolution=חפש את הפתרון
# Defaults # Defaults
defaults.vault.vaultName=כספת defaults.vault.vaultName=כספת
@@ -36,6 +41,7 @@ traymenu.vault.reveal=חשוף
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=הוספת כספת addvaultwizard.title=הוספת כספת
## New ## New
addvaultwizard.new.title=הוסף ארגז חדש
### Name ### Name
addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת addvaultwizard.new.nameInstruction=בחירת שם עבור הכספת
addvaultwizard.new.namePrompt=שם הכספת addvaultwizard.new.namePrompt=שם הכספת
@@ -57,6 +63,11 @@ addvaultwizard.new.validCharacters.chars=תווי מילים (למשל: a, ж or
addvaultwizard.new.validCharacters.numbers=מספרים addvaultwizard.new.validCharacters.numbers=מספרים
addvaultwizard.new.validCharacters.dashes=מקף (%s) או קו תחתון (%s) addvaultwizard.new.validCharacters.dashes=מקף (%s) או קו תחתון (%s)
### Expert Settings ### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=הפעל הגדרות מתקדמות
addvaultwizard.new.expertSettings.shorteningThreshold.invalid=הזן ערך בין 36 ל-220 (ברירת המחדל 220)
addvaultwizard.new.expertSettings.shorteningThreshold.tooltip=פתח את התיעוד כדי ללמוד עוד.
addvaultwizard.new.expertSettings.shorteningThreshold.title=האורך המרבי של שמות הקבצים המוצפנים
addvaultwizard.new.expertSettings.shorteningThreshold.valid=בתוקף
### Password ### Password
addvaultwizard.new.createVaultBtn=צור כספת חדשה addvaultwizard.new.createVaultBtn=צור כספת חדשה
addvaultwizard.new.generateRecoveryKeyChoice=לא תיהיה אפשרות לקבל גישה למידע שלך בלי הסיסמה שלך. האם תרצה/י מפתח שחזור למקרה ותאבד/י את הסיסמה שלך? addvaultwizard.new.generateRecoveryKeyChoice=לא תיהיה אפשרות לקבל גישה למידע שלך בלי הסיסמה שלך. האם תרצה/י מפתח שחזור למקרה ותאבד/י את הסיסמה שלך?
@@ -80,6 +91,7 @@ addvault.new.readme.accessLocation.2=זהו מיקום גישה לכספת של
addvault.new.readme.accessLocation.3=כל קובץ אשר יצורף לספרייה זו יעבור הצפנה באמצעות Cryptomator. את/ה תוכל/י לעבוד עליו כמו עם כל קבוץ/מחיצה רגילים. זהו מצב הצגה מפוענח של התוכן, הקבצים שלך נשארים מוצפנים על הדיסק הקשיח שלך בכל רגע. addvault.new.readme.accessLocation.3=כל קובץ אשר יצורף לספרייה זו יעבור הצפנה באמצעות Cryptomator. את/ה תוכל/י לעבוד עליו כמו עם כל קבוץ/מחיצה רגילים. זהו מצב הצגה מפוענח של התוכן, הקבצים שלך נשארים מוצפנים על הדיסק הקשיח שלך בכל רגע.
addvault.new.readme.accessLocation.4=תרגיש/י בנוח להסיר את הקובץ הזה. addvault.new.readme.accessLocation.4=תרגיש/י בנוח להסיר את הקובץ הזה.
## Existing ## Existing
addvaultwizard.existing.title=הוסף כספת קיימת
addvaultwizard.existing.instruction=בחר את קובץ "vault.cryptomator" של כספת קיימת. אם קיים קובץ בשם "masterkey.cryptomator" בלבד, בחר/י אותו במקום. addvaultwizard.existing.instruction=בחר את קובץ "vault.cryptomator" של כספת קיימת. אם קיים קובץ בשם "masterkey.cryptomator" בלבד, בחר/י אותו במקום.
addvaultwizard.existing.chooseBtn=בחר... addvaultwizard.existing.chooseBtn=בחר...
addvaultwizard.existing.filePickerTitle=בחר קובץ כספת addvaultwizard.existing.filePickerTitle=בחר קובץ כספת
@@ -124,7 +136,10 @@ unlock.success.revealBtn=חשוף את הכונן
unlock.error.customPath.message=כשלון בקישור הכספת לנתיב הידני שהוגדר unlock.error.customPath.message=כשלון בקישור הכספת לנתיב הידני שהוגדר
unlock.error.customPath.description.notSupported=אם ברצונך להשתמש בנתיב ידני, אנא גש להעדפות ובחר סוג volume שתומך בכך. אחרת, לך לאפשרויות הכספת ובחר אפשרות יעד קישור נתמך. unlock.error.customPath.description.notSupported=אם ברצונך להשתמש בנתיב ידני, אנא גש להעדפות ובחר סוג volume שתומך בכך. אחרת, לך לאפשרויות הכספת ובחר אפשרות יעד קישור נתמך.
unlock.error.customPath.description.notExists=יעד הקישור הידני לא קיים. או שתיצור אותו במערכת הקבצים המקומית או שנה אותו באפשרויות הכספת. unlock.error.customPath.description.notExists=יעד הקישור הידני לא קיים. או שתיצור אותו במערכת הקבצים המקומית או שנה אותו באפשרויות הכספת.
unlock.error.customPath.description.inUse=אות דיסק או הנתיב המותאם "%s" כבר בשימוש.
unlock.error.customPath.description.hideawayNotDir=הקובץ הזמני, הנסתר %3$s עבור פתיחה לא ניתן להסרה. יש לבדוק את הקובץ ולמחוק אותו ידנית. unlock.error.customPath.description.hideawayNotDir=הקובץ הזמני, הנסתר %3$s עבור פתיחה לא ניתן להסרה. יש לבדוק את הקובץ ולמחוק אותו ידנית.
unlock.error.customPath.description.couldNotBeCleaned=הקלדת חנייה שלך לא יכולה להיות מחוברת לנתיב "%s". אנא נסה שוב או בחר נתיב אחר.
unlock.error.customPath.description.notEmptyDir=הנתיב המותאם "%s" אינו תיקייה ריקה. אנא בחר תיקייה ריקה ונסה שוב.
## Hub ## Hub
hub.noKeychain.message=לא ניתן לגשת למפתח המכשיר hub.noKeychain.message=לא ניתן לגשת למפתח המכשיר
hub.noKeychain.description=כדאי לשחרר כספות האב נדרש מפתח מכשיר שיאובטח בצרור מפתחות. כדאי להמשיך, אפשר ״%s״ ובחר את צרור המפתחות בהעדפות. hub.noKeychain.description=כדאי לשחרר כספות האב נדרש מפתח מכשיר שיאובטח בצרור מפתחות. כדאי להמשיך, אפשר ״%s״ ובחר את צרור המפתחות בהעדפות.
@@ -137,8 +152,6 @@ hub.auth.loginLink=לא הופנת? לחצן כאן לפתיחה.
hub.receive.message=מעבד תשובה… hub.receive.message=מעבד תשובה…
hub.receive.description=Cryptomator מקבל ומעבד את התשובה מה- האב. אנא המתן. hub.receive.description=Cryptomator מקבל ומעבד את התשובה מה- האב. אנא המתן.
### Register Device ### Register Device
hub.register.message=שם המכשיר נדרש
hub.register.description=נראה שזו הגישה הראשונה ל- האב ממכשיר זה. כדי לזהות אותך למתן הרשאות, עליך לתת שם למכשיר זה.
hub.register.nameLabel=שם מכשיר hub.register.nameLabel=שם מכשיר
hub.register.occupiedMsg=שם זה נמצא כבר בשימוש hub.register.occupiedMsg=שם זה נמצא כבר בשימוש
hub.register.registerBtn=אישור hub.register.registerBtn=אישור
@@ -350,6 +363,7 @@ main.vaultlist.contextMenu.unlockNow=בטל נעילה כעת
main.vaultlist.contextMenu.vaultoptions=הצג את אפשרויות הכספת main.vaultlist.contextMenu.vaultoptions=הצג את אפשרויות הכספת
main.vaultlist.contextMenu.reveal=חשוף את הכונן main.vaultlist.contextMenu.reveal=חשוף את הכונן
main.vaultlist.addVaultBtn=הוספה main.vaultlist.addVaultBtn=הוספה
main.vaultlist.addVaultBtn.menuItemNew=פתיחת כספת...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=תודה שבחרת ב- Cryptomator להגן על הקבצים שלך. אם אתה זקוק לסיוע, אנא עיין במדריכים שלנו: main.vaultDetail.welcomeOnboarding=תודה שבחרת ב- Cryptomator להגן על הקבצים שלך. אם אתה זקוק לסיוע, אנא עיין במדריכים שלנו:
@@ -455,6 +469,8 @@ recoveryKey.recover.resetSuccess.message=איפוס סיסמה הצליח
recoveryKey.recover.resetSuccess.description=ניתן לפתוח את הכספת עם הסיסמה החדשה. recoveryKey.recover.resetSuccess.description=ניתן לפתוח את הכספת עם הסיסמה החדשה.
# Convert Vault # Convert Vault
convertVault.convert.convertBtn.before=להמיר
convertVault.success.message=המרה הושלמה בהצלחה
# New Password # New Password
newPassword.promptText=הקש סיסמה חדשה newPassword.promptText=הקש סיסמה חדשה
@@ -479,4 +495,7 @@ quit.forced.message=חלק מה vaults לא היו ניתנים לנעילה
quit.forced.description=נעילה כספות נחסמה על ידי תהליכים ממתינים או קבצים פתוחים. אתה יכול לנעול את הכספת בכוח, אולם הפרעה לפעולת קריאה וכתיבה עשויה לגרום לאובדן מידע לא שמור. quit.forced.description=נעילה כספות נחסמה על ידי תהליכים ממתינים או קבצים פתוחים. אתה יכול לנעול את הכספת בכוח, אולם הפרעה לפעולת קריאה וכתיבה עשויה לגרום לאובדן מידע לא שמור.
quit.forced.forceAndQuitBtn=נעילה בכח ויציאה quit.forced.forceAndQuitBtn=נעילה בכח ויציאה
# Update Reminder # Update Reminder
updateReminder.title=בדיקת עדכון
updateReminder.message=חפש עדכונים?
updateReminder.notNow=לא עכשיו
@@ -147,8 +147,6 @@ hub.auth.loginLink=Nem sikerült az átirányítás? Kattintson ide a megnyitás
hub.receive.message=Válasz feldolgozása… hub.receive.message=Válasz feldolgozása…
hub.receive.description=Cryptomator fogadja és feldolgozza a Hub válaszát. Kérem, várjon. hub.receive.description=Cryptomator fogadja és feldolgozza a Hub válaszát. Kérem, várjon.
### Register Device ### Register Device
hub.register.message=Eszköznév szükséges
hub.register.description=Úgy tűnik, ez az első Hub-hozzáférés erről az eszközről. A hozzáférési jogosultság azonosításához el kell neveznie ezt az eszközt.
hub.register.nameLabel=Készülék neve hub.register.nameLabel=Készülék neve
hub.register.occupiedMsg=Ez a név már használatban van hub.register.occupiedMsg=Ez a név már használatban van
hub.register.registerBtn=Megerősítés hub.register.registerBtn=Megerősítés
@@ -153,9 +153,11 @@ hub.auth.loginLink=Non reindirizzato? Clicca qui per aprirlo.
hub.receive.message=Elaborazione della risposta… hub.receive.message=Elaborazione della risposta…
hub.receive.description=Cryptomator sta ricevendo ed elaborando la risposta da Hub. Attendere prego. hub.receive.description=Cryptomator sta ricevendo ed elaborando la risposta da Hub. Attendere prego.
### Register Device ### Register Device
hub.register.message=Nome del dispositivo richiesto hub.register.message=Nuovo dispositivo
hub.register.description=Questo sembra essere il primo accesso Hub da questo dispositivo. Per identificarlo per l'autorizzazione di accesso, è necessario nominare questo dispositivo. hub.register.description=Questo è il primo accesso Hub da questo dispositivo. Si prega di autorizzarlo utilizzando il codice di configurazione.
hub.register.nameLabel=Nome Del Dispositivo hub.register.nameLabel=Nome Del Dispositivo
hub.register.setupCodeLabel=Codice di configurazione
hub.register.invalidSetupCode=Codice di configurazione non valido
hub.register.occupiedMsg=Nome già in uso hub.register.occupiedMsg=Nome già in uso
hub.register.registerBtn=Conferma hub.register.registerBtn=Conferma
### Registration Success ### Registration Success
@@ -153,9 +153,11 @@ hub.auth.loginLink=リダイレクトされませんでしたか? ここをク
hub.receive.message=応答を処理中… hub.receive.message=応答を処理中…
hub.receive.description=Cryptomator が Hub からの応答を受信、処理中です。しばらくお待ちください。 hub.receive.description=Cryptomator が Hub からの応答を受信、処理中です。しばらくお待ちください。
### Register Device ### Register Device
hub.register.message=デバイスの名前が必要です hub.register.message=新しいデバイス
hub.register.description=このデバイスからハブにアクセスするのは初めてのようです。アクセス認証の際のデバイス識別のためにこのデバイスに名前を付ける必要があります hub.register.description=このデバイスから初めて Hub にアクセスします。セットアップ コードを使用して、認証してください
hub.register.nameLabel=デバイス名 hub.register.nameLabel=デバイス名
hub.register.setupCodeLabel=セットアップ コード
hub.register.invalidSetupCode=不正なセットアップ コード
hub.register.occupiedMsg=この名前は既に使われています hub.register.occupiedMsg=この名前は既に使われています
hub.register.registerBtn=確認 hub.register.registerBtn=確認
### Registration Success ### Registration Success
+22 -1
View File
@@ -20,6 +20,7 @@ error.description=예상치 못한 에러가 발생했습니다. 온라인에
error.hyperlink.lookup=에러 검색하기 error.hyperlink.lookup=에러 검색하기
error.hyperlink.report=에러 보고하기 error.hyperlink.report=에러 보고하기
error.technicalDetails=상세 정보: error.technicalDetails=상세 정보:
error.dismiss=무시
# Defaults # Defaults
defaults.vault.vaultName=Vault defaults.vault.vaultName=Vault
@@ -36,6 +37,7 @@ traymenu.vault.reveal=표시
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Vault 추가 addvaultwizard.title=Vault 추가
## New ## New
addvaultwizard.new.title=새로운 금고 추가
### Name ### Name
addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시요 addvaultwizard.new.nameInstruction=새 Vault의 이름을 입력하십시요
addvaultwizard.new.namePrompt=Vault 이름 addvaultwizard.new.namePrompt=Vault 이름
@@ -57,6 +59,7 @@ addvaultwizard.new.validCharacters.chars=문자 (예시: a, ж or 수)
addvaultwizard.new.validCharacters.numbers=숫자 addvaultwizard.new.validCharacters.numbers=숫자
addvaultwizard.new.validCharacters.dashes=대시 (%s) 또는 언더바 (%s) addvaultwizard.new.validCharacters.dashes=대시 (%s) 또는 언더바 (%s)
### Expert Settings ### Expert Settings
addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox=전문가용 설정 활성화
### Password ### Password
addvaultwizard.new.createVaultBtn=Vault 생성 addvaultwizard.new.createVaultBtn=Vault 생성
addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터에 접근할 수 없습니다. 비밀번호를 잊었을 때를 대비한 복구 키를 원하십니까? addvaultwizard.new.generateRecoveryKeyChoice=비밀번호가 없으면 데이터에 접근할 수 없습니다. 비밀번호를 잊었을 때를 대비한 복구 키를 원하십니까?
@@ -80,6 +83,7 @@ addvault.new.readme.accessLocation.2=이것은 당신의 Vault 접근 위치입
addvault.new.readme.accessLocation.3=이 볼륨에 추가된 모든 파일은 Cryptomator로 암호화됩니다. 다른 드라이브/폴더처럼 작업할 수 있습니다. 볼륨의 내용은 복호화 된 것 처럼 보여지지만, 모든 파일은 항상 암호화되어 하드디스크에 저장됩니다. addvault.new.readme.accessLocation.3=이 볼륨에 추가된 모든 파일은 Cryptomator로 암호화됩니다. 다른 드라이브/폴더처럼 작업할 수 있습니다. 볼륨의 내용은 복호화 된 것 처럼 보여지지만, 모든 파일은 항상 암호화되어 하드디스크에 저장됩니다.
addvault.new.readme.accessLocation.4=이 파일은 지우셔도 무방합니다. addvault.new.readme.accessLocation.4=이 파일은 지우셔도 무방합니다.
## Existing ## Existing
addvaultwizard.existing.title=기존 금고 추가
addvaultwizard.existing.instruction=이미 존재하는 vault 폴더 내에서 "vault.cryptomator" 파일을 선택하세요. 만약 "masterkey.cryptomator"만 있다면 그걸 대신 선택하세요. addvaultwizard.existing.instruction=이미 존재하는 vault 폴더 내에서 "vault.cryptomator" 파일을 선택하세요. 만약 "masterkey.cryptomator"만 있다면 그걸 대신 선택하세요.
addvaultwizard.existing.chooseBtn=선택 addvaultwizard.existing.chooseBtn=선택
addvaultwizard.existing.filePickerTitle=Vault 파일 선택 addvaultwizard.existing.filePickerTitle=Vault 파일 선택
@@ -122,6 +126,7 @@ unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음
unlock.success.revealBtn=드라이브 표시 unlock.success.revealBtn=드라이브 표시
## Failure ## Failure
## Hub ## Hub
hub.noKeychain.openBtn=설정 열기
### Waiting ### Waiting
### Receive Key ### Receive Key
### Register Device ### Register Device
@@ -204,6 +209,7 @@ health.check.detail.checkCancelled=검사가 취소되었습니다
health.check.detail.listFilters.label=필터 health.check.detail.listFilters.label=필터
health.check.exportBtn=보고서 내보내기 health.check.exportBtn=보고서 내보내기
## Result view ## Result view
health.result.severityFilter.warn=경고
## Fix Application ## Fix Application
health.fix.fixBtn=문제해결 health.fix.fixBtn=문제해결
health.fix.successTip=문제 해결이 성공적으로 완료되었습니다 health.fix.successTip=문제 해결이 성공적으로 완료되었습니다
@@ -238,6 +244,8 @@ preferences.volume.type=볼륨 유형
preferences.volume.type.automatic=자동 preferences.volume.type.automatic=자동
preferences.volume.tcp.port=TCP 포트 preferences.volume.tcp.port=TCP 포트
preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다: preferences.volume.supportedFeatures=현재 선택한 볼륨 타입은 다음과 같은 기능들을 지원합니다:
preferences.volume.feature.mountToDir=마운트할 폴더 지정
preferences.volume.feature.mountToDriveLetter=마운트할 드라이브 문자
preferences.volume.feature.mountFlags=사용자 정의 마운트 설정 preferences.volume.feature.mountFlags=사용자 정의 마운트 설정
preferences.volume.feature.readOnly=읽기 전용 마운트 preferences.volume.feature.readOnly=읽기 전용 마운트
## Updates ## Updates
@@ -262,16 +270,20 @@ stats.title=%s에 대한 통계
stats.cacheHitRate=캐시 히트율 stats.cacheHitRate=캐시 히트율
## Read ## Read
stats.read.throughput.idle=읽기: 대기중 stats.read.throughput.idle=읽기: 대기중
stats.read.throughput.kibs=읽기: %.2f KiB/s
stats.read.throughput.mibs=읽기: %.2f MiB/s stats.read.throughput.mibs=읽기: %.2f MiB/s
stats.read.total.data.none=데이터 읽기: - stats.read.total.data.none=데이터 읽기: -
stats.read.total.data.kib=데이터 읽기: %.1f KiB
stats.read.total.data.mib=데이터 읽기: %.1f MiB stats.read.total.data.mib=데이터 읽기: %.1f MiB
stats.read.total.data.gib=데이터 읽기: %.1f GiB stats.read.total.data.gib=데이터 읽기: %.1f GiB
stats.decr.total.data.none=데이터 복호화: - stats.decr.total.data.none=데이터 복호화: -
stats.decr.total.data.kib=데이터 복호화: %.1f KiB
stats.decr.total.data.mib=데이터 복호화: %.1f MiB stats.decr.total.data.mib=데이터 복호화: %.1f MiB
stats.decr.total.data.gib=데이터 복호화: %.1f GiB stats.decr.total.data.gib=데이터 복호화: %.1f GiB
stats.read.accessCount=총 읽기 횟수: %d stats.read.accessCount=총 읽기 횟수: %d
## Write ## Write
stats.write.throughput.idle=쓰기: 대기중 stats.write.throughput.idle=쓰기: 대기중
stats.write.throughput.kibs=쓰기: %.2f KiB/s
stats.write.throughput.mibs=쓰기: %.2f MiB/s stats.write.throughput.mibs=쓰기: %.2f MiB/s
stats.write.total.data.none=데이터 기록됨: - stats.write.total.data.none=데이터 기록됨: -
stats.write.total.data.kib=데이터 쓰기: %.1f KiB stats.write.total.data.kib=데이터 쓰기: %.1f KiB
@@ -284,6 +296,7 @@ stats.encr.total.data.gib=데이터 암호화: %.1f GiB
stats.write.accessCount=총 쓰기 횟수: %d stats.write.accessCount=총 쓰기 횟수: %d
## Accesses ## Accesses
stats.access.current=접근: %d
# Main Window # Main Window
@@ -301,6 +314,8 @@ main.vaultlist.contextMenu.unlockNow=지금 잠금해제
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기 main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
main.vaultlist.contextMenu.reveal=드라이브 표시 main.vaultlist.contextMenu.reveal=드라이브 표시
main.vaultlist.addVaultBtn=추가 main.vaultlist.addVaultBtn=추가
main.vaultlist.addVaultBtn.menuItemNew=새로운 금고...
main.vaultlist.addVaultBtn.menuItemExisting=기존 금고...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다. main.vaultDetail.welcomeOnboarding=파일을 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작안내서를 참조하시기 바랍니다.
@@ -322,6 +337,7 @@ main.vaultDetail.throughput.idle=대기
main.vaultDetail.throughput.kbps=%.1f KiB/s main.vaultDetail.throughput.kbps=%.1f KiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Vault 통계 main.vaultDetail.stats=Vault 통계
main.vaultDetail.locateEncryptedFileBtn=암호화된 파일 위치
### Missing ### Missing
main.vaultDetail.missing.info=Cryptomator가 이 경로에 있는 Vault를 찾지 못했습니다. main.vaultDetail.missing.info=Cryptomator가 이 경로에 있는 Vault를 찾지 못했습니다.
main.vaultDetail.missing.recheck=다시 시도 main.vaultDetail.missing.recheck=다시 시도
@@ -400,6 +416,9 @@ recoveryKey.recover.resetSuccess.message=비밀번호 재설정 성공
recoveryKey.recover.resetSuccess.description=이제 해당 vault를 새 비밀번호로 잠금 해제할 수 있습니다. recoveryKey.recover.resetSuccess.description=이제 해당 vault를 새 비밀번호로 잠금 해제할 수 있습니다.
# Convert Vault # Convert Vault
convertVault.convert.convertBtn.before=변환
convertVault.convert.convertBtn.processing=변환중…
convertVault.success.message=변환 완료
# New Password # New Password
newPassword.promptText=새 비밀번호를 입력하세요 newPassword.promptText=새 비밀번호를 입력하세요
@@ -421,4 +440,6 @@ quit.lockAndQuitBtn=Vault 잠금 후 종료하기
# Forced Quit # Forced Quit
quit.forced.forceAndQuitBtn=Vault 강제 잠금 후 종료하기 quit.forced.forceAndQuitBtn=Vault 강제 잠금 후 종료하기
# Update Reminder # Update Reminder
updateReminder.title=업데이트 확인
updateReminder.notNow=나중에
@@ -153,9 +153,11 @@ hub.auth.loginLink=Ikke videresendt? Klikk her for å åpne den.
hub.receive.message=Prosesserer svar… hub.receive.message=Prosesserer svar…
hub.receive.description=Cryptomator mottar og behandler svaret fra Hub. Vennligst vent. hub.receive.description=Cryptomator mottar og behandler svaret fra Hub. Vennligst vent.
### Register Device ### Register Device
hub.register.message=Enhetsnavn påkrevd hub.register.message=Ny Enhet
hub.register.description=Dette ser ut til å være den første Hub-tilgangen fra denne enheten. For å kunne identifisere den for tilgangsautorisasjon, må du å navngi denne enheten. hub.register.description=Dette er den første Hub-tilgangen fra denne enheten. Vennligst autoriser den ved å bruke oppsettskoden.
hub.register.nameLabel=Enhetsnavn hub.register.nameLabel=Enhetsnavn
hub.register.setupCodeLabel=Installasjonkode
hub.register.invalidSetupCode=Feil Installasjonkode
hub.register.occupiedMsg=Navnet er allerede i bruk hub.register.occupiedMsg=Navnet er allerede i bruk
hub.register.registerBtn=Bekreft hub.register.registerBtn=Bekreft
### Registration Success ### Registration Success
@@ -153,9 +153,11 @@ hub.auth.loginLink=Niet omgeleid? Klik hier om het te openen.
hub.receive.message=Antwoord verwerken… hub.receive.message=Antwoord verwerken…
hub.receive.description=Cryptomator ontvangt en verwerkt de reactie van Hub. Een ogenblik geduld. hub.receive.description=Cryptomator ontvangt en verwerkt de reactie van Hub. Een ogenblik geduld.
### Register Device ### Register Device
hub.register.message=Apparaatnaam vereist hub.register.message=Nieuw apparaat
hub.register.description=Dit lijkt de eerste Hub toegang te zijn vanaf dit apparaat. Om dit apparaat te kunnen identificeren voor autorisatie, moet u dit apparaat benoemen. hub.register.description=Dit is de eerste Hub toegang vanaf dit apparaat. Gelieve toegang te bevestigen met behulp van uw installatiecode.
hub.register.nameLabel=Apparaatnaam hub.register.nameLabel=Apparaatnaam
hub.register.setupCodeLabel=Installatiecode
hub.register.invalidSetupCode=Ongeldige installatiecode
hub.register.occupiedMsg=Naam al in gebruik hub.register.occupiedMsg=Naam al in gebruik
hub.register.registerBtn=Bevestig hub.register.registerBtn=Bevestig
### Registration Success ### Registration Success
@@ -153,9 +153,11 @@ hub.auth.loginLink=Nie przekierowano? Kliknij tutaj.
hub.receive.message=Przetwarzanie odpowiedzi… hub.receive.message=Przetwarzanie odpowiedzi…
hub.receive.description=Cryptomator odbiera i przetwarza odpowiedź z Huba, proszę czekać. hub.receive.description=Cryptomator odbiera i przetwarza odpowiedź z Huba, proszę czekać.
### Register Device ### Register Device
hub.register.message=Wymagana nazwa urządzenia hub.register.message=Nowe Urządzenie
hub.register.description=Wygląda że jest to pierwszy dostęp do Huba z tego urządzenia. Aby zidentyfikować go w celu uzyskania autoryzacji dostępu, musisz nazwać to urządzenie. hub.register.description=To jest pierwszy dostęp do centrum z tego urządzenia. Proszę autoryzować go za pomocą kodu konfiguracyjnego.
hub.register.nameLabel=Nazwa urządzenia hub.register.nameLabel=Nazwa urządzenia
hub.register.setupCodeLabel=Kod konfiguracyjny
hub.register.invalidSetupCode=Błędny kod konfiguracyjny
hub.register.occupiedMsg=Nazwa jest już używana hub.register.occupiedMsg=Nazwa jest już używana
hub.register.registerBtn=Zatwierdź hub.register.registerBtn=Zatwierdź
### Registration Success ### Registration Success
@@ -340,10 +342,10 @@ stats.write.total.data.none=Zapisane dane: -
stats.write.total.data.kib=Zapis danych: %.1f KiB stats.write.total.data.kib=Zapis danych: %.1f KiB
stats.write.total.data.mib=Zapisane dane: %.1f MiB stats.write.total.data.mib=Zapisane dane: %.1f MiB
stats.write.total.data.gib=Zapisane dane: %.1f GiB stats.write.total.data.gib=Zapisane dane: %.1f GiB
stats.encr.total.data.none=Dane odszyfrowane: - stats.encr.total.data.none=Dane zaszyfrowane: -
stats.encr.total.data.kib=Dane zaszyfrowane: %.1f KiB stats.encr.total.data.kib=Dane zaszyfrowane: %.1f KiB
stats.encr.total.data.mib=Dane odszyfrowane: %.1f MiB stats.encr.total.data.mib=Dane zaszyfrowane: %.1f MiB
stats.encr.total.data.gib=Dane odszyfrowane: %.1f GiB stats.encr.total.data.gib=Dane zaszyfrowane: %.1f GiB
stats.write.accessCount=Całkowity zapis: %d stats.write.accessCount=Całkowity zapis: %d
## Accesses ## Accesses
@@ -151,8 +151,6 @@ hub.auth.loginLink=Não foi redirecionado? Clique aqui para abrir.
hub.receive.message=A processar a resposta… hub.receive.message=A processar a resposta…
hub.receive.description=Cryptomator está a receber e a processar a resposta do Hub. Por favor aguarde. hub.receive.description=Cryptomator está a receber e a processar a resposta do Hub. Por favor aguarde.
### Register Device ### Register Device
hub.register.message=Nome do dispositivo necessário
hub.register.description=Parece ser o primeiro acesso ao Hub a partir deste dispositivo. Para identificá-lo para autorização de acesso, é preciso dar um nome a este dispositivo.
hub.register.nameLabel=Nome do dispositivo hub.register.nameLabel=Nome do dispositivo
hub.register.occupiedMsg=Nome já utilizado hub.register.occupiedMsg=Nome já utilizado
hub.register.registerBtn=Confirmar hub.register.registerBtn=Confirmar
@@ -167,7 +165,7 @@ hub.unauthorized.message=Acesso negado
hub.unauthorized.description=O seu dispositivo ainda não foi autorizado a aceder a este cofre. Peça ao proprietário do cofre para o autorizar. hub.unauthorized.description=O seu dispositivo ainda não foi autorizado a aceder a este cofre. Peça ao proprietário do cofre para o autorizar.
### License Exceeded ### License Exceeded
hub.invalidLicense.message=Licença Hub inválida hub.invalidLicense.message=Licença Hub inválida
hub.invalidLicense.description=O pedido do seu Cryptomator Hub tem uma licença inválida. Informe um administrador do Hub para atualizar ou renovar a licença. hub.invalidLicense.description=A entidade do seu Cryptomator Hub tem uma licença inválida. Por favor, informe o administrador do Hub para atualizar ou renovar a licença.
# Lock # Lock
## Force ## Force
@@ -153,9 +153,11 @@ hub.auth.loginLink=Não foi redirecionado? Clique aqui para abrir.
hub.receive.message=Processando resposta… hub.receive.message=Processando resposta…
hub.receive.description=Cryptomator está recebendo e processando a resposta do Hub. Por favor, aguarde. hub.receive.description=Cryptomator está recebendo e processando a resposta do Hub. Por favor, aguarde.
### Register Device ### Register Device
hub.register.message=Nome do dispositivo necessário hub.register.message=Novo Dispositivo
hub.register.description=Este parece ser o seu primeiro acesso ao Hub deste dispositivo. Para ser identificado e ter autorização de acesso, você precisa nomear este dispositivo. hub.register.description=Este é o primeiro acesso do Hub a partir deste dispositivo. Por favor, autorize-o usando seu código de configuração.
hub.register.nameLabel=Nome do dispositivo hub.register.nameLabel=Nome do dispositivo
hub.register.setupCodeLabel=Código de Configuração
hub.register.invalidSetupCode=Código de Configuração Invalido
hub.register.occupiedMsg=Este nome já está em uso hub.register.occupiedMsg=Este nome já está em uso
hub.register.registerBtn=Confirme hub.register.registerBtn=Confirme
### Registration Success ### Registration Success
+11 -4
View File
@@ -41,6 +41,7 @@ traymenu.vault.reveal=Afişare
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Adaugă un seif addvaultwizard.title=Adaugă un seif
## New ## New
addvaultwizard.new.title=Adaugă seif nou
### Name ### Name
addvaultwizard.new.nameInstruction=Alege un nume pentru seif addvaultwizard.new.nameInstruction=Alege un nume pentru seif
addvaultwizard.new.namePrompt=Nume seif addvaultwizard.new.namePrompt=Nume seif
@@ -55,8 +56,8 @@ addvaultwizard.new.fileAlreadyExists=Există deja un fișier sau un dosar cu num
addvaultwizard.new.locationDoesNotExist=Dosarul în calea specificată nu există sau nu poate fi accesat addvaultwizard.new.locationDoesNotExist=Dosarul în calea specificată nu există sau nu poate fi accesat
addvaultwizard.new.locationIsNotWritable=Nu există acces la scriere la calea specificată addvaultwizard.new.locationIsNotWritable=Nu există acces la scriere la calea specificată
addvaultwizard.new.locationIsOk=Locația potrivită pentru seiful dumneavoastră addvaultwizard.new.locationIsOk=Locația potrivită pentru seiful dumneavoastră
addvaultwizard.new.invalidName=Nume de seif invalid addvaultwizard.new.invalidName=Nume nevalid pentru seif
addvaultwizard.new.validName=Nume de seif valid addvaultwizard.new.validName=Nume valid pentru seif
addvaultwizard.new.validCharacters.message=Numele seifului poate conține următoarele caractere: addvaultwizard.new.validCharacters.message=Numele seifului poate conține următoarele caractere:
addvaultwizard.new.validCharacters.chars=Caractere (e.x. a, ж or 수) addvaultwizard.new.validCharacters.chars=Caractere (e.x. a, ж or 수)
addvaultwizard.new.validCharacters.numbers=Numere addvaultwizard.new.validCharacters.numbers=Numere
@@ -90,6 +91,7 @@ addvault.new.readme.accessLocation.2=Aceasta este locația de acces a seifului d
addvault.new.readme.accessLocation.3=Orice fișier adăugat la acest volum va fi criptat de către Cryptomator. Puteți lucra la el ca pe orice altă unitate/folder. Aceasta este doar o vizualizare decriptată a conținutului său, fișierele sunt criptate tot timpul pe hard disk-ul tău. addvault.new.readme.accessLocation.3=Orice fișier adăugat la acest volum va fi criptat de către Cryptomator. Puteți lucra la el ca pe orice altă unitate/folder. Aceasta este doar o vizualizare decriptată a conținutului său, fișierele sunt criptate tot timpul pe hard disk-ul tău.
addvault.new.readme.accessLocation.4=Puteți să ștergeți acest fișier. addvault.new.readme.accessLocation.4=Puteți să ștergeți acest fișier.
## Existing ## Existing
addvaultwizard.existing.title=Adaugă seif existent
addvaultwizard.existing.instruction=Alegeți fișierul "vault.cryptomator" al seifului dvs. existent. Dacă există doar un fișier numit "masterkey.cryptomator", alegeți-l pe acesta. addvaultwizard.existing.instruction=Alegeți fișierul "vault.cryptomator" al seifului dvs. existent. Dacă există doar un fișier numit "masterkey.cryptomator", alegeți-l pe acesta.
addvaultwizard.existing.chooseBtn=Alege… addvaultwizard.existing.chooseBtn=Alege…
addvaultwizard.existing.filePickerTitle=Selectați fișierul seif addvaultwizard.existing.filePickerTitle=Selectați fișierul seif
@@ -134,6 +136,11 @@ unlock.success.revealBtn=Dezvăluie partiția
unlock.error.customPath.message=Nu se poate monta seiful în locația alesă unlock.error.customPath.message=Nu se poate monta seiful în locația alesă
unlock.error.customPath.description.notSupported=Dacă doriți să continuați să utilizați locația aleasă, vă rugăm să mergeți la preferințe și să selectați o unitate de stocare suportată. În caz contrar, mergeți la opțiunile seifului și alegeți o locație de montare suportată. unlock.error.customPath.description.notSupported=Dacă doriți să continuați să utilizați locația aleasă, vă rugăm să mergeți la preferințe și să selectați o unitate de stocare suportată. În caz contrar, mergeți la opțiunile seifului și alegeți o locație de montare suportată.
unlock.error.customPath.description.notExists=Locația de montare aleasă nu există. Creați-o în sistemul de fișiere local sau schimbați-o din opțiunile seifului. unlock.error.customPath.description.notExists=Locația de montare aleasă nu există. Creați-o în sistemul de fișiere local sau schimbați-o din opțiunile seifului.
unlock.error.customPath.description.inUse=Litera de unitate sau locația de montare personalizată ''%s'' este deja folosită.
unlock.error.customPath.description.hideawayNotDir=Fișierul ascuns, temporar "%3$s" folosit pentru deblocare nu a putut fi șters. Vă rugăm să verificați fișierul și să îl ștergeți manual.
unlock.error.customPath.description.couldNotBeCleaned=Seiful dvs. nu a putut fi montat în locația ''%s''. Vă rugăm încercați din nou sau alegeți o altă locație.
unlock.error.customPath.description.notEmptyDir=Locația personalizată ''%s'' nu este un dosar gol. Vă rugăm alegeți un dosar gol și încercați din nou.
unlock.error.customPath.description.generic=Ați selectat o locație de montare personalizată pentru acest seif, dar utilizarea acesteia a eșuat cu mesajul: %2$s
## Hub ## Hub
hub.noKeychain.message=Cheia dispozitivului nu a putut fi accesată hub.noKeychain.message=Cheia dispozitivului nu a putut fi accesată
hub.noKeychain.description=Pentru a debloca seifele de tip Hub, este necesară o cheie a dispozitivului, care este securizată prin intermediul unui keychain. Pentru a continua, activați „%s” și selectați un keychain în preferințe. hub.noKeychain.description=Pentru a debloca seifele de tip Hub, este necesară o cheie a dispozitivului, care este securizată prin intermediul unui keychain. Pentru a continua, activați „%s” și selectați un keychain în preferințe.
@@ -146,8 +153,6 @@ hub.auth.loginLink=Nu ați fost redirecționat? Apăsați aici pentru a deschide
hub.receive.message=Se procesează răspunsul… hub.receive.message=Se procesează răspunsul…
hub.receive.description=In acest moment Criptomatorul primește și procesează răspunsul de la Hub. Vă rugăm să așteptați. hub.receive.description=In acest moment Criptomatorul primește și procesează răspunsul de la Hub. Vă rugăm să așteptați.
### Register Device ### Register Device
hub.register.message=Numele dispozitivului este necesar
hub.register.description=Se pare că este prima data când accesați Hub-ul de pe acest dispozitiv. Trebuie sa denumiți acest dispozitiv pentru autorizarea accesului.
hub.register.nameLabel=Numele dispozitivului hub.register.nameLabel=Numele dispozitivului
hub.register.occupiedMsg=Acest nume este deja utilizat hub.register.occupiedMsg=Acest nume este deja utilizat
hub.register.registerBtn=Confirmați hub.register.registerBtn=Confirmați
@@ -359,6 +364,8 @@ main.vaultlist.contextMenu.unlockNow=Deblochează acum
main.vaultlist.contextMenu.vaultoptions=Arată opțiunile seifului main.vaultlist.contextMenu.vaultoptions=Arată opțiunile seifului
main.vaultlist.contextMenu.reveal=Dezvăluie unitatea main.vaultlist.contextMenu.reveal=Dezvăluie unitatea
main.vaultlist.addVaultBtn=Adaugă main.vaultlist.addVaultBtn=Adaugă
main.vaultlist.addVaultBtn.menuItemNew=Seif nou...
main.vaultlist.addVaultBtn.menuItemExisting=Seif existent...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Vă mulțumim că ați ales Cryptomator pentru a vă proteja fișierele. Dacă aveți nevoie de asistență, verificați ghidurile noastre de pornire: main.vaultDetail.welcomeOnboarding=Vă mulțumim că ați ales Cryptomator pentru a vă proteja fișierele. Dacă aveți nevoie de asistență, verificați ghidurile noastre de pornire:
@@ -153,9 +153,11 @@ hub.auth.loginLink=Не перенаправлено? Нажмите здесь,
hub.receive.message=Обработка ответа… hub.receive.message=Обработка ответа…
hub.receive.description=Cryptomator принимает и обрабатывает ответ от хаба. Подождите. hub.receive.description=Cryptomator принимает и обрабатывает ответ от хаба. Подождите.
### Register Device ### Register Device
hub.register.message=Требуется имя устройства hub.register.message=Новое устройство
hub.register.description=Похоже, это первый доступ к хабу с данного устройства. Чтобы идентифицирваоть его для предоставления доступа, нужно дать устройству имя. hub.register.description=Это первый доступ к Хабу с этого устройства. Пожалуйста, авторизуйте его, используя ваш установочный код.
hub.register.nameLabel=Имя устройства hub.register.nameLabel=Имя устройства
hub.register.setupCodeLabel=Код установки
hub.register.invalidSetupCode=Неверный код установки
hub.register.occupiedMsg=Имя уже используется hub.register.occupiedMsg=Имя уже используется
hub.register.registerBtn=Подтвердить hub.register.registerBtn=Подтвердить
### Registration Success ### Registration Success
+10 -2
View File
@@ -41,6 +41,7 @@ traymenu.vault.reveal=Odkryť
# Add Vault Wizard # Add Vault Wizard
addvaultwizard.title=Pridať trezor addvaultwizard.title=Pridať trezor
## New ## New
addvaultwizard.new.title=Pridať nový trezor
### Name ### Name
addvaultwizard.new.nameInstruction=Zvoľte názov pre trezor addvaultwizard.new.nameInstruction=Zvoľte názov pre trezor
addvaultwizard.new.namePrompt=Názov trezoru addvaultwizard.new.namePrompt=Názov trezoru
@@ -90,6 +91,7 @@ addvault.new.readme.accessLocation.2=Toto je prístupové miesto vášho trezoru
addvault.new.readme.accessLocation.3=Všetky súbory pridané do tohto zväzku budú šifrované programom Cryptomator. Môžete na tom pracovať ako na akomkoľvek inom disku / priečinku. Toto je iba dešifrované zobrazenie jeho obsahu, vaše súbory zostávajú stále šifrované na pevnom disku. addvault.new.readme.accessLocation.3=Všetky súbory pridané do tohto zväzku budú šifrované programom Cryptomator. Môžete na tom pracovať ako na akomkoľvek inom disku / priečinku. Toto je iba dešifrované zobrazenie jeho obsahu, vaše súbory zostávajú stále šifrované na pevnom disku.
addvault.new.readme.accessLocation.4=Tento súbor môžete kedykoľvek odstrániť. addvault.new.readme.accessLocation.4=Tento súbor môžete kedykoľvek odstrániť.
## Existing ## Existing
addvaultwizard.existing.title=Pridať existujúci trezor
addvaultwizard.existing.instruction=Zvoľte "vault.cryptomator" súbor Vášho existujúceho trezora. Ak existuje iba súbor s menom "masterkey.cryptomator", vyberte ho namiesto. addvaultwizard.existing.instruction=Zvoľte "vault.cryptomator" súbor Vášho existujúceho trezora. Ak existuje iba súbor s menom "masterkey.cryptomator", vyberte ho namiesto.
addvaultwizard.existing.chooseBtn=Vybrať… addvaultwizard.existing.chooseBtn=Vybrať…
addvaultwizard.existing.filePickerTitle=Zvoľte súbor trezora addvaultwizard.existing.filePickerTitle=Zvoľte súbor trezora
@@ -133,7 +135,12 @@ unlock.success.revealBtn=Odkryť disk
## Failure ## Failure
unlock.error.customPath.message=Nie je možné namapovať trezor na uživateĺskej ceste unlock.error.customPath.message=Nie je možné namapovať trezor na uživateĺskej ceste
unlock.error.customPath.description.notSupported=Ak chcete naďalej používať vlastnú cestu, prejdite do nastavení a vyberte typ zväzku, ktorý ju podporuje. V opačnom prípade prejdite na možnosti trezoru a vyberte podporovaný bod pripojenia. unlock.error.customPath.description.notSupported=Ak chcete naďalej používať vlastnú cestu, prejdite do nastavení a vyberte typ zväzku, ktorý ju podporuje. V opačnom prípade prejdite na možnosti trezoru a vyberte podporovaný bod pripojenia.
unlock.error.customPath.description.notExists=Vlastná cesta pripojenia neexistuje. Buď ju vytvorte v miestnom súborovom systéme, alebo ju zmeňte v možnostiach trezora.
unlock.error.customPath.description.inUse=Písmeno disku alebo vlastná cesta pripojenia "%s" sa už používa.
unlock.error.customPath.description.hideawayNotDir=Dočasne, skrytý súbor "%3$s" použitý pre odomknutie nemôže byť odstránený. Prosím skontrolujte súbor a následne zmažte manuálne. unlock.error.customPath.description.hideawayNotDir=Dočasne, skrytý súbor "%3$s" použitý pre odomknutie nemôže byť odstránený. Prosím skontrolujte súbor a následne zmažte manuálne.
unlock.error.customPath.description.couldNotBeCleaned=Váš trezor sa nepodarilo pripojiť na cestu "%s". Skúste to prosím znova alebo vyberte inú cestu.
unlock.error.customPath.description.notEmptyDir=Vlastná cesta pripojenia "%s" nie je prázdny priečinok. Vyberte prázdny priečinok a skúste to znova.
unlock.error.customPath.description.generic=Pre tento trezor ste vybrali vlastnú cestu pripojenia, ale jej použitie zlyhalo so správou: %2$s
## Hub ## Hub
hub.noKeychain.message=Nemôžem pristúpiť ku kľúču zariadenia hub.noKeychain.message=Nemôžem pristúpiť ku kľúču zariadenia
hub.noKeychain.description=V poradí odomknutia Hub trezorov, je požadovaný kľúč zariadenia ktorý je zabezpečený použitím keychain. K vykonaniu povoľte "%s" a zvoľte keychain v nastaveniach. hub.noKeychain.description=V poradí odomknutia Hub trezorov, je požadovaný kľúč zariadenia ktorý je zabezpečený použitím keychain. K vykonaniu povoľte "%s" a zvoľte keychain v nastaveniach.
@@ -146,8 +153,6 @@ hub.auth.loginLink=Nepresmerované? Kliknúť tu pre otvorenie.
hub.receive.message=Spracovávanie odpovede… hub.receive.message=Spracovávanie odpovede…
hub.receive.description=Cryptomator prijíma a spracúva odpovede z Hub-u. Prosím počkajte. hub.receive.description=Cryptomator prijíma a spracúva odpovede z Hub-u. Prosím počkajte.
### Register Device ### Register Device
hub.register.message=Názov zariadenia je požadovaný
hub.register.description=Zdá sa, že ide o prvý prístup k Hub-u z tohto zariadenia. Z dôvodu identifikácie prístupovej autorizácie, je potrebné pomenovať toto zariadenie.
hub.register.nameLabel=Názov zariadenia hub.register.nameLabel=Názov zariadenia
hub.register.occupiedMsg=Názov už existuje hub.register.occupiedMsg=Názov už existuje
hub.register.registerBtn=Potvrdiť hub.register.registerBtn=Potvrdiť
@@ -359,6 +364,8 @@ main.vaultlist.contextMenu.unlockNow=Odomknúť teraz
main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti trezora main.vaultlist.contextMenu.vaultoptions=Ukáž možnosti trezora
main.vaultlist.contextMenu.reveal=Odkry disk main.vaultlist.contextMenu.reveal=Odkry disk
main.vaultlist.addVaultBtn=Pridať main.vaultlist.addVaultBtn=Pridať
main.vaultlist.addVaultBtn.menuItemNew=Nový trezor...
main.vaultlist.addVaultBtn.menuItemExisting=Existujúci trezor...
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Ďakujeme Vám že ste si zvolili Cryptomator pre ochranu Vaších súborov. Ak potrebujete akúkoľvek pomoc, pozrite si našu príručku ako začať: main.vaultDetail.welcomeOnboarding=Ďakujeme Vám že ste si zvolili Cryptomator pre ochranu Vaších súborov. Ak potrebujete akúkoľvek pomoc, pozrite si našu príručku ako začať:
@@ -442,6 +449,7 @@ vaultOptions.masterkey.showRecoveryKeyBtn=Ukázať klúč obnovy
vaultOptions.masterkey.recoverPasswordBtn=Obnoviť heslo vaultOptions.masterkey.recoverPasswordBtn=Obnoviť heslo
## Hub ## Hub
vaultOptions.hub=Obnova vaultOptions.hub=Obnova
vaultOptions.hub.convertInfo=Kľúč na obnovenie môžete v prípade núdze použiť na konverziu tohto trezora Hub na trezor založený na hesle.
vaultOptions.hub.convertBtn=Konvertovať do Password-Based trezora vaultOptions.hub.convertBtn=Konvertovať do Password-Based trezora
# Recovery Key # Recovery Key
@@ -153,8 +153,6 @@ hub.auth.loginLink=Inte omdirigerad? Klicka här för att öppna den.
hub.receive.message=Bearbetar svar… hub.receive.message=Bearbetar svar…
hub.receive.description=Cryptomator tar emot och bearbetar svaret från Hub. Vänligen vänta. hub.receive.description=Cryptomator tar emot och bearbetar svaret från Hub. Vänligen vänta.
### Register Device ### Register Device
hub.register.message=Enhetens namn krävs
hub.register.description=Detta verkar vara den första Hub-åtkomsten från den här enheten. För att identifiera den för åtkomstbehörighet, måste du namnge den här enheten.
hub.register.nameLabel=Enhetsnamn hub.register.nameLabel=Enhetsnamn
hub.register.occupiedMsg=Namnet används redan hub.register.occupiedMsg=Namnet används redan
hub.register.registerBtn=Bekräfta hub.register.registerBtn=Bekräfta
@@ -136,8 +136,6 @@ hub.auth.loginLink=Je, haijaelekezwa kwingine? Bofya hapa ili kuifungua.
hub.receive.message=Inachakata jibu… hub.receive.message=Inachakata jibu…
hub.receive.description=Cryptomator inapokea na kuchakata jibu kutoka kwenye Kitovu. Tafadhali subiri. hub.receive.description=Cryptomator inapokea na kuchakata jibu kutoka kwenye Kitovu. Tafadhali subiri.
### Register Device ### Register Device
hub.register.message=Jina la kifaa linahitajika
hub.register.description=Hii inaonekana kuwa ufikiaji wa kwanza wa Hub kutoka kwa kifaa hiki. Ili kukitambua kwa uidhinishaji wa ufikiaji, unahitaji kukipa kifaa hiki jina.
hub.register.nameLabel=Jina la Kifaa hub.register.nameLabel=Jina la Kifaa
hub.register.occupiedMsg=Jina tayari linatumika hub.register.occupiedMsg=Jina tayari linatumika
hub.register.registerBtn=Thibitisha hub.register.registerBtn=Thibitisha
@@ -153,9 +153,11 @@ hub.auth.loginLink=Yönlendirilmedi mi? Açmak için buraya tıklayın.
hub.receive.message=Yanıt işleniyor… hub.receive.message=Yanıt işleniyor…
hub.receive.description=Cryptomator, Hub'dan yanıtı alıyor ve işliyor. Lütfen bekleyin. hub.receive.description=Cryptomator, Hub'dan yanıtı alıyor ve işliyor. Lütfen bekleyin.
### Register Device ### Register Device
hub.register.message=Cihaz adı gerekli hub.register.message=Yeni Cihaz
hub.register.description=Bu cihazdan ilk Hub erişimi gibi görünüyor. Erişim yetkilendirmesini tanımlamak için bu cihazı isimlendirmeniz gerekir. hub.register.description=Bu işlem bu cihazdan yapılan ilk Hub erişimidir. Lütfen kurulum kodunuzu kullanarak yetkilendirin.
hub.register.nameLabel=Cihaz adı hub.register.nameLabel=Cihaz adı
hub.register.setupCodeLabel=Kurulum Kodu
hub.register.invalidSetupCode=Geçersiz Kurulum Kodu
hub.register.occupiedMsg=Ad zaten kullanımda hub.register.occupiedMsg=Ad zaten kullanımda
hub.register.registerBtn=Onayla hub.register.registerBtn=Onayla
### Registration Success ### Registration Success
@@ -141,8 +141,6 @@ hub.auth.loginLink=Не переадресувало? Натисніть тут,
hub.receive.message=Обробка відповіді… hub.receive.message=Обробка відповіді…
hub.receive.description=Cryptomator отримує та обробляє відповідь від Hub. Будь ласка, зачекайте. hub.receive.description=Cryptomator отримує та обробляє відповідь від Hub. Будь ласка, зачекайте.
### Register Device ### Register Device
hub.register.message=Потрібне ім'я пристрою
hub.register.description=Схоже, це перша спроба доступу до Hub з цього пристрою. Для того, щоб ідентифікувати його для авторизації доступу, вам потрібно назвати цей пристрій.
hub.register.nameLabel=Назва пристрою hub.register.nameLabel=Назва пристрою
hub.register.occupiedMsg=Ця назва вже використовується hub.register.occupiedMsg=Ця назва вже використовується
hub.register.registerBtn=Підтвердити hub.register.registerBtn=Підтвердити
@@ -138,8 +138,6 @@ hub.auth.loginLink=Chưa được chuyển hướng? Nhấn vào đây để m
hub.receive.message=Đang xử lý phản hồi… hub.receive.message=Đang xử lý phản hồi…
hub.receive.description=Cryptomator đang nhận và xử lý phản hồi từ Hub. Vui lòng chờ. hub.receive.description=Cryptomator đang nhận và xử lý phản hồi từ Hub. Vui lòng chờ.
### Register Device ### Register Device
hub.register.message=Tên thiết bị bắt buộc
hub.register.description=Đây dường như là lần truy cập Hub đầu tiên từ thiết bị này. Để xác định nó để cấp quyền truy cập, bạn cần đặt tên cho thiết bị này.
hub.register.nameLabel=Tên thiết bị hub.register.nameLabel=Tên thiết bị
hub.register.occupiedMsg=Tên đã sử dụng hub.register.occupiedMsg=Tên đã sử dụng
hub.register.registerBtn=Xác nhận hub.register.registerBtn=Xác nhận
@@ -153,9 +153,11 @@ hub.auth.loginLink=未重定向?点此打开
hub.receive.message=正在处理响应… hub.receive.message=正在处理响应…
hub.receive.description=Cryptomator 正在接收和处理来自 Hub 的响应,请稍等 hub.receive.description=Cryptomator 正在接收和处理来自 Hub 的响应,请稍等
### Register Device ### Register Device
hub.register.message=设备名称(必填) hub.register.message=设备
hub.register.description=似乎是设备的首次 Hub 访问。为了识别它以进行访问授权,您需要命名此设备。 hub.register.description=是从此设备第一次访问 Hub,请通过设置代码进行授权
hub.register.nameLabel=设备名称 hub.register.nameLabel=设备名称
hub.register.setupCodeLabel=设置代码
hub.register.invalidSetupCode=无效的设置代码
hub.register.occupiedMsg=名称已被占用 hub.register.occupiedMsg=名称已被占用
hub.register.registerBtn=确定 hub.register.registerBtn=确定
### Registration Success ### Registration Success
@@ -147,8 +147,6 @@ hub.auth.loginLink=未被轉送?點擊這裡打開
hub.receive.message=正在處理程式回應… hub.receive.message=正在處理程式回應…
hub.receive.description=Cryptomator正在接收並處理來自Hub的回應。 請等待。 hub.receive.description=Cryptomator正在接收並處理來自Hub的回應。 請等待。
### Register Device ### Register Device
hub.register.message=需要設備名稱
hub.register.description=似乎為第一次透過 Hub 訪問此設備。您需要命名此設備,以便識別設備並授予讀取權限。
hub.register.nameLabel=設備名稱 hub.register.nameLabel=設備名稱
hub.register.occupiedMsg=名称已被占用 hub.register.occupiedMsg=名称已被占用
hub.register.registerBtn=確認 hub.register.registerBtn=確認
@@ -22,6 +22,9 @@ error.hyperlink.report=回報錯誤
error.technicalDetails=詳情: error.technicalDetails=詳情:
error.existingSolutionDescription=Cryptomator 沒有預料到會發生這種情況。但我們找到了一個現有的解決方案來解決這個錯誤。請查看以下連結。 error.existingSolutionDescription=Cryptomator 沒有預料到會發生這種情況。但我們找到了一個現有的解決方案來解決這個錯誤。請查看以下連結。
error.hyperlink.solution=查詢解決方案 error.hyperlink.solution=查詢解決方案
error.lookupPermissionMessage=Cryptomator 可以在線查找此問題的解決方案。 這將從您的 IP 地址向我們的問題數據庫發送請求。
error.dismiss=忽略
error.lookUpSolution=查詢解決方案
# Defaults # Defaults
defaults.vault.vaultName=加密檔案庫 defaults.vault.vaultName=加密檔案庫
@@ -134,6 +137,7 @@ unlock.error.customPath.message=無法將檔案庫掛載至自訂路徑
unlock.error.customPath.description.notSupported=如果要繼續使用自訂的掛載路徑,必須變更成支援的磁區空間類型,不然就必須使用不同的掛載路徑 unlock.error.customPath.description.notSupported=如果要繼續使用自訂的掛載路徑,必須變更成支援的磁區空間類型,不然就必須使用不同的掛載路徑
unlock.error.customPath.description.notExists=自訂的掛載路徑並不存在‧ 請在本機創立該路徑,或者在加密庫選項中更改 unlock.error.customPath.description.notExists=自訂的掛載路徑並不存在‧ 請在本機創立該路徑,或者在加密庫選項中更改
unlock.error.customPath.description.inUse=磁碟代號或自訂掛載路徑「%s」已被使用。 unlock.error.customPath.description.inUse=磁碟代號或自訂掛載路徑「%s」已被使用。
unlock.error.customPath.description.hideawayNotDir=無法移除用於解鎖的臨時隱藏檔案「%3$s」。請檢查該檔案,然後手動刪除。
unlock.error.customPath.description.couldNotBeCleaned=無法將您的保險庫掛載至路徑「%s」。請再試一次或選擇不同的路徑。 unlock.error.customPath.description.couldNotBeCleaned=無法將您的保險庫掛載至路徑「%s」。請再試一次或選擇不同的路徑。
unlock.error.customPath.description.notEmptyDir=自訂掛載路徑「%s」不是一個空資料夾。請選擇一個空資料夾並重試。 unlock.error.customPath.description.notEmptyDir=自訂掛載路徑「%s」不是一個空資料夾。請選擇一個空資料夾並重試。
unlock.error.customPath.description.generic=您為此保險庫選擇了自訂掛載路徑,但使用時出現了錯誤訊息:%2$s unlock.error.customPath.description.generic=您為此保險庫選擇了自訂掛載路徑,但使用時出現了錯誤訊息:%2$s
@@ -149,8 +153,6 @@ hub.auth.loginLink=未轉送?點擊這裡打開
hub.receive.message=處理著回應… hub.receive.message=處理著回應…
hub.receive.description=Cryptomator 正在接收並處理來自 Hub 的回應, 請等待。 hub.receive.description=Cryptomator 正在接收並處理來自 Hub 的回應, 請等待。
### Register Device ### Register Device
hub.register.message=需要設備名稱
hub.register.description=似乎這是第一次 Hub 訪問此設備。為了辨識它以授權訪問,您需要命名此設備。
hub.register.nameLabel=設備名稱 hub.register.nameLabel=設備名稱
hub.register.occupiedMsg=名稱已被使用 hub.register.occupiedMsg=名稱已被使用
hub.register.registerBtn=確認 hub.register.registerBtn=確認
@@ -4,6 +4,7 @@ import com.nimbusds.jose.JWEObject;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.cryptolib.common.P384KeyPair; import org.cryptomator.cryptolib.common.P384KeyPair;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
@@ -15,18 +16,106 @@ import java.text.ParseException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
@SuppressWarnings("resource")
public class JWEHelperTest { public class JWEHelperTest {
private static final String JWE = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52Nm02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD-kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA"; // key pairs from frontend tests (crypto.spec.ts):
private static final String USER_PRIV_KEY = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDCi4K1Ts3DgTz/ufkLX7EGMHjGpJv+WJmFgyzLwwaDFSfLpDw0Kgf3FKK+LAsV8r+hZANiAARLOtFebIjxVYUmDV09Q1sVxz2Nm+NkR8fu6UojVSRcCW13tEZatx8XGrIY9zC7oBCEdRqDc68PMSvS5RA0Pg9cdBNc/kgMZ1iEmEv5YsqOcaNADDSs0bLlXb35pX7Kx5Y=";
private static final String USER_PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAESzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1UkXAltd7RGWrcfFxqyGPcwu6AQhHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL+WLKjnGjQAw0rNGy5V29+aV+yseW";
private static final String DEVICE_PRIV_KEY = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB2bmFCWy2p+EbAn8NWS5Om+GA7c5LHhRZb8g2pSMSf0fsd7k7dZDVrnyHFiLdd/YGhZANiAAR6bsjTEdXKWIuu1Bvj6Y8wySlIROy7YpmVZTY128ItovCD8pcR4PnFljvAIb2MshCdr1alX4g6cgDOqcTeREiObcSfucOU9Ry1pJ/GnX6KA0eSljrk6rxjSDos8aiZ6Mg=";
private static final String DEVICE_PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEem7I0xHVyliLrtQb4+mPMMkpSETsu2KZlWU2NdvCLaLwg/KXEeD5xZY7wCG9jLIQna9WpV+IOnIAzqnE3kRIjm3En7nDlPUctaSfxp1+igNHkpY65Oq8Y0g6LPGomejI";
// used for JWE generation in frontend: (jwe.spec.ts):
private static final String PRIV_KEY = "ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ"; private static final String PRIV_KEY = "ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ";
private static final String PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu"; private static final String PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu";
@Test @Test
public void testDecrypt() throws ParseException, InvalidKeySpecException { @DisplayName("decryptUserKey with device key")
var jwe = JWEObject.parse(JWE); public void testDecryptUserKeyECDHES() throws ParseException, InvalidKeySpecException {
var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))); var jwe = JWEObject.parse("""
eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\
0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\
NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\
jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\
OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\
L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\
TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\
PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\
QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\
q2koh2NbQ""");
var deviceKeyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(DEVICE_PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEVICE_PRIV_KEY)));
var masterkey = JWEHelper.decrypt(jwe, keyPair.getPrivate()); var userKey = JWEHelper.decryptUserKey(jwe, deviceKeyPair.getPrivate());
Assertions.assertArrayEquals(Base64.getDecoder().decode(USER_PRIV_KEY), userKey.getEncoded());
}
@Test
@DisplayName("decryptUserKey with incorrect device key")
public void testDecryptUserKeyECDHESWrongKey() throws ParseException, InvalidKeySpecException {
var jwe = JWEObject.parse("""
eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\
0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\
NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\
jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\
OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\
L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\
TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\
PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\
QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\
q2koh2NbQ""");
var userKeyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(USER_PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(USER_PRIV_KEY)));
var incorrectDevicePrivateKey = userKeyPair.getPrivate();
Assertions.assertThrows(JWEHelper.InvalidJweKeyException.class, () -> JWEHelper.decryptUserKey(jwe, incorrectDevicePrivateKey));
}
@Test
@DisplayName("decryptUserKey with setup code")
public void testDecryptUserKeyPBES2() throws ParseException {
var jwe = JWEObject.parse("""
eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\
xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\
aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\
P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\
mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\
kDjUaxwUKqpvT7qaAQ
""");
var userKey = JWEHelper.decryptUserKey(jwe, "123456");
Assertions.assertArrayEquals(Base64.getDecoder().decode(PRIV_KEY), userKey.getEncoded());
}
@Test
@DisplayName("decryptUserKey with incorrect setup code")
public void testDecryptUserKeyPBES2WrongKey() throws ParseException {
var jwe = JWEObject.parse("""
eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\
xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\
aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\
P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\
mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\
kDjUaxwUKqpvT7qaAQ
""");
Assertions.assertThrows(JWEHelper.InvalidJweKeyException.class, () -> JWEHelper.decryptUserKey(jwe, "654321"));
}
@Test
@DisplayName("decryptVaultKey")
public void testDecryptVaultKey() throws ParseException, InvalidKeySpecException {
var jwe = JWEObject.parse("""
eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlA\
tMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0\
FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52N\
m02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUi\
OiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD\
-kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2\
U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA""");
var privateKey = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))).getPrivate();
var masterkey = JWEHelper.decryptVaultKey(jwe, privateKey);
var expectedEncKey = new byte[32]; var expectedEncKey = new byte[32];
var expectedMacKey = new byte[32]; var expectedMacKey = new byte[32];
@@ -44,13 +133,11 @@ public class JWEHelperTest {
"eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkJyYm9UQkl5Y0NDUEdJQlBUekU2RjBnbTRzRjRCamZPN1I0a2x0aWlCaThKZkxxcVdXNVdUSVBLN01yMXV5QVUiLCJ5IjoiNUpGVUI0WVJiYjM2RUZpN2Y0TUxMcFFyZXd2UV9Tc3dKNHRVbFd1a2c1ZU04X1ZyM2pkeml2QXI2WThRczVYbSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..QEq4Z2m6iwBx2ioS.IBo8TbKJTS4pug.61Z-agIIXgP8bX10O_yEMA", // json payload field "key" not a string "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkJyYm9UQkl5Y0NDUEdJQlBUekU2RjBnbTRzRjRCamZPN1I0a2x0aWlCaThKZkxxcVdXNVdUSVBLN01yMXV5QVUiLCJ5IjoiNUpGVUI0WVJiYjM2RUZpN2Y0TUxMcFFyZXd2UV9Tc3dKNHRVbFd1a2c1ZU04X1ZyM2pkeml2QXI2WThRczVYbSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..QEq4Z2m6iwBx2ioS.IBo8TbKJTS4pug.61Z-agIIXgP8bX10O_yEMA", // json payload field "key" not a string
"eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ" // json payload field "key" invalid base64 data "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ" // json payload field "key" invalid base64 data
}) })
public void testDecryptInvalid(String malformed) throws ParseException, InvalidKeySpecException { public void testDecryptInvalidVaultKey(String malformed) throws ParseException, InvalidKeySpecException {
var jwe = JWEObject.parse(malformed); var jwe = JWEObject.parse(malformed);
var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))); var privateKey = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY))).getPrivate();
Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> { Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> JWEHelper.decryptVaultKey(jwe, privateKey));
JWEHelper.decrypt(jwe, keyPair.getPrivate());
});
} }
} }