From 204e1e22c63749ab86dcc4cde11fdc8946c079ee Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sun, 10 Jun 2018 16:16:51 +0700 Subject: [PATCH] Experiment with PGP signed messages --- source/net/filebot/License.java | 42 ++++++++++++++++------ source/net/filebot/LicenseModel.java | 18 +--------- source/net/filebot/Main.java | 14 +++++++- source/net/filebot/Settings.java | 11 +++++- source/net/filebot/cli/ArgumentBean.java | 7 ++++ source/net/filebot/util/FileUtilities.java | 4 +++ 6 files changed, 66 insertions(+), 30 deletions(-) diff --git a/source/net/filebot/License.java b/source/net/filebot/License.java index da6773f3..68a67230 100644 --- a/source/net/filebot/License.java +++ b/source/net/filebot/License.java @@ -2,10 +2,12 @@ package net.filebot; import static java.nio.charset.StandardCharsets.*; import static java.util.stream.Collectors.*; +import static net.filebot.util.FileUtilities.*; import static net.filebot.util.JsonUtilities.*; import static net.filebot.util.RegularExpressions.*; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.Serializable; import java.net.URL; import java.time.Instant; @@ -25,39 +27,38 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import net.filebot.util.ByteBufferOutputStream; +import net.filebot.util.SystemProperty; import net.filebot.web.WebRequest; public class License implements Serializable { - private byte[] bytes; - + private String product; private long id; private long expires; private Exception error; public License(byte[] bytes) { - this.bytes = bytes; - try { // verify and get clear signed content - Map properties = getProperties(); + Map properties = getProperties(bytes); + this.product = properties.get("Product"); this.id = Long.parseLong(properties.get("Order")); this.expires = LocalDate.parse(properties.get("Valid-Until"), DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(ZoneOffset.UTC).plusDays(1).minusSeconds(1).toInstant().toEpochMilli(); // verify license online - verifyLicense(); + verifyLicense(bytes); } catch (Exception e) { error = e; } } - public Map getProperties() throws Exception { - return NEWLINE.splitAsStream(verifyClearSignMessage()).map(s -> s.split(": ", 2)).collect(toMap(a -> a[0], a -> a[1])); + private Map getProperties(byte[] bytes) throws Exception { + return NEWLINE.splitAsStream(verifyClearSignMessage(bytes)).map(s -> s.split(": ", 2)).collect(toMap(a -> a[0], a -> a[1])); } - public String verifyClearSignMessage() throws Exception { + private String verifyClearSignMessage(byte[] bytes) throws Exception { ArmoredInputStream armoredInput = new ArmoredInputStream(new ByteArrayInputStream(bytes)); // read content @@ -90,7 +91,7 @@ public class License implements Serializable { return clearSignMessage; } - private void verifyLicense() throws Exception { + private void verifyLicense(byte[] bytes) throws Exception { Cache cache = CacheManager.getInstance().getCache("license", CacheType.Persistent); Object json = cache.json(id, i -> new URL("https://license.filebot.net/verify/" + i)).fetch((url, modified) -> WebRequest.post(url, bytes, "application/octet-stream", null)).expire(Cache.ONE_MONTH).get(); @@ -111,7 +112,26 @@ public class License implements Serializable { @Override public String toString() { - return String.format("License %s (Valid-Until: %s)", id, Instant.ofEpochMilli(expires).atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)); + return String.format("%s License %s (Valid-Until: %s)", product, id, Instant.ofEpochMilli(expires).atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + public static final SystemProperty FILE = SystemProperty.of("net.filebot.license", File::new, ApplicationFolder.AppData.resolve("license.txt")); + public static final MemoizedResource INSTANCE = Resource.lazy(() -> new License(readFile(FILE.get()))); + + public static License configure(File file) throws Exception { + byte[] bytes = readFile(file); + + // check if license file is valid and not expired + License license = new License(bytes); + license.check(); + + // write to default license file path + writeFile(bytes, FILE.get()); + + // clear memoized instance and reload on next access + INSTANCE.clear(); + + return license; } } diff --git a/source/net/filebot/LicenseModel.java b/source/net/filebot/LicenseModel.java index 0d8bcf05..cf2fa0c5 100644 --- a/source/net/filebot/LicenseModel.java +++ b/source/net/filebot/LicenseModel.java @@ -1,13 +1,9 @@ package net.filebot; -import static net.filebot.Settings.*; import static net.filebot.platform.windows.WinAppUtilities.*; -import static net.filebot.util.FileUtilities.*; import java.io.File; -import net.filebot.util.SystemProperty; - public enum LicenseModel { MicrosoftStore { @@ -32,13 +28,10 @@ public enum LicenseModel { PGPSignedMessage { - public final SystemProperty LICENSE_FILE = SystemProperty.of("net.filebot.license", File::new, ApplicationFolder.AppData.resolve("license.txt")); - public final MemoizedResource LICENSE = Resource.lazy(() -> new License(readFile(LICENSE_FILE.get()))); - @Override public void check() throws LicenseError { try { - LICENSE.get().check(); + License.INSTANCE.get().check(); } catch (Exception e) { throw new LicenseError(e.getMessage()); } @@ -47,13 +40,4 @@ public enum LicenseModel { public abstract void check() throws LicenseError; - public static LicenseModel get() { - if (isUWP()) - return MicrosoftStore; - if (isMacSandbox()) - return MacAppStore; - - return PGPSignedMessage; - } - } diff --git a/source/net/filebot/Main.java b/source/net/filebot/Main.java index 3b069a70..d04ace50 100644 --- a/source/net/filebot/Main.java +++ b/source/net/filebot/Main.java @@ -162,11 +162,23 @@ public class Main { SwingEventBus.getInstance().post(new FileTransferable(files)); } + // import license if started with license file + if (LicenseModel.PGPSignedMessage == LICENSE) { + args.getLicenseFile().ifPresent(f -> { + try { + License license = License.configure(f); + log.info(license + " has been activated."); + } catch (Throwable e) { + log.severe("License Error: " + e.getMessage()); + } + }); + } + // JavaFX is used for ProgressMonitor and GettingStartedDialog try { initJavaFX(); } catch (Throwable e) { - log.log(Level.SEVERE, "Failed to initialize JavaFX. Please install JavaFX.", e); + log.log(Level.SEVERE, "Failed to initialize JavaFX", e); } // check if application help should be shown diff --git a/source/net/filebot/Settings.java b/source/net/filebot/Settings.java index 7734460b..239a5761 100644 --- a/source/net/filebot/Settings.java +++ b/source/net/filebot/Settings.java @@ -25,7 +25,7 @@ import net.filebot.util.PreferencesMap.StringAdapter; public final class Settings { - public static final LicenseModel LICENSE = LicenseModel.get(); + public static final LicenseModel LICENSE = getLicenseModel(); public static String getApplicationName() { return getApplicationProperty("application.name"); @@ -137,6 +137,15 @@ public final class Settings { return Runtime.getRuntime().availableProcessors(); } + public static LicenseModel getLicenseModel() { + if (isUWP()) + return LicenseModel.MicrosoftStore; + if (isMacSandbox()) + return LicenseModel.MacAppStore; + + return LicenseModel.PGPSignedMessage; + } + public static String getAppStoreName() { if (isMacApp()) return "Mac App Store"; diff --git a/source/net/filebot/cli/ArgumentBean.java b/source/net/filebot/cli/ArgumentBean.java index 443fa500..c0c2642c 100644 --- a/source/net/filebot/cli/ArgumentBean.java +++ b/source/net/filebot/cli/ArgumentBean.java @@ -139,6 +139,9 @@ public class ArgumentBean { @Option(name = "-help", usage = "Print this help message") public boolean help = false; + @Option(name = "--license", usage = "Import license file", metaVar = "file") + public String license = null; + @Option(name = "--def", usage = "Define script variables", handler = BindingsHandler.class) public Map defines = new LinkedHashMap(); @@ -348,6 +351,10 @@ public class ArgumentBean { }).orElseThrow(error("Illegal mode", mode)); } + public Optional getLicenseFile() { + return optional(license).map(File::new); + } + private final String[] args; public ArgumentBean(String... args) throws CmdLineException { diff --git a/source/net/filebot/util/FileUtilities.java b/source/net/filebot/util/FileUtilities.java index cbaec6d9..7bafea51 100644 --- a/source/net/filebot/util/FileUtilities.java +++ b/source/net/filebot/util/FileUtilities.java @@ -225,6 +225,10 @@ public final class FileUtilities { return destination; } + public static File writeFile(byte[] data, File destination) throws IOException { + return Files.write(destination.toPath(), data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE).toFile(); + } + public static Reader createTextReader(InputStream in, boolean guess, Charset declaredEncoding) throws IOException { byte head[] = new byte[BOM.SIZE]; in.mark(head.length);