diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5a718df --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,73 @@ +name: Build + +on: + [push] + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + outputs: + artifact-version: ${{ steps.setversion.outputs.version }} + env: + BUILD_VERSION: SNAPSHOT + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 14 + - uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Ensure to use tagged version + run: mvn versions:set --file ./pom.xml -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' + if: startsWith(github.ref, 'refs/tags/') + - name: Export the project version to the job environment and fix it as an ouput of this job + id: setversion + run: | + v=$(mvn help:evaluate "-Dexpression=project.version" -q -DforceStdout) + echo "::set-env name=BUILD_VERSION::${v}" + echo "::set-output name=version::${v}" + - name: Build and Test + run: mvn -B install + - name: Upload snapshot artifact cryptomator-cli-${{ env.BUILD_VERSION }}.jar + uses: actions/upload-artifact@v2 + with: + name: cryptomator-cli-${{ env.BUILD_VERSION }}.jar + path: target/cryptomator-cli-*.jar + + release: + name: Draft a Release on GitHub Releases and uploads the build artifacts to it + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Download cryptomator-cli.jar + uses: actions/download-artifact@v1 + with: + name: cryptomator-cli-${{ needs.build.outputs.artifact-version }}.jar + path: . + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: | + :construction: Work in Progress + draft: true + prerelease: false + - name: Upload cryptomator-cli-${{ needs.build.outputs.artifact-version }}.jar to GitHub Releases + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: cryptomator-cli-${{ needs.build.outputs.artifact-version }}.jar + asset_name: cryptomator-cli-${{ needs.build.outputs.artifact-version }}.jar + asset_content_type: application/jar diff --git a/.gitignore b/.gitignore index 5ddd9fc..3ef5c20 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ test-output/ out/ .idea_modules/ *.iws + +*.iml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f862116..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: java -sudo: false -jdk: -- oraclejdk8 -install: -- mvn dependency:go-offline -script: -- mvn clean install -cache: - directories: - - "$HOME/.m2" -deploy: -- provider: releases - api_key: - secure: wclnmySSl1rMEWn5oe2cB1YamgmubDJxLo3vaQtpyKAVqdN6sy0VKa2/UjucxZY2mHJKQjWSjhpXSpROH2Ufp0RoC9wsBaICa+dEDcFO8dxxsczuOHsJzTr8ewfaIMzaPVxltOtyhEQcA+u5oX5l8aWFHpqYD8V86NKo5l0PF9wGTrjSksbzDq44lmU/dzjC1VaDjLTIjaGsqYQf6b3SXD8k3Loo1vlp/OFAryvOSZU9/53b7KfpAx7FckYZHLPOEPQDUIpKr5xTr2dRATaHXxT/9xqiIZz7kHOGDQYLo3lszdQdU+N8oL50RrPvkBtEFHIALv66CclTlP+Z6FoSLAgv13u+b2XpdmG/VeI1MM4tdW9fDPAQ1AQrH5nqiM4sKOLeViPtXYuKwH8TvNsTIA1Jl6QqjfmbEEGDefWUiRcc/g/O6Rxz3hjBB5lDvyKpT8nfuk0j1qYlLPjTuaGGRP5Ajjw+rGx4nTfA38Me5dldZ67bmbuBVDEKvSctTF86nntQm0Xf7vSUkd2eRZGT0Tj4SzeLVSroqFMOhlYqbNz4Zc67QwtRAjd3y5+6HqjwiVzzJM747PNFpy5jPee71z/Sdw/RtIGv0TroJmSwoIlJjbRYiYXT176xKZtzrOAUHwhDytzRqMi6DZj+ZSROOX+Rdwuc+lRRgvdFMDngMQI= - file_glob: true - file: - - "target/cryptomator-cli-*.jar" - skip_cleanup: true - prerelease: true - on: - repo: cryptomator/cli - branch: master - tags: true diff --git a/README.md b/README.md index d111a44..fe24100 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -[![Build Status](https://travis-ci.org/cryptomator/cli.svg?branch=develop)](https://travis-ci.org/cryptomator/cli) +[![Build](https://github.com/cryptomator/cli/workflows/Build/badge.svg)](https://github.com/cryptomator/cli/actions?query=workflow%3ABuild) +[![Latest Release](https://img.shields.io/github/release/cryptomator/cli/all.svg)](https://github.com/cryptomator/cli/releases/latest) -# Cryptomator CLI version +# Cryptomator CLI -This is a minimal command line program which unlocks vaults, which can then be accessed via an embedded WebDAV server. +This is a minimal command-line program that unlocks vaults which can then be accessed via an embedded WebDAV server. ## Disclaimer @@ -10,18 +11,54 @@ This project is in an early stage and not ready for production use. We recommend ## Download and Usage -Download the jar file via [GitHub Releases](https://github.com/cryptomator/cli/releases) +Download the jar file via [GitHub Releases](https://github.com/cryptomator/cli/releases). -Cryptomator CLI depends on a Java 8 JRE. In addition the JCE unlimited strength policy files (needed for 256-bit keys) must be installed. +Cryptomator CLI requires that at least JDK 11 is present on your system. ```sh java -jar cryptomator-cli-x.y.z.jar \ --vault demoVault=/path/to/vault --password demoVault=topSecret \ --vault otherVault=/path/to/differentVault --passwordfile otherVault=/path/to/fileWithPassword \ - --bind 0.0.0.0 --port 8080 + --bind 127.0.0.1 --port 8080 # you can now mount http://localhost:8080/demoVault/ ``` +Then you can access the vault using any WebDAV client. + +### Linux via davfs2 + +First, you need to create a mount point for your vault + +```sh +sudo mkdir /media/your/mounted/folder +``` + +Then you can mount the vault + +```sh +sudo mount -t davfs http://localhost:8080/demoVault/ /media/your/mounted/folder +``` + +To unmount the vault, run + +```sh +sudo umount /media/your/mounted/folder +``` + +### macOS via AppleScript + +Mount the vault with + +```sh +osascript -e 'mount volume "http://localhost:8080/demoVault/"' +``` + +Unmount the vault with + +```sh +osascript -e 'tell application "Finder" to if "demoVault" exists then eject "demoVault"' +``` + ## License This project is dual-licensed under the AGPLv3 for FOSS projects as well as a commercial license derived from the LGPL for independent software vendors and resellers. If you want to use this library in applications, that are *not* licensed under the AGPL, feel free to contact our [support team](https://cryptomator.org/help/). diff --git a/pom.xml b/pom.xml index 7de11e6..d0f1f12 100644 --- a/pom.xml +++ b/pom.xml @@ -2,18 +2,18 @@ 4.0.0 org.cryptomator cli - 0.3.1 + 0.4.0 Cryptomator CLI Command line program to access encrypted files via WebDAV. https://github.com/cryptomator/cli - 1.4.0 - 0.6.2 - 1.3.1 - 1.2.2 + 1.9.10 + 1.0.11 + 1.4 + 1.2.3 - 1.8 + 11 UTF-8 @@ -71,17 +71,18 @@ maven-compiler-plugin - 3.6.1 + 3.8.1 ${java.version} ${java.version} + ${java.version} true maven-assembly-plugin - 3.0.0 + 3.3.0 make-assembly diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java index c87b0cf..13de342 100644 --- a/src/main/java/org/cryptomator/cli/Args.java +++ b/src/main/java/org/cryptomator/cli/Args.java @@ -8,14 +8,12 @@ *******************************************************************************/ package org.cryptomator.cli; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; @@ -23,6 +21,10 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.cryptomator.cli.pwd.PasswordFromFileStrategy; +import org.cryptomator.cli.pwd.PasswordFromStdInputStrategy; +import org.cryptomator.cli.pwd.PasswordStrategy; +import org.cryptomator.cli.pwd.PasswordFromPropertyStrategy; /** * Parses program arguments. Does not validate them. @@ -76,10 +78,7 @@ public class Args { private final Properties vaultPaths; private final Properties vaultPasswords; private final Properties vaultPasswordFiles; - - private boolean hasPasswordOrPasswordFile(Object vaultPath) { - return vaultPasswords.containsKey(vaultPath) || vaultPasswordFiles.containsKey(vaultPath); - } + private final Map passwordStrategies; public Args(CommandLine commandLine) throws ParseException { this.bindAddr = commandLine.getOptionValue("bind", "localhost"); @@ -87,6 +86,7 @@ public Args(CommandLine commandLine) throws ParseException { this.vaultPaths = commandLine.getOptionProperties("vault"); this.vaultPasswords = commandLine.getOptionProperties("password"); this.vaultPasswordFiles = commandLine.getOptionProperties("passwordfile"); + this.passwordStrategies = new HashMap<>(); } public String getBindAddr() { @@ -98,32 +98,13 @@ public int getPort() { } public Set getVaultNames() { - return vaultPaths.keySet().stream().filter(this::hasPasswordOrPasswordFile).map(String.class::cast).collect(Collectors.toSet()); + return vaultPaths.keySet().stream().map(String.class::cast).collect(Collectors.toSet()); } public String getVaultPath(String vaultName) { return vaultPaths.getProperty(vaultName); } - public String getVaultPasswordPath(String vaultName) { - return vaultPasswordFiles.getProperty(vaultName); - } - - public String getVaultPassword(String vaultName) { - if (vaultPasswords.getProperty(vaultName) == null) { - Path vaultPasswordPath = Paths.get(vaultPasswordFiles.getProperty(vaultName)); - if (Files.isReadable(vaultPasswordPath) && Files.isRegularFile(vaultPasswordPath)) { - try (Stream lines = Files.lines(vaultPasswordPath)) { - return lines.findFirst().get().toString(); - } catch (IOException e) { - return null; - } - } - return null; - } - return vaultPasswords.getProperty(vaultName); - } - public static Args parse(String[] arguments) throws ParseException { CommandLine commandLine = new DefaultParser().parse(OPTIONS, arguments); return new Args(commandLine); @@ -133,4 +114,26 @@ public static void printUsage() { new HelpFormatter().printHelp(USAGE, OPTIONS); } + public PasswordStrategy addPasswortStrategy(final String vaultName) { + PasswordStrategy passwordStrategy = new PasswordFromStdInputStrategy(vaultName); + + if (vaultPasswords.getProperty(vaultName) != null) { + passwordStrategy = new PasswordFromPropertyStrategy( + vaultName, + vaultPasswords.getProperty(vaultName) + ); + } else if (vaultPasswordFiles.getProperty(vaultName) != null) { + passwordStrategy = new PasswordFromFileStrategy( + vaultName, + Paths.get(vaultPasswordFiles.getProperty(vaultName)) + ); + } + + this.passwordStrategies.put(vaultName, passwordStrategy); + return passwordStrategy; + } + + public PasswordStrategy getPasswordStrategy(final String vaultName) { + return passwordStrategies.get(vaultName); + } } diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java index 76ec3df..ab7940b 100644 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java @@ -12,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Set; import org.apache.commons.cli.ParseException; import org.cryptomator.cryptofs.CryptoFileSystemProperties; @@ -40,22 +41,21 @@ public static void main(String[] rawArgs) throws IOException { } private static void validate(Args args) throws IllegalArgumentException { + Set vaultNames = args.getVaultNames(); if (args.getPort() < 0 || args.getPort() > 65536) { throw new IllegalArgumentException("Invalid WebDAV Port."); } - if (args.getVaultNames().size() == 0) { + if (vaultNames.size() == 0) { throw new IllegalArgumentException("No vault specified."); } - for (String vaultName : args.getVaultNames()) { + for (String vaultName : vaultNames) { Path vaultPath = Paths.get(args.getVaultPath(vaultName)); - if ((args.getVaultPasswordPath(vaultName) != null) && args.getVaultPassword(vaultName) == null) { - throw new IllegalArgumentException("Cannot read password from file: " + Paths.get(args.getVaultPasswordPath(vaultName))); - } if (!Files.isDirectory(vaultPath)) { throw new IllegalArgumentException("Not a directory: " + vaultPath); } + args.addPasswortStrategy(vaultName).validate(); } } @@ -67,7 +67,7 @@ private static void startup(Args args) throws IOException { for (String vaultName : args.getVaultNames()) { Path vaultPath = Paths.get(args.getVaultPath(vaultName)); LOG.info("Unlocking vault \"{}\" located at {}", vaultName, vaultPath); - String vaultPassword = args.getVaultPassword(vaultName); + String vaultPassword = args.getPasswordStrategy(vaultName).password(); CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase(vaultPassword).build(); Path vaultRoot = CryptoFileSystemProvider.newFileSystem(vaultPath, properties).getPath("/"); WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName); diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java new file mode 100644 index 0000000..cd20261 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java @@ -0,0 +1,43 @@ +package org.cryptomator.cli.pwd; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class PasswordFromFileStrategy implements PasswordStrategy { + private static final Logger LOG = LoggerFactory.getLogger(PasswordFromFileStrategy.class); + + private final String vaultName; + private final Path pathToFile; + + public PasswordFromFileStrategy(final String vaultName, final Path pathToFile) { + this.vaultName = vaultName; + this.pathToFile = pathToFile; + } + + @Override + public String password() { + LOG.info("Vault " + "'" + vaultName + "'" + " password from file."); + + if (Files.isReadable(pathToFile) && Files.isRegularFile(pathToFile)) { + try (Stream lines = Files.lines(pathToFile)) { + return lines.findFirst().get().toString(); + } catch (IOException e) { + return null; + } + } + return null; + } + + @Override + public void validate() throws IllegalArgumentException { + if (!Files.isReadable(pathToFile)) { + throw new IllegalArgumentException("Cannot read password from file: " + pathToFile); + } + } + +} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java new file mode 100644 index 0000000..8d5909b --- /dev/null +++ b/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java @@ -0,0 +1,29 @@ +package org.cryptomator.cli.pwd; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PasswordFromPropertyStrategy implements PasswordStrategy { + private static final Logger LOG = LoggerFactory.getLogger(PasswordFromPropertyStrategy.class); + + private final String vaultName; + private final String password; + + public PasswordFromPropertyStrategy(final String vaultName, final String password) { + this.vaultName = vaultName; + this.password = password; + } + + @Override + public String password() { + LOG.info("Vault " + "'" + vaultName + "'" + " password from property."); + return this.password; + } + + @Override + public void validate() throws IllegalArgumentException { + if (password.equals("")) { + throw new IllegalArgumentException("Invalid password"); + } + } +} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java new file mode 100644 index 0000000..e374e7f --- /dev/null +++ b/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java @@ -0,0 +1,53 @@ +package org.cryptomator.cli.pwd; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.Console; +import java.io.IOException; +import java.io.InputStreamReader; + +public class PasswordFromStdInputStrategy implements PasswordStrategy { + private static final Logger LOG = LoggerFactory.getLogger(PasswordFromStdInputStrategy.class); + + private final String vaultName; + private final String inputMessage = "Enter password for vault '%s': "; + + public PasswordFromStdInputStrategy(final String vaultName) { + this.vaultName = vaultName; + } + + @Override + public String password() { + LOG.info("Vault " + "'" + vaultName + "'" + " password from standard input."); + + String password = ""; + Console console = System.console(); + if (console == null) { + LOG.warn("No console: non-interactive mode, instead use insecure replacement, PW is shown!"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + System.out.println(String.format(inputMessage, vaultName)); + + try { + password = reader.readLine(); + } catch (IOException e) { + LOG.error("There was an error reading line from console."); + e.printStackTrace(); + } + } else { + System.out.println(String.format(inputMessage, vaultName)); + password = new String(console.readPassword()); + } + + return password; + } + + @Override + public void validate() throws IllegalArgumentException { + if (vaultName.equals("")) { + throw new IllegalArgumentException("Invalid vault name"); + } + } +} diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java new file mode 100644 index 0000000..adf7c89 --- /dev/null +++ b/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java @@ -0,0 +1,6 @@ +package org.cryptomator.cli.pwd; + +public interface PasswordStrategy { + String password(); + void validate() throws IllegalArgumentException; +}