From ca1fc5fea0d29e9e080e68228382974d9f60e020 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sat, 9 Jun 2018 08:04:26 +0700 Subject: [PATCH] Experiment with PGP signed messages --- .classpath | 2 + ivy.xml | 2 + source/net/filebot/License.java | 107 ++++++++++++++++++++++++++++++++ source/net/filebot/license.key | Bin 0 -> 1195 bytes 4 files changed, 111 insertions(+) create mode 100644 source/net/filebot/License.java create mode 100644 source/net/filebot/license.key diff --git a/.classpath b/.classpath index dae3d23e..940479cc 100644 --- a/.classpath +++ b/.classpath @@ -52,5 +52,7 @@ + + diff --git a/ivy.xml b/ivy.xml index a99efe0e..e5888abd 100644 --- a/ivy.xml +++ b/ivy.xml @@ -28,6 +28,8 @@ + + diff --git a/source/net/filebot/License.java b/source/net/filebot/License.java new file mode 100644 index 00000000..737494d9 --- /dev/null +++ b/source/net/filebot/License.java @@ -0,0 +1,107 @@ +package net.filebot; + +import static java.nio.charset.StandardCharsets.*; +import static java.util.stream.Collectors.*; +import static net.filebot.CachedResource.*; +import static net.filebot.util.FileUtilities.*; +import static net.filebot.util.RegularExpressions.*; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; + +import net.filebot.util.ByteBufferOutputStream; +import net.filebot.web.WebRequest; + +public class License implements Serializable { + + public static final File LICENSE_FILE = ApplicationFolder.AppData.resolve("license.txt"); + public static final Resource INSTANCE = Resource.lazy(() -> new License(readFile(LICENSE_FILE))); + + private int id; + private long expires; + + public License(byte[] bytes) throws Exception { + String content = verifyClearSignMessage(new ByteArrayInputStream(bytes), License.class.getResourceAsStream("license.key")); + + // parse properties file + Map properties = NEWLINE.splitAsStream(content).map(s -> s.split(": ", 2)).collect(toMap(a -> a[0], a -> a[1])); + + this.id = Integer.parseInt(properties.get("Order")); + this.expires = LocalDate.parse(properties.get("Valid-Until"), DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(ZoneOffset.UTC).plusDays(1).minusSeconds(1).toInstant().toEpochMilli(); + + System.out.println(properties); + + // verify license online first + verifyLicense(id, bytes); + } + + @Override + public String toString() { + return String.format("%s (Valid-Until: %s)", id, Instant.ofEpochMilli(expires).atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + public boolean isValid() { + return expires < System.currentTimeMillis(); + } + + private void verifyLicense(int id, byte[] file) throws Exception { + Cache cache = CacheManager.getInstance().getCache("license", CacheType.Persistent); + String message = new CachedResource(id, i -> new URL("https://license.filebot.net/verify.cgi?order=" + id), (url, modified) -> WebRequest.post(url, file, "application/octet-stream", null), getText(UTF_8), String.class::cast, Cache.ONE_MONTH, cache).get().trim(); + + if (!message.equals("OK")) { + throw new PGPException(message); + } + } + + private String verifyClearSignMessage(InputStream clearSignFile, InputStream publicKeyFile) throws Exception { + ArmoredInputStream armoredInput = new ArmoredInputStream(clearSignFile); + + // read content + ByteBufferOutputStream content = new ByteBufferOutputStream(256); + int character; + + while ((character = armoredInput.read()) >= 0 && armoredInput.isClearText()) { + content.write(character); + } + + // read public key + PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyFile, new JcaKeyFingerprintCalculator()); + PGPPublicKey publicKey = publicKeyRing.getPublicKey(); + + // read signature + PGPSignatureList signatureList = (PGPSignatureList) new JcaPGPObjectFactory(armoredInput).nextObject(); + PGPSignature signature = signatureList.get(0); + signature.init(new JcaPGPContentVerifierBuilderProvider(), publicKey); + + // normalize clear sign message + String clearSignMessage = NEWLINE.splitAsStream(UTF_8.decode(content.getByteBuffer())).map(String::trim).collect(joining("\r\n")); + + // verify signature + signature.update(clearSignMessage.getBytes(UTF_8)); + + if (!signature.verify()) { + throw new PGPException("BAD LICENSE: Signature does not match"); + } + + return clearSignMessage; + } + +} diff --git a/source/net/filebot/license.key b/source/net/filebot/license.key new file mode 100644 index 0000000000000000000000000000000000000000..6927b837132e5f6359afd30e8c53b8c0db89a0ad GIT binary patch literal 1195 zcmV;c1XTN(0SyFO8nN&J2mrj#A6Dw>K0nv%(6G5v%{DY^F5NN}lT&NTcu&(UY_*rUejic8QYiW5+jK13+EXT z71JfmSnE4}@lmX~#9T`kG4Dc7O^jNalw{=`S4lcwWl_U)4V_d;^ z!ZN66k1UcR$wKtY@u2__0RRECEJkT;WkPRsAWUgvWo~n2AWdO&bY*fNJZx!WWo~n2 zKxS!dWnyo1E^cLXK8XQ11QP)Q03rnfTN<(O0viJc3ke7Z0|EvW2m%QT3j`Jd0|5da z0Rk6*0162Z$`XwTB+ek&xZMZ+767>dXYw@vome*IWNifhpzT_JTCR%hnXoKKg2yVU z-a2Z>>y%^ByvI~nw^lh{(%5J}Sj41Ux}0_A^k^Vx!}u0_;djIR%c+6&9+VDfh8#t3 zvPTTiIHe(%PUQ;45oc6M>iO5DK`_SC#D7%oQ@18r7Eo!Sbe5<;NPH?MztV-3?qp+W zQqMN|!e~k3#w{a93u9oYxv)Rpk?#yIhe*$h?I-YE<>VYig!A=Dxzso}?ljHfMx6O8 zfP4d6>*B{c8>HB;g}fixbm9s8ez`$Y-oeLdu$DXUq|WT~;|9hC&kvYoHn1iKcalhb zCvL1lWj6UCtQ#a4^h%1Vxd9CXTN<(O0SExZ^+w*f^fm?EK_6VJQctv03;y9@-IEKj z2H1pv2(wslyn2@uK> zjR+*pAlayD2me|2VJ-CZ^4V8p-`-ItR$Nu)KmQ)#3Rxj%P?prjhN!MmYa-2iXKq_v zT}>WPiMo{AoGah&$y%ZVkV0O4P@@WA?MW@BE;}NnoRHHC^sl9ZfpmO)oc*E~X=r&f z&u)DsR&j6#!I;SITmNq9F+Q*k+if1zy zcuSi{K|=^jM-o@_FQiOzc+Yl}RXIi9yrE4r$5F29QI+09Cv4KKaeYJlrAO4AT0O^i z<#|_f{h4ggilE>_z(wEXjwT)XX(UAc1y+3a((fLgC*ci(FQ0PW54K9c&B_r0Jux|E Jqil|_#^y@eDV6{L literal 0 HcmV?d00001