Skip to content

Commit 31139e1

Browse files
committed
* Changed to use a self-signed certificate generated in code for unit testing so that it works in Github actions
* Updated to use internal Sun APIs * Updated to Savant 2.0.0-RC.1 for Java 17 support
1 parent f04fa03 commit 31139e1

14 files changed

Lines changed: 243 additions & 108 deletions

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ jobs:
2323
mkdir -p ~/dev/savant
2424
mkdir -p ~/.savant/plugins
2525
cd ~/dev/savant
26-
curl -fSL https://github.com/savant-build/savant-core/releases/download/1.0.3/savant-1.0.3.tar.gz > savant-1.0.3.tar.gz
27-
tar -xzf savant-1.0.3.tar.gz
28-
ln -s savant-1.0.3 current
29-
rm savant-1.0.3.tar.gz
26+
curl -fSL https://github.com/savant-build/savant-core/releases/download/2.0.0-RC.1/savant-2.0.0-RC.1.tar.gz > savant.tar.gz
27+
tar -xzf savant.tar.gz
28+
ln -s savant-2.0.0-RC.1 current
29+
rm savant.tar.gz
3030
cat <<EOF > ~/.savant/plugins/org.savantbuild.plugin.java.properties
3131
17=${JAVA_HOME_17_X64}
3232
EOF

build.savant

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313
* either express or implied. See the License for the specific
1414
* language governing permissions and limitations under the License.
1515
*/
16-
savantVersion = "1.0.0"
17-
1816
restifyVersion = "4.1.2"
1917
testngVersion = "7.3.0"
2018

