From 52543c2d42c4e20454f61fde6e13a6503b2a887e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 4 Sep 2024 17:13:02 +0200 Subject: [PATCH] remove old code & prepare pom --- pom.xml | 65 +++++-- src/main/java/org/cryptomator/cli/Args.java | 176 ------------------ .../org/cryptomator/cli/CryptomatorCli.java | 167 ----------------- .../java/org/cryptomator/cli/Version.java | 11 -- .../cryptomator/cli/frontend/FuseMount.java | 65 ------- .../org/cryptomator/cli/frontend/WebDav.java | 43 ----- .../cli/pwd/PasswordFromFileStrategy.java | 43 ----- .../cli/pwd/PasswordFromPropertyStrategy.java | 29 --- .../cli/pwd/PasswordFromStdInputStrategy.java | 53 ------ .../cryptomator/cli/pwd/PasswordStrategy.java | 6 - 10 files changed, 49 insertions(+), 609 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cli/Args.java delete mode 100644 src/main/java/org/cryptomator/cli/CryptomatorCli.java delete mode 100644 src/main/java/org/cryptomator/cli/Version.java delete mode 100644 src/main/java/org/cryptomator/cli/frontend/FuseMount.java delete mode 100644 src/main/java/org/cryptomator/cli/frontend/WebDav.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java delete mode 100644 src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java diff --git a/pom.xml b/pom.xml index 026e7d1..f90328c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,20 +2,34 @@ 4.0.0 org.cryptomator cli - 0.6.0-SNAPSHOT + 1.0.0-SNAPSHOT Cryptomator CLI Command line program to access encrypted files via WebDAV. https://github.com/cryptomator/cli - 2.6.5 - 2.0.3 - 1.5.0 - 1.2.13 - 1.3.3 - - 17 UTF-8 + UTF-8 + 22 + + + org.cryptomator.hubcli.HubCli + -Ob + + + 2.7.0-beta1 + 2.0.6 + 5.0.0 + 1.5.6 + 2.0.16 + + + 24.0.1 + 4.7.6 + + + 3.13.0 + 3.7.1 @@ -34,6 +48,13 @@ cryptomator.org http://cryptomator.org + + Armin Schrenk + armin.schrenk@skymatic.de + +1 + Skymatic GmbH + https://skymatic.de + @@ -53,14 +74,18 @@ ${fuse-nio.version} - - commons-cli - commons-cli - ${commons.cli.version} + info.picocli + picocli + ${picocli.version} + + org.slf4j + slf4j-api + ${slf4j.version} + ch.qos.logback logback-core @@ -77,18 +102,26 @@ maven-compiler-plugin - 3.8.1 + ${maven-compiler.version} - ${java.version} - ${java.version} ${java.version} true + + + info.picocli + picocli-codegen + ${picocli.version} + + + + -Aproject=${project.groupId}/${project.artifactId} + maven-assembly-plugin - 3.3.0 + ${maven-assembly.version} make-assembly diff --git a/src/main/java/org/cryptomator/cli/Args.java b/src/main/java/org/cryptomator/cli/Args.java deleted file mode 100644 index 5b8ab1d..0000000 --- a/src/main/java/org/cryptomator/cli/Args.java +++ /dev/null @@ -1,176 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Sebastian Stenzel and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE.txt. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - *******************************************************************************/ -package org.cryptomator.cli; - -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 org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -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. - */ -public class Args { - - private static final String USAGE = "java -jar cryptomator-cli.jar" // - + " --bind localhost --port 8080" // - + " --vault mySecretVault=/path/to/vault --password mySecretVault=FooBar3000" // - + " --vault myOtherVault=/path/to/other/vault --password myOtherVault=BarFoo4000" // - + " --vault myThirdVault=/path/to/third/vault --passwordfile myThirdVault=/path/to/passwordfile"; - private static final Options OPTIONS = new Options(); - static { - OPTIONS.addOption(Option.builder() // - .longOpt("bind") // - .argName("WebDAV bind address") // - .desc("TCP socket bind address of the WebDAV server. Use 0.0.0.0 to accept all incoming connections.") // - .hasArg() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("port") // - .argName("WebDAV port") // - .desc("TCP port, the WebDAV server should listen on.") // - .hasArg() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("vault") // - .argName("Path of a vault") // - .desc("Format must be vaultName=/path/to/vault") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("password") // - .argName("Password of a vault") // - .desc("Format must be vaultName=password") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("passwordfile") // - .argName("Passwordfile for a vault") // - .desc("Format must be vaultName=passwordfile") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("fusemount") // - .argName("mount point") // - .desc("Format must be vaultName=mountpoint") // - .valueSeparator() // - .hasArgs() // - .build()); - OPTIONS.addOption(Option.builder() // - .longOpt("version") // - .desc("Prints version and exits") // - .hasArg(false) - .build()); - } - - private final String bindAddr; - private final int port; - private final boolean hasValidWebDavConfig; - private final Properties vaultPaths; - private final Properties vaultPasswords; - private final Properties vaultPasswordFiles; - private final Map passwordStrategies; - private final Properties fuseMountPoints; - private final boolean hasVersion; - - public Args(CommandLine commandLine) throws ParseException { - if (commandLine.hasOption("bind") && commandLine.hasOption("port")) { - hasValidWebDavConfig = true; - this.bindAddr = commandLine.getOptionValue("bind", "localhost"); - this.port = Integer.parseInt(commandLine.getOptionValue("port", "0")); - } else { - hasValidWebDavConfig = false; - this.bindAddr = ""; - this.port = -1; - } - this.vaultPaths = commandLine.getOptionProperties("vault"); - this.vaultPasswords = commandLine.getOptionProperties("password"); - this.vaultPasswordFiles = commandLine.getOptionProperties("passwordfile"); - this.passwordStrategies = new HashMap<>(); - this.fuseMountPoints = commandLine.getOptionProperties("fusemount"); - this.hasVersion = commandLine.hasOption("version"); - } - - public boolean hasValidWebDavConf() { - return hasValidWebDavConfig; - } - - public String getBindAddr() { - return bindAddr; - } - - public int getPort() { - return port; - } - - public Set getVaultNames() { - return vaultPaths.keySet().stream().map(String.class::cast).collect(Collectors.toSet()); - } - - public String getVaultPath(String vaultName) { - return vaultPaths.getProperty(vaultName); - } - - public static Args parse(String[] arguments) throws ParseException { - CommandLine commandLine = new DefaultParser().parse(OPTIONS, arguments); - return new Args(commandLine); - } - - 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); - } - - public Path getFuseMountPoint(String vaultName) { - String mountPoint = fuseMountPoints.getProperty(vaultName); - if (mountPoint == null) { - return null; - } - Path mountPointPath = Paths.get(mountPoint); - return mountPointPath; - } - - public boolean hasVersion() { - return hasVersion; - } -} diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java deleted file mode 100644 index d416ec8..0000000 --- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Sebastian Stenzel and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE.txt. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - *******************************************************************************/ -package org.cryptomator.cli; - -import org.cryptomator.cli.frontend.FuseMount; -import org.cryptomator.cli.frontend.WebDav; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Set; - -import com.google.common.base.Preconditions; -import org.apache.commons.cli.ParseException; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; -import org.cryptomator.cryptofs.CryptoFileSystemProvider; -import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CryptomatorCli { - - private static final Logger LOG = LoggerFactory.getLogger(CryptomatorCli.class); - - private static final byte[] PEPPER = new byte[0]; - private static final String SCHEME = "masterkeyfile"; - - public static void main(String[] rawArgs) throws IOException { - try { - Args args = Args.parse(rawArgs); - - // print version and exit if --version option present. - if (args.hasVersion()) { - printVersion(); - return; - } - - validate(args); - startup(args); - } catch (ParseException e) { - LOG.error("Invalid or missing arguments", e); - Args.printUsage(); - } catch (IllegalArgumentException e) { - LOG.error(e.getMessage()); - Args.printUsage(); - } - } - - private static void validate(Args args) throws IllegalArgumentException { - Set vaultNames = args.getVaultNames(); - if (args.hasValidWebDavConf() && (args.getPort() < 0 || args.getPort() > 65536)) { - throw new IllegalArgumentException("Invalid WebDAV Port."); - } - - if (vaultNames.size() == 0) { - throw new IllegalArgumentException("No vault specified."); - } - - for (String vaultName : vaultNames) { - Path vaultPath = Paths.get(args.getVaultPath(vaultName)); - if (!Files.isDirectory(vaultPath)) { - throw new IllegalArgumentException("Not a directory: " + vaultPath); - } - args.addPasswortStrategy(vaultName).validate(); - - Path mountPoint = args.getFuseMountPoint(vaultName); - if (mountPoint != null && !Files.isDirectory(mountPoint)) { - throw new IllegalArgumentException("Fuse mount point does not exist: " + mountPoint); - } - } - } - - private static void startup(Args args) throws IOException { - LOG.info("Starting Cryptomator CLI {}", Version.IMPLEMENTATION_VERSION); - Optional server = initWebDavServer(args); - ArrayList mounts = new ArrayList<>(); - - SecureRandom secureRandom; - try { - secureRandom = SecureRandom.getInstanceStrong(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e); - } - MasterkeyFileAccess masterkeyFileAccess = new MasterkeyFileAccess(PEPPER, secureRandom); - - for (String vaultName : args.getVaultNames()) { - Path vaultPath = Paths.get(args.getVaultPath(vaultName)); - LOG.info("Unlocking vault \"{}\" located at {}", vaultName, vaultPath); - String vaultPassword = args.getPasswordStrategy(vaultName).password(); - CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties() - .withKeyLoader(keyId -> { - Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME); - Path keyFilePath = vaultPath.resolve(keyId.getSchemeSpecificPart()); - return masterkeyFileAccess.load(keyFilePath, vaultPassword); - }) - .build(); - - Path vaultRoot = CryptoFileSystemProvider.newFileSystem(vaultPath, properties).getPath("/"); - - Path fuseMountPoint = args.getFuseMountPoint(vaultName); - if (fuseMountPoint != null) { - FuseMount newMount = new FuseMount(vaultRoot, fuseMountPoint); - if (newMount.mount()) { - mounts.add(newMount); - } - } - - server.ifPresent(serv -> serv.addServlet(vaultRoot, vaultName)); - } - - waitForShutdown(() -> { - LOG.info("Shutting down..."); - try { - server.ifPresent(serv -> serv.stop()); - - for (FuseMount mount : mounts) { - mount.unmount(); - } - LOG.info("Shutdown successful."); - } catch (Throwable e) { - LOG.error("Error during shutdown", e); - } - }); - } - - private static Optional initWebDavServer(Args args) { - Optional server = Optional.empty(); - if (args.hasValidWebDavConf()) { - server = Optional.of(new WebDav(args.getBindAddr(), args.getPort())); - } - return server; - } - - private static void waitForShutdown(Runnable runnable) { - Runtime.getRuntime().addShutdownHook(new Thread(runnable)); - LOG.info("Press Ctrl+C to terminate."); - - // Block the main thread infinitely as otherwise when using - // Fuse mounts the application quits immediately. - try { - Object mainThreadBlockLock = new Object(); - synchronized (mainThreadBlockLock) { - while (true) { - mainThreadBlockLock.wait(); - } - } - } catch (Exception e) { - LOG.error("Main thread blocking failed."); - } - } - - private static void printVersion() { - String version = Version.IMPLEMENTATION_VERSION; - System.out.println(version); - } -} diff --git a/src/main/java/org/cryptomator/cli/Version.java b/src/main/java/org/cryptomator/cli/Version.java deleted file mode 100644 index b1fac09..0000000 --- a/src/main/java/org/cryptomator/cli/Version.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.cryptomator.cli; - -public class Version { - public static final String IMPLEMENTATION_VERSION = getImplementationVersion(); - - private static String getImplementationVersion() { - return Version.class - .getPackage() - .getImplementationVersion(); - } -} diff --git a/src/main/java/org/cryptomator/cli/frontend/FuseMount.java b/src/main/java/org/cryptomator/cli/frontend/FuseMount.java deleted file mode 100644 index 40ca15a..0000000 --- a/src/main/java/org/cryptomator/cli/frontend/FuseMount.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.cryptomator.cli.frontend; - -import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; -import org.cryptomator.frontend.fuse.mount.FuseMountException; -import org.cryptomator.frontend.fuse.mount.FuseMountFactory; -import org.cryptomator.frontend.fuse.mount.Mount; -import org.cryptomator.frontend.fuse.mount.Mounter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.file.Path; - -public class FuseMount { - private static final Logger LOG = LoggerFactory.getLogger(FuseMount.class); - - private Path vaultRoot; - private Path mountPoint; - private Mount mnt; - - public FuseMount(Path vaultRoot, Path mountPoint) { - this.vaultRoot = vaultRoot; - this.mountPoint = mountPoint; - this.mnt = null; - } - - public boolean mount() { - if (mnt != null) { - LOG.info("Already mounted to {}", mountPoint); - return false; - } - - try { - Mounter mounter = FuseMountFactory.getMounter(); - EnvironmentVariables envVars = EnvironmentVariables.create() // - .withFlags(mounter.defaultMountFlags()) // - .withFileNameTranscoder(mounter.defaultFileNameTranscoder()) // - .withMountPoint(mountPoint).build(); - mnt = mounter.mount(vaultRoot, envVars); - LOG.info("Mounted to {}", mountPoint); - } catch (FuseMountException e) { - LOG.error("Can't mount: {}, error: {}", mountPoint, e.getMessage()); - return false; - } - return true; - } - - public void unmount() { - try { - mnt.unmount(); - LOG.info("Unmounted {}", mountPoint); - } catch (FuseMountException e) { - LOG.error("Can't unmount gracefully: {}. Force unmount.", e.getMessage()); - forceUnmount(); - } - } - - private void forceUnmount() { - try { - mnt.unmountForced(); - LOG.info("Unmounted {}", mountPoint); - } catch (FuseMountException e) { - LOG.error("Force unmount failed: {}", e.getMessage()); - } - } -} diff --git a/src/main/java/org/cryptomator/cli/frontend/WebDav.java b/src/main/java/org/cryptomator/cli/frontend/WebDav.java deleted file mode 100644 index 7eec299..0000000 --- a/src/main/java/org/cryptomator/cli/frontend/WebDav.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cryptomator.cli.frontend; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.nio.file.Path; -import java.util.ArrayList; - -import org.cryptomator.frontend.webdav.WebDavServer; -import org.cryptomator.frontend.webdav.servlet.WebDavServletController; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WebDav { - private static final Logger LOG = LoggerFactory.getLogger(WebDav.class); - - private final WebDavServer server; - private ArrayList servlets = new ArrayList<>(); - - public WebDav(String bindAddr, int port) { - try { - server = WebDavServer.create(new InetSocketAddress(InetAddress.getByName(bindAddr), port)); - server.start(); - LOG.info("WebDAV server started: {}:{}", bindAddr, port); - } catch (UnknownHostException e) { - throw new IllegalStateException("Error creating WebDavServer", e); - } - } - - public void stop() { - for (WebDavServletController controller : servlets) { - controller.stop(); - } - server.stop(); - } - - public void addServlet(Path vaultRoot, String vaultName) { - WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName); - servlets.add(servlet); - servlet.start(); - } -} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java b/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java deleted file mode 100644 index cd20261..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromFileStrategy.java +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index 8d5909b..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromPropertyStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index e374e7f..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordFromStdInputStrategy.java +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index adf7c89..0000000 --- a/src/main/java/org/cryptomator/cli/pwd/PasswordStrategy.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.cryptomator.cli.pwd; - -public interface PasswordStrategy { - String password(); - void validate() throws IllegalArgumentException; -}