/* * Copyright (c) 2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.wso2.carbon.identity.entitlement.wsxacml; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMNamespace; import org.apache.axiom.om.util.AXIOMUtil; import org.apache.axiom.soap.SOAP11Constants; import org.apache.axiom.soap.SOAP12Constants; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFactory; import org.apache.axis2.AxisFault; import org.apache.axis2.context.MessageContext; import org.apache.axis2.rpc.receivers.RPCMessageReceiver; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xerces.impl.Constants; import org.apache.xerces.util.SecurityManager; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.signature.XMLSignature; import org.joda.time.DateTime; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Issuer; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.Statement; import org.opensaml.saml2.core.impl.AssertionBuilder; import org.opensaml.saml2.core.impl.IssuerBuilder; import org.opensaml.saml2.core.impl.ResponseBuilder; import org.opensaml.xacml.ctx.RequestType; import org.opensaml.xacml.ctx.ResponseType; import org.opensaml.xacml.profile.saml.XACMLAuthzDecisionQueryType; import org.opensaml.xacml.profile.saml.XACMLAuthzDecisionStatementType; import org.opensaml.xacml.profile.saml.impl.XACMLAuthzDecisionStatementTypeImplBuilder; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.XMLObjectBuilder; import org.opensaml.xml.io.Marshaller; import org.opensaml.xml.io.MarshallerFactory; import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallerFactory; import org.opensaml.xml.security.x509.BasicX509Credential; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.KeyInfo; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureValidator; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.X509Certificate; import org.opensaml.xml.signature.X509Data; import org.opensaml.xml.validation.ValidationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.identity.entitlement.EntitlementException; import org.wso2.carbon.identity.entitlement.util.CarbonEntityResolver; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class WSXACMLMessageReceiver extends RPCMessageReceiver { private static final String SECURITY_MANAGER_PROPERTY = Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; private static final int ENTITY_EXPANSION_LIMIT = 0; private static Log log = LogFactory.getLog(WSXACMLMessageReceiver.class); private static boolean isBootStrapped = false; private static OMNamespace xacmlContextNS = OMAbstractFactory.getOMFactory() .createOMNamespace("urn:oasis:names:tc:xacml:2.0:context:schema:os", "xacml-context"); /** * Bootstrap the OpenSAML2 library only if it is not bootstrapped. */ public static void doBootstrap() { if (!isBootStrapped) { try { DefaultBootstrap.bootstrap(); isBootStrapped = true; } catch (ConfigurationException e) { log.error("Error in bootstrapping the OpenSAML2 library", e); } } } /** * Create the issuer object to be added * * @return : the issuer of the statements */ private static Issuer createIssuer() { IssuerBuilder issuer = (IssuerBuilder) org.opensaml.xml.Configuration.getBuilderFactory(). getBuilder(Issuer.DEFAULT_ELEMENT_NAME); Issuer issuerObject = issuer.buildObject(); issuerObject.setValue("https://identity.carbon.wso2.org"); issuerObject.setSPProvidedID("SPPProvierId"); return issuerObject; } /** * Overloaded method to sign a SAML response * * @param response : SAML response to be signed * @param signatureAlgorithm : algorithm to be used in signing * @param cred : signing credentials * @return signed SAML response * @throws EntitlementException */ private static Response setSignature(Response response, String signatureAlgorithm, X509Credential cred) throws EntitlementException { doBootstrap(); try { Signature signature = (Signature) buildXMLObject(Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(cred); signature.setSignatureAlgorithm(signatureAlgorithm); signature.setCanonicalizationAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); try { KeyInfo keyInfo = (KeyInfo) buildXMLObject(KeyInfo.DEFAULT_ELEMENT_NAME); X509Data data = (X509Data) buildXMLObject(X509Data.DEFAULT_ELEMENT_NAME); X509Certificate cert = (X509Certificate) buildXMLObject(X509Certificate.DEFAULT_ELEMENT_NAME); String value = org.apache.xml.security.utils.Base64.encode(cred.getEntityCertificate().getEncoded()); cert.setValue(value); data.getX509Certificates().add(cert); keyInfo.getX509Datas().add(data); signature.setKeyInfo(keyInfo); } catch (CertificateEncodingException e) { throw new EntitlementException("errorGettingCert"); } response.setSignature(signature); List<Signature> signatureList = new ArrayList<Signature>(); signatureList.add(signature); //Marshall and Sign MarshallerFactory marshallerFactory = org.opensaml.xml.Configuration.getMarshallerFactory(); Marshaller marshaller = marshallerFactory.getMarshaller(response); marshaller.marshall(response); org.apache.xml.security.Init.init(); Signer.signObjects(signatureList); return response; } catch (Exception e) { throw new EntitlementException("Error When signing the assertion.", e); } } /** * Create XMLObject from a given QName * * @param objectQName: QName of the object to be built into a XMLObject * @return built xmlObject * @throws EntitlementException */ private static XMLObject buildXMLObject(QName objectQName) throws EntitlementException { XMLObjectBuilder builder = org.opensaml.xml.Configuration.getBuilderFactory().getBuilder(objectQName); if (builder == null) { throw new EntitlementException("Unable to retrieve builder for object QName " + objectQName); } return builder.buildObject(objectQName.getNamespaceURI(), objectQName.getLocalPart(), objectQName.getPrefix()); } /** * Create basic credentials needed to generate signature using EntitlementServiceComponent * * @return basicX509Credential */ private static BasicX509Credential createBasicCredentials() { Certificate certificate = null; PrivateKey issuerPK = null; KeyStoreManager keyMan = KeyStoreManager.getInstance(-1234); try { certificate = keyMan.getDefaultPrimaryCertificate(); issuerPK = keyMan.getDefaultPrivateKey(); } catch (Exception e) { log.error("Error occurred while getting the KeyStore from KeyManger.", e); } BasicX509Credential basicCredential = new BasicX509Credential(); basicCredential.setEntityCertificate((java.security.cert.X509Certificate) certificate); basicCredential.setPrivateKey(issuerPK); return basicCredential; } /** * Set relevant xacml namespace to all the children in the given iterator. * * * @param iterator: Iterator for all children inside OMElement */ private static void setXACMLNamespace(Iterator iterator) { while (iterator.hasNext()) { OMElement omElement2 = (OMElement) iterator.next(); omElement2.setNamespace(xacmlContextNS); if (omElement2.getChildElements().hasNext()) { setXACMLNamespace(omElement2.getChildElements()); } } } @Override public void invokeBusinessLogic(MessageContext inMessageContext, MessageContext outMessageContext) throws AxisFault { try { OMElement xacmlAuthzDecisionQueryElement = inMessageContext.getEnvelope().getBody().getFirstElement(); String xacmlAuthzDecisionQuery = xacmlAuthzDecisionQueryElement.toString(); String xacmlRequest = extractXACMLRequest(xacmlAuthzDecisionQuery); String serviceClass; try { serviceClass = inMessageContext.getAxisService().getParameterValue("XACMLHandlerImplClass"). toString().trim(); } catch (NullPointerException e) { log.error("WS-XACML ServiceClass not specified in service context"); throw new AxisFault("WS-XACML ServiceClass not specified in service context"); } if (serviceClass == null || serviceClass.length() == 0) { log.error("WS-XACML ServiceClass not specified in service context"); throw new AxisFault("WS-XACML ServiceClass not specified in service context"); } XACMLHandler xacmlHandler = (XACMLHandler) Class.forName(serviceClass).newInstance(); xacmlRequest = xacmlRequest.replaceAll("xacml-context:", ""); String xacmlResponse = xacmlHandler.XACMLAuthzDecisionQuery(xacmlRequest); String samlResponse = secureXACMLResponse(xacmlResponse); OMElement samlResponseElement = AXIOMUtil.stringToOM(samlResponse); SOAPEnvelope outSOAPEnvelope = createDefaultSOAPEnvelope(inMessageContext); if (outSOAPEnvelope != null) { outSOAPEnvelope.getBody().addChild(samlResponseElement); outMessageContext.setEnvelope(outSOAPEnvelope); } else { throw new Exception("SOAP envelope can not be null"); } } catch (Exception e) { log.error("Error occurred while evaluating XACML request.", e); throw new AxisFault("Error occurred while evaluating XACML request.", e); } } /* Creating a soap response according the the soap namespce uri */ private SOAPEnvelope createDefaultSOAPEnvelope(MessageContext inMsgCtx) { String soapNamespace = inMsgCtx.getEnvelope().getNamespace() .getNamespaceURI(); SOAPFactory soapFactory = null; if (soapNamespace.equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI)) { soapFactory = OMAbstractFactory.getSOAP11Factory(); } else if (soapNamespace .equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)) { soapFactory = OMAbstractFactory.getSOAP12Factory(); } else { log.error("Unknown SOAP Envelope"); } if (soapFactory != null) { return soapFactory.getDefaultEnvelope(); } return null; } /** * Extract XACML request from passed in SAML-XACMLAuthzDecisionQuery * * @param decisionQuery : XACMLAuthxDecisionQuery passed in from PEP as a String * @return xacml Request * @throws Exception */ private String extractXACMLRequest(String decisionQuery) throws Exception { RequestType xacmlRequest = null; doBootstrap(); String queryString = null; XACMLAuthzDecisionQueryType xacmlAuthzDecisionQuery; try { xacmlAuthzDecisionQuery = (XACMLAuthzDecisionQueryType) unmarshall(decisionQuery); //Access the XACML request only if Issuer and the Signature are valid. if (validateIssuer(xacmlAuthzDecisionQuery.getIssuer())) { if (validateSignature(xacmlAuthzDecisionQuery.getSignature())) { xacmlRequest = xacmlAuthzDecisionQuery.getRequest(); } else { log.debug("The submitted signature is not valid!"); } } else { log.debug("The submitted issuer is not valid!"); } if (xacmlRequest != null) { queryString = marshall(xacmlRequest); queryString = queryString.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "").replace("\n", ""); } return queryString; } catch (Exception e) { log.error("Error unmarshalling the XACMLAuthzDecisionQuery.", e); throw new Exception("Error unmarshalling the XACMLAuthzDecisionQuery.", e); } } /** * Constructing the SAML or XACML Objects from a String * * @param xmlString Decoded SAML or XACML String * @return SAML or XACML Object * @throws org.wso2.carbon.identity.entitlement.EntitlementException */ public XMLObject unmarshall(String xmlString) throws EntitlementException { try { doBootstrap(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setExpandEntityReferences(false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT); documentBuilderFactory.setAttribute(SECURITY_MANAGER_PROPERTY, securityManager); DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); docBuilder.setEntityResolver(new CarbonEntityResolver()); Document document = docBuilder.parse(new ByteArrayInputStream(xmlString.trim().getBytes())); Element element = document.getDocumentElement(); UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element); return unmarshaller.unmarshall(element); } catch (Exception e) { log.error("Error in constructing XML(SAML or XACML) Object from the encoded String", e); throw new EntitlementException("Error in constructing XML(SAML or XACML) from the encoded String ", e); } } /** * Check for the validity of the issuer * * @param issuer :who makes the claims inside the Query * @return whether the issuer is valid */ private boolean validateIssuer(Issuer issuer) { boolean isValidated = false; if (issuer.getValue().equals("https://identity.carbon.wso2.org") && issuer.getSPProvidedID().equals("SPPProvierId")) { isValidated = true; } return isValidated; } /** * ` * Serialize XML objects * * @param xmlObject : XACML or SAML objects to be serialized * @return serialized XACML or SAML objects * @throws EntitlementException */ private String marshall(XMLObject xmlObject) throws EntitlementException { try { doBootstrap(); System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); MarshallerFactory marshallerFactory = org.opensaml.xml.Configuration.getMarshallerFactory(); Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject); Element element = marshaller.marshall(xmlObject); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS"); LSSerializer writer = impl.createLSSerializer(); LSOutput output = impl.createLSOutput(); output.setByteStream(byteArrayOutputStream); writer.write(element, output); return byteArrayOutputStream.toString(); } catch (Exception e) { log.error("Error Serializing the SAML Response"); throw new EntitlementException("Error Serializing the SAML Response", e); } } /** * Check the validity of the Signature * * @param signature : XML Signature that authenticates the assertion * @return whether the signature is valid * @throws Exception */ private boolean validateSignature(Signature signature) throws Exception { boolean isSignatureValid = false; try { SignatureValidator validator = new SignatureValidator(getPublicX509CredentialImpl()); validator.validate(signature); isSignatureValid = true; } catch (ValidationException e) { log.warn("Signature validation failed."); } catch (Exception e) { throw new Exception("Error in getting public X509Credentials to validate signature. "); } return isSignatureValid; } /** * get a org.wso2.carbon.identity.entitlement.wsxacml.X509CredentialImpl using RegistryService * * @return created X509Credential */ private X509CredentialImpl getPublicX509CredentialImpl() throws Exception { X509CredentialImpl credentialImpl; KeyStoreManager keyStoreManager; try { keyStoreManager = KeyStoreManager.getInstance(-1234); // load the default pub. cert using the configuration in carbon.xml java.security.cert.X509Certificate cert = keyStoreManager.getDefaultPrimaryCertificate(); credentialImpl = new X509CredentialImpl(cert); return credentialImpl; } catch (Exception e) { log.error("Error instantiating an org.wso2.carbon.identity.entitlement.wsxacml.X509CredentialImpl " + "object for the public cert.", e); throw new Exception("Error instantiating an org.wso2.carbon.identity.entitlement.wsxacml.X509CredentialImpl " + "object for the public cert.", e); } } /** * Encapsulates the passed in xacml response into a saml response * * @param xacmlResponse : xacml response returned from PDP * @return saml response * @throws Exception */ public String secureXACMLResponse(String xacmlResponse) throws Exception { ResponseType responseType; String responseString; doBootstrap(); try { responseType = (ResponseType) unmarshall(formatResponse(xacmlResponse)); } catch (Exception e) { log.error("Error while unmarshalling the formatted XACML response.", e); throw new EntitlementException("Error while unmarshalling the formatted XACML response.", e); } XACMLAuthzDecisionStatementTypeImplBuilder xacmlauthz = (XACMLAuthzDecisionStatementTypeImplBuilder) org.opensaml.xml.Configuration.getBuilderFactory(). getBuilder(XACMLAuthzDecisionStatementType.TYPE_NAME_XACML20); XACMLAuthzDecisionStatementType xacmlAuthzDecisionStatement = xacmlauthz .buildObject(Statement.DEFAULT_ELEMENT_NAME, XACMLAuthzDecisionStatementType.TYPE_NAME_XACML20); xacmlAuthzDecisionStatement.setResponse(responseType); AssertionBuilder assertionBuilder = (AssertionBuilder) org.opensaml.xml.Configuration.getBuilderFactory() .getBuilder(Assertion.DEFAULT_ELEMENT_NAME); DateTime currentTime = new DateTime(); Assertion assertion = assertionBuilder.buildObject(); assertion.setVersion(org.opensaml.common.SAMLVersion.VERSION_20); assertion.setIssuer(createIssuer()); assertion.setIssueInstant(currentTime); assertion.getStatements().add(xacmlAuthzDecisionStatement); ResponseBuilder builder = (ResponseBuilder) org.opensaml.xml.Configuration.getBuilderFactory() .getBuilder(Response.DEFAULT_ELEMENT_NAME); Response response = builder.buildObject(); response.getAssertions().add(assertion); response.setIssuer(createIssuer()); DateTime issueInstant = new DateTime(); response.setIssueInstant(issueInstant); response = setSignature(response, XMLSignature.ALGO_ID_SIGNATURE_RSA, createBasicCredentials()); try { responseString = marshall(response); responseString = responseString.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", ""); return responseString; } catch (EntitlementException e) { log.error("Error occurred while marshalling the SAML Response.", e); throw new Exception("Error occurred while marshalling the SAML Response.", e); } } /** * Format the sent in response as required by OpenSAML * * @param xacmlResponse : received XACML response * @return formatted response */ private String formatResponse(String xacmlResponse) throws Exception { xacmlResponse = xacmlResponse.replace("\n", ""); OMElement omElemnt; try { omElemnt = org.apache.axiom.om.util.AXIOMUtil.stringToOM(xacmlResponse); omElemnt.setNamespace(xacmlContextNS); if (omElemnt.getChildren() != null) { Iterator childIterator = omElemnt.getChildElements(); setXACMLNamespace(childIterator); } } catch (Exception e) { log.error("Error while generating the OMElement from the XACML request.", e); throw new Exception("Error while generating the OMElement from the XACML request.", e); } return omElemnt.toString(); } }