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 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
466 String msg = MessageFormatter.format(
467 "Error resolving attributes for principal '{}' for SAML request from relying party '{}'",
468 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
469 log.error(msg, e);
470 throw new ProfileException(msg, e);
471 }
472 }
473
474
475
476
477
478
479
480
481
482
483 protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
484 throws ProfileException {
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 log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
830 requestContext.getInboundMessageIssuer());
831
832
833 String requiredNameFormat = null;
834 if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
835 AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
836 if (authnRequest.getNameIDPolicy() != null) {
837 requiredNameFormat = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getFormat());
838
839 if (requiredNameFormat != null
840 && (requiredNameFormat.equals("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted") || requiredNameFormat
841 .equals(NameID.UNSPECIFIED))) {
842 requiredNameFormat = null;
843 }
844 }
845 }
846
847
848 List<String> supportedNameFormats = getNameFormats(requestContext);
849 if (requiredNameFormat != null) {
850 supportedNameFormats.clear();
851 supportedNameFormats.add(requiredNameFormat);
852 }
853 if (!supportedNameFormats.isEmpty()) {
854 log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
855 supportedNameFormats);
856 } else {
857 log.debug("Relying party '{}' indicated no preferred name formats", requestContext
858 .getInboundMessageIssuer());
859 }
860
861 BaseAttribute<?> nameIdAttribute = null;
862 SAML2NameIDEncoder nameIdEncoder = null;
863
864 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
865 if (principalAttributes != null) {
866 for (BaseAttribute<?> attribute : principalAttributes.values()) {
867 if (attribute == null) {
868 continue;
869 }
870
871 for (AttributeEncoder encoder : attribute.getEncoders()) {
872 if (encoder == null) {
873 continue;
874 }
875
876 if (encoder instanceof SAML2NameIDEncoder) {
877 nameIdEncoder = (SAML2NameIDEncoder)encoder;
878
879 if (requiredNameFormat != null) {
880 if (nameIdEncoder.getNameFormat().equals(requiredNameFormat)) {
881 nameIdAttribute = attribute;
882 nameIdEncoder = (SAML2NameIDEncoder) encoder;
883 break;
884 }
885 } else {
886 if (supportedNameFormats.isEmpty()
887 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
888 nameIdAttribute = attribute;
889 nameIdEncoder = (SAML2NameIDEncoder) encoder;
890 break;
891 }
892 }
893 }
894 }
895 }
896 }
897
898 if (nameIdAttribute == null || nameIdEncoder == null) {
899 if (requiredNameFormat != null) {
900 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
901 StatusCode.INVALID_NAMEID_POLICY_URI, "NameID Format not supported: " + requiredNameFormat));
902 String msg = MessageFormatter
903 .format(
904 "No attribute of principal '{}' can be encoded in to a NameID of required format '{}' for relying party '{}'",
905 new Object[] { requestContext.getPrincipalName(), requiredNameFormat,
906 requestContext.getInboundMessageIssuer() });
907 log.warn(msg);
908 throw new ProfileException(msg);
909 } else {
910 return null;
911 }
912 }
913
914 log.debug("Using attribute '{}' supporting NameID format '{}' to create the NameID for relying party '{}'",
915 new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
916 requestContext.getInboundMessageIssuer() });
917 try {
918 return nameIdEncoder.encode(nameIdAttribute);
919 } catch (AttributeEncodingException e) {
920 log.error("Unable to encode NameID attribute", e);
921 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
922 throw new ProfileException("Unable to encode NameID attribute", e);
923 }
924 }
925
926
927
928
929
930
931
932
933 protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
934 Response samlResponse = responseBuilder.buildObject();
935 samlResponse.setIssueInstant(new DateTime());
936 populateStatusResponse(requestContext, samlResponse);
937
938 samlResponse.setStatus(requestContext.getFailureStatus());
939
940 return samlResponse;
941 }
942
943
944
945
946
947
948
949
950
951
952
953
954 protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
955 SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
956
957 EncryptionParameters dataEncParams = SecurityHelper
958 .buildDataEncryptionParams(null, securityConfiguration, null);
959
960 Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
961 if (keyEncryptionCredential == null) {
962 log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
963 throw new SecurityException("Could not resolve key encryption credential");
964 }
965 String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
966 KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
967 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
968
969 Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
970 encrypter.setKeyPlacement(KeyPlacement.INLINE);
971 return encrypter;
972 }
973
974
975
976
977
978
979
980
981
982
983 protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
984 MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver(getMetadataProvider());
985
986 CriteriaSet criteriaSet = new CriteriaSet();
987 criteriaSet.add(new EntityIDCriteria(peerEntityId));
988 criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
989 criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
990
991 return kekCredentialResolver.resolveSingle(criteriaSet);
992 }
993
994
995
996
997
998
999 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
1000 SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
1001 auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
1002 auditLogEntry.setMessageProfile(getProfileId());
1003 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
1004 auditLogEntry.setPrincipalName(context.getPrincipalName());
1005 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
1006 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
1007 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
1008 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
1009 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
1010 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
1011 if (context.getReleasedAttributes() != null) {
1012 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
1013 }
1014
1015 getAduitLog().info(auditLogEntry.toString());
1016 }
1017
1018
1019 protected class SAML2AuditLogEntry extends AuditLogEntry {
1020
1021
1022 private StatusResponseType samlResponse;
1023
1024
1025 private NameID unencryptedNameId;
1026
1027
1028
1029
1030
1031
1032 public StatusResponseType getSAMLResponse() {
1033 return samlResponse;
1034 }
1035
1036
1037
1038
1039
1040
1041 public void setSAMLResponse(StatusResponseType response) {
1042 samlResponse = response;
1043 }
1044
1045
1046
1047
1048
1049
1050 public NameID getUnencryptedNameId() {
1051 return unencryptedNameId;
1052 }
1053
1054
1055
1056
1057
1058
1059 public void setUnencryptedNameId(NameID id) {
1060 unencryptedNameId = id;
1061 }
1062
1063
1064 public String toString() {
1065 StringBuilder entryString = new StringBuilder(super.toString());
1066
1067 StringBuilder assertionIds = new StringBuilder();
1068
1069 if (samlResponse instanceof Response) {
1070 List<Assertion> assertions = ((Response) samlResponse).getAssertions();
1071 if (assertions != null && !assertions.isEmpty()) {
1072 for (Assertion assertion : assertions) {
1073 assertionIds.append(assertion.getID());
1074 assertionIds.append(",");
1075 }
1076 }
1077 }
1078
1079 if (unencryptedNameId != null) {
1080 entryString.append(unencryptedNameId.getValue());
1081 }
1082 entryString.append("|");
1083
1084 entryString.append(assertionIds.toString());
1085 entryString.append("|");
1086
1087 return entryString.toString();
1088 }
1089 }
1090 }