mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
82
Commits
1.10.1
...
1.11.0-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6225d8d1f0 | ||
|
|
ed975b459e | ||
|
|
800b2440b3 | ||
|
|
f50a49e7a3 | ||
|
|
92ece4dfb6 | ||
|
|
174225c60e | ||
|
|
3071410a22 | ||
|
|
5c5777ffc5 | ||
|
|
8dd8b93656 | ||
|
|
3b2d455b4a | ||
|
|
b0dfa22903 | ||
|
|
88b1c28d88 | ||
|
|
5b85d7859a | ||
|
|
468eed58d5 | ||
|
|
34c17be474 | ||
|
|
5dedd6b1c1 | ||
|
|
25e8e81686 | ||
|
|
1f7ab03bbb | ||
|
|
86f3cb7288 | ||
|
|
a1b8bf23b4 | ||
|
|
1f21d5ea4c | ||
|
|
f8ff7201d7 | ||
|
|
3567c036c3 | ||
|
|
0cf1f087ad | ||
|
|
209f60727e | ||
|
|
560a979e0e | ||
|
|
1debe4c7c8 | ||
|
|
2e2aa77727 | ||
|
|
162ebf6545 | ||
|
|
2d503a0edb | ||
|
|
7f7f0a099a | ||
|
|
109f5d1faa | ||
|
|
9b55f6fc56 | ||
|
|
83879f5cfe | ||
|
|
083ac5a7e0 | ||
|
|
a7eba377ba | ||
|
|
4ee1e6d9f1 | ||
|
|
24a63c10d0 | ||
|
|
0d805b2d43 | ||
|
|
7bc47fe6d7 | ||
|
|
ae50846257 | ||
|
|
ce466e7715 | ||
|
|
15885545b5 | ||
|
|
29fedcd390 | ||
|
|
fadd6b761f | ||
|
|
d857335901 | ||
|
|
93b4cbfb2c | ||
|
|
625334c6c8 | ||
|
|
943374856e | ||
|
|
e1d7fd92ad | ||
|
|
c10dc74818 | ||
|
|
92d9f2c18d | ||
|
|
d7488b6984 | ||
|
|
88ad3cd724 | ||
|
|
ba9e0b0c12 | ||
|
|
57bf5e11b5 | ||
|
|
594ca47c85 | ||
|
|
6c5ee14c73 | ||
|
|
8e9d54b44b | ||
|
|
70805665bf | ||
|
|
ac243a706e | ||
|
|
aa382ce80d | ||
|
|
b7da508ccc | ||
|
|
4043e3f71f | ||
|
|
08887f1147 | ||
|
|
cfd433b328 | ||
|
|
f48963a6d0 | ||
|
|
6ce34efb52 | ||
|
|
cd10d38990 | ||
|
|
8cb21c97e3 | ||
|
|
0ad8ce77ef | ||
|
|
e358ffd666 | ||
|
|
496f9a9cd5 | ||
|
|
61025de0d6 | ||
|
|
f08049b960 | ||
|
|
746c8f54eb | ||
|
|
9fc1efa005 | ||
|
|
448eac8ff5 | ||
|
|
5a18d086e0 | ||
|
|
918ace2eb6 | ||
|
|
fe733967dc | ||
|
|
59f5c0cb12 |
@@ -10,8 +10,8 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: 20
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: 20
|
JAVA_VERSION: 21
|
||||||
|
COFFEELIBS_JDK_VERSION: 21
|
||||||
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 +29,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 +43,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_VERSION }} libgtk2.0-0
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: 20
|
JAVA_VERSION: 21
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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: |
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ on:
|
|||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
JAVA_DIST: 'temurin'
|
JAVA_DIST: 'zulu'
|
||||||
JAVA_VERSION: 20
|
JAVA_VERSION: 21
|
||||||
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'
|
||||||
|
|
||||||
@@ -37,7 +37,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 +143,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 +174,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 +223,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 +265,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:
|
||||||
|
|||||||
Generated
+11
-12
@@ -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>
|
||||||
|
|||||||
Generated
+1
-1
@@ -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>
|
||||||
Vendored
+6
-4
@@ -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 ""
|
||||||
Vendored
+1
-1
@@ -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, 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
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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
|
||||||
|
|||||||
Vendored
+6
-6
@@ -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
|
||||||
|
|||||||
Vendored
+4
-2
@@ -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 `
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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 ?>
|
||||||
|
|||||||
@@ -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.1</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.3</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.2.4</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.2.2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>1.3.0</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.4</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.7.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>
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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"), //
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -154,6 +155,7 @@ public class ErrorController implements FxController {
|
|||||||
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()//
|
||||||
.header("User-Agent", userAgent)
|
.header("User-Agent", userAgent)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
.uri(URI.create(ERROR_CODES_URL_FORMAT.formatted(URLEncoder.encode(errorCode.toString(),StandardCharsets.UTF_8))))//
|
.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())//
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ 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> {
|
||||||
@@ -34,6 +36,7 @@ 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)) //
|
||||||
|
.withRequestTimeout(Duration.ofSeconds(10)) //
|
||||||
.authFlow(URI.create(hubConfig.authEndpoint)) //
|
.authFlow(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()))) //
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
+1
-1
@@ -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>
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user