-
Notifications
You must be signed in to change notification settings - Fork 311
/
Wss4jSecurityInterceptor.java
925 lines (776 loc) · 33.1 KB
/
Wss4jSecurityInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
/*
* Copyright 2005-2022 the original author or authors.
*
* Licensed 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.springframework.ws.soap.security.wss4j2;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.ConfigurationConstants;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.principal.WSUsernameTokenPrincipalImpl;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.engine.WSSConfig;
import org.apache.wss4j.dom.engine.WSSecurityEngine;
import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
import org.apache.wss4j.dom.handler.HandlerAction;
import org.apache.wss4j.dom.handler.RequestData;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.dom.message.token.Timestamp;
import org.apache.wss4j.dom.util.WSSecurityUtil;
import org.apache.wss4j.dom.validate.Credential;
import org.apache.wss4j.dom.validate.SignatureTrustValidator;
import org.apache.wss4j.dom.validate.TimestampValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor;
import org.springframework.ws.soap.security.WsSecuritySecurementException;
import org.springframework.ws.soap.security.WsSecurityValidationException;
import org.springframework.ws.soap.security.callback.CallbackHandlerChain;
import org.springframework.ws.soap.security.callback.CleanupCallback;
import org.springframework.ws.soap.security.wss4j2.callback.UsernameTokenPrincipalCallback;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
/**
* A WS-Security endpoint interceptor based on Apache's WSS4J. This interceptor supports messages created by the
* {@link org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the
* {@link org.springframework.ws.soap.saaj.SaajSoapMessageFactory}.
* <p>
* The validation and securement actions executed by this interceptor are configured via {@code validationActions} and
* {@code securementActions} properties, respectively. Actions should be passed as a space-separated strings.
* <p>
* Valid <strong>validation</strong> actions are: <blockquote>
* <table>
* <tr>
* <th>Validation action</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>{@code UsernameToken}</td>
* <td>Validates username token</td>
* </tr>
* <tr>
* <td>{@code Timestamp}</td>
* <td>Validates the timestamp</td>
* </tr>
* <tr>
* <td>{@code Encrypt}</td>
* <td>Decrypts the message</td>
* </tr>
* <tr>
* <td>{@code Signature}</td>
* <td>Validates the signature</td>
* </tr>
* <tr>
* <td>{@code NoSecurity}</td>
* <td>No action performed</td>
* </tr>
* </table>
* </blockquote>
* <p>
* <strong>Securement</strong> actions are: <blockquote>
* <table>
* <tr>
* <th>Securement action</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>{@code UsernameToken}</td>
* <td>Adds a username token</td>
* </tr>
* <tr>
* <td>{@code UsernameTokenSignature}</td>
* <td>Adds a username token and a signature username token secret key</td>
* </tr>
* <tr>
* <td>{@code Timestamp}</td>
* <td>Adds a timestamp</td>
* </tr>
* <tr>
* <td>{@code Encrypt}</td>
* <td>Encrypts the response</td>
* </tr>
* <tr>
* <td>{@code Signature}</td>
* <td>Signs the response</td>
* </tr>
* <tr>
* <td>{@code NoSecurity}</td>
* <td>No action performed</td>
* </tr>
* </table>
* </blockquote>
* <p>
* The order of the actions that the client performed to secure the messages is significant and is enforced by the
* interceptor.
*
* @author Tareq Abed Rabbo
* @author Arjen Poutsma
* @author Greg Turnquist
* @author Jamin Hitchcock
* @author Rob Leland
* @author Lars Uffmann
* @author Andreas Winter
* @see <a href="http://ws.apache.org/wss4j/">Apache WSS4J 2.0</a>
* @since 2.3.0
*/
public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor implements InitializingBean {
public static final String SECUREMENT_USER_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementUser";
public static final String SECUREMENT_PASSWORD_PROPERTY_NAME = "Wss4jSecurityInterceptor.securementPassword";
private String securementActions;
private String securementUsername;
private CallbackHandler validationCallbackHandler;
private String validationActions;
private List<Integer> validationActionsVector;
private String validationActor;
private Crypto validationDecryptionCrypto;
private Crypto validationSignatureCrypto;
private boolean timestampStrict = true;
private boolean enableSignatureConfirmation;
private int validationTimeToLive = 300;
private int securementTimeToLive = 300;
private int futureTimeToLive = 60;
private WSSConfig wssConfig;
private final Wss4jHandler handler = new Wss4jHandler();
private final WSSecurityEngine securityEngine;
private boolean enableRevocation;
private boolean bspCompliant;
private boolean addInclusivePrefixes = true;
private boolean securementUseDerivedKey;
private CallbackHandler samlCallbackHandler;
// Allow RSA 15 to maintain default behavior
private boolean allowRSA15KeyTransportAlgorithm = true;
// To maintain same behavior as default, this flag is set to true
private boolean removeSecurityHeader = true;
private List<Pattern> signatureSubjectDnPatterns = emptyList();
/**
* Create a {@link WSSecurityEngine} by default.
*/
public Wss4jSecurityInterceptor() {
this.securityEngine = new WSSecurityEngine();
}
/**
* Inject a customize {@link WSSecurityEngine}.
*
* @param securityEngine
*/
public Wss4jSecurityInterceptor(WSSecurityEngine securityEngine) {
this.securityEngine = securityEngine;
}
public void setSecurementActions(String securementActions) {
this.securementActions = securementActions;
}
/**
* The actor name of the {@code wsse:Security} header.
* <p>
* If this parameter is omitted, the actor name is not set.
* <p>
* The value of the actor or role has to match the receiver's setting or may contain standard values.
*/
public void setSecurementActor(String securementActor) {
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
}
/**
* Defines whether to use a single certificate or a whole certificate chain when constructing
* a BinarySecurityToken used for direct reference in signature.
* The default is "true", meaning that only a single certificate is used.
*/
public void setSecurementSignatureSingleCertificate(boolean useSingleCertificate) {
handler.setOption(WSHandlerConstants.USE_SINGLE_CERTIFICATE, useSingleCertificate);
}
public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
}
/**
* Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type
* {@code IssuerSerial}. For possible encryption key identifier types refer to
* {@link org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For encryption {@code IssuerSerial},
* {@code X509KeyIdentifier}, {@code DirectReference}, {@code Thumbprint}, {@code SKIKeyIdentifier}, and
* {@code EmbeddedKeyName} are valid only.
*/
public void setSecurementEncryptionKeyIdentifier(String securementEncryptionKeyIdentifier) {
handler.setOption(WSHandlerConstants.ENC_KEY_ID, securementEncryptionKeyIdentifier);
}
/**
* Defines which algorithm to use to encrypt the generated symmetric key. Currently WSS4J supports
* {@link WSConstants#KEYTRANSPORT_RSA15} and {@link WSConstants#KEYTRANSPORT_RSAOEP}.
*/
public void setSecurementEncryptionKeyTransportAlgorithm(String securementEncryptionKeyTransportAlgorithm) {
handler.setOption(WSHandlerConstants.ENC_KEY_TRANSPORT, securementEncryptionKeyTransportAlgorithm);
}
/**
* Property to define which parts of the request shall be encrypted.
* <p>
* The value of this property is a list of semicolon separated element names that identify the elements to encrypt. An
* encryption mode specifier and a namespace identification, each inside a pair of curly brackets, may precede each
* element name.
* <p>
* The encryption mode specifier is either {@code {Content}} or {@code {Element}}. Please refer to the W3C XML
* Encryption specification about the differences between Element and Content encryption. The encryption mode defaults
* to {@code Content} if it is omitted. Example of a list:
*
* <pre>
* <property name="securementEncryptionParts"
* value="{Content}{http://example.org/paymentv2}CreditCard;
* {Element}{}UserName" />
* </pre>
*
* The first entry of the list identifies the element {@code CreditCard} in the namespace
* {@code http://example.org/paymentv2}, and will encrypt its content. Be aware that the element name, the namespace
* identifier, and the encryption modifier are case sensitive.
* <p>
* The encryption modifier and the namespace identifier can be omitted. In this case the encryption mode defaults to
* {@code Content} and the namespace is set to the SOAP namespace.
* <p>
* An empty encryption mode defaults to {@code Content}, an empty namespace identifier defaults to the SOAP namespace.
* The second line of the example defines {@code Element} as encryption mode for an {@code UserName} element in the
* SOAP namespace.
* <p>
* To specify an element without a namespace use the string {@code Null} as the namespace name (this is a case
* sensitive string)
* <p>
* If no list is specified, the handler encrypts the SOAP Body in {@code Content} mode by default.
*/
public void setSecurementEncryptionParts(String securementEncryptionParts) {
handler.setOption(WSHandlerConstants.ENCRYPTION_PARTS, securementEncryptionParts);
}
/**
* Defines which symmetric encryption algorithm to use. WSS4J supports the following alorithms:
* {@link WSConstants#TRIPLE_DES}, {@link WSConstants#AES_128}, {@link WSConstants#AES_256}, and
* {@link WSConstants#AES_192}. Except for AES 192 all of these algorithms are required by the XML Encryption
* specification.
*/
public void setSecurementEncryptionSymAlgorithm(String securementEncryptionSymAlgorithm) {
this.handler.setOption(WSHandlerConstants.ENC_SYM_ALGO, securementEncryptionSymAlgorithm);
}
/**
* The user's name for encryption.
* <p>
* The encryption functions uses the public key of this user's certificate to encrypt the generated symmetric key.
* <p>
* If this parameter is not set, then the encryption function falls back to the
* {@link org.apache.ws.security.handler.WSHandlerConstants#USER} parameter to get the certificate.
* <p>
* If <b>only</b> encryption of the SOAP body data is requested, it is recommended to use this parameter to define the
* username. The application can then use the standard user and password functions (see example at
* {@link org.apache.ws.security.handler.WSHandlerConstants#USER} to enable HTTP authentication functions.
* <p>
* Encryption only does not authenticate a user / sender, therefore it does not need a password.
* <p>
* Placing the username of the encryption certificate in the configuration file is not a security risk, because the
* public key of that certificate is used only.
*/
public void setSecurementEncryptionUser(String securementEncryptionUser) {
handler.setOption(WSHandlerConstants.ENCRYPTION_USER, securementEncryptionUser);
}
public void setSecurementPassword(String securementPassword) {
this.handler.setSecurementPassword(securementPassword);
}
/**
* Specific parameter for UsernameToken action to define the encoding of the passowrd.
* <p>
* The parameter can be set to either {@link WSConstants#PW_DIGEST} or to {@link WSConstants#PW_TEXT}.
* <p>
* The default setting is PW_DIGEST.
*/
public void setSecurementPasswordType(String securementUsernameTokenPasswordType) {
handler.setOption(WSHandlerConstants.PASSWORD_TYPE, securementUsernameTokenPasswordType);
}
/**
* Defines which signature algorithm to use.
*
* @see WSConstants#RSA
* @see WSConstants#DSA
*/
public void setSecurementSignatureAlgorithm(String securementSignatureAlgorithm) {
handler.setOption(WSHandlerConstants.SIG_ALGO, securementSignatureAlgorithm);
}
/**
* Defines which signature digest algorithm to use.
*/
public void setSecurementSignatureDigestAlgorithm(String digestAlgorithm) {
handler.setOption(WSHandlerConstants.SIG_DIGEST_ALGO, digestAlgorithm);
}
public void setSecurementSignatureCrypto(Crypto securementSignatureCrypto) {
handler.setSecurementSignatureCrypto(securementSignatureCrypto);
}
/**
* Defines which key identifier type to use. The WS-Security specifications recommends to use the identifier type
* {@code IssuerSerial}. For possible signature key identifier types refer to
* {@link org.apache.ws.security.handler.WSHandlerConstants#keyIdentifier}. For signature {@code IssuerSerial} and
* {@code DirectReference} are valid only.
*/
public void setSecurementSignatureKeyIdentifier(String securementSignatureKeyIdentifier) {
handler.setOption(WSHandlerConstants.SIG_KEY_ID, securementSignatureKeyIdentifier);
}
/**
* Property to define which parts of the request shall be signed.
* <p>
* Refer to {@link #setSecurementEncryptionParts(String)} for a detailed description of the format of the value
* string.
* <p>
* If this property is not specified the handler signs the SOAP Body by default.
* <p>
* The WS Security specifications define several formats to transfer the signature tokens (certificates) or references
* to these tokens. Thus, the plain element name {@code Token} signs the token and takes care of the different
* formats.
* <p>
* To sign the SOAP body <b>and</b> the signature token the value of this parameter must contain:
*
* <pre>
* <property name="securementSignatureParts"
* value="{}{http://schemas.xmlsoap.org/soap/envelope/}Body; Token" />
* </pre>
*
* To specify an element without a namespace use the string {@code Null} as the namespace name (this is a case
* sensitive string)
* <p>
* If there is no other element in the request with a local name of {@code Body} then the SOAP namespace identifier
* can be empty ({@code {}}).
*/
public void setSecurementSignatureParts(String securementSignatureParts) {
handler.setOption(WSHandlerConstants.SIGNATURE_PARTS, securementSignatureParts);
}
/**
* The user's name for signature.
* <p>
* This name is used as the alias name in the keystore to get user's certificate and private key to perform signing.
* <p>
* If this parameter is not set, then the signature function falls back to the alias specified by
* {@link #setSecurementUsername(String)}.
*/
public void setSecurementSignatureUser(String securementSignatureUser) {
handler.setOption(WSHandlerConstants.SIGNATURE_USER, securementSignatureUser);
}
/** Sets the username for securement username token or/and the alias of the private key for securement signature */
public void setSecurementUsername(String securementUsername) {
this.securementUsername = securementUsername;
}
/** Sets the time to live on the outgoing message */
public void setSecurementTimeToLive(int securementTimeToLive) {
if (securementTimeToLive <= 0) {
throw new IllegalArgumentException("timeToLive must be positive");
}
this.securementTimeToLive = securementTimeToLive;
}
/**
* Enables the derivation of keys as per the UsernameTokenProfile 1.1 spec. Default is {@code true}.
*/
public void setSecurementUseDerivedKey(boolean securementUseDerivedKey) {
this.securementUseDerivedKey = securementUseDerivedKey;
}
/**
* Sets the SAML Callback used for generating SAML tokens.
*
* @param samlCallback
*/
public void setSecurementSamlCallbackHandler(CallbackHandler samlCallbackHandler) {
this.samlCallbackHandler = samlCallbackHandler;
}
/** Sets the server-side time to live */
public void setValidationTimeToLive(int validationTimeToLive) {
if (validationTimeToLive <= 0) {
throw new IllegalArgumentException("timeToLive must be positive");
}
this.validationTimeToLive = validationTimeToLive;
}
/** Sets the validation actions to be executed by the interceptor. */
public void setValidationActions(String actions) {
this.validationActions = actions;
try {
validationActionsVector = WSSecurityUtil.decodeAction(actions);
} catch (WSSecurityException ex) {
throw new IllegalArgumentException(ex);
}
}
public void setValidationActor(String validationActor) {
this.validationActor = validationActor;
}
/**
* Sets the {@link CallbackHandler} to use when validating messages.
*
* @see #setValidationCallbackHandlers(CallbackHandler[])
*/
public void setValidationCallbackHandler(CallbackHandler callbackHandler) {
this.validationCallbackHandler = callbackHandler;
}
/**
* Sets the {@link CallbackHandler}s to use when validating messages.
*
* @see #setValidationCallbackHandler(CallbackHandler)
*/
public void setValidationCallbackHandlers(CallbackHandler[] callbackHandler) {
this.validationCallbackHandler = new CallbackHandlerChain(callbackHandler);
}
/** Sets the Crypto to use to decrypt incoming messages */
public void setValidationDecryptionCrypto(Crypto decryptionCrypto) {
this.validationDecryptionCrypto = decryptionCrypto;
}
/** Sets the Crypto to use to verify the signature of incoming messages */
public void setValidationSignatureCrypto(Crypto signatureCrypto) {
this.validationSignatureCrypto = signatureCrypto;
}
/**
* Certificate constraints which will be applied to the subject DN of the certificate used for
* signature validation, after trust verification of the certificate chain associated with the
* certificate.
*
* @param patterns A list of regex patterns which will be applied to the subject DN.
*
* @see <a href="https://ws.apache.org/wss4j/config.html">WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS</a>
*/
public void setValidationSubjectDnConstraints(List<Pattern> patterns) {
signatureSubjectDnPatterns = patterns;
}
/** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {
handler.setOption(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, enableSignatureConfirmation);
this.enableSignatureConfirmation = enableSignatureConfirmation;
}
/** Sets if the generated timestamp header's precision is in milliseconds. */
public void setTimestampPrecisionInMilliseconds(boolean timestampPrecisionInMilliseconds) {
handler.setOption(WSHandlerConstants.TIMESTAMP_PRECISION, timestampPrecisionInMilliseconds);
}
/** Sets whether or not timestamp verification is done with the server-side time to live */
public void setTimestampStrict(boolean timestampStrict) {
this.timestampStrict = timestampStrict;
}
/**
* Enables the {@code mustUnderstand} attribute on WS-Security headers on outgoing messages. Default is {@code true}.
*/
public void setSecurementMustUnderstand(boolean securementMustUnderstand) {
handler.setOption(WSHandlerConstants.MUST_UNDERSTAND, securementMustUnderstand);
}
/**
* Sets whether or not a {@code Nonce} element is added to the {@code UsernameToken}s. Default is {@code false}.
*/
public void setSecurementUsernameTokenNonce(boolean securementUsernameTokenNonce) {
handler.setOption(ConfigurationConstants.ADD_USERNAMETOKEN_NONCE, securementUsernameTokenNonce);
}
/**
* Sets whether or not a {@code Created} element is added to the {@code UsernameToken}s. Default is {@code false}.
*/
public void setSecurementUsernameTokenCreated(boolean securementUsernameTokenCreated) {
handler.setOption(ConfigurationConstants.ADD_USERNAMETOKEN_CREATED, securementUsernameTokenCreated);
}
/**
* Sets the web service specification settings.
* <p>
* The default settings follow the latest OASIS and changing anything might violate the OASIS specs.
*
* @param config web service security configuration or {@code null} to use default settings
*/
public void setWssConfig(WSSConfig config) {
securityEngine.setWssConfig(config);
wssConfig = config;
}
/**
* Set whether to enable CRL checking or not when verifying trust in a certificate.
*/
public void setEnableRevocation(boolean enableRevocation) {
this.enableRevocation = enableRevocation;
}
/**
* Set the WS-I Basic Security Profile compliance mode. Default is {@code true}.
*/
public void setBspCompliant(boolean bspCompliant) {
this.handler.setOption(WSHandlerConstants.IS_BSP_COMPLIANT, bspCompliant);
this.bspCompliant = bspCompliant;
}
/**
* Sets whether to add an InclusiveNamespaces PrefixList as a CanonicalizationMethod child when generating Signatures
* using WSConstants.C14N_EXCL_OMIT_COMMENTS. Default is {@code true}.
*/
public void setAddInclusivePrefixes(boolean addInclusivePrefixes) {
this.handler.setOption(WSHandlerConstants.ADD_INCLUSIVE_PREFIXES, addInclusivePrefixes);
this.addInclusivePrefixes = addInclusivePrefixes;
}
/**
* Sets whether the RSA 1.5 key transport algorithm is allowed.
*/
public void setAllowRSA15KeyTransportAlgorithm(boolean allow) {
this.allowRSA15KeyTransportAlgorithm = allow;
}
/**
* Sets the time in seconds in the future within which the Created time of an incoming Timestamp is valid. The default
* is 60 seconds.
*/
public void setFutureTimeToLive(int futureTimeToLive) {
if (futureTimeToLive <= 0) {
throw new IllegalArgumentException("futureTimeToLive must be positive");
}
this.futureTimeToLive = futureTimeToLive;
}
public boolean getRemoveSecurityHeader() {
return removeSecurityHeader;
}
public void setRemoveSecurityHeader(boolean removeSecurityHeader) {
this.removeSecurityHeader = removeSecurityHeader;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.isTrue(validationActions != null || securementActions != null,
"validationActions or securementActions are required");
if (validationActions != null) {
if (validationActionsVector.contains(WSConstants.UT)) {
Assert.notNull(validationCallbackHandler, "validationCallbackHandler is required");
}
if (validationActionsVector.contains(WSConstants.SIGN)) {
Assert.notNull(validationSignatureCrypto, "validationSignatureCrypto is required");
}
}
// securement actions are not to be validated at start up as they could
// be configured dynamically via the message context
}
@Override
protected void secureMessage(SoapMessage soapMessage, MessageContext messageContext)
throws WsSecuritySecurementException {
List<HandlerAction> securementActionsVector = new ArrayList<HandlerAction>();
try {
securementActionsVector = WSSecurityUtil.decodeHandlerAction(securementActions, wssConfig);
} catch (WSSecurityException ex) {
throw new Wss4jSecuritySecurementException(ex.getMessage(), ex);
}
if (securementActionsVector.isEmpty() && !enableSignatureConfirmation) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Securing message [" + soapMessage + "] with actions [" + securementActions + "]");
}
RequestData requestData = initializeRequestData(messageContext);
Document envelopeAsDocument = soapMessage.getDocument();
try {
handler.doSenderAction(envelopeAsDocument, requestData, securementActionsVector, false);
} catch (WSSecurityException ex) {
throw new Wss4jSecuritySecurementException(ex.getMessage(), ex);
}
soapMessage.setDocument(envelopeAsDocument);
}
/**
* Creates and initializes a request data for the given message context.
*
* @param messageContext the message context
* @return the request data
*/
protected RequestData initializeRequestData(MessageContext messageContext) {
RequestData requestData = new RequestData();
requestData.setMsgContext(messageContext);
// reads securementUsername first from the context then from the property
String contextUsername = (String) messageContext.getProperty(SECUREMENT_USER_PROPERTY_NAME);
if (StringUtils.hasLength(contextUsername)) {
requestData.setUsername(contextUsername);
} else {
requestData.setUsername(securementUsername);
}
requestData.setTimeStampTTL(securementTimeToLive);
requestData.setUseDerivedKeyForMAC(securementUseDerivedKey);
requestData.setWssConfig(wssConfig);
messageContext.setProperty(WSHandlerConstants.TTL_TIMESTAMP, Integer.toString(securementTimeToLive));
if (this.samlCallbackHandler != null) {
messageContext.setProperty(WSHandlerConstants.SAML_CALLBACK_REF, this.samlCallbackHandler);
}
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
return requestData;
}
/**
* Creates and initializes a request data for the given message context.
*
* @param messageContext the message context
* @return the request data
*/
protected RequestData initializeValidationRequestData(MessageContext messageContext) {
RequestData requestData = new RequestData();
requestData.setMsgContext(messageContext);
requestData.setWssConfig(wssConfig);
requestData.setDecCrypto(validationDecryptionCrypto);
requestData.setSigVerCrypto(validationSignatureCrypto);
requestData.setCallbackHandler(validationCallbackHandler);
messageContext.setProperty(WSHandlerConstants.TIMESTAMP_STRICT, timestampStrict);
messageContext.setProperty(WSHandlerConstants.TTL_TIMESTAMP, Integer.toString(validationTimeToLive));
messageContext.setProperty(WSHandlerConstants.TTL_FUTURE_TIMESTAMP, Integer.toString(futureTimeToLive));
requestData.setTimeStampStrict(timestampStrict);
requestData.setTimeStampTTL(validationTimeToLive);
requestData.setTimeStampFutureTTL(futureTimeToLive);
requestData.setAllowRSA15KeyTransportAlgorithm(allowRSA15KeyTransportAlgorithm);
requestData.setDisableBSPEnforcement(!bspCompliant);
if (requestData.getBSPEnforcer() != null) {
requestData.getBSPEnforcer().setDisableBSPRules(!bspCompliant);
}
requestData.setAddInclusivePrefixes(addInclusivePrefixes);
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);
requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
return requestData;
}
@Override
protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
throws WsSecurityValidationException {
if (logger.isDebugEnabled()) {
logger.debug("Validating message [" + soapMessage + "] with actions [" + validationActions + "]");
}
if (CollectionUtils.isEmpty(validationActionsVector)) {
return;
}
Document envelopeAsDocument = soapMessage.getDocument();
// Header processing
try {
RequestData validationData = initializeValidationRequestData(messageContext);
String actor = validationActor;
if (actor == null) {
actor = "";
}
Element elem = WSSecurityUtil.getSecurityHeader(envelopeAsDocument, actor);
WSHandlerResult result = securityEngine.processSecurityHeader(elem, validationData);
// Results verification
if (CollectionUtils.isEmpty(result.getResults())) {
throw new Wss4jSecurityValidationException("No WS-Security header found");
}
checkResults(result.getResults(), validationActionsVector);
// puts the results in the context
// useful for Signature Confirmation
updateContextWithResults(messageContext, result.getResults());
verifyCertificateTrust(result);
verifyTimestamp(result);
processPrincipal(result);
} catch (WSSecurityException ex) {
throw new Wss4jSecurityValidationException(ex.getMessage(), ex);
}
soapMessage.setDocument(envelopeAsDocument);
if (this.getRemoveSecurityHeader()) {
soapMessage.getEnvelope().getHeader().removeHeaderElement(WS_SECURITY_NAME);
}
}
/**
* Checks whether the received headers match the configured validation actions. Subclasses could override this method
* for custom verification behavior.
*
* @param results the results of the validation function
* @param validationActions the decoded validation actions
* @throws Wss4jSecurityValidationException if the results are deemed invalid
*/
protected void checkResults(List<WSSecurityEngineResult> results, List<Integer> validationActions)
throws Wss4jSecurityValidationException {
if (!handler.checkReceiverResultsAnyOrder(results, validationActions)) {
throw new Wss4jSecurityValidationException("Security processing failed (actions mismatch)");
}
}
/**
* Puts the results of WS-Security headers processing in the message context. Some actions like Signature Confirmation
* require this.
*/
@SuppressWarnings("unchecked")
private void updateContextWithResults(MessageContext messageContext, List<WSSecurityEngineResult> results) {
List<WSHandlerResult> handlerResults;
if ((handlerResults = (List<WSHandlerResult>) messageContext
.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) {
handlerResults = new ArrayList<WSHandlerResult>();
messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
}
WSHandlerResult rResult = new WSHandlerResult(validationActor, results,
Collections.<Integer, List<WSSecurityEngineResult>> emptyMap());
handlerResults.add(0, rResult);
messageContext.setProperty(WSHandlerConstants.RECV_RESULTS, handlerResults);
}
/**
* Verifies the trust of a certificate.
*
* @param result
*/
protected void verifyCertificateTrust(WSHandlerResult result) throws WSSecurityException {
List<WSSecurityEngineResult> results = result.getActionResults().get(WSConstants.SIGN);
if (!CollectionUtils.isEmpty(results)) {
WSSecurityEngineResult actionResult = results.get(0);
X509Certificate returnCert = (X509Certificate) actionResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
Credential credential = new Credential();
credential.setCertificates(new X509Certificate[] { returnCert });
RequestData requestData = new RequestData();
requestData.setSigVerCrypto(validationSignatureCrypto);
requestData.setEnableRevocation(enableRevocation);
SignatureTrustValidator validator = new SignatureTrustValidator();
validator.validate(credential, requestData);
}
}
/**
* Verifies the timestamp.
*
* @param result
*/
protected void verifyTimestamp(WSHandlerResult result) throws WSSecurityException {
List<WSSecurityEngineResult> results = result.getActionResults().get(WSConstants.TS);
if (!CollectionUtils.isEmpty(results)) {
WSSecurityEngineResult actionResult = results.get(0);
Timestamp timestamp = (Timestamp) actionResult.get(WSSecurityEngineResult.TAG_TIMESTAMP);
if (timestamp != null && timestampStrict) {
Credential credential = new Credential();
credential.setTimestamp(timestamp);
RequestData requestData = new RequestData();
requestData.setWssConfig(WSSConfig.getNewInstance());
requestData.setTimeStampTTL(validationTimeToLive);
requestData.setTimeStampStrict(timestampStrict);
requestData.setTimeStampFutureTTL(futureTimeToLive);
TimestampValidator validator = new TimestampValidator();
validator.validate(credential, requestData);
}
}
}
private void processPrincipal(WSHandlerResult result) {
List<WSSecurityEngineResult> results = result.getActionResults().get(WSConstants.UT);
if (!CollectionUtils.isEmpty(results)) {
WSSecurityEngineResult actionResult = results.get(0);
Principal principal = (Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL);
if (principal instanceof WSUsernameTokenPrincipalImpl usernameTokenPrincipal) {
UsernameTokenPrincipalCallback callback = new UsernameTokenPrincipalCallback(usernameTokenPrincipal);
try {
validationCallbackHandler.handle(new Callback[] { callback });
} catch (IOException ex) {
logger.warn("Principal callback resulted in IOException", ex);
} catch (UnsupportedCallbackException ex) {
// ignore
}
}
}
}
@Override
protected void cleanUp() {
if (validationCallbackHandler != null) {
try {
CleanupCallback cleanupCallback = new CleanupCallback();
validationCallbackHandler.handle(new Callback[] { cleanupCallback });
} catch (IOException ex) {
logger.warn("Cleanup callback resulted in IOException", ex);
} catch (UnsupportedCallbackException ex) {
// ignore
}
}
}
}