Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public class SamlRealmSettings {
Setting.simpleString("signing.keystore.alias", Setting.Property.NodeScope);
public static final Setting<List<String>> SIGNING_MESSAGE_TYPES = Setting.listSetting("signing.saml_messages",
Collections.singletonList("*"), Function.identity(), Setting.Property.NodeScope);

public static final Setting<List<String>> REQUESTED_AUTHN_CONTEXT_CLASS_REF = Setting.listSetting("req_authn_context_class_ref",
Collections.emptyList(), Function.identity(),Setting.Property.NodeScope);
public static final Setting<TimeValue> CLOCK_SKEW = Setting.positiveTimeSetting("allowed_clock_skew", TimeValue.timeValueMinutes(3),
Setting.Property.NodeScope);

Expand All @@ -79,7 +80,7 @@ public static Set<Setting<?>> getSettings() {
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
NAMEID_FORMAT, NAMEID_ALLOW_CREATE, NAMEID_SP_QUALIFIER, FORCE_AUTHN,
POPULATE_USER_METADATA, CLOCK_SKEW,
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES);
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES, REQUESTED_AUTHN_CONTEXT_CLASS_REF);
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
set.addAll(SIGNING_SETTINGS.getAllSettings());
set.addAll(SSLConfigurationSettings.withPrefix(SSL_PREFIX).getAllSettings());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.Audience;
import org.opensaml.saml.saml2.core.AudienceRestriction;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.Conditions;
import org.opensaml.saml.saml2.core.EncryptedAssertion;
import org.opensaml.saml.saml2.core.EncryptedAttribute;
Expand Down Expand Up @@ -219,6 +220,7 @@ private List<Attribute> processAssertion(Assertion assertion, boolean requireSig
checkConditions(assertion.getConditions());
checkIssuer(assertion.getIssuer(), assertion);
checkSubject(assertion.getSubject(), assertion, allowedSamlRequestIds);
checkAuthnStatement(assertion.getAuthnStatements());

List<Attribute> attributes = new ArrayList<>();
for (AttributeStatement statement : assertion.getAttributeStatements()) {
Expand All @@ -236,6 +238,33 @@ private List<Attribute> processAssertion(Assertion assertion, boolean requireSig
return attributes;
}

private void checkAuthnStatement(List<AuthnStatement> authnStatements) {
if (authnStatements.size() != 1) {
throw samlException("SAML Assertion subject contains {} Authn Statements while exactly one was expected.",
authnStatements.size());
}
final AuthnStatement authnStatement = authnStatements.get(0);
// "past now" that is now - the maximum skew we will tolerate. Essentially "if our clock is 2min fast, what time is it now?"
final Instant now = now();
final Instant pastNow = now.minusMillis(maxSkewInMillis());
if (authnStatement.getSessionNotOnOrAfter() != null &&
pastNow.isBefore(toInstant(authnStatement.getSessionNotOnOrAfter())) == false) {
throw samlException("Rejecting SAML assertion's Authentication Statement because [{}] is on/after [{}]", pastNow,
authnStatement.getSessionNotOnOrAfter());
}
List<String> reqAuthnCtxClassRef = this.getSpConfiguration().getReqAuthnCtxClassRef();
if (reqAuthnCtxClassRef.isEmpty() == false) {
String authnCtxClassRefValue = null;
if (authnStatement.getAuthnContext() != null && authnStatement.getAuthnContext().getAuthnContextClassRef() != null) {
authnCtxClassRefValue = authnStatement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef();
}
if (Strings.isNullOrEmpty(authnCtxClassRefValue) || reqAuthnCtxClassRef.contains(authnCtxClassRefValue) == false) {
throw samlException("Rejecting SAML assertion as the AuthnContextClassRef [{}] is not one of the ({}) that were " +
"requested in the corresponding AuthnRequest", authnCtxClassRefValue, reqAuthnCtxClassRef);
}
}
}

private Attribute decrypt(EncryptedAttribute encrypted) {
if (decrypter == null) {
logger.info("SAML message has encrypted attribute [" + text(encrypted, 32) + "], but no encryption key has been configured");
Expand All @@ -254,7 +283,7 @@ private void checkConditions(Conditions conditions) {
if (logger.isTraceEnabled()) {
logger.trace("SAML Assertion was intended for the following Service providers: {}",
conditions.getAudienceRestrictions().stream().map(r -> text(r, 32))
.collect(Collectors.joining(" | ")));
.collect(Collectors.joining(" | ")));
logger.trace("SAML Assertion is only valid between: " + conditions.getNotBefore() + " and " + conditions.getNotOnOrAfter());
}
checkAudienceRestrictions(conditions.getAudienceRestrictions());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Strings;
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;

import java.time.Clock;

/**
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
*/
Expand Down Expand Up @@ -55,10 +57,27 @@ AuthnRequest build() {
if (nameIdSettings != null) {
request.setNameIDPolicy(buildNameIDPolicy());
}
if (super.serviceProvider.getReqAuthnCtxClassRef().isEmpty() == false) {
request.setRequestedAuthnContext(buildRequestedAuthnContext());
}
request.setForceAuthn(forceAuthn);
return request;
}

private RequestedAuthnContext buildRequestedAuthnContext() {
RequestedAuthnContext requestedAuthnContext = SamlUtils.buildObject(RequestedAuthnContext.class, RequestedAuthnContext
.DEFAULT_ELEMENT_NAME);
for (String authnCtxClass : super.serviceProvider.getReqAuthnCtxClassRef()) {
AuthnContextClassRef authnContextClassRef = SamlUtils.buildObject(AuthnContextClassRef.class, AuthnContextClassRef
.DEFAULT_ELEMENT_NAME);
authnContextClassRef.setAuthnContextClassRef(authnCtxClass);
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
}
// We handle only EXACT comparison
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
return requestedAuthnContext;
}

private NameIDPolicy buildNameIDPolicy() {
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
nameIDPolicy.setFormat(nameIdSettings.format);
Expand Down Expand Up @@ -87,5 +106,4 @@ static class NameIDPolicySettings {
this.spNameQualifier = spNameQualifier;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_ENTITY_ID;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.SP_LOGOUT;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.TYPE;
import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.REQUESTED_AUTHN_CONTEXT_CLASS_REF;

/**
* This class is {@link Releasable} because it uses a library that thinks timers and timer tasks
Expand Down Expand Up @@ -273,8 +274,9 @@ static SpConfiguration getSpConfiguration(RealmConfig config) throws IOException
final String serviceProviderId = require(config, SP_ENTITY_ID);
final String assertionConsumerServiceURL = require(config, SP_ACS);
final String logoutUrl = SP_LOGOUT.get(config.settings());
final List<String> reqAuthnCtxClassRef = REQUESTED_AUTHN_CONTEXT_CLASS_REF.get(config.settings());
return new SpConfiguration(serviceProviderId, assertionConsumerServiceURL,
logoutUrl, buildSigningConfiguration(config), buildEncryptionCredential(config));
logoutUrl, buildSigningConfiguration(config), buildEncryptionCredential(config), reqAuthnCtxClassRef);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ public class SpConfiguration {
private final String ascUrl;
private final String logoutUrl;
private final SigningConfiguration signingConfiguration;
private final List<String> reqAuthnCtxClassRef;
private final List<X509Credential> encryptionCredentials;

public SpConfiguration(final String entityId, final String ascUrl, final String logoutUrl,
final SigningConfiguration signingConfiguration, @Nullable final List<X509Credential> encryptionCredential) {
final SigningConfiguration signingConfiguration, @Nullable final List<X509Credential> encryptionCredential,
final List<String> authnCtxClassRef) {
this.entityId = entityId;
this.ascUrl = ascUrl;
this.logoutUrl = logoutUrl;
Expand All @@ -33,6 +35,7 @@ public SpConfiguration(final String entityId, final String ascUrl, final String
} else {
this.encryptionCredentials = Collections.<X509Credential>emptyList();
}
this.reqAuthnCtxClassRef = authnCtxClassRef;
}

/**
Expand All @@ -57,4 +60,8 @@ List<X509Credential> getEncryptionCredentials() {
SigningConfiguration getSigningConfiguration() {
return signingConfiguration;
}

List<String> getReqAuthnCtxClassRef() {
return reqAuthnCtxClassRef;
}
}
Loading