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