1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.wss4j.stax.impl.processor.output;
20
21 import java.security.Key;
22 import java.security.PrivateKey;
23 import java.security.cert.X509Certificate;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.List;
27
28 import javax.crypto.spec.SecretKeySpec;
29 import javax.xml.namespace.QName;
30 import javax.xml.parsers.ParserConfigurationException;
31 import javax.xml.stream.XMLStreamException;
32
33 import org.apache.wss4j.common.crypto.CryptoType;
34 import org.apache.wss4j.common.ext.WSPasswordCallback;
35 import org.apache.wss4j.common.ext.WSSecurityException;
36 import org.apache.wss4j.common.saml.OpenSAMLUtil;
37 import org.apache.wss4j.common.saml.SAMLCallback;
38 import org.apache.wss4j.common.saml.SAMLUtil;
39 import org.apache.wss4j.common.saml.SamlAssertionWrapper;
40 import org.apache.wss4j.common.saml.bean.KeyInfoBean;
41 import org.apache.wss4j.common.saml.bean.SubjectBean;
42 import org.apache.wss4j.stax.ext.WSSConfigurationException;
43 import org.apache.wss4j.stax.ext.WSSConstants;
44 import org.apache.wss4j.stax.ext.WSSSecurePart;
45 import org.apache.wss4j.stax.ext.WSSSecurityProperties;
46 import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
47 import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
48 import org.apache.wss4j.stax.utils.WSSUtils;
49 import org.apache.xml.security.exceptions.XMLSecurityException;
50 import org.apache.xml.security.stax.config.JCEAlgorithmMapper;
51 import org.apache.xml.security.stax.ext.AbstractOutputProcessor;
52 import org.apache.xml.security.stax.ext.OutputProcessorChain;
53 import org.apache.xml.security.stax.ext.SecurePart;
54 import org.apache.xml.security.stax.ext.XMLSecurityConstants;
55 import org.apache.xml.security.stax.ext.stax.XMLSecAttribute;
56 import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
57 import org.apache.xml.security.stax.impl.securityToken.GenericOutboundSecurityToken;
58 import org.apache.xml.security.stax.impl.util.IDGenerator;
59 import org.apache.xml.security.stax.securityEvent.TokenSecurityEvent;
60 import org.apache.xml.security.stax.securityToken.OutboundSecurityToken;
61 import org.apache.xml.security.stax.securityToken.SecurityTokenProvider;
62 import org.opensaml.saml.common.SAMLVersion;
63 import org.w3c.dom.Document;
64 import org.w3c.dom.Element;
65
66 public class SAMLTokenOutputProcessor extends AbstractOutputProcessor {
67
68 private static final org.slf4j.Logger LOG =
69 org.slf4j.LoggerFactory.getLogger(SAMLTokenOutputProcessor.class);
70
71 public SAMLTokenOutputProcessor() throws XMLSecurityException {
72 super();
73 addBeforeProcessor(BinarySecurityTokenOutputProcessor.class);
74 addBeforeProcessor(WSSSignatureOutputProcessor.class);
75 }
76
77 @Override
78 public void processEvent(XMLSecEvent xmlSecEvent, final OutputProcessorChain outputProcessorChain)
79 throws XMLStreamException, XMLSecurityException {
80
81 try {
82 final SAMLCallback samlCallback = new SAMLCallback();
83 SAMLUtil.doSAMLCallback(((WSSSecurityProperties) getSecurityProperties()).getSamlCallbackHandler(), samlCallback);
84 SamlAssertionWrapper samlAssertionWrapper = new SamlAssertionWrapper(samlCallback);
85
86 if (samlCallback.isSignAssertion()) {
87 samlAssertionWrapper.signAssertion(
88 samlCallback.getIssuerKeyName(),
89 samlCallback.getIssuerKeyPassword(),
90 samlCallback.getIssuerCrypto(),
91 samlCallback.isSendKeyValue(),
92 samlCallback.getCanonicalizationAlgorithm(),
93 samlCallback.getSignatureAlgorithm(),
94 samlCallback.getSignatureDigestAlgorithm()
95 );
96 }
97
98 boolean senderVouches = false;
99 boolean hok = false;
100 List<String> methods = samlAssertionWrapper.getConfirmationMethods();
101 if (methods != null && !methods.isEmpty()) {
102 String confirmMethod = methods.get(0);
103 if (OpenSAMLUtil.isMethodSenderVouches(confirmMethod)) {
104 senderVouches = true;
105 } else if (OpenSAMLUtil.isMethodHolderOfKey(confirmMethod)) {
106 hok = true;
107 }
108 }
109
110 final String securityTokenReferenceId = IDGenerator.generateID(null);
111 final String tokenId = samlAssertionWrapper.getId();
112
113 final FinalSAMLTokenOutputProcessor finalSAMLTokenOutputProcessor;
114
115 XMLSecurityConstants.Action action = getAction();
116 boolean includeSTR = false;
117
118 GenericOutboundSecurityToken securityToken = null;
119
120
121 String sigTokenId =
122 outputProcessorChain.getSecurityContext().get(WSSConstants.PROP_USE_THIS_TOKEN_ID_FOR_SIGNATURE);
123 SecurityTokenProvider<OutboundSecurityToken> signatureTokenProvider = null;
124 if (sigTokenId != null) {
125 signatureTokenProvider =
126 outputProcessorChain.getSecurityContext().getSecurityTokenProvider(sigTokenId);
127 if (signatureTokenProvider != null) {
128 securityToken =
129 (GenericOutboundSecurityToken)signatureTokenProvider.getSecurityToken();
130 }
131 }
132
133 if (WSSConstants.SAML_TOKEN_SIGNED.equals(action) && senderVouches) {
134 includeSTR = true;
135 if (securityToken == null) {
136 securityToken = getSecurityToken(samlCallback, outputProcessorChain);
137 }
138
139 finalSAMLTokenOutputProcessor = new FinalSAMLTokenOutputProcessor(securityToken, samlAssertionWrapper,
140 securityTokenReferenceId, senderVouches, includeSTR);
141 finalSAMLTokenOutputProcessor.setAction(getAction(), getActionOrder());
142
143 securityToken.setProcessor(finalSAMLTokenOutputProcessor);
144
145 } else if (WSSConstants.SAML_TOKEN_SIGNED.equals(action) && hok) {
146
147 final Element ref;
148 if (securityToken != null) {
149 ref = securityToken.getCustomTokenReference();
150 } else {
151 ref = null;
152 }
153
154 finalSAMLTokenOutputProcessor = new FinalSAMLTokenOutputProcessor(null, samlAssertionWrapper,
155 securityTokenReferenceId, senderVouches, includeSTR);
156
157 final SAMLSecurityTokenProvider securityTokenProvider =
158 new SAMLSecurityTokenProvider(samlCallback, (WSSSecurityProperties)getSecurityProperties(),
159 tokenId, ref, finalSAMLTokenOutputProcessor);
160
161
162 TokenSecurityEvent<OutboundSecurityToken> tokenSecurityEvent =
163 new TokenSecurityEvent<OutboundSecurityToken>(WSSecurityEventConstants.SAML_TOKEN) {
164
165 public OutboundSecurityToken getSecurityToken() {
166 try {
167 return securityTokenProvider.getSecurityToken();
168 } catch (XMLSecurityException e) {
169 LOG.debug(e.getMessage(), e);
170 }
171 return null;
172 }
173 };
174 outputProcessorChain.getSecurityContext().registerSecurityEvent(tokenSecurityEvent);
175
176 outputProcessorChain.getSecurityContext().registerSecurityTokenProvider(tokenId, securityTokenProvider);
177 outputProcessorChain.getSecurityContext().put(WSSConstants.PROP_USE_THIS_TOKEN_ID_FOR_SIGNATURE, tokenId);
178 } else if (WSSConstants.SAML_TOKEN_UNSIGNED.equals(getAction())) {
179
180
181
182 QName assertionName = new QName(WSSConstants.NS_SAML2, "Assertion");
183 if (samlAssertionWrapper.getSamlVersion() == SAMLVersion.VERSION_11) {
184 assertionName = new QName(WSSConstants.NS_SAML, "Assertion");
185 }
186
187 Iterator<SecurePart> signaturePartsIterator =
188 securityProperties.getSignatureSecureParts().iterator();
189 while (signaturePartsIterator.hasNext()) {
190 SecurePart securePart = signaturePartsIterator.next();
191 if (samlAssertionWrapper.getId().equals(securePart.getIdToSecure())
192 || assertionName.equals(securePart.getName())) {
193 includeSTR = true;
194 signaturePartsIterator.remove();
195 break;
196 }
197 }
198
199 finalSAMLTokenOutputProcessor = new FinalSAMLTokenOutputProcessor(null, samlAssertionWrapper,
200 securityTokenReferenceId, senderVouches,
201 includeSTR);
202 if (includeSTR) {
203 finalSAMLTokenOutputProcessor.addBeforeProcessor(WSSSignatureOutputProcessor.class);
204 }
205 } else {
206 finalSAMLTokenOutputProcessor = new FinalSAMLTokenOutputProcessor(null, samlAssertionWrapper,
207 securityTokenReferenceId, senderVouches,
208 includeSTR);
209 }
210
211 finalSAMLTokenOutputProcessor.setXMLSecurityProperties(getSecurityProperties());
212 finalSAMLTokenOutputProcessor.setAction(action, getActionOrder());
213 finalSAMLTokenOutputProcessor.init(outputProcessorChain);
214
215 if (includeSTR) {
216 WSSSecurePart securePart =
217 new WSSSecurePart(
218 new QName(WSSConstants.SOAPMESSAGE_NS10_STR_TRANSFORM), SecurePart.Modifier.Element);
219 securePart.setIdToSecure(tokenId);
220 securePart.setIdToReference(securityTokenReferenceId);
221 outputProcessorChain.getSecurityContext().putAsMap(WSSConstants.SIGNATURE_PARTS, tokenId, securePart);
222 }
223
224 } finally {
225 outputProcessorChain.removeProcessor(this);
226 }
227 outputProcessorChain.processEvent(xmlSecEvent);
228 }
229
230 private GenericOutboundSecurityToken getSecurityToken(SAMLCallback samlCallback,
231 OutputProcessorChain outputProcessorChain) throws WSSecurityException {
232 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
233 cryptoType.setAlias(samlCallback.getIssuerKeyName());
234 X509Certificate[] certificates = null;
235 if (samlCallback.getIssuerCrypto() != null) {
236 certificates = samlCallback.getIssuerCrypto().getX509Certificates(cryptoType);
237 }
238 if (certificates == null) {
239 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
240 "empty",
241 new Object[] {"No issuer certs were found to sign the SAML Assertion using issuer name: "
242 + samlCallback.getIssuerKeyName()}
243 );
244 }
245
246 PrivateKey privateKey;
247 try {
248 privateKey = samlCallback.getIssuerCrypto().getPrivateKey(
249 samlCallback.getIssuerKeyName(), samlCallback.getIssuerKeyPassword());
250 } catch (Exception ex) {
251 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
252 }
253
254 final String binarySecurityTokenId = IDGenerator.generateID(null);
255
256 final GenericOutboundSecurityToken bstSecurityToken =
257 new GenericOutboundSecurityToken(binarySecurityTokenId, WSSecurityTokenConstants.X509V3Token,
258 privateKey, certificates);
259
260 SecurityTokenProvider<OutboundSecurityToken> securityTokenProvider =
261 new SecurityTokenProvider<OutboundSecurityToken>() {
262
263 @Override
264 public OutboundSecurityToken getSecurityToken() throws WSSecurityException {
265 return bstSecurityToken;
266 }
267
268 @Override
269 public String getId() {
270 return binarySecurityTokenId;
271 }
272 };
273
274 outputProcessorChain.getSecurityContext().registerSecurityTokenProvider(binarySecurityTokenId,
275 securityTokenProvider);
276 outputProcessorChain.getSecurityContext().put(WSSConstants.PROP_USE_THIS_TOKEN_ID_FOR_SIGNATURE,
277 binarySecurityTokenId);
278
279 return bstSecurityToken;
280 }
281
282 private static class SAMLSecurityTokenProvider
283 implements SecurityTokenProvider<OutboundSecurityToken> {
284
285 private GenericOutboundSecurityToken samlSecurityToken;
286 private SAMLCallback samlCallback;
287 private String tokenId;
288 private Element ref;
289 private FinalSAMLTokenOutputProcessor finalSAMLTokenOutputProcessor;
290 private WSSSecurityProperties securityProperties;
291
292 SAMLSecurityTokenProvider(SAMLCallback samlCallback, WSSSecurityProperties securityProperties, String tokenId,
293 Element ref, FinalSAMLTokenOutputProcessor finalSAMLTokenOutputProcessor) {
294 this.samlCallback = samlCallback;
295 this.securityProperties = securityProperties;
296 this.tokenId = tokenId;
297 this.ref = ref;
298 this.finalSAMLTokenOutputProcessor = finalSAMLTokenOutputProcessor;
299 }
300
301 @Override
302 public OutboundSecurityToken getSecurityToken() throws XMLSecurityException {
303
304 if (this.samlSecurityToken != null) {
305 return this.samlSecurityToken;
306 }
307
308 WSSecurityTokenConstants.TokenType tokenType;
309 if (samlCallback.getSamlVersion() == SAMLVersion.VERSION_10) {
310 tokenType = WSSecurityTokenConstants.SAML_10_TOKEN;
311 } else if (samlCallback.getSamlVersion() == SAMLVersion.VERSION_11) {
312 tokenType = WSSecurityTokenConstants.SAML_11_TOKEN;
313 } else {
314 tokenType = WSSecurityTokenConstants.SAML_20_TOKEN;
315 }
316
317 PrivateKey privateKey = getPrivateKeyUsingCallback();
318 if (privateKey != null) {
319 this.samlSecurityToken = new GenericOutboundSecurityToken(
320 tokenId, tokenType, privateKey, getCertificatesUsingCallback());
321 } else {
322 this.samlSecurityToken = new GenericOutboundSecurityToken(
323 tokenId, tokenType) {
324
325 @Override
326 public Key getSecretKey(String algorithmURI) throws WSSecurityException {
327
328 Key key;
329 try {
330 key = super.getSecretKey(algorithmURI);
331 } catch (XMLSecurityException e) {
332 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e);
333 }
334 if (key != null) {
335 return key;
336 }
337 byte[] secretKey = getSecretKeyUsingCallback();
338 if (secretKey != null && secretKey.length > 0) {
339 String algoFamily = JCEAlgorithmMapper.getJCEKeyAlgorithmFromURI(algorithmURI);
340 key = new SecretKeySpec(secretKey, algoFamily);
341 setSecretKey(algorithmURI, key);
342 }
343 return key;
344 }
345 };
346 }
347 this.samlSecurityToken.setProcessor(finalSAMLTokenOutputProcessor);
348 this.samlSecurityToken.setCustomTokenReference(ref);
349 return this.samlSecurityToken;
350 }
351
352 private PrivateKey getPrivateKeyUsingCallback()
353 throws WSSConfigurationException, WSSecurityException {
354
355 SubjectBean subjectBean = samlCallback.getSubject();
356 if (subjectBean != null) {
357 KeyInfoBean keyInfoBean = subjectBean.getKeyInfo();
358 if (keyInfoBean != null) {
359 X509Certificate x509Certificate = keyInfoBean.getCertificate();
360 if (x509Certificate != null) {
361 String alias = securityProperties.getSignatureCrypto().getX509Identifier(x509Certificate);
362 if (alias == null) {
363 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "aliasIsNull");
364 }
365 WSPasswordCallback wsPasswordCallback =
366 new WSPasswordCallback(alias, WSPasswordCallback.SIGNATURE);
367 WSSUtils.doPasswordCallback(securityProperties.getCallbackHandler(), wsPasswordCallback);
368 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
369 cryptoType.setAlias(alias);
370 return securityProperties.getSignatureCrypto().getPrivateKey(alias, wsPasswordCallback.getPassword());
371 } else if (keyInfoBean.getPublicKey() != null) {
372 return securityProperties.getSignatureCrypto().getPrivateKey(
373 samlCallback.getIssuerKeyName(), samlCallback.getIssuerKeyPassword());
374 }
375 }
376 }
377
378 return null;
379 }
380
381 private X509Certificate[] getCertificatesUsingCallback()
382 throws WSSConfigurationException, WSSecurityException {
383
384 SubjectBean subjectBean = samlCallback.getSubject();
385 if (subjectBean != null) {
386 KeyInfoBean keyInfoBean = subjectBean.getKeyInfo();
387 if (keyInfoBean != null) {
388 X509Certificate x509Certificate = keyInfoBean.getCertificate();
389 if (x509Certificate != null) {
390 String alias = securityProperties.getSignatureCrypto().getX509Identifier(x509Certificate);
391 if (alias == null) {
392 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "aliasIsNull");
393 }
394 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
395 cryptoType.setAlias(alias);
396 return securityProperties.getSignatureCrypto().getX509Certificates(cryptoType);
397 }
398 }
399 }
400
401 return new X509Certificate[0];
402 }
403
404
405 private byte[] getSecretKeyUsingCallback()
406 throws WSSConfigurationException, WSSecurityException {
407
408 SubjectBean subjectBean = samlCallback.getSubject();
409 if (subjectBean != null) {
410 KeyInfoBean keyInfoBean = subjectBean.getKeyInfo();
411 if (keyInfoBean != null && keyInfoBean.getCertificate() == null && keyInfoBean.getPublicKey() == null) {
412 return keyInfoBean.getEphemeralKey();
413 }
414 }
415
416 return new byte[0];
417 }
418
419 @Override
420 public String getId() {
421 return tokenId;
422 }
423 }
424
425 class FinalSAMLTokenOutputProcessor extends AbstractOutputProcessor {
426
427 private final OutboundSecurityToken securityToken;
428 private final SamlAssertionWrapper samlAssertionWrapper;
429 private final String securityTokenReferenceId;
430 private boolean senderVouches = false;
431 private boolean includeSTR = false;
432
433 FinalSAMLTokenOutputProcessor(OutboundSecurityToken securityToken, SamlAssertionWrapper samlAssertionWrapper,
434 String securityTokenReferenceId, boolean senderVouches,
435 boolean includeSTR) throws XMLSecurityException {
436 super();
437 this.addAfterProcessor(UsernameTokenOutputProcessor.class);
438 this.addAfterProcessor(SAMLTokenOutputProcessor.class);
439 this.addBeforeProcessor(WSSSignatureOutputProcessor.class);
440 this.samlAssertionWrapper = samlAssertionWrapper;
441 this.securityTokenReferenceId = securityTokenReferenceId;
442 this.senderVouches = senderVouches;
443 this.securityToken = securityToken;
444 this.includeSTR = includeSTR;
445 }
446
447 @Override
448 public void processEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain)
449 throws XMLStreamException, XMLSecurityException {
450
451 outputProcessorChain.processEvent(xmlSecEvent);
452
453 if (WSSUtils.isSecurityHeaderElement(xmlSecEvent, ((WSSSecurityProperties) getSecurityProperties()).getActor())) {
454
455 OutputProcessorChain subOutputProcessorChain = outputProcessorChain.createSubChain(this);
456 if (includeBST()) {
457
458 OutputProcessorUtils.updateSecurityHeaderOrder(
459 outputProcessorChain, WSSConstants.TAG_WSSE_BINARY_SECURITY_TOKEN, getAction(), false);
460
461 WSSUtils.createBinarySecurityTokenStructure(this, outputProcessorChain, securityToken.getId(),
462 securityToken.getX509Certificates(), getSecurityProperties().isUseSingleCert());
463 }
464
465 final QName headerElementName;
466 if (samlAssertionWrapper.getSamlVersion() == SAMLVersion.VERSION_11) {
467 headerElementName = WSSConstants.TAG_SAML_ASSERTION;
468 } else {
469 headerElementName = WSSConstants.TAG_SAML2_ASSERTION;
470 }
471 OutputProcessorUtils.updateSecurityHeaderOrder(outputProcessorChain, headerElementName, getAction(), false);
472
473 try {
474 Document doc = ((WSSSecurityProperties) getSecurityProperties()).getDocumentCreator().newDocument();
475 outputDOMElement(samlAssertionWrapper.toDOM(doc), subOutputProcessorChain);
476 } catch (ParserConfigurationException ex) {
477 LOG.debug("Error writing out SAML Assertion", ex);
478 throw new XMLSecurityException(ex);
479 }
480 if (includeSTR) {
481 OutputProcessorUtils.updateSecurityHeaderOrder(
482 outputProcessorChain, WSSConstants.TAG_WSSE_SECURITY_TOKEN_REFERENCE, getAction(), false);
483 outputSecurityTokenReference(subOutputProcessorChain, samlAssertionWrapper,
484 securityTokenReferenceId, samlAssertionWrapper.getId());
485 }
486 outputProcessorChain.removeProcessor(this);
487 }
488 }
489
490 private boolean includeBST() {
491 return senderVouches
492 && getSecurityProperties().getSignatureKeyIdentifiers().contains(
493 WSSecurityTokenConstants.KEYIDENTIFIER_SECURITY_TOKEN_DIRECT_REFERENCE)
494 && securityToken != null
495 && !(WSSConstants.SAML_TOKEN_SIGNED.equals(action)
496 && ((WSSSecurityProperties)getSecurityProperties()).isIncludeSignatureToken());
497 }
498 }
499
500 private void outputSecurityTokenReference(
501 OutputProcessorChain outputProcessorChain, SamlAssertionWrapper samlAssertionWrapper,
502 String referenceId, String tokenId) throws XMLStreamException, XMLSecurityException {
503
504 List<XMLSecAttribute> attributes = new ArrayList<>(2);
505 WSSecurityTokenConstants.TokenType tokenType = WSSecurityTokenConstants.SAML_11_TOKEN;
506 if (samlAssertionWrapper.getSamlVersion() == SAMLVersion.VERSION_11) {
507 attributes.add(createAttribute(WSSConstants.ATT_WSSE11_TOKEN_TYPE, WSSConstants.NS_SAML11_TOKEN_PROFILE_TYPE));
508 } else {
509 tokenType = WSSecurityTokenConstants.SAML_20_TOKEN;
510 attributes.add(createAttribute(WSSConstants.ATT_WSSE11_TOKEN_TYPE, WSSConstants.NS_SAML20_TOKEN_PROFILE_TYPE));
511 }
512 attributes.add(createAttribute(WSSConstants.ATT_WSU_ID, referenceId));
513 createStartElementAndOutputAsEvent(outputProcessorChain, WSSConstants.TAG_WSSE_SECURITY_TOKEN_REFERENCE, false, attributes);
514 WSSUtils.createSAMLKeyIdentifierStructure(this, outputProcessorChain, tokenType, tokenId);
515 createEndElementAndOutputAsEvent(outputProcessorChain, WSSConstants.TAG_WSSE_SECURITY_TOKEN_REFERENCE);
516 }
517
518 }