View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.wss4j.stax.impl.processor.input;
20  
21  import java.security.Key;
22  import java.security.PublicKey;
23  import java.security.cert.X509Certificate;
24  import java.util.ArrayList;
25  import java.util.Deque;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.crypto.spec.SecretKeySpec;
31  import jakarta.xml.bind.JAXBElement;
32  import jakarta.xml.bind.JAXBException;
33  import jakarta.xml.bind.Unmarshaller;
34  import javax.xml.namespace.QName;
35  import javax.xml.parsers.ParserConfigurationException;
36  import javax.xml.stream.XMLStreamConstants;
37  import javax.xml.stream.XMLStreamException;
38  import javax.xml.stream.events.Attribute;
39  import javax.xml.stream.events.Comment;
40  import javax.xml.stream.events.Namespace;
41  import javax.xml.stream.events.ProcessingInstruction;
42  
43  import org.apache.wss4j.binding.wss10.ObjectFactory;
44  import org.apache.wss4j.binding.wss10.SecurityTokenReferenceType;
45  import org.apache.wss4j.common.ext.WSSecurityException;
46  import org.apache.wss4j.common.saml.OpenSAMLUtil;
47  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
48  import org.apache.wss4j.stax.ext.WSInboundSecurityContext;
49  import org.apache.wss4j.stax.ext.WSSConstants;
50  import org.apache.wss4j.stax.ext.WSSSecurityProperties;
51  import org.apache.wss4j.stax.securityEvent.SamlTokenSecurityEvent;
52  import org.apache.wss4j.stax.securityEvent.SignedPartSecurityEvent;
53  import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
54  import org.apache.wss4j.stax.securityToken.SamlSecurityToken;
55  import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
56  import org.apache.wss4j.stax.utils.WSSUtils;
57  import org.apache.wss4j.stax.validate.SamlTokenValidator;
58  import org.apache.wss4j.stax.validate.SamlTokenValidatorImpl;
59  import org.apache.wss4j.stax.validate.TokenContext;
60  import org.apache.xml.security.binding.xmldsig.KeyInfoType;
61  import org.apache.xml.security.binding.xmldsig.KeyValueType;
62  import org.apache.xml.security.binding.xmldsig.X509DataType;
63  import org.apache.xml.security.binding.xmlenc.EncryptedKeyType;
64  import org.apache.xml.security.exceptions.XMLSecurityException;
65  import org.apache.xml.security.stax.config.JCEAlgorithmMapper;
66  import org.apache.xml.security.stax.ext.AbstractInputProcessor;
67  import org.apache.xml.security.stax.ext.AbstractInputSecurityHeaderHandler;
68  import org.apache.xml.security.stax.ext.InputProcessorChain;
69  import org.apache.xml.security.stax.ext.XMLSecurityConstants;
70  import org.apache.xml.security.stax.ext.XMLSecurityProperties;
71  import org.apache.xml.security.stax.ext.stax.XMLSecAttribute;
72  import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
73  import org.apache.xml.security.stax.ext.stax.XMLSecNamespace;
74  import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
75  import org.apache.xml.security.stax.impl.XMLSecurityEventReader;
76  import org.apache.xml.security.stax.impl.securityToken.AbstractInboundSecurityToken;
77  import org.apache.xml.security.stax.impl.util.IDGenerator;
78  import org.apache.xml.security.stax.securityEvent.SecurityEvent;
79  import org.apache.xml.security.stax.securityEvent.SecurityEventListener;
80  import org.apache.xml.security.stax.securityEvent.SignedElementSecurityEvent;
81  import org.apache.xml.security.stax.securityToken.InboundSecurityToken;
82  import org.apache.xml.security.stax.securityToken.SecurityToken;
83  import org.apache.xml.security.stax.securityToken.SecurityTokenConstants.TokenUsage;
84  import org.apache.xml.security.utils.XMLUtils;
85  import org.apache.xml.security.stax.securityToken.SecurityTokenFactory;
86  import org.apache.xml.security.stax.securityToken.SecurityTokenProvider;
87  import org.opensaml.security.credential.BasicCredential;
88  import org.opensaml.security.x509.BasicX509Credential;
89  import org.opensaml.xmlsec.signature.Signature;
90  import org.opensaml.xmlsec.signature.support.SignatureException;
91  import org.opensaml.xmlsec.signature.support.SignatureValidator;
92  import org.w3c.dom.Attr;
93  import org.w3c.dom.Document;
94  import org.w3c.dom.Element;
95  import org.w3c.dom.Node;
96  
97  /**
98   * Processor for the SAML Assertion XML Structure
99   */
100 public class SAMLTokenInputHandler extends AbstractInputSecurityHeaderHandler {
101 
102     @Override
103     public void handle(final InputProcessorChain inputProcessorChain, final XMLSecurityProperties securityProperties,
104                        Deque<XMLSecEvent> eventQueue, Integer index) throws XMLSecurityException {
105 
106         final Document samlTokenDocument = (Document) parseStructure(eventQueue, index, securityProperties);
107 
108         final WSSSecurityProperties wssSecurityProperties = (WSSSecurityProperties) securityProperties;
109         final WSInboundSecurityContext wsInboundSecurityContext = (WSInboundSecurityContext) inputProcessorChain.getSecurityContext();
110         final Element samlElement = samlTokenDocument.getDocumentElement();
111         final SamlAssertionWrapper samlAssertionWrapper = new SamlAssertionWrapper(samlElement);
112 
113         SamlTokenValidator samlTokenValidator =
114             wssSecurityProperties.getValidator(new QName(samlElement.getNamespaceURI(), samlElement.getLocalName()));
115         if (samlTokenValidator == null) {
116             samlTokenValidator = new SamlTokenValidatorImpl();
117         }
118 
119         //important: check the signature before we do other processing...
120         if (samlAssertionWrapper.isSigned()) {
121             Signature signature = samlAssertionWrapper.getSignature();
122             if (signature == null) {
123                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN,
124                         "empty", new Object[] {"no signature to validate"});
125             }
126 
127             int sigKeyInfoIdx = getSignatureKeyInfoIndex(eventQueue);
128             if (sigKeyInfoIdx < 0) {
129                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken");
130             }
131             InboundSecurityToken sigSecurityToken = parseKeyInfo(inputProcessorChain, securityProperties, eventQueue, sigKeyInfoIdx);
132 
133             if (sigSecurityToken == null) {
134                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken");
135             }
136 
137             samlTokenValidator.validate(sigSecurityToken, wssSecurityProperties);
138 
139             BasicCredential credential = null;
140             if (sigSecurityToken.getX509Certificates() != null) {
141                 credential = new BasicX509Credential(sigSecurityToken.getX509Certificates()[0]);
142             } else if (sigSecurityToken.getPublicKey() != null) {
143                 credential = new BasicCredential(sigSecurityToken.getPublicKey());
144             } else {
145                 throw new WSSecurityException(
146                         WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity",
147                         new Object[] {"cannot get certificate or key"}
148                 );
149             }
150             try {
151                 SignatureValidator.validate(signature, credential);
152             } catch (SignatureException ex) {
153                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
154                         ex, "empty", new Object[] {"SAML signature validation failed"});
155             }
156         }
157 
158         final InboundSecurityToken subjectSecurityToken;
159 
160         List<String> methods = samlAssertionWrapper.getConfirmationMethods();
161         boolean holderOfKey = false;
162         if (methods != null) {
163             for (String method : methods) {
164                 if (OpenSAMLUtil.isMethodHolderOfKey(method)) {
165                     holderOfKey = true;
166                     break;
167                 }
168             }
169         }
170 
171         if (holderOfKey) {
172             int subjectKeyInfoIndex = getSubjectKeyInfoIndex(eventQueue);
173             if (subjectKeyInfoIndex < 0) {
174                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken");
175             }
176 
177             subjectSecurityToken = parseKeyInfo(inputProcessorChain, securityProperties, eventQueue, subjectKeyInfoIndex);
178             if (subjectSecurityToken == null) {
179                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken");
180             }
181         } else {
182             subjectSecurityToken = null;
183         }
184 
185         final List<XMLSecEvent> xmlSecEvents = getResponsibleXMLSecEvents(eventQueue, index);
186         final List<QName> elementPath = getElementPath(eventQueue);
187         final TokenContext tokenContext =
188             new TokenContext(wssSecurityProperties, wsInboundSecurityContext, xmlSecEvents, elementPath);
189 
190         final SamlSecurityToken samlSecurityToken =
191                 samlTokenValidator.validate(samlAssertionWrapper, subjectSecurityToken, tokenContext);
192 
193         SecurityTokenProvider<InboundSecurityToken> subjectSecurityTokenProvider =
194                 new SecurityTokenProvider<InboundSecurityToken>() {
195 
196             @Override
197             public InboundSecurityToken getSecurityToken() throws XMLSecurityException {
198                 return (InboundSecurityToken)samlSecurityToken;
199             }
200 
201             @Override
202             public String getId() {
203                 return samlAssertionWrapper.getId();
204             }
205         };
206 
207         wsInboundSecurityContext.registerSecurityTokenProvider(samlAssertionWrapper.getId(), subjectSecurityTokenProvider);
208 
209         //fire a tokenSecurityEvent
210         SamlTokenSecurityEvent samlTokenSecurityEvent = new SamlTokenSecurityEvent();
211         samlTokenSecurityEvent.setSecurityToken((SamlSecurityToken)subjectSecurityTokenProvider.getSecurityToken());
212         samlTokenSecurityEvent.setCorrelationID(samlAssertionWrapper.getId());
213         wsInboundSecurityContext.registerSecurityEvent(samlTokenSecurityEvent);
214 
215         if (wssSecurityProperties.isValidateSamlSubjectConfirmation()) {
216             boolean soap12 = false;
217             if (elementPath.get(0) != null && WSSConstants.NS_SOAP12.equals(elementPath.get(0).getNamespaceURI())) {
218                 soap12 = true;
219             }
220             SAMLTokenVerifierInputProcessor samlTokenVerifierInputProcessor =
221                     new SAMLTokenVerifierInputProcessor(
222                             securityProperties, samlAssertionWrapper, subjectSecurityTokenProvider, subjectSecurityToken,
223                             soap12);
224             wsInboundSecurityContext.addSecurityEventListener(samlTokenVerifierInputProcessor);
225             inputProcessorChain.addProcessor(samlTokenVerifierInputProcessor);
226         }
227     }
228 
229     private int getSubjectKeyInfoIndex(Deque<XMLSecEvent> eventQueue) {
230         int idx = -1;
231         Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator();
232         while (xmlSecEventIterator.hasNext()) {
233             XMLSecEvent xmlSecEvent = xmlSecEventIterator.next();
234             idx++;
235             if (XMLStreamConstants.START_ELEMENT == xmlSecEvent.getEventType()) {
236                 QName elementName = xmlSecEvent.asStartElement().getName();
237                 if (WSSConstants.TAG_dsig_KeyInfo.equals(elementName)) {
238                     List<QName> elementPath = xmlSecEvent.asStartElement().getElementPath();
239                     if (elementPath.size() >= 4) {
240                         int lastIndex = elementPath.size() - 2;
241                         if ("SubjectConfirmationData".equals(elementPath.get(lastIndex).getLocalPart())
242                                 && "SubjectConfirmation".equals(elementPath.get(lastIndex - 1).getLocalPart())
243                                 && "Subject".equals(elementPath.get(lastIndex - 2).getLocalPart())) {
244                             return idx;
245                         } else if ("SubjectConfirmation".equals(elementPath.get(lastIndex).getLocalPart())
246                                 && "Subject".equals(elementPath.get(lastIndex - 1).getLocalPart())) {
247                             return idx;
248                         }
249                     }
250                 }
251             }
252         }
253         return idx;
254     }
255 
256     private int getSignatureKeyInfoIndex(Deque<XMLSecEvent> eventQueue) {
257         int idx = -1;
258         Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator();
259         while (xmlSecEventIterator.hasNext()) {
260             XMLSecEvent xmlSecEvent = xmlSecEventIterator.next();
261             idx++;
262             if (XMLStreamConstants.START_ELEMENT == xmlSecEvent.getEventType()) {
263                 QName elementName = xmlSecEvent.asStartElement().getName();
264                 if (WSSConstants.TAG_dsig_KeyInfo.equals(elementName)) {
265                     List<QName> elementPath = xmlSecEvent.asStartElement().getElementPath();
266                     if (elementPath.size() >= 4) {
267                         int lastIndex = elementPath.size() - 2;
268                         if ("Signature".equals(elementPath.get(lastIndex).getLocalPart())
269                                 && "Assertion".equals(elementPath.get(lastIndex - 1).getLocalPart())) {
270                             return idx;
271                         }
272                     }
273                 }
274             }
275         }
276         return idx;
277     }
278 
279     private InboundSecurityToken parseKeyInfo(InputProcessorChain inputProcessorChain, XMLSecurityProperties securityProperties,
280                                        Deque<XMLSecEvent> eventQueue, int index) throws XMLSecurityException {
281         XMLSecEvent xmlSecEvent = null;
282         int idx = 0;
283         Iterator<XMLSecEvent> xmlSecEventIterator = eventQueue.descendingIterator();
284         while (xmlSecEventIterator.hasNext() && idx <= index) {
285             xmlSecEvent = xmlSecEventIterator.next();
286             idx++;
287         }
288         //forward to next start element
289         while (xmlSecEventIterator.hasNext()) {
290             xmlSecEvent = xmlSecEventIterator.next();
291             if (xmlSecEvent.isStartElement()) {
292                 break;
293             }
294             idx++;
295         }
296         if (xmlSecEvent == null || !xmlSecEvent.isStartElement()) {
297             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "noKeyInSAMLToken");
298         }
299 
300         final XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
301         final QName elementName = xmlSecStartElement.getName();
302         if (WSSConstants.TAG_WST_BINARY_SECRET.equals(elementName)
303             || WSSConstants.TAG_WST0512_BINARY_SECRET.equals(elementName)) {
304 
305             final StringBuilder stringBuilder = new StringBuilder();
306             loop:
307             while (xmlSecEventIterator.hasNext()) {
308                 xmlSecEvent = xmlSecEventIterator.next();
309                 switch (xmlSecEvent.getEventType()) {   //NOPMD
310                     case XMLStreamConstants.END_ELEMENT:
311                         if (xmlSecEvent.asEndElement().getName().equals(elementName)) {
312                             break loop;
313                         }
314                         break;
315                     case XMLStreamConstants.CHARACTERS:
316                         stringBuilder.append(xmlSecEvent.asCharacters().getText());
317                         break;
318                 }
319             }
320 
321             return new AbstractInboundSecurityToken(
322                     inputProcessorChain.getSecurityContext(), IDGenerator.generateID(null),
323                     WSSecurityTokenConstants.KeyIdentifier_NoKeyInfo, true) {
324                 @Override
325                 public WSSecurityTokenConstants.TokenType getTokenType() {
326                     return WSSecurityTokenConstants.DefaultToken;
327                 }
328 
329                 @Override
330                 public boolean isAsymmetric() throws XMLSecurityException {
331                     return false;
332                 }
333 
334                 @Override
335                 protected Key getKey(String algorithmURI, XMLSecurityConstants.AlgorithmUsage algorithmUsage, String correlationID)
336                         throws XMLSecurityException {
337                     Key key = super.getKey(algorithmURI, algorithmUsage, correlationID);
338                     if (key == null) {
339                         String algoFamily = JCEAlgorithmMapper.getJCEKeyAlgorithmFromURI(algorithmURI);
340                         key = new SecretKeySpec(XMLUtils.decode(stringBuilder.toString()), algoFamily);
341                         setSecretKey(algorithmURI, key);
342                     }
343                     return key;
344                 }
345             };
346         } else {
347             Object object = null;
348             try {
349                 Unmarshaller unmarshaller = WSSConstants.getJaxbUnmarshaller(securityProperties.isDisableSchemaValidation());
350                 object = unmarshaller.unmarshal(new XMLSecurityEventReader(eventQueue, idx));
351             } catch (JAXBException e) {
352                 throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, e);
353             }
354 
355             if (object instanceof JAXBElement) {
356                 object = ((JAXBElement<?>) object).getValue();
357             }
358 
359             KeyInfoType keyInfoType = null;
360             if (object instanceof X509DataType) {
361                 JAXBElement<X509DataType> x509DataTypeJAXBElement =
362                         new org.apache.xml.security.binding.xmldsig.ObjectFactory().createX509Data((X509DataType) object);
363                 keyInfoType = new KeyInfoType();
364                 SecurityTokenReferenceType securityTokenReferenceType = new SecurityTokenReferenceType();
365                 securityTokenReferenceType.getAny().add(x509DataTypeJAXBElement);
366                 JAXBElement<SecurityTokenReferenceType> securityTokenReferenceTypeJAXBElement =
367                         new ObjectFactory().createSecurityTokenReference(securityTokenReferenceType);
368                 keyInfoType.getContent().add(securityTokenReferenceTypeJAXBElement);
369             } else if (object instanceof EncryptedKeyType) {
370                 EncryptedKeyType encryptedKeyType = (EncryptedKeyType) object;
371 
372                 WSSEncryptedKeyInputHandler encryptedKeyInputHandler = new WSSEncryptedKeyInputHandler();
373                 encryptedKeyInputHandler.handle(inputProcessorChain, encryptedKeyType, xmlSecStartElement, securityProperties);
374 
375                 SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider =
376                     inputProcessorChain.getSecurityContext().getSecurityTokenProvider(encryptedKeyType.getId());
377                 if (securityTokenProvider != null) {
378                     return securityTokenProvider.getSecurityToken();
379                 }
380 
381             } else if (object instanceof SecurityTokenReferenceType) {
382                 JAXBElement<SecurityTokenReferenceType> securityTokenReferenceTypeJAXBElement =
383                         new ObjectFactory().createSecurityTokenReference((SecurityTokenReferenceType) object);
384                 keyInfoType = new KeyInfoType();
385                 keyInfoType.getContent().add(securityTokenReferenceTypeJAXBElement);
386             } else if (object instanceof KeyValueType) {
387                 JAXBElement<KeyValueType> keyValueTypeJAXBElement =
388                         new org.apache.xml.security.binding.xmldsig.ObjectFactory().createKeyValue((KeyValueType) object);
389                 keyInfoType = new KeyInfoType();
390                 keyInfoType.getContent().add(keyValueTypeJAXBElement);
391             } else {
392                 throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, "unsupportedKeyInfo");
393             }
394 
395             return SecurityTokenFactory.getInstance().getSecurityToken(
396                     keyInfoType, WSSecurityTokenConstants.KeyUsage_Signature_Verification,
397                     securityProperties, inputProcessorChain.getSecurityContext());
398         }
399     }
400 
401     @SuppressWarnings("unchecked")
402     @Override
403     protected <T> T parseStructure(Deque<XMLSecEvent> eventDeque, int index, XMLSecurityProperties securityProperties)
404             throws XMLSecurityException {
405         Document document = null;
406         try {
407             document = ((WSSSecurityProperties) securityProperties).getDocumentCreator().newDocument();
408         } catch (ParserConfigurationException e) {
409             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, e);
410         }
411 
412         Iterator<XMLSecEvent> xmlSecEventIterator = eventDeque.descendingIterator();
413         int curIdx = 0;
414         while (curIdx++ < index) {
415             xmlSecEventIterator.next();
416         }
417 
418         Node currentNode = document;
419         while (xmlSecEventIterator.hasNext()) {
420             XMLSecEvent next = xmlSecEventIterator.next();
421             currentNode = parseXMLEvent(next, currentNode, document);
422         }
423         return (T) document;
424     }
425 
426     //todo custom SAML unmarshaller directly to XMLObject?
427     public Node parseXMLEvent(XMLSecEvent xmlSecEvent, Node currentNode, Document document) throws WSSecurityException {
428         switch (xmlSecEvent.getEventType()) {
429             case XMLStreamConstants.START_ELEMENT:
430                 XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
431                 Element element = document.createElementNS(xmlSecStartElement.getName().getNamespaceURI(),
432                         xmlSecStartElement.getName().getLocalPart());
433                 if (xmlSecStartElement.getName().getPrefix() != null && !xmlSecStartElement.getName().getPrefix().isEmpty()) {
434                     element.setPrefix(xmlSecStartElement.getName().getPrefix());
435                 }
436                 currentNode = currentNode.appendChild(element);
437                 @SuppressWarnings("unchecked")
438                 Iterator<Namespace> namespaceIterator = xmlSecStartElement.getNamespaces();
439                 while (namespaceIterator.hasNext()) {
440                     XMLSecNamespace next = (XMLSecNamespace)namespaceIterator.next();
441                     parseXMLEvent(next, currentNode, document);
442                 }
443                 @SuppressWarnings("unchecked")
444                 Iterator<Attribute> attributesIterator = xmlSecStartElement.getAttributes();
445                 while (attributesIterator.hasNext()) {
446                     XMLSecAttribute next = (XMLSecAttribute)attributesIterator.next();
447                     parseXMLEvent(next, currentNode, document);
448                 }
449                 //add namespace which is not declared on current element but must be on a parent element:
450                 String elementNs = document.lookupNamespaceURI(xmlSecStartElement.getName().getPrefix());
451                 if (elementNs == null) {
452                     parseXMLEvent(xmlSecStartElement.getElementNamespace(), currentNode, document);
453                 }
454                 break;
455             case XMLStreamConstants.END_ELEMENT:
456                 if (currentNode.getParentNode() != null) {
457                     currentNode = currentNode.getParentNode();
458                 }
459                 break;
460             case XMLStreamConstants.PROCESSING_INSTRUCTION:
461                 Node piNode = document.createProcessingInstruction(
462                         ((ProcessingInstruction) xmlSecEvent).getTarget(),
463                         ((ProcessingInstruction) xmlSecEvent).getTarget()
464                 );
465                 currentNode.appendChild(piNode);
466                 break;
467             case XMLStreamConstants.CHARACTERS:
468                 Node characterNode = document.createTextNode(xmlSecEvent.asCharacters().getData());
469                 currentNode.appendChild(characterNode);
470                 break;
471             case XMLStreamConstants.COMMENT:
472                 Node commentNode = document.createComment(((Comment) xmlSecEvent).getText());
473                 currentNode.appendChild(commentNode);
474                 break;
475             case XMLStreamConstants.START_DOCUMENT:
476                 break;
477             case XMLStreamConstants.END_DOCUMENT:
478                 return currentNode;
479             case XMLStreamConstants.ATTRIBUTE:
480                 final XMLSecAttribute xmlSecAttribute = (XMLSecAttribute) xmlSecEvent;
481                 Attr attributeNode = document.createAttributeNS(
482                         xmlSecAttribute.getName().getNamespaceURI(),
483                         xmlSecAttribute.getName().getLocalPart());
484                 attributeNode.setPrefix(xmlSecAttribute.getName().getPrefix());
485                 attributeNode.setValue(xmlSecAttribute.getValue());
486                 ((Element) currentNode).setAttributeNodeNS(attributeNode);
487 
488                 //add namespace which is not declared on current element but must be on a parent element:
489                 String attrNs = document.lookupNamespaceURI(xmlSecAttribute.getName().getPrefix());
490                 if (attrNs == null) {
491                     parseXMLEvent(xmlSecAttribute.getAttributeNamespace(), currentNode, document);
492                 }
493                 break;
494             case XMLStreamConstants.DTD:
495                 //todo?:
496                 /*
497                 Node dtdNode = document.getDoctype().getEntities()
498                 ((DTD)xmlSecEvent).getDocumentTypeDeclaration():
499                 ((DTD)xmlSecEvent).getEntities()
500                 */
501                 break;
502             case XMLStreamConstants.NAMESPACE:
503                 Namespace namespace = (Namespace) xmlSecEvent;
504                 Attr namespaceNode;
505                 String prefix = namespace.getPrefix();
506                 if (prefix == null || prefix.isEmpty()) {
507                     namespaceNode = document.createAttributeNS(WSSConstants.NS_XML, "xmlns");
508                 } else {
509                     namespaceNode = document.createAttributeNS(WSSConstants.NS_XML, "xmlns:" + prefix);
510                 }
511                 namespaceNode.setValue(namespace.getNamespaceURI());
512                 ((Element) currentNode).setAttributeNodeNS(namespaceNode);
513                 break;
514             default:
515                 throw new WSSecurityException(
516                         WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN,
517                         "empty",
518                         new Object[] {"Illegal XMLEvent received: " + xmlSecEvent.getEventType()});
519         }
520         return currentNode;
521     }
522 
523     /**
524      * Processor to check the holder-of-key or sender-vouches requirements against the received assertion
525      * which can not be done until the whole soap-header is processed and we know that the whole soap-body
526      * is signed.
527      */
528     static class SAMLTokenVerifierInputProcessor extends AbstractInputProcessor implements SecurityEventListener {
529 
530         private SamlAssertionWrapper samlAssertionWrapper;
531         private SecurityTokenProvider<InboundSecurityToken> securityTokenProvider;
532         private InboundSecurityToken subjectSecurityToken;
533         private List<SignedElementSecurityEvent> samlTokenSignedElementSecurityEvents = new ArrayList<>();
534         private SignedPartSecurityEvent bodySignedPartSecurityEvent;
535 
536         private final boolean soap12;
537         private final List<QName> saml1TokenPath;
538         private final List<QName> saml2TokenPath;
539 
540         SAMLTokenVerifierInputProcessor(XMLSecurityProperties securityProperties,
541                                         SamlAssertionWrapper samlAssertionWrapper,
542                                         SecurityTokenProvider<InboundSecurityToken> securityTokenProvider,
543                                         InboundSecurityToken subjectSecurityToken,
544                                         boolean soap12) {
545             super(securityProperties);
546             this.setPhase(XMLSecurityConstants.Phase.POSTPROCESSING);
547             this.addAfterProcessor(OperationInputProcessor.class.getName());
548             this.samlAssertionWrapper = samlAssertionWrapper;
549             this.securityTokenProvider = securityTokenProvider;
550             this.subjectSecurityToken = subjectSecurityToken;
551 
552             this.soap12 = soap12;
553             if (soap12) {
554                 saml1TokenPath = new ArrayList<>(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH);
555                 saml1TokenPath.add(WSSConstants.TAG_SAML_ASSERTION);
556                 saml2TokenPath = new ArrayList<>(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH);
557                 saml2TokenPath.add(WSSConstants.TAG_SAML2_ASSERTION);
558             } else {
559                 saml1TokenPath = new ArrayList<>(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
560                 saml1TokenPath.add(WSSConstants.TAG_SAML_ASSERTION);
561                 saml2TokenPath = new ArrayList<>(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
562                 saml2TokenPath.add(WSSConstants.TAG_SAML2_ASSERTION);
563             }
564         }
565 
566         @Override
567         public void registerSecurityEvent(SecurityEvent securityEvent) throws XMLSecurityException {
568             if (WSSecurityEventConstants.SIGNED_PART.equals(securityEvent.getSecurityEventType())) {
569                 SignedPartSecurityEvent signedPartSecurityEvent = (SignedPartSecurityEvent) securityEvent;
570 
571                 List<QName> elementPath = signedPartSecurityEvent.getElementPath();
572                 if (soap12 && WSSUtils.pathMatches(WSSConstants.SOAP_12_BODY_PATH, elementPath)
573                     || !soap12 && WSSUtils.pathMatches(WSSConstants.SOAP_11_BODY_PATH, elementPath)) {
574                     bodySignedPartSecurityEvent = signedPartSecurityEvent;
575                 }
576             } else if (WSSecurityEventConstants.SignedElement.equals(securityEvent.getSecurityEventType())) {
577                 SignedElementSecurityEvent signedPartSecurityEvent = (SignedElementSecurityEvent) securityEvent;
578 
579                 List<QName> elementPath = signedPartSecurityEvent.getElementPath();
580                 if (WSSUtils.pathMatches(saml2TokenPath, elementPath)
581                     || WSSUtils.pathMatches(saml1TokenPath, elementPath)) {
582                     samlTokenSignedElementSecurityEvents.add(signedPartSecurityEvent);
583                 }
584             }
585         }
586 
587         @Override
588         public XMLSecEvent processHeaderEvent(InputProcessorChain inputProcessorChain)
589                 throws XMLStreamException, XMLSecurityException {
590             return inputProcessorChain.processHeaderEvent();
591         }
592 
593         @Override
594         public XMLSecEvent processEvent(InputProcessorChain inputProcessorChain)
595                 throws XMLStreamException, XMLSecurityException {
596 
597             XMLSecEvent xmlSecEvent = inputProcessorChain.processEvent();
598             if (xmlSecEvent.getEventType() == XMLStreamConstants.START_ELEMENT) {
599                 XMLSecStartElement xmlSecStartElement = xmlSecEvent.asStartElement();
600                 List<QName> elementPath = xmlSecStartElement.getElementPath();
601                 if (elementPath.size() == 3 && WSSUtils.isInSOAPBody(elementPath)) {
602                     inputProcessorChain.removeProcessor(this);
603                     checkPossessionOfKey(inputProcessorChain, samlAssertionWrapper, subjectSecurityToken);
604                 }
605             }
606             return xmlSecEvent;
607         }
608 
609         private void checkPossessionOfKey(
610                 InputProcessorChain inputProcessorChain, SamlAssertionWrapper samlAssertionWrapper,
611                 InboundSecurityToken subjectSecurityToken) throws WSSecurityException {
612 
613             boolean methodNotSatisfied = false;
614             try {
615                 SecurityToken httpsSecurityToken = getHttpsSecurityToken(inputProcessorChain);
616 
617                 List<SecurityTokenProvider<? extends InboundSecurityToken>> securityTokenProviders =
618                         inputProcessorChain.getSecurityContext().getRegisteredSecurityTokenProviders();
619 
620                 List<String> confirmationMethods = samlAssertionWrapper.getConfirmationMethods();
621                 for (int i = 0; i < confirmationMethods.size(); i++) {
622                     String confirmationMethod = confirmationMethods.get(i);
623                     if (OpenSAMLUtil.isMethodHolderOfKey(confirmationMethod)) {
624 
625                         X509Certificate[] subjectCertificates = subjectSecurityToken.getX509Certificates();
626                         PublicKey subjectPublicKey = subjectSecurityToken.getPublicKey();
627                         Key subjectSecretKey = null;
628                         Map<String, Key> subjectKeyMap = subjectSecurityToken.getSecretKey();
629                         if (!subjectKeyMap.isEmpty()) {
630                             subjectSecretKey = subjectKeyMap.values().toArray(new Key[subjectKeyMap.size()])[0];
631                         }
632 
633                         /**
634                          * Check the holder-of-key requirements against the received assertion. The subject
635                          * credential of the SAML Assertion must have been used to sign some portion of
636                          * the message, thus showing proof-of-possession of the private/secret key. Alternatively,
637                          * the subject credential of the SAML Assertion must match a client certificate credential
638                          * when 2-way TLS is used.
639                          */
640 
641                         //compare https token first:
642                         if (httpsSecurityToken != null
643                                 && httpsSecurityToken.getX509Certificates() != null
644                                 && httpsSecurityToken.getX509Certificates().length > 0) {
645 
646                             X509Certificate httpsCertificate = httpsSecurityToken.getX509Certificates()[0];
647 
648                             //compare certificates:
649                             if (subjectCertificates != null && subjectCertificates.length > 0
650                                     && httpsCertificate.equals(subjectCertificates[0])) {
651                                 return;
652                                 //compare public keys:
653                             } else if (httpsCertificate.getPublicKey().equals(subjectPublicKey)) {
654                                 return;
655                             }
656                         }
657 
658                         // Now try message signatures
659                         for (int j = 0; j < securityTokenProviders.size(); j++) {
660                             SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = securityTokenProviders.get(j);
661                             InboundSecurityToken securityToken = securityTokenProvider.getSecurityToken();
662                             // Don't compare to the original SAML Token credentials...
663                             if (securityToken == httpsSecurityToken || securityToken == subjectSecurityToken
664                                 || !containsSignature(securityToken.getTokenUsages())) {
665                                 continue;
666                             }
667                             X509Certificate[] x509Certificates = securityToken.getX509Certificates();
668                             PublicKey publicKey = securityToken.getPublicKey();
669                             Map<String, Key> keyMap = securityToken.getSecretKey();
670                             if (x509Certificates != null && x509Certificates.length > 0
671                                 && subjectCertificates != null && subjectCertificates.length > 0
672                                 && subjectCertificates[0].equals(x509Certificates[0])) {
673                                 return;
674                             }
675                             if (publicKey != null && publicKey.equals(subjectPublicKey)) {
676                                 return;
677                             }
678                             Iterator<Map.Entry<String, Key>> iterator = keyMap.entrySet().iterator();
679                             while (iterator.hasNext()) {
680                                 Map.Entry<String, Key> next = iterator.next();
681                                 if (next.getValue().equals(subjectSecretKey)) {
682                                     return;
683                                 }
684                             }
685                         }
686                         methodNotSatisfied = true;
687                     } else if (OpenSAMLUtil.isMethodSenderVouches(confirmationMethod)) {
688                         /**
689                          * Check the sender-vouches requirements against the received assertion. The SAML
690                          * Assertion and the SOAP Body must be signed by the same signature.
691                          */
692 
693                         //
694                         // If we have a 2-way TLS connection, then we don't have to check that the
695                         // assertion + SOAP body are signed
696                         if (httpsSecurityToken != null
697                                 && httpsSecurityToken.getX509Certificates() != null
698                                 && httpsSecurityToken.getX509Certificates().length > 0) {
699                             return;
700                         }
701 
702                         SignedElementSecurityEvent samlTokenSignedElementSecurityEvent = null;
703                         for (int j = 0; j < samlTokenSignedElementSecurityEvents.size(); j++) {
704                             SignedElementSecurityEvent signedElementSecurityEvent = samlTokenSignedElementSecurityEvents.get(j);
705                             if (securityTokenProvider.getSecurityToken().getXMLSecEvent()
706                                 == signedElementSecurityEvent.getXmlSecEvent()) {
707 
708                                 samlTokenSignedElementSecurityEvent = signedElementSecurityEvent;
709                             }
710                         }
711                         if (bodySignedPartSecurityEvent != null
712                             && samlTokenSignedElementSecurityEvent != null
713                             && bodySignedPartSecurityEvent.getSecurityToken()
714                                 == samlTokenSignedElementSecurityEvent.getSecurityToken()) {
715                             return;
716                         }
717                         methodNotSatisfied = true;
718                     }
719                 }
720             } catch (XMLSecurityException e) {
721                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
722             }
723             if (methodNotSatisfied) {
724                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION,
725                     "empty",
726                     new Object[] {"SAML proof-of-possession of the private/secret key failed"});
727             }
728         }
729 
730         private SecurityToken getHttpsSecurityToken(InputProcessorChain inputProcessorChain) throws XMLSecurityException {
731             List<SecurityTokenProvider<? extends InboundSecurityToken>> securityTokenProviders =
732                     inputProcessorChain.getSecurityContext().getRegisteredSecurityTokenProviders();
733             for (int i = 0; i < securityTokenProviders.size(); i++) {
734                 SecurityTokenProvider<? extends InboundSecurityToken> securityTokenProvider = securityTokenProviders.get(i);
735                 SecurityToken securityToken = securityTokenProvider.getSecurityToken();
736                 if (WSSecurityTokenConstants.HTTPS_TOKEN.equals(securityToken.getTokenType())) {
737                     return securityToken;
738                 }
739             }
740             return null;
741         }
742 
743         private boolean containsSignature(List<TokenUsage> tokenUses) {
744             return tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_MAIN_SIGNATURE)
745                 || tokenUses.contains(WSSecurityTokenConstants.TokenUsage_Signature)
746                 || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS)
747                 || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_ENDORSING_SUPPORTING_TOKENS)
748                 || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_SIGNED_ENDORSING_ENCRYPTED_SUPPORTING_TOKENS)
749                 || tokenUses.contains(WSSecurityTokenConstants.TOKENUSAGE_SIGNED_ENDORSING_SUPPORTING_TOKENS);
750         }
751     }
752 }