/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * This product combines work with different licenses. See the "NOTICE" text file for details on the * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative * works that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egiz.eaaf.modules.pvp2.impl.binding; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; import at.gv.egiz.eaaf.modules.pvp2.api.message.InboundMessageInterface; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.exception.InvalidPvpRequestException; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2Exception; import at.gv.egiz.eaaf.modules.pvp2.exception.SamlBindingException; import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileResponse; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.EaafHttpRedirectDeflateDecoder; import at.gv.egiz.eaaf.modules.pvp2.impl.verification.PvpSamlMessageHandlerChain; import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.decoder.MessageDecodingException; import org.opensaml.messaging.handler.MessageHandlerException; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.SAMLBindingSupport; import org.opensaml.saml.common.binding.impl.CheckMessageVersionHandler; import org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHandler; import org.opensaml.saml.common.messaging.context.SAMLBindingContext; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder; import org.opensaml.saml.saml2.binding.security.impl.SAML2HTTPRedirectDeflateSignatureSecurityHandler; import org.opensaml.saml.saml2.core.RequestAbstractType; import org.opensaml.saml.saml2.core.StatusResponseType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.net.URIComparator; public class RedirectBinding extends AbstractBinding implements IDecoder, IEncoder { private static final Logger log = LoggerFactory.getLogger(RedirectBinding.class); @Override public void encodeRequest(final HttpServletRequest req, final HttpServletResponse resp, final RequestAbstractType request, final String targetLocation, final String relayState, final EaafX509Credential credentials, final IRequest pendingReq) throws Pvp2Exception { try { log.debug("create SAML RedirectBinding response"); final HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder(); encoder.setHttpServletResponse(resp); final MessageContext messageContext = buildBasicMessageContext(encoder, request); // set endpoint url messageContext.addSubcontext(injectEndpointInfos(request, targetLocation)); // inject signing context messageContext.addSubcontext(injectSigningInfos(credentials)); // set relayState of exists SAMLBindingSupport.setRelayState(messageContext, relayState); // encode message encoder.initialize(); encoder.encode(); } catch (final Exception e) { log.warn("Can not encode SAML2 Redirect-Binding request", e); throw new SamlBindingException("internal.pvp.95", new Object[] { PvpConstants.REDIRECT, "encoding", e.getMessage() }, e); } } @Override public void encodeResponse(final HttpServletRequest req, final HttpServletResponse resp, final StatusResponseType response, final String targetLocation, final String relayState, final EaafX509Credential credentials, final IRequest pendingReq) throws Pvp2Exception { try { log.debug("create SAML RedirectBinding response"); final HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder(); encoder.setHttpServletResponse(resp); final MessageContext messageContext = buildBasicMessageContext(encoder, response); // set endpoint url messageContext.addSubcontext(injectEndpointInfos(response, targetLocation)); // inject signing context messageContext.addSubcontext(injectSigningInfos(credentials)); // set relayState of exists SAMLBindingSupport.setRelayState(messageContext, relayState); // encode message encoder.initialize(); encoder.encode(); } catch (final Exception e) { log.warn("Can not encode SAML2 Redirect-Binding request", e); throw new SamlBindingException("internal.pvp.95", new Object[] { PvpConstants.REDIRECT, "encoding", e.getMessage() }, e); } } @Override public InboundMessageInterface decode(final HttpServletRequest req, final HttpServletResponse resp, final IPvp2MetadataProvider metadataProvider, final boolean isSpEndPoint, final URIComparator comparator) throws Pvp2Exception { // TODO: implement one flat decoder to get SAML2 request/response parametes as // same as in SAML2HTTPRedirectDeflateSignatureSecurityHandler final EaafHttpRedirectDeflateDecoder decode = new EaafHttpRedirectDeflateDecoder(); decode.setHttpServletRequest(req); // decode request try { decode.initialize(); decode.decode(); } catch (MessageDecodingException | ComponentInitializationException e) { throw new SamlBindingException("internal.pvp.95", new Object[] { PvpConstants.REDIRECT, "decoding", e.getMessage() }, e); } final MessageContext messageContext = decode.getMessageContext(); final SAMLBindingContext bindingContext = messageContext.getSubcontext(SAMLBindingContext.class, true); if (!bindingContext.hasBindingSignature()) { log.info("SAML Redirect-Binding message contains no signature. Message will be rejected"); throw new InvalidPvpRequestException("internal.pvp.02", null); } //inject informations into message context that are required for further processing injectInboundMessageContexts(messageContext, metadataProvider); log.trace("Signature validation on binding-level starts ... "); final PvpSamlMessageHandlerChain messageValidatorChain = new PvpSamlMessageHandlerChain(); final SAML2HTTPRedirectDeflateSignatureSecurityHandler redirectBindingSignaturHandler = new SAML2HTTPRedirectDeflateSignatureSecurityHandler(); redirectBindingSignaturHandler.setHttpServletRequest(req); messageValidatorChain.addHandler(new CheckMessageVersionHandler()); messageValidatorChain.addHandler(redirectBindingSignaturHandler); messageValidatorChain.addHandler(new MessageLifetimeSecurityHandler()); /*TODO: maybe we add it in a later version * Because: * - AuthnRequest replay should not be a problem on IDP side * - Response replay will be not possible, because EAAF PVP implements * countermeasure based on one-time tokens for each request * */ //final MessageReplaySecurityHandler replaySecurityHandler = new MessageReplaySecurityHandler(); //final StorageService replayCacheStorage = null; //final ReplayCache replayCache = new ReplayCache(); //replayCache.setId("Message replay cache"); //replayCache.setStrict(true); //replayCache.setStorage(replayCacheStorage); //replaySecurityHandler.setReplayCache(replayCache ); //messageValidatorChain.addHandler(replaySecurityHandler); try { messageValidatorChain.initialize(); messageValidatorChain.invoke(messageContext); } catch (final ComponentInitializationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (final MessageHandlerException e) { // TODO Auto-generated catch block e.printStackTrace(); } InboundMessage msg = null; if (messageContext.getMessage() instanceof RequestAbstractType) { final RequestAbstractType inboundMessage = (RequestAbstractType) messageContext.getMessage(); msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); msg.setEntityID(inboundMessage.getIssuer().getValue()); } else if (messageContext.getMessage() instanceof StatusResponseType) { final StatusResponseType inboundMessage = (StatusResponseType) messageContext.getMessage(); msg = new PvpSProfileResponse(inboundMessage); msg.setEntityID(inboundMessage.getIssuer().getValue()); } else { // create empty container if request type is unknown msg = new InboundMessage(); } msg.setVerified(false); msg.setRelayState(SAMLBindingSupport.getRelayState(messageContext)); return msg; // final BasicSAMLMessageContext messageContext = // new BasicSAMLMessageContext<>(); // messageContext.setInboundMessageTransport(new HttpServletRequestAdapter(req)); // // // set metadata descriptor type // if (isSpEndPoint) { // messageContext.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); // decode.setURIComparator(comparator); // // } else { // messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); // decode.setURIComparator(comparator); // } // // messageContext.setMetadataProvider(metadataProvider); // // final SAML2HTTPRedirectDeflateSignatureRule signatureRule = // new SAML2HTTPRedirectDeflateSignatureRule( // TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); // final PvpAuthRequestSignedRole signedRole = new PvpAuthRequestSignedRole(); // final BasicSecurityPolicy policy = new BasicSecurityPolicy(); // policy.getPolicyRules().add(signedRole); // policy.getPolicyRules().add(signatureRule); // final SecurityPolicyResolver resolver = new StaticSecurityPolicyResolver(policy); // messageContext.setSecurityPolicyResolver(resolver); // // // set metadata descriptor type // if (isSpEndPoint) { // messageContext.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME); // } else { // messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); // } // // SAML2AuthnRequestsSignedSecurityHandler // // try { // decode.decode(messageContext); // // // check signature // signatureRule.evaluate(messageContext); // // } catch (final SecurityException e) { // if (StringUtils.isEmpty(messageContext.getInboundMessageIssuer())) { // throw e; // // } // // if (metadataProvider instanceof IRefreshableMetadataProvider) { // log.debug("PVP2X message validation FAILED. Reload metadata for entityID: " // + messageContext.getInboundMessageIssuer()); // if (!((IRefreshableMetadataProvider) metadataProvider) // .refreshMetadataProvider(messageContext.getInboundMessageIssuer())) { // throw e; // } else { // log.trace("PVP2X metadata reload finished. Check validate message again."); // decode.decode(messageContext); // // // check signature // signatureRule.evaluate(messageContext); // // } // log.trace("Second PVP2X message validation finished"); // // } else { // throw e; // // } // } // // InboundMessage msg = null; // if (messageContext.getInboundMessage() instanceof RequestAbstractType) { // final RequestAbstractType inboundMessage = // (RequestAbstractType) messageContext.getInboundMessage(); // msg = new PvpSProfileRequest(inboundMessage, getSaml2BindingName()); // // } else if (messageContext.getInboundMessage() instanceof StatusResponseType) { // final StatusResponseType inboundMessage = // (StatusResponseType) messageContext.getInboundMessage(); // msg = new PvpSProfileResponse(inboundMessage); // // } else { // // create empty container if request type is unknown // msg = new InboundMessage(); // } // // if (messageContext.getPeerEntityMetadata() != null) { // msg.setEntityID(messageContext.getPeerEntityMetadata().getEntityID()); // } else { // log.info( // "No Metadata found for OA with EntityID " + messageContext.getInboundMessageIssuer()); // } // // msg.setVerified(true); // msg.setRelayState(messageContext.getRelayState()); // // return msg; } @Override public boolean handleDecode(final String action, final HttpServletRequest req) { return (action.equals(PvpConstants.REDIRECT) || action.equals(PvpConstants.SINGLELOGOUT)) && req.getMethod().equals("GET"); } @Override public String getSaml2BindingName() { return SAMLConstants.SAML2_REDIRECT_BINDING_URI; } }