From 9b19c7a665a1ad11b640cc170816717e193c47ba Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Fri, 3 Jul 2026 16:36:54 +0100 Subject: [PATCH] Switch XML Security CryptoLoader to use URIResolver --- .../org/apache/cxf/resource/URIResolver.java | 5 +- .../cxf/rs/security/common/CryptoLoader.java | 8 +- .../rs/security/common/CryptoLoaderTest.java | 78 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/common/CryptoLoaderTest.java diff --git a/core/src/main/java/org/apache/cxf/resource/URIResolver.java b/core/src/main/java/org/apache/cxf/resource/URIResolver.java index 1a823839b7d..633cf682e96 100644 --- a/core/src/main/java/org/apache/cxf/resource/URIResolver.java +++ b/core/src/main/java/org/apache/cxf/resource/URIResolver.java @@ -450,7 +450,10 @@ private void tryRemote(String uriStr) throws IOException { } } - private static void checkAllowedScheme(URL targetUrl) throws IOException { + /** + * Verifies that the URL scheme is permitted by URIResolver allowlist policy. + */ + public static void checkAllowedScheme(URL targetUrl) throws IOException { String scheme = targetUrl.getProtocol(); if (scheme == null) { return; diff --git a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/common/CryptoLoader.java b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/common/CryptoLoader.java index 6640156f1e5..4e4daa527b5 100644 --- a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/common/CryptoLoader.java +++ b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/common/CryptoLoader.java @@ -29,6 +29,7 @@ import org.apache.cxf.endpoint.Endpoint; import org.apache.cxf.helpers.CastUtils; import org.apache.cxf.message.Message; +import org.apache.cxf.resource.URIResolver; import org.apache.cxf.service.model.EndpointInfo; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoFactory; @@ -85,10 +86,11 @@ public Crypto getCrypto(Message message, } public static Crypto loadCryptoFromURL(URL url) throws IOException, WSSecurityException { + URIResolver.checkAllowedScheme(url); Properties props = new Properties(); - InputStream in = url.openStream(); - props.load(in); - in.close(); + try (InputStream in = url.openStream()) { + props.load(in); + } return CryptoFactory.getInstance(props); } diff --git a/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/common/CryptoLoaderTest.java b/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/common/CryptoLoaderTest.java new file mode 100644 index 00000000000..f8cea46635f --- /dev/null +++ b/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/common/CryptoLoaderTest.java @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.rs.security.common; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.ext.WSSecurityException; + +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class CryptoLoaderTest { + + @Test + public void testDisallowedSchemeRejected() throws Exception { + URL url = new URL("ftp://example.com/crypto.properties"); + + try { + CryptoLoader.loadCryptoFromURL(url); + fail("Expected IOException for disallowed scheme"); + } catch (IOException ex) { + assertTrue(ex.getMessage().contains("not permitted for URIResolver")); + } + } + + @Test + public void testAllowedFileSchemeLoadsCrypto() throws Exception { + File tmp = File.createTempFile("cxf-crypto", ".properties"); + tmp.deleteOnExit(); + + String props = "org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin\n" + + "org.apache.wss4j.crypto.merlin.keystore.type=jks\n"; + + try (FileOutputStream out = new FileOutputStream(tmp)) { + out.write(props.getBytes(StandardCharsets.UTF_8)); + } + + Crypto crypto = CryptoLoader.loadCryptoFromURL(tmp.toURI().toURL()); + assertNotNull(crypto); + } + + @Test(expected = WSSecurityException.class) + public void testAllowedFileSchemeWithBadPropertiesFailsInCryptoFactory() throws Exception { + File tmp = File.createTempFile("cxf-crypto-invalid", ".properties"); + tmp.deleteOnExit(); + + try (FileOutputStream out = new FileOutputStream(tmp)) { + out.write("org.apache.wss4j.crypto.provider=bad.Provider\n".getBytes(StandardCharsets.UTF_8)); + } + + CryptoLoader.loadCryptoFromURL(tmp.toURI().toURL()); + } +}