This repository has been archived by the owner on Nov 29, 2022. It is now read-only.
/
SAMLUtil.java
623 lines (535 loc) · 27.6 KB
/
SAMLUtil.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
/*
* Copyright 2009-2010 Vladimir Schaefer
*
* 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.security.saml.util;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.opensaml.common.SAMLException;
import org.opensaml.common.SAMLRuntimeException;
import org.opensaml.common.binding.decoding.URIComparator;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.metadata.ArtifactResolutionService;
import org.opensaml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.SSODescriptor;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.ws.message.decoder.MessageDecodingException;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.transport.InTransport;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.security.BasicSecurityConfiguration;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.signature.KeyInfo;
import org.opensaml.xml.signature.SignableXMLObject;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.signature.X509Data;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.util.XMLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.MetadataManager;
import org.w3c.dom.Element;
import javax.net.ssl.HostnameVerifier;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;
/**
* Utility class for SAML entities
*
* @author Vladimir Schaefer
*/
public class SAMLUtil {
private static final Logger log = LoggerFactory.getLogger(SAMLUtil.class);
/** The URIComparator implementation to use. */
private static final URIComparator DEFAULT_URI_COMPARATOR = new DefaultURLComparator();
/**
* Method determines binding supported by the given endpoint. Usually the biding is encoded in the binding attribute
* of the endpoint, but in some cases more processing is needed (e.g. for HoK profile).
*
* @param endpoint endpoint
* @return binding supported by the endpoint
*/
public static String getBindingForEndpoint(Endpoint endpoint) {
String bindingName = endpoint.getBinding();
// For HoK profile the used binding is determined in a different way
if (org.springframework.security.saml.SAMLConstants.SAML2_HOK_WEBSSO_PROFILE_URI.equals(bindingName)) {
QName attributeName = org.springframework.security.saml.SAMLConstants.WEBSSO_HOK_METADATA_ATT_NAME;
String endpointLocation = endpoint.getUnknownAttributes().get(attributeName);
if (endpointLocation != null) {
bindingName = endpointLocation;
} else {
throw new SAMLRuntimeException("Holder of Key profile endpoint doesn't contain attribute hoksso:ProtocolBinding");
}
}
return bindingName;
}
/**
* Returns Single logout service for given binding of the IDP.
*
* @param descriptor IDP to search for service in
* @param binding binding supported by the service
* @return SSO service capable of handling the given binding
* @throws MetadataProviderException if the service can't be determined
*/
public static SingleLogoutService getLogoutServiceForBinding(SSODescriptor descriptor, String binding) throws MetadataProviderException {
List<SingleLogoutService> services = descriptor.getSingleLogoutServices();
for (SingleLogoutService service : services) {
if (binding.equals(service.getBinding())) {
return service;
}
}
log.debug("No binding found for IDP with binding " + binding);
throw new MetadataProviderException("Binding " + binding + " is not supported for this IDP");
}
public static String getLogoutBinding(IDPSSODescriptor idp, SPSSODescriptor sp) throws MetadataProviderException {
List<SingleLogoutService> logoutServices = idp.getSingleLogoutServices();
if (logoutServices.size() == 0) {
throw new MetadataProviderException("IDP doesn't contain any SingleLogout endpoints");
}
String binding = null;
// Let's find first binding supported by both IDP and SP
idp:
for (SingleLogoutService idpService : logoutServices) {
for (SingleLogoutService spService : sp.getSingleLogoutServices()) {
if (idpService.getBinding().equals(spService.getBinding())) {
binding = idpService.getBinding();
break idp;
}
}
}
// In case there's no common endpoint let's use first available
if (binding == null) {
binding = idp.getSingleLogoutServices().iterator().next().getBinding();
}
return binding;
}
public static IDPSSODescriptor getIDPSSODescriptor(EntityDescriptor idpEntityDescriptor) throws MessageDecodingException {
IDPSSODescriptor idpSSODescriptor = idpEntityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
if (idpSSODescriptor == null) {
log.error("Could not find an IDPSSODescriptor in metadata.");
throw new MessageDecodingException("Could not find an IDPSSODescriptor in metadata.");
}
return idpSSODescriptor;
}
/**
* Loads the assertionConsumerIndex designated by the index. In case an index is specified the consumer
* is located and returned, otherwise default consumer is used.
*
* @param ssoDescriptor descriptor
* @param index to load, can be null
* @return consumer service
* @throws org.opensaml.common.SAMLRuntimeException
* in case assertionConsumerService with given index isn't found
*/
public static AssertionConsumerService getConsumerService(SPSSODescriptor ssoDescriptor, Integer index) {
if (index != null) {
for (AssertionConsumerService service : ssoDescriptor.getAssertionConsumerServices()) {
if (index.equals(service.getIndex())) {
log.debug("Found assertionConsumerService with index {} and binding {}", index, service.getBinding());
return service;
}
}
throw new SAMLRuntimeException("AssertionConsumerService with index " + index + " wasn't found for ServiceProvider " + ssoDescriptor.getID() + ", please check your metadata");
}
log.debug("Index for AssertionConsumerService not specified, returning default");
return ssoDescriptor.getDefaultAssertionConsumerService();
}
public static ArtifactResolutionService getArtifactResolutionService(IDPSSODescriptor idpssoDescriptor, int endpointIndex) throws MessageDecodingException {
List<ArtifactResolutionService> artifactResolutionServices = idpssoDescriptor.getArtifactResolutionServices();
if (artifactResolutionServices == null || artifactResolutionServices.size() == 0) {
log.error("Could not find any artifact resolution services in metadata.");
throw new MessageDecodingException("Could not find any artifact resolution services in metadata.");
}
ArtifactResolutionService artifactResolutionService = null;
for (ArtifactResolutionService ars : artifactResolutionServices) {
if (ars.getIndex() == endpointIndex) {
artifactResolutionService = ars;
break;
}
}
if (artifactResolutionService == null) {
throw new MessageDecodingException("Could not find artifact resolution service with index " + endpointIndex + " in IDP data.");
}
return artifactResolutionService;
}
/**
* Determines whether filter with the given name should be invoked for the current request. Filter is used
* when requestURI contains the filterName.
*
* @param filterName name of the filter to search URI for
* @param request request
* @return true if filter should be processed for this request
*/
public static boolean processFilter(String filterName, HttpServletRequest request) {
return (request.getRequestURI().contains(filterName));
}
/**
* Helper method compares whether SHA-1 hash of the entityId equals the hashID.
*
* @param hashID hash id to compare
* @param entityId entity id to hash and verify
* @return true if values match
* @throws MetadataProviderException in case SHA-1 hash can't be initialized
*/
public static boolean compare(byte[] hashID, String entityId) throws MetadataProviderException {
try {
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
byte[] hashedEntityId = sha1Digester.digest(entityId.getBytes());
for (int i = 0; i < hashedEntityId.length; i++) {
if (hashedEntityId[i] != hashID[i]) {
return false;
}
}
return true;
} catch (NoSuchAlgorithmException e) {
throw new MetadataProviderException("SHA-1 message digest not available", e);
}
}
/**
* Verifies that the alias is valid. Alias mus be non-empty string which only include ASCII characters.
*
* @param alias alias to verify
* @param entityId id of the entity
* @throws MetadataProviderException in case any validation problem is found
*/
public static void verifyAlias(String alias, String entityId) throws MetadataProviderException {
if (alias == null) {
throw new MetadataProviderException("Alias for entity " + entityId + " is null");
} else if (alias.length() == 0) {
throw new MetadataProviderException("Alias for entity " + entityId + " is empty");
} else if (!alias.matches("\\p{ASCII}*")) {
throw new MetadataProviderException("Only ASCII characters can be used in the alias " + alias + " for entity " + entityId);
}
}
/**
* Parses list of all Base64 encoded certificates found inside the KeyInfo element. All present X509Data
* elements are processed.
*
* @param keyInfo key info to parse
* @return found base64 encoded certificates
*/
public static List<String> getBase64EncodeCertificates(KeyInfo keyInfo) {
List<String> certList = new LinkedList<String>();
if (keyInfo == null) {
return certList;
}
List<X509Data> x509Datas = keyInfo.getX509Datas();
for (X509Data x509Data : x509Datas) {
if (x509Data != null) {
certList.addAll(getBase64EncodedCertificates(x509Data));
}
}
return certList;
}
/**
* Parses list of Base64 encoded certificates present in the X509Data element.
*
* @param x509Data data to parse
* @return list with 0..n certificates
*/
public static List<String> getBase64EncodedCertificates(X509Data x509Data) {
List<String> certList = new LinkedList<String>();
if (x509Data == null) {
return certList;
}
for (org.opensaml.xml.signature.X509Certificate xmlCert : x509Data.getX509Certificates()) {
if (xmlCert != null && xmlCert.getValue() != null) {
certList.add(xmlCert.getValue());
}
}
return certList;
}
/**
* Analyzes the request headers in order to determine if it comes from an ECP-enabled
* client and based on this decides whether ECP profile will be used. Subclasses can override
* the method to control when is the ECP invoked.
*
* @param request request to analyze
* @return whether the request comes from an ECP-enabled client or not
*/
public static boolean isECPRequest(HttpServletRequest request) {
String acceptHeader = request.getHeader("Accept");
String paosHeader = request.getHeader(org.springframework.security.saml.SAMLConstants.PAOS_HTTP_HEADER);
return acceptHeader != null && paosHeader != null
&& acceptHeader.contains(org.springframework.security.saml.SAMLConstants.PAOS_HTTP_ACCEPT_HEADER)
&& paosHeader.contains(org.opensaml.common.xml.SAMLConstants.PAOS_NS)
&& paosHeader.contains(org.opensaml.common.xml.SAMLConstants.SAML20ECP_NS);
}
/**
* Method helps to identify which endpoint is used to process the current message. It expects a list of potential
* endpoints based on the current profile and selects the one which uses the specified binding and matches
* the URL of incoming message.
*
* @param endpoints endpoints to check
* @param messageBinding binding
* @param inTransport transport which received the current message
* @param <T> type of the endpoint
* @return first endpoint satisfying the requestURL and binding conditions
* @throws SAMLException in case endpoint can't be found
*/
public static <T extends Endpoint> T getEndpoint(List<T> endpoints, String messageBinding, InTransport inTransport) throws SAMLException {
return getEndpoint(endpoints, messageBinding, inTransport, DEFAULT_URI_COMPARATOR);
}
/**
* Method helps to identify which endpoint is used to process the current message. It expects a list of potential
* endpoints based on the current profile and selects the one which uses the specified binding and matches
* the URL of incoming message.
*
* @param endpoints endpoints to check
* @param messageBinding binding
* @param inTransport transport which received the current message
* @param uriComparator URI comparator
* @param <T> type of the endpoint
* @return first endpoint satisfying the requestURL and binding conditions
* @throws SAMLException in case endpoint can't be found
*/
public static <T extends Endpoint> T getEndpoint(List<T> endpoints, String messageBinding, InTransport inTransport, URIComparator uriComparator) throws SAMLException {
HttpServletRequest httpRequest = ((HttpServletRequestAdapter)inTransport).getWrappedRequest();
String requestURL = DatatypeHelper.safeTrimOrNullString(httpRequest.getRequestURL().toString());
String queryString = DatatypeHelper.safeTrimOrNullString(httpRequest.getQueryString());
if (queryString != null){
requestURL += '?' + queryString;
}
for (T endpoint : endpoints) {
String binding = getBindingForEndpoint(endpoint);
// Check that destination and binding matches
if (binding.equals(messageBinding)) {
if (endpoint.getLocation() != null && uriComparator.compare(endpoint.getLocation(), requestURL)) {
log.debug("Found endpoint {} for request URL {} based on location attribute in metadata", endpoint, requestURL);
return endpoint;
} else if (endpoint.getResponseLocation() != null && uriComparator.compare(endpoint.getResponseLocation(), requestURL)) {
log.debug("Found endpoint {} for request URL {} based on response location attribute in metadata", endpoint, requestURL);
return endpoint;
}
}
}
throw new SAMLException("Endpoint with message binding " + messageBinding + " and URL " + requestURL + " wasn't found in local metadata");
}
/**
* Loads IDP descriptor for entity with the given entityID. Fails when it can't be found.
* @param metadata metadata manager
* @param idpId entity ID
* @return descriptor
* @throws MetadataProviderException in case descriptor can't be found
*/
public static IDPSSODescriptor getIDPDescriptor(MetadataManager metadata, String idpId) throws MetadataProviderException {
if (!metadata.isIDPValid(idpId)) {
log.debug("IDP name of the authenticated user is not valid", idpId);
throw new MetadataProviderException("IDP with name " + idpId + " wasn't found in the list of configured IDPs");
}
IDPSSODescriptor idpssoDescriptor = (IDPSSODescriptor) metadata.getRole(idpId, IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS);
if (idpssoDescriptor == null) {
throw new MetadataProviderException("Given IDP " + idpId + " doesn't contain any IDPSSODescriptor element");
}
return idpssoDescriptor;
}
/**
* Helper method that marshals the given message.
*
* @param message message the marshall and serialize
* @return marshaled message
* @throws org.opensaml.ws.message.encoder.MessageEncodingException
* thrown if the give message can not be marshaled into its DOM representation
*/
public static Element marshallMessage(XMLObject message) throws MessageEncodingException {
try {
if (message.getDOM() != null) {
log.debug("XMLObject already had cached DOM, returning that element");
return message.getDOM();
}
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(message);
if (marshaller == null) {
throw new MessageEncodingException("Unable to marshall message, no marshaller registered for message object: "
+ message.getElementQName());
}
Element messageElem = marshaller.marshall(message);
if (log.isTraceEnabled()) {
log.trace("Marshalled message into DOM:\n{}", XMLHelper.nodeToString(messageElem));
}
return messageElem;
} catch (MarshallingException e) {
log.error("Encountered error marshalling message to its DOM representation", e);
throw new MessageEncodingException("Encountered error marshalling message into its DOM representation", e);
}
}
/**
* Method digitally signs and marshals the object in case it is signable and the signing credential is provided.
*
* In case the object is already signed or the signing credential is not provided message is just marshalled.
*
* @param signableMessage object to sign
* @param signingCredential credential to sign with
* @param signingAlgorithm signing algorithm to use (optional). Leave null to use credential's default algorithm
* @param signingAlgorithm digest method algorithm to use (optional). Leave null to use credential's default algorithm
* @param keyInfoGenerator name of generator used to create KeyInfo elements with key data
* @throws org.opensaml.ws.message.encoder.MessageEncodingException
* thrown if there is a problem marshalling or signing the message
* @return marshalled and signed message
*/
@SuppressWarnings("unchecked")
public static Element marshallAndSignMessage(SignableXMLObject signableMessage, Credential signingCredential, String signingAlgorithm, String digestMethodAlgorithm, String keyInfoGenerator) throws MessageEncodingException {
if (signingCredential != null && !signableMessage.isSigned()) {
XMLObjectBuilder<Signature> signatureBuilder = org.opensaml.Configuration.getBuilderFactory().getBuilder(
Signature.DEFAULT_ELEMENT_NAME);
Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
if (signingAlgorithm != null) {
signature.setSignatureAlgorithm(signingAlgorithm);
}
signature.setSigningCredential(signingCredential);
BasicSecurityConfiguration secConfig = null;
if (digestMethodAlgorithm != null)
{
secConfig = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
secConfig.setSignatureReferenceDigestMethod(digestMethodAlgorithm);
}
try {
SecurityHelper.prepareSignatureParams(signature, signingCredential, secConfig, keyInfoGenerator);
} catch (org.opensaml.xml.security.SecurityException e) {
throw new MessageEncodingException("Error preparing signature for signing", e);
}
signableMessage.setSignature(signature);
Element element = marshallMessage(signableMessage);
try {
Signer.signObject(signature);
} catch (SignatureException e) {
log.error("Unable to sign protocol message", e);
throw new MessageEncodingException("Unable to sign protocol message", e);
}
return element;
} else {
return marshallMessage(signableMessage);
}
}
/**
* Verifies that the current time is within skewInSec interval from the time value.
*
* @param skewInSec skew interval in seconds
* @param time time the current time must fit into with the given skew
* @return true if time matches, false otherwise
*/
public static boolean isDateTimeSkewValid(int skewInSec, DateTime time) {
return isDateTimeSkewValid(skewInSec, 0, time);
}
/**
* Verifies that the current time fits into interval defined by time minus backwardInterval minus skew and time plus forward interval plus skew.
*
*
* @param skewInSec skew interval in seconds
* @param forwardInterval forward interval in sec
* @param time time the current time must fit into with the given skew
* @return true if time matches, false otherwise
*/
public static boolean isDateTimeSkewValid(int skewInSec, long forwardInterval, DateTime time) {
final DateTime reference = new DateTime();
final Interval validTimeInterval = new Interval(
reference.minusSeconds(skewInSec + (int)forwardInterval),
reference.plusSeconds(skewInSec)
);
return validTimeInterval.contains(time);
}
/**
* Method replaces all characters which are not allowed in xsd:NCName type with underscores. It also makes sure
* that value doesn't start with a hyphen by replacing it with underscore.
*
* @param value value to clean
* @return null for null input, otherwise cleaned value
*/
public static String getNCNameString(String value) {
if (value == null) {
return null;
}
String cleanValue = value.replaceAll("[^a-zA-Z0-9-_.]", "_");
if (cleanValue.startsWith("-")) {
cleanValue = "_" + cleanValue.substring(1);
}
return cleanValue;
}
/**
* Populates hostname verifier of the given type. Supported values are default, defaultAndLocalhost,
* strict and allowAll. Unsupported values will return default verifier.
*
* @param hostnameVerificationType type
* @return verifier
*/
public static HostnameVerifier getHostnameVerifier(String hostnameVerificationType) {
HostnameVerifier hostnameVerifier;
if ("default".equalsIgnoreCase(hostnameVerificationType)) {
hostnameVerifier = org.apache.commons.ssl.HostnameVerifier.DEFAULT;
} else if ("defaultAndLocalhost".equalsIgnoreCase(hostnameVerificationType)) {
hostnameVerifier = org.apache.commons.ssl.HostnameVerifier.DEFAULT_AND_LOCALHOST;
} else if ("strict".equalsIgnoreCase(hostnameVerificationType)) {
hostnameVerifier = org.apache.commons.ssl.HostnameVerifier.STRICT;
} else if ("allowAll".equalsIgnoreCase(hostnameVerificationType)) {
hostnameVerifier = org.apache.commons.ssl.HostnameVerifier.ALLOW_ALL;
} else {
hostnameVerifier = org.apache.commons.ssl.HostnameVerifier.DEFAULT;
}
return hostnameVerifier;
}
/**
* Method digitally signs the EntityDescriptor element (when configured with property sign metadata) and
* serializes the result into a string.
*
* @param metadataManager metadata manager
* @param keyManager key manager
* @param descriptor descriptor to sign and serialize
* @param extendedMetadata information about metadata signing, looked up when null
* @return serialized and signed metadata
* @throws MarshallingException in case serialization fails
*/
public static String getMetadataAsString(MetadataManager metadataManager, KeyManager keyManager, EntityDescriptor descriptor, ExtendedMetadata extendedMetadata) throws MarshallingException {
Element element;
if (extendedMetadata == null) {
try {
extendedMetadata = metadataManager.getExtendedMetadata(descriptor.getEntityID());
} catch (MetadataProviderException e) {
log.error("Unable to locate extended metadata", e);
throw new MarshallingException("Unable to locate extended metadata", e);
}
}
try {
if (extendedMetadata.isLocal() && extendedMetadata.isSignMetadata()) {
Credential credential = keyManager.getCredential(extendedMetadata.getSigningKey());
String signingAlgorithm = extendedMetadata.getSigningAlgorithm();
String keyGenerator = extendedMetadata.getKeyInfoGeneratorName();
String digestMethodAlgorithm = extendedMetadata.getDigestMethodAlgorithm();
element = SAMLUtil.marshallAndSignMessage(descriptor, credential, signingAlgorithm, digestMethodAlgorithm, keyGenerator);
} else {
element = SAMLUtil.marshallMessage(descriptor);
}
} catch (MessageEncodingException e) {
log.error("Unable to marshall message", e);
throw new MarshallingException("Unable to marshall message", e);
}
return XMLHelper.nodeToString(element);
}
}