Updated ACME client works!
At least to the point before the challenge heh. Also did a code analysis.
This commit is contained in:
parent
cca6235179
commit
173cb4caed
9 changed files with 137 additions and 141 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* 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 {
|
|||
* <p>
|
||||
* 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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Runnable>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, "<a href=\"" + url + "\">If you can see this, click here to continue</a>", exchange);
|
||||
}
|
||||
|
@ -113,9 +113,9 @@ public class IOHelper {
|
|||
return new Cookies();
|
||||
Map<String, String> 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<String, String> 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. <b>Make sure to save the user data.</b>
|
||||
*
|
||||
* @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<String, String> GetPOSTKeyValues(HttpExchange exchange) {
|
||||
try {
|
||||
String[] content = GetPOST(exchange).split("\\&");
|
||||
String[] content = GetPOST(exchange).split("&");
|
||||
HashMap<String, String> vars = new HashMap<>();
|
||||
for (String var : content) {
|
||||
String[] spl = var.split("\\=");
|
||||
String[] spl = var.split("=");
|
||||
if (spl.length == 1)
|
||||
vars.put(spl[0], "");
|
||||
else
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String, Socket> connections = new HashMap<>();
|
||||
private final Map<String, Socket> 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
|
||||
|
|
|
@ -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<String>) () -> {
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue