forked from Qortal/qortal
Merge branch 'ssl' into launch
This commit is contained in:
commit
66276a6f65
@ -2,15 +2,31 @@ package org.qortal.api;
|
|||||||
|
|
||||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
|
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
|
||||||
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
||||||
import org.eclipse.jetty.server.CustomRequestLog;
|
import org.eclipse.jetty.server.CustomRequestLog;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.OptionalSslConnectionFactory;
|
||||||
import org.eclipse.jetty.server.RequestLog;
|
import org.eclipse.jetty.server.RequestLog;
|
||||||
import org.eclipse.jetty.server.RequestLogWriter;
|
import org.eclipse.jetty.server.RequestLogWriter;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
import org.eclipse.jetty.server.handler.InetAccessHandler;
|
import org.eclipse.jetty.server.handler.InetAccessHandler;
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
@ -18,6 +34,7 @@ import org.eclipse.jetty.servlet.FilterHolder;
|
|||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.glassfish.jersey.server.ResourceConfig;
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
import org.qortal.api.resource.AnnotationPostProcessor;
|
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||||
@ -56,9 +73,58 @@ public class ApiService {
|
|||||||
public void start() {
|
public void start() {
|
||||||
try {
|
try {
|
||||||
// Create API server
|
// Create API server
|
||||||
|
|
||||||
|
// SSL support if requested
|
||||||
|
String keystorePathname = Settings.getInstance().getSslKeystorePathname();
|
||||||
|
String keystorePassword = Settings.getInstance().getSslKeystorePassword();
|
||||||
|
|
||||||
|
if (keystorePathname != null && keystorePassword != null) {
|
||||||
|
// SSL version
|
||||||
|
if (!Files.isReadable(Path.of(keystorePathname)))
|
||||||
|
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||||
|
|
||||||
|
// BouncyCastle-specific SSLContext build
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||||
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||||
|
|
||||||
|
try (InputStream keystoreStream = Files.newInputStream(Paths.get(keystorePathname))) {
|
||||||
|
keyStore.load(keystoreStream, keystorePassword.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
|
||||||
|
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
|
||||||
|
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setSslContext(sslContext);
|
||||||
|
|
||||||
|
this.server = new Server();
|
||||||
|
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.setSecureScheme("https");
|
||||||
|
httpConfig.setSecurePort(Settings.getInstance().getApiPort());
|
||||||
|
|
||||||
|
SecureRequestCustomizer src = new SecureRequestCustomizer();
|
||||||
|
httpConfig.addCustomizer(src);
|
||||||
|
|
||||||
|
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);
|
||||||
|
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
|
||||||
|
|
||||||
|
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||||
|
new OptionalSslConnectionFactory(sslConnectionFactory, HttpVersion.HTTP_1_1.asString()),
|
||||||
|
sslConnectionFactory,
|
||||||
|
httpConnectionFactory);
|
||||||
|
portUnifiedConnector.setHost(Settings.getInstance().getBindAddress());
|
||||||
|
portUnifiedConnector.setPort(Settings.getInstance().getApiPort());
|
||||||
|
|
||||||
|
this.server.addConnector(portUnifiedConnector);
|
||||||
|
} else {
|
||||||
|
// Non-SSL
|
||||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
||||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort());
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort());
|
||||||
this.server = new Server(endpoint);
|
this.server = new Server(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
ErrorHandler errorHandler = new ApiErrorHandler();
|
ErrorHandler errorHandler = new ApiErrorHandler();
|
||||||
|
@ -63,6 +63,9 @@ public class Settings {
|
|||||||
private Boolean apiRestricted;
|
private Boolean apiRestricted;
|
||||||
private boolean apiLoggingEnabled = false;
|
private boolean apiLoggingEnabled = false;
|
||||||
private boolean apiDocumentationEnabled = false;
|
private boolean apiDocumentationEnabled = false;
|
||||||
|
// Both of these need to be set for API to use SSL
|
||||||
|
private String sslKeystorePathname = null;
|
||||||
|
private String sslKeystorePassword = null;
|
||||||
|
|
||||||
// Specific to this node
|
// Specific to this node
|
||||||
private boolean wipeUnconfirmedOnStart = false;
|
private boolean wipeUnconfirmedOnStart = false;
|
||||||
@ -295,6 +298,14 @@ public class Settings {
|
|||||||
return this.apiDocumentationEnabled;
|
return this.apiDocumentationEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSslKeystorePathname() {
|
||||||
|
return this.sslKeystorePathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSslKeystorePassword() {
|
||||||
|
return this.sslKeystorePassword;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getWipeUnconfirmedOnStart() {
|
public boolean getWipeUnconfirmedOnStart() {
|
||||||
return this.wipeUnconfirmedOnStart;
|
return this.wipeUnconfirmedOnStart;
|
||||||
}
|
}
|
||||||
|
47
tools/build-keystore.sh
Executable file
47
tools/build-keystore.sh
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Assumes Let's Encrypt
|
||||||
|
|
||||||
|
if [ $# -ne 1 -a $# -ne 3 ]; then
|
||||||
|
echo "usage: ${0%%*/} <domain> [<keystore> <password>]"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
domain=$1
|
||||||
|
keystore=${2:-core-api.keystore}
|
||||||
|
pass=${3:-kspassword}
|
||||||
|
|
||||||
|
LEdirs=(/usr/local/etc /etc /opt .)
|
||||||
|
for LEdir in "${LEdirs[@]}"; do
|
||||||
|
srcdir="${LEdir}/letsencrypt/live/${domain}"
|
||||||
|
if [ -d "$srcdir" ]; then
|
||||||
|
echo "Using certs & keys from ${srcdir}"
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
unset srcdir
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${srcdir}" ]; then
|
||||||
|
echo "Can't find Let's Encrypt folder for ${domain}"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# key & cert
|
||||||
|
rm -f "${domain}.p12"
|
||||||
|
openssl pkcs12 \
|
||||||
|
-inkey "${srcdir}/privkey.pem" -in "${srcdir}/fullchain.pem" \
|
||||||
|
-export -out "${domain}.p12" -passout pass:"${pass}" \
|
||||||
|
-name "${domain}"
|
||||||
|
|
||||||
|
rm -f "${keystore}"
|
||||||
|
keytool -importkeystore -noprompt \
|
||||||
|
-srckeystore "${domain}.p12" -srcstoretype PKCS12 -srcstorepass "${pass}" \
|
||||||
|
-destkeystore "${keystore}" -deststorepass "${pass}" -destkeypass "${pass}" \
|
||||||
|
-alias "${domain}"
|
||||||
|
|
||||||
|
printf "Built keystore: ${keystore}, with password: ${pass}\nFor settings.json:\n"
|
||||||
|
|
||||||
|
printf "\tsslKeystorePathname: \"%s\",\n" "${keystore}"
|
||||||
|
printf "\tsslKeystorePassword: \"%s\",\n" "${pass}"
|
Loading…
Reference in New Issue
Block a user