View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.soap.security.wss4j;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.security.Principal;
23  import java.security.cert.X509Certificate;
24  import java.util.Vector;
25  import javax.security.auth.callback.Callback;
26  import javax.security.auth.callback.CallbackHandler;
27  import javax.security.auth.callback.UnsupportedCallbackException;
28  import javax.xml.soap.MessageFactory;
29  
30  import org.apache.axiom.soap.SOAPEnvelope;
31  import org.apache.axiom.soap.SOAPFactory;
32  import org.apache.axiom.soap.SOAPMessage;
33  import org.apache.ws.security.WSConstants;
34  import org.apache.ws.security.WSSecurityEngine;
35  import org.apache.ws.security.WSSecurityEngineResult;
36  import org.apache.ws.security.WSSecurityException;
37  import org.apache.ws.security.WSUsernameTokenPrincipal;
38  import org.apache.ws.security.components.crypto.Crypto;
39  import org.apache.ws.security.handler.RequestData;
40  import org.apache.ws.security.handler.WSHandlerConstants;
41  import org.apache.ws.security.handler.WSHandlerResult;
42  import org.apache.ws.security.message.token.Timestamp;
43  import org.apache.ws.security.util.WSSecurityUtil;
44  import org.w3c.dom.Document;
45  
46  import org.springframework.beans.factory.InitializingBean;
47  import org.springframework.util.Assert;
48  import org.springframework.util.CollectionUtils;
49  import org.springframework.util.StringUtils;
50  import org.springframework.ws.context.DefaultMessageContext;
51  import org.springframework.ws.context.MessageContext;
52  import org.springframework.ws.soap.SoapMessage;
53  import org.springframework.ws.soap.axiom.AxiomSoapMessage;
54  import org.springframework.ws.soap.axiom.support.AxiomUtils;
55  import org.springframework.ws.soap.saaj.SaajSoapMessage;
56  import org.springframework.ws.soap.saaj.SaajSoapMessageException;
57  import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
58  import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor;
59  import org.springframework.ws.soap.security.WsSecuritySecurementException;
60  import org.springframework.ws.soap.security.WsSecurityValidationException;
61  import org.springframework.ws.soap.security.callback.CallbackHandlerChain;
62  import org.springframework.ws.soap.security.callback.CleanupCallback;
63  import org.springframework.ws.soap.security.wss4j.callback.UsernameTokenPrincipalCallback;
64  
65  /**
66   * A WS-Security endpoint interceptor based on Apache's WSS4J. This inteceptor supports messages created by the {@link
67   * org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the {@link org.springframework.ws.soap.saaj.SaajSoapMessageFactory}.
68   * <p/>
69   * The validation and securement actions executed by this interceptor are configured via <code>validationActions</code>
70   * and <code>securementActions</code> properties, respectively. Actions should be passed as a space-separated strings.
71   * <p/>
72   * Valid <strong>validation</strong> actions are:
73   * <p/>
74   * <blockquote><table> <tr><th>Validation action</th><th>Description</th></tr> <tr><td><code>UsernameToken</code></td><td>Validates
75   * username token</td></tr> <tr><td><code>Timestamp</code></td><td>Validates the timestamp</td></tr>
76   * <tr><td><code>Encrypt</code></td><td>Decrypts the message</td></tr> <tr><td><code>Signature</code></td><td>Validates
77   * the signature</td></tr> <tr><td><code>NoSecurity</code></td><td>No action performed</td></tr> </table></blockquote>
78   * <p/>
79   * <strong>Securement</strong> actions are: <blockquote><table> <tr><th>Securement action</th><th>Description</th></tr>
80   * <tr><td><code>UsernameToken</td></code><td>Adds a username token</td></tr> <tr><td><code>UsernameTokenSignature</td></code><td>Adds
81   * a username token and a signature username token secret key</td></tr> <tr><td><code>Timestamp</td></code><td>Adds a
82   * timestamp</td></tr> <tr><td><code>Encrypt</td></code><td>Encrypts the response</td></tr>
83   * <tr><td><code>Signature</td></code><td>Signs the response</td></tr> <tr><td><code>NoSecurity</td></code><td>No action
84   * performed</td></tr> </table></blockquote>
85   * <p/>
86   * The order of the actions that the client performed to secure the messages is significant and is enforced by the
87   * interceptor.
88   *
89   * @author Tareq Abed Rabbo
90   * @author Arjen Poutsma
91   * @see <a href="http://ws.apache.org/wss4j/">Apache WSS4J</a>
92   * @since 1.5.0
93   */
94  public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor implements InitializingBean {
95  
96      public static final String SECUREMENT_USER_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementUser";
97  
98      private int securementAction;
99  
100     private String securementActions;
101 
102     private Vector securementActionsVector;
103 
104     private String securementUsername;
105 
106     private CallbackHandler validationCallbackHandler;
107 
108     private int validationAction;
109 
110     private String validationActions;
111 
112     private Vector validationActionsVector;
113 
114     private String validationActor;
115 
116     private Crypto validationDecryptionCrypto;
117 
118     private Crypto validationSignatureCrypto;
119 
120     private boolean timestampStrict = true;
121 
122     private boolean enableSignatureConfirmation;
123 
124     private int validationTimeToLive = 300;
125 
126     private int securementTimeToLive = 300;
127 
128     private final Wss4jHandler handler = new Wss4jHandler();
129 
130     private final WSSecurityEngine securityEngine = WSSecurityEngine.getInstance();
131 
132     public void setSecurementActions(String securementActions) {
133         this.securementActions = securementActions;
134         securementActionsVector = new Vector();
135         try {
136             securementAction = WSSecurityUtil.decodeAction(securementActions, securementActionsVector);
137         }
138         catch (WSSecurityException ex) {
139             throw new IllegalArgumentException(ex);
140         }
141     }
142 
143     /**
144      * The actor name of the <code>wsse:Security</code> header.
145      * <p/>
146      * If this parameter is omitted, the actor name is not set.
147      * <p/>
148      * The value of the actor or role has to match the receiver's setting or may contain standard values.
149      */
150     public void setSecurementActor(String securementActor) {
151         handler.setOption(WSHandlerConstants.ACTOR, securementActor);
152     }
153 
154     /**
155      * Sets the {@link org.apache.ws.security.WSPasswordCallback} handler to use when securing messages.
156      *
157      * @see #setSecurementCallbackHandlers(CallbackHandler[])
158      */
159     public void setSecurementCallbackHandler(CallbackHandler securementCallbackHandler) {
160         handler.setSecurementCallbackHandler(securementCallbackHandler);
161     }
162 
163     /**
164      * Sets the {@link org.apache.ws.security.WSPasswordCallback} handlers to use when securing messages.
165      *
166      * @see #setSecurementCallbackHandler(CallbackHandler)
167      */
168     public void setSecurementCallbackHandlers(CallbackHandler[] securementCallbackHandler) {
169         handler.setSecurementCallbackHandler(new CallbackHandlerChain(securementCallbackHandler));
170     }
171 
172     public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
173         handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
174     }
175 
176     /** Sets the key name that needs to be sent for encryption. */
177     public void setSecurementEncryptionEmbeddedKeyName(String securementEncryptionEmbeddedKeyName) {
178         handler.setOption(WSHandlerConstants.ENC_KEY_NAME, securementEncryptionEmbeddedKeyName);
179     }
180 
181     /**
182      * Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type
183      * <code>IssuerSerial</code>. For possible encryption key identifier types refer to {@link
184      * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For encryption <code>IssuerSerial</code>,
185      * <code>X509KeyIdentifier</code>,  <code>DirectReference</code>, <code>Thumbprint</code>,
186      * <code>SKIKeyIdentifier</code>, and <code>EmbeddedKeyName</code> are valid only.
187      */
188     public void setSecurementEncryptionKeyIdentifier(String securementEncryptionKeyIdentifier) {
189         handler.setOption(WSHandlerConstants.ENC_KEY_ID, securementEncryptionKeyIdentifier);
190     }
191 
192     /**
193      * Defines which algorithm to use to encrypt the generated symmetric key. Currently WSS4J supports {@link
194      * WSConstants#KEYTRANSPORT_RSA15} and {@link WSConstants#KEYTRANSPORT_RSAOEP}.
195      */
196     public void setSecurementEncryptionKeyTransportAlgorithm(String securementEncryptionKeyTransportAlgorithm) {
197         handler.setOption(WSHandlerConstants.ENC_KEY_TRANSPORT, securementEncryptionKeyTransportAlgorithm);
198     }
199 
200     /**
201      * Property to define which parts of the request shall be encrypted.
202      * <p/>
203      * The value of this property is a list of semi-colon separated element names that identify the elements to encrypt.
204      * An encryption mode specifier and a namespace identification, each inside a pair of curly brackets, may precede
205      * each element name.
206      * <p/>
207      * The encryption mode specifier is either <code>{Content}</code> or <code>{Element}</code>. Please refer to the W3C
208      * XML Encryption specification about the differences between Element and Content encryption. The encryption mode
209      * defaults to <code>Content</code> if it is omitted. Example of a list:
210      * <pre>
211      * &lt;property name="securementEncryptionParts"
212      *   value="{Content}{http://example.org/paymentv2}CreditCard;
213      *             {Element}{}UserName" />
214      * </pre>
215      * The the first entry of the list identifies the element <code>CreditCard</code> in the namespace
216      * <code>http://example.org/paymentv2</code>, and will encrypt its content. Be aware that the element name, the
217      * namespace identifier, and the encryption modifier are case sensitive.
218      * <p/>
219      * The encryption modifier and the namespace identifier can be omitted. In this case the encryption mode defaults to
220      * <code>Content</code> and the namespace is set to the SOAP namespace.
221      * <p/>
222      * An empty encryption mode defaults to <code>Content</code>, an empty namespace identifier defaults to the SOAP
223      * namespace. The second line of the example defines <code>Element</code> as encryption mode for an
224      * <code>UserName</code> element in the SOAP namespace.
225      * <p/>
226      * To specify an element without a namespace use the string <code>Null</code> as the namespace name (this is a case
227      * sensitive string)
228      * <p/>
229      * If no list is specified, the handler encrypts the SOAP Body in <code>Content</code> mode by default.
230      */
231     public void setSecurementEncryptionParts(String securementEncryptionParts) {
232         handler.setOption(WSHandlerConstants.ENCRYPTION_PARTS, securementEncryptionParts);
233     }
234 
235     /**
236      * Defines which symmetric encryption algorithm to use. WSS4J supports the following alorithms: {@link
237      * WSConstants#TRIPLE_DES}, {@link WSConstants#AES_128}, {@link WSConstants#AES_256}, and {@link
238      * WSConstants#AES_192}. Except for AES 192 all of these algorithms are required by the XML Encryption
239      * specification.
240      */
241     public void setSecurementEncryptionSymAlgorithm(String securementEncryptionSymAlgorithm) {
242         this.handler.setOption(WSHandlerConstants.ENC_SYM_ALGO, securementEncryptionSymAlgorithm);
243     }
244 
245     /**
246      * The user's name for encryption.
247      * <p/>
248      * The encryption functions uses the public key of this user's certificate to encrypt the generated symmetric key.
249      * <p/>
250      * If this parameter is not set, then the encryption function falls back to the {@link
251      * org.apache.ws.security.handler.WSHandlerConstants#USER} parameter to get the certificate.
252      * <p/>
253      * If <b>only</b> encryption of the SOAP body data is requested, it is recommended to use this parameter to define
254      * the username. The application can then use the standard user and password functions (see example at {@link
255      * org.apache.ws.security.handler.WSHandlerConstants#USER} to enable HTTP authentication functions.
256      * <p/>
257      * Encryption only does not authenticate a user / sender, therefore it does not need a password.
258      * <p/>
259      * Placing the username of the encryption certificate in the configuration file is not a security risk, because the
260      * public key of that certificate is used only.
261      * <p/>
262      */
263     public void setSecurementEncryptionUser(String securementEncryptionUser) {
264         handler.setOption(WSHandlerConstants.ENCRYPTION_USER, securementEncryptionUser);
265     }
266 
267     public void setSecurementPassword(String securementPassword) {
268         this.handler.setSecurementPassword(securementPassword);
269     }
270 
271     /**
272      * Specific parameter for UsernameToken action to define the encoding of the passowrd.
273      * <p/>
274      * The parameter can be set to either {@link WSConstants#PW_DIGEST} or to {@link WSConstants#PW_TEXT}.
275      * <p/>
276      * The default setting is PW_DIGEST.
277      */
278     public void setSecurementPasswordType(String securementUsernameTokenPasswordType) {
279         handler.setOption(WSHandlerConstants.PASSWORD_TYPE, securementUsernameTokenPasswordType);
280     }
281 
282     /**
283      * Defines which signature algorithm to use.
284      * @see WSConstants#RSA
285      * @see WSConstants#DSA
286      */
287     public void setSecurementSignatureAlgorithm(String securementSignatureAlgorithm) {
288         handler.setOption(WSHandlerConstants.SIG_ALGO, securementSignatureAlgorithm);
289     }
290 
291     public void setSecurementSignatureCrypto(Crypto securementSignatureCrypto) {
292         handler.setSecurementSignatureCrypto(securementSignatureCrypto);
293     }
294 
295     /**
296      * Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type
297      * <code>IssuerSerial</code>. For possible signature key identifier types refer to {@link
298      * org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For signature <code>IssuerSerial</code> and
299      * <code>DirectReference</code> are valid only.
300      */
301     public void setSecurementSignatureKeyIdentifier(String securementSignatureKeyIdentifier) {
302         handler.setOption(WSHandlerConstants.SIG_KEY_ID, securementSignatureKeyIdentifier);
303     }
304 
305     /**
306      * Property to define which parts of the request shall be signed.
307      * <p/>
308      * Refer to {@link #setSecurementEncryptionParts(String)} for a detailed description of the format of the value
309      * string.
310      * <p/>
311      * If this property is not specified the handler signs the SOAP Body by default.
312      * <p/>
313      * The WS Security specifications define several formats to transfer the signature tokens (certificates) or
314      * references to these tokens. Thus, the plain element name <code>Token</code> signs the token and takes care of the
315      * different formats.
316      * <p/>
317      * To sign the SOAP body <b>and</b> the signature token the value of this parameter must contain:
318      * <pre>
319      * &lt;property name="securementSignatureParts"
320      *   value="{}{http://schemas.xmlsoap.org/soap/envelope/}Body; Token" />
321      * </pre>
322      * To specify an element without a namespace use the string <code>Null</code> as the namespace name (this is a case
323      * sensitive string)
324      * <p/>
325      * If there is no other element in the request with a local name of <code>Body</code> then the SOAP namespace
326      * identifier can be empty (<code>{}</code>).
327      */
328     public void setSecurementSignatureParts(String securementSignatureParts) {
329         handler.setOption(WSHandlerConstants.SIGNATURE_PARTS, securementSignatureParts);
330     }
331 
332     /**
333      * The user's name for signature.
334      * <p/>
335      * This name is used as the alias name in the keystore to get user's
336      * certificate and private key to perform signing.
337      * <p/>
338      * If this parameter is not set, then the signature
339      * function falls back to the alias specified by {@link #setSecurementUsername(String)}.
340      * <p/>
341      */
342     public void setSecurementSignatureUser(String securementSignatureUser) {
343         handler.setOption(WSHandlerConstants.SIGNATURE_USER, securementSignatureUser);
344     }
345 
346     /** Sets the username for securement username token or/and the alias of the private key for securement signature */
347     public void setSecurementUsername(String securementUsername) {
348         this.securementUsername = securementUsername;
349     }
350 
351     /** Sets the time to live on the outgoing message */
352     public void setSecurementTimeToLive(int securementTimeToLive) {
353         if (securementTimeToLive <= 0) {
354             throw new IllegalArgumentException("timeToLive must be positive");
355         }
356         this.securementTimeToLive = securementTimeToLive;
357     }
358 
359     /** Sets the server-side time to live */
360     public void setValidationTimeToLive(int validationTimeToLive) {
361         if (validationTimeToLive <= 0) {
362             throw new IllegalArgumentException("timeToLive must be positive");
363         }
364         this.validationTimeToLive = validationTimeToLive;
365     }
366 
367     /** Sets the server-side time to live
368      * @deprecated Use  {@link #setValidationTimeToLive(int)} instead.
369      * */
370     public void setTimeToLive(int timeToLive) {
371         setValidationTimeToLive(timeToLive);
372     }
373 
374     /** Sets the validation actions to be executed by the interceptor. */
375     public void setValidationActions(String actions) {
376         this.validationActions = actions;
377         try {
378             validationActionsVector = new Vector();
379             validationAction = WSSecurityUtil.decodeAction(actions, validationActionsVector);
380         }
381         catch (WSSecurityException ex) {
382             throw new IllegalArgumentException(ex);
383         }
384     }
385 
386     public void setValidationActor(String validationActor) {
387         this.validationActor = validationActor;
388     }
389 
390     /**
391      * Sets the {@link org.apache.ws.security.WSPasswordCallback} handler to use when validating messages.
392      *
393      * @see #setValidationCallbackHandlers(CallbackHandler[])
394      */
395     public void setValidationCallbackHandler(CallbackHandler callbackHandler) {
396         this.validationCallbackHandler = callbackHandler;
397     }
398 
399     /**
400      * Sets the {@link org.apache.ws.security.WSPasswordCallback} handlers to use when validating messages.
401      *
402      * @see #setValidationCallbackHandler(CallbackHandler)
403      */
404     public void setValidationCallbackHandlers(CallbackHandler[] callbackHandler) {
405         this.validationCallbackHandler = new CallbackHandlerChain(callbackHandler);
406     }
407 
408     /** Sets the Crypto to use to decrypt incoming messages */
409     public void setValidationDecryptionCrypto(Crypto decryptionCrypto) {
410         this.validationDecryptionCrypto = decryptionCrypto;
411     }
412 
413     /** Sets the Crypto to use to verify the signature of incoming messages */
414     public void setValidationSignatureCrypto(Crypto signatureCrypto) {
415         this.validationSignatureCrypto = signatureCrypto;
416     }
417 
418     /** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
419     public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {
420         handler.setOption(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, enableSignatureConfirmation);
421         this.enableSignatureConfirmation = enableSignatureConfirmation;
422     }
423 
424     /** Sets if the generated timestamp header's precision is in milliseconds. */
425     public void setTimestampPrecisionInMilliseconds(boolean timestampPrecisionInMilliseconds) {
426         handler.setOption(WSHandlerConstants.TIMESTAMP_PRECISION, timestampPrecisionInMilliseconds);
427     }
428 
429     /** Sets whether or not timestamp verification is done with the server-side time to live */
430     public void setTimestampStrict(boolean timestampStrict) {
431         this.timestampStrict = timestampStrict;
432     }
433 
434     /**
435      * Enables the <code>mustUnderstand</code> attribute on WS-Security headers on outgoing messages. Default is
436      * <code>true</code>.
437      */
438     public void setSecurementMustUnderstand(boolean securementMustUnderstand) {
439         handler.setOption(WSHandlerConstants.MUST_UNDERSTAND, securementMustUnderstand);
440     }
441 
442     /**
443      * Sets the additional elements in <code>UsernameToken</code>s.
444      * <p/>
445      * The value of this parameter is a list of element names that are added to the UsernameToken. The names of the list
446      * a separated by spaces.
447      * <p/>
448      * The list may contain the names <code>Nonce</code> and <code>Created</code> only (case sensitive). Use this option
449      * if the password type is <code>passwordText</code> and the handler shall add the <code>Nonce</code> and/or
450      * <code>Created</code> elements.
451      */
452     public void setSecurementUsernameTokenElements(String securementUsernameTokenElements) {
453         handler.setOption(WSHandlerConstants.ADD_UT_ELEMENTS, securementUsernameTokenElements);
454     }
455 
456     public void afterPropertiesSet() throws Exception {
457         Assert.isTrue(validationActions != null || securementActions != null,
458                 "validationActions or securementActions are required");
459         if (validationActions != null) {
460             if ((validationAction & WSConstants.UT) != 0) {
461                 Assert.notNull(validationCallbackHandler, "validationCallbackHandler is required");
462             }
463 
464             if ((validationAction & WSConstants.SIGN) != 0) {
465                 Assert.notNull(validationSignatureCrypto, "validationSignatureCrypto is required");
466             }
467         }
468         // securement actions are not to be validated at start up as they could
469         // be configured dynamically via the message context
470 
471         // allow for qualified password types for .Net interoperability
472         securityEngine.getWssConfig().setAllowNamespaceQualifiedPasswordTypes(true);
473     }
474 
475     protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext)
476             throws WsSecuritySecurementException {
477         if (securementAction == WSConstants.NO_SECURITY && !enableSignatureConfirmation) {
478             return;
479         }
480         if (logger.isDebugEnabled()) {
481             logger.debug("Securing message [" + soapMessage + "] with actions [" + securementActions + "]");
482         }
483         RequestData requestData = initializeRequestData(messageContext);
484 
485         Document envelopeAsDocument = toDocument(soapMessage, messageContext);
486         try {
487             // In case on signature confirmation with no other securement
488             // action, we need to pass an empty securementActionsVector to avoid
489             // NPE
490             if (securementAction == WSConstants.NO_SECURITY) {
491                 securementActionsVector = new Vector(0);
492             }
493 
494             handler.doSenderAction(securementAction, envelopeAsDocument, requestData, securementActionsVector, false);
495         }
496         catch (WSSecurityException ex) {
497             throw new Wss4jSecuritySecurementException(ex.getMessage(), ex);
498         }
499 
500         replaceMessage(soapMessage, envelopeAsDocument);
501     }
502 
503     /** Creates and initializes a request data */
504     private RequestData initializeRequestData(MessageContext messageContext) {
505         RequestData requestData = new RequestData();
506         requestData.setMsgContext(messageContext);
507 
508         // reads securementUsername first from the context then from the
509         // property
510         String contextUsername = (String) messageContext.getProperty(SECUREMENT_USER_PROPERTY_NAME);
511         if (StringUtils.hasLength(contextUsername)) {
512             requestData.setUsername(contextUsername);
513         }
514         else {
515             requestData.setUsername(securementUsername);
516         }
517 
518         requestData.setTimeToLive(securementTimeToLive);
519 
520         return requestData;
521     }
522 
523     protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
524             throws WsSecurityValidationException {
525         if (logger.isDebugEnabled()) {
526             logger.debug("Validating message [" + soapMessage + "] with actions [" + validationActions + "]");
527         }
528 
529         if (validationAction == WSConstants.NO_SECURITY) {
530             return;
531         }
532 
533         Document envelopeAsDocument = toDocument(soapMessage, messageContext);
534 
535         // Header processing
536 
537         try {
538             Vector results = securityEngine
539                     .processSecurityHeader(envelopeAsDocument, validationActor, validationCallbackHandler,
540                             validationSignatureCrypto, validationDecryptionCrypto);
541 
542             // Results verification
543             if (CollectionUtils.isEmpty(results)) {
544                 throw new Wss4jSecurityValidationException("No WS-Security header found");
545             }
546 
547             checkResults(results, validationActionsVector);
548 
549             // puts the results in the context
550             // useful for Signature Confirmation
551             updateContextWithResults(messageContext, results);
552 
553             verifyCertificateTrust(results);
554 
555             verifyTimestamp(results);
556 
557             processPrincipal(results);
558         }
559         catch (WSSecurityException ex) {
560             throw new Wss4jSecurityValidationException(ex.getMessage(), ex);
561         }
562 
563         replaceMessage(soapMessage, envelopeAsDocument);
564 
565         soapMessage.getEnvelope().getHeader().removeHeaderElement(WS_SECURITY_NAME);
566     }
567 
568     /**
569      * Checks whether the received headers match the configured validation actions. Subclasses could override this method
570      * for custom verification behavior.
571      * @param results the results of the validation function
572      * @param validationActionsVector the decoded validation actions
573      * @throws Wss4jSecurityValidationException if the results are deemed invalid
574      */
575     protected void checkResults(Vector results, Vector validationActionsVector)
576             throws Wss4jSecurityValidationException {
577         if (!handler.checkReceiverResultsAnyOrder(results, validationActionsVector)) {
578             throw new Wss4jSecurityValidationException("Security processing failed (actions mismatch)");
579         }
580     }
581 
582     /**
583      * Puts the results of WS-Security headers processing in the message context. Some actions like Signature
584      * Confirmation require this.
585      */
586     private void updateContextWithResults(MessageContext messageContext, Vector results) {
587         Vector handlerResults;
588         if ((handlerResults = (Vector) messageContext.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) {
589             handlerResults = new Vector();
590             messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
591         }
592         WSHandlerResult rResult = new WSHandlerResult(validationActor, results);
593         handlerResults.add(0, rResult);
594         messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
595     }
596 
597     /** Verifies the trust of a certificate. */
598     protected void verifyCertificateTrust(Vector results) throws WSSecurityException {
599         RequestData requestData = new RequestData();
600         requestData.setSigCrypto(validationSignatureCrypto);
601         WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN);
602 
603         if (actionResult != null) {
604             X509Certificate returnCert = (X509Certificate) actionResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE)
605                     ;
606             if (!handler.verifyTrust(returnCert, requestData)) {
607                 throw new Wss4jSecurityValidationException("The certificate used for the signature is not trusted");
608             }
609         }
610     }
611 
612     /** Verifies the timestamp. */
613     protected void verifyTimestamp(Vector results) throws WSSecurityException {
614         WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.TS);
615 
616         if (actionResult != null) {
617             Timestamp timestamp = (Timestamp) actionResult.get(WSSecurityEngineResult.TAG_TIMESTAMP);
618             if (timestamp != null && timestampStrict) {
619                 if (!handler.verifyTimestamp(timestamp, validationTimeToLive)) {
620                     throw new Wss4jSecurityValidationException("Invalid timestamp : " + timestamp.getID());
621                 }
622             }
623         }
624     }
625 
626     private void processPrincipal(Vector results) {
627         WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.UT);
628 
629         if (actionResult != null) {
630             Principal principal = (Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
631             if (principal != null && principal instanceof WSUsernameTokenPrincipal) {
632                 WSUsernameTokenPrincipal usernameTokenPrincipal = (WSUsernameTokenPrincipal) principal;
633                 UsernameTokenPrincipalCallback callback = new UsernameTokenPrincipalCallback(usernameTokenPrincipal);
634                 try {
635                     validationCallbackHandler.handle(new Callback[]{callback});
636                 }
637                 catch (IOException ex) {
638                     logger.warn("Principal callback resulted in IOException", ex);
639                 }
640                 catch (UnsupportedCallbackException ex) {
641                     // ignore
642                 }
643             }
644         }
645     }
646 
647     /** Converts the given {@link SoapMessage} into a {@link Document}. */
648     private Document toDocument(SoapMessage soapMessage, MessageContext messageContext) {
649         if (soapMessage instanceof SaajSoapMessage) {
650             SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage;
651             // return saajSoapMessage.getSaajMessage().getSOAPPart(); // does not work, see SWS-345
652             Assert.isInstanceOf(DefaultMessageContext.class, messageContext);
653             DefaultMessageContext defaultMessageContext = (DefaultMessageContext) messageContext;
654             Assert.isInstanceOf(SaajSoapMessageFactory.class, defaultMessageContext.getMessageFactory());
655             MessageFactory messageFactory =
656                     ((SaajSoapMessageFactory) defaultMessageContext.getMessageFactory()).getMessageFactory();
657             try {
658                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
659                 saajSoapMessage.writeTo(bos);
660                 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
661                 javax.xml.soap.SOAPMessage saajMessage =
662                         messageFactory.createMessage(saajSoapMessage.getSaajMessage().getMimeHeaders(), bis);
663                 saajSoapMessage.setSaajMessage(saajMessage);
664                 return saajMessage.getSOAPPart();
665             }
666             catch (Exception ex) {
667                 throw new SaajSoapMessageException("Could not save changes", ex);
668             }
669         }
670         else if (soapMessage instanceof AxiomSoapMessage) {
671             AxiomSoapMessage axiomMessage = (AxiomSoapMessage) soapMessage;
672             return AxiomUtils.toDocument(axiomMessage.getAxiomMessage().getSOAPEnvelope());
673         }
674         else {
675             throw new IllegalArgumentException("Message type not supported [" + soapMessage + "]");
676         }
677     }
678 
679     /**
680      * Replaces the contents of the given {@link SoapMessage} with that of the document parameter. Only required when
681      * using Axiom, since the document returned by {@link #toDocument} is live for a {@link SaajSoapMessage}.
682      */
683     private void replaceMessage(SoapMessage soapMessage, Document envelope) {
684         if (soapMessage instanceof AxiomSoapMessage) {
685             // construct a new Axiom message with the processed envelope
686             AxiomSoapMessage axiomMessage = (AxiomSoapMessage) soapMessage;
687             // save the Soap Action
688             String soapAction = axiomMessage.getSoapAction();
689             SOAPEnvelope envelopeFromDOMDocument = AxiomUtils.toEnvelope(envelope);
690             SOAPFactory factory = (SOAPFactory) axiomMessage.getAxiomMessage().getOMFactory();
691             SOAPMessage newMessage = factory.createSOAPMessage();
692             newMessage.setSOAPEnvelope(envelopeFromDOMDocument);
693 
694             // replace the Axiom message
695             axiomMessage.setAxiomMessage(newMessage);
696             // restore the Soap Action
697             axiomMessage.setSoapAction(soapAction);
698         }
699     }
700 
701     protected void cleanUp() {
702         if (validationCallbackHandler != null) {
703             try {
704                 CleanupCallback cleanupCallback = new CleanupCallback();
705                 validationCallbackHandler.handle(new Callback[]{cleanupCallback});
706             }
707             catch (IOException ex) {
708                 logger.warn("Cleanup callback resulted in IOException", ex);
709             }
710             catch (UnsupportedCallbackException ex) {
711                 // ignore
712             }
713         }
714     }
715 }