Compare commits

..

1 Commits

Author SHA1 Message Date
Sebastian Stenzel
8dae4c55ea check license's x5c claim 2026-03-06 13:28:44 +01:00
77 changed files with 897 additions and 2281 deletions

View File

@@ -3,7 +3,10 @@ updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "Etc/UTC"
ignore:
- dependency-name: "org.cryptomator:integrations-api"
versions: ["2.0.0-alpha1"]
@@ -15,9 +18,36 @@ updates:
- dependency-name: "org.apache.maven.plugins:maven-surefire-plugin"
versions: [ "3.5.4", "3.5.5" ]
groups:
maven-dependencies:
java-test-dependencies:
patterns:
- "org.junit.jupiter:*"
- "org.mockito:*"
- "org.hamcrest:*"
- "com.google.jimfs:jimfs"
maven-build-plugins:
patterns:
- "org.apache.maven.plugins:*"
- "org.jacoco:jacoco-maven-plugin"
- "org.owasp:dependency-check-maven"
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
- "org.codehaus.mojo:license-maven-plugin"
javafx:
patterns:
- "org.openjfx:*"
java-production-dependencies:
patterns:
- "*"
exclude-patterns:
- "org.openjfx:*"
- "org.apache.maven.plugins:*"
- "org.jacoco:jacoco-maven-plugin"
- "org.owasp:dependency-check-maven"
- "me.fabriciorby:maven-surefire-junit5-tree-reporter"
- "org.codehaus.mojo:license-maven-plugin"
- "org.junit.jupiter:*"
- "org.mockito:*"
- "org.hamcrest:*"
- "com.google.jimfs:jimfs"
- package-ecosystem: "github-actions"
directory: "/" # even for `.github/workflows`

View File

