This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-rampart.git
commit f8dcc7460487a18382320a0a553781e914fafb31 Author: Robert Lazarski <robertlazar...@gmail.com> AuthorDate: Wed Nov 6 10:41:26 2024 -1000 RAMPART-234 Allow custom https listeners to populate the client certificate chain in the message context --- .../java/org/apache/rampart/RampartConstants.java | 5 + .../java/org/apache/rampart/util/RampartUtil.java | 114 +++++++++++++++++---- .../resources/org/apache/rampart/errors.properties | 5 +- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/modules/rampart-core/src/main/java/org/apache/rampart/RampartConstants.java b/modules/rampart-core/src/main/java/org/apache/rampart/RampartConstants.java index e280d746..a7741f10 100644 --- a/modules/rampart-core/src/main/java/org/apache/rampart/RampartConstants.java +++ b/modules/rampart-core/src/main/java/org/apache/rampart/RampartConstants.java @@ -5,6 +5,11 @@ public class RampartConstants { public static final String TIME_LOG = "org.apache.rampart.TIME"; public static final String MESSAGE_LOG = "org.apache.rampart.MESSAGE"; public static final String SEC_FAULT = "SECURITY_VALIDATION_FAILURE"; + /** + * The key under which the HTTPS client certificate, determened by the https listener, may + * be populated as a property of the message context. + */ + public static final String HTTPS_CLIENT_CERT_KEY = "https.client.cert.key"; public static final String MERLIN_CRYPTO_IMPL = "org.apache.ws.security.components.crypto.Merlin"; public static final String MERLIN_CRYPTO_IMPL_CACHE_KEY = "org.apache.ws.security.crypto.merlin.file"; diff --git a/modules/rampart-core/src/main/java/org/apache/rampart/util/RampartUtil.java b/modules/rampart-core/src/main/java/org/apache/rampart/util/RampartUtil.java index 2ed59869..c09ca1ab 100644 --- a/modules/rampart-core/src/main/java/org/apache/rampart/util/RampartUtil.java +++ b/modules/rampart-core/src/main/java/org/apache/rampart/util/RampartUtil.java @@ -31,11 +31,14 @@ import org.apache.axis2.dataretrieval.DRConstants; import org.apache.axis2.dataretrieval.client.MexClient; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.TransportInDescription; +import org.apache.axis2.engine.AxisConfiguration; import org.apache.axis2.mex.MexConstants; import org.apache.axis2.mex.MexException; import org.apache.axis2.mex.om.Metadata; import org.apache.axis2.mex.om.MetadataReference; import org.apache.axis2.mex.om.MetadataSection; +import org.apache.axis2.kernel.TransportListener; import org.apache.axis2.kernel.http.HTTPConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -58,7 +61,14 @@ import org.apache.rampart.policy.model.CryptoConfig; import org.apache.rampart.policy.model.KerberosConfig; import org.apache.rampart.policy.model.RampartConfig; import org.apache.ws.secpolicy.SPConstants; -import org.apache.ws.secpolicy.model.*; +import org.apache.ws.secpolicy.model.HttpsToken; +import org.apache.ws.secpolicy.model.IssuedToken; +import org.apache.ws.secpolicy.model.SecureConversationToken; +import org.apache.ws.secpolicy.model.SupportingToken; +import org.apache.ws.secpolicy.model.UsernameToken; +import org.apache.ws.secpolicy.model.Wss10; +import org.apache.ws.secpolicy.model.Wss11; +import org.apache.ws.secpolicy.model.X509Token; import org.apache.wss4j.dom.message.WSSecBase; import org.apache.wss4j.common.crypto.CryptoType; import org.apache.wss4j.common.crypto.Crypto; @@ -1814,29 +1824,91 @@ public class RampartUtil { return null; } - public static void validateTransport(RampartMessageData rmd) throws RampartException { - - RampartPolicyData rpd = rmd.getPolicyData(); - - if (rpd == null) { - return; - } - - if (rpd.isTransportBinding() && !rmd.isInitiator()) { - if (rpd.getTransportToken() instanceof HttpsToken) { - String incomingTransport = rmd.getMsgContext().getIncomingTransportName(); - if (!incomingTransport.equals(org.apache.axis2.Constants.TRANSPORT_HTTPS)) { - throw new RampartException("invalidTransport", - new String[]{incomingTransport}); + /** + * Validate transport binding policy assertions. + * In case an HttpsToken is required by the security policy the method will verify that the + * HTTPS transport was used indeed. Furthermore if the assertion requires a client certificate + * being used, the method will try to obtain the client certificate chain first from the + * message context properties directly under the key {@link RampartConstants#HTTPS_CLIENT_CERT_KEY} + * and, if the property is not available, will try to get the HttpsServletRequest from the + * message context properties (populated there by the AxisServlet if axis2 is running inside a servlet + * engine) and retrieve the https client certificate chain from its attributes. The client certificate + * chain is expected to be available under the <code>javax.servlet.request.X509Certificate</code> + * attribute of the servlet request. No further trust verification is done for the client + * certificate - the transport listener should have already verified this. + * + * @param messageData + * @throws RampartException + */ + public static void validateTransport(RampartMessageData messageData) throws RampartException { + + MessageContext msgContext = messageData.getMsgContext(); + RampartPolicyData policyData = messageData.getPolicyData(); + AxisConfiguration axisConf = msgContext.getConfigurationContext().getAxisConfiguration(); + + if(policyData != null && policyData.isTransportBinding() && !messageData.isInitiator()){ + if (policyData.getTransportToken() instanceof HttpsToken) { + try { + TransportInDescription transportIn = msgContext.getTransportIn(); + if (transportIn == null) { + transportIn = msgContext.getOptions().getTransportIn(); + } + + //maybe the transportIn was not populated by the receiver + if (transportIn == null) { + transportIn = axisConf.getTransportIn(msgContext.getIncomingTransportName()); + } + + if (transportIn == null) { + throw new RampartException("httpsVerificationFailed"); + } + + TransportListener receiver = transportIn.getReceiver(); + String incomingEPR = receiver.getEPRsForService(msgContext.getAxisService().getName(), + null)[0].getAddress(); + if (incomingEPR == null) { + incomingEPR = msgContext.getIncomingTransportName(); + } + + if (!incomingEPR.startsWith(org.apache.axis2.Constants.TRANSPORT_HTTPS)) { + if (incomingEPR.indexOf(':') > 0) { + incomingEPR = incomingEPR.substring(0, incomingEPR.indexOf(':')); + } + throw new RampartException("invalidTransport", new String[] { incomingEPR }); + } + } catch (AxisFault af) { + String incomingTransport = msgContext.getIncomingTransportName(); + if (!incomingTransport.equals(org.apache.axis2.Constants.TRANSPORT_HTTPS)) { + throw new RampartException("invalidTransport", new String[] { incomingTransport }); + } } - if (((HttpsToken) rpd.getTransportToken()).isRequireClientCertificate()) { - MessageContext messageContext = rmd.getMsgContext(); - HttpServletRequest request = ((HttpServletRequest) messageContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST)); - if (request == null || request.getAttribute("javax.servlet.request.X509Certificate") == null) { - throw new RampartException("clientAuthRequired"); + MessageContext messageContext = messageData.getMsgContext(); + HttpServletRequest request = ((HttpServletRequest) messageContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST)); + + // verify client certificate used + // try to obtain the client certificate chain directly from the message context + // and then from the servlet request + HttpsToken token = (HttpsToken)policyData.getTransportToken(); + if (token.isRequireClientCertificate()) { + Object certificateChainProperty = msgContext.getProperty(RampartConstants.HTTPS_CLIENT_CERT_KEY); + if (certificateChainProperty instanceof X509Certificate[]) { + // HTTPS client certificate chain found + return; + } else { + Object requestProperty = msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); + if (requestProperty instanceof HttpServletRequest) { + Object certificateChain = request.getAttribute("javax.servlet.request.X509Certificate"); //$NON-NLS-1$ + if (certificateChain instanceof X509Certificate[]) { + // HTTPS client certificate chain found + return; + } + } } - } + + // HTTPS client certificate chain NOT found + throw new RampartException("httpsClientCertValidationFailed"); + } } } diff --git a/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties b/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties index 034b91d1..fea93359 100644 --- a/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties +++ b/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties @@ -101,6 +101,9 @@ bodyNotSigned = Soap Body must be signed unexprectedSignature = Unexpected signature invalidTransport = Expected transport is "https" but incoming transport found : \"{0}\" requiredElementsMissing = Required Elements not found in the incoming message : {0} +httpsVerificationFailed = Unable to verify HTTPS transport usage: incoming transport description is unavailable +httpsClientCertValidationFailed = Unable to verify HTTPS client certificate usage: client certificate chain is not available. +requiredElementsMissing = Required Elements not found in the incoming message : {0} repeatingNonceValue = Nonce value : {0}, already seen before for user name : {1}. Possibly this could be a replay attack. invalidNonceLifeTime = Invalid value for nonceLifeTime in rampart configuration file. invalidIssuerAddress = Invalid value for Issuer @@ -112,4 +115,4 @@ invalidServicePrincipalNameForm = Invalid servicePrincipalNameForm found in Ramp noKerberosConfigDefined = No kerberosConfig policy assertion defined in rampart config. errorInBuildingKereberosToken = Error in building kereberos token. cannotLoadKrbTokenDecoderClass = Cannot load Kerberos token decoder class: {0} -cannotCreateKrbTokenDecoderInstance = Cannot create instance of Kerberos token decoder : {0} \ No newline at end of file +cannotCreateKrbTokenDecoderInstance = Cannot create instance of Kerberos token decoder : {0}