| /** |
| * 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.wss4j.stax.impl.processor.input; |
| |
| import java.security.Key; |
| import java.security.PublicKey; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.crypto.spec.SecretKeySpec; |
| import javax.xml.bind.JAXBElement; |
| import javax.xml.bind.JAXBException; |
| import javax.xml.bind.Unmarshaller; |
| import javax.xml.namespace.QName; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.stream.XMLStreamConstants; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.events.Attribute; |
| import javax.xml.stream.events.Comment; |
| import javax.xml.stream.events.Namespace; |
| import javax.xml.stream.events.ProcessingInstruction; |
| |
| import org.apache.wss4j.binding.wss10.ObjectFactory; |
| import org.apache.wss4j.binding.wss10.SecurityTokenReferenceType; |
| import org.apache.wss4j.common.ext.WSSecurityException; |
| import org.apache.wss4j.common.saml.OpenSAMLUtil; |
| import org.apache.wss4j.common.saml.SamlAssertionWrapper; |
| import org.apache.wss4j.stax.ext.WSInboundSecurityContext; |
| import org.apache.wss4j.stax.ext.WSSConstants; |
| import org.apache.wss4j.stax.ext.WSSSecurityProperties; |
| import org.apache.wss4j.stax.securityEvent.SamlTokenSecurityEvent; |
| import org.apache.wss4j.stax.securityEvent.SignedPartSecurityEvent; |
| import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants; |
| import org.apache.wss4j.stax.securityToken.SamlSecurityToken; |
| import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants; |
| import org.apache.wss4j.stax.utils.WSSUtils; |
| import org.apache.wss4j.stax.validate.SamlTokenValidator; |
| import org.apache.wss4j.stax.validate.SamlTokenValidatorImpl; |
| import org.apache.wss4j.stax.validate.TokenContext; |
| import org.apache.xml.security.binding.xmldsig.KeyInfoType; |
| import org.apache.xml.security.binding.xmldsig.KeyValueType; |
| import org.apache.xml.security.binding.xmldsig.X509DataType; |
| import org.apache.xml.security.binding.xmlenc.EncryptedKeyType; |
| import org.apache.xml.security.exceptions.XMLSecurityException; |
| import org.apache.xml.security.stax.config.JCEAlgorithmMapper; |
| import org.apache.xml.security.stax.ext.AbstractInputProcessor; |
| import org.apache.xml.security.stax.ext.AbstractInputSecurityHeaderHandler; |
| import org.apache.xml.security.stax.ext.InputProcessorChain; |
| import org.apache.xml.security.stax.ext.XMLSecurityConstants; |
| import org.apache.xml.security.stax.ext.XMLSecurityProperties; |
| import org.apache.xml.security.stax.ext.stax.XMLSecAttribute; |
| import org.apache.xml.security.stax.ext.stax.XMLSecEvent; |
| import org.apache.xml.security.stax.ext.stax.XMLSecNamespace; |
| import org.apache.xml.security.stax.ext.stax.XMLSecStartElement; |
| import org.apache.xml.security.stax.impl.XMLSecurityEventReader; |
| import org.apache.xml.security.stax.impl.securityToken.AbstractInboundSecurityToken; |
| import org.apache.xml.security.stax.impl.util.IDGenerator; |
| import org.apache.xml.security.stax.securityEvent.SecurityEvent; |
| import org.apache.xml.security.stax.securityEvent.SecurityEventListener; |
| import org.apache.xml.security.stax.securityEvent.SignedElementSecurityEvent; |
| import org.apache.xml.security.stax.securityToken.InboundSecurityToken; |
| import org.apache.xml.security.stax.securityToken.SecurityToken; |
| import org.apache.xml.security.stax.securityToken.SecurityTokenConstants.TokenUsage; |
| import org.apache.xml.security.utils.XMLUtils; |
| import org.apache.xml.security.stax.securityToken.SecurityTokenFactory; |
| import org.apache.xml.security.stax.securityToken.SecurityTokenProvider; |
| import org.opensaml.security.credential.BasicCredential; |
| import org.opensaml.security.x509.BasicX509Credential; |
| import org.opensaml.xmlsec.signature.Signature; |
| import org.opensaml.xmlsec.signature.support.SignatureException; |
| import org.opensaml.xmlsec.signature.support.SignatureValidator; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * Processor for the SAML Assertion XML Structure |
| */ |
| public class SAMLTokenInputHandler extends AbstractInputSecurityHeaderHandler { |
| |
| @Override |
| public void handle(final InputProcessorChain inputProcessorChain, final XMLSecurityProperties securityProperties, |
| Deque<XMLSecEvent> eventQueue, Integer index) throws XMLSecurityException { |
| |
| final Document samlTokenDocument = (Document) parseStructure(eventQueue, index, securityProperties); |
| |
| final WSSSecurityProperties wssSecurityProperties = (WSSSecurityProperties) securityProperties; |
| final WSInboundSecurityContext wsInboundSecurityContext = (WSInboundSecurityContext) inputProcessorChain.getSecurityContext(); |
| final Element samlElement = samlTokenDocument.getDocumentElement(); |
| final SamlAssertionWrapper samlAssertionWrapper = new SamlAssertionWrapper(samlElement); |
| |
| SamlTokenValidator samlTokenValidator = |
| wssSecurityProperties.getValidator(new QName(samlElement.getNamespaceURI(), samlElement.getLocalName())); |
| if (samlTokenValidator == null) { |
| samlTokenValidator = new SamlTokenValidatorImpl(); |
| } |
| |
| //important: check the signature before we do other processing... |
| if (samlAssertionWrapper.isSigned()) { |
| Signature signature = samlAssertionWrapper.getSignature(); |
| if (signature == null) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, |
| "empty", new Object[] {"no signature to validate"}); |
| } |
| |
| int sigKeyInfoIdx = getSignatureKeyInfoIndex(eventQueue); |
| if (sigKeyInfoIdx < 0) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken"); |
| } |
| InboundSecurityToken sigSecurityToken = parseKeyInfo(inputProcessorChain, securityProperties, eventQueue, sigKeyInfoIdx); |
| |
| if (sigSecurityToken == null) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken"); |
| } |
| |
| samlTokenValidator.validate(sigSecurityToken, wssSecurityProperties); |
| |
| BasicCredential credential = null; |
| if (sigSecurityToken.getX509Certificates() != null) { |
| credential = new BasicX509Credential(sigSecurityToken.getX509Certificates()[0]); |
| } else if (sigSecurityToken.getPublicKey() != null) { |
| credential = new BasicCredential(sigSecurityToken.getPublicKey()); |
| } else { |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity", |
| new Object[] {"cannot get certificate or key"} |
| ); |
| } |
| try { |
| SignatureValidator.validate(signature, credential); |
| } catch (SignatureException ex) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, |
| ex, "empty", new Object[] {"SAML signature validation failed"}); |
| } |
| } |
| |
| final InboundSecurityToken subjectSecurityToken; |
| |
| List<String> methods = samlAssertionWrapper.getConfirmationMethods(); |
| boolean holderOfKey = false; |
| if (methods != null) { |
| for (String method : methods) { |
| if (OpenSAMLUtil.isMethodHolderOfKey(method)) { |
| holderOfKey = true; |
| break; |
| } |
| } |
| } |
| |
| if (holderOfKey) { |
| int subjectKeyInfoIndex = getSubjectKeyInfoIndex(eventQueue); |
| if (subjectKeyInfoIndex < 0) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken"); |
| } |
| |
| subjectSecurityToken = parseKeyInfo(inputProcessorChain, securityProperties, eventQueue, subjectKeyInfoIndex); |
| if (subjectSecurityToken == null) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken"); |
| } |
| } else { |
| subjectSecurityToken = null; |
| } |
| |
| final List<XMLSecEvent> xmlSecEvents = getResponsibleXMLSecEvents(eventQueue, index); |
| final List<QName> elementPath = getElementPath(eventQueue); |
| final TokenContext tokenContext = |
| new TokenContext(wssSecurityProperties, wsInboundSecurityContext, xmlSecEvents, elementPath); |
| |
| final SamlSecurityToken samlSecurityToken = |
| samlTokenValidator.validate(samlAssertionWrapper, subjectSecurityToken, tokenContext); |
| |
| SecurityTokenProvider<InboundSecurityToken> subjectSecurityTokenProvider = |
| new SecurityTokenProvider<InboundSecurityToken>() { |
| |
| @Override |
| public InboundSecurityToken getSecurityToken() throws XMLSecurityException { |
| return (InboundSecurityToken)samlSecurityToken; |
| } |
| |
| @Override |
| public String getId() { |
| return samlAssertionWrapper.getId(); |
| } |
| }; |
| |
| wsInboundSecurityContext.registerSecurityTokenProvider(samlAssertionWrapper.getId(), subjectSecurityTokenProvider); |
| |
| //fire a tokenSecurityEvent |
| SamlTokenSecurityEvent samlTokenSecurityEvent = new SamlTokenSecurityEvent(); |
| samlTokenSecurityEvent.setSecurityToken((SamlSecurityToken)subjectSecurityTokenProvider.getSecurityToken()); |
| samlTokenSecurityEvent.setCorrelationID(samlAssertionWrapper.getId()); |
| wsInboundSecurityContext.registerSecurityEvent(samlTokenSecurityEvent); |
| |
| if (wssSecurityProperties.isValidateSamlSubjectConfirmation()) { |
| boolean soap12 = false; |
| if (elementPath.get(0) != null && WSSConstants.NS_SOAP12.equals(elementPath.get(0).getNamespaceURI())) { |
| soap12 = true; |
| } |
| SAMLTokenVerifierInputProcessor samlTokenVerifierInputProcessor = |
| new SAMLTokenVerifierInputProcessor( |
| securityProperties, samlAssertionWrapper, subjectSecurityTokenProvider, subjectSecurityToken, |
| soap12); |
| wsInboundSecurityContext.addSecurityEventListener(samlTokenVerifierInputProcessor); |
| inputProcessorChain.addProcessor(samlTokenVerifierInputProcessor); |
| } |
| } |
| |
| private int getSubjectKeyInfoIndex(Deque<XMLSecEvent> eventQueue) { |
| int idx = -1; |
| Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator(); |
| while (xmlSecEventIterator.hasNext()) { |
| XMLSecEvent xmlSecEvent = xmlSecEventIterator.next(); |
| idx++; |
| switch (xmlSecEvent.getEventType()) { |
| case XMLStreamConstants.START_ELEMENT: |
| QName elementName = xmlSecEvent.asStartElement().getName(); |
| if (WSSConstants.TAG_dsig_KeyInfo.equals(elementName)) { |
| List<QName> elementPath = xmlSecEvent.asStartElement().getElementPath(); |
| if (elementPath.size() >= 4) { |
| int lastIndex = elementPath.size() - 2; |
| if ("SubjectConfirmationData".equals(elementPath.get(lastIndex).getLocalPart()) |
| && "SubjectConfirmation".equals(elementPath.get(lastIndex - 1).getLocalPart()) |
| && "Subject".equals(elementPath.get(lastIndex - 2).getLocalPart())) { |
| return idx; |
| } else if ("SubjectConfirmation".equals(elementPath.get(lastIndex).getLocalPart()) |
| && "Subject".equals(elementPath.get(lastIndex - 1).getLocalPart())) { |
| return idx; |
| } |
| } |
| } |
| } |
| } |
| return idx; |
| } |
| |
| private int getSignatureKeyInfoIndex(Deque<XMLSecEvent> eventQueue) { |
| int idx = -1; |
| Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator(); |
| while (xmlSecEventIterator.hasNext()) { |
| XMLSecEvent xmlSecEvent = xmlSecEventIterator.next(); |
| idx++; |
| switch (xmlSecEvent.getEventType()) { |
| case XMLStreamConstants.START_ELEMENT: |
| QName elementName = xmlSecEvent.asStartElement().getName(); |
| if (WSSConstants.TAG_dsig_KeyInfo.equals(elementName)) { |
| List<QName> elementPath = xmlSecEvent.asStartElement().getElementPath(); |
| if (elementPath.size() >= 4) { |
| int lastIndex = elementPath.size() - 2; |
| if ("Signature".equals(elementPath.get(lastIndex).getLocalPart()) |
| && "Assertion".equals(elementPath.get(lastIndex - 1).getLocalPart())) { |
| return idx; |
| } |
| } |
| } |
| } |
| } |
| return idx; |
| } |
| |
| private InboundSecurityToken parseKeyInfo(InputProcessorChain inputProcessorChain, XMLSecurityProperties securityProperties, |
| Deque<XMLSecEvent> eventQueue, int index) throws XMLSecurityException { |
| XMLSecEvent xmlSecEvent = null; |
| int idx = 0; |
| Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator(); |
| while (xmlSecEventIterator.hasNext() && idx <= index) { |
| xmlSecEvent = xmlSecEventIterator.next(); |
| idx++; |
| } |
| //forward to next start element |
| while (xmlSecEventIterator.hasNext()) { |
| xmlSecEvent = xmlSecEventIterator.next(); |
| if (xmlSecEvent.isStartElement()) { |
| break; |
| } |
| idx++; |
| } |
| if (xmlSecEvent == null || !xmlSecEvent.isStartElement()) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken"); |
| } |
| |
| final XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement(); |
| final QName elementName = xmlSecStartElement.getName(); |
| if (WSSConstants.TAG_WST_BINARY_SECRET.equals(elementName) |
| || WSSConstants.TAG_WST0512_BINARY_SECRET.equals(elementName)) { |
| |
| final StringBuilder stringBuilder = new StringBuilder(); |
| loop: |
| while (xmlSecEventIterator.hasNext()) { |
| xmlSecEvent = xmlSecEventIterator.next(); |
| switch (xmlSecEvent.getEventType()) { |
| case XMLStreamConstants.END_ELEMENT: |
| if (xmlSecEvent.asEndElement().getName().equals(elementName)) { |
| break loop; |
| } |
| break; |
| case XMLStreamConstants.CHARACTERS: |
| stringBuilder.append(xmlSecEvent.asCharacters().getText()); |
| break; |
| } |
| } |
| |
| return new AbstractInboundSecurityToken( |
| inputProcessorChain.getSecurityContext(), IDGenerator.generateID(null), |
| WSSecurityTokenConstants.KeyIdentifier_NoKeyInfo, true) { |
| @Override |
| public WSSecurityTokenConstants.TokenType getTokenType() { |
| return WSSecurityTokenConstants.DefaultToken; |
| } |
| |
| @Override |
| public boolean isAsymmetric() throws XMLSecurityException { |
| return false; |
| } |
| |
| @Override |
| protected Key getKey(String algorithmURI, XMLSecurityConstants.AlgorithmUsage algorithmUsage, String correlationID) |
| throws XMLSecurityException { |
| Key key = super.getKey(algorithmURI, algorithmUsage, correlationID); |
| if (key == null) { |
| String algoFamily = JCEAlgorithmMapper.getJCEKeyAlgorithmFromURI(algorithmURI); |
| key = new SecretKeySpec(XMLUtils.decode(stringBuilder.toString()), algoFamily); |
| setSecretKey(algorithmURI, key); |
| } |
| return key; |
| } |
| }; |
| } else { |
| Object object = null; |
| try { |
| Unmarshaller unmarshaller = WSSConstants.getJaxbUnmarshaller(securityProperties.isDisableSchemaValidation()); |
| object = unmarshaller.unmarshal(new XMLSecurityEventReader(eventQueue, idx)); |
| } catch (JAXBException e) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, e); |
| } |
| |
| if (object instanceof JAXBElement) { |
| object = ((JAXBElement<?>) object).getValue(); |
| } |
| |
| KeyInfoType keyInfoType = null; |
| if (object instanceof X509DataType) { |
| JAXBElement<X509DataType> x509DataTypeJAXBElement = |
| new org.apache.xml.security.binding.xmldsig.ObjectFactory().createX509Data((X509DataType) object); |
| keyInfoType = new KeyInfoType(); |
| SecurityTokenReferenceType securityTokenReferenceType = new SecurityTokenReferenceType(); |
| securityTokenReferenceType.getAny().add(x509DataTypeJAXBElement); |
| JAXBElement<SecurityTokenReferenceType> securityTokenReferenceTypeJAXBElement = |
| new ObjectFactory().createSecurityTokenReference(securityTokenReferenceType); |
| keyInfoType.getContent().add(securityTokenReferenceTypeJAXBElement); |
| } else if (object instanceof EncryptedKeyType) { |
| EncryptedKeyType encryptedKeyType = (EncryptedKeyType) object; |
| |
| WSSEncryptedKeyInputHandler encryptedKeyInputHandler = new WSSEncryptedKeyInputHandler(); |
| encryptedKeyInputHandler.handle(inputProcessorChain, encryptedKeyType, xmlSecStartElement, securityProperties); |
| |
| SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = |
| inputProcessorChain.getSecurityContext().getSecurityTokenProvider(encryptedKeyType.getId()); |
| if (securityTokenProvider != null) { |
| return securityTokenProvider.getSecurityToken(); |
| } |
| |
| } else if (object instanceof SecurityTokenReferenceType) { |
| JAXBElement<SecurityTokenReferenceType> securityTokenReferenceTypeJAXBElement = |
| new ObjectFactory().createSecurityTokenReference((SecurityTokenReferenceType) object); |
| keyInfoType = new KeyInfoType(); |
| keyInfoType.getContent().add(securityTokenReferenceTypeJAXBElement); |
| } else if (object instanceof KeyValueType) { |
| JAXBElement<KeyValueType> keyValueTypeJAXBElement = |
| new org.apache.xml.security.binding.xmldsig.ObjectFactory().createKeyValue((KeyValueType) object); |
| keyInfoType = new KeyInfoType(); |
| keyInfoType.getContent().add(keyValueTypeJAXBElement); |
| } else { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, "unsupportedKeyInfo"); |
| } |
| |
| return SecurityTokenFactory.getInstance().getSecurityToken( |
| keyInfoType, WSSecurityTokenConstants.KeyUsage_Signature_Verification, |
| securityProperties, inputProcessorChain.getSecurityContext()); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| protected <T> T parseStructure(Deque<XMLSecEvent> eventDeque, int index, XMLSecurityProperties securityProperties) |
| throws XMLSecurityException { |
| Document document = null; |
| try { |
| document = ((WSSSecurityProperties) securityProperties).getDocumentCreator().newDocument(); |
| } catch (ParserConfigurationException e) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, e); |
| } |
| |
| Iterator<XMLSecEvent> xmlSecEventIterator = eventDeque.descendingIterator(); |
| int curIdx = 0; |
| while (curIdx++ < index) { |
| xmlSecEventIterator.next(); |
| } |
| |
| Node currentNode = document; |
| while (xmlSecEventIterator.hasNext()) { |
| XMLSecEvent next = xmlSecEventIterator.next(); |
| currentNode = parseXMLEvent(next, currentNode, document); |
| } |
| return (T) document; |
| } |
| |
| //todo custom SAML unmarshaller directly to XMLObject? |
| public Node parseXMLEvent(XMLSecEvent xmlSecEvent, Node currentNode, Document document) throws WSSecurityException { |
| switch (xmlSecEvent.getEventType()) { |
| case XMLStreamConstants.START_ELEMENT: |
| XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement(); |
| Element element = document.createElementNS(xmlSecStartElement.getName().getNamespaceURI(), |
| xmlSecStartElement.getName().getLocalPart()); |
| if (xmlSecStartElement.getName().getPrefix() != null && !xmlSecStartElement.getName().getPrefix().isEmpty()) { |
| element.setPrefix(xmlSecStartElement.getName().getPrefix()); |
| } |
| currentNode = currentNode.appendChild(element); |
| @SuppressWarnings("unchecked") |
| Iterator<Namespace> namespaceIterator = xmlSecStartElement.getNamespaces(); |
| while (namespaceIterator.hasNext()) { |
| XMLSecNamespace next = (XMLSecNamespace)namespaceIterator.next(); |
| parseXMLEvent(next, currentNode, document); |
| } |
| @SuppressWarnings("unchecked") |
| Iterator<Attribute> attributesIterator = xmlSecStartElement.getAttributes(); |
| while (attributesIterator.hasNext()) { |
| XMLSecAttribute next = (XMLSecAttribute)attributesIterator.next(); |
| parseXMLEvent(next, currentNode, document); |
| } |
| //add namespace which is not declared on current element but must be on a parent element: |
| String elementNs = document.lookupNamespaceURI(xmlSecStartElement.getName().getPrefix()); |
| if (elementNs == null) { |
| parseXMLEvent(xmlSecStartElement.getElementNamespace(), currentNode, document); |
| } |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| if (currentNode.getParentNode() != null) { |
| currentNode = currentNode.getParentNode(); |
| } |
| break; |
| case XMLStreamConstants.PROCESSING_INSTRUCTION: |
| Node piNode = document.createProcessingInstruction( |
| ((ProcessingInstruction) xmlSecEvent).getTarget(), |
| ((ProcessingInstruction) xmlSecEvent).getTarget() |
| ); |
| currentNode.appendChild(piNode); |
| break; |
| case XMLStreamConstants.CHARACTERS: |
| Node characterNode = document.createTextNode(xmlSecEvent.asCharacters().getData()); |
| currentNode.appendChild(characterNode); |
| break; |
| case XMLStreamConstants.COMMENT: |
| Node commentNode = document.createComment(((Comment) xmlSecEvent).getText()); |
| currentNode.appendChild(commentNode); |
| break; |
| case XMLStreamConstants.START_DOCUMENT: |
| break; |
| case XMLStreamConstants.END_DOCUMENT: |
| return currentNode; |
| case XMLStreamConstants.ATTRIBUTE: |
| final XMLSecAttribute xmlSecAttribute = (XMLSecAttribute) xmlSecEvent; |
| Attr attributeNode = document.createAttributeNS( |
| xmlSecAttribute.getName().getNamespaceURI(), |
| xmlSecAttribute.getName().getLocalPart()); |
| attributeNode.setPrefix(xmlSecAttribute.getName().getPrefix()); |
| attributeNode.setValue(xmlSecAttribute.getValue()); |
| ((Element) currentNode).setAttributeNodeNS(attributeNode); |
| |
| //add namespace which is not declared on current element but must be on a parent element: |
| String attrNs = document.lookupNamespaceURI(xmlSecAttribute.getName().getPrefix()); |
| if (attrNs == null) { |
| parseXMLEvent(xmlSecAttribute.getAttributeNamespace(), currentNode, document); |
| } |
| break; |
| case XMLStreamConstants.DTD: |
| //todo?: |
| /* |
| Node dtdNode = document.getDoctype().getEntities() |
| ((DTD)xmlSecEvent).getDocumentTypeDeclaration(): |
| ((DTD)xmlSecEvent).getEntities() |
| */ |
| break; |
| case XMLStreamConstants.NAMESPACE: |
| Namespace namespace = (Namespace) xmlSecEvent; |
| Attr namespaceNode; |
| String prefix = namespace.getPrefix(); |
| if (prefix == null || prefix.isEmpty()) { |
| namespaceNode = document.createAttributeNS(WSSConstants.NS_XML, "xmlns"); |
| } else { |
| namespaceNode = document.createAttributeNS(WSSConstants.NS_XML, "xmlns:" + prefix); |
| } |
| namespaceNode.setValue(namespace.getNamespaceURI()); |
| ((Element) currentNode).setAttributeNodeNS(namespaceNode); |
| break; |
| default: |
| throw new WSSecurityException( |
| WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, |
| "empty", |
| new Object[] {"Illegal XMLEvent received: " + xmlSecEvent.getEventType()}); |
| } |
| return currentNode; |
| } |
| |
| /** |
| * Processor to check the holder-of-key or sender-vouches requirements against the received assertion |
| * which can not be done until the whole soap-header is processed and we know that the whole soap-body |
| * is signed. |
| */ |
| static class SAMLTokenVerifierInputProcessor extends AbstractInputProcessor implements SecurityEventListener { |
| |
| private SamlAssertionWrapper samlAssertionWrapper; |
| private SecurityTokenProvider<InboundSecurityToken> securityTokenProvider; |
| private InboundSecurityToken subjectSecurityToken; |
| private List<SignedElementSecurityEvent> samlTokenSignedElementSecurityEvents = new ArrayList<>(); |
| private SignedPartSecurityEvent bodySignedPartSecurityEvent; |
| |
| private final boolean soap12; |
| private final List<QName> saml1TokenPath; |
| private final List<QName> saml2TokenPath; |
| |
| SAMLTokenVerifierInputProcessor(XMLSecurityProperties securityProperties, |
| SamlAssertionWrapper samlAssertionWrapper, |
| SecurityTokenProvider<InboundSecurityToken> securityTokenProvider, |
| InboundSecurityToken subjectSecurityToken, |
| boolean soap12) { |
| super(securityProperties); |
| this.setPhase(XMLSecurityConstants.Phase.POSTPROCESSING); |
| this.addAfterProcessor(OperationInputProcessor.class.getName()); |
| this.samlAssertionWrapper = samlAssertionWrapper; |
| this.securityTokenProvider = securityTokenProvider; |
| this.subjectSecurityToken = subjectSecurityToken; |
| |
| this.soap12 = soap12; |
| if (soap12) { |
| saml1TokenPath = new ArrayList<>(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH); |
| saml1TokenPath.add(WSSConstants.TAG_SAML_ASSERTION); |
| saml2TokenPath = new ArrayList<>(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH); |
| saml2TokenPath.add(WSSConstants.TAG_SAML2_ASSERTION); |
| } else { |
| saml1TokenPath = new ArrayList<>(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH); |
| saml1TokenPath.add(WSSConstants.TAG_SAML_ASSERTION); |
| saml2TokenPath = new ArrayList<>(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH); |
| saml2TokenPath.add(WSSConstants.TAG_SAML2_ASSERTION); |
| } |
| } |
| |
| @Override |
| public void registerSecurityEvent(SecurityEvent securityEvent) throws XMLSecurityException { |
| if (WSSecurityEventConstants.SIGNED_PART.equals(securityEvent.getSecurityEventType())) { |
| SignedPartSecurityEvent signedPartSecurityEvent = (SignedPartSecurityEvent) securityEvent; |
| |
| List<QName> elementPath = signedPartSecurityEvent.getElementPath(); |
| if (soap12 && WSSUtils.pathMatches(WSSConstants.SOAP_12_BODY_PATH, elementPath) |
| || !soap12 && WSSUtils.pathMatches(WSSConstants.SOAP_11_BODY_PATH, elementPath)) { |
| bodySignedPartSecurityEvent = signedPartSecurityEvent; |
| } |
| } else if (WSSecurityEventConstants.SignedElement.equals(securityEvent.getSecurityEventType())) { |
| SignedElementSecurityEvent signedPartSecurityEvent = (SignedElementSecurityEvent) securityEvent; |
| |
| List<QName> elementPath = signedPartSecurityEvent.getElementPath(); |
| if (WSSUtils.pathMatches(saml2TokenPath, elementPath) |
| || WSSUtils.pathMatches(saml1TokenPath, elementPath)) { |
| samlTokenSignedElementSecurityEvents.add(signedPartSecurityEvent); |
| } |
| } |
| } |
| |
| @Override |
| public XMLSecEvent processHeaderEvent(InputProcessorChain inputProcessorChain) |
| throws XMLStreamException, XMLSecurityException { |
| return inputProcessorChain.processHeaderEvent(); |
| } |
| |
| @Override |
| public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain) |
| throws XMLStreamException, XMLSecurityException { |
| |
| XMLSecEvent xmlSecEvent = inputProcessorChain.processEvent(); |
| if (xmlSecEvent.getEventType() == XMLStreamConstants.START_ELEMENT) { |
| XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement(); |
| List<QName> elementPath = xmlSecStartElement.getElementPath(); |
| if (elementPath.size() == 3 && WSSUtils.isInSOAPBody(elementPath)) { |
| inputProcessorChain.removeProcessor(this); |
| checkPossessionOfKey(inputProcessorChain, samlAssertionWrapper, subjectSecurityToken); |
| } |
| } |
| return xmlSecEvent; |
| } |
| |
| private void checkPossessionOfKey( |
| InputProcessorChain inputProcessorChain, SamlAssertionWrapper samlAssertionWrapper, |
| InboundSecurityToken subjectSecurityToken) throws WSSecurityException { |
| |
| boolean methodNotSatisfied = false; |
| try { |
| SecurityToken httpsSecurityToken = getHttpsSecurityToken(inputProcessorChain); |
| |
| List<SecurityTokenProvider<? extends InboundSecurityToken>> securityTokenProviders = |
| inputProcessorChain.getSecurityContext().getRegisteredSecurityTokenProviders(); |
| |
| List<String> confirmationMethods = samlAssertionWrapper.getConfirmationMethods(); |
| for (int i = 0; i < confirmationMethods.size(); i++) { |
| String confirmationMethod = confirmationMethods.get(i); |
| if (OpenSAMLUtil.isMethodHolderOfKey(confirmationMethod)) { |
| |
| X509Certificate[] subjectCertificates = subjectSecurityToken.getX509Certificates(); |
| PublicKey subjectPublicKey = subjectSecurityToken.getPublicKey(); |
| Key subjectSecretKey = null; |
| Map<String, Key> subjectKeyMap = subjectSecurityToken.getSecretKey(); |
| if (!subjectKeyMap.isEmpty()) { |
| subjectSecretKey = subjectKeyMap.values().toArray(new Key[subjectKeyMap.size()])[0]; |
| } |
| |
| /** |
| * Check the holder-of-key requirements against the received assertion. The subject |
| * credential of the SAML Assertion must have been used to sign some portion of |
| * the message, thus showing proof-of-possession of the private/secret key. Alternatively, |
| * the subject credential of the SAML Assertion must match a client certificate credential |
| * when 2-way TLS is used. |
| */ |
| |
| //compare https token first: |
| if (httpsSecurityToken != null |
| && httpsSecurityToken.getX509Certificates() != null |
| && httpsSecurityToken.getX509Certificates().length > 0) { |
| |
| X509Certificate httpsCertificate = httpsSecurityToken.getX509Certificates()[0]; |
| |
| //compare certificates: |
| if (subjectCertificates != null && subjectCertificates.length > 0 |
| && httpsCertificate.equals(subjectCertificates[0])) { |
| return; |
| //compare public keys: |
| } else if (httpsCertificate.getPublicKey().equals(subjectPublicKey)) { |
| return; |
| } |
| } |
| |
| // Now try message signatures |
| for (int j = 0; j < securityTokenProviders.size(); j++) { |
| SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = securityTokenProviders.get(j); |
| InboundSecurityToken securityToken = securityTokenProvider.getSecurityToken(); |
| // Don't compare to the original SAML Token credentials... |
| if (securityToken == httpsSecurityToken || securityToken == subjectSecurityToken |
| || !containsSignature(securityToken.getTokenUsages())) { |
| continue; |
| } |
| X509Certificate[] x509Certificates = securityToken.getX509Certificates(); |
| PublicKey publicKey = securityToken.getPublicKey(); |
| Map<String, Key> keyMap = securityToken.getSecretKey(); |
| if (x509Certificates != null && x509Certificates.length > 0 |
| && subjectCertificates != null && subjectCertificates.length > 0 |
| && subjectCertificates[0].equals(x509Certificates[0])) { |
| return; |
| } |
| if (publicKey != null && publicKey.equals(subjectPublicKey)) { |
| return; |
| } |
| Iterator<Map.Entry<String, Key>> iterator = keyMap.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<String, Key> next = iterator.next(); |
| if (next.getValue().equals(subjectSecretKey)) { |
| return; |
| } |
| } |
| } |
| methodNotSatisfied = true; |
| } else if (OpenSAMLUtil.isMethodSenderVouches(confirmationMethod)) { |
| /** |
| * Check the sender-vouches requirements against the received assertion. The SAML |
| * Assertion and the SOAP Body must be signed by the same signature. |
| */ |
| |
| // |
| // If we have a 2-way TLS connection, then we don't have to check that the |
| // assertion + SOAP body are signed |
| if (httpsSecurityToken != null |
| && httpsSecurityToken.getX509Certificates() != null |
| && httpsSecurityToken.getX509Certificates().length > 0) { |
| return; |
| } |
| |
| SignedElementSecurityEvent samlTokenSignedElementSecurityEvent = null; |
| for (int j = 0; j < samlTokenSignedElementSecurityEvents.size(); j++) { |
| SignedElementSecurityEvent signedElementSecurityEvent = samlTokenSignedElementSecurityEvents.get(j); |
| if (securityTokenProvider.getSecurityToken().getXMLSecEvent() |
| == signedElementSecurityEvent.getXmlSecEvent()) { |
| |
| samlTokenSignedElementSecurityEvent = signedElementSecurityEvent; |
| } |
| } |
| if (bodySignedPartSecurityEvent != null |
| && samlTokenSignedElementSecurityEvent != null |
| && bodySignedPartSecurityEvent.getSecurityToken() |
| == samlTokenSignedElementSecurityEvent.getSecurityToken()) { |
| return; |
| } |
| methodNotSatisfied = true; |
| } |
| } |
| } catch (XMLSecurityException e) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e); |
| } |
| if (methodNotSatisfied) { |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION, |
| "empty", |
| new Object[] {"SAML proof-of-possession of the private/secret key failed"}); |
| } |
| } |
| |
| private SecurityToken getHttpsSecurityToken(InputProcessorChain inputProcessorChain) throws XMLSecurityException { |
| List<SecurityTokenProvider<? extends InboundSecurityToken>> securityTokenProviders = |
| inputProcessorChain.getSecurityContext().getRegisteredSecurityTokenProviders(); |
| for (int i = 0; i < securityTokenProviders.size(); i++) { |
| SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = securityTokenProviders.get(i); |
| SecurityToken securityToken = securityTokenProvider.getSecurityToken(); |
| if (WSSecurityTokenConstants.HTTPS_TOKEN.equals(securityToken.getTokenType())) { |
| return securityToken; |
| } |
| } |
| return null; |
| } |
| |
| private boolean containsSignature(List<TokenUsage> tokenUses) { |
| return tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_MAIN_SIGNATURE) |
| || tokenUses.contains(WSSecurityTokenConstants.TokenUsage_Signature) |
| || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS) |
| || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_ENDORSING_SUPPORTING_TOKENS) |
| || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_SIGNED_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS) |
| || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_SIGNED_ENDORSING_SUPPORTING_TOKENS); |
| } |
| } |
| } |