@@ -1,34 +0,0 @@
<!-- HEADER -->
> [!WARN]
> 🚧 DO NOT EDIT 🚧
>
> The [builds are still running](https://github.com/cryptomator/cryptomator/actions/workflows/create-release.yml).
> This banner will be replaced after the builds are finished.
<!-- /HEADER -->
<!--REPLACE with auto-generated release notes (see below)
### What's New 🎉
### Bugfixes 🐛
### Other Changes 📎
END REPLACE-->
For a comprehensive view of changes, read the [CHANGELOG](https://github.com/cryptomator/cryptomator/blob/$VERSION/CHANGELOG.md).
---
💾 SHA-256 checksums of release artifacts:
```
$TARBALL
$EXE
$MSI
$DMG_x64
$DMG_arm64
$APPIMAGE_x86_64
$APPIMAGE_aarch64
```
> [!TIP]
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
<!-- Auto-Generated Release Notes: -->

View File

@@ -1,189 +0,0 @@
# Cryptomator Release Workflow
This document describes the automated release pipeline defined in [`draft-release.yml`](draft-release.yml) and [`post-publish.yml`](post-publish.yml).
## Overview
The release process has two phases:
1. **Draft phase** (`draft-release.yml`) -- triggered by pushing a signed git tag. Compiles, tests, builds platform installers, and creates a **draft** GitHub Release.
2. **Post-publish phase** (`post-publish.yml`) -- triggered when the draft release is manually **published**. Submits Windows installers for AV whitelisting, notifies the team for DEB build and latest-version update, and triggers downstream updates (website, docs, winget).
```mermaid
---
config:
htmlLabels: false
---
flowchart TD
%% ── Trigger ──────────────────────────────────────────────
push_tag([🏷 Signed tag pushed])
%% ── Draft phase ──────────────────────────────────────────
push_tag --> get-version
subgraph draft["draft-release.yml"]
get-version["get-version
*parse semver from tag*"]
get-version --> create-release-draft
create-release-draft["create-release-draft
*compile & test (Linux)
create draft release
sign source tarball*"]
create-release-draft --> build-exe-and-msi
create-release-draft --> build-dmg-arm64
create-release-draft --> build-dmg-x64
create-release-draft --> build-appimages
build-exe-and-msi["build-exe-and-msi
*calls win-exe.yml
MSI + EXE (x64)
code-signed & GPG-signed*"]
build-dmg-arm64["build-dmg-arm64
*calls mac-dmg.yml
DMG (arm64)
notarized & GPG-signed*"]
build-dmg-x64["build-dmg-x64
*calls mac-dmg-x64.yml
DMG (x64)
notarized & GPG-signed*"]
build-appimages["build-appimages
*calls appimage.yml
AppImage (x86_64 + aarch64)
GPG-signed*"]
build-exe-and-msi --> update-sha256sums
build-dmg-arm64 --> update-sha256sums
build-dmg-x64 --> update-sha256sums
build-appimages --> update-sha256sums
update-sha256sums["update-sha256sums
*compute checksums
update release body*"]
end
update-sha256sums --> manual_review
%% ── Manual gate ──────────────────────────────────────────
manual_review{{Manual review
& publish}}
%% ── Post-publish phase ───────────────────────────────────
manual_review --> published([📢 Release published])
published --> post-publish
subgraph post-publish["post-publish.yml"]
direction TB
check-release["check-release
*classify release tag
stable, alpha, beta, rc, unknown*"]
notify["notify
*Slack notifications
deb build & version check*"]
get-asset-urls["get-asset-urls
*extract MSI & EXE
download URLs*"]
check-release --> notify-winget
check-release --> trigger-website
check-release --> trigger-docs
get-asset-urls --> allowlist-msi
allowlist-msi --> allowlist-exe
allowlist-msi["allowlist-msi-x64
*av-whitelist.yml
Kaspersky & Avast*"]
allowlist-exe["allowlist-exe-x64
*av-whitelist.yml
Kaspersky & Avast*"]
notify-winget["notify-winget
*Slack: ready for winget
stable only*"]
trigger-website["trigger-website-update
*dispatch to
cryptomator.github.io
stable only*"]
trigger-docs["trigger-docs-update
*dispatch to
cryptomator/docs
stable only, Windows*"]
end
```
## Phase 1: Draft Release (`draft-release.yml`)
**Trigger:** push of any tag (`*`)
### Jobs
| Job | Runs on | Description |
|-----|---------|-------------|
| **get-version** | ubuntu | Parses the tag into semver components (`semVerNum`, `semVerSuffix`, `revNum`, `versionType`). The release is aborted if not an alpha, beta, rc or 'stable' release. |
| **create-release-draft** | ubuntu | Checks out the repo, verifies the tag is **signed** and lives on a `main` or `release/*` branch. Runs `mvn verify` (with `xvfb-run`). Creates a GitHub Release **draft** using the [release body template](../release-body.md.template). Downloads and GPG-signs the source tarball. |
| **build-exe-and-msi** | windows | Calls [`win-exe.yml`](win-exe.yml). Builds the MSI and EXE bundle installer for x64 Windows. Code-signed via Azure Trusted Signing, GPG-signed, and uploaded to the draft release. Outputs SHA-256 checksums. |
| **build-dmg-arm64** | macos-15 | Calls [`mac-dmg.yml`](mac-dmg.yml). Builds the DMG for Apple Silicon. Code-signed, notarized with Apple, GPG-signed, and uploaded. Outputs SHA-256 checksum. |
| **build-dmg-x64** | macos-15-large | Calls [`mac-dmg-x64.yml`](mac-dmg-x64.yml). Same as above but for Intel Macs. Uses macFUSE instead of FUSE-T. |
| **build-appimages** | ubuntu | Calls [`appimage.yml`](appimage.yml). Builds AppImages for x86_64 and aarch64 (matrix). GPG-signed and uploaded with `.zsync` delta-update files. Outputs SHA-256 checksums. |
| **update-sha256sums** | ubuntu | Runs after all builds complete. Computes the source tarball checksum, collects all artifact checksums, and updates the draft release body via `envsubst`. Replaces the "builds still running" banner with a success notice. |
### Release Artifacts
After the draft phase, the GitHub Release contains:
| Artifact | Platform |
|----------|----------|
| `cryptomator-<ver>.tar.gz.asc` | Source (GPG signature) |
| `Cryptomator-<ver>-x64.msi` + `.asc` | Windows |
| `Cryptomator-<ver>-x64.exe` + `.asc` | Windows |
| `Cryptomator-<ver>-arm64.dmg` + `.asc` | macOS (Apple Silicon) |
| `Cryptomator-<ver>-x64.dmg` + `.asc` | macOS (Intel) |
| `cryptomator-<ver>-x86_64.AppImage` + `.zsync` + `.asc` | Linux (x86_64) |
| `cryptomator-<ver>-aarch64.AppImage` + `.zsync` + `.asc` | Linux (aarch64) |
All artifacts are signed with GPG key [`615D449FE6E6A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
## Manual Review Gate
After the draft phase completes, a maintainer reviews the draft release on GitHub. This is the point to:
- Verify all artifacts are present and checksums look correct.
- Edit the auto-generated release notes (What's New, Bugfixes, Other Changes).
- **Publish** the release when ready, which triggers phase 2.
## Phase 2: Post-Publish (`post-publish.yml`)
**Trigger:** `release: [published]`
### Jobs
| Job | Condition | Description |
|-----|-----------|-------------|
| **notify** | always | Sends Slack notifications to `#cryptomator-desktop`: ready to build `.deb` package, and reminder to update `latest-version.json` on S3. |
| **get-asset-urls** | always | Extracts MSI and EXE download URLs from the release assets. |
| **check-release** | always | Classifies the published release tag as `stable`, `alpha`, `beta`, `rc`, or `unknown`. Stable-only follow-up jobs depend on this output. Unlike `get-version.yml` workflow, this job does not perform semver validation. |
| **allowlist-msi-x64** | Windows release | Calls [`av-whitelist.yml`](av-whitelist.yml). Uploads the MSI to Kaspersky and Avast for whitelisting. |
| **allowlist-exe-x64** | Windows release | Same as above for the EXE. Runs sequentially after MSI. |
| **notify-winget** | stable + Windows | Sends a Slack notification that the release is ready for [winget submission](winget.yml). |
| **trigger-website-update** | stable | Dispatches `desktop-release` event to `cryptomator/cryptomator.github.io`. |
| **trigger-docs-update** | stable + Windows | Dispatches `desktop-release` event to `cryptomator/docs`. |
### Manual Follow-ups
These steps are triggered by team members after Slack notifications:
- **Debian package** -- Run the [`debian.yml`](debian.yml) workflow to build `.deb` and optionally upload to the PPA.
- **winget** -- Run the [`winget.yml`](winget.yml) workflow to submit to the Windows Package Manager.
- **latest-version.json** -- Update the version-check file on S3 (`static.cryptomator.org/desktop/latest-version.json`).
## Signing & Security
- **Git tag** must be SSH-signed and reside on `main` or `release/*`.
- **Windows** installers are code-signed using Azure Trusted Signing.
- **macOS** DMGs are code-signed with an Apple Developer certificate and notarized via `notarytool`.
- **All artifacts** receive a detached GPG signature (`.asc`) using key `615D449FE6E6A235`.
- **AV whitelisting** is submitted to Kaspersky and Avast after publish (Windows installers only).
- The draft release is created using `CRYPTOBOT_RELEASE_TOKEN`, not `GITHUB_TOKEN`, to ensure proper permissions and trigger downstream workflows.

View File

@@ -1,44 +1,17 @@
name: Build AppImage
on:
schedule:
- cron: '0 23 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
upload-to-draft:
type: boolean
default: true
outputs:
sha256-appimage-x64:
description: "SHA256 sum of the x64 appimage"
value: ${{ jobs.collect-sha256sums.outputs.x64-sha256sum}}
sha256-appimage-aarch64:
description: "SHA256 sum of the aarch64 appimage"
value: ${{ jobs.collect-sha256sums.outputs.aarch64-sha256sum}}
release:
types: [published]
workflow_dispatch:
inputs:
semVerNum:
description: 'The Major.Minor.Patch part of the version'
version:
description: 'Version'
required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
create-pr:
description: 'Create a PR for aur-bin repo'
type: boolean
default: false
push:
branches-ignore:
- 'dependabot/**'
@@ -51,15 +24,21 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }} #okay if not defined
build:
name: Build AppImage
runs-on: ${{ matrix.os }}
needs: [get-version]
env:
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
SEMVER_NUM: ${{ needs.get-version.outputs.semVerNum }}
REV_NUM: ${{ needs.get-version.outputs.revNum }}
strategy:
fail-fast: false
matrix:
@@ -68,12 +47,10 @@ jobs:
arch: x86_64
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip'
openjfx-sha: 'e0a9c29d8cf3af9b8b48848b43f87b5785bc107c53a951b19668ce05842bba1b'
appimagetool-sha: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0'
- os: ubuntu-24.04-arm
arch: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip'
openjfx-sha: 'c3408f818693cce09e59829a8e862a82c7695fdfcd585c41cfd527f5fc3fe646'
appimagetool-sha: 'f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Java
@@ -104,7 +81,7 @@ jobs:
exit 1
fi
- name: Set version
run : mvn versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
run : mvn versions:set -DnewVersion="$SEMVER_STR"
- name: Run maven
run: mvn -B clean package -Plinux -DskipTests
- name: Patch target dir
@@ -146,13 +123,13 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${VERSION_NUM}.${REVISION_NUM}"
--copyright "(C) 2016 - 2025 Skymatic GmbH"
--app-version "${SEMVER_NUM}.${REV_NUM}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"/etc/cryptomator/config.properties\""
@@ -163,9 +140,8 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/.local/share/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${REV_NUM}\""
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\""
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
--resource-dir dist/linux/resources
- name: Patch Cryptomator.AppDir
@@ -189,8 +165,7 @@ jobs:
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
- name: Download AppImageKit
run: |
curl --silent --fail-with-body --proto "=https" -L "https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-${{ matrix.arch }}.AppImage" -o appimagetool.AppImage
echo "${{ matrix.appimagetool-sha }} appimagetool.AppImage" | shasum -a256 --check
curl --silent --fail-with-body --proto "=https" -L "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.arch }}.AppImage" -o appimagetool.AppImage
chmod +x appimagetool.AppImage
./appimagetool.AppImage --appimage-extract
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
@@ -202,7 +177,7 @@ jobs:
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Build AppImage
run: >
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.arch }}.AppImage
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${SEMVER_STR}-${{ matrix.arch }}.AppImage
-u "gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.arch }}.AppImage.zsync"
--sign --sign-key=615D449FE6E6A235
- name: Create detached GPG signatures
@@ -219,10 +194,9 @@ jobs:
cryptomator-*.asc
if-no-files-found: error
- name: Publish AppImage on GitHub Releases
if: inputs.upload-to-draft
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
@@ -230,24 +204,79 @@ jobs:
cryptomator-*.zsync
cryptomator-*.asc
collect-sha256sums:
name: Collect AppImage checksums
create-aur-bin-pr:
name: Create PR for aur-bin repo
if: github.event_name == 'workflow_dispatch' && inputs.create-pr || github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable'
runs-on: ubuntu-latest
needs: [build]
if: inputs.upload-to-draft
outputs:
x64-sha256sum: ${{ steps.sha256sum.outputs.x64-sha256sum }}
aarch64-sha256sum: ${{ steps.sha256sum.outputs.aarch64-sha256sum }}
needs: [build, get-version]
container:
image: archlinux:base-devel
env:
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
PKGDEST: ${{ github.workspace }}/pkgdest
SRCDEST: ${{ github.workspace }}/srcdest
steps:
- name: Download AppImage artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
pattern: appimage-*
path: appimage-artifacts
- name: Compute SHA256 sums
id: sha256sum
- name: Prepare pacman
run: |
read -ra X64_SUM < <(sha256sum appimage-artifacts/appimage-x86_64/cryptomator-*-x86_64.AppImage)
read -ra AARCH64_SUM < <(sha256sum appimage-artifacts/appimage-aarch64/cryptomator-*-aarch64.AppImage)
echo "x64-sha256sum=${X64_SUM[0]}" >> "$GITHUB_OUTPUT"
echo "aarch64-sha256sum=${AARCH64_SUM[0]}" >> "$GITHUB_OUTPUT"
pacman-key --init
pacman-key --populate archlinux
pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip github-cli curl pacman-contrib
- name: Checkout cryptomator/aur-bin
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Create build user
run: |
useradd -m builder
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/builder
chown -R builder:builder "$GITHUB_WORKSPACE"
install -d -m 0755 -o builder -g builder "$PKGDEST" "$SRCDEST"
- name: Import Cryptomator release signing key
# try first ubuntu. on failure try openpgp keyservers
run: >
sudo -u builder gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
|| sudo -u builder gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
- name: Checkout release branch
run: |
git config --global safe.directory '*'
git checkout -b "release/${SEMVER_STR}"
- name: Update build file
run: |
sed -i -e "s|^pkgver=.*$|pkgver=${SEMVER_STR}|" PKGBUILD
sed -i -e 's|^pkgrel=.*$|pkgrel=1|' PKGBUILD
sudo -u builder updpkgsums
sudo -u builder makepkg --printsrcinfo > .SRCINFO
- name: Build package with makepkg
run: >
sudo -u builder
env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST"
makepkg --syncdeps --cleanbuild --noconfirm --log
- name: Commit and push
run: |
git config user.name "cryptobot"
git config user.email "cryptobot@users.noreply.github.com"
git config push.autoSetupRemote true
git stage PKGBUILD .SRCINFO
git commit -m "Prepare release ${SEMVER_STR}"
git push
- name: Create pull request
id: create-pr
run: |
printf "Created by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > pr_body.md
PR_URL=$(gh pr create --title "Release ${SEMVER_STR}" --body-file pr_body.md)
echo "url=$PR_URL" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR-bin release PR for ${{ github.event.repository.name }} ${{ needs.get-version.outputs.semVerStr }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -1,115 +0,0 @@
name: PR for aur-bin repo
on:
release:
types: [published]
workflow_dispatch:
inputs:
src-tag:
description: 'Source or Release tag'
required: false
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.src-tag }}
create-aur-bin-pr:
name: Create PR for aur-bin repo
if: (github.event_name == 'workflow_dispatch') || (github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable')
runs-on: ubuntu-latest
needs: [get-version]
container:
image: archlinux:base-devel
env:
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
PKGDEST: ${{ github.workspace }}/pkgdest
SRCDEST: ${{ github.workspace }}/srcdest
steps:
- name: Prepare pacman
run: |
pacman-key --init
pacman-key --populate archlinux
pacman -Syu --noconfirm --needed git base-devel sudo gnupg maven unzip github-cli curl pacman-contrib
- name: Checkout cryptomator/aur-bin
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'cryptomator/aur-bin'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Create build user
run: |
useradd -m builder
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/builder
chown -R builder:builder "$GITHUB_WORKSPACE"
install -d -m 0755 -o builder -g builder "$PKGDEST" "$SRCDEST"
- name: Import Cryptomator release signing key
# try first ubuntu. on failure try openpgp keyservers
run: >
sudo -u builder gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
|| sudo -u builder gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 58117AFA1F85B3EEC154677D615D449FE6E6A235
- name: Checkout release branch
run: |
git config --global safe.directory '*'
git checkout -b "release/${SEMVER_STR}"
- name: Determine pkgrel
id: pkgrel
run: |
CURRENT_VERSION="$(sed -nE 's/^pkgver=(.*)$/\1/p' PKGBUILD | head -n1)"
CURRENT_REL="$(sed -nE 's/^pkgrel=([0-9]+).*$/\1/p' PKGBUILD | head -n1)"
if [[ "$CURRENT_VERSION" == "$TARGET_VERSION" && "$CURRENT_REL" =~ ^[0-9]+$ ]]; then
NEXT_REL=$((CURRENT_REL + 1))
else
NEXT_REL=1
fi
echo "value=${NEXT_REL}" >> "$GITHUB_OUTPUT"
echo "dist-version=${TARGET_VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT"
env:
TARGET_VERSION: ${{ needs.get-version.outputs.semVerStr }}
- name: Update build file
run: |
sed -i -e "s|^pkgver=.*$|pkgver=${PKG_VERSION}|" PKGBUILD
sed -i -e "s|^pkgrel=.*$|pkgrel=${PKG_RELEASE}|" PKGBUILD
sudo -u builder updpkgsums
sudo -u builder makepkg --printsrcinfo > .SRCINFO
env:
PKG_VERSION: ${{ needs.get-version.outputs.semVerNum }}
PKG_RELEASE: ${{ steps.pkgrel.outputs.value }}
- name: Build package with makepkg
run: >
sudo -u builder
env PKGDEST="$PKGDEST" SRCDEST="$SRCDEST"
makepkg --syncdeps --cleanbuild --noconfirm --log
- name: Commit and push
run: |
git config user.name "cryptobot"
git config user.email "cryptobot@users.noreply.github.com"
git config push.autoSetupRemote true
git stage PKGBUILD .SRCINFO
git commit -m "Prepare release ${DIST_VERSION}"
git push
env:
DIST_VERSION: ${{ steps.pkgrel.outputs.dist-version }}
- name: Create pull request
id: create-pr
run: |
printf "Created by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > pr_body.md
PR_URL=$(gh pr create --title "Release ${DIST_VERSION}" --body-file pr_body.md)
echo "url=$PR_URL" >> "$GITHUB_OUTPUT"
env:
DIST_VERSION: ${{ steps.pkgrel.outputs.dist-version }}
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR-bin release PR for ${{ github.event.repository.name }} ${{ needs.get-version.outputs.semVerStr }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: ''
MSG_MINIMAL: true

View File

@@ -37,7 +37,7 @@ on:
jobs:
download-file:
name: Downloads the file into the VM
runs-on: ubuntu-slim
runs-on: ubuntu-latest
outputs:
fileName: ${{ steps.extractName.outputs.fileName}}
env:
@@ -58,12 +58,12 @@ jobs:
if-no-files-found: error
allowlist-kaspersky:
name: Anti Virus Allowlisting Kaspersky
runs-on: ubuntu-slim
runs-on: ubuntu-latest
needs: download-file
if: inputs.kaspersky
steps:
- name: Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload
@@ -78,12 +78,12 @@ jobs:
local-dir: ./upload/
allowlist-avast:
name: Anti Virus Allowlisting Avast
runs-on: ubuntu-slim
runs-on: ubuntu-latest
needs: download-file
if: inputs.avast
steps:
- name: Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: ${{ needs.download-file.outputs.fileName }}
path: upload

View File

@@ -29,7 +29,7 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Cache SonarCloud packages
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
@@ -47,3 +47,40 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Draft a release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
body: |-
> [!NOTE]
> 🚧 Work in Progress 🚧
>
> Please be patient, the [builds are still running](https://github.com/cryptomator/cryptomator/actions). Binary packages can be found here in a few moments.
<!--REPLACE with auto-generated release notes (see below)
### What's New 🎉
### Bugfixes 🐛
### Other Changes 📎
END REPLACE-->
For a comprehensive view of changes, read the [CHANGELOG](https://github.com/cryptomator/cryptomator/blob/develop/CHANGELOG.md).
---
<!-- Don't forget to include the
💾 SHA-256 checksums of release artifacts:
```
```
-->
> [!TIP]
> You can verify the GPG signature of all assets using our public key: [`5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
<!-- Auto-Generated Release Notes: -->

View File

@@ -74,10 +74,10 @@ jobs:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "JDK update available"
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions."
SLACK_FOOTER: ''
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -1,8 +1,6 @@
name: Build Debian Package
on:
schedule:
- cron: '0 22 20 * *'
workflow_dispatch:
inputs:
semver:

View File

@@ -1,157 +0,0 @@
name: Draft a Cryptomator Release
on:
push:
tags:
- '*'
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS'
defaults:
run:
shell: bash
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ''
create-release-draft:
name: Compile and Test
runs-on: ubuntu-latest
needs: get-version
if: needs.get-version.outputs.versionType != 'unknown'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Check the git tag is signed
run: git cat-file -p "${GITHUB_REF_NAME}" | grep "BEGIN SSH SIGNATURE"
- name: Check the git tag is on release or main branch
run: git branch -r --contains "${GITHUB_REF_NAME}" | grep -E '^\s*origin/(main|release/.*)\s*$'
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and Test
run: xvfb-run mvn -B verify -Plinux
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
- name: Draft a release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
discussion_category_name: releases
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
generate_release_notes: true
body_path: .github/release-body.md.template
- name: Download source tarball
run: |
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" https://github.com/cryptomator/cryptomator/archive/${{ github.ref }}.tar.gz --output cryptomator-${{ github.ref_name }}.tar.gz
- name: Sign source tarball with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
build-exe-and-msi:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/win-exe.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-dmg-arm64:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/mac-dmg.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-dmg-x64:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/mac-dmg-x64.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
build-appimages:
needs: [get-version, create-release-draft]
uses: ./.github/workflows/appimage.yml
with:
semVerNum: ${{needs.get-version.outputs.semVerNum}}
revisionNum: ${{needs.get-version.outputs.revNum}}
semVerSuffix: ${{needs.get-version.outputs.semVerSuffix}}
secrets: inherit
update-sha256sums:
runs-on: ubuntu-latest
needs: [get-version, build-exe-and-msi, build-dmg-arm64, build-dmg-x64, build-appimages]
env:
TAG: ${{ github.ref_name }}
SEMVER: ${{ needs.get-version.outputs.semVerStr }}
GH_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Compute source tarball SHA256
id: src-sha256
run: |
curl --silent --fail-with-body --proto "=https" -L \
-H "Accept: application/vnd.github+json" \
"https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz" \
--output "cryptomator-${SEMVER}.tar.gz"
read -ra CMD_OUTPUT < <(sha256sum "cryptomator-${SEMVER}.tar.gz")
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Update release body with checksums
run: |
CURRENT_BODY=$(gh release view "${TAG}" --json body --jq .body)
RELEASE_BODY=$(printf '%s\n' "${CURRENT_BODY}" | sed '/<!-- HEADER -->/,/<!-- \/HEADER -->/c\
<!-- HEADER -->\
> [!NOTE]\
> Release artifacts finished building successfully.\
>\
> SHA-256 checksums have been updated below.\
<!-- /HEADER -->')
export TARBALL="${SRC_SHA} cryptomator-${SEMVER}.tar.gz"
export MSI="${MSI_SHA} Cryptomator-${SEMVER}-x64.msi"
export EXE="${EXE_SHA} Cryptomator-${SEMVER}-x64.exe"
export DMG_arm64="${DMG_ARM64_SHA} Cryptomator-${SEMVER}-arm64.dmg"
export DMG_x64="${DMG_X64_SHA} Cryptomator-${SEMVER}-x64.dmg"
export APPIMAGE_x86_64="${APPIMAGE_X64_SHA} cryptomator-${SEMVER}-x86_64.AppImage"
export APPIMAGE_aarch64="${APPIMAGE_AARCH64_SHA} cryptomator-${SEMVER}-aarch64.AppImage"
envsubst '$VERSION $TARBALL $EXE $MSI $DMG_x64 $DMG_arm64 $APPIMAGE_x86_64 $APPIMAGE_aarch64' \
<<< "${RELEASE_BODY}" \
> release-body.md
gh release edit "${TAG}" --draft --notes-file release-body.md
env:
VERSION: ${{ needs.get-version.outputs.semVerStr }}
SRC_SHA: ${{ steps.src-sha256.outputs.value }}
MSI_SHA: ${{ needs.build-exe-and-msi.outputs.sha256-msi }}
EXE_SHA: ${{ needs.build-exe-and-msi.outputs.sha256-exe }}
DMG_ARM64_SHA: ${{ needs.build-dmg-arm64.outputs.sha256-dmg }}
DMG_X64_SHA: ${{ needs.build-dmg-x64.outputs.sha256-dmg }}
APPIMAGE_X64_SHA: ${{ needs.build-appimages.outputs.sha256-appimage-x64 }}
APPIMAGE_AARCH64_SHA: ${{ needs.build-appimages.outputs.sha256-appimage-aarch64 }}

85
.github/workflows/flathub.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Create PR for flathub
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag'
required: true
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.tag }}
tarball:
name: Determines tarball url and compute checksum
runs-on: ubuntu-latest
needs: [get-version]
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
outputs:
url: ${{ steps.url.outputs.url}}
sha512: ${{ steps.sha512.outputs.sha512}}
steps:
- name: Determine tarball url
id: url
run: |
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz"
echo "url=${URL}" >> "$GITHUB_OUTPUT"
env:
TAG: ${{ inputs.tag || github.event.release.tag_name}}
- name: Download source tarball and compute checksum
id: sha512
run: |
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" ${{ steps.url.outputs.url }} --output cryptomator.tar.gz
TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1)
echo "sha512=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT"
flathub:
name: Create PR for flathub
runs-on: ubuntu-latest
needs: [tarball, get-version]
env:
FLATHUB_PR_URL: tbd
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: 'flathub/org.cryptomator.Cryptomator'
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Checkout release branch
run: |
git checkout -b release/${{ needs.get-version.outputs.semVerStr }}
- name: Update build file
run: |
sed -i -e 's/VERSION: [0-9]\+\.[0-9]\+\.[0-9]\+.*/VERSION: ${{ needs.get-version.outputs.semVerStr }}/g' org.cryptomator.Cryptomator.yaml
sed -i -e 's/sha512: [0-9A-Za-z_\+-]\{128\} #CRYPTOMATOR/sha512: ${{ needs.tarball.outputs.sha512 }} #CRYPTOMATOR/g' org.cryptomator.Cryptomator.yaml
sed -i -e 's;url: https://github.com/cryptomator/cryptomator/archive/refs/tags/[^[:blank:]]\+;url: ${{ needs.tarball.outputs.url }};g' org.cryptomator.Cryptomator.yaml
- name: Commit and push
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
git config push.autoSetupRemote true
git stage .
git commit -m "Prepare release ${{needs.get-version.outputs.semVerStr}}"
git push
- name: Create pull request
run: |
printf "> [!IMPORTANT]\n> Todos:\n> - [ ] Update maven dependencies\n> - [ ] Check for JDK update\n> - [ ] Check for JFX update" > pr_body.md
PR_URL=$(gh pr create --title "Release ${{ needs.get-version.outputs.semVerStr }}" --body-file pr_body.md)
echo "FLATHUB_PR_URL=$PR_URL" >> "$GITHUB_ENV"
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
if: github.event_name == 'release'
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created."
SLACK_MESSAGE: "See <${{ env.FLATHUB_PR_URL }}|PR> on how to proceed.>."
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -14,9 +14,6 @@ on:
semVerNum:
description: "The numerical part of the version string"
value: ${{ jobs.determine-version.outputs.semVerNum}}
semVerSuffix:
description: "The suffix of the version string"
value: ${{ jobs.determine-version.outputs.semVerSuffix}}
revNum:
description: "The revision number"
value: ${{ jobs.determine-version.outputs.revNum}}
@@ -35,7 +32,6 @@ jobs:
outputs:
semVerNum: ${{ steps.versions.outputs.semVerNum }}
semVerStr: ${{ steps.versions.outputs.semVerStr }}
semVerSuffix: ${{ steps.versions.outputs.semVerSuffix }}
revNum: ${{ steps.versions.outputs.revNum }}
type: ${{ steps.versions.outputs.type}}
steps:
@@ -58,30 +54,25 @@ jobs:
else
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
fi
SEM_VER_NUM=$(echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
SEM_VER_SUFFIX="${SEM_VER_STR#"$SEM_VER_NUM"}"
SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
REVCOUNT=`git rev-list --count HEAD`
TYPE="unknown"
if [[ -z $SEM_VER_SUFFIX ]]; then
if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TYPE="stable"
elif [[ $SEM_VER_SUFFIX =~ -alpha[1-9]+$ ]]; then
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then
TYPE="alpha"
elif [[ $SEM_VER_SUFFIX =~ -beta[1-9]+$ ]]; then
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then
TYPE="beta"
elif [[ $SEM_VER_SUFFIX =~ -rc[1-9]+$ ]]; then
elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then
TYPE="rc"
fi
echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT
echo "semVerSuffix=${SEM_VER_SUFFIX}" >> $GITHUB_OUTPUT
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
echo "type=${TYPE}" >> $GITHUB_OUTPUT
env:
VERSION_STRING: ${{ inputs.version }}
- name: Validate Version
uses: skymatic/semver-validation-action@7c80b6b03a18b42884761daa9862ff5683ec8c8a # v4.0.0
uses: skymatic/semver-validation-action@7a6ae1c9e121540d11c9c7e4e667c83d583aa153 # v3.0.0
with:
version: ${{ steps.versions.outputs.semVerStr }}

View File

@@ -1,264 +0,0 @@
name: Build flatpak
on:
release:
types: [published]
workflow_dispatch:
inputs:
src-tag:
description: 'Source or Release tag'
required: false
create-pr:
description: 'Create Flathub PR'
required: false
type: boolean
default: false
push:
branches-ignore:
- 'dependabot/**'
paths:
- '.github/workflows/get-version.yml'
- '.github/workflows/linux-flatpak.yml'
- 'dist/linux/flatpak/**'
- 'dist/linux/common/**'
- 'dist/linux/resources/**'
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.src-tag }}
build-flatpak:
name: "Build flatpak"
needs: [get-version]
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
options: --privileged
strategy:
fail-fast: false
matrix:
variant:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.variant.runner }}
permissions:
contents: read
env:
SRC_GIT_SHA: ${{ inputs.src-tag || github.sha}}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: flathub/org.cryptomator.Cryptomator
submodules: true
- name: Checkout build script
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: build-scripts
- name: Checkout app source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: cryptomator
ref: ${{ env.SRC_GIT_SHA }}
fetch-depth: 0
- name: Prepare build files
# using envsubst instead of yq to keep linebreaks
run: |
cp -r -f build-scripts/dist/linux/flatpak/* .
envsubst '$FLATPAK_VERSION $FLATPAK_REVISION $CRYPTOMATOR_SOURCE' < org.cryptomator.Cryptomator.TEMPLATE.yaml > org.cryptomator.Cryptomator.yaml
env:
FLATPAK_VERSION: ${{ needs.get-version.outputs.semVerNum }}
FLATPAK_REVISION: 1
CRYPTOMATOR_SOURCE: |-
type: git
path: cryptomator
commit: ${{ env.SRC_GIT_SHA }}
- name: Copy build script for upload
run: cp org.cryptomator.Cryptomator.yaml org.cryptomator.Cryptomator.${{matrix.variant.arch}}.yaml
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
archive: false
if-no-files-found: error
path: |
org.cryptomator.Cryptomator.${{matrix.variant.arch}}.yaml
- uses: flatpak/flatpak-github-actions/flatpak-builder@401fe28a8384095fc1531b9d320b292f0ee45adb # SNAPSHOT due to using keep-build-dirs
with:
bundle: cryptomator.flatpak
manifest-path: org.cryptomator.Cryptomator.yaml
cache-key: flatpak-builder-${{ env.SRC_GIT_SHA }}
arch: ${{ matrix.variant.arch }}
keep-build-dirs: true
- name: Collect maven dependencies
working-directory: .flatpak-builder/build/cryptomator-1/.m2/repository/
run: |
find * -type f \( -iname '*.jar' -o -iname '*.pom' \) | sort -V > /tmp/maven-dependency-files.txt
grep -v '^org/openjfx/javafx-' /tmp/maven-dependency-files.txt > maven-dependency-files-common.txt
grep '^org/openjfx/javafx-' /tmp/maven-dependency-files.txt > maven-dependency-files-javafx.txt
- name: Update arch independent maven dependencies
run: |
(
cd .flatpak-builder/build/cryptomator-1/.m2/repository/
while IFS= read -r dependencyPath; do
dependencyName=$(dirname "$dependencyPath")
dependencySha=$(sha256sum "$dependencyPath" | cut -c 1-64)
cat <<EOF
- type: file
dest: .m2/repository/${dependencyName}
url: https://repo.maven.apache.org/maven2/${dependencyPath}
sha256: ${dependencySha}
EOF
done < maven-dependency-files-common.txt
) > maven-dependencies.yaml
- name: Update arch specific maven dependencies
run: |
(
cd .flatpak-builder/build/cryptomator-1/.m2/repository/
while IFS= read -r dependencyPath; do
dependencyName=$(dirname "$dependencyPath")
dependencySha=$(sha256sum "$dependencyPath" | cut -c 1-64)
cat <<EOF
- type: file
dest: .m2/repository/${dependencyName}
url: https://repo.maven.apache.org/maven2/${dependencyPath}
sha256: ${dependencySha}
only-arches: [${{ matrix.variant.arch }}]
EOF
done < maven-dependency-files-javafx.txt
) > javafx-maven-dependencies-${{ matrix.variant.arch }}.yaml
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: maven-sources-${{ matrix.variant.arch }}
if-no-files-found: error
path: |
maven-dependencies.yaml
javafx-maven-dependencies-${{ matrix.variant.arch }}.yaml
verify-maven-sources:
name: Verify maven sources
runs-on: ubuntu-latest
needs: [build-flatpak]
permissions:
contents: none
steps:
- name: Download updated maven aarch64 dependencies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: maven-sources-aarch64
path: mvn-src-aarch64
- name: Download updated maven x86_64 dependencies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: maven-sources-x86_64
path: mvn-src-x64
- name: Verify arch independent maven dependencies
run: cmp --silent mvn-src-aarch64/maven-dependencies.yaml mvn-src-x64/maven-dependencies.yaml
create-pr:
name: Create PR for flathub
runs-on: ubuntu-latest
needs: [get-version, verify-maven-sources]
if: (github.event_name == 'workflow_dispatch' && inputs.create-pr ) || (github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable')
permissions:
contents: write
env:
TARBALL_URL: 'https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name || inputs.src-tag }}.tar.gz'
steps:
- name: Check that input "src-tag" is actually a tag
if: github.event_name == 'workflow_dispatch'
run: |
if [ -z "$SRC_TAG" ]; then
echo '::error::Input "src-tag" must be set to create a Flathub PR'
exit 1
fi
env:
SRC_TAG: ${{ inputs.src-tag }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: flathub/org.cryptomator.Cryptomator
submodules: true #TODO: Update submodule!
token: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Checkout release branch
run: |
git checkout -b release/${{ needs.get-version.outputs.semVerStr }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: cryptomator
- name: Download source tarball and compute checksum
id: sha512
run: |
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" ${TARBALL_URL} --output cryptomator.tar.gz
TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1)
echo "value=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT"
- name: Download updated maven aarch64 dependencies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: maven-sources-aarch64
path: mvn-src-aarch64
- name: Download updated maven x86_64 dependencies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: maven-sources-x86_64
path: mvn-src-x64
- name: Determine revision
id: revision
run: |
CURRENT_VERSION="$(yq '(.modules[] | select(.name == "cryptomator") | .build-options.env.VERSION)' org.cryptomator.Cryptomator.yaml)"
CURRENT_REVISION="$(yq '(.modules[] | select(.name == "cryptomator") | .build-options.env.REVISION_NO)' org.cryptomator.Cryptomator.yaml)"
if [[ "$CURRENT_VERSION" == "$TARGET_VERSION" && "$CURRENT_REVISION" =~ ^[0-9]+$ ]]; then
NEXT_REVISION=$((CURRENT_REVISION + 1))
else
NEXT_REVISION=1
fi
echo "value=${NEXT_REVISION}" >> "$GITHUB_OUTPUT"
env:
TARGET_VERSION: ${{ needs.get-version.outputs.semVerStr }}
- name: Update build files
run: |
cp -r -f cryptomator/dist/linux/flatpak/* .
cp -r -f mvn-src-x64/* .
cp -r -f mvn-src-aarch64/* .
envsubst '$FLATPAK_VERSION $FLATPAK_REVISION $CRYPTOMATOR_SOURCE' < org.cryptomator.Cryptomator.TEMPLATE.yaml > org.cryptomator.Cryptomator.yaml
yq -i 'del(.modules[] | select(.name == "cryptomator") | .build-options.build-args)' org.cryptomator.Cryptomator.yaml
yq -i '(.modules[] | select(.name == "cryptomator") | .sources) += ["maven-dependencies.yaml", "javafx-maven-dependencies-x86_64.yaml", "javafx-maven-dependencies-aarch64.yaml"]' org.cryptomator.Cryptomator.yaml
env:
FLATPAK_VERSION: ${{ needs.get-version.outputs.semVerNum }}
FLATPAK_REVISION: ${{ steps.revision.outputs.value}}
CRYPTOMATOR_SOURCE: |-
type: archive
sha512: ${{steps.sha512.outputs.value}}
url: ${{ env.TARBALL_URL }}
- name: Commit and push
run: |
git config user.name "cryptobot"
git config user.email "cryptobot@users.noreply.github.com"
git config push.autoSetupRemote true
git stage org.cryptomator.Cryptomator.yaml maven-dependencies.yaml javafx-maven-dependencies-aarch64.yaml javafx-maven-dependencies-x86_64.yaml
git commit -m "Prepare release ${{needs.get-version.outputs.semVerStr}}"
git push
- name: Create pull request
id: create-pr
run: |
printf "Created by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > pr_body.md
PR_URL=$(gh pr create --title "Release ${{ needs.get-version.outputs.semVerStr }}" --body-file pr_body.md)
echo "FLATHUB_PR_URL=$PR_URL" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
if: github.event_name == 'release'
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.FLATHUB_PR_URL }}|PR> on how to proceed."
SLACK_FOOTER: ''
MSG_MINIMAL: true

View File

@@ -3,8 +3,6 @@ name: Build Arch package
on:
release:
types: [published]
schedule:
- cron: '0 21 20 * *'
workflow_dispatch:
inputs:
version:
@@ -132,6 +130,7 @@ jobs:
- name: Determine pkgrel
id: pkgrel
run: |
TARGET_VERSION='${{ needs.get-version.outputs.semVerStr }}'
CURRENT_VERSION="$(sed -nE 's/^pkgver=(.*)$/\1/p' PKGBUILD | head -n1)"
CURRENT_REL="$(sed -nE 's/^pkgrel=([0-9]+).*$/\1/p' PKGBUILD | head -n1)"
@@ -142,11 +141,11 @@ jobs:
fi
echo "value=${NEXT_REL}" >> "$GITHUB_OUTPUT"
echo "dist-version=${TARGET_VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT"
echo "dist-version=${VERSION}-${NEXT_REL}" >> "$GITHUB_OUTPUT"
env:
TARGET_VERSION: ${{ needs.get-version.outputs.semVerStr }}
VERSION: ${{ needs.get-version.outputs.semVerStr }}
- name: Download PKGBUILD template
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: pkgbuild-file
- name: Prepare PKGBUILD
@@ -192,10 +191,10 @@ jobs:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "AUR release PR created for ${{ github.event.repository.name }} ${{ steps.pkgrel.outputs.dist-version }} ."
SLACK_MESSAGE: "See <${{ steps.create-pr.outputs.url }}|PR> on how to proceed."
SLACK_FOOTER: ''
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -9,45 +9,13 @@ name: Build macOS .dmg for x64
#######################################
on:
schedule:
- cron: '0 20 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
notarize:
description: 'Notarize'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-dmg:
description: "SHA256 sum of the x64 dmg"
value: ${{ jobs.build.outputs.sha256sum}}
release:
types: [published]
workflow_dispatch:
inputs:
semVerNum:
description: 'The Major.Minor.Patch part of the version'
version:
description: 'Version'
required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
notarize:
description: 'Notarize'
required: true
@@ -57,17 +25,17 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs:
build:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-arm:
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }}
outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
needs: [get-version]
strategy:
fail-fast: false
matrix:
@@ -109,7 +77,7 @@ jobs:
exit 1
fi
- name: Set version
run : mvn versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pmac -DskipTests
- name: Patch target dir
@@ -149,8 +117,8 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${VERSION_NUM}"
--copyright "(C) 2016 - 2025 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
--java-options "-Xss5m"
@@ -159,7 +127,7 @@ jobs:
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
@@ -169,8 +137,7 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NUM}\""
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
- name: Patch Cryptomator.app
@@ -178,10 +145,12 @@ jobs:
mv appdir/Cryptomator.app Cryptomator.app
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
- name: Generate license for dmg
run: >
@@ -270,14 +239,16 @@ jobs:
--eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NUM}-${{ matrix.output-suffix }}.dmg dmg
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Codesign .dmg
run: |
codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg
env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg
if: inputs.notarize || github.event_name == 'schedule'
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with:
app-path: 'Cryptomator-*.dmg'
@@ -285,12 +256,8 @@ jobs:
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: '/Applications/Xcode_16.app'
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(shasum -a256 Cryptomator-*.dmg)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.output-suffix }}.dmg"
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -311,10 +278,9 @@ jobs:
Cryptomator-*.asc
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: inputs.upload-to-draft
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |

View File

@@ -1,45 +1,13 @@
name: Build macOS .dmg for arm64
on:
schedule:
- cron: '0 20 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
notarize:
description: 'Notarize'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-dmg:
description: "SHA256 sum of the arm64 dmg"
value: ${{ jobs.build.outputs.sha256sum}}
release:
types: [published]
workflow_dispatch:
inputs:
semVerNum:
description: 'The Major.Minor.Patch part of the version'
version:
description: 'Version'
required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
notarize:
description: 'Notarize'
required: true
@@ -55,17 +23,17 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '25.0.2+10.0.LTS'
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build:
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }}
outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
needs: [get-version]
strategy:
fail-fast: false
matrix:
@@ -107,7 +75,7 @@ jobs:
exit 1
fi
- name: Set version
run : mvn versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pmac -DskipTests
- name: Patch target dir
@@ -147,8 +115,8 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${VERSION_NUM}"
--copyright "(C) 2016 - 2025 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac"
--java-options "-Xss5m"
@@ -157,7 +125,7 @@ jobs:
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.adminConfigPath=\"/Library/Application Support/Cryptomator/config.properties\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
@@ -167,9 +135,8 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism"
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NUM}\""
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log"
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
- name: Patch Cryptomator.app
@@ -177,10 +144,12 @@ jobs:
mv appdir/Cryptomator.app Cryptomator.app
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
cp dist/mac/resources/Assets.car Cryptomator.app/Contents/Resources/
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NUM}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
- name: Generate license for dmg
run: >
@@ -269,14 +238,16 @@ jobs:
--eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NUM}-${{ matrix.output-suffix }}.dmg dmg
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Codesign .dmg
run: |
codesign -s ${CODESIGN_IDENTITY} --timestamp Cryptomator-*.dmg
env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
- name: Notarize .dmg
if: inputs.notarize || github.event_name == 'schedule'
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3
with:
app-path: 'Cryptomator-*.dmg'
@@ -284,12 +255,8 @@ jobs:
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: '/Applications/Xcode_16.app'
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(shasum -a256 Cryptomator-*.dmg)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.output-suffix }}.dmg"
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -310,10 +277,9 @@ jobs:
Cryptomator-*.asc
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: inputs.upload-to-draft
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |

View File

@@ -7,7 +7,7 @@ on:
jobs:
no-response:
runs-on: ubuntu-slim
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

View File

@@ -5,141 +5,35 @@ on:
types: [published]
jobs:
notify:
runs-on: ubuntu-slim
get-version:
runs-on: ubuntu-latest
steps:
- name: Notify about DEB build
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
SLACK_FOOTER: ''
MSG_MINIMAL: true
- name: Notify about latest-version update
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Requiring version check source update for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}."
SLACK_MESSAGE: 'Check S3 bucket for <https://static.cryptomator.org/desktop/latest-version.json|latest-version.json>.'
SLACK_FOOTER: ''
MSG_MINIMAL: true
get-asset-urls:
name: Get release asset URLs
runs-on: ubuntu-slim
outputs:
is-windows-release: ${{ steps.urls.outputs.urls-present }}
msi-url: ${{ steps.urls.outputs.msi }}
exe-url: ${{ steps.urls.outputs.exe }}
steps:
- name: Extract MSI and EXE download URLs
id: urls
- name: Download source tarball
run: |
MSI_URL=$(jq -r '[.[] | select(.name | endswith("-x64.msi"))][0].browser_download_url // "null"' <<< "$RELEASE_ASSETS")
EXE_URL=$(jq -r '[.[] | select(.name | endswith("-x64.exe"))][0].browser_download_url // "null"' <<< "$RELEASE_ASSETS")
if [[ "$MSI_URL" == "null" || -z "$MSI_URL" || "$EXE_URL" == "null" || -z "$EXE_URL" ]]; then
echo "urls-present=false" >> $GITHUB_OUTPUT
else
echo "urls-present=true" >> $GITHUB_OUTPUT
echo "msi=${MSI_URL}" >> $GITHUB_OUTPUT
echo "exe=${EXE_URL}" >> $GITHUB_OUTPUT
fi
env:
RELEASE_ASSETS: ${{ toJson(github.event.release.assets) }}
allowlist-msi-x64:
needs: [get-asset-urls]
if: needs.get-asset-urls.outputs.is-windows-release == 'true'
uses: ./.github/workflows/av-whitelist.yml
with:
url: ${{ needs.get-asset-urls.outputs.msi-url }}
secrets: inherit
allowlist-exe-x64:
needs: [get-asset-urls, allowlist-msi-x64]
if: needs.get-asset-urls.outputs.is-windows-release == 'true'
uses: ./.github/workflows/av-whitelist.yml
with:
url: ${{ needs.get-asset-urls.outputs.exe-url }}
secrets: inherit
check-release:
name: Analyzes the release for certain properties
runs-on: ubuntu-slim
outputs:
release-kind: ${{steps.determine-kind.outputs.value}} # Possible values are [alpha, beta, rc, stable, unknown]
steps:
- id: determine-kind
curl --silent --fail-with-body --proto "=https" -L -H "Accept: application/vnd.github+json" https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
- name: Sign source tarball with key 615D449FE6E6A235
run: |
SEM_VER_NUM=$(echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
SEM_VER_SUFFIX="${SEM_VER_STR#"$SEM_VER_NUM"}"
TYPE="unknown"
if [[ -z $SEM_VER_SUFFIX ]]; then
TYPE="stable"
elif [[ $SEM_VER_SUFFIX =~ -alpha[1-9]+$ ]]; then
TYPE="alpha"
elif [[ $SEM_VER_SUFFIX =~ -beta[1-9]+$ ]]; then
TYPE="beta"
elif [[ $SEM_VER_SUFFIX =~ -rc[1-9]+$ ]]; then
TYPE="rc"
fi
echo "value=${TYPE}" >> $GITHUB_OUTPUT
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
env:
SEM_VER_STR: ${{ github.event.release.tag_name }}
notify-winget:
name: Notify for winget-release
if: needs.get-asset-urls.outputs.is-windows-release == 'true' && needs.check-release.outputs.release-kind == 'stable'
needs: [check-release, get-asset-urls]
runs-on: ubuntu-slim
steps:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Publish asc on GitHub Releases
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
cryptomator-*.tar.gz.asc
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_CRYPTOMATOR_DESKTOP }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: ''
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml|release to winget>."
SLACK_FOOTER: ''
MSG_MINIMAL: true
trigger-website-update:
needs: [check-release]
runs-on: ubuntu-slim
if: needs.check-release.outputs.release-kind == 'stable'
steps:
- name: Start website update workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
event-type: desktop-release
token: ${{ secrets.CRYPTOBOT_WORKFLOW_DISPATCH_TOKEN }}
repository: cryptomator/cryptomator.github.io
client-payload: '{ "version": "${{ github.event.release.tag_name }}", "release": ${{ toJson(github.event.release.assets) }} }'
trigger-docs-update:
needs: [check-release, get-asset-urls]
runs-on: ubuntu-slim
if: needs.get-asset-urls.outputs.is-windows-release == 'true' && needs.check-release.outputs.release-kind == 'stable'
steps:
- name: Start docs update workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
event-type: desktop-release
token: ${{ secrets.CRYPTOBOT_WORKFLOW_DISPATCH_TOKEN }}
repository: cryptomator/docs
client-payload: '{ "version": "${{ github.event.release.tag_name }}", "release": ${{ toJson(github.event.release.assets) }} }'
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -50,7 +50,7 @@ jobs:
exit 1
fi
- name: Cache NVD DB
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.m2/repository/org/owasp/dependency-check-data/
key: dependency-check-${{ github.run_id }}

View File

@@ -7,7 +7,7 @@ on:
jobs:
stale:
runs-on: ubuntu-slim
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

View File

@@ -1,48 +1,13 @@
name: Build Windows Installer
on:
schedule:
- cron: '0 19 20 * *'
workflow_call:
inputs:
semVerNum:
type: string
description: 'The Major.Minor.Patch part of the version'
required: true
revisionNum:
type: string
description: 'The revision number'
required: true
semVerSuffix:
type: string
description: 'The suffix of the version, including dash'
required: true
sign:
description: 'Sign binaries'
default: true
type: boolean
upload-to-draft:
type: boolean
default: true
outputs:
sha256-msi:
description: "SHA256 sum of the x64 msi"
value: ${{ jobs.build-msi.outputs.sha256sum}}
sha256-exe:
description: "SHA256 sum of the x64 exe"
value: ${{ jobs.build-exe.outputs.sha256sum}}
release:
types: [published]
workflow_dispatch:
inputs:
semVerNum:
description: 'The Major.Minor.Patch part of the version'
version:
description: 'Version'
required: false
revisionNum:
description: 'The revision number'
required: false
semVerSuffix:
description: 'The suffix of the version, including dash'
required: false
default: '-SNAPSHOT'
sign:
description: 'Sign binaries'
required: false
@@ -57,9 +22,6 @@ on:
env:
VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}}
REVISION_NUM: ${{ inputs.revisionNum || '0' }}
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
@@ -72,11 +34,15 @@ defaults:
shell: bash
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-msi:
name: Build .msi Installer
runs-on: ${{ matrix.os }}
outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
needs: [ get-version ]
strategy:
matrix:
include:
@@ -128,7 +94,7 @@ jobs:
exit 1
fi
- name: Set version
run: mvn versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}"
run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B clean package -Pwin -DskipTests
- name: Patch target dir
@@ -168,13 +134,13 @@ jobs:
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${VERSION_NUM}.${REVISION_NUM}"
--copyright "(C) 2016 - 2025 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\""
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\""
@@ -185,13 +151,12 @@ jobs:
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\""
--java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"msi-${REVISION_NUM}\""
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json\""
--java-options "-Dcryptomator.disableUpdateCheck=false"
--java-options "-XX:ErrorFile=C:/cryptomator/cryptomator_crash.log"
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true"
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico
--add-launcher "Cryptomator (Debug)=dist/win/debug-launcher.properties"
@@ -227,7 +192,7 @@ jobs:
& $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: Sign DLLs with Azure Trusted Signing
if: inputs.sign || github.event_name == 'schedule'
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\appdir
@@ -236,6 +201,17 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign DLLs with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'appdir'
file-extensions: 'dll,exe,ps1'
recursive: true
sign-description: 'Cryptomator'
sign-url: 'https://cryptomator.org'
username: ${{ secrets.WIN_CODESIGN_USERNAME }}
password: ${{ secrets.WIN_CODESIGN_PW }}
- name: Replace DLLs inside jars with signed ones
shell: pwsh
run: |
@@ -271,8 +247,8 @@ jobs:
--dest installer
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2026 Skymatic GmbH"
--app-version "${VERSION_NUM}.${REVISION_NUM}"
--copyright "(C) 2016 - 2025 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
--win-menu
--win-dir-chooser
--win-shortcut-prompt
@@ -285,7 +261,7 @@ jobs:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Sign MSI with Azure Trusted Signing
if: inputs.sign || github.event_name == 'schedule'
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\installer
@@ -294,12 +270,8 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.msi)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Add possible alpha/beta tags and architecture to installer name
run: mv installer/Cryptomator-*.msi "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.arch }}.msi"
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -319,9 +291,7 @@ jobs:
build-exe:
name: Build .exe installer
runs-on: ${{ matrix.os }}
needs: [ build-msi ]
outputs:
sha256sum: ${{ steps.sha256sum.outputs.value }}
needs: [ get-version, build-msi ]
strategy:
matrix:
include:
@@ -341,7 +311,7 @@ jobs:
env:
WIX_VERSION: ${{ env.WIX_VERSION }}
- name: Download .msi
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: msi-${{ matrix.arch }}
path: dist/win/bundle/resources
@@ -368,10 +338,10 @@ jobs:
shell: pwsh
- name: Download WinFsp
run: |
curl --silent --fail-with-body --proto "=https" -L "$env:WINFSP_MSI" --output $env:WINFSP_PATH
$computedHash = (Get-FileHash -Path "$env:WINFSP_PATH" -Algorithm SHA256).Hash.ToLower()
if ($computedHash -ne "$env:WINFSP_MSI_HASH") {
throw "Checksum mismatch for ${env:WINFSP_PATH} (expected ${env:WINFSP_MSI_HASH}, got $computedHash)."
curl --silent --fail-with-body --proto "=https" -L ${{ env.WINFSP_MSI }} --output $env:WINFSP_PATH
$computedHash = (Get-FileHash -Path $env:WINFSP_PATH -Algorithm SHA256).Hash.ToLower()
if ($computedHash -ne "${{ env.WINFSP_MSI_HASH }}") {
throw "Checksum mismatch for $env:WINFSP_PATH (expected ${{ env.WINFSP_MSI_HASH }}, got $computedHash)."
}
env:
WINFSP_PATH: 'dist/win/bundle/resources/winfsp.msi'
@@ -385,22 +355,21 @@ jobs:
run: >
wix build
-define BundleName="Cryptomator"
-define BundleVersion="${VERSION_NUM}.${REVISION_NUM}"
-define BundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
-define BundleVendor="Skymatic GmbH"
-define BundleCopyright="(C) 2016 - 2026 Skymatic GmbH"
-define BundleCopyright="(C) 2016 - 2025 Skymatic GmbH"
-define AboutUrl="https://cryptomator.org"
-define HelpUrl="https://cryptomator.org/contact"
-define UpdateUrl="https://cryptomator.org/downloads/"
-ext "WixToolset.Util.wixext"
-ext "WixToolset.BootstrapperApplications.wixext"
./bundle/bundleWithWinfsp.wxs
-out "../../installer/Cryptomator-Installer.exe"
-out "../../installer/unsigned/Cryptomator-Installer.exe"
- name: Detach burn engine in preparation to sign
if: inputs.sign || github.event_name == 'schedule'
run: >
wix burn detach installer/Cryptomator-Installer.exe -engine tmp/engine.exe
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe
- name: Sign WiX burn engine with Azure Trusted Signing
if: inputs.sign || github.event_name == 'schedule'
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\tmp
@@ -410,14 +379,21 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign burn engine with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'tmp'
file-extensions: 'exe'
sign-description: 'Cryptomator Bundle Installer'
sign-url: 'https://cryptomator.org'
username: ${{ secrets.WIN_CODESIGN_USERNAME }}
password: ${{ secrets.WIN_CODESIGN_PW }}
- name: Reattach signed burn engine to installer
if: inputs.sign || github.event_name == 'schedule'
shell: pwsh
run: |
Move-Item -Path installer/Cryptomator-Installer.exe -Destination tmp/Cryptomator-Installer.exe
wix burn reattach tmp/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
run: >
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
- name: Sign EXE installer with Azure Trusted Signing
if: inputs.sign || github.event_name == 'schedule'
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\installer
@@ -427,12 +403,18 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- id: sha256sum
run: |
read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.exe)
echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT
- name: Sign installer with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@957d3c2c08c56855fdac41e5afb9a7aca8c30dd9 # no specific version
with:
base-dir: 'installer'
file-extensions: 'exe'
sign-description: 'Cryptomator Bundle Installer'
sign-url: 'https://cryptomator.org'
username: ${{ secrets.WIN_CODESIGN_USERNAME }}
password: ${{ secrets.WIN_CODESIGN_PW }}
- name: Add possible alpha/beta tags to installer name
run: mv installer/Cryptomator-Installer.exe "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.executable-suffix }}.exe"
run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.executable-suffix }}.exe
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -451,22 +433,58 @@ jobs:
publish:
name: Publish installers to the github release
if: inputs.upload-to-draft
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
runs-on: ubuntu-latest
needs: [ build-msi, build-exe ]
outputs:
download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }}
steps:
- name: Download installers
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
merge-multiple: true
- name: Publish installers on GitHub Releases
id: publish
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
draft: true
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
# do not change ordering of filelist, required for correct job output
files: |
*x64.msi
*x64.exe
*.asc
allowlist-msi-x64:
uses: ./.github/workflows/av-whitelist.yml
needs: [ publish ]
with:
url: ${{ needs.publish.outputs.download-url-msi-x64 }}
secrets: inherit
allowlist-exe-x64:
uses: ./.github/workflows/av-whitelist.yml
needs: [ publish, allowlist-msi-x64 ]
with:
url: ${{ needs.publish.outputs.download-url-exe-x64 }}
secrets: inherit
notify-winget:
name: Notify for winget-release
if: needs.get-version.outputs.versionType == 'stable'
needs: [publish, get-version]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_USERNAME: 'Cryptobot'
SLACK_ICON: false
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "MSI packages of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml| release them to winget>."
SLACK_FOOTER: false
MSG_MINIMAL: true

View File

@@ -18,7 +18,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }}
- name: Submit package
uses: vedantmgoyal2009/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5 # no_specific_version
uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # no_specific_version
with:
identifier: Cryptomator.Cryptomator
version: ${{ inputs.tag }}

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator/mnt&quot; -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{userhome}/.config/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/.config/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/.config/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/logs&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/.local/share/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dfuse.experimental=&quot;true&quot; -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator-Dev/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator-Dev/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -5,7 +5,7 @@
</envs>
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json&quot; -Dcryptomator.p12Path=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12&quot; -Dcryptomator.ipcSocketPath=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{userhome}/Library/Logs/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Library/Application Support/Cryptomator-Dev/mnt&quot; -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -7,45 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
The changelog starts with version 1.19.0.
Changes to prior versions can be found on the [Github release page](https://github.com/cryptomator/cryptomator/releases).
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.19.2...HEAD)
### Changed
* Refactored release pipeline to allow immutable releases ([#4205](https://github.com/cryptomator/cryptomator/pull/4205))
## [1.19.2](https://github.com/cryptomator/cryptomator/releases/1.19.2) - 2026-03-20
### Security
* Cryptomamtor Hub Vaults: Additional patch for (#4179, [GHSA-34rf-rwr3-7g43](https://github.com/cryptomator/cryptomator/security/advisories/GHSA-34rf-rwr3-7g43))
## [1.19.1](https://github.com/cryptomator/cryptomator/releases/1.19.1) - 2026-03-12
### Security
* Cryptomamtor Hub Vaults: Fixed possible man-in-the-middle attack with tampered vault config (#4179, [GHSA-34rf-rwr3-7g43](https://github.com/cryptomator/cryptomator/security/advisories/GHSA-34rf-rwr3-7g43))
* Disallow unencrypted http connections to hub by default ([CVE-2026-32309](https://github.com/cryptomator/cryptomator/security/advisories/GHSA-vv33-h7qx-c264))
* Disallow loading of masterkey file from arbitrary paths (#4180, [CVE-2026-32310](https://github.com/cryptomator/cryptomator/security/advisories/GHSA-5phc-5pfx-hr52))
* Fixed not-configured plugin directory does not disable plugin search ([#4176](https://github.com/cryptomator/cryptomator/pull/4176))
### Added
* Trust on first use, adding new config properties `cryptomator.hub.allowedHosts` and `cryptomator.hub.enableTrustOnFirstUse` (#4179)
### Fixed
* Fixed Finder window opens twice when revealing vault on macOS ([#4177](https://github.com/cryptomator/cryptomator/pull/4177))
* Fixed app does not start due to secret service detection failure on Linux ([#4175](https://github.com/cryptomator/cryptomator/pull/4175))
### Changed
* Pin version of appimagetool([#4181](https://github.com/cryptomator/cryptomator/pull/4181))
* Updated translations
* Updated dependencies:
* `org.cryptomator:integrations-api` from 1.8.0-beta1 to 1.8.0
* `org.cryptomator:integrations-linux` from 1.7.0-beta4 to 1.7.0
* `org.cryptomator:integrations-mac` from 1.5.0-beta3 to 1.5.0
## [1.19.0](https://github.com/cryptomator/cryptomator/releases/tag/1.19.0) - 2026-03-09
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.18.0...HEAD)
### Added
* Self-Update Mechanism ([#3948](https://github.com/cryptomator/cryptomator/pull/3948))
@@ -69,7 +31,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
* Disable user defined app start config on Windows ([#4132](https://github.com/cryptomator/cryptomator/issues/4132))
* Disable plugin loading by default ([#4136](https://github.com/cryptomator/cryptomator/4136))
* Use JDK 25 ([#4031](https://github.com/cryptomator/cryptomator/pull/4031))
* Update JavaFX to 25.0.2 ([#4145](https://github.com/cryptomator/cryptomator/pull/4145))
* Update JavaFX to 25.0.2 ([#4145](https://github.com/cryptomator/cryptomator/pull/4145)))
* Updated translations
* Updated dependencies
* `ch.qos.logback:*` from 1.5.19 to 1.5.32

View File

@@ -82,7 +82,7 @@ ${JAVA_HOME}/bin/jpackage \
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2026 Skymatic GmbH" \
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--app-version "${VERSION}.${REVISION_NO}" \
@@ -99,7 +99,6 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \
--java-options "-XX:ErrorFile=/cryptomator/cryptomator_crash.log" \
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" \
--resource-dir ../resources
# transform AppDir
@@ -124,7 +123,7 @@ ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/meta
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
# load AppImageTool
curl -L https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage
curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage
chmod +x /tmp/appimagetool.AppImage
# create AppImage

View File

@@ -73,7 +73,6 @@
<url type="faq">https://community.cryptomator.org/c/kb/faq</url>
<url type="help">https://docs.cryptomator.org/</url>
<url type="translate">https://translate.cryptomator.org</url>
<url type="vcs-browser">https://github.com/cryptomator/cryptomator</url>
<developer id="de.skymatic">
<name>Skymatic GmbH</name>
@@ -84,15 +83,6 @@
</content_rating>
<releases>
<release date="2026-03-20" version="1.19.2">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.2</url>
</release>
<release date="2026-03-12" version="1.19.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.1</url>
</release>
<release date="2026-03-09" version="1.19.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.19.0</url>
</release>
<release date="2025-11-12" version="1.18.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.18.0</url>
</release>

View File

@@ -46,7 +46,7 @@ override_dh_auto_build:
--vendor "Skymatic GmbH" \
--java-options "--enable-preview" \
--java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
--copyright "(C) 2016 - 2026 Skymatic GmbH" \
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
--java-options "-Xss5m" \
--java-options "-Xmx256m" \
--java-options "-Dfile.encoding=\"utf-8\"" \
@@ -64,7 +64,6 @@ override_dh_auto_build:
--java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \
--java-options "-Dcryptomator.integrationsLinux.autoStartCmd=\"cryptomator\"" \
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" \
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
--resource-dir resources \
--verbose

View File

@@ -1,15 +0,0 @@
#!/bin/sh
# From: https://gitlab.gnome.org/GNOME/gnome-builder/-/blob/main/build-aux/flatpak/fusermount-wrapper.sh
if [ -z "$_FUSE_COMMFD" ]; then
FD_ARGS=
else
FD_ARGS="--env=_FUSE_COMMFD=${_FUSE_COMMFD} --forward-fd=${_FUSE_COMMFD}"
fi
if [ -e /proc/self/fd/3 ] && [ 3 != "$_FUSE_COMMFD" ]; then
FD_ARGS="$FD_ARGS --forward-fd=3"
fi
exec flatpak-spawn --host --forward-fd=1 --forward-fd=2 $FD_ARGS fusermount3 "$@"

View File

@@ -1,182 +0,0 @@
app-id: org.cryptomator.Cryptomator
command: cryptomator
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
separate-locales: false
finish-args:
# Required for FUSE, see https://github.com/flathub/org.cryptomator.Cryptomator/pull/68#issuecomment-1935136502
- --device=all
# Set the PATH environment variable in the application, as flatpak is resetting the shell's PATH
- --env=PATH=/app/bin/:/usr/bin/
# Allow filesystem access to the user's home dir
# Needed to manage vaults there
- --filesystem=home
# Reading system certificates
- --filesystem=host-etc:ro
# Allow access to the XDG data directory
# Needed to connect to KeePassXC's UNIX domain socket
- --filesystem=xdg-run/org.keepassxc.KeePassXC.BrowserServer
- --filesystem=xdg-run/app/org.keepassxc.KeePassXC/
# Share IPC namespace with the host, without it the X11 shared memory extension will not work
- --share=ipc
# Allow access to the network
- --share=network
# Show windows using X11
- --socket=x11
# Needed to reveal encrypted files
- --talk-name=org.freedesktop.FileManager1
# Run any command on the host
# Needed to spawn fusermount on the host
- --talk-name=org.freedesktop.Flatpak
# Allow desktop notifications
- --talk-name=org.freedesktop.Notifications
# Allow access to the GNOME secret service API and to talk to the GNOME keyring daemon
- --talk-name=org.freedesktop.secrets
- --talk-name=org.gnome.keyring
# Allow to talk to the KDE kwallet daemon
- --talk-name=org.kde.kwalletd5
- --talk-name=org.kde.kwalletd6
# Needed to talk to the gvfs daemons over D-Bus and list mounts using the GIO APIs
- --talk-name=org.gtk.vfs.*
# Allow access to appindicator icons
- --talk-name=org.ayatana
# Allow access to appindicator icons on KDE
- --talk-name=org.kde.StatusNotifierWatcher
cleanup:
- /include
- /lib/pkgconfig
modules:
- shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json
- name: libfuse
buildsystem: meson
config-opts:
- -Dexamples=false
- -Dinitscriptdir=
- -Duseroot=false
- -Dtests=false
# don't install rules on the host
- -Dudevrulesdir=/tmp/
sources:
- type: archive
url: https://github.com/libfuse/libfuse/releases/download/fuse-3.16.2/fuse-3.16.2.tar.gz
sha256: f797055d9296b275e981f5f62d4e32e089614fc253d1ef2985851025b8a0ce87
x-checker-data:
type: anitya
project-id: 861
url-template: https://github.com/libfuse/libfuse/releases/download/fuse-$version/fuse-$version.tar.gz
versions: {<: '3.17.0'}
- name: host-command-wrapper
buildsystem: simple
build-commands:
- install fusermount-wrapper.sh /app/bin/fusermount3
sources:
- type: file
path: build-aux/fusermount-wrapper.sh
- name: cryptomator
buildsystem: simple
build-options:
build-args:
- --share=network
env:
PATH: /app/bin:/usr/bin
MAVEN_OPTS: -Dmaven.repo.local=.m2/repository
JAVA_HOME: jdk
JMODS_PATH: jmods
VERSION: $FLATPAK_VERSION
REVISION_NO: '$FLATPAK_REVISION'
build-commands:
# Setup Java
- tar xvfz jdk.tar.gz --transform 's!^[^/]*!jdk!'
- mkdir jmods
- unzip -j openjfx.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d jmods
# Setup Maven
- mkdir maven
- tar xf maven.tar.gz --strip-components=1 --exclude=jansi-native --directory=maven
# Build project
- maven/bin/mvn clean package -DskipTests -P"linux-$(uname -m)"
- cp target/cryptomator-*.jar target/mods
- cd target
- $JAVA_HOME/bin/jlink
--output runtime
--module-path $JMODS_PATH
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
--no-header-files
--no-man-pages
--strip-debug
--compress=zip-0
- $JAVA_HOME/bin/jpackage
--type app-image
--runtime-image runtime
--input target/libs
--module-path target/mods
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
--dest .
--name Cryptomator
--vendor 'Skymatic GmbH'
--copyright '(C) 2016 - 2026 Skymatic GmbH'
--java-options '--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator'
--java-options "--sun-misc-unsafe-memory-access=allow"
--java-options '-Xss5m'
--java-options '-Xmx256m'
--java-options '-Dfile.encoding='utf-8''
--java-options '-Djava.net.useSystemProxies=true'
--java-options "-Dcryptomator.appVersion='${VERSION}'"
--java-options "-Dcryptomator.buildNumber='flatpak-${REVISION_NO}'"
--java-options '-Dcryptomator.ipcSocketPath='@{userhome}/.config/Cryptomator/ipc.socket''
--java-options '-Dcryptomator.adminConfigPath='/run/host/etc/cryptomator/config.properties''
--java-options '-Dcryptomator.logDir='@{userhome}/.local/share/Cryptomator/logs''
--java-options '-Dcryptomator.mountPointsDir='@{userhome}/.local/share/Cryptomator/mnt''
--java-options '-Dcryptomator.pluginDir='@{userhome}/.local/share/Cryptomator/plugins''
--java-options '-Dcryptomator.p12Path='@{userhome}/.config/Cryptomator/key.p12''
--java-options '-Dcryptomator.settingsPath='@{userhome}/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json''
--java-options '-Dcryptomator.showTrayIcon=true'
--java-options '-Dcryptomator.updateMechanism=org.cryptomator.linux.update.FlatpakUpdater'
--java-options '-Dcryptomator.networking.truststore.p12Path='/run/host/etc/cryptomator/certs.p12''
--java-options '-Dcryptomator.hub.enableTrustOnFirstUse=true'
--app-version "${VERSION}.${REVISION_NO}"
--verbose
- cp -R Cryptomator /app/
- ln -s /app/Cryptomator/bin/Cryptomator /app/bin/cryptomator
- cp -R /app/lib/* /app/Cryptomator/lib/app/
- install -D -m0644 -t /app/share/applications/ dist/linux/common/org.cryptomator.Cryptomator.desktop
- install -D -m0644 -t /app/share/icons/hicolor/scalable/apps/ dist/linux/common/org.cryptomator.Cryptomator.svg
- install -D -m0644 -T dist/linux/common/org.cryptomator.Cryptomator.tray.svg /app/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-symbolic.svg
- install -D -m0644 -T dist/linux/common/org.cryptomator.Cryptomator.tray-unlocked.svg /app/share/icons/hicolor/symbolic/apps/org.cryptomator.Cryptomator.tray-unlocked-symbolic.svg
- install -D -m0644 -t /app/share/metainfo/ dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml
sources:
- $CRYPTOMATOR_SOURCE
- type: file
dest-filename: jdk.tar.gz
only-arches:
- x86_64
url: https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_x64_linux_hotspot_25.0.2_10.tar.gz
sha512: 29043fde119a031c2ca8d57aed445fedd9e7f74608fcdc7a809076ba84cfd1c31f08de2ecccf352e159fdcd1cae172395ed46363007552ff242057826c81ab3a
- type: file
dest-filename: jdk.tar.gz
only-arches:
- aarch64
url: https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.2%2B10/OpenJDK25U-jdk_aarch64_linux_hotspot_25.0.2_10.tar.gz
sha512: f1d3ccec3e1f1bed9d632f14b9223709d6e5c2e0d922125d068870dd3016492a2ca8f08924d4a9d0dc5eb2159fa09efee366a748fd0093475baf29e5c70c781a
- type: file
dest-filename: openjfx.zip
only-arches:
- x86_64
url: https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-x64_bin-jmods.zip
sha512: 21f550217101c513f9eb1d7947eba30cb79618238e6539ce770e54e84b01574cdaeba40af602391145f163dd8e43e3794395467413152f13ffffeff948b0ca1b
- type: file
dest-filename: openjfx.zip
only-arches:
- aarch64
url: https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_linux-aarch64_bin-jmods.zip
sha512: a9268409b3803e386490bf1319d0f0a14173cebe862c12254cd51b430ee0a297437d9e38d5ebeae0da8899be898b312b103330d09dcfd3e63c1e7d15f2f14311
- type: file
dest-filename: maven.tar.gz
url: https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.13/apache-maven-3.9.13-bin.tar.gz
sha512: d9ccd44ba2991586e359c29eb86780ae8ff4ec1b88b0b8af3af074803472690cf2017782a9c4401343c62cbcd056231db9612e1e551cbd9747c21746d732c015
x-checker-data:
type: anitya
project-id: 1894
stable-only: true
url-template: https://repo1.maven.org/maven2/org/apache/maven/apache-maven/$version/apache-maven-$version-bin.tar.gz
versions: {<: '4.0'}

View File

@@ -94,7 +94,6 @@ build() {
--java-options "-Dcryptomator.p12Path=\"@{userhome}/.config/Cryptomator/key.p12\"" \
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" \
--java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" \
--app-version "${pkgver//_*/}" \
--verbose
}

View File

@@ -24,7 +24,7 @@ rm -rf runtime dmg *.app *.dmg
# set variables
APP_NAME="Cryptomator"
VENDOR="Skymatic GmbH"
COPYRIGHT_YEARS="2016 - 2026"
COPYRIGHT_YEARS="2016 - 2025"
PACKAGE_IDENTIFIER="org.cryptomator"
MAIN_JAR_GLOB="cryptomator-*.jar"
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
@@ -125,7 +125,6 @@ ${JAVA_HOME}/bin/jpackage \
--java-options "-Dcryptomator.showTrayIcon=true" \
--java-options "-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism" \
--java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \
--java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" \
--mac-package-identifier ${PACKAGE_IDENTIFIER} \
--resource-dir ../resources

1
dist/win/build.ps1 vendored
View File

@@ -167,7 +167,6 @@ $javaOptions = @(
"--java-options", "-Dcryptomator.showTrayIcon=true"
"--java-options", "-Dcryptomator.buildNumber=`"msi-$revisionNo`""
"--java-options", "-Dcryptomator.disableUpdateCheck=false"
"--java-options", "-Dcryptomator.hub.enableTrustOnFirstUse=true"
)

View File

@@ -4,18 +4,15 @@
:: This file must be located in the INSTALLDIR
set "LOOPBACK_ALIAS=%1"
set "ACTION=%2"
if "%ACTION%"=="" set "ACTION=install"
:: Log for debugging
echo LOOPBACK_ALIAS=%LOOPBACK_ALIAS%
echo ACTION=%ACTION%
:: Change to INSTALLDIR
cd %~dp0
:: Execute the PowerShell script
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File .\patchWebDAV.ps1^
-LoopbackAlias %LOOPBACK_ALIAS% -Action %ACTION%
-LoopbackAlias %LOOPBACK_ALIAS%
:: Return the exit code from PowerShell
exit /b %ERRORLEVEL%

View File

@@ -1,17 +1,15 @@
#Requires -RunAsAdministrator
Param(
[Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias,
[string] $Action = "install"
[Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias
)
New-Variable -Name "sysdir" -Value ([Environment]::SystemDirectory) -Option Constant -Scope Global
New-Variable -Name "hostsFile" -Value "$sysdir\drivers\etc\hosts" -Option Constant -Scope Global
# Adds an alias for 127.0.0.1 to the hosts file
function Add-AliasToHost {
param (
[string]$LoopbackAlias
)
$sysdir = [Environment]::SystemDirectory
$hostsFile = "$sysdir\drivers\etc\hosts"
$aliasLine = "127.0.0.1 $LoopbackAlias"
foreach ($line in Get-Content $hostsFile) {
@@ -20,26 +18,9 @@ function Add-AliasToHost {
}
}
$content = Get-Content $hostsFile
$content += "`r`n$aliasLine"
$content | Set-Content "$hostsFile.tmp" -Encoding ascii
Move-Item "$hostsFile.tmp" $hostsFile -Force
Add-Content -Path $hostsFile -Encoding ascii -Value "`r`n$aliasLine"
}
# Removes an alias for 127.0.0.1 from the hosts file
function Remove-AliasFromHost {
param (
[string]$LoopbackAlias
)
$aliasLine = "127.0.0.1 $LoopbackAlias"
$content = Get-Content $hostsFile
$newContent = $content | Where-Object { $_ -ne $aliasLine }
$newContent | Set-Content "$hostsFile.tmp" -Encoding ascii
Move-Item "$hostsFile.tmp" $hostsFile -Force
}
# Sets in the registry the webclient file size limit to the maximum value
function Set-WebDAVFileSizeLimit {
@@ -73,20 +54,14 @@ function Edit-ProviderOrder {
New-ItemProperty -Path $RegistryPath -Name $Name -Value $UpdatedOrder -PropertyType String -Force | Out-Null
}
if ($Action -eq "install") {
Add-AliasToHost $LoopbackAlias
Write-Output 'Ensured alias exists in hosts file'
Set-WebDAVFileSizeLimit
Write-Output 'Set WebDAV file size limit'
Add-AliasToHost $LoopbackAlias
Write-Output 'Ensured alias exists in hosts file'
Edit-ProviderOrder
Write-Output 'Ensured correct provider order'
} elseif ($Action -eq "uninstall") {
Remove-AliasFromHost $LoopbackAlias
Write-Output 'Ensured alias removed from hosts file'
} else {
Write-Error "Invalid action: $Action. Only 'install' or 'uninstall' are valid."
}
Set-WebDAVFileSizeLimit
Write-Output 'Set WebDAV file size limit'
Edit-ProviderOrder
Write-Output 'Ensured correct provider order'
exit 0

View File

@@ -109,7 +109,7 @@
</ns0:CreateFolder>
</ns0:Component>
<ns0:Component Id="AdminConfigFile" NeverOverwrite="yes" Permanent="yes">
<ns0:File Id="EmptyAdminConfig" Source="$(env.JP_WIXWIZARD_RESOURCES)\..\..\common\config.properties" Name="config.properties" KeyPath="yes">
<ns0:File Id="EmptyAdminConfig" Source="$(env.JP_WIXWIZARD_RESOURCES)\..\..\common\cryptomator.config" Name="cryptomator.config" KeyPath="yes">
<util:PermissionEx User="SYSTEM" GenericAll="yes"/>
<util:PermissionEx User="Administrators" GenericAll="yes"/>
<util:PermissionEx User="Users" GenericRead="yes" GenericExecute="yes"/>
@@ -158,13 +158,9 @@
<ns0:CustomAction Id="DisableUserConfig" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- WebDAV patches -->
<ns0:SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot; install" Sequence="execute" Before="PatchWebDAV" />
<ns0:SetProperty Id="PatchWebDAV" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot;" Sequence="execute" Before="PatchWebDAV" />
<ns0:CustomAction Id="PatchWebDAV" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- WebDAV patches (Uninstall) -->
<ns0:SetProperty Id="PatchWebDAVUninstall" Value="&quot;[INSTALLDIR]patchWebDAV.bat&quot; &quot;$(var.LoopbackAlias)&quot; uninstall" Sequence="execute" Before="PatchWebDAVUninstall" />
<ns0:CustomAction Id="PatchWebDAVUninstall" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<!-- Update check configuration -->
<ns0:SetProperty Id="PatchUpdateCheck" Value="&quot;[INSTALLDIR]patchUpdateCheck.bat&quot; &quot;[DISABLEUPDATECHECK]&quot;" Sequence="execute" Before="PatchUpdateCheck" />
<ns0:CustomAction Id="PatchUpdateCheck" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
@@ -218,8 +214,9 @@
<ns0:RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to Wix4CloseApplications_* -->
<ns0:Custom Action="DisableUserConfig" After="InstallFiles" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<!-- Skip action on uninstall -->
<!-- TODO: don't skip action, but remove cryptomator alias from hosts file -->
<ns0:Custom Action="PatchWebDAV" After="DisableUserConfig" Condition="NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
<ns0:Custom Action="PatchWebDAVUninstall" Before="RemoveFiles" Condition="Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE" />
<!-- Configure update check setting if property is provided -->
<ns0:Custom Action="PatchUpdateCheck" After="PatchWebDAV" Condition="DISABLEUPDATECHECK AND NOT (Installed AND (NOT REINSTALL) AND (NOT UPGRADINGPRODUCTCODE) AND REMOVE)"/>
</ns0:InstallExecuteSequence>
@@ -231,4 +228,4 @@
<ns0:WixVariable Id="WixUIBannerBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\banner.bmp" />
<ns0:WixVariable Id="WixUIDialogBmp" Value="$(env.JP_WIXWIZARD_RESOURCES)\background.bmp" />
</ns0:Package>
</ns0:Wix>
</ns0:Wix>

56
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.20.0-SNAPSHOT</version>
<version>1.19.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -35,10 +35,10 @@
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.10.0</cryptomator.cryptofs.version>
<cryptomator.cryptolib.version>2.2.2</cryptomator.cryptolib.version>
<cryptomator.integrations.version>1.8.0</cryptomator.integrations.version>
<cryptomator.integrations.version>1.8.0-beta1</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.6.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.5.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0</cryptomator.integrations.linux.version>
<cryptomator.integrations.mac.version>1.5.0-beta3</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.7.0-beta4</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>6.0.1</cryptomator.fuse.version>
<cryptomator.webdav.version>3.0.1</cryptomator.webdav.version>
<cryptomator.webdav-servlet.version>1.2.12</cryptomator.webdav-servlet.version>
@@ -527,57 +527,11 @@
</profile>
<profile>
<id>linux-aarch64</id>
<id>linux</id>
<activation>
<os>
<family>unix</family>
<name>Linux</name>
<arch>aarch64</arch>
</os>
<property>
<name>idea.version</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>integrations-linux</artifactId>
<version>${cryptomator.integrations.linux.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.version}</version>
<classifier>linux-aarch64</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>linux-aarch64</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<classifier>linux-aarch64</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
<classifier>linux-aarch64</classifier>
</dependency>
</dependencies>
</profile>
<profile>
<id>linux-x86_64</id>
<activation>
<os>
<family>unix</family>
<name>Linux</name>
<arch>amd64</arch>
</os>
<property>
<name>idea.version</name>

View File

@@ -9,13 +9,10 @@ import org.slf4j.LoggerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@@ -23,22 +20,22 @@ public class Environment {
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
private static final int DEFAULT_MIN_PW_LENGTH = 8;
public static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
public static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
public static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
public static final String WINDOWS_HELLO_KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.windowsHelloKeychainPaths";
public static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
public static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
public static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
public static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
public static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
public static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
public static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
public static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
public static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
public static final String DISABLE_UPDATE_CHECK_PROP_NAME = "cryptomator.disableUpdateCheck";
public static final String HUB_ALLOWED_HOSTS_PROP_NAME = "cryptomator.hub.allowedHosts";
public static final String HUB_TOFU_PROP_NAME = "cryptomator.hub.enableTrustOnFirstUse";
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
private static final String WINDOWS_HELLO_KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.windowsHelloKeychainPaths";
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
private static final String MOUNTPOINT_DIR_PROP_NAME = "cryptomator.mountPointsDir";
private static final String MIN_PW_LENGTH_PROP_NAME = "cryptomator.minPwLength";
private static final String APP_VERSION_PROP_NAME = "cryptomator.appVersion";
private static final String BUILD_NUMBER_PROP_NAME = "cryptomator.buildNumber";
private static final String PLUGIN_DIR_PROP_NAME = "cryptomator.pluginDir";
private static final String TRAY_ICON_PROP_NAME = "cryptomator.showTrayIcon";
private static final String DISABLE_UPDATE_CHECK_PROP_NAME = "cryptomator.disableUpdateCheck";
private static final String LICENSE_CHAIN_REQUIRED_CN_PROP_NAME = "cryptomator.licenseChainRequiredCn";
private static final String DEFAULT_LICENSE_CHAIN_REQUIRED_CN = "License Intermediate CA (Prod)";
private Environment() {}
@@ -62,8 +59,7 @@ public class Environment {
logCryptomatorSystemProperty(PLUGIN_DIR_PROP_NAME);
logCryptomatorSystemProperty(TRAY_ICON_PROP_NAME);
logCryptomatorSystemProperty(DISABLE_UPDATE_CHECK_PROP_NAME);
logCryptomatorSystemProperty(HUB_ALLOWED_HOSTS_PROP_NAME);
logCryptomatorSystemProperty(HUB_TOFU_PROP_NAME);
logCryptomatorSystemProperty(LICENSE_CHAIN_REQUIRED_CN_PROP_NAME);
}
public static Environment getInstance() {
@@ -152,16 +148,8 @@ public class Environment {
return Boolean.getBoolean(DISABLE_UPDATE_CHECK_PROP_NAME);
}
public Set<String> hubAllowedHosts() {
var allowedHubHostsString = System.getProperty(HUB_ALLOWED_HOSTS_PROP_NAME, "");
return Arrays.stream(allowedHubHostsString.split(","))
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.collect(Collectors.toUnmodifiableSet());
}
public boolean hubTrustOnFirstUse() {
return Boolean.getBoolean(HUB_TOFU_PROP_NAME);
public String getLicenseChainRequiredCn() {
return System.getProperty(LICENSE_CHAIN_REQUIRED_CN_PROP_NAME, DEFAULT_LICENSE_CHAIN_REQUIRED_CN);
}
private Optional<Path> getPath(String propertyName) {

View File

@@ -2,31 +2,60 @@ package org.cryptomator.common;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.google.common.io.BaseEncoding;
import org.jetbrains.annotations.VisibleForTesting;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertPathValidatorException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import java.util.Optional;
@Singleton
class LicenseChecker {
private final JWTVerifier verifier;
private static final String LICENSE_ROOT_CERTIFICATE = """
-----BEGIN CERTIFICATE-----
MIIBqDCCAVqgAwIBAgIUKNImuR2JD+NyAWaYzb8V8w8SdFwwBQYDK2VwMD8xCzAJ
BgNVBAYTAkRFMRYwFAYDVQQKDA1Ta3ltYXRpYyBHbWJIMRgwFgYDVQQDDA9MaWNl
bnNlIFJvb3QgQ0EwIBcNMjYwMjI2MTMzMTQxWhgPMjA3NjAyMTQxMzMxNDFaMD8x
CzAJBgNVBAYTAkRFMRYwFAYDVQQKDA1Ta3ltYXRpYyBHbWJIMRgwFgYDVQQDDA9M
aWNlbnNlIFJvb3QgQ0EwKjAFBgMrZXADIQCOyUIv+3Ust66VWJ8yH5ruJGxyZC1u
LK2Yxb+ZtPGAQKNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFGhKUh0jCta2wXzxrUldIBqB4Bz3MB8GA1UdIwQYMBaAFGhK
Uh0jCta2wXzxrUldIBqB4Bz3MAUGAytlcANBAOERjFKpGnjxH1nh2u5lsCjX65zz
XisC7XFaZQikVLKzHK+YTIusi3x7dGCFBjO/m3ieQpt7BsaPo0lLL719pQ8=
-----END CERTIFICATE-----
""";
private final ECPublicKey legacyPublicKey;
private final X509Certificate rootCertificate;
private final String requiredChainCn;
@Inject
public LicenseChecker(@Named("licensePublicKey") String pemEncodedPublicKey) {
Algorithm algorithm = Algorithm.ECDSA512(decodePublicKey(pemEncodedPublicKey), null);
this.verifier = JWT.require(algorithm).build();
public LicenseChecker(@Named("licensePublicKey") String legacyLicensePublicKey, Environment environment) {
this(legacyLicensePublicKey, LICENSE_ROOT_CERTIFICATE, environment.getLicenseChainRequiredCn());
}
@VisibleForTesting
LicenseChecker(String legacyLicensePublicKey, String trustedRootCert, String requiredChainCn) {
this.legacyPublicKey = decodePublicKey(legacyLicensePublicKey);
this.rootCertificate = X509Helper.parsePemCertificate(trustedRootCert);
this.requiredChainCn = requiredChainCn;
}
private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) {
@@ -47,10 +76,44 @@ class LicenseChecker {
public Optional<DecodedJWT> check(String licenseKey) {
try {
DecodedJWT decodedJwt = JWT.decode(licenseKey);
Claim x5cClaim = decodedJwt.getHeaderClaim("x5c");
ECPublicKey signingKey;
if (x5cClaim == null || x5cClaim.isMissing()) {
signingKey = this.legacyPublicKey;
} else {
var certChain = verifyChain(x5cClaim);
signingKey = asEcPublicKey(certChain.getFirst().getPublicKey());
}
JWTVerifier verifier = JWT.require(Algorithm.ECDSA512(signingKey, null)).build();
return Optional.of(verifier.verify(licenseKey));
} catch (JWTVerificationException exception) {
} catch (JWTVerificationException | GeneralSecurityException e) {
return Optional.empty();
}
}
private List<X509Certificate> verifyChain(Claim x5cClaim) throws GeneralSecurityException {
List<String> x5cEntries = x5cClaim.asList(String.class);
if (x5cEntries == null || x5cEntries.isEmpty()) {
throw new CertPathValidatorException("x5c claim is empty.");
}
List<X509Certificate> certChain = X509Helper.parseX5cCertificateChain(x5cEntries);
boolean containsRequiredCn = certChain.stream() //
.flatMap(cert -> X509Helper.extractCommonName(cert).stream()) //
.anyMatch(requiredChainCn::equals);
if (!containsRequiredCn) {
throw new CertPathValidatorException("x5c certificate chain does not contain required CN " + requiredChainCn);
}
X509Helper.validateChain(certChain, rootCertificate);
return certChain;
}
private static ECPublicKey asEcPublicKey(PublicKey publicKey) {
if (publicKey instanceof ECPublicKey ecPublicKey) {
return ecPublicKey;
} else {
throw new IllegalArgumentException("Leaf certificate key is not an EC public key.");
}
}
}

View File

@@ -0,0 +1,121 @@
package org.cryptomator.common;
import com.google.common.io.BaseEncoding;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.security.GeneralSecurityException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateFactory;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.cert.PKIXParameters;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
final class X509Helper {
private static final String CERT_BEGIN = "-----BEGIN CERTIFICATE-----";
private static final String CERT_END = "-----END CERTIFICATE-----";
private X509Helper() {
}
static X509Certificate parsePemCertificate(String pemCertificate) {
String base64 = pemCertificate.replace(CERT_BEGIN, "").replace(CERT_END, "").replaceAll("\\s", "");
byte[] der = BaseEncoding.base64().decode(base64);
return parseDerCertificate(der);
}
static List<X509Certificate> parseX5cCertificateChain(List<String> x5cEntries) {
List<X509Certificate> certificates = new ArrayList<>(x5cEntries.size());
for (String x5cEntry : x5cEntries) {
byte[] der = BaseEncoding.base64().decode(x5cEntry);
certificates.add(parseDerCertificate(der));
}
return certificates;
}
static void validateChain(List<X509Certificate> certificateChain, X509Certificate rootCertificate) throws GeneralSecurityException {
if (certificateChain.isEmpty()) {
throw new IllegalArgumentException("Certificate chain must not be empty.");
}
List<X509Certificate> certPathCertificates = new ArrayList<>(certificateChain);
if (rootCertificate.equals(certPathCertificates.get(certPathCertificates.size() - 1))) {
certPathCertificates.remove(certPathCertificates.size() - 1);
}
if (certPathCertificates.isEmpty()) {
throw new IllegalArgumentException("Certificate path must contain at least one non-root certificate.");
}
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(certPathCertificates);
PKIXParameters params = new PKIXParameters(Set.of(new TrustAnchor(rootCertificate, null)));
params.setRevocationEnabled(false);
CertPathValidator.getInstance("PKIX").validate(certPath, params);
}
static Optional<String> extractCommonName(X509Certificate cert) {
String rfc2253Name = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
for (String rdn : splitRdns(rfc2253Name)) {
int equalsPos = rdn.indexOf('=');
if (equalsPos > 0 && "CN".equalsIgnoreCase(rdn.substring(0, equalsPos).trim())) {
return Optional.of(unescapeRdnValue(rdn.substring(equalsPos + 1).trim()));
}
}
return Optional.empty();
}
private static List<String> splitRdns(String distinguishedName) {
List<String> rdns = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean escaped = false;
for (int i = 0; i < distinguishedName.length(); i++) {
char c = distinguishedName.charAt(i);
if (escaped) {
current.append(c);
escaped = false;
} else if (c == '\\') {
current.append(c);
escaped = true;
} else if (c == ',') {
rdns.add(current.toString());
current.setLength(0);
} else {
current.append(c);
}
}
rdns.add(current.toString());
return rdns;
}
private static String unescapeRdnValue(String value) {
StringBuilder unescaped = new StringBuilder(value.length());
boolean escaped = false;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (escaped) {
unescaped.append(c);
escaped = false;
} else if (c == '\\') {
escaped = true;
} else {
unescaped.append(c);
}
}
return unescaped.toString();
}
private static X509Certificate parseDerCertificate(byte[] derEncodedCertificate) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(derEncodedCertificate));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid certificate.", e);
}
}
}

View File

@@ -24,12 +24,9 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.geometry.NodeOrientation;
import java.nio.file.Path;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
public class Settings {
@@ -81,7 +78,6 @@ public class Settings {
public final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
public final ObjectProperty<Path> previouslyUsedVaultDirectory;
public final StringProperty lastUpdateAttemptedByVersion;
public final ObservableSet<String> trustedHosts;
public static Settings create(SettingsProvider provider, Environment env) {
var defaults = new SettingsJson();
@@ -122,7 +118,6 @@ public class Settings {
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
this.previouslyUsedVaultDirectory = new SimpleObjectProperty<>(this, "previouslyUsedVaultDirectory", json.previouslyUsedVaultDirectory);
this.lastUpdateAttemptedByVersion = new SimpleStringProperty(this, "lastUpdateAttemptedByVersion", json.lastUpdateAttemptedByVersion);
this.trustedHosts = FXCollections.observableSet(json.trustedHosts);
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
@@ -154,7 +149,6 @@ public class Settings {
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
previouslyUsedVaultDirectory.addListener(this::somethingChanged);
lastUpdateAttemptedByVersion.addListener(this::somethingChanged);
trustedHosts.addListener(this::somethingChanged);
}
@SuppressWarnings("deprecation")
@@ -213,7 +207,6 @@ public class Settings {
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
json.previouslyUsedVaultDirectory = previouslyUsedVaultDirectory.get();
json.lastUpdateAttemptedByVersion = lastUpdateAttemptedByVersion.get();
json.trustedHosts = Set.copyOf(trustedHosts);
return json;
}

View File

@@ -4,23 +4,17 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class SettingsJson {
@JsonProperty("directories")
@JsonSetter(nulls = Nulls.AS_EMPTY)
List<VaultSettingsJson> directories = new ArrayList<>();
List<VaultSettingsJson> directories = List.of();
@JsonProperty("writtenByVersion")
String writtenByVersion;
@@ -105,8 +99,4 @@ class SettingsJson {
@JsonProperty("lastUpdateAttemptedByVersion")
String lastUpdateAttemptedByVersion;
@JsonProperty("trustedHosts")
@JsonSetter(nulls = Nulls.AS_EMPTY)
Set<String> trustedHosts = new HashSet<>();
}

View File

@@ -27,8 +27,6 @@ import java.util.Set;
* <li>cryptomator.p12Path</li>
* <li>cryptomator.mountPointsDir</li>
* <li>cryptomator.disableUpdateCheck</li>
* <li>cryptomator.hub.allowedHosts</li>
* <li>cryptomator.hub.enableTrustOnFirstUse</li>
* </ul>
*
* @see Properties
@@ -44,9 +42,7 @@ class AdminPropertiesFactory {
"cryptomator.pluginDir", //
"cryptomator.p12Path", //
"cryptomator.mountPointsDir", //
"cryptomator.disableUpdateCheck", //
"cryptomator.hub.allowedHosts", //
"cryptomator.hub.enableTrustOnFirstUse");
"cryptomator.disableUpdateCheck");
/**

View File

@@ -19,7 +19,6 @@ public enum FxmlFile {
HEALTH_START("/fxml/health_start.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
HUB_NO_KEYCHAIN("/fxml/hub_no_keychain.fxml"), //
HUB_CHECK_HOST_TRUST("/fxml/hub_check_host_trust.fxml"), //
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
@@ -30,7 +29,6 @@ public enum FxmlFile {
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
HUB_UNTRUSTED_HOST("/fxml/hub_untrusted_host.fxml"), //
HUB_REQUIRE_ACCOUNT_INIT("/fxml/hub_require_account_init.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //

View File

@@ -58,6 +58,8 @@ public class DecryptFileNamesViewController implements FxController {
private final Stage window;
private final Vault vault;
private final ResourceBundle resourceBundle;
private final List<Path> initialList;
@FXML
public TableColumn<CipherAndCleartext, String> ciphertextColumn;
@FXML
@@ -66,11 +68,12 @@ public class DecryptFileNamesViewController implements FxController {
public TableView<CipherAndCleartext> cipherToCleartextTable;
@Inject
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.resourceBundle = resourceBundle;
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
this.initialList = pathsToDecrypt;
}
@FXML
@@ -94,7 +97,8 @@ public class DecryptFileNamesViewController implements FxController {
});
cipherToCleartextTable.setOnDragDropped(event -> {
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
decrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
cipherToCleartextTable.setItems(mapping);
}
});
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
@@ -120,7 +124,9 @@ public class DecryptFileNamesViewController implements FxController {
});
}
});
window.setOnHidden(_ -> mapping.clear());
if (!initialList.isEmpty()) {
checkAndDecrypt(initialList);
}
}
private void copySingleCelltoClipboard() {
@@ -143,18 +149,10 @@ public class DecryptFileNamesViewController implements FxController {
fileChooser.setInitialDirectory(vault.getPath().toFile());
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
if (ciphertextNodes != null) {
decrypt(ciphertextNodes.stream().map(File::toPath).toList());
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList());
}
}
public void decrypt(List<Path> pathsToDecrypt) {
if (pathsToDecrypt.isEmpty()) {
return;
}
checkAndDecrypt(pathsToDecrypt);
cipherToCleartextTable.setItems(mapping);
}
private void checkAndDecrypt(List<Path> pathsToDecrypt) {
mapping.clear();
//Assumption: All files are in the same directory

View File

@@ -28,28 +28,23 @@ public interface DecryptNameComponent {
@FxmlScene(FxmlFile.DECRYPTNAMES)
Lazy<Scene> decryptNamesView();
DecryptFileNamesViewController controller();
@DecryptNameWindow
Vault vault();
default void showDecryptFileNameWindow(List<Path> pathsToDecrypt) {
default void showDecryptFileNameWindow() {
Stage s = window();
s.setScene(decryptNamesView().get());
s.sizeToScene();
if (vault().isUnlocked()) {
controller().decrypt(pathsToDecrypt);
s.show();
s.requestFocus();
} else {
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
s.close();
}
}
@Subcomponent.Factory
interface Factory {
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner);
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt);
}
}

View File

@@ -64,6 +64,17 @@ abstract class FxApplicationModule {
return builder.build();
}
@Provides
@FxApplicationScoped
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
static PreferencesComponent providePreferencesComponent(PreferencesComponent.Builder builder) {
return builder.build();
}
@Provides
@FxApplicationScoped
@@ -77,4 +88,10 @@ abstract class FxApplicationModule {
return factory.create();
}
@Provides
@FxApplicationScoped
static NotificationComponent provideNotificationComponent(NotificationComponent.Factory factory) {
return factory.create();
}
}

View File

@@ -39,7 +39,6 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@FxApplicationScoped
public class FxApplicationWindows {
@@ -48,15 +47,15 @@ public class FxApplicationWindows {
private final Stage primaryStage;
private final Optional<TrayIntegrationProvider> trayIntegration;
private final CachedLazy<MainWindowComponent> mainWindow;
private final CachedLazy<PreferencesComponent> preferencesWindow;
private final Lazy<MainWindowComponent> mainWindow;
private final Lazy<PreferencesComponent> preferencesWindow;
private final QuitComponent.Builder quitWindowBuilder;
private final UnlockComponent.Factory unlockWorkflowFactory;
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final CachedLazy<EventViewComponent> eventViewWindow;
private final CachedLazy<NotificationComponent> notificationWindow;
private final Lazy<EventViewComponent> eventViewWindow;
private final Lazy<NotificationComponent> notificationWindow;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
@@ -66,8 +65,8 @@ public class FxApplicationWindows {
@Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, //
Optional<TrayIntegrationProvider> trayIntegration, //
MainWindowComponent.Builder mainWindowBuilder, //
PreferencesComponent.Builder preferencesWindowBuilder, //
Lazy<MainWindowComponent> mainWindow, //
Lazy<PreferencesComponent> preferencesWindow, //
QuitComponent.Builder quitWindowBuilder, //
UnlockComponent.Factory unlockWorkflowFactory, //
UpdateReminderComponent.Factory updateReminderWindowFactory, //
@@ -75,21 +74,21 @@ public class FxApplicationWindows {
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
EventViewComponent.Factory eventViewWindowFactory, //
NotificationComponent.Factory notificationWindowFactory, //
Lazy<EventViewComponent> eventViewWindow, //
Lazy<NotificationComponent> notificationWindow,
ExecutorService executor, //
Dialogs dialogs) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
this.mainWindow = new CachedLazy<>(mainWindowBuilder::build);
this.preferencesWindow = new CachedLazy<>(preferencesWindowBuilder::build);
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.quitWindowBuilder = quitWindowBuilder;
this.unlockWorkflowFactory = unlockWorkflowFactory;
this.updateReminderWindowFactory = updateReminderWindowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.eventViewWindow = new CachedLazy<>(eventViewWindowFactory::create);
this.notificationWindow = new CachedLazy<>(notificationWindowFactory::create);
this.eventViewWindow = eventViewWindow;
this.notificationWindow = notificationWindow;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
@@ -219,29 +218,4 @@ public class FxApplicationWindows {
LOG.error("Failed to display stage", error);
}
}
private static class CachedLazy<T> implements Lazy<T> {
private final Supplier<T> supplier;
private volatile T instance = null;
public CachedLazy(Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
public T get() {
T value = instance;
if (value == null) {
synchronized (this) {
value = instance;
if (value == null) {
value = supplier.get();
instance = value;
}
}
}
return instance;
}
}
}

View File

@@ -1,5 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
@@ -11,6 +12,8 @@ import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.WorkerStateEvent;

View File

@@ -1,179 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import dagger.Lazy;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.Settings;
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.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
import java.net.URI;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
@KeyLoadingScoped
public class CheckHostTrustController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(CheckHostTrustController.class);
private static final String CHECK_KEY = "hub.checkHostTrust.message.check";
private static final String ASK_SINGULAR_KEY = "hub.checkHostTrust.message.ask";
private static final String ASK_PLURAL_KEY = "hub.checkHostTrust.message.ask.plural";
private static final String TRUSTED_CRYPTOMATOR_CLOUD_DOMAIN = ".cryptomator.cloud";
private final Stage window;
private final HubConfig hubConfig;
private final URI canonicalHubUri;
private final URI canonicalAuthUri;
private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> untrustedHostScene;
private final CompletableFuture<ReceivedKey> result;
private final Settings settings;
private final Environment env;
private final ResourceBundle resourceBundle;
private final SortedSet<String> hostnames;
private final StringProperty messageLabel;
@FXML
private TextFlow hostnamesFlow;
@Inject
public CheckHostTrustController(@KeyLoading Stage window, //
HubConfig hubConfig, //
@FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, //
@FxmlScene(FxmlFile.HUB_UNTRUSTED_HOST) Lazy<Scene> untrustedHostScene, //
CompletableFuture<ReceivedKey> result, //
Settings settings, //
Environment env, //
ResourceBundle resourceBundle) {
this.window = window;
this.hubConfig = hubConfig;
this.canonicalHubUri = hubConfig.getApiBaseUrl();
this.canonicalAuthUri = URI.create(hubConfig.authEndpoint);
this.authFlowScene = authFlowScene;
this.untrustedHostScene = untrustedHostScene;
this.result = result;
this.settings = settings;
this.env = env;
this.resourceBundle = resourceBundle;
this.hostnames = new TreeSet<>();
this.messageLabel = new SimpleStringProperty(resourceBundle.getString(CHECK_KEY));
}
@FXML
public void initialize() {
if (!isConsistentHubConfig()) {
LOG.warn("Inconsistent hub config detected. Denying access to protect the user.");
deny();
} else if (isAllCryptomatorCloud() && !isAnyHttpHost()) {
trust(); // trust *.cryptomator.cloud by default, domain is owned by Cryptomator maintainers
} else if (containsAllowedHosts(env.hubAllowedHosts())) {
trust(); // trust hosts explicitly allowlisted via system property
} else if (isAnyHttpHost() && !isAllLocalhost()) {
LOG.warn("Denying attempt to connect to hub instance via unencrypted HTTP.");
deny(); // never trust http hosts except for local testing
} else if (env.hubTrustOnFirstUse() && containsAllowedHosts(settings.trustedHosts)) {
trust(); // trust hosts previously allowlisted by the user
} else if (env.hubTrustOnFirstUse()) {
hostnames.add(getAuthority(canonicalHubUri));
hostnames.add(getAuthority(canonicalAuthUri));
renderHostnames(); // ask user whether to trust these hosts
} else {
LOG.warn("Cryptomator is not allowed to connect to {}. Check your {} config.", getAuthority(canonicalHubUri), Environment.HUB_ALLOWED_HOSTS_PROP_NAME);
deny();
}
}
@FXML
public void trust() {
settings.trustedHosts.addAll(hostnames);
Platform.runLater(() -> {
window.setScene(authFlowScene.get());
});
}
@FXML
public void deny() {
result.cancel(true);
Platform.runLater(() -> {
window.setScene(untrustedHostScene.get());
});
}
private void renderHostnames() {
hostnamesFlow.getChildren().clear();
for (var hostname : hostnames) {
hostnamesFlow.getChildren().add(new Text(hostname + System.lineSeparator()));
}
var messageKey = hostnames.size() > 1 ? ASK_PLURAL_KEY : ASK_SINGULAR_KEY;
messageLabel.set(resourceBundle.getString(messageKey));
}
private boolean isConsistentHubConfig() {
var canonicalHubAuthority = getAuthority(canonicalHubUri);
var canonicalAuthAuthority = getAuthority(canonicalAuthUri);
// apiBaseURL.host == deviceUrl.host == authSuccessUrl.host == authErrorUrl.host
return (hubConfig.apiBaseUrl == null || getAuthority(hubConfig.apiBaseUrl).equals(canonicalHubAuthority)) //
&& (hubConfig.devicesResourceUrl == null || getAuthority(hubConfig.devicesResourceUrl).equals(canonicalHubAuthority)) //
&& getAuthority(hubConfig.authSuccessUrl).equals(canonicalHubAuthority) //
&& getAuthority(hubConfig.authErrorUrl).equals(canonicalHubAuthority) //
// authUrl.host == tokenUrl.host:
&& getAuthority(hubConfig.tokenEndpoint).equals(canonicalAuthAuthority);
}
private boolean isAllCryptomatorCloud() {
return canonicalHubUri.getHost().endsWith(TRUSTED_CRYPTOMATOR_CLOUD_DOMAIN) && canonicalAuthUri.getHost().endsWith(TRUSTED_CRYPTOMATOR_CLOUD_DOMAIN);
}
private boolean isAnyHttpHost() {
return "http".equalsIgnoreCase(canonicalHubUri.getScheme()) || "http".equalsIgnoreCase(canonicalAuthUri.getScheme());
}
private boolean isAllLocalhost() {
return "localhost".equalsIgnoreCase(canonicalHubUri.getHost()) && "localhost".equalsIgnoreCase(canonicalAuthUri.getHost());
}
@VisibleForTesting
boolean containsAllowedHosts(Set<String> allowedHubHosts) {
return allowedHubHosts.contains(getAuthority(canonicalHubUri)) && allowedHubHosts.contains(getAuthority(canonicalAuthUri));
}
public static String getAuthority(String string) {
return getAuthority(URI.create(string));
}
public static String getAuthority(URI uri) {
if (uri.getPort() == -1) {
return "%s://%s".formatted(uri.getScheme(), uri.getHost());
} else {
return "%s://%s:%s".formatted(uri.getScheme(), uri.getHost(), uri.getPort());
}
}
//--- JavaFX property getter & setter
public StringProperty messageLabelProperty() {
return messageLabel;
}
public String getMessageLabel() {
return messageLabel.get();
}
}

View File

@@ -98,13 +98,6 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_NO_KEYCHAIN);
}
@Provides
@FxmlScene(FxmlFile.HUB_CHECK_HOST_TRUST)
@KeyLoadingScoped
static Scene provideHubCheckHostTrustScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_CHECK_HOST_TRUST);
}
@Provides
@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
@KeyLoadingScoped
@@ -175,13 +168,6 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
}
@Provides
@FxmlScene(FxmlFile.HUB_UNTRUSTED_HOST)
@KeyLoadingScoped
static Scene provideHubUntrustedHostScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_UNTRUSTED_HOST);
}
@Provides
@FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT)
@KeyLoadingScoped
@@ -194,11 +180,6 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(NoKeychainController.class)
abstract FxController bindNoKeychainController(NoKeychainController controller);
@Binds
@IntoMap
@FxControllerKey(CheckHostTrustController.class)
abstract FxController bindCheckHostAuthenticityController(CheckHostTrustController controller);
@Binds
@IntoMap
@FxControllerKey(AuthFlowController.class)
@@ -244,11 +225,6 @@ public abstract class HubKeyLoadingModule {
@FxControllerKey(UnauthorizedDeviceController.class)
abstract FxController bindUnauthorizedDeviceController(UnauthorizedDeviceController controller);
@Binds
@IntoMap
@FxControllerKey(UntrustedHostController.class)
abstract FxController bindUnauthorizedHostController(UntrustedHostController controller);
@Binds
@IntoMap
@FxControllerKey(RequireAccountInitController.class)

View File

@@ -36,19 +36,19 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy, FilesystemOwne
private final Stage window;
private final KeychainManager keychainManager;
private final AtomicReference<String> fsOwnerId;
private final Lazy<Scene> checkHostTrustScene;
private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<ReceivedKey> result;
private final DeviceKey deviceKey;
@Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_CHECK_HOST_TRUST) Lazy<Scene> checkHostTrustScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle, @Named("filesystemOwnerId") AtomicReference<String> fsOwnerId) {
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, @Named("filesystemOwnerId") AtomicReference<String> fsOwnerId) {
this.window = window;
this.keychainManager = keychainManager;
this.fsOwnerId = fsOwnerId;
window.setTitle(windowTitle);
window.setOnCloseRequest(_ -> result.cancel(true));
this.checkHostTrustScene = checkHostTrustScene;
this.authFlowScene = authFlowScene;
this.noKeychainScene = noKeychainScene;
this.result = result;
this.deviceKey = deviceKey;
@@ -62,7 +62,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy, FilesystemOwne
throw new NoKeychainAccessProviderException();
}
var keypair = deviceKey.get();
showWindow(checkHostTrustScene);
showWindow(authFlowScene);
var jwe = result.get();
return jwe.decryptMasterkey(keypair.getPrivate());
} catch (NoKeychainAccessProviderException e) {

View File

@@ -1,34 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.util.concurrent.CompletableFuture;
@KeyLoadingScoped
public class UntrustedHostController implements FxController {
private final Stage window;
private final CompletableFuture<ReceivedKey> result;
@Inject
public UntrustedHostController(@KeyLoading Stage window, CompletableFuture<ReceivedKey> result) {
this.window = window;
this.result = result;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void close() {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
result.cancel(true);
}
}

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import com.google.common.base.Preconditions;
import org.cryptomator.common.Constants;
import org.cryptomator.common.Passphrase;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
@@ -64,21 +63,16 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
window.setTitle(resourceBundle.getString("unlock.title").formatted(vault.getDisplayName()));
Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME);
if (!Constants.MASTERKEY_FILENAME.equals(keyId.getSchemeSpecificPart())) {
LOG.warn("unsupported masterkey path found in vault.cryptomator: {}", keyId.getSchemeSpecificPart());
}
try {
// determine masterkey file path:
Path filePath = vault.getPath().resolve(Constants.MASTERKEY_FILENAME);
Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart());
if (!Files.exists(filePath)) {
filePath = askUserForMasterkeyFilePath();
}
// unlock:
if (passphrase == null) {
askForPassphrase();
}
var masterkey = masterkeyFileAccess.load(filePath, passphrase);
// backup on successful unlock:
//backup
if (filePath.startsWith(vault.getPath())) {
try {
BackupHelper.attemptBackup(filePath);

View File

@@ -1,8 +1,9 @@
package org.cryptomator.ui.mainwindow;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tobiasdiez.easybind.EasyBind;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
@@ -57,7 +58,6 @@ public class VaultDetailUnlockedController implements FxController {
private final DecryptNameComponent.Factory decryptNameWindowFactory;
private final ResourceBundle resourceBundle;
private final LoadingCache<Vault, VaultStatisticsComponent> vaultStats;
private final LoadingCache<Vault, DecryptNameComponent> decryptNameWindows;
private final VaultStatisticsComponent.Builder vaultStatsBuilder;
private final ObservableValue<Boolean> accessibleViaPath;
private final ObservableValue<Boolean> accessibleViaUri;
@@ -89,8 +89,7 @@ public class VaultDetailUnlockedController implements FxController {
this.revealPathService = revealPathService;
this.decryptNameWindowFactory = decryptNameWindowFactory;
this.resourceBundle = resourceBundle;
this.vaultStats = Caffeine.newBuilder().weakValues().build(this::buildVaultStats);
this.decryptNameWindows = Caffeine.newBuilder().weakValues().build(this::buildDecryptNameWindow);
this.vaultStats = CacheBuilder.newBuilder().weakValues().build(CacheLoader.from(this::buildVaultStats));
this.vaultStatsBuilder = vaultStatsBuilder;
var mp = vault.flatMap(Vault::mountPointProperty);
this.accessibleViaPath = mp.map(m -> m instanceof Mountpoint.WithPath).orElse(false);
@@ -162,7 +161,7 @@ public class VaultDetailUnlockedController implements FxController {
}
private void showDecryptNameWindow(List<Path> pathsToDecrypt) {
decryptNameWindows.get(vault.get()).showDecryptFileNameWindow(pathsToDecrypt);
decryptNameWindowFactory.create(vault.get(), mainWindow, pathsToDecrypt).showDecryptFileNameWindow();
}
private boolean startsWithVaultAccessPoint(Path path) {
@@ -199,10 +198,6 @@ public class VaultDetailUnlockedController implements FxController {
return vaultStatsBuilder.vault(vault).build();
}
private DecryptNameComponent buildDecryptNameWindow(Vault vault) {
return decryptNameWindowFactory.create(vault, mainWindow);
}
@FXML
public void revealAccessLocation() {
vaultService.reveal(vault.get());
@@ -222,7 +217,7 @@ public class VaultDetailUnlockedController implements FxController {
@FXML
public void showVaultStatistics() {
vaultStats.get(vault.get()).showVaultStatisticsWindow();
vaultStats.getUnchecked(vault.get()).showVaultStatisticsWindow();
}
/* Getter/Setter */

View File

@@ -21,7 +21,6 @@ import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ToggleGroup;
@@ -57,7 +56,6 @@ public class GeneralPreferencesController implements FxController {
public CheckBox autoCloseVaultsCheckbox;
public CheckBox debugModeCheckbox;
public CheckBox autoStartCheckbox;
public Button resetTrustedHostsButton;
public ToggleGroup nodeOrientation;
private CompletionStage<Void> keychainMigrations = CompletableFuture.completedFuture(null);
@@ -107,9 +105,6 @@ public class GeneralPreferencesController implements FxController {
quickAccessServiceChoiceBox.setConverter(new NamedServiceConverter<>());
Bindings.bindBidirectional(settings.quickAccessService, quickAccessServiceChoiceBox.valueProperty(), quickAccessSettingsConverter);
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not());
if (resetTrustedHostsButton != null) {
resetTrustedHostsButton.disableProperty().bind(Bindings.isEmpty(settings.trustedHosts));
}
}
private void migrateKeychainEntries(Observable observable, KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider) {
@@ -136,10 +131,6 @@ public class GeneralPreferencesController implements FxController {
return autoStartProvider.isPresent();
}
public boolean isHubTrustOnFirstUseEnabled() {
return environment.hubTrustOnFirstUse();
}
@FXML
public void toggleAutoStart() {
autoStartProvider.ifPresent(autoStart -> {
@@ -162,11 +153,6 @@ public class GeneralPreferencesController implements FxController {
return !quickAccessServices.isEmpty();
}
@FXML
public void resetTrustedHosts() {
settings.trustedHosts.clear();
}
@FXML
public void showLogfileDirectory() {
try {

View File

@@ -260,7 +260,7 @@ public class UpdatesPreferencesController implements FxController {
public boolean isProhibitUpdateWhileUnlocked() {
// If the result of the last update check was from the fallback mechanism, we don't need to show the warning
return !unlockedVaults.isEmpty() && updateChecker.isUpdateAvailable() && !FallbackUpdateInfo.class.isInstance(updateChecker.getUpdate());
return !unlockedVaults.isEmpty() && !FallbackUpdateInfo.class.isInstance(updateChecker.getUpdate());
}
public BooleanBinding prohibitUpdateWhileUnlockedProperty() {

View File

@@ -18,10 +18,7 @@ import java.net.URISyntaxException;
public class ShareVaultController implements FxController {
private static final String SCHEME_PREFIX = "hub+";
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/" //
+ "?utm_source=cryptomator-desktop" //
+ "&utm_medium=app" //
+ "&utm_campaign=share-vault";
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/security/best-practices/#sharing-of-vaults";
private final Stage window;

View File

@@ -1,53 +0,0 @@
<?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.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 javafx.scene.text.TextFlow?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.CheckHostTrustController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT"
accessibleRole="DIALOG">
<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="QUESTION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="${controller.messageLabel}" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<TextFlow fx:id="hostnamesFlow" styleClass="text-flow" minHeight="60"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%hub.checkHostTrust.denyBtn" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#deny"/>
<Button text="%hub.checkHostTrust.trustBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#trust"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?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?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.hub.UntrustedHostController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT"
accessibleRole="DIALOG">
<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="EXCLAMATION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%hub.untrustedHost.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%hub.untrustedHost.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -22,7 +22,7 @@
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>
<Label text="© 2016 2026 Skymatic GmbH"/>
<Label text="© 2016 2025 Skymatic GmbH"/>
</VBox>
</HBox>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Hyperlink?>
@@ -35,8 +34,6 @@
<CheckBox fx:id="useQuickAccessCheckbox" text="%preferences.general.quickAccessService"/>
<ChoiceBox fx:id="quickAccessServiceChoiceBox" accessibleText="%preferences.general.quickAccessService"/>
</HBox>
<Button fx:id="resetTrustedHostsButton" text="%preferences.general.resetTrustedHosts" visible="${controller.hubTrustOnFirstUseEnabled}" managed="${controller.hubTrustOnFirstUseEnabled}" onAction="#resetTrustedHosts"/>
<Region VBox.vgrow="ALWAYS"/>
<HBox spacing="12" alignment="CENTER_LEFT">

View File

@@ -9,94 +9,90 @@
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<ScrollPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
fitToWidth="true"
hbarPolicy="NEVER">
<VBox spacing="6">
<fx:define>
<ToggleGroup fx:id="mountPointToggleGroup"/>
</fx:define>
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%vaultOptions.mount.volume.type" labelFor="$vaultVolumeTypeChoiceBox"/>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}" accessibleText="%vaultOptions.mount.info">
<graphic>
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip>
</Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}" accessibleText="%preferences.volume.docsTooltip">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</HBox>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.MountOptionsController"
spacing="6">
<fx:define>
<ToggleGroup fx:id="mountPointToggleGroup"/>
</fx:define>
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="%vaultOptions.mount.volume.type" labelFor="$vaultVolumeTypeChoiceBox"/>
<ChoiceBox fx:id="vaultVolumeTypeChoiceBox"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openVolumePreferences" visible="${controller.defaultMountServiceSelected}" managed="${controller.defaultMountServiceSelected}" accessibleText="%vaultOptions.mount.info">
<graphic>
<FontAwesome5IconView glyph="COGS" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%vaultOptions.mount.info" showDelay="100ms"/>
</tooltip>
</Hyperlink>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs" visible="${!controller.defaultMountServiceSelected}" managed="${!controller.defaultMountServiceSelected}" accessibleText="%preferences.volume.docsTooltip">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%preferences.volume.docsTooltip" showDelay="100ms"/>
</tooltip>
</Hyperlink>
</HBox>
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<Label styleClass="label-red" text="%vaultOptions.mount.volumeType.restartRequired" visible="${controller.selectedMountServiceRequiresRestart}" managed="${controller.selectedMountServiceRequiresRestart}"/>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port" labelFor="$vaultLoopbackPortField"/>
<NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.loopbackPortChangeable}" managed="${controller.loopbackPortChangeable}">
<Label text="%vaultOptions.mount.volume.tcp.port" labelFor="$vaultLoopbackPortField"/>
<NumericTextField fx:id="vaultLoopbackPortField"/>
<Button text="%generic.button.apply" fx:id="vaultLoopbackPortApplyButton" onAction="#doChangeLoopbackPort"/>
</HBox>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlyOptionAllowed}" managed="${controller.readOnlyOptionAllowed}"/>
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly" visible="${controller.readOnlyOptionAllowed}" managed="${controller.readOnlyOptionAllowed}"/>
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity" accessibleText="%vaultOptions.mount.customMountFlags">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
<Label text="%vaultOptions.mount.mountPoint">
<VBox visible="${controller.mountFlagsSupported}" managed="${controller.mountFlagsSupported}">
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
<TextField fx:id="mountFlagsField" HBox.hgrow="ALWAYS" maxWidth="Infinity" accessibleText="%vaultOptions.mount.customMountFlags">
<VBox.margin>
<Insets top="9"/>
<Insets left="24"/>
</VBox.margin>
</Label>
</TextField>
</VBox>
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointAutoBtn" text="%vaultOptions.mount.mountPoint.auto"/>
<Label text="%vaultOptions.mount.mountPoint">
<VBox.margin>
<Insets top="9"/>
</VBox.margin>
</Label>
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" accessibleText="%vaultOptions.mount.mountPoint.driveLetter"/>
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointAutoBtn" text="%vaultOptions.mount.mountPoint.auto"/>
<HBox spacing="6" visible="${controller.mountpointDriveLetterSupported}" managed="${controller.mountpointDriveLetterSupported}">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDriveLetterBtn" text="%vaultOptions.mount.mountPoint.driveLetter"/>
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointDriveLetterBtn.selected}" accessibleText="%vaultOptions.mount.mountPoint.driveLetter"/>
</HBox>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<HBox spacing="6" alignment="CENTER_LEFT">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" accessibleText="%vaultOptions.mount.mountPoint.custom">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
</children>
<VBox spacing="6" visible="${controller.mountpointDirSupported}" managed="${controller.mountpointDirSupported}">
<HBox spacing="6" alignment="CENTER_LEFT">
<RadioButton toggleGroup="${mountPointToggleGroup}" fx:id="mountPointDirBtn" text="%vaultOptions.mount.mountPoint.custom"/>
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointDirBtn.selected}">
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
</graphic>
</Button>
</HBox>
<TextField fx:id="directoryPathField" text="${controller.directoryPath}" visible="${mountPointDirBtn.selected}" managed="${mountPointDirBtn.managed}" maxWidth="Infinity" editable="false" accessibleText="%vaultOptions.mount.mountPoint.custom">
<VBox.margin>
<Insets left="24"/>
</VBox.margin>
</TextField>
</VBox>
</children>
</VBox>
</ScrollPane>
</VBox>

View File

@@ -162,12 +162,6 @@ unlock.error.title=Unlock "%s" failed
hub.noKeychain.message=Unable to access device key
hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
hub.noKeychain.openBtn=Open Preferences
### Check Host Authenticity
hub.checkHostTrust.message.check=Checking Configuration…
hub.checkHostTrust.message.ask=Trust this host?
hub.checkHostTrust.message.ask.plural=Trust these hosts?
hub.checkHostTrust.trustBtn=Trust
hub.checkHostTrust.denyBtn=Deny
### Waiting
hub.auth.message=Waiting for authentication…
hub.auth.description=You should automatically be redirected to the login page.
@@ -199,9 +193,6 @@ hub.archived.description=This vault has been archived and is no longer accessibl
### Unauthorized
hub.unauthorized.message=Access denied
hub.unauthorized.description=You are not authorized to open this vault. Contact the vault's owner to request access.
### Untrusted Host
hub.untrustedHost.message=Host not trusted
hub.untrustedHost.description=Connection to Hub was blocked for your security. If you believe the Hub host is safe, contact your Hub administrator or try again.
### Requires Account Initialization
hub.requireAccountInit.message=Action required
hub.requireAccountInit.description.0=To proceed, please complete the steps required in your
@@ -315,7 +306,6 @@ preferences.general.debugDirectory=Reveal log files
preferences.general.autoStart=Launch Cryptomator on system start
preferences.general.keychainBackend=Store passwords with
preferences.general.quickAccessService=Add unlocked vaults to the quick access area
preferences.general.resetTrustedHosts=Reset trusted hosts
## Interface
preferences.interface=Interface
preferences.interface.theme=Look & Feel
@@ -727,4 +717,4 @@ eventView.entry.inUse.ignoreLock=Ignore use status
## FileIsInUse Notification
notification.inUse.message=File is in use on another device
notification.inUse.description=The file is open by %s on %s. Ask them to close the file and let synchronization finish. You can ignore the status to open it now, but this may cause conflicts or overwrite newer changes.
notification.inUse.action=Ignore Use Status
notification.inUse.action=Ignore Use Status

View File

@@ -269,8 +269,6 @@ health.check.detail.checkFinishedAndFound=Kontrolproceduren er kørt færdig. Ge
health.check.detail.checkFailed=Kontrolproceduren blev afbrudt af en fejl.
health.check.detail.checkCancelled=Kontrolproceduren blev annulleret.
health.check.detail.listFilters.label=Filter
health.check.detail.filterSeverity=Filtrér efter sværhedsgrad
health.check.detail.filterFixState=Filtrér efter fix status
health.check.detail.fixAllSpecificBtn=Løs alle af type
health.check.exportBtn=Eksportér rapport
## Result view
@@ -360,7 +358,6 @@ preferences.contribute.promptText=Indsæt koden for supporter-certifikatet her
preferences.contribute.thankYou=Tak fordi du støtter Cryptomators open source-udvikling!
preferences.contribute.donate=Donér
preferences.contribute.sponsor=Sponsor
preferences.contribute.removeCert.tooltip=Fjern certifikat
### Remove License Key Dialog
removeCert.title=Fjern certifikat
@@ -370,7 +367,6 @@ removeCert.description=Cryptomators kernefunktioner påvirkes ikke af dette. Hve
## About
preferences.about=Om
preferences.about.thirdPartyLicenses=Tredjepartslicenser
# Vault Statistics
stats.title=Statistik for %s
@@ -410,7 +406,6 @@ stats.access.total=Samlede adgang: %d
# Main Window
## Vault List
main.vaultlist=Bokse
main.vaultlist.listEntry=Boks %s (%s)
main.vaultlist.emptyList.onboardingInstruction=Klik her for at tilføje en boks
main.vaultlist.contextMenu.remove=Fjern…
main.vaultlist.contextMenu.lock=Lås
@@ -424,15 +419,12 @@ main.vaultlist.addVaultBtn.menuItemExisting=Åbn eksisterende boks…
main.vaultlist.addVaultBtn.menuItemRecover=Genopret eksisterende boks…
main.vaultlist.addVaultButton.tooltip=Tilføj boks
main.vaultlist.showEventsButton.tooltip=Åbn begivenhedsvisning
main.vaultlist.showPreferencesButton.tooltip=Vis Indstillinger
##Notification
main.notification.updateAvailable=Opdatering er tilgængelig.
main.notification.support=Støt Cryptomator.
main.notification.closeButton.tooltip=Luk infobjælke
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Tak fordi du valgte Cryptomator til at beskytte dine filer. Hvis du har brug for hjælp, så tjek vores guider for at komme i gang:
main.vaultDetail.storageLocation=Placering af boks
### Locked
main.vaultDetail.lockedStatus=LÅST
main.vaultDetail.unlockBtn=Lås op…
@@ -490,7 +482,6 @@ vaultOptions.general=Generelt
vaultOptions.general.vaultName=Boks-navn
vaultOptions.general.autoLock.lockAfterTimePart1=Lås efter inaktivitet i
vaultOptions.general.autoLock.lockAfterTimePart2=minutter
vaultOptions.general.autoLock.accessibleText=Lås timeout i minutter
vaultOptions.general.unlockAfterStartup=Lås boksen op når Cryptomator starter
vaultOptions.general.actionAfterUnlock=Efter oplåsning af boks
vaultOptions.general.actionAfterUnlock.ignore=Gør intet
@@ -521,7 +512,6 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Glem gemt adgangskode
vaultOptions.masterkey.recoveryKeyExplanation=En gendannelsesnøgle er den eneste måde du kan få adgang til din boks på, hvis du har glemt dit password.
vaultOptions.masterkey.showRecoveryKeyBtn=Vis gendannelsesnøgle
vaultOptions.masterkey.recoverPasswordBtn=Nulstil adgangskode
vaultOptions.masterkey.missingMasterkeyFile=Disse tilvalg er kun tilgængelige hvis masterkeyfilen er til stede i boksmappen.
## Hub
vaultOptions.hub=Gendannelse
vaultOptions.hub.convertInfo=Du kan bruge gendannelsesnøglen til at konvertere denne Hub-boks til en adgangskode-baseret boks i en nødsituation.
@@ -675,8 +665,6 @@ decryptNames.filePicker.title=Vælg krypteret fil
decryptNames.filePicker.extensionDescription=Cryptomator krypteret fil
decryptNames.copyTable.tooltip=Kopiér tabel
decryptNames.clearTable.tooltip=Ryd tabel
decryptNames.column.encrypted=Krypteret
decryptNames.column.decrypted=Dekrypteret
decryptNames.copyHint=Kopiér celleindhold med %s
decryptNames.dropZone.message=Slip filer eller klik for at vælge
decryptNames.dropZone.error.vaultInternalFiles=Boks interne filer med intet dekryptérbart navn valgt
@@ -689,8 +677,6 @@ decryptNames.dropZone.error.generic=Kunne ikke dekryptere filnavne
eventView.title=Begivenheder
eventView.filter.allVaults=Alle
eventView.clearListButton.tooltip=Ryd liste
eventView.filterVaults=Filtrér efter boks
eventView.cell.actionsButton.tooltip=Begivenhedshandlinger
## event list entries
eventView.entry.vaultLocked.description=Lås "%s" op for detaljer
eventView.entry.conflictResolved.message=Løst konflikt
@@ -708,7 +694,6 @@ eventView.entry.brokenFileNode.copyDecrypted=Kopiér dekrypteret sti
eventView.entry.inUse.message=Fil i brug
eventView.entry.inUse.showDecrypted=Vis dekrypteret fil
eventView.entry.inUse.showEncrypted=Vis krypteret fil
eventView.entry.inUse.copyUserAndDevice=Kopiér låsebruger og enhedsnavn
eventView.entry.inUse.ignoreLock=Ignorér anvendelsesstatus

View File

@@ -42,7 +42,7 @@ defaults.vault.vaultName=Vault
# Tray Menu
traymenu.showMainWindow=보기
traymenu.showPreferencesWindow=환경 설정
traymenu.showPreferencesWindow=환경설정
traymenu.lockAllVaults=모두 잠그기
traymenu.quitApplication=종료
traymenu.vault.unlock=잠금 해제
@@ -114,7 +114,7 @@ addvaultwizard.success.nextStepsInstructions="%s" Vault가 추가되었습니다
addvaultwizard.success.unlockNow=지금 잠금 해제
# Remove Vault
removeVault.title=Vault "%s" 제거
removeVault.title=Vault 제거
removeVault.message=Vault를 삭제하시겠습니까?
removeVault.description=이 행위는 Cryptomator에서만 이 Vault를 지웁니다. 나중에 다시 추가할 수 있습니다. 암호화된 파일은 하드디스크에서 삭제되지 않습니다.
@@ -132,18 +132,18 @@ forgetPassword.confirmBtn=비밀번호 삭제
# Unlock
unlock.title="%s" 잠금 해제
unlock.passwordPrompt="%s"의 비밀번호를 입력하십시오.
unlock.savePassword=비밀번호 기억하기
unlock.savePassword=비밀번호 기억
unlock.unlockBtn=잠금 해제
## Select
unlock.chooseMasterkey.message=마스터키 파일을 찾을 수 없습니다
unlock.chooseMasterkey.description= "%s" Vault의 마스터키를 찾지 못했습니다. 마스터키 위치를 수동으로 선택하여 주십시오.
unlock.chooseMasterkey.description=이 Vault의 Masterkey를 찾지 못했습니다. 마스터 키 위치를 수동으로 선택하여 주십시오.
unlock.chooseMasterkey.restoreInstead=대신 마스터키 파일 복구
unlock.chooseMasterkey.filePickerTitle=Masterkey 파일 선택
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
unlock.success.message=잠금 해제 성공
unlock.success.description="%s"이(가) 성공적으로 잠금 해제되었습니다. 이제 이 Vault를 마운트 지점으로 접근할 수 있습니다.
unlock.success.rememberChoice=선택 기억하 다시 묻지 않음
unlock.success.rememberChoice=선택 기억하기, 다시 묻지 않음
unlock.success.revealBtn=드라이브 표시
## Failure
unlock.error.customPath.message=Vault를 사용자 정의 경로에 마운트할 수 없습니다.
@@ -166,7 +166,7 @@ hub.auth.message=인증 대기 중…
hub.auth.description=자동으로 로그인 페이지로 리다이렉트 될 것입니다.
hub.auth.loginLink=수동으로 열려면 클릭하십시오.
### Receive Key
hub.receive.message=응답 처리 중…
hub.receive.message=응답 처리중…
hub.receive.description=Hub로부터 응답을 처리하고 있습니다. 잠시만 기다려 주십시오.
### Register Device
hub.register.message=새 기기
@@ -181,7 +181,7 @@ hub.register.legacy.description=이 기기로부터 첫번째 Hub 접근입니
hub.registerSuccess.message=기기 등록됨
hub.registerSuccess.description=등록에 성공하였습니다. Vault를 잠금 해제할 수 있습니다.
hub.registerSuccess.unlockBtn=잠금 해제
hub.registerSuccess.legacy.description=Vault에 접근하기 위해서는 이 기기를 Vault 소유가 추가적으로 허가해야 합니다.
hub.registerSuccess.legacy.description=Vault에 접근하기 위해서는 이 기기를 Vault 소유가 추가적으로 허가해야 합니다.
### Registration Failed
hub.registerFailed.message=기기 등록 실패
hub.registerFailed.description.generic=등록 중에 오류가 발생했습니다. 앱 로그에서 자세한 정보를 확인할 수 있습니다.
@@ -209,18 +209,18 @@ lock.forced.retryBtn=재시도
lock.forced.forceBtn=강제 잠금
## Failure
lock.fail.message=Vault 잠금에 실패하였습니다.
lock.fail.description="%s" Vault를 잠글 수 없습니다. 저장되지 않은 작업이 다른 곳에 저장된 것과 중요한 읽기/쓰기 동작이 완료되었는지 확인 하십시요. Vault를 닫기 위해, Cryptomator 프로세스를 강제로 종료하십시오.
lock.fail.description="%s" Vault를 잠글 수 없습니다. 저장되지 않은 작업이 다른 곳에 저장된 것과 중요한 읽기/쓰기 동작이 완료되었는지 확인 하십시요. Vault를 닫기 위해, Cryptomator 프로세스를 강제로 종료 하십시오.
# Migration
migration.title=Vault 업그레이드
## Start
migration.start.header=Vault 업그레이드
migration.start.text=Vault "%s"를 현재 버전의 Cryptomator에서 열기 위해서는 해당 Vault를 새 버전으로 업그레이드해야 합니다. 업그레이드를 하기 전에 다음 사항들을 알고 있어야 합니다:
migration.start.text=Vault "%s"를 현재 버전의 Cryptomator에서 열기 위해서는 해당 vault를 새 버전으로 업그레이드해야 합니다. 업그레이드를 하기 전에 다음 사항들을 알고 있어야 합니다:
migration.start.remarkUndone=이 업그레이드는 되돌릴 수 없습니다.
migration.start.remarkVersions=과거 버전의 Cryptomator는 업그레이드된 Vault를 열 수 없습니다.
migration.start.remarkCanRun=이 Vault를 열 때 사용하는 모든 기기가 현재 버전의 Cryptomator를 실행할 수 있는지 확인해야 합니다.
migration.start.remarkSynced=업그레이드하기 전에 해당 Vault가 모든 기기에 정상적으로 동기화되어야 합니다.
migration.start.confirm=위 내용을 충분히 숙지하였음을 확인합니다.
migration.start.confirm=나는 위 정보를 읽고 정말 이해했습니다.
## Run
migration.run.enterPassword="%s"의 비밀번호를 입력하십시오.
migration.run.startMigrationBtn=Vault 마이그레이션
@@ -231,8 +231,8 @@ migration.success.unlockNow=지금 잠금 해제
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=지원하지 않는 파일 시스템
migration.error.missingFileSystemCapabilities.description=Vault가 부적절한 파일 시스템에 있기 때문에 마이그레이션이 시작되지 않았습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=파일 시스템이 긴 파일 이름을 지원하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=파일 시스템이 긴 경로를 지원하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=너무 긴 파일 이름을 파일 시스템에서 지원하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=너무 긴 경로를 파일 시스템에서 지원하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=파일 시스템이 읽기를 허용하지 않습니다.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=파일 시스템이 쓰기를 허용하지 않습니다.
## Impossible
@@ -267,7 +267,7 @@ health.check.detail.checkSkipped=선택된 검사항목이 없습니다.
health.check.detail.checkFinished=검사가 성공적으로 완료되었습니다.
health.check.detail.checkFinishedAndFound=검사가 완료되었습니다. 검사 결과를 확인해주세요.
health.check.detail.checkFailed=오류로 인해 검사가 종료되었습니다.
health.check.detail.checkCancelled=검사가 취소되었습니다.
health.check.detail.checkCancelled=검사가 취소되었습니다
health.check.detail.listFilters.label=필터
health.check.detail.filterSeverity=중요도로 정렬
health.check.detail.filterFixState=해결 상태로 정렬
@@ -286,7 +286,7 @@ health.result.severityTip.crit=상태: 심각\nVault 구조가 손상되었습
health.result.fixStateFilter.all=모든 문제 해결 상태
health.result.fixStateFilter.fixable=문제 해결 가능
health.result.fixStateFilter.notFixable=문제 해결 불가
health.result.fixStateFilter.fixing=문제 해결 중…
health.result.fixStateFilter.fixing=문제 해결중…
health.result.fixStateFilter.fixed=문제 해결됨
health.result.fixStateFilter.fixFailed=문제 해결 실패
## Fix Application
@@ -295,7 +295,7 @@ health.fix.successTip=문제 해결이 성공적으로 완료되었습니다
health.fix.failTip=문제 해결 실패, 상세 정보는 로그를 참조하십시오.
# Preferences
preferences.title=환경 설정
preferences.title=환경설정
## General
preferences.general=일반
preferences.general.startHidden=Cryptomator를 시작할 때 창 숨김
@@ -309,9 +309,9 @@ preferences.general.quickAccessService=열린 Vault를 빠른 접근 위치에
preferences.interface=인터페이스
preferences.interface.theme=테마
preferences.interface.theme.automatic=자동
preferences.interface.theme.dark=다크 모드
preferences.interface.theme.light=라이트 모드
preferences.interface.unlockThemes=다크 모드 사용 권한을 얻어보세요!
preferences.interface.theme.dark=어둡게
preferences.interface.theme.light=밝게
preferences.interface.unlockThemes=다크모드 해제
preferences.interface.language=언어 (재시작 필요)
preferences.interface.language.auto=시스템 기본 설정
preferences.interface.interfaceOrientation=인터페이스 방향
@@ -356,7 +356,7 @@ preferences.contribute=후원하기
preferences.contribute.registeredFor=%s(으)로 후원자 인증 등록됨
preferences.contribute.noCertificate=Cryptomator를 후원하시고 후원자 인증을 받으십시오. 라이선스 키와 비슷하지만 무료 소프트웨어를 사용하는 멋진 사람들을 위한 것입니다. ;-)
preferences.contribute.getCertificate=아직 후원자 인증이 없으신가요? 어떻게 얻는지 배울 수 있습니다.
preferences.contribute.promptText=후원자 인증 코드를 여기에 붙여넣기
preferences.contribute.promptText=후원자 인증코드를 여기에 붙여넣기
preferences.contribute.thankYou=Cryptomator의 오픈 소스 개발을 지원해 주셔서 감사합니다!
preferences.contribute.donate=후원하기
preferences.contribute.sponsor=스폰서
@@ -364,7 +364,7 @@ preferences.contribute.removeCert.tooltip=인증서 제거
### Remove License Key Dialog
removeCert.title=인증서 제거
removeCert.message=후원자 인증서를 제거하시겠습니까?
removeCert.message=서포터 인증서를 제거하시겠습니까?
removeCert.description=Cryptomator의 핵심 기능은 영향을 받지 않습니다. Vault에 대한 접근이 제한되거나 보안이 약화되지 않습니다.
#<-- Add entries for donations and code/translation/documentation contribution -->
@@ -392,7 +392,7 @@ stats.read.accessCount=총 읽기 횟수: %d
stats.write.throughput.idle=쓰기: 대기 중
stats.write.throughput.kibs=쓰기: %.2f KiB/s
stats.write.throughput.mibs=쓰기: %.2f MiB/s
stats.write.total.data.none=데이터 기: -
stats.write.total.data.none=데이터 기록됨: -
stats.write.total.data.kib=데이터 쓰기: %.1f KiB
stats.write.total.data.mib=데이터 쓰기: %.1f MiB
stats.write.total.data.gib=데이터 쓰기: %.1f GiB
@@ -427,11 +427,11 @@ main.vaultlist.showEventsButton.tooltip=이벤트 뷰어 열기
main.vaultlist.showPreferencesButton.tooltip=환경 설정 표시
##Notification
main.notification.updateAvailable=업데이트가 있습니다.
main.notification.support=Cryptomator 지원해 주세요
main.notification.support=Cryptomator 지원하기.
main.notification.closeButton.tooltip=정보 표시줄 닫기
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=파일 보호 위해 Cryptomator를 선택해 주셔서 감사합니다. 도움이 필요하시면 시작 가이드를 확인해 주십시오:
main.vaultDetail.welcomeOnboarding=파일 보호하기 위해 Cryptomator를 선택해주셔서 감사합니다. 만약 다른 도움이 필요하시면, 시작 안내서를 참조하시기 바랍니다.
main.vaultDetail.storageLocation=Vault 저장 위치
### Locked
main.vaultDetail.lockedStatus=잠김
@@ -702,18 +702,15 @@ eventView.entry.decryptionFailed.message=복호화 실패
eventView.entry.decryptionFailed.showEncrypted=암호화된 파일 보기
eventView.entry.brokenDirFile.message=망가진 디렉터리 링크
eventView.entry.brokenDirFile.showEncrypted=망가진 암호화된 링크 보기
eventView.entry.brokenFileNode.message=망가진 파일 시스템 노드
eventView.entry.brokenFileNode.message=망가진 파일시스템 노드
eventView.entry.brokenFileNode.showEncrypted=망가진 암호화된 노드 보기
eventView.entry.brokenFileNode.copyDecrypted=복호화된 경로 복사하기
eventView.entry.inUse.message=파일 사용 중
eventView.entry.inUse.showDecrypted=복호화된 파일 보기
eventView.entry.inUse.showEncrypted=암호화된 파일 보기
eventView.entry.inUse.copyUserAndDevice=파일 잠금 사용자 및 기기 이름 복사
eventView.entry.inUse.ignoreLock=사용 여부 상태 무시
# Notifications
## FileIsInUse Notification
notification.inUse.message=다른 기기에서 파일 사용 중
notification.inUse.description=해당 파일은 %s이(가) %s의 컴퓨터에서 열어 놓은 상태입니다. 파일을 닫고 동기화가 완료될 때까지 기다리도록 요청하세요. 상태를 무시하고 지금 파일을 열 수도 있지만, 이렇게 하면 충돌이 발생하거나 최신 변경 사항이 덮어쓰여질 수 있습니다.
notification.inUse.action=사용 여부 상태 무시

View File

@@ -488,7 +488,7 @@ wrongFileAlert.link=Para obter assistência, visite
## General
vaultOptions.general=Geral
vaultOptions.general.vaultName=Nome do cofre
vaultOptions.general.autoLock.lockAfterTimePart1=Bloquear quando inativo após
vaultOptions.general.autoLock.lockAfterTimePart1=Bloquear quando inativo por
vaultOptions.general.autoLock.lockAfterTimePart2=minuto(s)
vaultOptions.general.autoLock.accessibleText=Bloquear tempo limite em minutos
vaultOptions.general.unlockAfterStartup=Desbloquear o cofre ao iniciar o Cryptomator

View File

@@ -705,15 +705,10 @@ eventView.entry.brokenDirFile.showEncrypted=顯示損壞的加密路徑
eventView.entry.brokenFileNode.message=損壞的檔案系統節點
eventView.entry.brokenFileNode.showEncrypted=顯示損壞的加密節點
eventView.entry.brokenFileNode.copyDecrypted=複製解密路徑
eventView.entry.inUse.message=檔案正在使用中
eventView.entry.inUse.showDecrypted=顯示解密的檔案
eventView.entry.inUse.showEncrypted=顯示加密的檔案
eventView.entry.inUse.copyUserAndDevice=複製鎖定的使用者和裝置名稱
eventView.entry.inUse.ignoreLock=忽略使用狀態
# Notifications
## FileIsInUse Notification
notification.inUse.message=檔案正在被另一部裝置使用中
notification.inUse.description=這個檔案正在由 %s 在 %s 開啟中。告訴他們關閉檔案讓同步完成。您可以忽略這個狀態並且馬上開啟,但是可能會造成衝突或把新的變更覆蓋掉。
notification.inUse.action=忽略使用狀態
## FileIsInUse Notification

View File

@@ -9,25 +9,49 @@ import java.util.Optional;
public class LicenseCheckerTest {
@SuppressWarnings("SpellCheckingInspection")
private static final String PUBLIC_KEY = """
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\
Al8G7CqwoJOsW7Kddns=\
private static final String LEGACY_PUBLIC_KEY = """
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBiUVQ0HhZMuAOqiO2lPIT+MMSH4bcl\
6BOWnFn205bzTcRI9RuRdtrXVNwp/IPtjMVXTj/oW0r12HcrEdLmi9QI6QASTEByW\
LNTS/d94IoXmRYQTnC+RtH+H/4I1TWYw90aiig2yV0G1s0qCgAiyKswj+ST6r71NM\
/gepmlW3+qiv9/PU=\
""";
private static final String TEST_ROOT_CERT = """
-----BEGIN CERTIFICATE-----
MIIBpjCCAVigAwIBAgIUJWMTr10U1lkxgPhxjed4kPqSu4EwBQYDK2VwMD8xCzAJ
BgNVBAYTAkRFMRYwFAYDVQQKDA1Ta3ltYXRpYyBHbWJIMRgwFgYDVQQDDA9MaWNl
bnNlIFRlc3QgQ0EwHhcNMjYwMjI2MTUzMzIwWhcNMzYwMjI0MTUzMzIwWjA/MQsw
CQYDVQQGEwJERTEWMBQGA1UECgwNU2t5bWF0aWMgR21iSDEYMBYGA1UEAwwPTGlj
ZW5zZSBUZXN0IENBMCowBQYDK2VwAyEAVi5WsfyHgHiL0vr4d2Lt1g7kPgC5m8u0
DIKLalKJqHSjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEG
MB0GA1UdDgQWBBQBiO+yqMTag59eH8fgcF9gMsXeEzAfBgNVHSMEGDAWgBQBiO+y
qMTag59eH8fgcF9gMsXeEzAFBgMrZXADQQD1I6bFKdHW+MaSpNVl/seCJny0Tp5L
3+v6IyybvV0e66ks8BhsRWbqoSSBEaF4zlX7gAPzNuOIEFadM4s1fVMC
-----END CERTIFICATE-----
""";
private static final String TEST_CN = "License Issuer CA (Test)";
private LicenseChecker licenseChecker;
@BeforeEach
public void setup() {
licenseChecker = new LicenseChecker(PUBLIC_KEY);
licenseChecker = new LicenseChecker(LEGACY_PUBLIC_KEY, TEST_ROOT_CERT, TEST_CN);
}
@Test
public void testCheckValidLegacyLicense() {
String license = "eyJhbGciOiJFUzUxMiJ9.eyJhdWQiOiJDcnlwdG9tYXRvciIsInN1YiI6ImNyeXB0b2JvdEBleGFtcGxlLmNvbSIsImtpZCI6IllFVjFFSXY5dllMR1lfR1RoQ0VPTGtleW1xUDBlZVhVVTdNbWxKaHFnR0EiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MVwvIiwiZXhwIjozMjUwMzY4MDAwMCwiaWF0Ijo5NDY2ODQ4MDAsInNlYXRzIjozfQ.ATHMZ95Z3cx-ghYvbT9XBT-Z-c4BWJOK6WM3JbOjCzIO-3pddYpey0uAfMipIfLzHZypE4NPtNR4nB8z7JRGcqj8AMBpXBxZnDGXsftGHkOuHw6kTq2b-HPmcYBKFZ4hH7ptQRS9byaypFc7ftonLyYODWqar6DXtHSJtlf01jnuAdVi";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertTrue(decoded.isPresent());
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
}
@Test
public void testCheckValidLicense() {
@SuppressWarnings("SpellCheckingInspection")
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
String license = "eyJ4NWMiOlsiTUlJQ0REQ0NBYjZnQXdJQkFnSVVETU96eEpZMzgxSmpkTjgyY1c3cWQrQjN2aDB3QlFZREsyVndNRTR4Q3pBSkJnTlZCQVlUQWtSRk1SWXdGQVlEVlFRS0RBMVRhM2x0WVhScFl5QkhiV0pJTVNjd0pRWURWUVFEREI1TWFXTmxibk5sSUVsdWRHVnliV1ZrYVdGMFpTQkRRU0FvVkdWemRDa3dIaGNOTWpZd01qSTJNVFUwTURNMldoY05Nell3TWpJME1UVTBNRE0yV2pCSU1Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERWhNQjhHQTFVRUF3d1lUR2xqWlc1elpTQkpjM04xWlhJZ1EwRWdLRlJsYzNRcE1JR2JNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWpBNEdHQUFRQmlVVlEwSGhaTXVBT3FpTzJsUElUK01NU0g0YmNsNkJPV25GbjIwNWJ6VGNSSTlSdVJkdHJYVk53cFwvSVB0ak1WWFRqXC9vVzByMTJIY3JFZExtaTlRSTZRQVNURUJ5V0xOVFNcL2Q5NElvWG1SWVFUbkMrUnRIK0hcLzRJMVRXWXc5MGFpaWcyeVYwRzFzMHFDZ0FpeUtzd2orU1Q2cjcxTk1cL2dlcG1sVzMrcWl2OVwvUFdqUWpCQU1CMEdBMVVkRGdRV0JCU05qQnd2K1wvaVlRdnBPT3F6MDJ1N3hhQVNTSVRBZkJnTlZIU01FR0RBV2dCUk1BU3NwMWtpYXdKbThZb0o2KzhcL3NxMjFYNHpBRkJnTXJaWEFEUVFBNE9rXC8reTBiZHptMlJVbWtIZDZRRlM2V2JCS2Y5TzR6ejNVYzdpQk1wS0lxMWtCbHErN1RiYmdNSEp1K2FZYk9EY1JXVCsrNXN4NGkyT3Nwa2dPc0oiLCJNSUlCdFRDQ0FXZWdBd0lCQWdJVUpteDhVcXRnbktjYXVRYnFiMDRUYVVCRytVSXdCUVlESzJWd01EOHhDekFKQmdOVkJBWVRBa1JGTVJZd0ZBWURWUVFLREExVGEzbHRZWFJwWXlCSGJXSklNUmd3RmdZRFZRUUREQTlNYVdObGJuTmxJRlJsYzNRZ1EwRXdIaGNOTWpZd01qSTJNVFV6TmpNMldoY05Nell3TWpJME1UVXpOak0yV2pCT01Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERW5NQ1VHQTFVRUF3d2VUR2xqWlc1elpTQkpiblJsY20xbFpHbGhkR1VnUTBFZ0tGUmxjM1FwTUNvd0JRWURLMlZ3QXlFQUczdnI4Tks3WVpzMXE2cFF0SmhIRGJUMnhNRDNDMnlzbXNuZW13MUZRbGFqWmpCa01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUhcL0JBUURBZ0VHTUIwR0ExVWREZ1FXQkJSTUFTc3Axa2lhd0ptOFlvSjYrOFwvc3EyMVg0ekFmQmdOVkhTTUVHREFXZ0JRQmlPK3lxTVRhZzU5ZUg4ZmdjRjlnTXNYZUV6QUZCZ01yWlhBRFFRQktkR0cybmpWa3JqMEwxbWVXVTROcGxaZHlHUTJxYUNxNFBSenk1OUg5WW1EUzc5M1JsTWE0TU9ad0FtVGtVUTV2YWt1dnR6MTM0SWl6NmpKa0RCZ0YiXSwiYWxnIjoiRVM1MTIifQ.eyJhdWQiOiJDcnlwdG9tYXRvciIsInN1YiI6ImNyeXB0b2JvdEBleGFtcGxlLmNvbSIsImtpZCI6IllFVjFFSXY5dllMR1lfR1RoQ0VPTGtleW1xUDBlZVhVVTdNbWxKaHFnR0EiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MVwvIiwiZXhwIjozMjUwMzY4MDAwMCwiaWF0Ijo5NDY2ODQ4MDAsInNlYXRzIjozfQ.AMBwG0CnIu8FHLsmynfT-JHwE-pwjXGh8SoExaYWY6n88cL18gzI_tYl3cjZSvLVZImg-VRZi7SkhDVt5zrOXX12AdNst1jJc0HJIA0dJUiM22b3XmkIjionTXmK-Njgp9XIQV4qtYoFPXjsL4KKTa95yx5oTvDD7ZAHFxur5_nN3y4t";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
@@ -37,8 +61,7 @@ public class LicenseCheckerTest {
@Test
public void testCheckInvalidLicenseHeader() {
@SuppressWarnings("SpellCheckingInspection")
String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
String license = "EyJ4NWMiOlsiTUlJQ0REQ0NBYjZnQXdJQkFnSVVETU96eEpZMzgxSmpkTjgyY1c3cWQrQjN2aDB3QlFZREsyVndNRTR4Q3pBSkJnTlZCQVlUQWtSRk1SWXdGQVlEVlFRS0RBMVRhM2x0WVhScFl5QkhiV0pJTVNjd0pRWURWUVFEREI1TWFXTmxibk5sSUVsdWRHVnliV1ZrYVdGMFpTQkRRU0FvVkdWemRDa3dIaGNOTWpZd01qSTJNVFUwTURNMldoY05Nell3TWpJME1UVTBNRE0yV2pCSU1Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERWhNQjhHQTFVRUF3d1lUR2xqWlc1elpTQkpjM04xWlhJZ1EwRWdLRlJsYzNRcE1JR2JNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWpBNEdHQUFRQmlVVlEwSGhaTXVBT3FpTzJsUElUK01NU0g0YmNsNkJPV25GbjIwNWJ6VGNSSTlSdVJkdHJYVk53cFwvSVB0ak1WWFRqXC9vVzByMTJIY3JFZExtaTlRSTZRQVNURUJ5V0xOVFNcL2Q5NElvWG1SWVFUbkMrUnRIK0hcLzRJMVRXWXc5MGFpaWcyeVYwRzFzMHFDZ0FpeUtzd2orU1Q2cjcxTk1cL2dlcG1sVzMrcWl2OVwvUFdqUWpCQU1CMEdBMVVkRGdRV0JCU05qQnd2K1wvaVlRdnBPT3F6MDJ1N3hhQVNTSVRBZkJnTlZIU01FR0RBV2dCUk1BU3NwMWtpYXdKbThZb0o2KzhcL3NxMjFYNHpBRkJnTXJaWEFEUVFBNE9rXC8reTBiZHptMlJVbWtIZDZRRlM2V2JCS2Y5TzR6ejNVYzdpQk1wS0lxMWtCbHErN1RiYmdNSEp1K2FZYk9EY1JXVCsrNXN4NGkyT3Nwa2dPc0oiLCJNSUlCdFRDQ0FXZWdBd0lCQWdJVUpteDhVcXRnbktjYXVRYnFiMDRUYVVCRytVSXdCUVlESzJWd01EOHhDekFKQmdOVkJBWVRBa1JGTVJZd0ZBWURWUVFLREExVGEzbHRZWFJwWXlCSGJXSklNUmd3RmdZRFZRUUREQTlNYVdObGJuTmxJRlJsYzNRZ1EwRXdIaGNOTWpZd01qSTJNVFV6TmpNMldoY05Nell3TWpJME1UVXpOak0yV2pCT01Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERW5NQ1VHQTFVRUF3d2VUR2xqWlc1elpTQkpiblJsY20xbFpHbGhkR1VnUTBFZ0tGUmxjM1FwTUNvd0JRWURLMlZ3QXlFQUczdnI4Tks3WVpzMXE2cFF0SmhIRGJUMnhNRDNDMnlzbXNuZW13MUZRbGFqWmpCa01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUhcL0JBUURBZ0VHTUIwR0ExVWREZ1FXQkJSTUFTc3Axa2lhd0ptOFlvSjYrOFwvc3EyMVg0ekFmQmdOVkhTTUVHREFXZ0JRQmlPK3lxTVRhZzU5ZUg4ZmdjRjlnTXNYZUV6QUZCZ01yWlhBRFFRQktkR0cybmpWa3JqMEwxbWVXVTROcGxaZHlHUTJxYUNxNFBSenk1OUg5WW1EUzc5M1JsTWE0TU9ad0FtVGtVUTV2YWt1dnR6MTM0SWl6NmpKa0RCZ0YiXSwiYWxnIjoiRVM1MTIifQ.eyJhdWQiOiJDcnlwdG9tYXRvciIsInN1YiI6ImNyeXB0b2JvdEBleGFtcGxlLmNvbSIsImtpZCI6IllFVjFFSXY5dllMR1lfR1RoQ0VPTGtleW1xUDBlZVhVVTdNbWxKaHFnR0EiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MVwvIiwiZXhwIjozMjUwMzY4MDAwMCwiaWF0Ijo5NDY2ODQ4MDAsInNlYXRzIjozfQ.AMBwG0CnIu8FHLsmynfT-JHwE-pwjXGh8SoExaYWY6n88cL18gzI_tYl3cjZSvLVZImg-VRZi7SkhDVt5zrOXX12AdNst1jJc0HJIA0dJUiM22b3XmkIjionTXmK-Njgp9XIQV4qtYoFPXjsL4KKTa95yx5oTvDD7ZAHFxur5_nN3y4t";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
@@ -47,8 +70,7 @@ public class LicenseCheckerTest {
@Test
public void testCheckInvalidLicensePayload() {
@SuppressWarnings("SpellCheckingInspection")
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
String license = "eyJ4NWMiOlsiTUlJQ0REQ0NBYjZnQXdJQkFnSVVETU96eEpZMzgxSmpkTjgyY1c3cWQrQjN2aDB3QlFZREsyVndNRTR4Q3pBSkJnTlZCQVlUQWtSRk1SWXdGQVlEVlFRS0RBMVRhM2x0WVhScFl5QkhiV0pJTVNjd0pRWURWUVFEREI1TWFXTmxibk5sSUVsdWRHVnliV1ZrYVdGMFpTQkRRU0FvVkdWemRDa3dIaGNOTWpZd01qSTJNVFUwTURNMldoY05Nell3TWpJME1UVTBNRE0yV2pCSU1Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERWhNQjhHQTFVRUF3d1lUR2xqWlc1elpTQkpjM04xWlhJZ1EwRWdLRlJsYzNRcE1JR2JNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWpBNEdHQUFRQmlVVlEwSGhaTXVBT3FpTzJsUElUK01NU0g0YmNsNkJPV25GbjIwNWJ6VGNSSTlSdVJkdHJYVk53cFwvSVB0ak1WWFRqXC9vVzByMTJIY3JFZExtaTlRSTZRQVNURUJ5V0xOVFNcL2Q5NElvWG1SWVFUbkMrUnRIK0hcLzRJMVRXWXc5MGFpaWcyeVYwRzFzMHFDZ0FpeUtzd2orU1Q2cjcxTk1cL2dlcG1sVzMrcWl2OVwvUFdqUWpCQU1CMEdBMVVkRGdRV0JCU05qQnd2K1wvaVlRdnBPT3F6MDJ1N3hhQVNTSVRBZkJnTlZIU01FR0RBV2dCUk1BU3NwMWtpYXdKbThZb0o2KzhcL3NxMjFYNHpBRkJnTXJaWEFEUVFBNE9rXC8reTBiZHptMlJVbWtIZDZRRlM2V2JCS2Y5TzR6ejNVYzdpQk1wS0lxMWtCbHErN1RiYmdNSEp1K2FZYk9EY1JXVCsrNXN4NGkyT3Nwa2dPc0oiLCJNSUlCdFRDQ0FXZWdBd0lCQWdJVUpteDhVcXRnbktjYXVRYnFiMDRUYVVCRytVSXdCUVlESzJWd01EOHhDekFKQmdOVkJBWVRBa1JGTVJZd0ZBWURWUVFLREExVGEzbHRZWFJwWXlCSGJXSklNUmd3RmdZRFZRUUREQTlNYVdObGJuTmxJRlJsYzNRZ1EwRXdIaGNOTWpZd01qSTJNVFV6TmpNMldoY05Nell3TWpJME1UVXpOak0yV2pCT01Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERW5NQ1VHQTFVRUF3d2VUR2xqWlc1elpTQkpiblJsY20xbFpHbGhkR1VnUTBFZ0tGUmxjM1FwTUNvd0JRWURLMlZ3QXlFQUczdnI4Tks3WVpzMXE2cFF0SmhIRGJUMnhNRDNDMnlzbXNuZW13MUZRbGFqWmpCa01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUhcL0JBUURBZ0VHTUIwR0ExVWREZ1FXQkJSTUFTc3Axa2lhd0ptOFlvSjYrOFwvc3EyMVg0ekFmQmdOVkhTTUVHREFXZ0JRQmlPK3lxTVRhZzU5ZUg4ZmdjRjlnTXNYZUV6QUZCZ01yWlhBRFFRQktkR0cybmpWa3JqMEwxbWVXVTROcGxaZHlHUTJxYUNxNFBSenk1OUg5WW1EUzc5M1JsTWE0TU9ad0FtVGtVUTV2YWt1dnR6MTM0SWl6NmpKa0RCZ0YiXSwiYWxnIjoiRVM1MTIifQ.EyJhdWQiOiJDcnlwdG9tYXRvciIsInN1YiI6ImNyeXB0b2JvdEBleGFtcGxlLmNvbSIsImtpZCI6IllFVjFFSXY5dllMR1lfR1RoQ0VPTGtleW1xUDBlZVhVVTdNbWxKaHFnR0EiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MVwvIiwiZXhwIjozMjUwMzY4MDAwMCwiaWF0Ijo5NDY2ODQ4MDAsInNlYXRzIjozfQ.AMBwG0CnIu8FHLsmynfT-JHwE-pwjXGh8SoExaYWY6n88cL18gzI_tYl3cjZSvLVZImg-VRZi7SkhDVt5zrOXX12AdNst1jJc0HJIA0dJUiM22b3XmkIjionTXmK-Njgp9XIQV4qtYoFPXjsL4KKTa95yx5oTvDD7ZAHFxur5_nN3y4t";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
@@ -57,12 +79,11 @@ public class LicenseCheckerTest {
@Test
public void testCheckInvalidLicenseSignature() {
@SuppressWarnings("SpellCheckingInspection")
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
String license = "eyJ4NWMiOlsiTUlJQ0REQ0NBYjZnQXdJQkFnSVVETU96eEpZMzgxSmpkTjgyY1c3cWQrQjN2aDB3QlFZREsyVndNRTR4Q3pBSkJnTlZCQVlUQWtSRk1SWXdGQVlEVlFRS0RBMVRhM2x0WVhScFl5QkhiV0pJTVNjd0pRWURWUVFEREI1TWFXTmxibk5sSUVsdWRHVnliV1ZrYVdGMFpTQkRRU0FvVkdWemRDa3dIaGNOTWpZd01qSTJNVFUwTURNMldoY05Nell3TWpJME1UVTBNRE0yV2pCSU1Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERWhNQjhHQTFVRUF3d1lUR2xqWlc1elpTQkpjM04xWlhJZ1EwRWdLRlJsYzNRcE1JR2JNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWpBNEdHQUFRQmlVVlEwSGhaTXVBT3FpTzJsUElUK01NU0g0YmNsNkJPV25GbjIwNWJ6VGNSSTlSdVJkdHJYVk53cFwvSVB0ak1WWFRqXC9vVzByMTJIY3JFZExtaTlRSTZRQVNURUJ5V0xOVFNcL2Q5NElvWG1SWVFUbkMrUnRIK0hcLzRJMVRXWXc5MGFpaWcyeVYwRzFzMHFDZ0FpeUtzd2orU1Q2cjcxTk1cL2dlcG1sVzMrcWl2OVwvUFdqUWpCQU1CMEdBMVVkRGdRV0JCU05qQnd2K1wvaVlRdnBPT3F6MDJ1N3hhQVNTSVRBZkJnTlZIU01FR0RBV2dCUk1BU3NwMWtpYXdKbThZb0o2KzhcL3NxMjFYNHpBRkJnTXJaWEFEUVFBNE9rXC8reTBiZHptMlJVbWtIZDZRRlM2V2JCS2Y5TzR6ejNVYzdpQk1wS0lxMWtCbHErN1RiYmdNSEp1K2FZYk9EY1JXVCsrNXN4NGkyT3Nwa2dPc0oiLCJNSUlCdFRDQ0FXZWdBd0lCQWdJVUpteDhVcXRnbktjYXVRYnFiMDRUYVVCRytVSXdCUVlESzJWd01EOHhDekFKQmdOVkJBWVRBa1JGTVJZd0ZBWURWUVFLREExVGEzbHRZWFJwWXlCSGJXSklNUmd3RmdZRFZRUUREQTlNYVdObGJuTmxJRlJsYzNRZ1EwRXdIaGNOTWpZd01qSTJNVFV6TmpNMldoY05Nell3TWpJME1UVXpOak0yV2pCT01Rc3dDUVlEVlFRR0V3SkVSVEVXTUJRR0ExVUVDZ3dOVTJ0NWJXRjBhV01nUjIxaVNERW5NQ1VHQTFVRUF3d2VUR2xqWlc1elpTQkpiblJsY20xbFpHbGhkR1VnUTBFZ0tGUmxjM1FwTUNvd0JRWURLMlZ3QXlFQUczdnI4Tks3WVpzMXE2cFF0SmhIRGJUMnhNRDNDMnlzbXNuZW13MUZRbGFqWmpCa01CSUdBMVVkRXdFQlwvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUhcL0JBUURBZ0VHTUIwR0ExVWREZ1FXQkJSTUFTc3Axa2lhd0ptOFlvSjYrOFwvc3EyMVg0ekFmQmdOVkhTTUVHREFXZ0JRQmlPK3lxTVRhZzU5ZUg4ZmdjRjlnTXNYZUV6QUZCZ01yWlhBRFFRQktkR0cybmpWa3JqMEwxbWVXVTROcGxaZHlHUTJxYUNxNFBSenk1OUg5WW1EUzc5M1JsTWE0TU9ad0FtVGtVUTV2YWt1dnR6MTM0SWl6NmpKa0RCZ0YiXSwiYWxnIjoiRVM1MTIifQ.eyJhdWQiOiJDcnlwdG9tYXRvciIsInN1YiI6ImNyeXB0b2JvdEBleGFtcGxlLmNvbSIsImtpZCI6IllFVjFFSXY5dllMR1lfR1RoQ0VPTGtleW1xUDBlZVhVVTdNbWxKaHFnR0EiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MVwvIiwiZXhwIjozMjUwMzY4MDAwMCwiaWF0Ijo5NDY2ODQ4MDAsInNlYXRzIjozfQ.AMBwG0CnIu8FHLsmynfT-JHwE-pwjXGh8SoExaYWY6n88cL18gzI_tYl3cjZSvLVZImg-VRZi7SkhDVt5zrOXX12AdNst1jJc0HJIA0dJUiM22b3XmkIjionTXmK-Njgp9XIQV4qtYoFPXjsL4KKTa95yx5oTvDD7ZAHFxur5_nN3y4T";
Optional<DecodedJWT> decoded = licenseChecker.check(license);
Assertions.assertFalse(decoded.isPresent());
}
}
}

View File

@@ -29,8 +29,7 @@ public class SettingsJsonTest {
"checkForUpdatesEnabled": true,
"port": 8080,
"language": "de-DE",
"numTrayNotifications": 42,
"trustedHosts": null
"numTrayNotifications": 42
}
""";
@@ -45,7 +44,6 @@ public class SettingsJsonTest {
Assertions.assertTrue(jsonObj.autoCloseVaults);
Assertions.assertEquals("de-DE", jsonObj.language);
Assertions.assertEquals(42, jsonObj.numTrayNotifications);
Assertions.assertEquals(0, jsonObj.trustedHosts.size());
}
@SuppressWarnings("SpellCheckingInspection")

View File

@@ -1,47 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mockito;
import java.util.Set;
class CheckHostTrustControllerTest {
@ParameterizedTest
@CsvSource({
"https://auth.example.com, https://hub.example.com, true",
"https://hub.example.com, https://hub.example.com, true",
"https://auth.example.com, https://auth.example.com, true",
"https://auth.example.com, https://wrong.example.com, false",
"https://wrong.example.com, https://wrong.example.com, false"
})
void testContainsAllowedHosts(String apiBase, String authEndpoint, boolean expectedResult) {
var hubConfig = new HubConfig();
hubConfig.apiBaseUrl = apiBase;
hubConfig.authEndpoint = authEndpoint;
var controller = new CheckHostTrustController(Mockito.mock(), hubConfig, Mockito.mock(), Mockito.mock(), Mockito.mock(), Mockito.mock(), Mockito.mock(), Mockito.mock());
var actualResult = controller.containsAllowedHosts(Set.of("https://auth.example.com", "https://hub.example.com"));
Assertions.assertEquals(expectedResult, actualResult);
}
@ParameterizedTest
@CsvSource({
"https://example.com, https://example.com",
"https://example.com/foo/bar, https://example.com",
"https://example.com:8080, https://example.com:8080",
"https://user@example.com:8080/foo/bar, https://example.com:8080",
"https://user@example.com:443/foo/bar, https://example.com:443",
"http://user@example.com:80/foo/bar?foo=bar, http://example.com:80",
"http://user@example.com:8080/foo/bar?foo=bar, http://example.com:8080"
})
void testGetAuthority(String input, String expected) {
var actual = CheckHostTrustController.getAuthority(input);
Assertions.assertEquals(expected, actual);
}
}

View File

@@ -99,14 +99,5 @@
<packageUrl regex="true">^pkg:maven/org\.purejava/flatpak-update-portal@.*$</packageUrl>
<cpe>cpe:/a:flatpak:flatpak</cpe>
</suppress>
<suppress>
<notes><![CDATA[
Jetty is used as a local server only.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.eclipse\.jetty/jetty-.*$</packageUrl>
<vulnerabilityName>CVE-2025-11143</vulnerabilityName>
<cve>CVE-2025-11143</cve>
</suppress>
</suppressions>