diff --git a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/KeyManagementUtils.java b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/KeyManagementUtils.java index 766ae28df7d..e4a4a77be6e 100644 --- a/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/KeyManagementUtils.java +++ b/rt/rs/security/http-signature/src/main/java/org/apache/cxf/rs/security/httpsignature/utils/KeyManagementUtils.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.security.KeyStore; import java.security.PrivateKey; @@ -203,7 +204,7 @@ private static URL getResourceURL(String loc, Bus bus) throws Exception { } else { try { url = new URL(loc); - } catch (Exception ex) { + } catch (MalformedURLException ex) { // it can be either a classpath or file resource without a scheme url = getClasspathResourceURL(loc, KeyManagementUtils.class, bus); if (url == null) { @@ -213,6 +214,9 @@ private static URL getResourceURL(String loc, Bus bus) throws Exception { } } } + if (url != null) { + checkSupportedResourceUrlScheme(url, loc); + } } if (url == null) { LOG.warning("No resource " + loc + " is available"); @@ -220,6 +224,22 @@ private static URL getResourceURL(String loc, Bus bus) throws Exception { return url; } + private static void checkSupportedResourceUrlScheme(URL url, String loc) { + String scheme = url.getProtocol(); + if ("https".equalsIgnoreCase(scheme) + || "file".equalsIgnoreCase(scheme) + || "jar".equalsIgnoreCase(scheme) + || "zip".equalsIgnoreCase(scheme) + || "wsjar".equalsIgnoreCase(scheme)) { + return; + } + if ("http".equalsIgnoreCase(scheme)) { + throw new SignatureException("URL resource must use HTTPS: " + loc); + } + throw new SignatureException("URL scheme '" + scheme + + "' is not supported for HTTP signature resource loading: " + loc); + } + private static URL getClasspathResourceURL(String path, Class callingClass, Bus bus) { URL url = ClassLoaderUtils.getResource(path, callingClass); return url == null ? getResource(path, URL.class, bus) : url; diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseUtils.java index 0b52a35ef95..2bfdfaa554a 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseUtils.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.spec.AlgorithmParameterSpec; @@ -176,7 +177,7 @@ public static URL getResourceURL(String loc, Bus bus) throws IOException { } else { try { url = new URL(loc); - } catch (Exception ex) { + } catch (MalformedURLException ex) { // it can be either a classpath or file resource without a scheme url = JoseUtils.getClasspathResourceURL(loc, JoseUtils.class, bus); if (url == null) { @@ -186,6 +187,9 @@ public static URL getResourceURL(String loc, Bus bus) throws IOException { } } } + if (url != null) { + checkSupportedResourceUrlScheme(url, loc); + } } if (url == null) { LOG.warning("No resource " + loc + " is available"); @@ -193,6 +197,21 @@ public static URL getResourceURL(String loc, Bus bus) throws IOException { return url; } + private static void checkSupportedResourceUrlScheme(URL url, String loc) throws IOException { + String scheme = url.getProtocol(); + if ("https".equalsIgnoreCase(scheme) + || "file".equalsIgnoreCase(scheme) + || "jar".equalsIgnoreCase(scheme) + || "zip".equalsIgnoreCase(scheme) + || "wsjar".equalsIgnoreCase(scheme)) { + return; + } + if ("http".equalsIgnoreCase(scheme)) { + throw new IOException("URL resource must use HTTPS: " + loc); + } + throw new IOException("URL scheme '" + scheme + "' is not supported for JOSE resource loading: " + loc); + } + public static URL getClasspathResourceURL(String path, Class callingClass, Bus bus) { URL url = ClassLoaderUtils.getResource(path, callingClass); return url == null ? getResource(path, URL.class, bus) : url; diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java index f7f30daccbb..68818d71501 100644 --- a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java +++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwk/JwkUtilsTest.java @@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class JwkUtilsTest { @@ -208,6 +209,19 @@ public void testLoadPublicJwkSet() throws Exception { } } + @Test + public void testLoadPublicJwkSetRejectsHttpUrl() throws Exception { + final Properties props = new Properties(); + props.setProperty(JoseConstants.RSSEC_KEY_STORE_FILE, "http://example.com/keys.jwks"); + try { + JwkUtils.loadPublicJwkSet(null, props); + fail(); + } catch (JwkException e) { + assertNotNull(e.getCause()); + assertTrue(e.getCause().getMessage().contains("must use HTTPS")); + } + } + @Test public void testEcLeadingZeros() throws Exception { try (InputStream inputStream = this.getClass().getResourceAsStream("cert.pem")) { diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidator.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidator.java index b9e22be589d..563d80aeb50 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidator.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidator.java @@ -18,6 +18,8 @@ */ package org.apache.cxf.rs.security.oauth2.filters; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -55,7 +57,7 @@ protected JwsSignatureVerifier getInitializedSignatureVerifier(JwsHeaders jwsHea } public void setJwksURL(String jwksURL) { - this.jwksURL = jwksURL; + this.jwksURL = validateJwksURL(jwksURL); } @Override @@ -88,6 +90,20 @@ JsonWebKeys getJsonWebKeys() { .accept(MediaType.APPLICATION_JSON).get(JsonWebKeys.class); } + private static String validateJwksURL(String theJwksURL) { + Objects.requireNonNull(theJwksURL, "JWK Set URL must be specified"); + final URL url; + try { + url = new URL(theJwksURL); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid JWK Set URL: " + theJwksURL, ex); + } + if (!"https".equalsIgnoreCase(url.getProtocol())) { + throw new IllegalArgumentException("JWK Set URL must use HTTPS scheme"); + } + return theJwksURL; + } + // from Java 11 @SuppressWarnings("unchecked") static Predicate not(Predicate target) { diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AuthorizationMetadata.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AuthorizationMetadata.java index ad91bacea4c..eaa8febeb07 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AuthorizationMetadata.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AuthorizationMetadata.java @@ -91,11 +91,11 @@ public void setTokenEndpoint(URL tokenEndpoint) { } public URL getJwksURL() { - return getURLProperty(JWKS_URI); + return checkHttpsScheme(getURLProperty(JWKS_URI), JWKS_URI); } public void setJwksURL(URL jwksURL) { - setURLProperty(JWKS_URI, jwksURL); + setURLProperty(JWKS_URI, checkHttpsScheme(jwksURL, JWKS_URI)); } public URL getRegistrationEndpoint() { @@ -263,4 +263,11 @@ protected void setURLProperty(String name, URL url) { super.setProperty(name, url != null ? url.toString() : null); } + private static URL checkHttpsScheme(URL url, String name) { + if (url != null && !"https".equalsIgnoreCase(url.getProtocol())) { + throw new IllegalArgumentException(name + " must use HTTPS scheme"); + } + return url; + } + } diff --git a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidatorTest.java b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidatorTest.java index 71a21de2ee7..4a16a68ad1f 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidatorTest.java +++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwsJwksJwtAccessTokenValidatorTest.java @@ -87,4 +87,9 @@ public void testGetInitializedSignatureVerifierNoKid() { new JwsJwksJwtAccessTokenValidator().getInitializedSignatureVerifier(new JwsHeaders()); } + @Test(expected = IllegalArgumentException.class) + public void testSetJwksURLRejectsHttp() { + new JwsJwksJwtAccessTokenValidator().setJwksURL("http://any.url"); + } + } \ No newline at end of file