21-
project(group: "io.fusionauth", name: "java-http", version: "0.1.3", licenses: ["ApacheV2_0"]) {
19+
project(group: "io.fusionauth", name: "java-http", version: "0.1.4", licenses: ["ApacheV2_0"]) {
2220
workflow {
2321
fetch {
2422
cache()
@@ -46,16 +44,16 @@ project(group: "io.fusionauth", name: "java-http", version: "0.1.3", licenses: [
4644
}
4745

4846
// Plugins
49-
database = loadPlugin(id: "org.savantbuild.plugin:database:1.0.6")
50-
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:1.0.2")
51-
java = loadPlugin(id: "org.savantbuild.plugin:java:1.2.0")
52-
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:1.0.9")
53-
idea = loadPlugin(id: "org.savantbuild.plugin:idea:1.0.1")
54-
release = loadPlugin(id: "org.savantbuild.plugin:release-git:${savantVersion}")
55-
file = loadPlugin(id: "org.savantbuild.plugin:file:1.0.2")
47+
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:2.0.0-RC.1")
48+
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0-RC.1")
49+
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:2.0.0-RC.1")
50+
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0-RC.1")
51+
release = loadPlugin(id: "org.savantbuild.plugin:release-git:2.0.0-RC.1")
5652

5753
java.settings.javaVersion = "17"
54+
java.settings.compilerArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED -XDignore.symbol.file"
5855
javaTestNG.settings.javaVersion = "17"
56+
javaTestNG.settings.jvmArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED"
5957

6058
target(name: "clean", description: "Cleans the build directory") {
6159
java.clean()

java-http.ipr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
</profile>
1616
<version value="1.0" />
1717
</component>
18+
<component name="JavacSettings">
19+
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
20+
<module name="java-http" options="--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED" />
21+
</option>
22+
</component>
1823
<component name="Palette2">
1924
<group name="Swing">
2025
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">

src/main/java/io/fusionauth/http/security/SecurityTools.java

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
package io.fusionauth.http.security;
1717

18-
import javax.net.ssl.KeyManager;
1918
import javax.net.ssl.KeyManagerFactory;
2019
import javax.net.ssl.SSLContext;
20+
import javax.net.ssl.TrustManagerFactory;
2121
import java.io.ByteArrayInputStream;
2222
import java.io.IOException;
2323
import java.security.GeneralSecurityException;
@@ -28,7 +28,6 @@
2828
import java.security.cert.Certificate;
2929
import java.security.cert.CertificateException;
3030
import java.security.cert.CertificateFactory;
31-
import java.security.cert.X509Certificate;
3231
import java.security.interfaces.RSAPrivateKey;
3332
import java.security.spec.InvalidKeySpecException;
3433
import java.security.spec.PKCS8EncodedKeySpec;
@@ -57,46 +56,31 @@ private SecurityTools() {
5756
}
5857

5958
/**
60-
* This creates an in-memory keystore containing the certificate and private key and initializes the SSLContext with the key material it
61-
* contains.
59+
* This creates an in-memory trust store containing the certificate and initializes the SSLContext with it.
6260
*
63-
* @param certificateString A PEM formatted Certificate.
64-
* @param keyString A PKCS8 PEM formatted Private Key.
65-
* @return A SSLContext configured with the Certificate and Private Key.
61+
* @param certificate A Certificate object.
62+
* @return A SSLContext configured with the Certificate.
6663
*/
67-
public static SSLContext getServerContext(String certificateString, String keyString) throws GeneralSecurityException, IOException {
68-
byte[] certBytes = parseDERFromPEM(certificateString, CERT_START, CERT_END);
69-
byte[] keyBytes = parseDERFromPEM(keyString, P8_KEY_START, P8_KEY_END);
70-
71-
X509Certificate cert = generateCertificateFromDER(certBytes);
72-
PrivateKey key = generatePrivateKeyFromPKCS8DER(keyBytes);
64+
public static SSLContext clientContext(Certificate certificate) throws GeneralSecurityException, IOException {
7365
KeyStore keystore = KeyStore.getInstance("JKS");
7466
keystore.load(null);
75-
keystore.setCertificateEntry("cert-alias", cert);
76-
keystore.setKeyEntry("key-alias", key, "changeit".toCharArray(), new Certificate[]{cert});
67+
keystore.setCertificateEntry("cert-alias", certificate);
7768

78-
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
79-
kmf.init(keystore, "changeit".toCharArray());
80-
81-
KeyManager[] km = kmf.getKeyManagers();
69+
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
70+
tmf.init(keystore);
8271

8372
SSLContext context = SSLContext.getInstance("TLS");
84-
context.init(km, null, null);
73+
context.init(null, tmf.getTrustManagers(), null);
8574
return context;
8675
}
8776

88-
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
77+
public static Certificate parseCertificate(String certificate) throws CertificateException {
8978
CertificateFactory factory = CertificateFactory.getInstance("X.509");
90-
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
79+
byte[] certBytes = parseDERFromPEM(certificate, CERT_START, CERT_END);
80+
return factory.generateCertificate(new ByteArrayInputStream(certBytes));
9181
}
9282

93-
private static RSAPrivateKey generatePrivateKeyFromPKCS8DER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
94-
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
95-
KeyFactory factory = KeyFactory.getInstance("RSA");
96-
return (RSAPrivateKey) factory.generatePrivate(spec);
97-
}
98-
99-
private static byte[] parseDERFromPEM(String pem, String beginDelimiter, String endDelimiter) {
83+
public static byte[] parseDERFromPEM(String pem, String beginDelimiter, String endDelimiter) {
10084
int startIndex = pem.indexOf(beginDelimiter);
10185
if (startIndex < 0) {
10286
throw new IllegalArgumentException("Invalid PEM format");
@@ -111,4 +95,33 @@ private static byte[] parseDERFromPEM(String pem, String beginDelimiter, String
11195
String base64 = pem.substring(startIndex + beginDelimiter.length(), endIndex).replaceAll("\\s", "");
11296
return Base64.getDecoder().decode(base64);
11397
}
98+
99+
public static RSAPrivateKey parsePrivateKey(String privateKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
100+
byte[] keyBytes = parseDERFromPEM(privateKey, P8_KEY_START, P8_KEY_END);
101+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
102+
KeyFactory factory = KeyFactory.getInstance("RSA");
103+
return (RSAPrivateKey) factory.generatePrivate(spec);
104+
}
105+
106+
/**
107+
* This creates an in-memory keystore containing the certificate and private key and initializes the SSLContext with the key material it
108+
* contains.
109+
*
110+
* @param certificate A Certificate object.
111+
* @param privateKey A PrivateKey object.
112+
* @return A SSLContext configured with the Certificate and Private Key.
113+
*/
114+
public static SSLContext serverContext(Certificate certificate, PrivateKey privateKey) throws GeneralSecurityException, IOException {
115+
KeyStore keystore = KeyStore.getInstance("JKS");
116+
keystore.load(null);
117+
keystore.setCertificateEntry("cert-alias", certificate);
118+
keystore.setKeyEntry("key-alias", privateKey, "changeit".toCharArray(), new Certificate[]{certificate});
119+
120+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
121+
kmf.init(keystore, "changeit".toCharArray());
122+
123+
SSLContext context = SSLContext.getInstance("TLS");
124+
context.init(kmf.getKeyManagers(), null, null);
125+
return context;
126+
}
114127
}

src/main/java/io/fusionauth/http/server/HTTPListenerConfiguration.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717

1818
import java.net.InetAddress;
1919
import java.net.UnknownHostException;
20+
import java.security.GeneralSecurityException;
21+
import java.security.PrivateKey;
22+
import java.security.cert.Certificate;
2023
import java.util.Objects;
2124

25+
import io.fusionauth.http.security.SecurityTools;
26+
2227
/**
2328
* A listener configuration that is used to construct the HTTP server and bind various listeners.
2429
*
@@ -27,11 +32,11 @@
2732
public class HTTPListenerConfiguration {
2833
private final InetAddress bindAddress;
2934

30-
private final String certificate;
35+
private final Certificate certificate;
3136

3237
private final int port;
3338

34-
private final String privateKey;
39+
private final PrivateKey privateKey;
3540

3641
private final boolean tls;
3742

@@ -55,8 +60,27 @@ public HTTPListenerConfiguration(int port) {
5560
* @param port The port of this listener.
5661
* @param certificate The certificate as a PEM encoded X.509 certificate String.
5762
* @param privateKey The private key as a PKCS8 encoded DER private key.
63+
* @throws GeneralSecurityException If the private key or certificate Strings were not valid and could not be parsed.
5864
*/
59-
public HTTPListenerConfiguration(int port, String certificate, String privateKey) {
65+
public HTTPListenerConfiguration(int port, String certificate, String privateKey) throws GeneralSecurityException {
66+
Objects.requireNonNull(certificate);
67+
Objects.requireNonNull(privateKey);
68+
69+
this.bindAddress = allInterfaces();
70+
this.port = port;
71+
this.tls = true;
72+
this.certificate = SecurityTools.parseCertificate(certificate);
73+
this.privateKey = SecurityTools.parsePrivateKey(privateKey);
74+
}
75+
76+
/**
77+
* Stores the configuration for a single HTTP listener for the server. This constructor sets up a TLS based listener.
78+
*
79+
* @param port The port of this listener.
80+
* @param certificate The certificate Object.
81+
* @param privateKey The private key Object.
82+
*/
83+
public HTTPListenerConfiguration(int port, Certificate certificate, PrivateKey privateKey) {
6084
Objects.requireNonNull(certificate);
6185
Objects.requireNonNull(privateKey);
6286

@@ -90,8 +114,30 @@ public HTTPListenerConfiguration(InetAddress bindAddress, int port) {
90114
* @param port The port of this listener.
91115
* @param certificate The certificate as a PEM ecnoded X.509 certificate String.
92116
* @param privateKey The private key as a PKCS8 encoded DER private key.
117+
* @throws GeneralSecurityException If the private key or certificate Strings were not valid and could not be parsed.
118+
*/
119+
public HTTPListenerConfiguration(InetAddress bindAddress, int port, String certificate, String privateKey)
120+
throws GeneralSecurityException {
121+
Objects.requireNonNull(bindAddress);
122+
Objects.requireNonNull(certificate);
123+
Objects.requireNonNull(privateKey);
124+
125+
this.bindAddress = bindAddress;
126+
this.port = port;
127+
this.tls = true;
128+
this.certificate = SecurityTools.parseCertificate(certificate);
129+
this.privateKey = SecurityTools.parsePrivateKey(privateKey);
130+
}
131+
132+
/**
133+
* Stores the configuration for a single HTTP listener for the server. This constructor sets up a TLS based listener.
134+
*
135+
* @param bindAddress The bind address of this listener.
136+
* @param port The port of this listener.
137+
* @param certificate The certificate Object.
138+
* @param privateKey The private key Object.
93139
*/
94-
public HTTPListenerConfiguration(InetAddress bindAddress, int port, String certificate, String privateKey) {
140+
public HTTPListenerConfiguration(InetAddress bindAddress, int port, Certificate certificate, PrivateKey privateKey) {
95141
Objects.requireNonNull(bindAddress);
96142
Objects.requireNonNull(certificate);
97143
Objects.requireNonNull(privateKey);
@@ -107,15 +153,15 @@ public InetAddress getBindAddress() {
107153
return bindAddress;
108154
}
109155

110-
public String getCertificate() {
156+
public Certificate getCertificate() {
111157
return certificate;
112158
}
113159

114160
public int getPort() {
115161
return port;
116162
}
117163

118-
public String getPrivateKey() {
164+
public PrivateKey getPrivateKey() {
119165
return privateKey;
120166
}
121167

src/main/java/io/fusionauth/http/server/HTTPS11Processor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public HTTPS11Processor(HTTP11Processor delegate, HTTPServerConfiguration config
5858
this.logger = configuration.getLoggerFactory().getLogger(HTTPS11Processor.class);
5959

6060
if (listenerConfiguration.isTLS()) {
61-
SSLContext context = SecurityTools.getServerContext(listenerConfiguration.getCertificate(), listenerConfiguration.getPrivateKey());
61+
SSLContext context = SecurityTools.serverContext(listenerConfiguration.getCertificate(), listenerConfiguration.getPrivateKey());
6262
this.engine = context.createSSLEngine();
6363
this.engine.setUseClientMode(false);
6464

0 commit comments

Comments
 (0)