1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
18
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.joda.time.DateTime;
24 import org.opensaml.Configuration;
25 import org.opensaml.common.SAMLObjectBuilder;
26 import org.opensaml.common.SAMLVersion;
27 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28 import org.opensaml.common.xml.SAMLConstants;
29 import org.opensaml.saml2.core.Assertion;
30 import org.opensaml.saml2.core.AttributeQuery;
31 import org.opensaml.saml2.core.AttributeStatement;
32 import org.opensaml.saml2.core.Audience;
33 import org.opensaml.saml2.core.AudienceRestriction;
34 import org.opensaml.saml2.core.AuthnRequest;
35 import org.opensaml.saml2.core.Conditions;
36 import org.opensaml.saml2.core.Issuer;
37 import org.opensaml.saml2.core.NameID;
38 import org.opensaml.saml2.core.NameIDPolicy;
39 import org.opensaml.saml2.core.ProxyRestriction;
40 import org.opensaml.saml2.core.Response;
41 import org.opensaml.saml2.core.Statement;
42 import org.opensaml.saml2.core.Status;
43 import org.opensaml.saml2.core.StatusCode;
44 import org.opensaml.saml2.core.StatusMessage;
45 import org.opensaml.saml2.core.StatusResponseType;
46 import org.opensaml.saml2.core.Subject;
47 import org.opensaml.saml2.core.SubjectConfirmation;
48 import org.opensaml.saml2.core.SubjectConfirmationData;
49 import org.opensaml.saml2.encryption.Encrypter;
50 import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
51 import org.opensaml.saml2.metadata.Endpoint;
52 import org.opensaml.saml2.metadata.SPSSODescriptor;
53 import org.opensaml.security.MetadataCredentialResolver;
54 import org.opensaml.security.MetadataCriteria;
55 import org.opensaml.ws.message.encoder.MessageEncodingException;
56 import org.opensaml.ws.transport.http.HTTPInTransport;
57 import org.opensaml.xml.XMLObjectBuilder;
58 import org.opensaml.xml.encryption.EncryptionException;
59 import org.opensaml.xml.encryption.EncryptionParameters;
60 import org.opensaml.xml.encryption.KeyEncryptionParameters;
61 import org.opensaml.xml.io.Marshaller;
62 import org.opensaml.xml.io.MarshallingException;
63 import org.opensaml.xml.security.CriteriaSet;
64 import org.opensaml.xml.security.SecurityConfiguration;
65 import org.opensaml.xml.security.SecurityException;
66 import org.opensaml.xml.security.SecurityHelper;
67 import org.opensaml.xml.security.credential.Credential;
68 import org.opensaml.xml.security.credential.UsageType;
69 import org.opensaml.xml.security.criteria.EntityIDCriteria;
70 import org.opensaml.xml.security.criteria.UsageCriteria;
71 import org.opensaml.xml.signature.Signature;
72 import org.opensaml.xml.signature.SignatureException;
73 import org.opensaml.xml.signature.Signer;
74 import org.opensaml.xml.util.DatatypeHelper;
75 import org.opensaml.xml.util.Pair;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78
79 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
80 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
81 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
82 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDEncoder;
83 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
84 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
85 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
86 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
87 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
88 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
89 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
90 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
91 import edu.internet2.middleware.shibboleth.idp.session.Session;
92
93
94 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
95
96
97 public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
98
99
100 private Logger log = LoggerFactory.getLogger(AbstractSAML2ProfileHandler.class);
101
102
103 private SAMLObjectBuilder<Response> responseBuilder;
104
105
106 private SAMLObjectBuilder<Status> statusBuilder;
107
108
109 private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
110
111
112 private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
113
114
115 private SAMLObjectBuilder<Assertion> assertionBuilder;
116
117
118 private SAMLObjectBuilder<Issuer> issuerBuilder;
119
120
121 private SAMLObjectBuilder<Subject> subjectBuilder;
122
123
124 private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
125
126
127 private SAMLObjectBuilder<SubjectConfirmationData> subjectConfirmationDataBuilder;
128
129
130 private SAMLObjectBuilder<Conditions> conditionsBuilder;
131
132
133 private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
134
135
136 private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
137
138
139 private SAMLObjectBuilder<Audience> audienceBuilder;
140
141
142 private XMLObjectBuilder<Signature> signatureBuilder;
143
144
145 @SuppressWarnings("unchecked")
146 protected AbstractSAML2ProfileHandler() {
147 super();
148
149 responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
150 statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
151 statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
152 StatusCode.DEFAULT_ELEMENT_NAME);
153 statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
154 StatusMessage.DEFAULT_ELEMENT_NAME);
155 issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
156 assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
157 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
158 subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
159 subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
160 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
161 subjectConfirmationDataBuilder = (SAMLObjectBuilder<SubjectConfirmationData>) getBuilderFactory().getBuilder(
162 SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
163 conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
164 Conditions.DEFAULT_ELEMENT_NAME);
165 audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
166 AudienceRestriction.DEFAULT_ELEMENT_NAME);
167 proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
168 ProxyRestriction.DEFAULT_ELEMENT_NAME);
169 audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
170 signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
171 }
172
173
174 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
175 BaseSAML2ProfileRequestContext saml2Request = (BaseSAML2ProfileRequestContext) requestContext;
176 try {
177 super.populateRequestContext(requestContext);
178 } catch (ProfileException e) {
179 if (saml2Request.getFailureStatus() == null) {
180 saml2Request.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null, e.getMessage()));
181 }
182 throw e;
183 }
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197 protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
198 Session userSession = getUserSession(requestContext.getInboundMessageTransport());
199 if (userSession == null) {
200 NameID subject = (NameID) requestContext.getSubjectNameIdentifier();
201 if (subject != null && subject.getValue() != null) {
202 userSession = getUserSession(subject.getValue());
203 }
204 }
205
206 if (userSession != null) {
207 requestContext.setUserSession(userSession);
208 requestContext.setPrincipalName(userSession.getPrincipalName());
209 ServiceInformation serviceInfo = userSession.getServicesInformation().get(
210 requestContext.getInboundMessageIssuer());
211 if (serviceInfo != null) {
212 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
213 .getAuthenticationMethod());
214 }
215 }
216 }
217
218
219
220
221
222
223
224
225 protected void checkSamlVersion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
226 SAMLVersion version = requestContext.getInboundSAMLMessage().getVersion();
227 if (version.getMajorVersion() < 2) {
228 requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
229 StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
230 throw new ProfileException("SAML request version too low");
231 } else if (version.getMajorVersion() > 2 || version.getMinorVersion() > 0) {
232 requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
233 StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
234 throw new ProfileException("SAML request version too high");
235 }
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 protected Response buildResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
250 String subjectConfirmationMethod, List<Statement> statements) throws ProfileException {
251
252 DateTime issueInstant = new DateTime();
253
254 Response samlResponse = responseBuilder.buildObject();
255 samlResponse.setIssueInstant(issueInstant);
256 populateStatusResponse(requestContext, samlResponse);
257
258 Assertion assertion = null;
259 if (statements != null && !statements.isEmpty()) {
260 assertion = buildAssertion(requestContext, issueInstant);
261 assertion.getStatements().addAll(statements);
262 assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
263
264 postProcessAssertion(requestContext, assertion);
265
266 signAssertion(requestContext, assertion);
267
268 if (isEncryptAssertion(requestContext)) {
269 log.debug("Attempting to encrypt assertion to relying party '{}'",
270 requestContext.getInboundMessageIssuer());
271 try {
272 Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
273 samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
274 } catch (SecurityException e) {
275 log.error("Unable to construct encrypter", e);
276 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
277 "Unable to encrypt assertion"));
278 throw new ProfileException("Unable to construct encrypter", e);
279 } catch (EncryptionException e) {
280 log.error("Unable to encrypt assertion", e);
281 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
282 "Unable to encrypt assertion"));
283 throw new ProfileException("Unable to encrypt assertion", e);
284 }
285 } else {
286 samlResponse.getAssertions().add(assertion);
287 }
288 }
289
290 Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
291 samlResponse.setStatus(status);
292
293 postProcessResponse(requestContext, samlResponse);
294
295 return samlResponse;
296 }
297
298
299
300
301
302
303
304
305 protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
306 throws ProfileException {
307
308 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
309 try {
310 return requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
311 || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
312 .providesMessageConfidentiality(requestContext));
313 } catch (MessageEncodingException e) {
314 log.error("Unable to determine if outbound encoding '{}' can provide confidentiality",
315 encoder.getBindingURI());
316 throw new ProfileException("Unable to determine if assertions should be encrypted");
317 }
318 }
319
320
321
322
323
324
325
326
327
328 protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
329 throws ProfileException {
330 }
331
332
333
334
335
336
337
338
339
340 protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
341 throws ProfileException {
342 }
343
344
345
346
347
348
349
350
351
352 protected Assertion buildAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
353 Assertion assertion = assertionBuilder.buildObject();
354 assertion.setID(getIdGenerator().generateIdentifier());
355 assertion.setIssueInstant(issueInstant);
356 assertion.setVersion(SAMLVersion.VERSION_20);
357 assertion.setIssuer(buildEntityIssuer(requestContext));
358
359 Conditions conditions = buildConditions(requestContext, issueInstant);
360 assertion.setConditions(conditions);
361
362 return assertion;
363 }
364
365
366
367
368
369
370
371
372 protected Issuer buildEntityIssuer(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
373 Issuer issuer = issuerBuilder.buildObject();
374 issuer.setFormat(Issuer.ENTITY);
375 issuer.setValue(requestContext.getLocalEntityId());
376
377 return issuer;
378 }
379
380
381
382
383
384
385
386
387
388
389 protected Conditions buildConditions(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
390 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
391
392 Conditions conditions = conditionsBuilder.buildObject();
393 conditions.setNotBefore(issueInstant);
394 conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
395
396 Collection<String> audiences;
397
398
399 AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
400
401 Audience audience = audienceBuilder.buildObject();
402 audience.setAudienceURI(requestContext.getInboundMessageIssuer());
403 audienceRestriction.getAudiences().add(audience);
404 audiences = profileConfig.getAssertionAudiences();
405 if (audiences != null && audiences.size() > 0) {
406 for (String audienceUri : audiences) {
407 audience = audienceBuilder.buildObject();
408 audience.setAudienceURI(audienceUri);
409 audienceRestriction.getAudiences().add(audience);
410 }
411 }
412 conditions.getAudienceRestrictions().add(audienceRestriction);
413
414
415 audiences = profileConfig.getProxyAudiences();
416 if (audiences != null && audiences.size() > 0) {
417 ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
418 for (String audienceUri : audiences) {
419 audience = audienceBuilder.buildObject();
420 audience.setAudienceURI(audienceUri);
421 proxyRestriction.getAudiences().add(audience);
422 }
423
424 proxyRestriction.setProxyCount(profileConfig.getProxyCount());
425 conditions.getConditions().add(proxyRestriction);
426 }
427
428 return conditions;
429 }
430
431
432
433
434
435
436
437 protected void populateStatusResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
438 StatusResponseType response) {
439 response.setID(getIdGenerator().generateIdentifier());
440
441 response.setInResponseTo(requestContext.getInboundSAMLMessageId());
442 response.setIssuer(buildEntityIssuer(requestContext));
443
444 response.setVersion(SAMLVersion.VERSION_20);
445 }
446
447
448
449
450
451
452
453
454 protected void resolveAttributes(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
455 AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
456 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
457 try {
458 log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
459 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
460 Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
461
462 requestContext.setAttributes(principalAttributes);
463 } catch (AttributeRequestException e) {
464 log.warn(
465 "Error resolving attributes for principal '{}'. No name identifier or attribute statement will be included in response",
466 requestContext.getPrincipalName());
467 }
468 }
469
470
471
472
473
474
475
476
477
478
479 protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
480 throws ProfileException {
481 if (requestContext.getAttributes() == null) {
482 return null;
483 }
484
485 log.debug("Creating attribute statement in response to SAML request '{}' from relying party '{}'",
486 requestContext.getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
487
488 AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
489 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
490 try {
491 if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
492 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
493 .getInboundSAMLMessage(), requestContext.getAttributes().values());
494 } else {
495 return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
496 }
497 } catch (AttributeRequestException e) {
498 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
499 String msg = "Error encoding attributes for principal " + requestContext.getPrincipalName();
500 log.error(msg, e);
501 throw new ProfileException(msg, e);
502 }
503 }
504
505
506
507
508
509
510
511
512 protected void resolvePrincipal(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
513 AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
514 if (profileConfiguration == null) {
515 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
516 "Error resolving principal"));
517 String msg = "Unable to resolve principal, no SAML 2 profile configuration for relying party "
518 + requestContext.getInboundMessageIssuer();
519 log.warn(msg);
520 throw new ProfileException(msg);
521 }
522 SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
523 log.debug("Resolving principal name for subject of SAML request '{}' from relying party '{}'",
524 requestContext.getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
525
526 try {
527 String principal = attributeAuthority.getPrincipal(requestContext);
528 requestContext.setPrincipalName(principal);
529 } catch (AttributeRequestException e) {
530 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
531 "Error resolving principal"));
532 String msg = "Error resolving principal name for SAML request '" + requestContext.getInboundSAMLMessageId()
533 + "' from relying party '" + requestContext.getInboundMessageIssuer() + "'. Cause: "
534 + e.getMessage();
535 log.warn(msg);
536 throw new ProfileException(msg, e);
537 }
538 }
539
540
541
542
543
544
545
546
547
548
549
550 protected void signAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
551 throws ProfileException {
552 log.debug("Determining if SAML assertion to relying party '{}' should be signed",
553 requestContext.getInboundMessageIssuer());
554
555 boolean signAssertion = isSignAssertion(requestContext);
556
557 if (!signAssertion) {
558 return;
559 }
560
561 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
562
563 log.debug("Determining signing credntial for assertion to relying party '{}'",
564 requestContext.getInboundMessageIssuer());
565 Credential signatureCredential = profileConfig.getSigningCredential();
566 if (signatureCredential == null) {
567 signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
568 }
569
570 if (signatureCredential == null) {
571 String msg = "No signing credential is specified for relying party configuration "
572 + requestContext.getRelyingPartyConfiguration().getProviderId();
573 log.warn(msg);
574 throw new ProfileException(msg);
575 }
576
577 log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
578 Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
579
580 signature.setSigningCredential(signatureCredential);
581 try {
582
583
584 SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
585 } catch (SecurityException e) {
586 String msg = "Error preparing signature for signing";
587 log.error(msg);
588 throw new ProfileException(msg, e);
589 }
590
591 assertion.setSignature(signature);
592
593 Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
594 try {
595 assertionMarshaller.marshall(assertion);
596 Signer.signObject(signature);
597 } catch (MarshallingException e) {
598 String errMsg = "Unable to marshall assertion for signing";
599 log.error(errMsg, e);
600 throw new ProfileException(errMsg, e);
601 } catch (SignatureException e) {
602 String msg = "Unable to sign assertion";
603 log.error(msg, e);
604 throw new ProfileException(msg, e);
605 }
606 }
607
608
609
610
611
612
613
614
615 protected boolean isSignAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
616
617 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
618 AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
619
620 try {
621 boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
622 || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
623 .providesMessageIntegrity(requestContext));
624
625 log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
626 .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
627
628 if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
629 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
630 if (ssoDescriptor.getWantAssertionsSigned() != null) {
631 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
632 log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}",
633 requestContext.getInboundMessageIssuer(), signAssertion);
634 }
635 }
636
637 return signAssertion;
638 } catch (MessageEncodingException e) {
639 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
640 encoder.getBindingURI());
641 throw new ProfileException("Unable to determine if outbound assertion should be signed");
642 }
643 }
644
645
646
647
648
649
650
651
652
653
654
655 protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
656 Status status = statusBuilder.buildObject();
657
658 StatusCode statusCode = statusCodeBuilder.buildObject();
659 statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
660 status.setStatusCode(statusCode);
661
662 if (secondLevelCode != null) {
663 StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
664 secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
665 statusCode.setStatusCode(secondLevelStatusCode);
666 }
667
668 if (failureMessage != null) {
669 StatusMessage msg = statusMessageBuilder.buildObject();
670 msg.setMessage(failureMessage);
671 status.setStatusMessage(msg);
672 }
673
674 return status;
675 }
676
677
678
679
680
681
682
683
684
685
686
687
688
689 protected Subject buildSubject(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod,
690 DateTime issueInstant) throws ProfileException {
691 Subject subject = subjectBuilder.buildObject();
692 subject.getSubjectConfirmations().add(
693 buildSubjectConfirmation(requestContext, confirmationMethod, issueInstant));
694
695 NameID nameID = buildNameId(requestContext);
696 if (nameID == null) {
697 return subject;
698 }
699
700 requestContext.setSubjectNameIdentifier(nameID);
701
702 if (isEncryptNameID(requestContext)) {
703 log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext.getInboundMessageIssuer());
704 try {
705 Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
706 subject.setEncryptedID(encrypter.encrypt(nameID));
707 } catch (SecurityException e) {
708 log.error("Unable to construct encrypter", e);
709 requestContext
710 .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt NameID"));
711 throw new ProfileException("Unable to construct encrypter", e);
712 } catch (EncryptionException e) {
713 log.error("Unable to encrypt NameID", e);
714 requestContext
715 .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt NameID"));
716 throw new ProfileException("Unable to encrypt NameID", e);
717 }
718 } else {
719 subject.setNameID(nameID);
720 }
721
722 return subject;
723 }
724
725
726
727
728
729
730
731
732 protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
733
734 boolean nameIdEncRequiredByAuthnRequest = isRequestRequiresEncryptNameID(requestContext);
735
736 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
737 boolean nameIdEncRequiredByConfig = false;
738 try {
739 nameIdEncRequiredByConfig = requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
740 || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
741 .providesMessageConfidentiality(requestContext));
742 } catch (MessageEncodingException e) {
743 String msg = "Unable to determine if outbound encoding '" + encoder.getBindingURI()
744 + "' provides message confidentiality protection";
745 log.error(msg);
746 throw new ProfileException(msg);
747 }
748
749 return nameIdEncRequiredByAuthnRequest || nameIdEncRequiredByConfig;
750 }
751
752
753
754
755
756
757
758 protected boolean isRequestRequiresEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
759 boolean nameIdEncRequiredByAuthnRequest = false;
760 if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
761 AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
762 NameIDPolicy policy = authnRequest.getNameIDPolicy();
763 if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
764 nameIdEncRequiredByAuthnRequest = true;
765 }
766 }
767 return nameIdEncRequiredByAuthnRequest;
768 }
769
770
771
772
773
774
775
776
777
778
779 protected SubjectConfirmation buildSubjectConfirmation(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
780 String confirmationMethod, DateTime issueInstant) {
781 SubjectConfirmationData confirmationData = subjectConfirmationDataBuilder.buildObject();
782 HTTPInTransport inTransport = (HTTPInTransport) requestContext.getInboundMessageTransport();
783 confirmationData.setAddress(inTransport.getPeerAddress());
784 confirmationData.setInResponseTo(requestContext.getInboundSAMLMessageId());
785 confirmationData.setNotOnOrAfter(issueInstant.plus(requestContext.getProfileConfiguration()
786 .getAssertionLifetime()));
787
788 Endpoint relyingPartyEndpoint = requestContext.getPeerEntityEndpoint();
789 if (relyingPartyEndpoint != null) {
790 if (relyingPartyEndpoint.getResponseLocation() != null) {
791 confirmationData.setRecipient(relyingPartyEndpoint.getResponseLocation());
792 } else {
793 confirmationData.setRecipient(relyingPartyEndpoint.getLocation());
794 }
795 }
796
797 SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
798 subjectConfirmation.setMethod(confirmationMethod);
799 subjectConfirmation.setSubjectConfirmationData(confirmationData);
800
801 return subjectConfirmation;
802 }
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818 protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
819 Pair<BaseAttribute, SAML2NameIDEncoder> nameIdAttributeAndEncoder = null;
820 try {
821 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML2NameIDEncoder.class, requestContext);
822 } catch (ProfileException e) {
823 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
824 "Required NameID format not supported"));
825 throw e;
826 }
827
828 if (nameIdAttributeAndEncoder == null) {
829 return null;
830 }
831
832 BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
833 requestContext.setNameIdentifierAttribute(nameIdAttribute);
834 SAML2NameIDEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
835
836 log.debug(
837 "Using attribute '{}' supporting NameID format '{}' to create the NameID for relying party '{}'",
838 new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
839 requestContext.getInboundMessageIssuer(), });
840 try {
841
842 NameID nameId = nameIdEncoder.encode(nameIdAttribute);
843 nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
844 return nameId;
845 } catch (AttributeEncodingException e) {
846 log.error("Unable to encode NameID attribute", e);
847 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
848 throw new ProfileException("Unable to encode NameID attribute", e);
849 }
850 }
851
852
853
854
855
856
857
858
859 protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
860 Response samlResponse = responseBuilder.buildObject();
861 samlResponse.setIssueInstant(new DateTime());
862 populateStatusResponse(requestContext, samlResponse);
863
864 samlResponse.setStatus(requestContext.getFailureStatus());
865
866 return samlResponse;
867 }
868
869
870
871
872
873
874
875
876
877
878
879
880 protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
881 SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
882
883 EncryptionParameters dataEncParams = SecurityHelper
884 .buildDataEncryptionParams(null, securityConfiguration, null);
885
886 Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
887 if (keyEncryptionCredential == null) {
888 log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
889 throw new SecurityException("Could not resolve key encryption credential");
890 }
891 String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
892 KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
893 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
894
895 Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
896 encrypter.setKeyPlacement(KeyPlacement.INLINE);
897 return encrypter;
898 }
899
900
901
902
903
904
905
906
907
908
909 protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
910 MetadataCredentialResolver kekCredentialResolver = getMetadataCredentialResolver();
911
912 CriteriaSet criteriaSet = new CriteriaSet();
913 criteriaSet.add(new EntityIDCriteria(peerEntityId));
914 criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
915 criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
916
917 return kekCredentialResolver.resolveSingle(criteriaSet);
918 }
919
920
921
922
923
924
925 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
926 SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
927 auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
928 auditLogEntry.setMessageProfile(getProfileId());
929 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
930 auditLogEntry.setPrincipalName(context.getPrincipalName());
931 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
932 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
933 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
934 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
935 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
936 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
937 if (context.getReleasedAttributes() != null) {
938 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
939 }
940
941 if (context.getNameIdentifierAttribute() != null) {
942 Object idValue = context.getNameIdentifierAttribute().getValues().iterator().next();
943 if(idValue != null){
944 auditLogEntry.setNameIdValue(idValue.toString());
945 }
946 }
947
948 getAduitLog().info(auditLogEntry.toString());
949 }
950
951
952 protected class SAML2AuditLogEntry extends AuditLogEntry {
953
954
955 private StatusResponseType samlResponse;
956
957
958
959
960
961
962 public StatusResponseType getSAMLResponse() {
963 return samlResponse;
964 }
965
966
967
968
969
970
971 public void setSAMLResponse(StatusResponseType response) {
972 samlResponse = response;
973 }
974
975
976 public String toString() {
977 StringBuilder entryString = new StringBuilder(super.toString());
978
979 StringBuilder assertionIds = new StringBuilder();
980
981 if (samlResponse instanceof Response) {
982 List<Assertion> assertions = ((Response) samlResponse).getAssertions();
983 if (assertions != null && !assertions.isEmpty()) {
984 for (Assertion assertion : assertions) {
985 assertionIds.append(assertion.getID());
986 assertionIds.append(",");
987 }
988 }
989 }
990
991 if (getNameIdValue() != null) {
992 entryString.append(getNameIdValue());
993 }
994 entryString.append("|");
995
996 entryString.append(assertionIds.toString());
997 entryString.append("|");
998
999 return entryString.toString();
1000 }
1001 }
1002 }