From cca62351793c7733206260cb5f12e19a18cb63cb Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 12 Jun 2018 00:14:07 +0200 Subject: [PATCH 1/2] Updated Acme client, needs migrating --- pom.xml | 10 ++---- src/buttondevteam/website/AcmeClient.java | 34 ++++++++----------- .../website/ButtonWebsiteModule.java | 3 +- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 67befd7..684028b 100644 --- a/pom.xml +++ b/pom.xml @@ -45,11 +45,6 @@ - - - - - *:* @@ -126,15 +121,16 @@ master-SNAPSHOT provided + org.shredzone.acme4j acme4j-client - 0.10 + 2.1 org.shredzone.acme4j acme4j-utils - 0.10 + 2.1 diff --git a/src/buttondevteam/website/AcmeClient.java b/src/buttondevteam/website/AcmeClient.java index 9722667..b00a61a 100644 --- a/src/buttondevteam/website/AcmeClient.java +++ b/src/buttondevteam/website/AcmeClient.java @@ -13,23 +13,14 @@ */ //Modified package buttondevteam.website; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Writer; -import java.net.URI; -import java.security.KeyPair; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Collection; - -import org.shredzone.acme4j.*; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.website.page.AcmeChallengePage; +import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Certificate; +import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Status; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; -import org.shredzone.acme4j.exception.AcmeConflictException; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.util.CSRBuilder; import org.shredzone.acme4j.util.CertificateUtils; @@ -37,8 +28,12 @@ import org.shredzone.acme4j.util.KeyPairUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.website.page.AcmeChallengePage; +import java.io.*; +import java.net.URI; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; /** * A simple client test tool. @@ -73,12 +68,11 @@ public class AcmeClient { // Load the user key file. If there is no key file, create a new one. // Keep this key pair in a safe place! In a production environment, you will not be // able to access your account again if you should lose the key pair. - KeyPair userKeyPair = loadOrCreateKeyPair(USER_KEY_FILE); + KeyPair userKeyPair = loadOrCreateKeyPair(USER_KEY_FILE); //TODO: Migrate to new version // Create a session for Let's Encrypt. // Use "acme://letsencrypt.org" for production server - Session session = new Session("acme://letsencrypt.org" + (TBMCCoreAPI.IsTestServer() ? "/staging" : ""), - userKeyPair); + Session session = new Session("acme://letsencrypt.org" + (TBMCCoreAPI.IsTestServer() ? "/staging" : "")); // Get the Registration to the account. // If there is no account yet, create a new one. diff --git a/src/buttondevteam/website/ButtonWebsiteModule.java b/src/buttondevteam/website/ButtonWebsiteModule.java index c1f022a..6e21f05 100644 --- a/src/buttondevteam/website/ButtonWebsiteModule.java +++ b/src/buttondevteam/website/ButtonWebsiteModule.java @@ -132,7 +132,8 @@ public class ButtonWebsiteModule extends JavaPlugin { } }); final Calendar calendar = Calendar.getInstance(); - if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY && !TBMCCoreAPI.IsTestServer()) { // Only update every week + //if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY && !TBMCCoreAPI.IsTestServer()) { // Only update every week + if (true) { //TODO: TMP Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); AcmeClient.main("server.figytuna.com"); // Task is running async so we don't need an extra thread } From 173cb4caed13141693c56a1c6865f0c7adb486a5 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Wed, 4 Jul 2018 19:14:22 +0200 Subject: [PATCH 2/2] Updated ACME client works! At least to the point before the challenge heh. Also did a code analysis. --- .gitignore | 2 +- src/buttondevteam/website/AcmeClient.java | 145 ++++++++++-------- .../website/ButtonWebsiteModule.java | 39 ++--- src/buttondevteam/website/io/IOHelper.java | 19 ++- src/buttondevteam/website/io/Response.java | 6 +- .../website/page/BridgePage.java | 20 +-- .../website/page/BuildNotificationsPage.java | 34 ++-- src/buttondevteam/website/page/LoginPage.java | 7 +- src/buttondevteam/website/page/Page.java | 6 +- 9 files changed, 137 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index d6ecfc9..979d137 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,7 @@ publish/ *.publishproj # NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line +## TO!DO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output diff --git a/src/buttondevteam/website/AcmeClient.java b/src/buttondevteam/website/AcmeClient.java index b00a61a..bf4b084 100644 --- a/src/buttondevteam/website/AcmeClient.java +++ b/src/buttondevteam/website/AcmeClient.java @@ -15,15 +15,11 @@ package buttondevteam.website; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.website.page.AcmeChallengePage; -import org.shredzone.acme4j.Authorization; -import org.shredzone.acme4j.Certificate; -import org.shredzone.acme4j.Session; -import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.*; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.util.CSRBuilder; -import org.shredzone.acme4j.util.CertificateUtils; import org.shredzone.acme4j.util.KeyPairUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +27,6 @@ import org.slf4j.LoggerFactory; import java.io.*; import java.net.URI; import java.security.KeyPair; -import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; @@ -42,16 +37,16 @@ import java.util.Collection; */ public class AcmeClient { // File name of the User Key Pair - private static final File USER_KEY_FILE = new File("user.key"); + public static final File USER_KEY_FILE = new File("user.key"); // File name of the Domain Key Pair - private static final File DOMAIN_KEY_FILE = new File("domain.key"); + public static final File DOMAIN_KEY_FILE = new File("domain.key"); // File name of the CSR - private static final File DOMAIN_CSR_FILE = new File("domain.csr"); + public static final File DOMAIN_CSR_FILE = new File("domain.csr"); // File name of the signed certificate - private static final File DOMAIN_CHAIN_FILE = new File("domain-chain.crt"); + public static final File DOMAIN_CHAIN_FILE = new File("domain-chain.crt"); // RSA key size of generated key pairs private static final int KEY_SIZE = 2048; @@ -68,7 +63,7 @@ public class AcmeClient { // Load the user key file. If there is no key file, create a new one. // Keep this key pair in a safe place! In a production environment, you will not be // able to access your account again if you should lose the key pair. - KeyPair userKeyPair = loadOrCreateKeyPair(USER_KEY_FILE); //TODO: Migrate to new version + KeyPair userKeyPair = loadOrCreateKeyPair(USER_KEY_FILE); // Create a session for Let's Encrypt. // Use "acme://letsencrypt.org" for production server @@ -76,11 +71,13 @@ public class AcmeClient { // Get the Registration to the account. // If there is no account yet, create a new one. - Registration reg = findOrRegisterAccount(session); + Account acc = findOrRegisterAccount(session, userKeyPair); + + Order order = acc.newOrder().domains(domains).create(); // Separately authorize every requested domain. - for (String domain : domains) { - authorize(reg, domain); + for (Authorization auth : order.getAuthorizations()) { + authorize(auth); } // Load or create a key pair for the domains. This should not be the userKeyPair! @@ -96,19 +93,44 @@ public class AcmeClient { csrb.write(out); } - // Now request a signed certificate. - Certificate certificate = reg.requestCertificate(csrb.getEncoded()); + LOG.info("Ordering certificate..."); + // Order the certificate + order.execute(csrb.getEncoded()); + + // Wait for the order to complete + try { + int attempts = 10; + while (order.getStatus() != Status.VALID && attempts-- > 0) { + // Did the order fail? + if (order.getStatus() == Status.INVALID) { + throw new AcmeException("Order failed... Giving up."); + } + + // Wait for a few seconds + Thread.sleep(3000L); + + // Then update the status + order.update(); + if (order.getStatus() != Status.VALID) + LOG.info("Not yet..."); + } + } catch (InterruptedException ex) { + LOG.error("interrupted", ex); + Thread.currentThread().interrupt(); + } + + // Get the certificate + Certificate certificate = order.getCertificate(); + + if (certificate == null) + throw new AcmeException("Certificate is null. Wot."); LOG.info("Success! The certificate for domains " + domains + " has been generated!"); - LOG.info("Certificate URI: " + certificate.getLocation()); - - // Download the leaf certificate and certificate chain. - X509Certificate cert = certificate.download(); - X509Certificate[] chain = certificate.downloadChain(); + LOG.info("Certificate URL: " + certificate.getLocation()); // Write a combined file containing the certificate and chain. try (FileWriter fw = new FileWriter(DOMAIN_CHAIN_FILE)) { - CertificateUtils.writeX509CertificateChain(fw, cert, chain); + certificate.writeCertificate(fw); } // That's all! Configure your web server to use the DOMAIN_KEY_FILE and @@ -135,44 +157,34 @@ public class AcmeClient { } /** - * Finds your {@link Registration} at the ACME server. It will be found by your user's public key. If your key is not known to the server yet, a new registration will be created. - *

