diff --git a/modules/rampart-core/src/main/java/org/apache/rampart/PolicyBasedResultsValidator.java b/modules/rampart-core/src/main/java/org/apache/rampart/PolicyBasedResultsValidator.java index 63efd3b0..a78e52eb 100644 --- a/modules/rampart-core/src/main/java/org/apache/rampart/PolicyBasedResultsValidator.java +++ b/modules/rampart-core/src/main/java/org/apache/rampart/PolicyBasedResultsValidator.java @@ -931,7 +931,7 @@ protected boolean isCertificateInKeyStore( // If a certificate has been found, the certificates must be compared // to ensure against phony DNs (compare encoded form including signature) // - if (foundCerts != null && foundCerts[0] != null && foundCerts[0].equals(cert)) { + if (foundCerts != null && foundCerts.length > 0 && foundCerts[0] != null && foundCerts[0].equals(cert)) { if (log.isDebugEnabled()) { log.debug( "Direct trust for certificate with " + cert.getSubjectX500Principal().getName() diff --git a/modules/rampart-core/src/test/java/org/apache/rampart/PolicyBasedResultsValidatorTest.java b/modules/rampart-core/src/test/java/org/apache/rampart/PolicyBasedResultsValidatorTest.java index 43a4b6fc..fb4dae26 100644 --- a/modules/rampart-core/src/test/java/org/apache/rampart/PolicyBasedResultsValidatorTest.java +++ b/modules/rampart-core/src/test/java/org/apache/rampart/PolicyBasedResultsValidatorTest.java @@ -19,15 +19,33 @@ package org.apache.rampart; import java.io.ByteArrayInputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import junit.framework.TestCase; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import javax.security.auth.callback.CallbackHandler; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.CryptoType; +import org.apache.wss4j.common.ext.WSSecurityException; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -39,6 +57,114 @@ */ public class PolicyBasedResultsValidatorTest extends TestCase { + // ----------------------------------------------------------------------- + // isCertificateInKeyStore tests (RAMPART-461) + // ----------------------------------------------------------------------- + + /** + * Concrete subclass that exposes the protected method under test. + * No real crypto wiring is needed — we inject stub Crypto instances. + */ + private static class Validator extends PolicyBasedResultsValidator { + boolean callIsCertificateInKeyStore(Crypto crypto, X509Certificate cert) throws RampartException { + return isCertificateInKeyStore(crypto, cert); + } + } + + /** Minimal Crypto stub whose getX509Certificates() is controlled by the caller. */ + private static class StubCrypto implements Crypto { + private final X509Certificate[] returnValue; + + StubCrypto(X509Certificate[] returnValue) { + this.returnValue = returnValue; + } + + public X509Certificate[] getX509Certificates(CryptoType cryptoType) throws WSSecurityException { + return returnValue; + } + + // ---- All other Crypto methods are unused by isCertificateInKeyStore ---- + public String getCryptoProvider() { return null; } + public void setCryptoProvider(String provider) {} + public String getTrustProvider() { return null; } + public void setTrustProvider(String provider) {} + public String getDefaultX509Identifier() throws WSSecurityException { return null; } + public void setDefaultX509Identifier(String identifier) {} + public void setCertificateFactory(java.security.cert.CertificateFactory f) {} + public java.security.cert.CertificateFactory getCertificateFactory() throws WSSecurityException { return null; } + public X509Certificate loadCertificate(java.io.InputStream in) throws WSSecurityException { return null; } + public byte[] getSKIBytesFromCert(X509Certificate cert) throws WSSecurityException { return null; } + public byte[] getBytesFromCertificates(X509Certificate[] certs) throws WSSecurityException { return null; } + public X509Certificate[] getCertificatesFromBytes(byte[] data) throws WSSecurityException { return null; } + public String getX509Identifier(X509Certificate cert) throws WSSecurityException { return null; } + public java.security.PrivateKey getPrivateKey(X509Certificate cert, CallbackHandler cb) throws WSSecurityException { return null; } + public java.security.PrivateKey getPrivateKey(java.security.PublicKey publicKey, CallbackHandler cb) throws WSSecurityException { return null; } + public java.security.PrivateKey getPrivateKey(String identifier, String password) throws WSSecurityException { return null; } + public void verifyTrust(X509Certificate[] certs, boolean enableRevocation, + java.util.Collection subjectCertConstraints, + java.util.Collection issuerCertConstraints) throws WSSecurityException {} + public void verifyTrust(java.security.PublicKey publicKey) throws WSSecurityException {} + } + + /** Generates a fresh self-signed RSA certificate in memory. */ + private static X509Certificate generateSelfSignedCert() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + X500Name name = new X500Name("CN=Test, O=Rampart, C=US"); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); + Date notBefore = new Date(System.currentTimeMillis() - 1000L); + Date notAfter = new Date(System.currentTimeMillis() + 365L * 24 * 3600 * 1000); + + X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + name, serial, notBefore, notAfter, name, kp.getPublic()); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(kp.getPrivate()); + return new JcaX509CertificateConverter().getCertificate(builder.build(signer)); + } + + /** + * RAMPART-461: WSS4J 2.x returns an empty array (never null) when no + * certificate matches. Before the fix, foundCerts[0] threw + * ArrayIndexOutOfBoundsException. After the fix it must return false. + */ + public void testEmptyArrayReturnsFalse() throws Exception { + Validator v = new Validator(); + X509Certificate cert = generateSelfSignedCert(); + // WSS4J 2.x: no match → empty array + assertFalse("empty array must return false", + v.callIsCertificateInKeyStore(new StubCrypto(new X509Certificate[0]), cert)); + } + + /** Null array (legacy / defensive) must return false without NPE. */ + public void testNullArrayReturnsFalse() throws Exception { + Validator v = new Validator(); + X509Certificate cert = generateSelfSignedCert(); + assertFalse("null array must return false", + v.callIsCertificateInKeyStore(new StubCrypto(null), cert)); + } + + /** When the keystore holds the exact same cert, return true. */ + public void testMatchingCertReturnsTrue() throws Exception { + Validator v = new Validator(); + X509Certificate cert = generateSelfSignedCert(); + assertTrue("same cert must return true", + v.callIsCertificateInKeyStore(new StubCrypto(new X509Certificate[]{cert}), cert)); + } + + /** When the keystore holds a different cert, return false. */ + public void testDifferentCertReturnsFalse() throws Exception { + Validator v = new Validator(); + X509Certificate certA = generateSelfSignedCert(); + X509Certificate certB = generateSelfSignedCert(); + assertFalse("different cert must return false", + v.callIsCertificateInKeyStore(new StubCrypto(new X509Certificate[]{certB}), certA)); + } + + // ----------------------------------------------------------------------- + // Signature-wrapping tests (RAMPART-428) — unchanged below + // ----------------------------------------------------------------------- + private static final String SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/"; // A signature-wrapping layout: the original (signed) Body is relocated into a