- * This is a simple way of finding your {@link Registration}. A better way is to get the URI of your new registration with {@link Registration#getLocation()} and store it somewhere. If you need to - * get access to your account later, reconnect to it via {@link Registration#bind(Session, URI)} by using the stored location. + * Finds your {@link Account} at the ACME server. It will be found by your user's public key. If your key is not known to the server yet, a new registration will be created. * * @param session * {@link Session} to bind with - * @return {@link Registration} connected to your account + * @param kp The user keypair + * @return {@link Account} connected to your account */ - private Registration findOrRegisterAccount(Session session) throws AcmeException, IOException { - Registration reg; + private Account findOrRegisterAccount(Session session, KeyPair kp) throws AcmeException, IOException { + Account acc; URI loc = ButtonWebsiteModule.getRegistration(); if (loc != null) { LOG.info("Loading account from file"); - return Registration.bind(session, loc); + return new Login(loc.toURL(), kp, session).getAccount(); } - try { - // Try to create a new Registration. - reg = new RegistrationBuilder().create(session); - LOG.info("Registered a new user, URI: " + reg.getLocation()); + // Try to create a new Registration. + AccountBuilder ab = new AccountBuilder().useKeyPair(kp); - // This is a new account. Let the user accept the Terms of Service. - // We won't be able to authorize domains until the ToS is accepted. - URI agreement = reg.getAgreement(); - LOG.info("Terms of Service: " + agreement); - acceptAgreement(reg, agreement); + // This is a new account. Let the user accept the Terms of Service. + // We won't be able to authorize domains until the ToS is accepted. + URI agreement = session.getMetadata().getTermsOfService(); + acceptAgreement(ab, agreement); + acc = ab.create(session); + LOG.info("Registered a new user, URI: " + acc.getLocation()); + ButtonWebsiteModule.storeRegistration(acc.getLocation()); - } catch (AcmeConflictException ex) { - // The Key Pair is already registered. getLocation() contains the - // URL of the existing registration's location. Bind it to the session. - reg = Registration.bind(session, ex.getLocation()); - LOG.info("Account does already exist, URI: " + reg.getLocation(), ex); - ButtonWebsiteModule.storeRegistration(ex.getLocation()); - } - - return reg; + return acc; } /** @@ -180,18 +192,18 @@ public class AcmeClient { *

* You need separate authorizations for subdomains (e.g. "www" subdomain). Wildcard certificates are not currently supported. * - * @param reg - * {@link Registration} of your account - * @param domain - * Name of the domain to authorize + * @param auth + * {@link Authorization} for the domain */ - private void authorize(Registration reg, String domain) throws AcmeException { - // Authorize the domain. - Authorization auth = reg.authorizeDomain(domain); - LOG.info("Authorization for domain " + domain); + private void authorize(Authorization auth) throws AcmeException { + LOG.info("Authorization for domain " + auth.getDomain()); - // Find the desired challenge and prepare it. - Challenge challenge = httpChallenge(auth, domain); + // The authorization is already valid. No need to process a challenge. + if (auth.getStatus() == Status.VALID) { + return; + } + + Challenge challenge = httpChallenge(auth); if (challenge == null) { throw new AcmeException("No challenge found"); @@ -227,7 +239,7 @@ public class AcmeClient { // All reattempts are used up and there is still no valid authorization? if (challenge.getStatus() != Status.VALID) { - throw new AcmeException("Failed to pass the challenge for domain " + domain + ", ... Giving up."); + throw new AcmeException("Failed to pass the challenge for domain " + auth.getDomain() + ", ... Giving up."); } } @@ -241,11 +253,9 @@ public class AcmeClient { * * @param auth * {@link Authorization} to find the challenge in - * @param domain - * Domain name to be authorized * @return {@link Challenge} to verify */ - public Challenge httpChallenge(Authorization auth, String domain) throws AcmeException { + public Challenge httpChallenge(Authorization auth) throws AcmeException { // Find a single http-01 challenge Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE); if (challenge == null) { @@ -256,7 +266,7 @@ public class AcmeClient { /* * else LOG.info("Store the challenge data! Can't do automatically."); */ - LOG.info("It should be reachable at: http://" + domain + "/.well-known/acme-challenge/" + challenge.getToken()); + LOG.info("It should be reachable at: http://" + auth.getDomain() + "/.well-known/acme-challenge/" + challenge.getToken()); // LOG.info("File name: " + challenge.getToken()); // LOG.info("Content: " + challenge.getAuthorization()); /* @@ -274,12 +284,13 @@ public class AcmeClient { /** * Presents the user a link to the Terms of Service, and asks for confirmation. If the user denies confirmation, an exception is thrown. * - * @param reg - * {@link Registration} User's registration + * @param ab + * {@link AccountBuilder} for the user * @param agreement * {@link URI} of the Terms of Service */ - public void acceptAgreement(Registration reg, URI agreement) throws AcmeException, IOException { + public void acceptAgreement(AccountBuilder ab, URI agreement) throws AcmeException, IOException { + LOG.info("Terms of Service: " + agreement); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Do you accept the terms? (y/n)"); if (br.readLine().equalsIgnoreCase("y\n")) { @@ -287,7 +298,7 @@ public class AcmeClient { } // Motify the Registration and accept the agreement - reg.modify().setAgreement(agreement).commit(); + ab.agreeToTermsOfService(); LOG.info("Updated user's ToS"); } diff --git a/src/buttondevteam/website/ButtonWebsiteModule.java b/src/buttondevteam/website/ButtonWebsiteModule.java index 6e21f05..8e29e30 100644 --- a/src/buttondevteam/website/ButtonWebsiteModule.java +++ b/src/buttondevteam/website/ButtonWebsiteModule.java @@ -4,7 +4,10 @@ import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.website.io.IOHelper; import buttondevteam.website.page.*; -import com.sun.net.httpserver.*; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsParameters; +import com.sun.net.httpserver.HttpsServer; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; @@ -14,10 +17,7 @@ import org.bukkit.plugin.java.JavaPlugin; import javax.net.ssl.*; import java.io.*; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.*; import java.security.KeyPair; import java.security.KeyStore; import java.security.PrivateKey; @@ -46,7 +46,7 @@ public class ButtonWebsiteModule extends JavaPlugin { // initialise the keystore char[] password = "password".toCharArray(); KeyStore ks = KeyStore.getInstance("JKS"); - String certfile = "domain-chain.crt"; /* your cert path */ + File certfile = AcmeClient.DOMAIN_CHAIN_FILE; /* your cert path */ File keystoreFile = new File("keystore.keystore"); ks.load(keystoreFile.exists() ? new FileInputStream(keystoreFile) : null, password); @@ -57,9 +57,9 @@ public class ButtonWebsiteModule extends JavaPlugin { CertificateFactory cf = CertificateFactory.getInstance("X.509"); InputStream certstream = fullStream(certfile); - Certificate[] certs = cf.generateCertificates(certstream).stream().toArray(Certificate[]::new); + Certificate[] certs = cf.generateCertificates(certstream).toArray(new Certificate[0]); - BufferedReader br = new BufferedReader(new FileReader("domain.key")); + BufferedReader br = new BufferedReader(new FileReader(AcmeClient.DOMAIN_KEY_FILE)); Security.addProvider(new BouncyCastleProvider()); @@ -107,7 +107,7 @@ public class ButtonWebsiteModule extends JavaPlugin { } }); } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while starting the webserver!", e); + TBMCCoreAPI.SendException("An error occurred while starting the webserver!", e); getServer().getPluginManager().disablePlugin(this); } } @@ -124,16 +124,10 @@ public class ButtonWebsiteModule extends JavaPlugin { Bukkit.getScheduler().runTaskAsynchronously(this, () -> { this.getLogger().info("Starting webserver..."); server.setExecutor( - new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue(100))); - httpserver.createContext("/", new HttpHandler() { - @Override - public void handle(HttpExchange exchange) throws IOException { - IOHelper.SendResponse(IOHelper.Redirect("https://server.figytuna.com/", exchange)); - } - }); + new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100))); + httpserver.createContext("/", exchange -> IOHelper.SendResponse(IOHelper.Redirect("https://server.figytuna.com/", exchange))); final Calendar calendar = Calendar.getInstance(); - //if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY && !TBMCCoreAPI.IsTestServer()) { // Only update every week - if (true) { //TODO: TMP + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY && !TBMCCoreAPI.IsTestServer()) { // Only update every week Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); AcmeClient.main("server.figytuna.com"); // Task is running async so we don't need an extra thread } @@ -168,7 +162,7 @@ public class ButtonWebsiteModule extends JavaPlugin { httpserver.createContext("/" + page.GetName(), page); } - static void storeRegistration(URI location) { + static void storeRegistration(URL location) { final ButtonWebsiteModule plugin = getPlugin(ButtonWebsiteModule.class); plugin.getConfig().set("registration", location.toString()); plugin.saveConfig(); @@ -184,13 +178,12 @@ public class ButtonWebsiteModule extends JavaPlugin { } } - private static InputStream fullStream(String fname) throws IOException { - FileInputStream fis = new FileInputStream(fname); + private static InputStream fullStream(File f) throws IOException { + FileInputStream fis = new FileInputStream(f); DataInputStream dis = new DataInputStream(fis); byte[] bytes = new byte[dis.available()]; dis.readFully(bytes); dis.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - return bais; + return new ByteArrayInputStream(bytes); } } diff --git a/src/buttondevteam/website/io/IOHelper.java b/src/buttondevteam/website/io/IOHelper.java index 7ceaf72..09ab235 100644 --- a/src/buttondevteam/website/io/IOHelper.java +++ b/src/buttondevteam/website/io/IOHelper.java @@ -103,7 +103,7 @@ public class IOHelper { .AddHeaders(exchange); } - public static Response Redirect(String url, HttpExchange exchange) throws IOException { + public static Response Redirect(String url, HttpExchange exchange) { exchange.getResponseHeaders().add("Location", url); return new Response(303, "If you can see this, click here to continue", exchange); } @@ -113,9 +113,9 @@ public class IOHelper { return new Cookies(); Map map = new HashMap<>(); for (String cheader : exchange.getRequestHeaders().get("Cookie")) { - String[] spl = cheader.split("\\;\\s*"); + String[] spl = cheader.split(";\\s*"); for (String s : spl) { - String[] kv = s.split("\\="); + String[] kv = s.split("="); if (kv.length < 2) continue; map.put(kv[0], kv[1]); @@ -123,7 +123,7 @@ public class IOHelper { } if (!map.containsKey("expiretime")) return new Cookies(); - Cookies cookies = null; + Cookies cookies; try { cookies = new Cookies(map.get("expiretime")); for (Entry item : map.entrySet()) @@ -137,12 +137,11 @@ public class IOHelper { /** * Get logged in user. It may also send logout headers if the cookies are invalid, or login headers to keep the user logged in. Make sure to save the user data. - * - * @param exchange + * + * @param exchange The exchange * @return The logged in user or null if not logged in. - * @throws IOException */ - public static WebUser GetLoggedInUser(HttpExchange exchange) throws IOException { + public static WebUser GetLoggedInUser(HttpExchange exchange) { Cookies cookies = GetCookies(exchange); if (!cookies.containsKey("user_id") || !cookies.containsKey("session_id")) return null; @@ -188,10 +187,10 @@ public class IOHelper { public static HashMap GetPOSTKeyValues(HttpExchange exchange) { try { - String[] content = GetPOST(exchange).split("\\&"); + String[] content = GetPOST(exchange).split("&"); HashMap vars = new HashMap<>(); for (String var : content) { - String[] spl = var.split("\\="); + String[] spl = var.split("="); if (spl.length == 1) vars.put(spl[0], ""); else diff --git a/src/buttondevteam/website/io/Response.java b/src/buttondevteam/website/io/Response.java index 9cd9db0..dd0246c 100644 --- a/src/buttondevteam/website/io/Response.java +++ b/src/buttondevteam/website/io/Response.java @@ -3,9 +3,9 @@ package buttondevteam.website.io; import com.sun.net.httpserver.HttpExchange; public class Response { - public int code; - public String content; - public HttpExchange exchange; + public final int code; + public final String content; + public final HttpExchange exchange; public Response(int code, String content, HttpExchange exchange) { this.code = code; diff --git a/src/buttondevteam/website/page/BridgePage.java b/src/buttondevteam/website/page/BridgePage.java index c64dd53..38f383f 100644 --- a/src/buttondevteam/website/page/BridgePage.java +++ b/src/buttondevteam/website/page/BridgePage.java @@ -1,5 +1,9 @@ package buttondevteam.website.page; +import buttondevteam.website.io.Response; +import com.sun.net.httpserver.HttpExchange; +import org.bukkit.Bukkit; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -8,15 +12,8 @@ import java.net.SocketException; import java.util.HashMap; import java.util.Map; -import org.apache.commons.io.IOUtils; -import org.bukkit.Bukkit; - -import com.sun.net.httpserver.HttpExchange; - -import buttondevteam.website.io.Response; - public class BridgePage extends Page { - private Map connections = new HashMap<>(); + private final Map connections = new HashMap<>(); @Override public String GetName() { @@ -101,20 +98,17 @@ public class BridgePage extends Page { connections.values().remove(socket); } - private int copyStream(InputStream is, OutputStream os) throws IOException { // Based on IOUtils.copy() + private void copyStream(InputStream is, OutputStream os) throws IOException { // Based on IOUtils.copy() byte[] buffer = new byte[4096]; - long count = 0; - int n = 0; + int n; try { while (-1 != (n = is.read(buffer))) { // Read is blocking os.write(buffer, 0, n); - count += n; os.flush(); } } catch (SocketException e) { // Conection closed os.flush(); } - return (int) count; } @Override diff --git a/src/buttondevteam/website/page/BuildNotificationsPage.java b/src/buttondevteam/website/page/BuildNotificationsPage.java index fff65a1..207c2f1 100644 --- a/src/buttondevteam/website/page/BuildNotificationsPage.java +++ b/src/buttondevteam/website/page/BuildNotificationsPage.java @@ -1,5 +1,16 @@ package buttondevteam.website.page; +import buttondevteam.lib.PluginUpdater; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.website.io.IOHelper; +import buttondevteam.website.io.Response; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.sun.net.httpserver.HttpExchange; +import org.bukkit.Bukkit; + import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PublicKey; @@ -10,16 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.function.Supplier; -import org.bukkit.Bukkit; - -import com.google.gson.*; -import com.sun.net.httpserver.HttpExchange; - -import buttondevteam.lib.PluginUpdater; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.website.io.IOHelper; -import buttondevteam.website.io.Response; - public class BuildNotificationsPage extends Page { @Override @@ -31,8 +32,11 @@ public class BuildNotificationsPage extends Page { private static final String publickey = ((Supplier) () -> { try { - return fromString(TBMCCoreAPI.DownloadString("https://api.travis-ci.org/config"), - "config.notifications.webhook.public_key").getAsString().replace("-----BEGIN PUBLIC KEY-----", "") + JsonElement pubkey = fromString(TBMCCoreAPI.DownloadString("https://api.travis-ci.org/config"), + "config.notifications.webhook.public_key"); + if (pubkey == null) + return null; + return pubkey.getAsString().replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll("\n", "").replace("-----END PUBLIC KEY-----", ""); } catch (Exception e) { throw new RuntimeException(e); @@ -47,7 +51,7 @@ public class BuildNotificationsPage extends Page { final String payload = post.get("payload"); if (signatures != null && signatures.size() > 0 && post.containsKey("payload") && verifySignature(payload.getBytes(StandardCharsets.UTF_8), - Base64.getDecoder().decode(signatures.get(0)), publickey)) { + Base64.getDecoder().decode(signatures.get(0)))) { Bukkit.getPluginManager() .callEvent(new PluginUpdater.UpdatedEvent(gson.fromJson(payload, JsonObject.class))); return new Response(200, "All right", exchange); @@ -61,9 +65,9 @@ public class BuildNotificationsPage extends Page { // Method for signature verification that initializes with the Public Key, // updates the data to be verified and then verifies them using the signature - private boolean verifySignature(byte[] data, byte[] signature, String keystr) throws Exception { + private boolean verifySignature(byte[] data, byte[] signature) throws Exception { Signature sig = Signature.getInstance("SHA1withRSA"); - sig.initVerify(getPublic(keystr)); + sig.initVerify(getPublic(BuildNotificationsPage.publickey)); sig.update(data); return sig.verify(signature); diff --git a/src/buttondevteam/website/page/LoginPage.java b/src/buttondevteam/website/page/LoginPage.java index a8f1ee5..410c68d 100644 --- a/src/buttondevteam/website/page/LoginPage.java +++ b/src/buttondevteam/website/page/LoginPage.java @@ -7,7 +7,6 @@ import buttondevteam.website.io.Response; import com.google.common.collect.HashBiMap; import com.sun.net.httpserver.HttpExchange; -import java.io.IOException; import java.util.Map; import java.util.UUID; @@ -43,11 +42,7 @@ public class LoginPage extends Page { cp.connectWith(wu = WebUser.getUser(UUID.randomUUID().toString(), WebUser.class)); //Create new user with random UUID IOHelper.LoginUser(exchange, wu); states.remove(state); - try { - return IOHelper.Redirect("https://chromagaming.figytuna.com/", exchange); - } catch (IOException e) { - throw new RuntimeException(e); - } + return IOHelper.Redirect("https://chromagaming.figytuna.com/", exchange); } else return new Response(418, "Now what", exchange); //Minecraft doesn't have full OAuth } return new Response(400, "Wut", exchange); diff --git a/src/buttondevteam/website/page/Page.java b/src/buttondevteam/website/page/Page.java index c5f4c60..81134fe 100644 --- a/src/buttondevteam/website/page/Page.java +++ b/src/buttondevteam/website/page/Page.java @@ -19,7 +19,7 @@ public abstract class Page implements HttpHandler { public final void handle(HttpExchange exchange) { try { exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "https://chromagaming.figytuna.com"); - if (exactPage() ? exchange.getRequestURI().getPath().equals("/" + GetName()) : true) + if (!exactPage() || exchange.getRequestURI().getPath().equals("/" + GetName())) IOHelper.SendResponse(handlePage(exchange)); else { IOHelper.SendResponse(404, "404 Not found: " + exchange.getRequestURI().getPath(), exchange); @@ -46,8 +46,8 @@ public abstract class Page implements HttpHandler { /** * Whether to return 404 when the URL doesn't match the exact path - * - * @return + * + * @return Whether it should only match the page path */ public boolean exactPage() { return true;