[Security] Add SAML authentication support (elastic/x-pack-elasticsearch#3646)

Introduces:

- SAML Realm
- REST & Transport actions to support SAML single signon / signout
- Tests for above
- More XML than you ever wanted to see.

Original commit: elastic/x-pack-elasticsearch@b0fe7bb652
This commit is contained in:
Tim Vernum 2018-01-21 08:43:00 +10:00 committed by GitHub
parent 50864eabce
commit 47213f5675
202 changed files with 16282 additions and 574 deletions

View File

@ -49,8 +49,8 @@ dependencies {
//testCompile project(path: ':core:cli', configuration: 'runtime')
testCompile 'org.elasticsearch:securemock:1.2'
testCompile "org.elasticsearch:mocksocket:${versions.mocksocket}"
testCompile 'org.slf4j:slf4j-log4j12:1.6.2'
testCompile 'org.slf4j:slf4j-api:1.6.2'
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
testCompile "org.slf4j:slf4j-api:${versions.slf4j}"
testCompile project(path: ':modules:reindex', configuration: 'runtime')
testCompile project(path: ':modules:parent-join', configuration: 'runtime')
testCompile project(path: ':modules:analysis-common', configuration: 'runtime')

View File

@ -13,3 +13,10 @@ logger.xpack_security_audit_logfile.name = org.elasticsearch.xpack.security.audi
logger.xpack_security_audit_logfile.level = info
logger.xpack_security_audit_logfile.appenderRef.audit_rolling.ref = audit_rolling
logger.xpack_security_audit_logfile.additivity = false
logger.xmlsig.name = org.apache.xml.security.signature.XMLSignature
logger.xmlsig.level = error
logger.samlxml_decrypt.name = org.opensaml.xmlsec.encryption.support.Decrypter
logger.samlxml_decrypt.level = fatal
logger.saml2_decrypt.name = org.opensaml.saml.saml2.encryption.Decrypter
logger.saml2_decrypt.level = fatal

View File

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Action for authenticating using SAML assertions
*/
public final class SamlAuthenticateAction
extends Action<SamlAuthenticateRequest, SamlAuthenticateResponse, SamlAuthenticateRequestBuilder> {
public static final String NAME = "cluster:admin/xpack/security/saml/authenticate";
public static final SamlAuthenticateAction INSTANCE = new SamlAuthenticateAction();
private SamlAuthenticateAction() {
super(NAME);
}
@Override
public SamlAuthenticateRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new SamlAuthenticateRequestBuilder(client);
}
@Override
public SamlAuthenticateResponse newResponse() {
return new SamlAuthenticateResponse();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.util.List;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
/**
* Represents a request to authenticate using SAML assertions.
*/
public final class SamlAuthenticateRequest extends ActionRequest {
private byte[] saml;
private List<String> validRequestIds;
public SamlAuthenticateRequest() {
}
@Override
public ActionRequestValidationException validate() {
return null;
}
public byte[] getSaml() {
return saml;
}
public void setSaml(byte[] saml) {
this.saml = saml;
}
public List<String> getValidRequestIds() {
return validRequestIds;
}
public void setValidRequestIds(List<String> validRequestIds) {
this.validRequestIds = validRequestIds;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.util.List;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Request builder used to populate a {@link SamlAuthenticateRequest}
*/
public final class SamlAuthenticateRequestBuilder
extends ActionRequestBuilder<SamlAuthenticateRequest, SamlAuthenticateResponse, SamlAuthenticateRequestBuilder> {
public SamlAuthenticateRequestBuilder(ElasticsearchClient client) {
super(client, SamlAuthenticateAction.INSTANCE, new SamlAuthenticateRequest());
}
public SamlAuthenticateRequestBuilder saml(byte[] saml) {
request.setSaml(saml);
return this;
}
public SamlAuthenticateRequestBuilder validRequestIds(List<String> validRequestIds) {
request.setValidRequestIds(validRequestIds);
return this;
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
/**
* The response from converting a SAML assertion into a security token.
* Actually nothing SAML specific in this...
*/
public final class SamlAuthenticateResponse extends ActionResponse {
private String principal;
private String tokenString;
private String refreshToken;
private TimeValue expiresIn;
public SamlAuthenticateResponse() {
}
public SamlAuthenticateResponse(String principal, String tokenString, String refreshToken, TimeValue expiresIn) {
this.principal = principal;
this.tokenString = tokenString;
this.refreshToken = refreshToken;
this.expiresIn = expiresIn;
}
public String getPrincipal() {
return principal;
}
public String getTokenString() {
return tokenString;
}
public String getRefreshToken() {
return refreshToken;
}
public TimeValue getExpiresIn() {
return expiresIn;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(principal);
out.writeString(tokenString);
out.writeString(refreshToken);
expiresIn.writeTo(out);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
principal = in.readString();
tokenString = in.readString();
refreshToken = in.readString();
expiresIn = new TimeValue(in);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Action to perform IdP-initiated logout for a SAML-SSO user
*/
public final class SamlInvalidateSessionAction
extends Action<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse, SamlInvalidateSessionRequestBuilder> {
public static final String NAME = "cluster:admin/xpack/security/saml/invalidate";
public static final SamlInvalidateSessionAction INSTANCE = new SamlInvalidateSessionAction();
private SamlInvalidateSessionAction() {
super(NAME);
}
@Override
public SamlInvalidateSessionRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new SamlInvalidateSessionRequestBuilder(client);
}
@Override
public SamlInvalidateSessionResponse newResponse() {
return new SamlInvalidateSessionResponse();
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* Represents a request to invalidate a SAML session using a {@code LogoutRequest}.
*/
public final class SamlInvalidateSessionRequest extends ActionRequest {
@Nullable
private String realmName;
@Nullable
private String assertionConsumerServiceURL;
private String queryString;
public SamlInvalidateSessionRequest() {
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(queryString)) {
validationException = addValidationError("queryString is missing", validationException);
}
return validationException;
}
public String getQueryString() {
return queryString;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
public String getRealmName() {
return realmName;
}
public void setRealmName(String realmName) {
this.realmName = realmName;
}
public String getAssertionConsumerServiceURL() {
return assertionConsumerServiceURL;
}
public void setAssertionConsumerServiceURL(String assertionConsumerServiceURL) {
this.assertionConsumerServiceURL = assertionConsumerServiceURL;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"realmName='" + realmName + '\'' +
", assertionConsumerServiceURL='" + assertionConsumerServiceURL + '\'' +
", url-query=" + queryString.length() + " chars" +
'}';
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Request builder used to populate a {@link SamlInvalidateSessionRequest}
*/
public final class SamlInvalidateSessionRequestBuilder
extends ActionRequestBuilder<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse, SamlInvalidateSessionRequestBuilder> {
public SamlInvalidateSessionRequestBuilder(ElasticsearchClient client) {
super(client, SamlInvalidateSessionAction.INSTANCE, new SamlInvalidateSessionRequest());
}
public SamlInvalidateSessionRequestBuilder queryString(String query) {
request.setQueryString(query);
return this;
}
public SamlInvalidateSessionRequestBuilder realmName(String name) {
request.setRealmName(name);
return this;
}
public SamlInvalidateSessionRequestBuilder assertionConsumerService(String url) {
request.setAssertionConsumerServiceURL(url);
return this;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Response to an IdP-initiated SAML {@code &lt;LogoutRequest&gt;}
*/
public final class SamlInvalidateSessionResponse extends ActionResponse {
private String realmName;
private int count;
private String redirectUrl;
public SamlInvalidateSessionResponse() {
}
public SamlInvalidateSessionResponse(String realmName, int count, String redirectUrl) {
this.realmName = realmName;
this.count = count;
this.redirectUrl = redirectUrl;
}
public String getRealmName() {
return realmName;
}
public int getCount() {
return count;
}
public String getRedirectUrl() {
return redirectUrl;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(realmName);
out.writeInt(count);
out.writeString(redirectUrl);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
realmName = in.readString();
count = in.readInt();
redirectUrl = in.readString();
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Action for initiating a logout process for a SAML-SSO user
*/
public final class SamlLogoutAction extends Action<SamlLogoutRequest, SamlLogoutResponse, SamlLogoutRequestBuilder> {
public static final String NAME = "cluster:admin/xpack/security/saml/logout";
public static final SamlLogoutAction INSTANCE = new SamlLogoutAction();
private SamlLogoutAction() {
super(NAME);
}
@Override
public SamlLogoutRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new SamlLogoutRequestBuilder(client);
}
@Override
public SamlLogoutResponse newResponse() {
return new SamlLogoutResponse();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* Represents a request to prepare a SAML {@code &lt;LogoutRequest&gt;}.
*/
public final class SamlLogoutRequest extends ActionRequest {
private String token;
@Nullable
private String refreshToken;
public SamlLogoutRequest() {
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(token)) {
validationException = addValidationError("token is missing", validationException);
}
return validationException;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Request builder used to populate a {@link SamlLogoutRequest}
*/
public final class SamlLogoutRequestBuilder extends ActionRequestBuilder<SamlLogoutRequest,
SamlLogoutResponse, SamlLogoutRequestBuilder> {
public SamlLogoutRequestBuilder(ElasticsearchClient client) {
super(client, SamlLogoutAction.INSTANCE, new SamlLogoutRequest());
}
public SamlLogoutRequestBuilder token(String token) {
request.setToken(token);
return this;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Response containing a SAML {@code &lt;LogoutRequest&gt;} for the current user
*/
public final class SamlLogoutResponse extends ActionResponse {
private String redirectUrl;
public SamlLogoutResponse() {
}
public SamlLogoutResponse(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public String getRedirectUrl() {
return redirectUrl;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(redirectUrl);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
redirectUrl = in.readString();
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Action for initiating an authentication process using SAML assertions
*/
public final class SamlPrepareAuthenticationAction
extends Action<SamlPrepareAuthenticationRequest, SamlPrepareAuthenticationResponse, SamlPrepareAuthenticationRequestBuilder> {
public static final String NAME = "cluster:admin/xpack/security/saml/prepare";
public static final SamlPrepareAuthenticationAction INSTANCE = new SamlPrepareAuthenticationAction();
private SamlPrepareAuthenticationAction() {
super(NAME);
}
@Override
public SamlPrepareAuthenticationRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new SamlPrepareAuthenticationRequestBuilder(client);
}
@Override
public SamlPrepareAuthenticationResponse newResponse() {
return new SamlPrepareAuthenticationResponse();
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Represents a request to prepare a SAML {@code &lt;AuthnRequest&gt;}.
*/
public final class SamlPrepareAuthenticationRequest extends ActionRequest {
@Nullable
private String realmName;
@Nullable
private String assertionConsumerServiceURL;
public SamlPrepareAuthenticationRequest() {
}
@Override
public ActionRequestValidationException validate() {
return null;
}
public String getRealmName() {
return realmName;
}
public void setRealmName(String realmName) {
this.realmName = realmName;
}
public String getAssertionConsumerServiceURL() {
return assertionConsumerServiceURL;
}
public void setAssertionConsumerServiceURL(String assertionConsumerServiceURL) {
this.assertionConsumerServiceURL = assertionConsumerServiceURL;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"realmName=" + realmName +
", assertionConsumerServiceURL=" + assertionConsumerServiceURL +
'}';
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
realmName = in.readOptionalString();
assertionConsumerServiceURL = in.readOptionalString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(realmName);
out.writeOptionalString(assertionConsumerServiceURL);
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Request builder used to populate a {@link SamlPrepareAuthenticationRequest}
*/
public final class SamlPrepareAuthenticationRequestBuilder extends ActionRequestBuilder<SamlPrepareAuthenticationRequest,
SamlPrepareAuthenticationResponse, SamlPrepareAuthenticationRequestBuilder> {
public SamlPrepareAuthenticationRequestBuilder(ElasticsearchClient client) {
super(client, SamlPrepareAuthenticationAction.INSTANCE, new SamlPrepareAuthenticationRequest());
}
public SamlPrepareAuthenticationRequestBuilder realmName(String name) {
request.setRealmName(name);
return this;
}
public SamlPrepareAuthenticationRequestBuilder assertionConsumerService(String url) {
request.setAssertionConsumerServiceURL(url);
return this;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Response containing a SAML {@code &lt;AuthnRequest&gt;} for a specific realm.
*/
public final class SamlPrepareAuthenticationResponse extends ActionResponse {
private String realmName;
private String requestId;
private String redirectUrl;
public SamlPrepareAuthenticationResponse() {
}
public SamlPrepareAuthenticationResponse(String realmName, String requestId, String redirectUrl) {
this.realmName = realmName;
this.requestId = requestId;
this.redirectUrl = redirectUrl;
}
public String getRealmName() {
return realmName;
}
public String getRequestId() {
return requestId;
}
public String getRedirectUrl() {
return redirectUrl;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(redirectUrl);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
redirectUrl = in.readString();
}
}

View File

@ -5,8 +5,6 @@
*/
package org.elasticsearch.xpack.security.action.user;
import java.util.Collections;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;

View File

@ -10,6 +10,7 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.security.authc.saml.SamlRealmSettings;
import java.util.Collections;
import java.util.HashMap;
@ -31,6 +32,7 @@ public final class InternalRealmsSettings {
map.put(LdapRealmSettings.AD_TYPE, LdapRealmSettings.getSettings(LdapRealmSettings.AD_TYPE));
map.put(LdapRealmSettings.LDAP_TYPE, LdapRealmSettings.getSettings(LdapRealmSettings.LDAP_TYPE));
map.put(PkiRealmSettings.TYPE, PkiRealmSettings.getSettings());
map.put(SamlRealmSettings.TYPE, SamlRealmSettings.getSettings());
return Collections.unmodifiableMap(map);
}
}

View File

@ -154,13 +154,17 @@ public class RealmSettings {
}
private static void validateRealm(String name, Settings settings, Map<String, Set<Setting<?>>> validSettings) {
final String type = TYPE_SETTING.get(settings);
final String type = getRealmType(settings);
if (isNullOrEmpty(type)) {
throw new IllegalArgumentException("missing realm type [" + getFullSettingKey(name, TYPE_SETTING) + "] for realm");
}
validateRealm(name, type, settings, validSettings.get(type));
}
public static String getRealmType(Settings settings) {
return TYPE_SETTING.get(settings);
}
private static void validateRealm(String name, String type, Settings settings, Set<Setting<?>> validSettings) {
if (validSettings == null) {
// For backwards compatibility, we assume that is we don't know the valid settings for a realm.type then everything

View File

@ -0,0 +1,129 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.xpack.ssl.SSLConfigurationSettings;
import org.elasticsearch.xpack.ssl.X509KeyPairSettings;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
public class SamlRealmSettings {
public static final String TYPE = "saml";
private static final String TRANSIENT_NAMEID_FORMAT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
// these settings will be used under the prefix xpack.security.authc.realms.REALM_NAME.
private static final String IDP_METADATA_SETTING_PREFIX = "idp.metadata.";
public static final Setting<String> IDP_ENTITY_ID = Setting.simpleString("idp.entity_id", Setting.Property.NodeScope);
public static final Setting<String> IDP_METADATA_PATH
= Setting.simpleString(IDP_METADATA_SETTING_PREFIX + "path", Setting.Property.NodeScope);
public static final Setting<TimeValue> IDP_METADATA_HTTP_REFRESH
= Setting.timeSetting(IDP_METADATA_SETTING_PREFIX + "http.refresh", TimeValue.timeValueHours(1), Setting.Property.NodeScope);
public static final Setting<Boolean> IDP_SINGLE_LOGOUT = Setting.boolSetting("idp.use_single_logout", true, Setting.Property.NodeScope);
public static final Setting<String> SP_ENTITY_ID = Setting.simpleString("sp.entity_id", Setting.Property.NodeScope);
public static final Setting<String> SP_ACS = Setting.simpleString("sp.acs", Setting.Property.NodeScope);
public static final Setting<String> SP_LOGOUT = Setting.simpleString("sp.logout", Setting.Property.NodeScope);
public static final Setting<String> NAMEID_FORMAT = new Setting<>("nameid_format", s -> TRANSIENT_NAMEID_FORMAT, Function.identity(),
Setting.Property.NodeScope);
public static final Setting<Boolean> FORCE_AUTHN = Setting.boolSetting("force_authn", false, Setting.Property.NodeScope);
public static final Setting<Boolean> POPULATE_USER_METADATA = Setting.boolSetting("populate_user_metadata", true,
Setting.Property.NodeScope);
public static final AttributeSetting PRINCIPAL_ATTRIBUTE = new AttributeSetting("principal");
public static final AttributeSetting GROUPS_ATTRIBUTE = new AttributeSetting("groups");
public static final AttributeSetting DN_ATTRIBUTE = new AttributeSetting("dn");
public static final AttributeSetting NAME_ATTRIBUTE = new AttributeSetting("name");
public static final AttributeSetting MAIL_ATTRIBUTE = new AttributeSetting("mail");
public static final X509KeyPairSettings ENCRYPTION_SETTINGS = new X509KeyPairSettings("encryption.", false);
public static final Setting<String> ENCRYPTION_KEY_ALIAS = new Setting<>("encryption.keystore.alias", "key", Function.identity(),
Setting.Property.NodeScope);
public static final X509KeyPairSettings SIGNING_SETTINGS = new X509KeyPairSettings("signing.", false);
public static final Setting<String> SIGNING_KEY_ALIAS = new Setting<>("signing.keystore.alias", "key", Function.identity(),
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<TimeValue> CLOCK_SKEW = Setting.positiveTimeSetting("allowed_clock_skew", TimeValue.timeValueMinutes(3),
Setting.Property.NodeScope);
public static final String SSL_PREFIX = "ssl.";
private SamlRealmSettings() {
}
/**
* @return The {@link Setting setting configuration} for this realm type
*/
public static Set<Setting<?>> getSettings() {
final Set<Setting<?>> set = Sets.newHashSet(IDP_ENTITY_ID, IDP_METADATA_PATH,
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
NAMEID_FORMAT, FORCE_AUTHN,
CLOCK_SKEW,
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES);
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
set.addAll(SIGNING_SETTINGS.getAllSettings());
set.addAll(SSLConfigurationSettings.withPrefix(SSL_PREFIX).getAllSettings());
set.addAll(PRINCIPAL_ATTRIBUTE.settings());
set.addAll(GROUPS_ATTRIBUTE.settings());
set.addAll(DN_ATTRIBUTE.settings());
set.addAll(NAME_ATTRIBUTE.settings());
set.addAll(MAIL_ATTRIBUTE.settings());
return set;
}
/**
* The SAML realm offers a number of settings that rely on attributes that are populate by the Identity Provider in the SAML Response.
* Each attribute has 2 settings:
* <ul>
* <li>The name of the SAML attribute to use</li>
* <li>A java pattern (regex) to apply to that attribute value in order to extract the substring that should be used.</li>
* </ul>
* For example, the Elasticsearch User Principal could be configured to come from the SAML "mail" attribute, and extract only the
* local-port of the user's email address (i.e. the name before the '@').
* This class encapsulates those 2 settings.
*/
public static final class AttributeSetting {
static final String ATTRIBUTES_PREFIX = "attributes.";
static final String ATTRIBUTE_PATTERNS_PREFIX = "attribute_patterns.";
private final Setting<String> attribute;
private final Setting<String> pattern;
AttributeSetting(String name) {
attribute = Setting.simpleString(ATTRIBUTES_PREFIX + name, Setting.Property.NodeScope);
pattern = Setting.simpleString(ATTRIBUTE_PATTERNS_PREFIX + name, Setting.Property.NodeScope);
}
public Collection<Setting<?>> settings() {
return Arrays.asList(getAttribute(), getPattern());
}
public String name() {
return getAttribute().getKey();
}
public Setting<String> getAttribute() {
return attribute;
}
public Setting<String> getPattern() {
return pattern;
}
}
}

View File

@ -5,11 +5,6 @@
*/
package org.elasticsearch.xpack.security.authz.privilege;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.xpack.security.support.Automatons;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
@ -18,6 +13,13 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.xpack.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.security.action.token.RefreshTokenAction;
import org.elasticsearch.xpack.security.support.Automatons;
import static org.elasticsearch.xpack.security.support.Automatons.minusAndMinimize;
import static org.elasticsearch.xpack.security.support.Automatons.patterns;
@ -25,6 +27,8 @@ public final class ClusterPrivilege extends Privilege {
// shared automatons
private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*");
private static final Automaton MANAGE_SAML_AUTOMATON = patterns("cluster:admin/xpack/security/saml/*",
InvalidateTokenAction.NAME, RefreshTokenAction.NAME);
private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*");
private static final Automaton MONITOR_ML_AUTOMATON = patterns("cluster:monitor/xpack/ml/*");
private static final Automaton MONITOR_WATCHER_AUTOMATON = patterns("cluster:monitor/xpack/watcher/*");
@ -50,6 +54,7 @@ public final class ClusterPrivilege extends Privilege {
new ClusterPrivilege("manage_ingest_pipelines", MANAGE_INGEST_PIPELINE_AUTOMATON);
public static final ClusterPrivilege TRANSPORT_CLIENT = new ClusterPrivilege("transport_client", TRANSPORT_CLIENT_AUTOMATON);
public static final ClusterPrivilege MANAGE_SECURITY = new ClusterPrivilege("manage_security", MANAGE_SECURITY_AUTOMATON);
public static final ClusterPrivilege MANAGE_SAML = new ClusterPrivilege("manage_saml", MANAGE_SAML_AUTOMATON);
public static final ClusterPrivilege MANAGE_PIPELINE = new ClusterPrivilege("manage_pipeline", "cluster:admin/ingest/pipeline/*");
public static final Predicate<String> ACTION_MATCHER = ClusterPrivilege.ALL.predicate();
@ -67,6 +72,7 @@ public final class ClusterPrivilege extends Privilege {
.put("manage_ingest_pipelines", MANAGE_INGEST_PIPELINES)
.put("transport_client", TRANSPORT_CLIENT)
.put("manage_security", MANAGE_SECURITY)
.put("manage_saml", MANAGE_SAML)
.put("manage_pipeline", MANAGE_PIPELINE)
.immutableMap();

View File

@ -5,6 +5,11 @@
*/
package org.elasticsearch.xpack.security.authz.store;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
@ -74,7 +79,7 @@ public class ReservedRolesStore {
null,
MetadataUtils.DEFAULT_RESERVED_METADATA))
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME },
new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(),
RoleDescriptor.IndicesPrivileges.builder()
@ -130,4 +135,4 @@ public class ReservedRolesStore {
public static Set<String> names() {
return RESERVED_ROLES.keySet();
}
}
}

View File

@ -38,6 +38,11 @@ import org.elasticsearch.xpack.security.action.rolemapping.GetRoleMappingsReques
import org.elasticsearch.xpack.security.action.rolemapping.GetRoleMappingsResponse;
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingAction;
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingRequestBuilder;
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateRequest;
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateRequestBuilder;
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateResponse;
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateAction;
import org.elasticsearch.xpack.security.action.saml.SamlPrepareAuthenticationRequestBuilder;
import org.elasticsearch.xpack.security.action.token.CreateTokenAction;
import org.elasticsearch.xpack.security.action.token.CreateTokenRequest;
import org.elasticsearch.xpack.security.action.token.CreateTokenRequestBuilder;
@ -73,6 +78,7 @@ import org.elasticsearch.xpack.security.action.user.SetEnabledRequestBuilder;
import org.elasticsearch.xpack.security.action.user.SetEnabledResponse;
import java.io.IOException;
import java.util.List;
/**
* A wrapper to elasticsearch clients that exposes all Security related APIs
@ -286,6 +292,21 @@ public class SecurityClient {
client.execute(InvalidateTokenAction.INSTANCE, request, listener);
}
public SamlAuthenticateRequestBuilder prepareSamlAuthenticate(byte[] xmlContent, List<String> validIds) {
final SamlAuthenticateRequestBuilder builder = new SamlAuthenticateRequestBuilder(client);
builder.saml(xmlContent);
builder.validRequestIds(validIds);
return builder;
}
public void samlAuthenticate(SamlAuthenticateRequest request, ActionListener< SamlAuthenticateResponse> listener) {
client.execute(SamlAuthenticateAction.INSTANCE, request, listener);
}
public SamlPrepareAuthenticationRequestBuilder prepareSamlPrepareAuthentication() {
return new SamlPrepareAuthenticationRequestBuilder(client);
}
public CreateTokenRequestBuilder prepareRefreshToken(String refreshToken) {
return new CreateTokenRequestBuilder(client, RefreshTokenAction.INSTANCE)
.setRefreshToken(refreshToken)

View File

@ -16,12 +16,15 @@ public class MetadataUtils {
public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Collections.singletonMap(RESERVED_METADATA_KEY, true);
private MetadataUtils() {}
private MetadataUtils() {
}
public static void writeValue(StringBuilder sb, Object object) {
if (object instanceof Map) {
if (object == null) {
sb.append(object);
} else if (object instanceof Map) {
sb.append("{");
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) object).entrySet()) {
sb.append(entry.getKey()).append("=");
writeValue(sb, entry.getValue());
}

View File

@ -86,10 +86,14 @@ import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.network.InetAddressHelper;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
/**
* Utility methods that deal with {@link Certificate}, {@link KeyStore}, {@link X509ExtendedTrustManager}, {@link X509ExtendedKeyManager}
* and other certificate related objects.
@ -164,6 +168,52 @@ public class CertUtils {
throw new IllegalStateException("failed to find a X509ExtendedKeyManager");
}
public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair, Settings settings,
@Nullable String trustStoreAlgorithm, Environment environment) {
if (trustStoreAlgorithm == null) {
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
}
final KeyConfig keyConfig = createKeyConfig(keyPair, settings, trustStoreAlgorithm);
if (keyConfig == null) {
return null;
} else {
return keyConfig.createKeyManager(environment);
}
}
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
String keyPath = keyPair.keyPath.get(settings).orElse(null);
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
if (keyPath != null && keyStorePath != null) {
throw new IllegalArgumentException("you cannot specify a keystore and key file");
}
if (keyPath != null) {
SecureString keyPassword = keyPair.keyPassword.get(settings);
String certPath = keyPair.certificatePath.get(settings).orElse(null);
if (certPath == null) {
throw new IllegalArgumentException("you must specify the certificates [" + keyPair.certificatePath.getKey()
+ "] to use with the key [" + keyPair.keyPath.getKey() + "]");
}
return new PEMKeyConfig(keyPath, keyPassword, certPath);
}
if (keyStorePath != null) {
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
if (keyStoreKeyPassword.length() == 0) {
keyStoreKeyPassword = keyStorePassword;
}
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
trustStoreAlgorithm);
}
return null;
}
/**
* Creates a {@link X509ExtendedTrustManager} based on the provided certificates
*
@ -244,7 +294,7 @@ public class CertUtils {
return readCertificates(resolvedPaths);
}
static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
public static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
List<Certificate> certificates = new ArrayList<>(certPaths.size());
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
for (Path path : certPaths) {

View File

@ -7,13 +7,6 @@ package org.elasticsearch.xpack.ssl;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.SecureString;
@ -23,6 +16,16 @@ import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
import java.io.IOException;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents the configuration for an SSLContext
*/
@ -47,7 +50,7 @@ public final class SSLConfiguration {
* @param settings the SSL specific settings; only the settings under a *.ssl. prefix
*/
SSLConfiguration(Settings settings) {
this.keyConfig = createKeyConfig(settings, null);
this.keyConfig = createKeyConfig(settings, (SSLConfiguration) null);
this.trustConfig = createTrustConfig(settings, keyConfig, null);
this.ciphers = getListOrDefault(SETTINGS_PARSER.ciphers, settings, XPackSettings.DEFAULT_CIPHERS);
this.supportedProtocols = getListOrDefault(SETTINGS_PARSER.supportedProtocols, settings, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS);
@ -180,43 +183,23 @@ public final class SSLConfiguration {
}
private static KeyConfig createKeyConfig(Settings settings, SSLConfiguration global) {
String keyStorePath = SETTINGS_PARSER.keystorePath.get(settings).orElse(null);
String keyPath = SETTINGS_PARSER.keyPath.get(settings).orElse(null);
if (keyPath != null && keyStorePath != null) {
throw new IllegalArgumentException("you cannot specify a keystore and key file");
} else if (keyStorePath == null && keyPath == null) {
if (global != null) {
return global.keyConfig();
} else if (System.getProperty("javax.net.ssl.keyStore") != null) {
// TODO: we should not support loading a keystore from sysprops...
try (SecureString keystorePassword = new SecureString(System.getProperty("javax.net.ssl.keyStorePassword", ""))) {
return new StoreKeyConfig(System.getProperty("javax.net.ssl.keyStore"), "jks", keystorePassword, keystorePassword,
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
}
}
return KeyConfig.NONE;
final String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
final KeyConfig config = CertUtils.createKeyConfig(SETTINGS_PARSER.x509KeyPair, settings, trustStoreAlgorithm);
if (config != null) {
return config;
}
if (keyPath != null) {
SecureString keyPassword = SETTINGS_PARSER.keyPassword.get(settings);
String certPath = SETTINGS_PARSER.cert.get(settings).orElse(null);
if (certPath == null) {
throw new IllegalArgumentException("you must specify the certificates to use with the key");
}
return new PEMKeyConfig(keyPath, keyPassword, certPath);
} else {
SecureString keyStorePassword = SETTINGS_PARSER.keystorePassword.get(settings);
String keyStoreAlgorithm = SETTINGS_PARSER.keystoreAlgorithm.get(settings);
String keyStoreType = SSLConfigurationSettings.getKeyStoreType(SETTINGS_PARSER.keystoreType, settings, keyStorePath);
SecureString keyStoreKeyPassword = SETTINGS_PARSER.keystoreKeyPassword.get(settings);
if (keyStoreKeyPassword.length() == 0) {
keyStoreKeyPassword = keyStorePassword;
}
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
trustStoreAlgorithm);
if (global != null) {
return global.keyConfig();
}
if (System.getProperty("javax.net.ssl.keyStore") != null) {
// TODO: we should not support loading a keystore from sysprops...
try (SecureString keystorePassword = new SecureString(System.getProperty("javax.net.ssl.keyStorePassword", ""))) {
return new StoreKeyConfig(System.getProperty("javax.net.ssl.keyStore"), "jks", keystorePassword, keystorePassword,
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
}
}
return KeyConfig.NONE;
}
private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) {
@ -247,7 +230,7 @@ public final class SSLConfiguration {
} else if (trustStorePath != null) {
SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
String trustStoreType = SSLConfigurationSettings.getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
return new StoreTrustConfig(trustStorePath, trustStoreType, trustStorePassword, trustStoreAlgorithm);
} else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null) {
try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.xpack.ssl;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.util.Arrays;
@ -21,27 +20,23 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.CollectionUtils;
/**
* Bridges SSLConfiguration into the {@link Settings} framework, using {@link Setting} objects.
*/
public class SSLConfigurationSettings {
final X509KeyPairSettings x509KeyPair;
public final Setting<List<String>> ciphers;
public final Setting<List<String>> supportedProtocols;
public final Setting<Optional<String>> keystorePath;
public final Setting<SecureString> keystorePassword;
public final Setting<String> keystoreAlgorithm;
public final Setting<Optional<String>> keystoreType;
public final Setting<SecureString> keystoreKeyPassword;
public final Setting<Optional<String>> truststorePath;
public final Setting<SecureString> truststorePassword;
public final Setting<String> truststoreAlgorithm;
public final Setting<Optional<String>> truststoreType;
public final Setting<Optional<String>> trustRestrictionsPath;
public final Setting<Optional<String>> keyPath;
public final Setting<SecureString> keyPassword;
public final Setting<Optional<String>> cert;
public final Setting<List<String>> caPaths;
public final Setting<Optional<SSLClientAuth>> clientAuth;
public final Setting<Optional<VerificationMode>> verificationMode;
@ -49,11 +44,6 @@ public class SSLConfigurationSettings {
// public for PKI realm
public final Setting<SecureString> legacyTruststorePassword;
// pkg private for tests
final Setting<SecureString> legacyKeystorePassword;
final Setting<SecureString> legacyKeystoreKeyPassword;
final Setting<SecureString> legacyKeyPassword;
private final List<Setting<?>> allSettings;
/**
@ -73,41 +63,28 @@ public class SSLConfigurationSettings {
public static final Setting<List<String>> SUPPORTED_PROTOCOLS_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.supported_protocols", SUPPORTED_PROTOCOLS_TEMPLATE) ;
private static final Function<String, Setting<Optional<String>>> KEYSTORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Property.NodeScope, Property.Filtered);
public static final Setting<Optional<String>> KEYSTORE_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.path", KEYSTORE_PATH_TEMPLATE);
"xpack.security.ssl.keystore.path", X509KeyPairSettings.KEYSTORE_PATH_TEMPLATE);
private static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
public static final Setting<SecureString> LEGACY_KEYSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.password", LEGACY_KEYSTORE_PASSWORD_TEMPLATE);
"xpack.security.ssl.keystore.password", X509KeyPairSettings.LEGACY_KEYSTORE_PASSWORD_TEMPLATE);
private static final Function<String, Setting<SecureString>> KEYSTORE_PASSWORD_TEMPLATE = key -> SecureSetting.secureString(key,
LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_password", "keystore.password")));
public static final Setting<SecureString> KEYSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.secure_password", KEYSTORE_PASSWORD_TEMPLATE);
"xpack.security.ssl.keystore.secure_password", X509KeyPairSettings.KEYSTORE_PASSWORD_TEMPLATE);
private static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
public static final Setting<SecureString> LEGACY_KEYSTORE_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.key_password", LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE);
"xpack.security.ssl.keystore.key_password", X509KeyPairSettings.LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE);
private static final Function<String, Setting<SecureString>> KEYSTORE_KEY_PASSWORD_TEMPLATE = key ->
SecureSetting.secureString(key, LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_key_password",
"keystore.key_password")));
public static final Setting<SecureString> KEYSTORE_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.secure_key_password", KEYSTORE_KEY_PASSWORD_TEMPLATE);
"xpack.security.ssl.keystore.secure_key_password", X509KeyPairSettings.KEYSTORE_KEY_PASSWORD_TEMPLATE);
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Property.NodeScope, Property.Filtered);
public static final Setting<Optional<String>> TRUST_STORE_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.truststore.path", TRUST_STORE_PATH_TEMPLATE);
private static final Function<String, Setting<Optional<String>>> KEY_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Property.NodeScope, Property.Filtered);
public static final Setting<Optional<String>> KEY_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.key", KEY_PATH_TEMPLATE);
"xpack.security.ssl.key", X509KeyPairSettings.KEY_PATH_TEMPLATE);
private static final Function<String, Setting<SecureString>> LEGACY_TRUSTSTORE_PASSWORD_TEMPLATE = key ->
new Setting<>(key, "", SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
@ -120,11 +97,8 @@ public class SSLConfigurationSettings {
public static final Setting<SecureString> TRUSTSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.truststore.secure_password", TRUSTSTORE_PASSWORD_TEMPLATE);
private static final Function<String, Setting<String>> KEY_STORE_ALGORITHM_TEMPLATE = key ->
new Setting<>(key, s -> KeyManagerFactory.getDefaultAlgorithm(),
Function.identity(), Property.NodeScope, Property.Filtered);
public static final Setting<String> KEY_STORE_ALGORITHM_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.algorithm", KEY_STORE_ALGORITHM_TEMPLATE);
"xpack.security.ssl.keystore.algorithm", X509KeyPairSettings.KEY_STORE_ALGORITHM_TEMPLATE);
private static final Function<String, Setting<String>> TRUST_STORE_ALGORITHM_TEMPLATE = key ->
new Setting<>(key, s -> TrustManagerFactory.getDefaultAlgorithm(),
@ -132,12 +106,11 @@ public class SSLConfigurationSettings {
public static final Setting<String> TRUST_STORE_ALGORITHM_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.truststore.algorithm", TRUST_STORE_ALGORITHM_TEMPLATE);
private static final Function<String, Setting<Optional<String>>> KEY_STORE_TYPE_TEMPLATE = key ->
new Setting<>(key, s -> null, Optional::ofNullable, Property.NodeScope, Property.Filtered);
public static final Setting<Optional<String>> KEY_STORE_TYPE_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.keystore.type", KEY_STORE_TYPE_TEMPLATE);
"xpack.security.ssl.keystore.type", X509KeyPairSettings.KEY_STORE_TYPE_TEMPLATE);
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_TYPE_TEMPLATE = KEY_STORE_TYPE_TEMPLATE;
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_TYPE_TEMPLATE =
X509KeyPairSettings.KEY_STORE_TYPE_TEMPLATE;
public static final Setting<Optional<String>> TRUST_STORE_TYPE_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.truststore.type", TRUST_STORE_TYPE_TEMPLATE);
@ -146,21 +119,14 @@ public class SSLConfigurationSettings {
public static final Setting<Optional<String>> TRUST_RESTRICTIONS_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.trust_restrictions", TRUST_RESTRICTIONS_TEMPLATE);
private static final Function<String, Setting<SecureString>> LEGACY_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
public static final Setting<SecureString> LEGACY_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.key_passphrase", LEGACY_KEY_PASSWORD_TEMPLATE);
"xpack.security.ssl.key_passphrase", X509KeyPairSettings.LEGACY_KEY_PASSWORD_TEMPLATE);
private static final Function<String, Setting<SecureString>> KEY_PASSWORD_TEMPLATE = key ->
SecureSetting.secureString(key, LEGACY_KEY_PASSWORD_TEMPLATE.apply(key.replace("secure_key_passphrase",
"key_passphrase")));
public static final Setting<SecureString> KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.secure_key_passphrase", KEY_PASSWORD_TEMPLATE);
"xpack.security.ssl.secure_key_passphrase", X509KeyPairSettings.KEY_PASSWORD_TEMPLATE);
private static final Function<String, Setting<Optional<String>>> CERT_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Property.NodeScope, Property.Filtered);
public static final Setting<Optional<String>> CERT_PROFILES = Setting.affixKeySetting("transport.profiles.",
"xpack.security.ssl.certificate", CERT_TEMPLATE);
"xpack.security.ssl.certificate", X509KeyPairSettings.CERT_TEMPLATE);
private static final Function<String, Setting<List<String>>> CAPATH_SETTING_TEMPLATE = key -> Setting.listSetting(key, Collections
.emptyList(), Function.identity(), Property.NodeScope, Property.Filtered);
@ -187,34 +153,25 @@ public class SSLConfigurationSettings {
*/
private SSLConfigurationSettings(String prefix) {
assert prefix != null : "Prefix cannot be null (but can be blank)";
x509KeyPair = new X509KeyPairSettings(prefix, true);
ciphers = CIPHERS_SETTING_TEMPLATE.apply(prefix + "cipher_suites");
supportedProtocols = SUPPORTED_PROTOCOLS_TEMPLATE.apply(prefix + "supported_protocols");
keystorePath = KEYSTORE_PATH_TEMPLATE.apply(prefix + "keystore.path");
legacyKeystorePassword = LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.password");
keystorePassword = KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_password");
legacyKeystoreKeyPassword = LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.key_password");
keystoreKeyPassword = KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_key_password");
truststorePath = TRUST_STORE_PATH_TEMPLATE.apply(prefix + "truststore.path");
legacyTruststorePassword = LEGACY_TRUSTSTORE_PASSWORD_TEMPLATE.apply(prefix + "truststore.password");
truststorePassword = TRUSTSTORE_PASSWORD_TEMPLATE.apply(prefix + "truststore.secure_password");
keystoreAlgorithm = KEY_STORE_ALGORITHM_TEMPLATE.apply(prefix + "keystore.algorithm");
truststoreAlgorithm = TRUST_STORE_ALGORITHM_TEMPLATE.apply(prefix + "truststore.algorithm");
keystoreType = KEY_STORE_TYPE_TEMPLATE.apply(prefix + "keystore.type");
truststoreType = TRUST_STORE_TYPE_TEMPLATE.apply(prefix + "truststore.type");
trustRestrictionsPath = TRUST_RESTRICTIONS_TEMPLATE.apply(prefix + "trust_restrictions.path");
keyPath = KEY_PATH_TEMPLATE.apply(prefix + "key");
legacyKeyPassword = LEGACY_KEY_PASSWORD_TEMPLATE.apply(prefix + "key_passphrase");
keyPassword = KEY_PASSWORD_TEMPLATE.apply(prefix + "secure_key_passphrase");
cert = CERT_TEMPLATE.apply(prefix + "certificate");
caPaths = CAPATH_SETTING_TEMPLATE.apply(prefix + "certificate_authorities");
clientAuth = CLIENT_AUTH_SETTING_TEMPLATE.apply(prefix + "client_authentication");
verificationMode = VERIFICATION_MODE_SETTING_TEMPLATE.apply(prefix + "verification_mode");
this.allSettings = Arrays.asList(ciphers, supportedProtocols,
keystorePath, keystorePassword, keystoreAlgorithm, keystoreType, keystoreKeyPassword,
final List<Setting<? extends Object>> settings = CollectionUtils.arrayAsArrayList(ciphers, supportedProtocols,
truststorePath, truststorePassword, truststoreAlgorithm, truststoreType, trustRestrictionsPath,
keyPath, keyPassword, cert, caPaths, clientAuth, verificationMode,
legacyKeystorePassword, legacyKeystoreKeyPassword, legacyKeyPassword, legacyTruststorePassword);
caPaths, clientAuth, verificationMode, legacyTruststorePassword);
settings.addAll(x509KeyPair.getAllSettings());
this.allSettings = Collections.unmodifiableList(settings);
}
public static String getKeyStoreType(Setting<Optional<String>> setting, Settings settings, String path) {
@ -262,4 +219,5 @@ public class SSLConfigurationSettings {
KEY_PATH_PROFILES, LEGACY_KEY_PASSWORD_PROFILES, KEY_PASSWORD_PROFILES,CERT_PROFILES,CAPATH_SETTING_PROFILES,
CLIENT_AUTH_SETTING_PROFILES, VERIFICATION_MODE_SETTING_PROFILES);
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.ssl;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import java.security.KeyStore;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.SecureSetting;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.env.Environment;
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
/**
* An encapsulation of the configuration options for X.509 Key Pair support in X-Pack security.
* The most common use is as the private key and associated certificate for SSL/TLS support, but it can also be used for providing
* signing or encryption keys (if they are X.509 based).
* This class supports using a {@link java.security.KeyStore} (with configurable {@link KeyStore#getType() type}) or PEM based files.
*/
public class X509KeyPairSettings {
static final Function<String, Setting<Optional<String>>> KEYSTORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
static final Function<String, Setting<SecureString>> KEYSTORE_PASSWORD_TEMPLATE = key -> SecureSetting.secureString(key,
LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_password", "keystore.password")));
static final Function<String, Setting<String>> KEY_STORE_ALGORITHM_TEMPLATE = key ->
new Setting<>(key, s -> KeyManagerFactory.getDefaultAlgorithm(),
Function.identity(), Setting.Property.NodeScope, Setting.Property.Filtered);
static final Function<String, Setting<Optional<String>>> KEY_STORE_TYPE_TEMPLATE = key ->
new Setting<>(key, s -> null, Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
static final Function<String, Setting<SecureString>> KEYSTORE_KEY_PASSWORD_TEMPLATE = key ->
SecureSetting.secureString(key, LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_key_password",
"keystore.key_password")));
static final Function<String, Setting<Optional<String>>> KEY_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
static final Function<String, Setting<Optional<String>>> CERT_TEMPLATE = key -> new Setting<>(key, s -> null,
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
static final Function<String, Setting<SecureString>> LEGACY_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
static final Function<String, Setting<SecureString>> KEY_PASSWORD_TEMPLATE = key ->
SecureSetting.secureString(key, LEGACY_KEY_PASSWORD_TEMPLATE.apply(key.replace("secure_key_passphrase",
"key_passphrase")));
private final String prefix;
// Specify private cert/key pair via keystore
final Setting<Optional<String>> keystorePath;
final Setting<SecureString> keystorePassword;
final Setting<String> keystoreAlgorithm;
final Setting<Optional<String>> keystoreType;
final Setting<SecureString> keystoreKeyPassword;
// Specify private cert/key pair via key and certificate files
final Setting<Optional<String>> keyPath;
final Setting<SecureString> keyPassword;
final Setting<Optional<String>> certificatePath;
// Optional support for legacy (non secure) passwords
// pkg private for tests
final Setting<SecureString> legacyKeystorePassword;
final Setting<SecureString> legacyKeystoreKeyPassword;
final Setting<SecureString> legacyKeyPassword;
private final List<Setting<?>> allSettings;
public X509KeyPairSettings(String prefix, boolean acceptNonSecurePasswords) {
keystorePath = KEYSTORE_PATH_TEMPLATE.apply(prefix + "keystore.path");
keystorePassword = KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_password");
keystoreAlgorithm = KEY_STORE_ALGORITHM_TEMPLATE.apply(prefix + "keystore.algorithm");
keystoreType = KEY_STORE_TYPE_TEMPLATE.apply(prefix + "keystore.type");
keystoreKeyPassword = KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_key_password");
keyPath = KEY_PATH_TEMPLATE.apply(prefix + "key");
keyPassword = KEY_PASSWORD_TEMPLATE.apply(prefix + "secure_key_passphrase");
certificatePath = CERT_TEMPLATE.apply(prefix + "certificate");
legacyKeystorePassword = LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.password");
legacyKeystoreKeyPassword = LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.key_password");
legacyKeyPassword = LEGACY_KEY_PASSWORD_TEMPLATE.apply(prefix + "key_passphrase");
this.prefix = prefix;
final List<Setting<?>> settings = CollectionUtils.arrayAsArrayList(
keystorePath, keystorePassword, keystoreAlgorithm, keystoreType, keystoreKeyPassword,
keyPath, keyPassword, certificatePath);
if (acceptNonSecurePasswords) {
settings.add(legacyKeystorePassword);
settings.add(legacyKeystoreKeyPassword);
settings.add(legacyKeyPassword);
}
allSettings = Collections.unmodifiableList(settings);
}
public Collection<? extends Setting<?>> getAllSettings() {
return allSettings;
}
public String getPrefix() {
return prefix;
}
}

View File

@ -28,6 +28,14 @@ grant {
permission java.io.FilePermission "\\\\.\\pipe\\*", "read,write";
};
grant codeBase "${codebase.xmlsec-2.0.8.jar}" {
// needed during initialization of OpenSAML library where xml security algorithms are registered
// see https://github.com/apache/santuario-java/blob/e79f1fe4192de73a975bc7246aee58ed0703343d/src/main/java/org/apache/xml/security/utils/JavaUtils.java#L205-L220
// and https://git.shibboleth.net/view/?p=java-opensaml.git;a=blob;f=opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/SignatureMarshaller.java;hb=db0eaa64210f0e32d359cd6c57bedd57902bf811#l52
// which uses it in the opensaml-xmlsec-impl
permission java.security.SecurityPermission "org.apache.xml.security.register";
};
grant codeBase "${codebase.netty-common}" {
// for reading the system-wide configuration for the backlog of established sockets
permission java.io.FilePermission "/proc/sys/net/core/somaxconn", "read";

View File

@ -170,6 +170,9 @@
},
"invalidated" : {
"type" : "boolean"
},
"realm" : {
"type" : "keyword"
}
}
}

View File

@ -8,6 +8,8 @@ package org.elasticsearch.test;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
@ -23,4 +25,25 @@ public class TestMatchers extends Matchers {
}
};
}
public static Matcher<String> matchesPattern(String regex) {
return matchesPattern(Pattern.compile(regex));
}
public static Matcher<String> matchesPattern(Pattern pattern) {
return predicate("Matches " + pattern.pattern(), String.class, pattern.asPredicate());
}
private static <T> Matcher<T> predicate(String description, Class<T> type, Predicate<T> stringPredicate) {
return new CustomMatcher<T>(description) {
@Override
public boolean matches(Object item) {
if (type.isInstance(item)) {
return stringPredicate.test(type.cast(item));
} else {
return false;
}
}
};
}
}

View File

@ -45,13 +45,13 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
public void testParseKeystoreAlgorithmWithPrefix() {
final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("xpack.security.authc.realms.ldap1.ssl.");
assertThat(ssl.keystoreAlgorithm.match("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm"), is(true));
assertThat(ssl.x509KeyPair.keystoreAlgorithm.match("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm"), is(true));
final String algo = randomAlphaOfLength(16);
final Settings settings = Settings.builder()
.put("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm", algo)
.build();
assertThat(ssl.keystoreAlgorithm.get(settings), is(algo));
assertThat(ssl.x509KeyPair.keystoreAlgorithm.get(settings), is(algo));
}
public void testParseProtocolsListWithPrefix() {
@ -68,16 +68,16 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
final SSLConfigurationSettings ssl = SSLConfigurationSettings.withoutPrefix();
final Settings settings = Settings.EMPTY;
assertThat(ssl.caPaths.get(settings).size(), is(0));
assertThat(ssl.cert.get(settings).isPresent(), is(false));
assertThat(ssl.x509KeyPair.certificatePath.get(settings).isPresent(), is(false));
assertThat(ssl.ciphers.get(settings).size(), is(0));
assertThat(ssl.clientAuth.get(settings).isPresent(), is(false));
assertThat(ssl.keyPassword.exists(settings), is(false));
assertThat(ssl.keyPath.get(settings).isPresent(), is(false));
assertThat(ssl.keystoreAlgorithm.get(settings), is(KeyManagerFactory.getDefaultAlgorithm()));
assertThat(ssl.keystoreType.get(settings).isPresent(), is(false));
assertThat(ssl.keystoreKeyPassword.exists(settings), is(false));
assertThat(ssl.keystorePassword.exists(settings), is(false));
assertThat(ssl.keystorePath.get(settings).isPresent(), is(false));
assertThat(ssl.x509KeyPair.keyPassword.exists(settings), is(false));
assertThat(ssl.x509KeyPair.keyPath.get(settings).isPresent(), is(false));
assertThat(ssl.x509KeyPair.keystoreAlgorithm.get(settings), is(KeyManagerFactory.getDefaultAlgorithm()));
assertThat(ssl.x509KeyPair.keystoreType.get(settings).isPresent(), is(false));
assertThat(ssl.x509KeyPair.keystoreKeyPassword.exists(settings), is(false));
assertThat(ssl.x509KeyPair.keystorePassword.exists(settings), is(false));
assertThat(ssl.x509KeyPair.keystorePath.get(settings).isPresent(), is(false));
assertThat(ssl.supportedProtocols.get(settings).size(), is(0));
assertThat(ssl.truststoreAlgorithm.get(settings), is(TrustManagerFactory.getDefaultAlgorithm()));
assertThat(ssl.truststoreType.get(settings).isPresent(), is(false));
@ -86,7 +86,7 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
assertThat(ssl.trustRestrictionsPath.get(settings).isPresent(), is(false));
assertThat(ssl.verificationMode.get(settings).isPresent(), is(false));
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.keystoreType, settings, null), is("jks"));
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.x509KeyPair.keystoreType, settings, null), is("jks"));
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.truststoreType, settings, null), is("jks"));
}

View File

@ -92,7 +92,7 @@ public class SSLConfigurationTests extends ESTestCase {
assertThat(ksKeyInfo.keyStorePassword, is(equalTo("password")));
assertThat(ksKeyInfo.keyPassword, is(equalTo("password")));
assertSettingDeprecationsAndWarnings(new Setting<?>[] {
SSLConfiguration.SETTINGS_PARSER.legacyKeystorePassword});
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystorePassword});
}
public void testKeystoreKeyPassword() {
@ -122,7 +122,9 @@ public class SSLConfigurationTests extends ESTestCase {
assertThat(ksKeyInfo.keyStorePassword, is(equalTo("password")));
assertThat(ksKeyInfo.keyPassword, is(equalTo("keypass")));
assertSettingDeprecationsAndWarnings(new Setting<?>[] {
SSLConfiguration.SETTINGS_PARSER.legacyKeystorePassword, SSLConfiguration.SETTINGS_PARSER.legacyKeystoreKeyPassword});
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystorePassword,
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystoreKeyPassword
});
}
public void testInferKeystoreTypeFromJksFile() {
@ -332,7 +334,7 @@ public class SSLConfigurationTests extends ESTestCase {
assertNotNull(keyManager);
assertThat(config.trustConfig(), instanceOf(CombiningTrustConfig.class));
assertCombiningTrustConfigContainsCorrectIssuers(config);
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.legacyKeyPassword});
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeyPassword});
}
public void testPEMKeyAndTrustFiles() {
@ -381,7 +383,7 @@ public class SSLConfigurationTests extends ESTestCase {
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
TrustManager trustManager = keyConfig.createTrustManager(env);
assertNotNull(trustManager);
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.legacyKeyPassword});
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeyPassword});
}
private void assertCombiningTrustConfigContainsCorrectIssuers(SSLConfiguration sslConfiguration) {

View File

@ -25,7 +25,10 @@ dependencies {
provided project(path: ':plugins:transport-nio', configuration: 'runtime')
testCompile project(path: ':x-pack-elasticsearch:plugin:monitoring')
testCompile project(path: ':x-pack-elasticsearch:plugin:watcher')
testCompile ( project(path: ':x-pack-elasticsearch:plugin:watcher') ) {
// Shouldn't have a dependency on watcher at all, but since we do, drop guava
exclude group: "com.google.guava", module: "guava"
}
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'testArtifacts')
@ -33,10 +36,38 @@ dependencies {
compile 'com.unboundid:unboundid-ldapsdk:3.2.0'
compile 'org.bouncycastle:bcprov-jdk15on:1.58'
compile 'org.bouncycastle:bcpkix-jdk15on:1.58'
// the following are all SAML dependencies - might as well download the whole internet
compile "org.opensaml:opensaml-core:3.3.0"
compile "org.opensaml:opensaml-saml-api:3.3.0"
compile "org.opensaml:opensaml-saml-impl:3.3.0"
compile "org.opensaml:opensaml-messaging-api:3.3.0"
compile "org.opensaml:opensaml-messaging-impl:3.3.0"
compile "org.opensaml:opensaml-security-api:3.3.0"
compile "org.opensaml:opensaml-security-impl:3.3.0"
compile "org.opensaml:opensaml-profile-api:3.3.0"
compile "org.opensaml:opensaml-profile-impl:3.3.0"
compile "org.opensaml:opensaml-xmlsec-api:3.3.0"
compile "org.opensaml:opensaml-xmlsec-impl:3.3.0"
compile "org.opensaml:opensaml-soap-api:3.3.0"
compile "org.opensaml:opensaml-soap-impl:3.3.0"
compile "org.opensaml:opensaml-storage-api:3.3.0"
compile "org.opensaml:opensaml-storage-impl:3.3.0"
compile "net.shibboleth.utilities:java-support:7.3.0"
compile "org.apache.santuario:xmlsec:2.0.8"
compile "io.dropwizard.metrics:metrics-core:3.2.2"
compile "org.cryptacular:cryptacular:1.2.0"
compile "org.slf4j:slf4j-api:${versions.slf4j}"
compile "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
compile "org.apache.httpcomponents:httpcore:${versions.httpcore}"
compile "org.apache.httpcomponents:httpasyncclient:${versions.httpasyncclient}"
compile "org.apache.httpcomponents:httpcore-nio:${versions.httpcore}"
compile "org.apache.httpcomponents:httpclient-cache:${versions.httpclient}"
compile 'com.google.guava:guava:19.0'
testCompile 'org.elasticsearch:securemock:1.2'
testCompile "org.elasticsearch:mocksocket:${versions.mocksocket}"
testCompile 'org.slf4j:slf4j-log4j12:1.6.2'
testCompile 'org.slf4j:slf4j-api:1.6.2'
//testCompile "org.yaml:snakeyaml:${versions.snakeyaml}"
}
@ -68,6 +99,8 @@ dependencyLicenses {
mapping from: /netty-.*/, to: 'netty'
mapping from: /bc.*/, to: 'bouncycastle'
mapping from: /transport-netty.*/, to: 'elasticsearch'
mapping from: /java-support|opensaml-.*/, to: 'shibboleth'
mapping from: /http.*/, to: 'httpclient'
ignoreSha 'x-pack-core'
ignoreSha 'transport-netty4'
}
@ -81,6 +114,121 @@ forbiddenPatterns {
forbiddenApisMain {
signaturesURLs += file('forbidden/ldap-signatures.txt').toURI().toURL()
signaturesURLs += file('forbidden/xml-signatures.txt').toURI().toURL()
}
// classes are missing, e.g. com.ibm.icu.lang.UCharacter
thirdPartyAudit.excludes = [
// SAML dependencies
// [missing classes] Some cli utilities that we don't use depend on these missing JCommander classes
'com.beust.jcommander.JCommander',
'com.beust.jcommander.converters.BaseConverter',
// [missing classes] Shibboleth + OpenSAML have servlet support that we don't use
'javax.servlet.AsyncContext',
'javax.servlet.DispatcherType',
'javax.servlet.Filter',
'javax.servlet.FilterChain',
'javax.servlet.FilterConfig',
'javax.servlet.RequestDispatcher',
'javax.servlet.ServletContext',
'javax.servlet.ServletException',
'javax.servlet.ServletInputStream',
'javax.servlet.ServletOutputStream',
'javax.servlet.ServletRequest',
'javax.servlet.ServletResponse',
'javax.servlet.http.Cookie',
'javax.servlet.http.HttpServletRequest',
'javax.servlet.http.HttpServletResponse',
'javax.servlet.http.HttpServletResponseWrapper',
'javax.servlet.http.HttpSession',
'javax.servlet.http.Part',
// [missing classes] Shibboleth + OpenSAML have velocity support that we don't use
'org.apache.velocity.VelocityContext',
'org.apache.velocity.app.VelocityEngine',
'org.apache.velocity.context.Context',
'org.apache.velocity.exception.VelocityException',
'org.apache.velocity.runtime.RuntimeServices',
'org.apache.velocity.runtime.log.LogChute',
'org.apache.velocity.runtime.resource.loader.StringResourceLoader',
'org.apache.velocity.runtime.resource.util.StringResourceRepository',
// [missing classes] OpenSAML depends on Apache XML security which depends on Xalan, but only for functionality that OpenSAML doesn't use
'org.apache.xml.dtm.DTM',
'org.apache.xml.utils.PrefixResolver',
'org.apache.xml.utils.PrefixResolverDefault',
'org.apache.xpath.Expression',
'org.apache.xpath.NodeSetDTM',
'org.apache.xpath.XPath',
'org.apache.xpath.XPathContext',
'org.apache.xpath.compiler.FunctionTable',
'org.apache.xpath.functions.Function',
'org.apache.xpath.objects.XNodeSet',
'org.apache.xpath.objects.XObject',
// [missing classes] OpenSAML storage has an optional LDAP storage impl
'org.ldaptive.AttributeModification',
'org.ldaptive.AttributeModificationType',
'org.ldaptive.Connection',
'org.ldaptive.DeleteOperation',
'org.ldaptive.DeleteRequest',
'org.ldaptive.LdapAttribute',
'org.ldaptive.LdapEntry',
'org.ldaptive.LdapException',
'org.ldaptive.ModifyOperation',
'org.ldaptive.ModifyRequest',
'org.ldaptive.Response',
'org.ldaptive.ResultCode',
'org.ldaptive.SearchOperation',
'org.ldaptive.SearchRequest',
'org.ldaptive.SearchResult',
'org.ldaptive.ext.MergeOperation',
'org.ldaptive.ext.MergeRequest',
'org.ldaptive.pool.ConnectionPool',
'org.ldaptive.pool.PooledConnectionFactory',
// [missing classes] OpenSAML storage has an optional JSON-backed storage impl
'javax.json.Json',
'javax.json.JsonException',
'javax.json.JsonNumber',
'javax.json.JsonObject',
'javax.json.JsonReader',
'javax.json.JsonValue$ValueType',
'javax.json.JsonValue',
'javax.json.stream.JsonGenerator',
// [missing classes] OpenSAML storage has an optional JPA storage impl
'javax.persistence.EntityManager',
'javax.persistence.EntityManagerFactory',
'javax.persistence.EntityTransaction',
'javax.persistence.LockModeType',
'javax/persistence/Query',
// [missing classes] OpenSAML storage and HttpClient cache have optional memcache support
'net.spy.memcached.CASResponse',
'net.spy.memcached.CASValue',
'net.spy.memcached.MemcachedClient',
'net.spy.memcached.MemcachedClientIF',
'net.spy.memcached.CachedData',
'net.spy.memcached.internal.OperationFuture',
'net.spy.memcached.transcoders.Transcoder',
// [missing classes] Http Client cache has optional ehcache support
'net.sf.ehcache.Ehcache',
'net.sf.ehcache.Element',
// [missing classes] SLF4j includes an optional class that depends on an extension class (!)
'org.slf4j.ext.EventData',
// Guava uses internal java api: sun.misc.Unsafe
'com.google.common.cache.Striped64',
'com.google.common.cache.Striped64$1',
'com.google.common.cache.Striped64$Cell',
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator',
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator$1',
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper',
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1',
]
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
thirdPartyAudit.excludes += [
'javax.xml.bind.JAXBContext',
'javax.xml.bind.JAXBElement',
'javax.xml.bind.JAXBException',
'javax.xml.bind.Unmarshaller',
'javax.xml.bind.UnmarshallerHandler',
];
}
run {

View File

@ -0,0 +1,8 @@
@defaultMessage DocumentBuilderFactory should not be used directly. Use SamlUtils#getHardenedDocumentBuilder(java.lang.String[]) instead.
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.parsers.DocumentBuilderFactory#newInstance(java.lang.String, java.lang.ClassLoader)
@defaultMessage TransformerFactory should not be used directly. Use SamlUtils#getHardenedXMLTransformer() instead.
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance(java.lang.String, java.lang.ClassLoader)

View File

@ -0,0 +1 @@
94f6cb97d7f7487a183f283ae80c6e61c86156e3

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,710 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="dns-prefetch" href="https://assets-cdn.github.com">
<link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
<link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
<link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
<link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/frameworks-2d2d4c150f7000385741c6b992b302689ecd172246c6428904e0813be9bceca6.css" media="all" rel="stylesheet" />
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-0522ae8d3b3bdc841d2f91f90efd5f1fd9040d910905674cd134ced43a6dfea6.css" media="all" rel="stylesheet" />
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/site-cfab053e93f0e27f4c63d4ff6b7957bd25f711667fe678e747f8a4d88c47b38d.css" media="all" rel="stylesheet" />
<meta name="viewport" content="width=device-width">
<title>cryptacular/NOTICE at master · vt-middleware/cryptacular · GitHub</title>
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
<meta property="fb:app_id" content="1401488693436528">
<meta content="https://avatars7.githubusercontent.com/u/6122907?v=4&amp;s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="vt-middleware/cryptacular" property="og:title" /><meta content="https://github.com/vt-middleware/cryptacular" property="og:url" /><meta content="cryptacular - The friendly complement to the BouncyCastle crypto API for Java." property="og:description" />
<link rel="assets" href="https://assets-cdn.github.com/">
<meta name="pjax-timeout" content="1000">
<meta name="request-id" content="E0E4:26F16:12A5AE6:1D11801:596C7978" data-pjax-transient>
<meta name="selected-link" value="repo_source" data-pjax-transient>
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
<meta name="google-analytics" content="UA-3769691-2">
<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="https://collector.githubapp.com/github-external/browser_event" name="octolytics-event-url" /><meta content="E0E4:26F16:12A5AE6:1D11801:596C7978" name="octolytics-dimension-request_id" /><meta content="sea" name="octolytics-dimension-region_edge" /><meta content="iad" name="octolytics-dimension-region_render" />
<meta content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" name="analytics-location" />
<meta class="js-ga-set" name="dimension1" content="Logged Out">
<meta name="hostname" content="github.com">
<meta name="user-login" content="">
<meta name="expected-hostname" content="github.com">
<meta name="js-proxy-site-detection-payload" content="N2ZhMjk0NTA4MjI1NmZhYTVlNzM5NzVjZmFkOWY2NGFkNmMxYzcyMGViNzAzZGQxMGMzZmJhZDQ3YWZiZTI0OHx7InJlbW90ZV9hZGRyZXNzIjoiMTEwLjIwLjIyMC4xMzUiLCJyZXF1ZXN0X2lkIjoiRTBFNDoyNkYxNjoxMkE1QUU2OjFEMTE4MDE6NTk2Qzc5NzgiLCJ0aW1lc3RhbXAiOjE1MDAyODEyMDgsImhvc3QiOiJnaXRodWIuY29tIn0=">
<meta name="html-safe-nonce" content="5aa226b80a18dc40894e1d405e4eb31cfca7d616">
<meta http-equiv="x-pjax-version" content="f682644ce1bb9629b9d9d9bedf64801b">
<link href="https://github.com/vt-middleware/cryptacular/commits/master.atom" rel="alternate" title="Recent Commits to cryptacular:master" type="application/atom+xml">
<meta name="description" content="cryptacular - The friendly complement to the BouncyCastle crypto API for Java.">
<meta name="go-import" content="github.com/vt-middleware/cryptacular git https://github.com/vt-middleware/cryptacular.git">
<meta content="6122907" name="octolytics-dimension-user_id" /><meta content="vt-middleware" name="octolytics-dimension-user_login" /><meta content="15714989" name="octolytics-dimension-repository_id" /><meta content="vt-middleware/cryptacular" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="15714989" name="octolytics-dimension-repository_network_root_id" /><meta content="vt-middleware/cryptacular" name="octolytics-dimension-repository_network_root_nwo" /><meta content="false" name="octolytics-dimension-repository_explore_github_marketplace_ci_cta_shown" />
<link rel="canonical" href="https://github.com/vt-middleware/cryptacular/blob/master/NOTICE" data-pjax-transient>
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
<link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#000000">
<link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
<meta name="theme-color" content="#1e2327">
</head>
<body class="logged-out env-production page-blob">
<div class="position-relative js-header-wrapper ">
<a href="#start-of-content" tabindex="1" class="px-2 py-4 show-on-focus js-skip-to-content">Skip to content</a>
<div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>
<header class="site-header js-details-container Details" role="banner">
<div class="site-nav-container">
<a class="header-logo-invertocat" href="https://github.com/" aria-label="Homepage" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
<svg aria-hidden="true" class="octicon octicon-mark-github" height="32" version="1.1" viewBox="0 0 16 16" width="32"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
</a>
<button class="btn-link float-right site-header-toggle js-details-target" type="button" aria-label="Toggle navigation" aria-expanded="false">
<svg aria-hidden="true" class="octicon octicon-three-bars" height="24" version="1.1" viewBox="0 0 12 16" width="18"><path fill-rule="evenodd" d="M11.41 9H.59C0 9 0 8.59 0 8c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zm0-4H.59C0 5 0 4.59 0 4c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zM.59 11H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1H.59C0 13 0 12.59 0 12c0-.59 0-1 .59-1z"/></svg>
</button>
<div class="site-header-menu">
<nav class="site-header-nav">
<a href="/features" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:features" data-selected-links="/features /features/code-review /features/project-management /features/integrations /features">
Features
</a> <a href="/business" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:business" data-selected-links="/business /business/security /business/customers /business">
Business
</a> <a href="/explore" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:explore" data-selected-links="/explore /trending /trending/developers /stars /integrations /integrations/feature/code /integrations/feature/collaborate /integrations/feature/ship showcases showcases_search showcases_landing /explore">
Explore
</a> <a href="/marketplace" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:marketplace" data-selected-links=" /marketplace">
Marketplace
</a> <a href="/pricing" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:pricing" data-selected-links="/pricing /pricing/developer /pricing/team /pricing/business-hosted /pricing/business-enterprise /pricing">
Pricing
</a> </nav>
<div class="site-header-actions">
<div class="header-search scoped-search site-scoped-search js-site-search" role="search">
<!-- '"` --><!-- </textarea></xmp> --></option></form><form accept-charset="UTF-8" action="/vt-middleware/cryptacular/search" class="js-site-search-form" data-scoped-search-url="/vt-middleware/cryptacular/search" data-unscoped-search-url="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<label class="form-control header-search-wrapper js-chromeless-input-container">
<a href="/vt-middleware/cryptacular/blob/master/NOTICE" class="header-search-scope no-underline">This repository</a>
<input type="text"
class="form-control header-search-input js-site-search-focus js-site-search-field is-clearable"
data-hotkey="s"
name="q"
value=""
placeholder="Search"
aria-label="Search this repository"
data-unscoped-placeholder="Search GitHub"
data-scoped-placeholder="Search"
autocapitalize="off">
<input type="hidden" class="js-site-search-type-field" name="type" >
</label>
</form></div>
<a class="text-bold site-header-link" href="/login?return_to=%2Fvt-middleware%2Fcryptacular%2Fblob%2Fmaster%2FNOTICE" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
<span class="text-gray">or</span>
<a class="text-bold site-header-link" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
</div>
</div>
</div>
</header>
</div>
<div id="start-of-content" class="show-on-focus"></div>
<div id="js-flash-container">
</div>
<div role="main">
<div itemscope itemtype="http://schema.org/SoftwareSourceCode">
<div id="js-repo-pjax-container" data-pjax-container>
<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
<div class="container repohead-details-container">
<ul class="pagehead-actions">
<li>
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to watch a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-eye" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
Watch
</a>
<a class="social-count" href="/vt-middleware/cryptacular/watchers"
aria-label="9 users are watching this repository">
9
</a>
</li>
<li>
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to star a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-star" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74z"/></svg>
Star
</a>
<a class="social-count js-social-count" href="/vt-middleware/cryptacular/stargazers"
aria-label="15 users starred this repository">
15
</a>
</li>
<li>
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to fork a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path fill-rule="evenodd" d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
Fork
</a>
<a href="/vt-middleware/cryptacular/network" class="social-count"
aria-label="1 user forked this repository">
1
</a>
</li>
</ul>
<h1 class="public ">
<svg aria-hidden="true" class="octicon octicon-repo" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
<span class="author" itemprop="author"><a href="/vt-middleware" class="url fn" rel="author">vt-middleware</a></span><!--
--><span class="path-divider">/</span><!--
--><strong itemprop="name"><a href="/vt-middleware/cryptacular" data-pjax="#js-repo-pjax-container">cryptacular</a></strong>
</h1>
</div>
<div class="container">
<nav class="reponav js-repo-nav js-sidenav-container-pjax"
itemscope
itemtype="http://schema.org/BreadcrumbList"
role="navigation"
data-pjax="#js-repo-pjax-container">
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/vt-middleware/cryptacular" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /vt-middleware/cryptacular" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-code" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"/></svg>
<span itemprop="name">Code</span>
<meta itemprop="position" content="1">
</a> </span>
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/vt-middleware/cryptacular/issues" class="js-selected-navigation-item reponav-item" data-hotkey="g i" data-selected-links="repo_issues repo_labels repo_milestones /vt-middleware/cryptacular/issues" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-issue-opened" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"/></svg>
<span itemprop="name">Issues</span>
<span class="Counter">4</span>
<meta itemprop="position" content="2">
</a> </span>
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/vt-middleware/cryptacular/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /vt-middleware/cryptacular/pulls" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
<span itemprop="name">Pull requests</span>
<span class="Counter">1</span>
<meta itemprop="position" content="3">
</a> </span>
<a href="/vt-middleware/cryptacular/projects" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /vt-middleware/cryptacular/projects">
<svg aria-hidden="true" class="octicon octicon-project" height="16" version="1.1" viewBox="0 0 15 16" width="15"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
Projects
<span class="Counter" >0</span>
</a>
<div class="reponav-dropdown js-menu-container">
<button type="button" class="btn-link reponav-item reponav-dropdown js-menu-target " data-no-toggle aria-expanded="false" aria-haspopup="true">
Insights
<svg aria-hidden="true" class="octicon octicon-triangle-down v-align-middle text-gray" height="11" version="1.1" viewBox="0 0 12 16" width="8"><path fill-rule="evenodd" d="M0 5l6 6 6-6z"/></svg>
</button>
<div class="dropdown-menu-content js-menu-content">
<div class="dropdown-menu dropdown-menu-sw">
<a class="dropdown-item" href="/vt-middleware/cryptacular/pulse" data-skip-pjax>
<svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0v2h3.6l.9-1.8.9 5.4L9 8.5l1.6 1.5H14V8z"/></svg>
Pulse
</a>
<a class="dropdown-item" href="/vt-middleware/cryptacular/graphs" data-skip-pjax>
<svg aria-hidden="true" class="octicon octicon-graph" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"/></svg>
Graphs
</a>
</div>
</div>
</div>
</nav>
</div>
</div>
<div class="container new-discussion-timeline experiment-repo-nav">
<div class="repository-content">
<a href="/vt-middleware/cryptacular/blob/c26911e3cd28497ce9daa3ce682e09cb2d1d8688/NOTICE" class="d-none js-permalink-shortcut" data-hotkey="y">Permalink</a>
<!-- blob contrib key: blob_contributors:v21:f5054b9e46039e0ad937c69e6151b7d4 -->
<div class="file-navigation js-zeroclipboard-container">
<div class="select-menu branch-select-menu js-menu-container js-select-menu float-left">
<button class=" btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
type="button" aria-label="Switch branches or tags" aria-expanded="false" aria-haspopup="true">
<i>Branch:</i>
<span class="js-select-button css-truncate-target">master</span>
</button>
<div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax>
<div class="select-menu-modal">
<div class="select-menu-header">
<svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
<span class="select-menu-title">Switch branches/tags</span>
</div>
<div class="select-menu-filters">
<div class="select-menu-text-filter">
<input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="form-control js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
</div>
<div class="select-menu-tabs">
<ul>
<li class="select-menu-tab">
<a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
</li>
<li class="select-menu-tab">
<a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
</li>
</ul>
</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/blob/ISSUE-31+32/NOTICE"
data-name="ISSUE-31+32"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
ISSUE-31+32
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/blob/gh-pages/NOTICE"
data-name="gh-pages"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
gh-pages
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open selected"
href="/vt-middleware/cryptacular/blob/master/NOTICE"
data-name="master"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
master
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/blob/v1.1/NOTICE"
data-name="v1.1"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
v1.1
</span>
</a>
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.2.1/NOTICE"
data-name="v1.2.1"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.2.1">
v1.2.1
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.2.0/NOTICE"
data-name="v1.2.0"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.2.0">
v1.2.0
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.1.2/NOTICE"
data-name="v1.1.2"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.1.2">
v1.1.2
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.1.1/NOTICE"
data-name="v1.1.1"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.1.1">
v1.1.1
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.1.0/NOTICE"
data-name="v1.1.0"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.1.0">
v1.1.0
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0/NOTICE"
data-name="v1.0"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0">
v1.0
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0-RC6/NOTICE"
data-name="v1.0-RC6"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC6">
v1.0-RC6
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0-RC4/NOTICE"
data-name="v1.0-RC4"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC4">
v1.0-RC4
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0-RC3/NOTICE"
data-name="v1.0-RC3"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC3">
v1.0-RC3
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0-RC2/NOTICE"
data-name="v1.0-RC2"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC2">
v1.0-RC2
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/vt-middleware/cryptacular/tree/v1.0-RC1/NOTICE"
data-name="v1.0-RC1"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC1">
v1.0-RC1
</span>
</a>
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
</div>
</div>
</div>
<div class="BtnGroup float-right">
<a href="/vt-middleware/cryptacular/find/master"
class="js-pjax-capture-input btn btn-sm BtnGroup-item"
data-pjax
data-hotkey="t">
Find file
</a>
<button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm BtnGroup-item tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button>
</div>
<div class="breadcrumb js-zeroclipboard-target">
<span class="repo-root js-repo-root"><span class="js-path-segment"><a href="/vt-middleware/cryptacular"><span>cryptacular</span></a></span></span><span class="separator">/</span><strong class="final-path">NOTICE</strong>
</div>
</div>
<div class="commit-tease">
<span class="float-right">
<a class="commit-tease-sha" href="/vt-middleware/cryptacular/commit/6dd6f199ac3ecc3b4c5aef9e04be3bbe265a30a1" data-pjax>
6dd6f19
</a>
<relative-time datetime="2017-07-06T22:28:36Z">Jul 7, 2017</relative-time>
</span>
<div>
<img alt="@dfish3r" class="avatar" height="20" src="https://avatars6.githubusercontent.com/u/1051499?v=4&amp;s=40" width="20" />
<a href="/dfish3r" class="user-mention" rel="contributor">dfish3r</a>
<a href="/vt-middleware/cryptacular/commit/6dd6f199ac3ecc3b4c5aef9e04be3bbe265a30a1" class="message" data-pjax="true" title="Update year in notice.">Update year in notice.</a>
</div>
<div class="commit-tease-contributors">
<button type="button" class="btn-link muted-link contributors-toggle" data-facebox="#blob_contributors_box">
<strong>1</strong>
contributor
</button>
</div>
<div id="blob_contributors_box" style="display:none">
<h2 class="facebox-header" data-facebox-id="facebox-header">Users who have contributed to this file</h2>
<ul class="facebox-user-list" data-facebox-id="facebox-description">
<li class="facebox-user-list-item">
<img alt="@dfish3r" height="24" src="https://avatars4.githubusercontent.com/u/1051499?v=4&amp;s=48" width="24" />
<a href="/dfish3r">dfish3r</a>
</li>
</ul>
</div>
</div>
<div class="file">
<div class="file-header">
<div class="file-actions">
<div class="BtnGroup">
<a href="/vt-middleware/cryptacular/raw/master/NOTICE" class="btn btn-sm BtnGroup-item" id="raw-url">Raw</a>
<a href="/vt-middleware/cryptacular/blame/master/NOTICE" class="btn btn-sm js-update-url-with-hash BtnGroup-item" data-hotkey="b">Blame</a>
<a href="/vt-middleware/cryptacular/commits/master/NOTICE" class="btn btn-sm BtnGroup-item" rel="nofollow">History</a>
</div>
<button type="button" class="btn-octicon disabled tooltipped tooltipped-nw"
aria-label="You must be signed in to make or propose changes">
<svg aria-hidden="true" class="octicon octicon-pencil" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/></svg>
</button>
<button type="button" class="btn-octicon btn-octicon-danger disabled tooltipped tooltipped-nw"
aria-label="You must be signed in to make or propose changes">
<svg aria-hidden="true" class="octicon octicon-trashcan" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/></svg>
</button>
</div>
<div class="file-info">
7 lines (5 sloc)
<span class="file-info-divider"></span>
165 Bytes
</div>
</div>
<div itemprop="text" class="blob-wrapper data type-text">
<table class="highlight tab-size js-file-line-container" data-tab-size="8">
<tr>
<td id="L1" class="blob-num js-line-number" data-line-number="1"></td>
<td id="LC1" class="blob-code blob-code-inner js-file-line">Cryptacular Java Library</td>
</tr>
<tr>
<td id="L2" class="blob-num js-line-number" data-line-number="2"></td>
<td id="LC2" class="blob-code blob-code-inner js-file-line">Copyright (C) 2003-2017 Virginia Tech.</td>
</tr>
<tr>
<td id="L3" class="blob-num js-line-number" data-line-number="3"></td>
<td id="LC3" class="blob-code blob-code-inner js-file-line">All rights reserved.</td>
</tr>
<tr>
<td id="L4" class="blob-num js-line-number" data-line-number="4"></td>
<td id="LC4" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="L5" class="blob-num js-line-number" data-line-number="5"></td>
<td id="LC5" class="blob-code blob-code-inner js-file-line">This product includes software developed at</td>
</tr>
<tr>
<td id="L6" class="blob-num js-line-number" data-line-number="6"></td>
<td id="LC6" class="blob-code blob-code-inner js-file-line">Virginia Tech (http://www.vt.edu).</td>
</tr>
</table>
</div>
</div>
<button type="button" data-facebox="#jump-to-line" data-facebox-class="linejump" data-hotkey="l" class="d-none">Jump to Line</button>
<div id="jump-to-line" style="display:none">
<!-- '"` --><!-- </textarea></xmp> --></option></form><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<input class="form-control linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
<button type="submit" class="btn">Go</button>
</form> </div>
</div>
<div class="modal-backdrop js-touch-events"></div>
</div>
</div>
</div>
</div>
<div class="container site-footer-container">
<div class="site-footer " role="contentinfo">
<ul class="site-footer-links float-right">
<li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact GitHub</a></li>
<li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
<li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
<li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
<li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
<li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
</ul>
<a href="https://github.com" aria-label="Homepage" class="site-footer-mark" title="GitHub">
<svg aria-hidden="true" class="octicon octicon-mark-github" height="24" version="1.1" viewBox="0 0 16 16" width="24"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
</a>
<ul class="site-footer-links">
<li>&copy; 2017 <span title="0.10938s from unicorn-2925809464-f2ltq">GitHub</span>, Inc.</li>
<li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
<li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
<li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
<li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
<li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
</ul>
</div>
</div>
<div id="ajax-error-message" class="ajax-error-message flash flash-error">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"/></svg>
<button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
</button>
You can't perform that action at this time.
</div>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-91f98c37fc84eac24836eec2567e9912742094369a04c4eba6e3cd1fa18902d9.js"></script>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-f84bb87b149685d1e6c6f057ee324f2cd496e677f5a359a8b5db853313bb83e6.js"></script>
<script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-13fa3aa50ac8f9fa9a7d198f0cd13b0905775d39446ad076d17d8f74a998438a.js"></script>
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner d-none">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"/></svg>
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
</div>
<div class="facebox" id="facebox" style="display:none;">
<div class="facebox-popup">
<div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
</div>
<button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
</button>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1 @@
6ce200f6b23222af3d8abb6b6459e6c44f4bb0e9

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,558 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
=========================================================================
This project includes Public Suffix List copied from
<https://publicsuffix.org/list/effective_tld_names.dat>
licensed under the terms of the Mozilla Public License, v. 2.0
Full license text: <http://mozilla.org/MPL/2.0/>
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -0,0 +1,6 @@
Apache HttpComponents Client
Copyright 1999-2016 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -0,0 +1 @@
bd50ea83908dbf2f387a333216e66d2f0c5079bd

View File

@ -0,0 +1 @@
f4be009e7505f6ceddf21e7960c759f413f15056

View File

@ -0,0 +1 @@
288ecc17f2025ad14f768163d42808987d5ffcd6

View File

@ -0,0 +1 @@
0a97a849b18b3798c4af1a2ca5b10c66cef17e3a

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,8 @@
Apache Log4j SLF4J Binding
Copyright 1999-2017 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -0,0 +1 @@
cd9886f498ee2ab2d994f0c779e5553b2c450416

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,4 @@
Dropwizard
Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2016 Dropwizard Team
This product includes software developed by Coda Hale and Yammer, Inc.

View File

@ -0,0 +1 @@
6fac68342891abec3c22d53e14c706ba3e58918b

View File

@ -0,0 +1 @@
5da0ff5d28546b3af8cc1487b4717fdeb675b8c4

View File

@ -0,0 +1 @@
38b21389971105f32099d04c6f63b4af505364ca

View File

@ -0,0 +1 @@
e4c72301b98cf4967c49c450de7da2dbc1f6b8d0

View File

@ -0,0 +1 @@
25c28fb4ab027fcaacaa268902cffc4451ac840c

View File

@ -0,0 +1 @@
c9611395e073206e59816b0b5ce5166450e8101e

View File

@ -0,0 +1 @@
391ac88f96a9f8f522d693c168d4c65fad20535d

View File

@ -0,0 +1 @@
89477899f0836040e9a584b451895a61d923bf96

View File

@ -0,0 +1 @@
48cf37a5080ee406aef21a49045f5e1d15ea46e6

View File

@ -0,0 +1 @@
4e900056cd80c1f0bd72497c26a48664089e04a8

View File

@ -0,0 +1 @@
ea912fe660d11ad443775974e3208f0563edcebd

View File

@ -0,0 +1 @@
7492688b067dca0568554ec4c7abf9f0b5e1f682

View File

@ -0,0 +1 @@
1244ecd4e8eccf74eb178906b0e9cac8a62bcbf7

View File

@ -0,0 +1 @@
e824f1e3ec14080412a4ab4b0807a13933d9be80

View File

@ -0,0 +1 @@
569ae8fc7c84817c5324e9f9b7958adf700a94c1

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1 @@
8619e95939167fb37245b5670135e4feb0ec7d50

View File

@ -0,0 +1,21 @@
Copyright (c) 2004-2017 QOS.ch
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
f5995bd4cd75816568c3b26d2552d957316ba8dc

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,9 @@
Apache XML Security for Java
Copyright 2000-2016 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -0,0 +1,18 @@
#!/bin/bash
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
source "`dirname "$0"`"/../elasticsearch-env
source "`dirname "$0"`"/x-pack-security-env
exec \
"$JAVA" \
$ES_JAVA_OPTS \
-Des.path.home="$ES_HOME" \
-Des.path.conf="$ES_PATH_CONF" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.xpack.security.authc.saml.SamlMetadataCommand \
"$@"

View File

@ -0,0 +1,23 @@
@echo off
rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
rem or more contributor license agreements. Licensed under the Elastic License;
rem you may not use this file except in compliance with the Elastic License.
setlocal enabledelayedexpansion
setlocal enableextensions
call "%~dp0..\elasticsearch-env.bat" || exit /b 1
call "%~dp0x-pack-security-env.bat" || exit /b 1
%JAVA% ^
%ES_JAVA_OPTS% ^
-Des.path.home="%ES_HOME%" ^
-Des.path.conf="%ES_PATH_CONF%" ^
-cp "%ES_CLASSPATH%" ^
org.elasticsearch.xpack.security.authc.saml.SamlMetadataCommand ^
%*
endlocal
endlocal

View File

@ -104,6 +104,14 @@ import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingAction;
import org.elasticsearch.xpack.security.action.rolemapping.TransportDeleteRoleMappingAction;
import org.elasticsearch.xpack.security.action.rolemapping.TransportGetRoleMappingsAction;
import org.elasticsearch.xpack.security.action.rolemapping.TransportPutRoleMappingAction;
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateAction;
import org.elasticsearch.xpack.security.action.saml.SamlInvalidateSessionAction;
import org.elasticsearch.xpack.security.action.saml.SamlPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.action.saml.SamlLogoutAction;
import org.elasticsearch.xpack.security.action.saml.TransportSamlAuthenticateAction;
import org.elasticsearch.xpack.security.action.saml.TransportSamlInvalidateSessionAction;
import org.elasticsearch.xpack.security.action.saml.TransportSamlPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.action.saml.TransportSamlLogoutAction;
import org.elasticsearch.xpack.security.action.token.CreateTokenAction;
import org.elasticsearch.xpack.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.security.action.token.RefreshTokenAction;
@ -171,6 +179,10 @@ import org.elasticsearch.xpack.security.rest.action.role.RestPutRoleAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestDeleteRoleMappingAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestGetRoleMappingsAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestPutRoleMappingAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlInvalidateSessionAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlLogoutAction;
import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordAction;
import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction;
import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
@ -658,7 +670,11 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new ActionHandler<>(CreateTokenAction.INSTANCE, TransportCreateTokenAction.class),
new ActionHandler<>(InvalidateTokenAction.INSTANCE, TransportInvalidateTokenAction.class),
new ActionHandler<>(GetCertificateInfoAction.INSTANCE, TransportGetCertificateInfoAction.class),
new ActionHandler<>(RefreshTokenAction.INSTANCE, TransportRefreshTokenAction.class)
new ActionHandler<>(RefreshTokenAction.INSTANCE, TransportRefreshTokenAction.class),
new ActionHandler<>(SamlPrepareAuthenticationAction.INSTANCE, TransportSamlPrepareAuthenticationAction.class),
new ActionHandler<>(SamlAuthenticateAction.INSTANCE, TransportSamlAuthenticateAction.class),
new ActionHandler<>(SamlLogoutAction.INSTANCE, TransportSamlLogoutAction.class),
new ActionHandler<>(SamlInvalidateSessionAction.INSTANCE, TransportSamlInvalidateSessionAction.class)
);
}
@ -699,7 +715,11 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new RestDeleteRoleMappingAction(settings, restController, getLicenseState()),
new RestGetTokenAction(settings, restController, getLicenseState()),
new RestInvalidateTokenAction(settings, restController, getLicenseState()),
new RestGetCertificateInfoAction(settings, restController)
new RestGetCertificateInfoAction(settings, restController),
new RestSamlPrepareAuthenticationAction(settings, restController, getLicenseState()),
new RestSamlAuthenticateAction(settings, restController, getLicenseState()),
new RestSamlLogoutAction(settings, restController, getLicenseState()),
new RestSamlInvalidateSessionAction(settings, restController, getLicenseState())
);
}

View File

@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.util.Map;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlToken;
/**
* Transport action responsible for taking saml content and turning it into a token.
*/
public final class TransportSamlAuthenticateAction extends HandledTransportAction<SamlAuthenticateRequest, SamlAuthenticateResponse> {
private final AuthenticationService authenticationService;
private final TokenService tokenService;
@Inject
public TransportSamlAuthenticateAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
AuthenticationService authenticationService, TokenService tokenService) {
super(settings, SamlAuthenticateAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
SamlAuthenticateRequest::new);
this.authenticationService = authenticationService;
this.tokenService = tokenService;
}
@Override
protected void doExecute(SamlAuthenticateRequest request,
ActionListener<SamlAuthenticateResponse> listener) {
final SamlToken saml = new SamlToken(request.getSaml(), request.getValidRequestIds());
logger.trace("Attempting to authenticate SamlToken [{}]", saml);
final ThreadContext threadContext = threadPool.getThreadContext();
Authentication originatingAuthentication = Authentication.getAuthentication(threadContext);
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
authenticationService.authenticate(SamlAuthenticateAction.NAME, request, saml, ActionListener.wrap(authentication -> {
final Map<String, Object> tokenMeta = threadContext.getTransient(SamlRealm.CONTEXT_TOKEN_DATA);
tokenService.createUserToken(authentication, originatingAuthentication,
ActionListener.wrap(tuple -> {
final String tokenString = tokenService.getUserTokenString(tuple.v1());
final TimeValue expiresIn = tokenService.getExpirationDelay();
listener.onResponse(
new SamlAuthenticateResponse(authentication.getUser().principal(), tokenString, tuple.v2(), expiresIn));
}, listener::onFailure), tokenMeta);
}, e -> {
logger.debug(() -> new ParameterizedMessage("SamlToken [{}] could not be authenticated", saml), e);
listener.onFailure(e);
}));
}
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.GroupedActionListener;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.UserToken;
import org.elasticsearch.xpack.security.authc.saml.SamlLogoutRequestHandler;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
import org.opensaml.saml.saml2.core.LogoutResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.findSamlRealms;
/**
* Transport action responsible for taking a SAML {@code LogoutRequest} and invalidating any associated Security Tokens
*/
public final class TransportSamlInvalidateSessionAction
extends HandledTransportAction<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse> {
private final TokenService tokenService;
private final Realms realms;
@Inject
public TransportSamlInvalidateSessionAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
TokenService tokenService, Realms realms) {
super(settings, SamlInvalidateSessionAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
SamlInvalidateSessionRequest::new);
this.tokenService = tokenService;
this.realms = realms;
}
@Override
protected void doExecute(SamlInvalidateSessionRequest request,
ActionListener<SamlInvalidateSessionResponse> listener) {
List<SamlRealm> realms = findSamlRealms(this.realms, request.getRealmName(), request.getAssertionConsumerServiceURL());
if (realms.isEmpty()) {
listener.onFailure(SamlUtils.samlException("Cannot find any matching realm for [{}]", request));
} else if (realms.size() > 1) {
listener.onFailure(SamlUtils.samlException("Found multiple matching realms [{}] for [{}]", realms, request));
} else {
invalidateSession(realms.get(0), request, listener);
}
}
private void invalidateSession(SamlRealm realm, SamlInvalidateSessionRequest request,
ActionListener<SamlInvalidateSessionResponse> listener) {
try {
final SamlLogoutRequestHandler.Result result = realm.getLogoutHandler().parseFromQueryString(request.getQueryString());
findAndInvalidateTokens(realm, result, ActionListener.wrap(count -> listener.onResponse(
new SamlInvalidateSessionResponse(realm.name(), count, buildLogoutResponseUrl(realm, result))
), listener::onFailure));
} catch (ElasticsearchSecurityException e) {
logger.info("Failed to invalidate SAML session", e);
listener.onFailure(e);
}
}
private String buildLogoutResponseUrl(SamlRealm realm, SamlLogoutRequestHandler.Result result) {
final LogoutResponse response = realm.buildLogoutResponse(result.getRequestId());
return new SamlRedirect(response, realm.getSigningConfiguration()).getRedirectUrl(result.getRelayState());
}
private void findAndInvalidateTokens(SamlRealm realm, SamlLogoutRequestHandler.Result result, ActionListener<Integer> listener) {
final Map<String, Object> tokenMetadata = realm.createTokenMetadata(result.getNameId(), result.getSession());
if (Strings.hasText((String) tokenMetadata.get(SamlRealm.TOKEN_METADATA_NAMEID_VALUE)) == false) {
// If we don't have a valid name-id to match against, don't do anything
logger.debug("Logout request [{}] has no NameID value, so cannot invalidate any sessions", result);
listener.onResponse(0);
return;
}
tokenService.findActiveTokensForRealm(realm.name(), ActionListener.wrap(tokens -> {
List<Tuple<UserToken, String>> sessionTokens = filterTokens(tokens, tokenMetadata);
logger.debug("Found [{}] token pairs to invalidate for SAML metadata [{}]", sessionTokens.size(), tokenMetadata);
if (sessionTokens.isEmpty()) {
listener.onResponse(0);
} else {
GroupedActionListener<Boolean> groupedListener = new GroupedActionListener<>(
ActionListener.wrap(collection -> listener.onResponse(collection.size()), listener::onFailure),
sessionTokens.size(), Collections.emptyList()
);
sessionTokens.forEach(tuple -> invalidateTokenPair(tuple, groupedListener));
}
}, e -> listener.onFailure(e)
));
}
private void invalidateTokenPair(Tuple<UserToken, String> tokenPair, ActionListener<Boolean> listener) {
// Invalidate the refresh token first, so the client doesn't trigger a refresh once the access token is invalidated
tokenService.invalidateRefreshToken(tokenPair.v2(), ActionListener.wrap(ignore -> tokenService.invalidateAccessToken(
tokenPair.v1(),
ActionListener.wrap(listener::onResponse, e -> {
logger.info("Failed to invalidate SAML access_token [{}] - {}", tokenPair.v1().getId(), e.toString());
listener.onFailure(e);
})), listener::onFailure));
}
private List<Tuple<UserToken, String>> filterTokens(List<Tuple<UserToken, String>> tokens, Map<String, Object> requiredMetadata) {
return tokens.stream()
.filter(tup -> {
Map<String, Object> actualMetadata = tup.v1().getMetadata();
return requiredMetadata.entrySet().stream().allMatch(e -> Objects.equals(actualMetadata.get(e.getKey()), e.getValue()));
})
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.io.IOException;
import java.util.Map;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.Realm;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.saml.SamlNameId;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
import org.elasticsearch.xpack.security.user.User;
import org.opensaml.saml.saml2.core.LogoutRequest;
/**
* Transport action responsible for generating a SAML {@code &lt;LogoutRequest&gt;} as a redirect binding URL.
*/
public final class TransportSamlLogoutAction
extends HandledTransportAction<SamlLogoutRequest, SamlLogoutResponse> {
private final Realms realms;
private final TokenService tokenService;
@Inject
public TransportSamlLogoutAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
Realms realms, TokenService tokenService) {
super(settings, SamlLogoutAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
SamlLogoutRequest::new);
this.realms = realms;
this.tokenService = tokenService;
}
@Override
protected void doExecute(SamlLogoutRequest request,
ActionListener<SamlLogoutResponse> listener) {
invalidateRefreshToken(request.getRefreshToken(), ActionListener.wrap(ignore -> {
try {
final String token = request.getToken();
tokenService.getAuthenticationAndMetaData(token, ActionListener.wrap(
tuple -> {
Authentication authentication = tuple.v1();
final Map<String, Object> tokenMetadata = tuple.v2();
SamlLogoutResponse response = buildResponse(authentication, tokenMetadata);
tokenService.invalidateAccessToken(token, ActionListener.wrap(
created -> {
if (logger.isTraceEnabled()) {
logger.trace("SAML Logout User [{}], Token [{}...{}]",
authentication.getUser().principal(),
token.substring(0, 8),
token.substring(token.length() - 8)
);
}
listener.onResponse(response);
},
listener::onFailure
));
}, listener::onFailure
));
} catch (IOException | ElasticsearchException e) {
logger.debug("Internal exception during SAML logout", e);
listener.onFailure(e);
}
}, listener::onFailure));
}
private void invalidateRefreshToken(String refreshToken, ActionListener<Boolean> listener) {
if (refreshToken == null) {
listener.onResponse(null);
} else {
tokenService.invalidateRefreshToken(refreshToken, listener);
}
}
private SamlLogoutResponse buildResponse(Authentication authentication, Map<String, Object> tokenMetadata) {
if (authentication == null) {
throw SamlUtils.samlException("No active authentication");
}
final User user = authentication.getUser();
if (user == null) {
throw SamlUtils.samlException("No active user");
}
final SamlRealm realm = findRealm(authentication);
final String tokenRealm = getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_REALM);
if (realm.name().equals(tokenRealm) == false) {
throw SamlUtils.samlException("Authenticating realm [{}] does not match token realm [{}]", realm, tokenRealm);
}
final SamlNameId nameId = new SamlNameId(
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_FORMAT),
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_VALUE),
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_QUALIFIER),
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_SP_QUALIFIER),
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_SP_PROVIDED_ID)
);
final String session = getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_SESSION);
final LogoutRequest logout = realm.buildLogoutRequest(nameId.asXml(), session);
if (logout == null) {
return new SamlLogoutResponse(null);
}
final String uri = new SamlRedirect(logout, realm.getSigningConfiguration()).getRedirectUrl();
return new SamlLogoutResponse(uri);
}
private String getMetadataString(Map<String, Object> metadata, String key) {
final Object value = metadata.get(key);
if (value == null) {
if (metadata.containsKey(key)) {
return null;
}
throw SamlUtils.samlException("Access token does not have SAML metadata [{}]", key);
}
if (value instanceof String) {
return (String) value;
} else {
throw SamlUtils.samlException("In access token, SAML metadata [{}] is [{}] rather than String", key, value.getClass());
}
}
private SamlRealm findRealm(Authentication authentication) {
final Authentication.RealmRef ref = authentication.getAuthenticatedBy();
if (ref == null || Strings.isNullOrEmpty(ref.getName())) {
throw SamlUtils.samlException("Authentication {} has no authenticating realm", authentication);
}
final Realm realm = realms.realm(ref.getName());
if (realm == null) {
throw SamlUtils.samlException("Authenticating realm {} does not exist", ref.getName());
}
if (realm instanceof SamlRealm) {
return (SamlRealm) realm;
} else {
throw SamlUtils.samlException("Authenticating realm {} is not a SAML realm", realm);
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.saml;
import java.util.List;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
import org.opensaml.saml.saml2.core.AuthnRequest;
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.findSamlRealms;
/**
* Transport action responsible for generating a SAML {@code &lt;AuthnRequest&gt;} as a redirect binding URL.
*/
public final class TransportSamlPrepareAuthenticationAction
extends HandledTransportAction<SamlPrepareAuthenticationRequest, SamlPrepareAuthenticationResponse> {
private final Realms realms;
@Inject
public TransportSamlPrepareAuthenticationAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
Realms realms) {
super(settings, SamlPrepareAuthenticationAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
SamlPrepareAuthenticationRequest::new);
this.realms = realms;
}
@Override
protected void doExecute(SamlPrepareAuthenticationRequest request,
ActionListener<SamlPrepareAuthenticationResponse> listener) {
List<SamlRealm> realms = findSamlRealms(this.realms, request.getRealmName(), request.getAssertionConsumerServiceURL() );
if (realms.isEmpty()) {
listener.onFailure(SamlUtils.samlException("Cannot find any matching realm for [{}]", request));
} else if (realms.size() > 1) {
listener.onFailure(SamlUtils.samlException("Found multiple matching realms [{}] for [{}]", realms, request));
} else {
prepareAuthentication(realms.get(0), listener);
}
}
private void prepareAuthentication(SamlRealm realm, ActionListener<SamlPrepareAuthenticationResponse> listener) {
final AuthnRequest authnRequest = realm.buildAuthenticationRequest();
try {
String redirectUrl = new SamlRedirect(authnRequest, realm.getSigningConfiguration()).getRedirectUrl();
listener.onResponse(new SamlPrepareAuthenticationResponse(
realm.name(),
authnRequest.getID(),
redirectUrl
));
} catch (ElasticsearchException e) {
listener.onFailure(e);
}
}
}

View File

@ -17,6 +17,8 @@ import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.UserToken;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import java.util.Collections;
@ -46,8 +48,8 @@ public final class TransportCreateTokenAction extends HandledTransportAction<Cre
protected void doExecute(CreateTokenRequest request, ActionListener<CreateTokenResponse> listener) {
Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext());
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) {
authenticationService.authenticate(CreateTokenAction.NAME, request,
request.getUsername(), request.getPassword(),
final UsernamePasswordToken authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword());
authenticationService.authenticate(CreateTokenAction.NAME, request, authToken,
ActionListener.wrap(authentication -> {
request.getPassword().close();
tokenService.createUserToken(authentication, originatingAuthentication, ActionListener.wrap(tuple -> {

View File

@ -18,7 +18,6 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -100,14 +99,13 @@ public class AuthenticationService extends AbstractComponent {
* Authenticates the username and password that are provided as parameters. This will not look
* at the values in the ThreadContext for Authentication.
*
* @param action The action of the message
* @param message The message that resulted in this authenticate call
* @param username The username to be used for authentication
* @param password The password to be used for authentication
* @param action The action of the message
* @param message The message that resulted in this authenticate call
* @param token The token (credentials) to be authenticated
*/
public void authenticate(String action, TransportMessage message, String username,
SecureString password, ActionListener<Authentication> listener) {
new Authenticator(action, message, null, listener, username, password).authenticateAsync();
public void authenticate(String action, TransportMessage message,
AuthenticationToken token, ActionListener<Authentication> listener) {
new Authenticator(action, message, null, listener).authenticateToken(token);
}
// pkg private method for testing
@ -142,13 +140,6 @@ public class AuthenticationService extends AbstractComponent {
this(new AuditableTransportRequest(auditTrail, failureHandler, threadContext, action, message), fallbackUser, listener);
}
Authenticator(String action, TransportMessage message, User fallbackUser,
ActionListener<Authentication> listener, String username,
SecureString password) {
this(new AuditableTransportRequest(auditTrail, failureHandler, threadContext, action, message), fallbackUser, listener);
this.authenticationToken = new UsernamePasswordToken(username, password);
}
private Authenticator(AuditableRequest auditableRequest, User fallbackUser, ActionListener<Authentication> listener) {
this.request = auditableRequest;
this.fallbackUser = fallbackUser;
@ -240,6 +231,7 @@ public class AuthenticationService extends AbstractComponent {
}
}
} catch (Exception e) {
logger.warn("An exception occurred while attempting to find authentication credentials", e);
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, null));
}
@ -441,6 +433,10 @@ public class AuthenticationService extends AbstractComponent {
// when an exception bubbles up even after successful authentication
action.run();
}
private void authenticateToken(AuthenticationToken token) {
this.consumeToken(token);
}
}
abstract static class AuditableRequest {

View File

@ -16,6 +16,7 @@ import java.util.Set;
import org.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.env.Environment;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
@ -30,36 +31,53 @@ import org.elasticsearch.xpack.security.authc.ldap.LdapRealm;
import org.elasticsearch.xpack.security.authc.ldap.LdapRealmSettings;
import org.elasticsearch.xpack.security.authc.pki.PkiRealm;
import org.elasticsearch.xpack.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
import org.elasticsearch.xpack.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.support.RoleMappingFileBootstrapCheck;
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
import org.elasticsearch.xpack.ssl.SSLService;
/**
* Provides a single entry point into dealing with all standard XPack security {@link Realm realms}.
* This class does not handle extensions.
* @see Realms for the component that manages configured realms (including custom extension realms)
*/
public class InternalRealms {
public final class InternalRealms {
/**
* The list of all <em>internal</em> realm types, excluding {@link ReservedRealm#TYPE}.
*/
private static final Set<String> TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE
private static final Set<String> XPACK_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE,
SamlRealmSettings.TYPE
)));
/**
* Determines whether <code>type</code> is an internal realm-type, optionally considering
* the {@link ReservedRealm}.
* The list of all standard realm types, which are those provided by x-pack and do not have extensive
* interaction with third party sources
*/
public static boolean isInternalRealm(String type, boolean includeReservedRealm) {
if (TYPES.contains(type)) {
private static final Set<String> STANDARD_TYPES =
Collections.unmodifiableSet(Sets.difference(XPACK_TYPES, Collections.singleton(SamlRealmSettings.TYPE)));
/**
* Determines whether <code>type</code> is an internal realm-type that is provided by x-pack,
* including the {@link ReservedRealm}
*/
static boolean isXPackRealm(String type) {
if (XPACK_TYPES.contains(type)) {
return true;
}
if (includeReservedRealm && ReservedRealm.TYPE.equals(type)) {
return true;
}
return false;
return ReservedRealm.TYPE.equals(type);
}
/**
* Determines whether <code>type</code> is an internal realm-type that is provided by x-pack,
* excluding the {@link ReservedRealm} and realms that have extensive interaction with
* third party sources
*/
static boolean isStandardRealm(String type) {
return STANDARD_TYPES.contains(type);
}
/**
@ -84,6 +102,7 @@ public class InternalRealms {
map.put(LdapRealmSettings.LDAP_TYPE, config -> new LdapRealm(LdapRealmSettings.LDAP_TYPE, config,
sslService, resourceWatcherService, nativeRoleMappingStore, threadPool));
map.put(PkiRealmSettings.TYPE, config -> new PkiRealm(config, resourceWatcherService, nativeRoleMappingStore));
map.put(SamlRealmSettings.TYPE, config -> SamlRealm.create(config, sslService, resourceWatcherService, nativeRoleMappingStore));
return Collections.unmodifiableMap(map);
}

View File

@ -15,6 +15,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.component.AbstractComponent;
@ -39,11 +41,12 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
private final ThreadContext threadContext;
private final ReservedRealm reservedRealm;
protected List<Realm> realms = Collections.emptyList();
// a list of realms that are considered default in that they are provided by x-pack and not a third party
List<Realm> internalRealmsOnly = Collections.emptyList();
protected List<Realm> realms;
// a list of realms that are considered standard in that they are provided by x-pack and
// interact with a 3rd party source on a limited basis
List<Realm> standardRealmsOnly;
// a list of realms that are considered native, that is they only interact with x-pack and no 3rd party auth sources
List<Realm> nativeRealmsOnly = Collections.emptyList();
List<Realm> nativeRealmsOnly;
public Realms(Settings settings, Environment env, Map<String, Realm.Factory> factories, XPackLicenseState licenseState,
ThreadContext threadContext, ReservedRealm reservedRealm) throws Exception {
@ -57,12 +60,12 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
this.realms = initRealms();
// pre-computing a list of internal only realms allows us to have much cheaper iteration than a custom iterator
// and is also simpler in terms of logic. These lists are small, so the duplication should not be a real issue here
List<Realm> internalRealms = new ArrayList<>();
List<Realm> standardRealms = new ArrayList<>();
List<Realm> nativeRealms = new ArrayList<>();
for (Realm realm : realms) {
// don't add the reserved realm here otherwise we end up with only this realm...
if (InternalRealms.isInternalRealm(realm.type(), false)) {
internalRealms.add(realm);
if (InternalRealms.isStandardRealm(realm.type())) {
standardRealms.add(realm);
}
if (FileRealmSettings.TYPE.equals(realm.type()) || NativeRealmSettings.TYPE.equals(realm.type())) {
@ -70,7 +73,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
}
}
for (List<Realm> realmList : Arrays.asList(internalRealms, nativeRealms)) {
for (List<Realm> realmList : Arrays.asList(standardRealms, nativeRealms)) {
if (realmList.isEmpty()) {
addNativeRealms(realmList);
}
@ -80,7 +83,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
assert realmList.get(0) == reservedRealm;
}
this.internalRealmsOnly = Collections.unmodifiableList(internalRealms);
this.standardRealmsOnly = Collections.unmodifiableList(standardRealms);
this.nativeRealmsOnly = Collections.unmodifiableList(nativeRealms);
}
@ -95,7 +98,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
case ALL:
return realms.iterator();
case DEFAULT:
return internalRealmsOnly.iterator();
return standardRealmsOnly.iterator();
case NATIVE:
return nativeRealmsOnly.iterator();
default:
@ -103,6 +106,10 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
}
}
public Stream<Realm> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
public List<Realm> asList() {
if (licenseState.isAuthAllowed() == false) {
return Collections.emptyList();
@ -113,7 +120,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
case ALL:
return Collections.unmodifiableList(realms);
case DEFAULT:
return Collections.unmodifiableList(internalRealmsOnly);
return Collections.unmodifiableList(standardRealmsOnly);
case NATIVE:
return Collections.unmodifiableList(nativeRealmsOnly);
default:
@ -265,7 +272,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
case NATIVE:
return FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type);
case DEFAULT:
return InternalRealms.isInternalRealm(type, true);
return InternalRealms.isStandardRealm(type) || ReservedRealm.TYPE.equals(type);
default:
throw new IllegalStateException("unknown enabled realm type [" + enabledRealmType + "]");
}

View File

@ -59,6 +59,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.engine.DocumentMissingException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xpack.XPackSettings;
@ -102,6 +103,8 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static org.elasticsearch.action.support.TransportActions.isShardNotAvailableException;
import static org.elasticsearch.gateway.GatewayService.STATE_NOT_RECOVERED_BLOCK;
@ -138,7 +141,7 @@ public final class TokenService extends AbstractComponent {
public static final Setting<TimeValue> TOKEN_EXPIRATION = Setting.timeSetting("xpack.security.authc.token.timeout",
TimeValue.timeValueMinutes(20L), TimeValue.timeValueSeconds(1L), Property.NodeScope);
public static final Setting<TimeValue> DELETE_INTERVAL = Setting.timeSetting("xpack.security.authc.token.delete.interval",
TimeValue.timeValueMinutes(30L), Property.NodeScope);
TimeValue.timeValueMinutes(30L), Property.NodeScope);
public static final Setting<TimeValue> DELETE_TIMEOUT = Setting.timeSetting("xpack.security.authc.token.delete.timeout",
TimeValue.MINUS_ONE, Property.NodeScope);
@ -161,9 +164,10 @@ public final class TokenService extends AbstractComponent {
/**
* Creates a new token service
*
* @param settings the node settings
* @param clock the clock that will be used for comparing timestamps
* @param client the client to use when checking for revocations
* @param clock the clock that will be used for comparing timestamps
* @param client the client to use when checking for revocations
*/
public TokenService(Settings settings, Clock clock, Client client,
SecurityLifecycleService lifecycleService, ClusterService clusterService) throws GeneralSecurityException {
@ -178,7 +182,7 @@ public final class TokenService extends AbstractComponent {
this.lifecycleService = lifecycleService;
this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis();
this.deleteInterval = DELETE_INTERVAL.get(settings);
this.enabled = XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings);
this.enabled = isTokenServiceEnabled(settings);
this.expiredTokenRemover = new ExpiredTokenRemover(settings, client);
ensureEncryptionCiphersSupported();
KeyAndCache keyAndCache = new KeyAndCache(new KeyAndTimestamp(tokenPassphrase, createdTimeStamps.incrementAndGet()),
@ -189,6 +193,9 @@ public final class TokenService extends AbstractComponent {
getTokenMetaData();
}
public static Boolean isTokenServiceEnabled(Settings settings) {
return XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings);
}
/**
* Create a token based on the provided authentication and metadata.
@ -199,6 +206,8 @@ public final class TokenService extends AbstractComponent {
ensureEnabled();
if (authentication == null) {
listener.onFailure(new IllegalArgumentException("authentication must be provided"));
} else if (originatingClientAuth == null) {
listener.onFailure(new IllegalArgumentException("originating client authentication must be provided"));
} else {
final Instant created = clock.instant();
final Instant expiration = getExpirationTime(created);
@ -226,6 +235,7 @@ public final class TokenService extends AbstractComponent {
builder.startObject("access_token")
.field("invalidated", false)
.field("user_token", userToken)
.field("realm", authentication.getAuthenticatedBy().getName())
.endObject();
builder.endObject();
IndexRequest request =
@ -255,19 +265,7 @@ public final class TokenService extends AbstractComponent {
listener.onResponse(null);
} else {
try {
decodeToken(token, ActionListener.wrap(userToken -> {
if (userToken != null) {
Instant currentTime = clock.instant();
if (currentTime.isAfter(userToken.getExpirationTime())) {
// token expired
listener.onFailure(expiredTokenException());
} else {
checkIfTokenIsRevoked(userToken, listener);
}
} else {
listener.onResponse(null);
}
}, listener::onFailure));
decodeAndValidateToken(token, listener);
} catch (IOException e) {
// could happen with a token that is not ours
logger.debug("invalid token", e);
@ -280,6 +278,40 @@ public final class TokenService extends AbstractComponent {
}
/**
* Reads the authentication and metadata from the given token.
* This method does not validate whether the token is expired or not.
*/
public void getAuthenticationAndMetaData(String token, ActionListener<Tuple<Authentication, Map<String, Object>>> listener)
throws IOException {
decodeToken(token, ActionListener.wrap(
userToken -> {
if (userToken == null) {
listener.onFailure(new ElasticsearchSecurityException("supplied token is not valid"));
} else {
listener.onResponse(new Tuple<>(userToken.getAuthentication(), userToken.getMetadata()));
}
},
listener::onFailure
));
}
private void decodeAndValidateToken(String token, ActionListener<UserToken> listener) throws IOException {
decodeToken(token, ActionListener.wrap(userToken -> {
if (userToken != null) {
Instant currentTime = clock.instant();
if (currentTime.isAfter(userToken.getExpirationTime())) {
// token expired
listener.onFailure(expiredTokenException());
} else {
checkIfTokenIsRevoked(userToken, listener);
}
} else {
listener.onResponse(null);
}
}, listener::onFailure));
}
/*
* Asynchronously decodes the string representation of a {@link UserToken}. The process for
* this is asynchronous as we may need to compute a key, which can be computationally expensive
* so this should not block the current thread, which is typically a network thread. A second
@ -304,7 +336,49 @@ public final class TokenService extends AbstractComponent {
getKeyAsync(decodedSalt, keyAndCache, ActionListener.wrap(decodeKey -> {
try {
final byte[] iv = in.readByteArray();
decryptToken(in, getDecryptionCipher(iv, decodeKey, version, decodedSalt), version, listener);
final Cipher cipher = getDecryptionCipher(iv, decodeKey, version, decodedSalt);
if (version.onOrAfter(Version.V_6_2_0)) {
// we only have the id and need to get the token from the doc!
decryptTokenId(in, cipher, version, ActionListener.wrap(tokenId ->
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
final GetRequest getRequest =
client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE,
getTokenDocumentId(tokenId)).request();
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest,
ActionListener.<GetResponse>wrap(response -> {
if (response.isExists()) {
Map<String, Object> accessTokenSource =
(Map<String, Object>) response.getSource().get("access_token");
if (accessTokenSource == null) {
listener.onFailure(new IllegalStateException("token document is missing " +
"the access_token field"));
} else if (accessTokenSource.containsKey("user_token") == false) {
listener.onFailure(new IllegalStateException("token document is missing " +
"the user_token field"));
} else {
Map<String, Object> userTokenSource =
(Map<String, Object>) accessTokenSource.get("user_token");
listener.onResponse(UserToken.fromSourceMap(userTokenSource));
}
} else {
listener.onFailure(
new IllegalStateException("token document is missing and must be present"));
}
}, e -> {
// if the index or the shard is not there / available we assume that
// the token is not valid
if (isShardNotAvailableException(e)) {
logger.warn("failed to get token [{}] since index is not available", tokenId);
listener.onResponse(null);
} else {
logger.error(new ParameterizedMessage("failed to get token [{}]", tokenId), e);
listener.onFailure(e);
}
}), client::get);
}), listener::onFailure));
} else {
decryptToken(in, cipher, version, listener);
}
} catch (GeneralSecurityException e) {
// could happen with a token that is not ours
logger.warn("invalid token", e);
@ -347,6 +421,13 @@ public final class TokenService extends AbstractComponent {
}
}
private static void decryptTokenId(StreamInput in, Cipher cipher, Version version, ActionListener<String> listener) throws IOException {
try (CipherInputStream cis = new CipherInputStream(in, cipher); StreamInput decryptedInput = new InputStreamStreamInput(cis)) {
decryptedInput.setVersion(version);
listener.onResponse(decryptedInput.readString());
}
}
/**
* This method performs the steps necessary to invalidate a token so that it may no longer be
* used. The process of invalidation involves a step that is needed for backwards compatibility
@ -377,6 +458,22 @@ public final class TokenService extends AbstractComponent {
}
}
/**
* This method performs the steps necessary to invalidate a token so that it may no longer be used.
*
* @see #invalidateAccessToken(String, ActionListener)
*/
public void invalidateAccessToken(UserToken userToken, ActionListener<Boolean> listener) {
ensureEnabled();
if (userToken == null) {
listener.onFailure(new IllegalArgumentException("token must be provided"));
} else {
maybeStartTokenRemover();
final long expirationEpochMilli = getExpirationTime().toEpochMilli();
indexBwcInvalidation(userToken, listener, new AtomicInteger(0), expirationEpochMilli);
}
}
public void invalidateRefreshToken(String refreshToken, ActionListener<Boolean> listener) {
ensureEnabled();
if (Strings.isNullOrEmpty(refreshToken)) {
@ -394,13 +491,14 @@ public final class TokenService extends AbstractComponent {
/**
* Performs the actual bwc invalidation of a token and then kicks off the new invalidation method
* @param userToken the token to invalidate
* @param listener the listener to notify upon completion
* @param attemptCount the number of attempts to invalidate that have already been tried
*
* @param userToken the token to invalidate
* @param listener the listener to notify upon completion
* @param attemptCount the number of attempts to invalidate that have already been tried
* @param expirationEpochMilli the expiration time as milliseconds since the epoch
*/
private void indexBwcInvalidation(UserToken userToken, ActionListener<Boolean> listener, AtomicInteger attemptCount,
long expirationEpochMilli) {
long expirationEpochMilli) {
if (attemptCount.get() > 5) {
listener.onFailure(invalidGrantException("failed to invalidate token"));
} else {
@ -413,34 +511,35 @@ public final class TokenService extends AbstractComponent {
final String tokenDocId = getTokenDocumentId(userToken);
final Version version = userToken.getVersion();
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, indexRequest,
ActionListener.<IndexResponse>wrap(indexResponse -> {
ActionListener<Boolean> wrappedListener =
ActionListener.wrap(ignore -> listener.onResponse(true), listener::onFailure);
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
}, e -> {
Throwable cause = ExceptionsHelper.unwrapCause(e);
if (cause instanceof VersionConflictEngineException) {
// expected since something else could have invalidated
ActionListener<Boolean> wrappedListener =
ActionListener.wrap(ignore -> listener.onResponse(false), listener::onFailure);
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
} else if (isShardNotAvailableException(e)) {
attemptCount.incrementAndGet();
indexBwcInvalidation(userToken, listener, attemptCount, expirationEpochMilli);
} else {
listener.onFailure(e);
}
}), client::index));
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, indexRequest,
ActionListener.<IndexResponse>wrap(indexResponse -> {
ActionListener<Boolean> wrappedListener =
ActionListener.wrap(ignore -> listener.onResponse(true), listener::onFailure);
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
}, e -> {
Throwable cause = ExceptionsHelper.unwrapCause(e);
if (cause instanceof VersionConflictEngineException) {
// expected since something else could have invalidated
ActionListener<Boolean> wrappedListener =
ActionListener.wrap(ignore -> listener.onResponse(false), listener::onFailure);
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
} else if (isShardNotAvailableException(e)) {
attemptCount.incrementAndGet();
indexBwcInvalidation(userToken, listener, attemptCount, expirationEpochMilli);
} else {
listener.onFailure(e);
}
}), client::index));
}
}
/**
* Performs the actual invalidation of a token
* @param tokenDocId the id of the token doc to invalidate
* @param listener the listener to notify upon completion
* @param attemptCount the number of attempts to invalidate that have already been tried
* @param srcPrefix the prefix to use when constructing the doc to update
*
* @param tokenDocId the id of the token doc to invalidate
* @param listener the listener to notify upon completion
* @param attemptCount the number of attempts to invalidate that have already been tried
* @param srcPrefix the prefix to use when constructing the doc to update
* @param documentVersion the expected version of the document we will update
*/
private void indexInvalidation(String tokenDocId, Version version, ActionListener<Boolean> listener, AtomicInteger attemptCount,
@ -553,29 +652,29 @@ public final class TokenService extends AbstractComponent {
.request();
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
ActionListener.<SearchResponse>wrap(searchResponse -> {
if (searchResponse.isTimedOut()) {
attemptCount.incrementAndGet();
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
} else if (searchResponse.getHits().getHits().length < 1) {
logger.info("could not find token document with refresh_token [{}]", refreshToken);
listener.onFailure(invalidGrantException("could not refresh the requested token"));
} else if (searchResponse.getHits().getHits().length > 1) {
listener.onFailure(new IllegalStateException("multiple tokens share the same refresh token"));
} else {
listener.onResponse(new Tuple<>(searchResponse, attemptCount));
}
}, e -> {
if (isShardNotAvailableException(e)) {
logger.debug("failed to search for token document, retrying", e);
attemptCount.incrementAndGet();
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
} else {
listener.onFailure(e);
}
}),
client::search));
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
ActionListener.<SearchResponse>wrap(searchResponse -> {
if (searchResponse.isTimedOut()) {
attemptCount.incrementAndGet();
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
} else if (searchResponse.getHits().getHits().length < 1) {
logger.info("could not find token document with refresh_token [{}]", refreshToken);
listener.onFailure(invalidGrantException("could not refresh the requested token"));
} else if (searchResponse.getHits().getHits().length > 1) {
listener.onFailure(new IllegalStateException("multiple tokens share the same refresh token"));
} else {
listener.onResponse(new Tuple<>(searchResponse, attemptCount));
}
}, e -> {
if (isShardNotAvailableException(e)) {
logger.debug("failed to search for token document, retrying", e);
attemptCount.incrementAndGet();
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
} else {
listener.onFailure(e);
}
}),
client::search));
}
}
@ -701,6 +800,81 @@ public final class TokenService extends AbstractComponent {
}
}
/**
* Find all stored refresh and access tokens that have not been invalidated or expired, and were issued against
* the specified realm.
*/
public void findActiveTokensForRealm(String realmName, ActionListener<List<Tuple<UserToken, String>>> listener) {
ensureEnabled();
if (Strings.isNullOrEmpty(realmName)) {
listener.onFailure(new IllegalArgumentException("Realm name is required"));
return;
}
final Instant now = clock.instant();
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "token"))
.filter(QueryBuilders.termQuery("access_token.realm", realmName))
.filter(QueryBuilders.boolQuery()
.should(QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("access_token.invalidated", false))
.must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli()))
)
.should(QueryBuilders.termQuery("refresh_token.invalidated", false))
);
SearchRequest request = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME)
.setQuery(boolQuery)
.setVersion(false)
.request();
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
ActionListener.<SearchResponse>wrap(searchResponse -> {
if (searchResponse.isTimedOut()) {
listener.onFailure(new ElasticsearchSecurityException("Failed to find user tokens"));
} else {
listener.onResponse(parseDocuments(searchResponse));
}
}, listener::onFailure),
client::search));
}
private List<Tuple<UserToken, String>> parseDocuments(SearchResponse searchResponse) {
return StreamSupport.stream(searchResponse.getHits().spliterator(), false).map(hit -> {
final Map<String, Object> source = hit.getSourceAsMap();
try {
return parseTokensFromDocument(source);
} catch (IOException e) {
throw invalidGrantException("cannot read token from document");
}
}).collect(Collectors.toList());
}
/**
* @return A {@link Tuple} of access-token and refresh-token-id
*/
private Tuple<UserToken, String> parseTokensFromDocument(Map<String, Object> source) throws IOException {
final String refreshToken = (String) ((Map<String, Object>) source.get("refresh_token")).get("token");
final Map<String, Object> userTokenSource = (Map<String, Object>)
((Map<String, Object>) source.get("access_token")).get("user_token");
final String id = (String) userTokenSource.get("id");
final Integer version = (Integer) userTokenSource.get("version");
final String authString = (String) userTokenSource.get("authentication");
final Long expiration = (Long) userTokenSource.get("expiration_time");
final Map<String, Object> metadata = (Map<String, Object>) userTokenSource.get("metadata");
Version authVersion = Version.fromId(version);
try (StreamInput in = StreamInput.wrap(Base64.getDecoder().decode(authString))) {
in.setVersion(authVersion);
Authentication authentication = new Authentication(in);
return new Tuple<>(new UserToken(id, Version.fromId(version), authentication, Instant.ofEpochMilli(expiration), metadata),
refreshToken);
}
}
private static String getInvalidatedTokenDocumentId(UserToken userToken) {
return getInvalidatedTokenDocumentId(userToken.getId());
}
@ -765,6 +939,8 @@ public final class TokenService extends AbstractComponent {
listener.onResponse(userToken);
}
}
} else if (userToken.getVersion().onOrAfter(Version.V_6_2_0)) {
listener.onFailure(new IllegalStateException("token document is missing and must be present"));
} else {
listener.onResponse(userToken);
}
@ -787,6 +963,7 @@ public final class TokenService extends AbstractComponent {
}
}
public TimeValue getExpirationDelay() {
return expirationDelay;
}
@ -840,7 +1017,11 @@ public final class TokenService extends AbstractComponent {
new CipherOutputStream(out, getEncryptionCipher(initializationVector, keyAndCache, userToken.getVersion()));
StreamOutput encryptedStreamOutput = new OutputStreamStreamOutput(encryptedOutput)) {
encryptedStreamOutput.setVersion(userToken.getVersion());
userToken.writeTo(encryptedStreamOutput);
if (userToken.getVersion().onOrAfter(Version.V_6_2_0)) {
encryptedStreamOutput.writeString(userToken.getId());
} else {
userToken.writeTo(encryptedStreamOutput);
}
encryptedStreamOutput.close();
return new String(os.toByteArray(), StandardCharsets.UTF_8);
}
@ -1047,6 +1228,7 @@ public final class TokenService extends AbstractComponent {
/**
* Prunes the keys and keeps up to the latest N keys around
*
* @param numKeysToKeep the number of keys to keep.
*/
synchronized TokenMetaData pruneKeys(int numKeysToKeep) {
@ -1058,8 +1240,8 @@ public final class TokenService extends AbstractComponent {
ArrayList<KeyAndCache> entries = new ArrayList<>(keyCache.cache.values());
Collections.sort(entries,
(left, right) -> Long.compare(right.keyAndTimestamp.getTimestamp(), left.keyAndTimestamp.getTimestamp()));
for (KeyAndCache value: entries) {
if (map.size() < numKeysToKeep || value.keyAndTimestamp.getTimestamp()>= currentKey
for (KeyAndCache value : entries) {
if (map.size() < numKeysToKeep || value.keyAndTimestamp.getTimestamp() >= currentKey
.keyAndTimestamp.getTimestamp()) {
logger.debug("keeping key {} ", value.getKeyHash());
map.put(value.getKeyHash(), value);
@ -1095,7 +1277,7 @@ public final class TokenService extends AbstractComponent {
byte[] saltArr = new byte[SALT_BYTES];
Map<BytesKey, KeyAndCache> map = new HashMap<>(metaData.getKeys().size());
long maxTimestamp = createdTimeStamps.get();
for (KeyAndTimestamp key : metaData.getKeys()) {
for (KeyAndTimestamp key : metaData.getKeys()) {
secureRandom.nextBytes(saltArr);
KeyAndCache keyAndCache = new KeyAndCache(key, new BytesKey(saltArr));
maxTimestamp = Math.max(keyAndCache.keyAndTimestamp.getTimestamp(), maxTimestamp);
@ -1252,7 +1434,7 @@ public final class TokenService extends AbstractComponent {
}
BytesKey getKeyHash() {
return keyHash;
return keyHash;
}
private static BytesKey calculateKeyHash(SecureString key) {

View File

@ -16,6 +16,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.time.Instant;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
@ -44,13 +45,24 @@ public final class UserToken implements Writeable, ToXContentObject {
this(Version.CURRENT, authentication, expirationTime, Collections.emptyMap());
}
/**
* Create a new token with an autogenerated id
*/
UserToken(Version version, Authentication authentication, Instant expirationTime, Map<String, Object> metadata) {
this.version = version;
this.id = UUIDs.base64UUID();
this(UUIDs.randomBase64UUID(), version, authentication, expirationTime, metadata);
}
/**
* Create a new token from an existing id
*/
UserToken(String id, Version version, Authentication authentication, Instant expirationTime, Map<String, Object> metadata) {
this.version = Objects.requireNonNull(version);
this.id = Objects.requireNonNull(id);
this.authentication = Objects.requireNonNull(authentication);
this.expirationTime = Objects.requireNonNull(expirationTime);
this.metadata = metadata;
}
/**
* Creates a new token based on the values from the stream
*/
@ -126,4 +138,18 @@ public final class UserToken implements Writeable, ToXContentObject {
}
return builder.endObject();
}
static UserToken fromSourceMap(Map<String, Object> source) throws IOException {
final String id = (String) source.get("id");
final Long expirationEpochMilli = (Long) source.get("expiration_time");
final Integer versionId = (Integer) source.get("version");
final Map<String, Object> metadata = (Map<String, Object>) source.get("metadata");
final String authString = (String) source.get("authentication");
final Version version = Version.fromId(versionId);
try (StreamInput in = StreamInput.wrap(Base64.getDecoder().decode(authString))) {
in.setVersion(version);
Authentication authentication = new Authentication(in);
return new UserToken(id, version, authentication, Instant.ofEpochMilli(expirationEpochMilli), metadata);
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.util.List;
import java.util.function.Supplier;
import org.opensaml.security.credential.Credential;
/**
* A simple container class that holds all configuration related to a SAML Identity Provider (IdP).
*/
class IdpConfiguration {
private final String entityId;
private final Supplier<List<Credential>> signingCredentials;
IdpConfiguration(String entityId, Supplier<List<Credential>> signingCredentials) {
this.entityId = entityId;
this.signingCredentials = signingCredentials;
}
/**
* The SAML identifier (as a URI) for the IDP
*/
String getEntityId() {
return entityId;
}
/**
* A list of credentials that the IDP uses for signing messages.
* A trusted message should be signed with any one (or more) of these credentials.
*/
List<Credential> getSigningCredentials() {
return signingCredentials.get();
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.internal.Nullable;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.NameIDType;
/**
* An lightweight collection of SAML attributes
*/
public class SamlAttributes {
public static final String NAMEID_SYNTHENTIC_ATTRIBUTE = "nameid";
public static final String PERSISTENT_NAMEID_SYNTHENTIC_ATTRIBUTE = "nameid:persistent";
private final SamlNameId name;
private final String session;
private final List<SamlAttribute> attributes;
SamlAttributes(SamlNameId name, String session, List<SamlAttribute> attributes) {
this.name = name;
this.session = session;
this.attributes = attributes;
}
/**
* Finds all values for the specified attribute
*
* @param attributeId The name of the attribute - either its {@code name} or @{code friendlyName}
* @return A list of all matching attribute values (may be empty).
*/
List<String> getAttributeValues(String attributeId) {
if (attributeId.equals(NAMEID_SYNTHENTIC_ATTRIBUTE)) {
return name == null ? Collections.emptyList() : Collections.singletonList(name.value);
}
if (name != null && NameIDType.PERSISTENT.equals(name.format) && attributeId.equals(PERSISTENT_NAMEID_SYNTHENTIC_ATTRIBUTE)) {
return Collections.singletonList(name.value);
}
if (Strings.isNullOrEmpty(attributeId)) {
return Collections.emptyList();
}
return attributes.stream()
.filter(attr -> attributeId.equals(attr.name) || attributeId.equals(attr.friendlyName))
.flatMap(attr -> attr.values.stream())
.collect(Collectors.toList());
}
List<SamlAttribute> attributes() {
return Collections.unmodifiableList(attributes);
}
SamlNameId name() {
return name;
}
String session() {
return session;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + name + ")[" + session + "]{" + attributes + "}";
}
static class SamlAttribute {
final String name;
final String friendlyName;
final List<String> values;
SamlAttribute(Attribute attribute) {
this(attribute.getName(), attribute.getFriendlyName(),
attribute.getAttributeValues().stream().map(x -> x.getDOM().getTextContent()).collect(Collectors.toList()));
}
SamlAttribute(String name, @Nullable String friendlyName, List<String> values) {
this.name = Objects.requireNonNull(name, "Attribute name cannot be null");
this.friendlyName = friendlyName;
this.values = Collections.unmodifiableList(values);
}
@Override
public String toString() {
if (Strings.isNullOrEmpty(friendlyName)) {
return name + '=' + values;
} else {
return friendlyName + '(' + name + ")=" + values;
}
}
}
}

View File

@ -0,0 +1,332 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
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.Conditions;
import org.opensaml.saml.saml2.core.EncryptedAssertion;
import org.opensaml.saml.saml2.core.EncryptedAttribute;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.Status;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.core.StatusDetail;
import org.opensaml.saml.saml2.core.StatusMessage;
import org.opensaml.saml.saml2.core.Subject;
import org.opensaml.saml.saml2.core.SubjectConfirmation;
import org.opensaml.saml.saml2.core.SubjectConfirmationData;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_BEARER;
/**
* Processes the IdP's SAML Response for our AuthnRequest, validates it, and extracts the relevant properties.
*/
class SamlAuthenticator extends SamlRequestHandler {
private static final String RESPONSE_TAG_NAME = "Response";
SamlAuthenticator(RealmConfig realmConfig,
Clock clock,
IdpConfiguration idp,
SpConfiguration sp,
TimeValue maxSkew) {
super(realmConfig, clock, idp, sp, maxSkew);
}
/**
* Processes the provided SAML response within the provided token and, if valid, extracts the relevant attributes from it.
*
* @throws org.elasticsearch.ElasticsearchSecurityException If the SAML is invalid for this realm/configuration
*/
SamlAttributes authenticate(SamlToken token) {
final Element root = parseSamlMessage(token.getContent());
if (RESPONSE_TAG_NAME.equals(root.getLocalName()) && SAML_NAMESPACE.equals(root.getNamespaceURI())) {
try {
return authenticateResponse(root, token.getAllowedSamlRequestIds());
} catch (ElasticsearchSecurityException e) {
logger.trace("Rejecting SAML response {} because {}", SamlUtils.toString(root), e.getMessage());
throw e;
}
} else {
throw samlException("SAML content [{}] should have a root element of Namespace=[{}] Tag=[{}]",
root, SAML_NAMESPACE, RESPONSE_TAG_NAME);
}
}
private SamlAttributes authenticateResponse(Element element, Collection<String> allowedSamlRequestIds) {
final Response response = buildXmlObject(element, Response.class);
if (response == null) {
throw samlException("Cannot convert element {} into Response object", element);
}
if (logger.isTraceEnabled()) {
logger.trace(SamlUtils.describeSamlObject(response));
}
if (Strings.hasText(response.getInResponseTo()) && allowedSamlRequestIds.contains(response.getInResponseTo()) == false) {
logger.debug("The SAML Response with ID {} is unsolicited. A user might have used a stale URL or the Identity Provider " +
"incorrectly populates the InResponseTo attribute", response.getID());
throw samlException("SAML content is in-response-to {} but expected one of {} ",
response.getInResponseTo(), allowedSamlRequestIds);
}
final Status status = response.getStatus();
if (status == null || status.getStatusCode() == null) {
throw samlException("SAML Response has no status code");
}
if (isSuccess(status) == false) {
throw samlException("SAML Response is not a 'success' response: Code={} Message={} Detail={}",
status.getStatusCode().getValue(), getMessage(status), getDetail(status));
}
checkResponseDestination(response);
Tuple<Assertion, List<Attribute>> details = extractDetails(response, allowedSamlRequestIds);
final Assertion assertion = details.v1();
final SamlNameId nameId = SamlNameId.forSubject(assertion.getSubject());
final String session = getSessionIndex(assertion);
final List<SamlAttributes.SamlAttribute> attributes = details.v2().stream()
.map(SamlAttributes.SamlAttribute::new)
.collect(Collectors.toList());
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("The SAML Assertion contained the following attributes: \n");
for (SamlAttributes.SamlAttribute attr : attributes) {
sb.append(attr).append("\n");
}
logger.trace(sb.toString());
}
if (attributes.isEmpty() && nameId == null) {
logger.debug("The Attribute Statements of SAML Response with ID {} contained no attributes and the SAML Assertion Subject did" +
"not contain a SAML NameID. Please verify that the Identity Provider configuration with regards to attribute " +
"release is correct. ", response.getID());
throw samlException("Could not process any SAML attributes in {}", response.getElementQName());
}
return new SamlAttributes(nameId, session, attributes);
}
private String getMessage(Status status) {
final StatusMessage sm = status.getStatusMessage();
return sm == null ? null : sm.getMessage();
}
private String getDetail(Status status) {
final StatusDetail sd = status.getStatusDetail();
return sd == null ? null : SamlUtils.toString(sd.getDOM());
}
private boolean isSuccess(Status status) {
return status.getStatusCode().getValue().equals(StatusCode.SUCCESS);
}
private String getSessionIndex(Assertion assertion) {
return assertion.getAuthnStatements().stream().map(as -> as.getSessionIndex()).filter(Objects::nonNull).findFirst().orElse(null);
}
private void checkResponseDestination(Response response) {
final String asc = getSpConfiguration().getAscUrl();
if (asc.equals(response.getDestination()) == false) {
throw samlException("SAML response " + response.getID() + " is for destination " + response.getDestination()
+ " but this realm uses " + asc);
}
}
private Tuple<Assertion, List<Attribute>> extractDetails(Response response, Collection<String> allowedSamlRequestIds) {
final boolean requireSignedAssertions;
if (response.isSigned()) {
validateSignature(response.getSignature());
requireSignedAssertions = false;
} else {
requireSignedAssertions = true;
}
checkIssuer(response.getIssuer(), response);
final int assertionCount = response.getAssertions().size() + response.getEncryptedAssertions().size();
if (assertionCount > 1) {
throw samlException("Expecting only 1 assertion, but response contains multiple (" + assertionCount + ")");
}
for (Assertion assertion : response.getAssertions()) {
return new Tuple<>(assertion, processAssertion(assertion, requireSignedAssertions, allowedSamlRequestIds));
}
for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) {
Assertion assertion = decrypt(encrypted);
moveToNewDocument(assertion);
assertion.getDOM().setIdAttribute("ID", true);
return new Tuple<>(assertion, processAssertion(assertion, requireSignedAssertions, allowedSamlRequestIds));
}
throw samlException("No assertions found in SAML response");
}
private void moveToNewDocument(XMLObject xmlObject) {
final Element element = xmlObject.getDOM();
final Document doc = element.getOwnerDocument().getImplementation().createDocument(null, null, null);
doc.adoptNode(element);
doc.appendChild(element);
}
private Assertion decrypt(EncryptedAssertion encrypted) {
if (decrypter == null) {
throw samlException("SAML assertion [" + text(encrypted, 32) + "] is encrypted, but no decryption key is available");
}
try {
return decrypter.decrypt(encrypted);
} catch (DecryptionException e) {
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML assertion [{}] with [{}]",
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
throw samlException("Failed to decrypt SAML assertion " + text(encrypted, 32), e);
}
}
private List<Attribute> processAssertion(Assertion assertion, boolean requireSignature, Collection<String> allowedSamlRequestIds) {
if (logger.isTraceEnabled()) {
logger.trace("(Possibly decrypted) Assertion: {}", SamlUtils.samlObjectToString(assertion));
logger.trace(SamlUtils.describeSamlObject(assertion));
}
// Do not further process unsigned Assertions
if (assertion.isSigned()) {
validateSignature(assertion.getSignature());
} else if (requireSignature) {
throw samlException("Assertion [{}] is not signed, but a signature is required", assertion.getElementQName());
}
checkConditions(assertion.getConditions());
checkIssuer(assertion.getIssuer(), assertion);
checkSubject(assertion.getSubject(), assertion, allowedSamlRequestIds);
List<Attribute> attributes = new ArrayList<>();
for (AttributeStatement statement : assertion.getAttributeStatements()) {
logger.trace("SAML AttributeStatement has [{}] attributes and [{}] encrypted attributes",
statement.getAttributes().size(), statement.getEncryptedAttributes().size());
attributes.addAll(statement.getAttributes());
for (EncryptedAttribute enc : statement.getEncryptedAttributes()) {
final Attribute attribute = decrypt(enc);
if (attribute != null) {
logger.trace("Successfully decrypted attribute: {}" + SamlUtils.samlObjectToString(attribute));
attributes.add(attribute);
}
}
}
return attributes;
}
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");
return null;
}
try {
return decrypter.decrypt(encrypted);
} catch (DecryptionException e) {
logger.info("Failed to decrypt SAML attribute " + text(encrypted, 32), e);
return null;
}
}
private void checkConditions(Conditions conditions) {
if (conditions != null) {
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(" | ")));
logger.trace("SAML Assertion is only valid between: " + conditions.getNotBefore() + " and " + conditions.getNotOnOrAfter());
}
checkAudienceRestrictions(conditions.getAudienceRestrictions());
checkLifetimeRestrictions(conditions);
}
}
private void checkSubject(Subject assertionSubject, XMLObject parent, Collection<String> allowedSamlRequestIds) {
if (assertionSubject == null) {
throw samlException("SAML Assertion ({}) has no Subject", text(parent, 16));
}
final List<SubjectConfirmationData> confirmationData = assertionSubject.getSubjectConfirmations().stream()
.filter(data -> data.getMethod().equals(METHOD_BEARER))
.map(SubjectConfirmation::getSubjectConfirmationData).filter(Objects::nonNull).collect(Collectors.toList());
if (confirmationData.size() != 1) {
throw samlException("SAML Assertion subject contains {} bearer SubjectConfirmation, while exactly one was expected.",
confirmationData.size());
}
if (logger.isTraceEnabled()) {
logger.trace("SAML Assertion Subject Confirmation intended recipient is: " + confirmationData.get(0).getRecipient());
logger.trace("SAML Assertion Subject Confirmation is only valid before: " + confirmationData.get(0).getNotOnOrAfter());
logger.trace("SAML Assertion Subject Confirmation is in response to: " + confirmationData.get(0).getInResponseTo());
}
checkRecipient(confirmationData.get(0));
checkLifetimeRestrictions(confirmationData.get(0));
checkInResponseTo(confirmationData.get(0), allowedSamlRequestIds);
}
private void checkRecipient(SubjectConfirmationData subjectConfirmationData) {
final SpConfiguration sp = getSpConfiguration();
if (sp.getAscUrl().equals(subjectConfirmationData.getRecipient()) == false) {
throw samlException("SAML Assertion SubjectConfirmationData Recipient {} does not match expected value {}",
subjectConfirmationData.getRecipient(), sp.getAscUrl());
}
}
private void checkInResponseTo(SubjectConfirmationData subjectConfirmationData, Collection<String> allowedSamlRequestIds) {
// Allow for IdP initiated SSO where InResponseTo MUST be missing
if (Strings.hasText(subjectConfirmationData.getInResponseTo())
&& allowedSamlRequestIds.contains(subjectConfirmationData.getInResponseTo()) == false) {
throw samlException("SAML Assertion SubjectConfirmationData is in-response-to {} but expected one of {} ",
subjectConfirmationData.getInResponseTo(), allowedSamlRequestIds);
}
}
private void checkAudienceRestrictions(List<AudienceRestriction> restrictions) {
final String spEntityId = this.getSpConfiguration().getEntityId();
final Predicate<AudienceRestriction> predicate = ar ->
ar.getAudiences().stream().map(Audience::getAudienceURI).anyMatch(spEntityId::equals);
if (restrictions.stream().allMatch(predicate) == false) {
throw samlException("Conditions [{}] do not match required audience [{}]",
restrictions.stream().map(r -> text(r, 32)).collect(Collectors.joining(" | ")), getSpConfiguration().getEntityId());
}
}
private void checkLifetimeRestrictions(Conditions conditions) {
// In order to compensate for clock skew we construct 2 alternate realities
// - a "future now" that is now + the maximum skew we will tolerate. Essentially "if our clock is 2min slow, what time is it now?"
// - a "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 futureNow = now.plusMillis(maxSkewInMillis());
final Instant pastNow = now.minusMillis(maxSkewInMillis());
if (conditions.getNotBefore() != null && futureNow.isBefore(toInstant(conditions.getNotBefore()))) {
throw samlException("Rejecting SAML assertion because [{}] is before [{}]", futureNow, conditions.getNotBefore());
}
if (conditions.getNotOnOrAfter() != null && pastNow.isBefore(toInstant(conditions.getNotOnOrAfter())) == false) {
throw samlException("Rejecting SAML assertion because [{}] is on/after [{}]", pastNow, conditions.getNotOnOrAfter());
}
}
private void checkLifetimeRestrictions(SubjectConfirmationData subjectConfirmationData) {
validateNotOnOrAfter(subjectConfirmationData.getNotOnOrAfter());
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.time.Clock;
import org.elasticsearch.ElasticsearchException;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
/**
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
*/
class SamlAuthnRequestBuilder extends SamlMessageBuilder {
private final String spBinding;
private final String idpBinding;
private String nameIdFormat;
private Boolean forceAuthn;
SamlAuthnRequestBuilder(SpConfiguration spConfig, String spBinding, EntityDescriptor idpDescriptor, String idBinding, Clock clock) {
super(idpDescriptor, spConfig, clock);
this.spBinding = spBinding;
this.idpBinding = idBinding;
}
SamlAuthnRequestBuilder nameIdFormat(String nameIdFormat) {
this.nameIdFormat = nameIdFormat;
return this;
}
SamlAuthnRequestBuilder forceAuthn(Boolean forceAuthn) {
this.forceAuthn = forceAuthn;
return this;
}
AuthnRequest build() {
final String destination = getIdpLocation();
final AuthnRequest request = SamlUtils.buildObject(AuthnRequest.class, AuthnRequest.DEFAULT_ELEMENT_NAME);
request.setID(buildId());
request.setIssueInstant(now());
request.setDestination(destination);
request.setProtocolBinding(spBinding);
request.setAssertionConsumerServiceURL(serviceProvider.getAscUrl());
request.setIssuer(buildIssuer());
request.setNameIDPolicy(buildNameIDPolicy());
request.setForceAuthn(forceAuthn);
return request;
}
private NameIDPolicy buildNameIDPolicy() {
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
nameIDPolicy.setFormat(nameIdFormat);
nameIDPolicy.setSPNameQualifier(serviceProvider.getEntityId());
nameIDPolicy.setAllowCreate(false);
return nameIDPolicy;
}
private String getIdpLocation() {
final String location = getIdentityProviderEndpoint(idpBinding, IDPSSODescriptor::getSingleSignOnServices);
if (location == null) {
throw new ElasticsearchException("Cannot find [{}]/[{}] in descriptor [{}]",
IDPSSODescriptor.DEFAULT_ELEMENT_NAME, idpBinding, identityProvider.getID());
}
return location;
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.rest.RestUtils;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.saml2.core.EncryptedID;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
import org.opensaml.xmlsec.signature.Signature;
import org.w3c.dom.Element;
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
/**
* Processes a LogoutRequest for an IdP-initiated logout.
*/
public class SamlLogoutRequestHandler extends SamlRequestHandler {
private static final String REQUEST_TAG_NAME = "LogoutRequest";
SamlLogoutRequestHandler(RealmConfig realmConfig, Clock clock, IdpConfiguration idp, SpConfiguration sp, TimeValue maxSkew) {
super(realmConfig, clock, idp, sp, maxSkew);
}
/**
* Processes the provided LogoutRequest and extracts the NameID and SessionIndex.
* Returns these in a {@link SamlAttributes} object with an empty attributes list.
* <p>
* The recommended binding for Logout (for maximum interoperability) is HTTP-Redirect.
* Under this binding the signature is applied to the query-string (including parameter
* names and url-encoded/base64-encoded/deflated values). Therefore in order to properly
* validate the signature, this method operates on a raw query- string.
*
* @throws ElasticsearchSecurityException If the SAML is invalid for this realm/configuration
*/
public Result parseFromQueryString(String queryString) {
final ParsedQueryString parsed = parseQueryStringAndValidateSignature(queryString);
final Element root = parseSamlMessage(inflate(decodeBase64(parsed.samlRequest)));
if (REQUEST_TAG_NAME.equals(root.getLocalName()) && SAML_NAMESPACE.equals(root.getNamespaceURI())) {
try {
final LogoutRequest logoutRequest = buildXmlObject(root, LogoutRequest.class);
return parseLogout(logoutRequest, parsed.hasSignature == false, parsed.relayState);
} catch (ElasticsearchSecurityException e) {
logger.trace("Rejecting SAML logout request {} because {}", SamlUtils.toString(root), e.getMessage());
throw e;
}
} else {
throw samlException("SAML content [{}] should have a root element of Namespace=[{}] Tag=[{}]",
root, SAML_NAMESPACE, REQUEST_TAG_NAME);
}
}
private ParsedQueryString parseQueryStringAndValidateSignature(String queryString) {
final String signatureInput = queryString.replaceAll("&Signature=.*$", "");
final Map<String, String> parameters = new HashMap<>();
RestUtils.decodeQueryString(queryString, 0, parameters);
final String samlRequest = parameters.get("SAMLRequest");
if (samlRequest == null) {
throw samlException("Could not parse SAMLRequest from query string: [{}]", queryString);
}
final String relayState = parameters.get("RelayState");
final String signatureAlgorithm = parameters.get("SigAlg");
final String signature = parameters.get("Signature");
if (signature == null || signatureAlgorithm == null) {
return new ParsedQueryString(samlRequest, false, relayState);
}
validateSignature(signatureInput, signatureAlgorithm, signature);
return new ParsedQueryString(samlRequest, true, relayState);
}
private Result parseLogout(LogoutRequest logoutRequest, boolean requireSignature, String relayState) {
final Signature signature = logoutRequest.getSignature();
if (signature == null) {
if (requireSignature) {
throw samlException("Logout request is not signed");
}
} else {
validateSignature(signature);
}
checkIssuer(logoutRequest.getIssuer(), logoutRequest);
checkDestination(logoutRequest);
validateNotOnOrAfter(logoutRequest.getNotOnOrAfter());
return new Result(logoutRequest.getID(), SamlNameId.fromXml(getNameID(logoutRequest)), getSessionIndex(logoutRequest), relayState);
}
private void validateSignature(String inputString, String signatureAlgorithm, String signature) {
final byte[] sigBytes = decodeBase64(signature);
final byte[] inputBytes = inputString.getBytes(StandardCharsets.US_ASCII);
final String signatureText = Strings.cleanTruncate(signature, 32);
checkIdpSignature(credential -> {
if (XMLSigningUtil.verifyWithURI(credential, signatureAlgorithm, sigBytes, inputBytes)) {
logger.debug(() -> new ParameterizedMessage("SAML Signature [{}] matches credentials [{}] [{}]",
signatureText, credential.getEntityId(), credential.getPublicKey()));
return true;
} else {
logger.debug(() -> new ParameterizedMessage("SAML Signature [{}] failed against credentials [{}] [{}]",
signatureText, credential.getEntityId(), credential.getPublicKey()));
return false;
}
}, signatureText);
}
private byte[] decodeBase64(String content) {
try {
return Base64.getDecoder().decode(content.replaceAll("\\s+", ""));
} catch (IllegalArgumentException e) {
logger.info("Failed to decode base64 string [{}] - {}", content, e.toString());
throw samlException("SAML message cannot be Base64 decoded", e);
}
}
private byte[] inflate(byte[] bytes) {
Inflater inflater = new Inflater(true);
try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
InflaterInputStream inflate = new InflaterInputStream(in, inflater);
ByteArrayOutputStream out = new ByteArrayOutputStream(bytes.length * 3 / 2)) {
Streams.copy(inflate, out);
return out.toByteArray();
} catch (IOException e) {
throw samlException("SAML message cannot be inflated", e);
}
}
private NameID getNameID(LogoutRequest logoutRequest) {
final NameID nameID = logoutRequest.getNameID();
if (nameID == null) {
final EncryptedID encryptedID = logoutRequest.getEncryptedID();
if (encryptedID != null) {
final SAMLObject samlObject = decrypt(encryptedID);
if (samlObject instanceof NameID) {
return (NameID) samlObject;
}
}
}
return nameID;
}
private SAMLObject decrypt(EncryptedID encrypted) {
if (decrypter == null) {
throw samlException("SAML EncryptedID [" + text(encrypted, 32) + "] is encrypted, but no decryption key is available");
}
try {
return decrypter.decrypt(encrypted);
} catch (DecryptionException e) {
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML EncryptedID [{}] with [{}]",
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
throw samlException("Failed to decrypt SAML EncryptedID " + text(encrypted, 32), e);
}
}
private String getSessionIndex(LogoutRequest logoutRequest) {
return logoutRequest.getSessionIndexes()
.stream()
.map(as -> as.getSessionIndex())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
private void checkDestination(LogoutRequest request) {
final String url = getSpConfiguration().getLogoutUrl();
if (url == null) {
throw samlException("SAML request " + request.getID() + " is for destination " + request.getDestination()
+ " but this realm is not configured for logout");
}
if (url.equals(request.getDestination()) == false) {
throw samlException("SAML request " + request.getID() + " is for destination " + request.getDestination()
+ " but this realm uses " + url);
}
}
static class ParsedQueryString {
final String samlRequest;
final boolean hasSignature;
final String relayState;
ParsedQueryString(String samlRequest, boolean hasSignature, String relayState) {
this.samlRequest = samlRequest;
this.hasSignature = hasSignature;
this.relayState = relayState;
}
}
public static class Result {
private final String requestId;
private final SamlNameId nameId;
private final String session;
private final String relayState;
public Result(String requestId, SamlNameId nameId, String session, String relayState) {
this.requestId = requestId;
this.nameId = nameId;
this.session = session;
this.relayState = relayState;
}
public String getRequestId() {
return requestId;
}
public SamlNameId getNameId() {
return nameId;
}
public String getSession() {
return session;
}
public String getRelayState() {
return relayState;
}
@Override
public String toString() {
return "SamlLogoutRequestHandler.Result{" +
"requestId='" + requestId + '\'' +
", nameId=" + nameId +
", session='" + session + '\'' +
", relayState='" + relayState + '\'' +
'}';
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.time.Clock;
import org.elasticsearch.common.Strings;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.SessionIndex;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.SSODescriptor;
/**
* Constructs {@code &lt;LogoutRequest&lt;} objects for use in a SAML Single-Sign-Out flow.
*/
class SamlLogoutRequestMessageBuilder extends SamlMessageBuilder {
private final NameID nameId;
private final String session;
SamlLogoutRequestMessageBuilder(Clock clock, SpConfiguration serviceProvider, EntityDescriptor identityProvider,
NameID nameId, String session) {
super(identityProvider, serviceProvider, clock);
this.nameId = nameId;
this.session = session;
}
LogoutRequest build() {
final String logoutUrl = getLogoutUrl();
if (Strings.isNullOrEmpty(logoutUrl)) {
logger.debug("Cannot perform logout because the IDP {} does not provide a logout service", identityProvider.getEntityID());
return null;
}
final SessionIndex sessionIndex = SamlUtils.buildObject(SessionIndex.class, SessionIndex.DEFAULT_ELEMENT_NAME);
sessionIndex.setSessionIndex(session);
final Issuer issuer = buildIssuer();
final LogoutRequest request = SamlUtils.buildObject(LogoutRequest.class, LogoutRequest.DEFAULT_ELEMENT_NAME);
request.setID(buildId());
request.setIssueInstant(now());
request.setDestination(logoutUrl);
request.setNameID(nameId);
request.getSessionIndexes().add(sessionIndex);
request.setIssuer(issuer);
return request;
}
protected String getLogoutUrl() {
return getIdentityProviderEndpoint(SAMLConstants.SAML2_REDIRECT_BINDING_URI, SSODescriptor::getSingleLogoutServices);
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.time.Clock;
import org.elasticsearch.common.Strings;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.Status;
import org.opensaml.saml.saml2.core.StatusCode;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.SSODescriptor;
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
/**
* Constructs {@code &lt;LogoutRespond&lt;} objects for use in a SAML Single-Sign-Out flow.
*/
class SamlLogoutResponseBuilder extends SamlMessageBuilder {
private final String inResponseTo;
private final String statusValue;
SamlLogoutResponseBuilder(Clock clock, SpConfiguration serviceProvider, EntityDescriptor identityProvider,
String inResponseTo, String statusValue) {
super(identityProvider, serviceProvider, clock);
this.inResponseTo = inResponseTo;
this.statusValue = statusValue;
}
LogoutResponse build() {
final String destination = getLogoutUrl();
if (Strings.isNullOrEmpty(destination)) {
throw samlException("Cannot send LogoutResponse because the IDP {} does not provide a logout service",
identityProvider.getEntityID());
}
final LogoutResponse res = SamlUtils.buildObject(LogoutResponse.class, LogoutResponse.DEFAULT_ELEMENT_NAME);
res.setID(buildId());
res.setIssueInstant(now());
res.setDestination(destination);
res.setIssuer(buildIssuer());
res.setInResponseTo(inResponseTo);
final Status status = SamlUtils.buildObject(Status.class, Status.DEFAULT_ELEMENT_NAME);
final StatusCode statusCode= SamlUtils.buildObject(StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME);
statusCode.setValue(this.statusValue);
status.setStatusCode(statusCode);
res.setStatus(status);
return res;
}
protected String getLogoutUrl() {
return getIdentityProviderEndpoint(SAMLConstants.SAML2_REDIRECT_BINDING_URI, SSODescriptor::getSingleLogoutServices);
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.time.Clock;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.logging.Loggers;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.metadata.Endpoint;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
/**
* Abstract base class for object that build some sort of {@link org.opensaml.saml.common.SAMLObject}
*/
public abstract class SamlMessageBuilder {
protected final Logger logger;
protected final Clock clock;
protected final SpConfiguration serviceProvider;
protected final EntityDescriptor identityProvider;
public SamlMessageBuilder(EntityDescriptor identityProvider, SpConfiguration serviceProvider, Clock clock) {
this.logger = Loggers.getLogger(getClass());
this.identityProvider = identityProvider;
this.serviceProvider = serviceProvider;
this.clock = clock;
}
protected String getIdentityProviderEndpoint(String binding,
Function<IDPSSODescriptor, ? extends Collection<? extends Endpoint>> selector) {
final List<String> locations = identityProvider.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME).stream()
.map(rd -> (IDPSSODescriptor) rd)
.flatMap(idp -> selector.apply(idp).stream())
.filter(endp -> binding.equals(endp.getBinding()))
.map(sso -> sso.getLocation())
.collect(Collectors.toList());
if (locations.isEmpty()) {
return null;
}
if (locations.size() > 1) {
throw new ElasticsearchException("Found multiple locations for binding [{}] in descriptor [{}] - [{}]",
binding, identityProvider.getID(), locations);
}
return locations.get(0);
}
protected DateTime now() {
return new DateTime(clock.millis(), DateTimeZone.UTC);
}
protected Issuer buildIssuer() {
Issuer issuer = SamlUtils.buildObject(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(this.serviceProvider.getEntityId());
return issuer;
}
protected String buildId() {
// 20 bytes (160 bits) of randomness as recommended by the SAML spec
return SamlUtils.generateSecureNCName(20);
}
}

View File

@ -0,0 +1,317 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import java.io.InputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.cli.EnvironmentAwareCommand;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.SuppressForbidden;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.logging.ServerLoggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.LocaleUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.RealmSettings;
import org.elasticsearch.xpack.security.authc.saml.SamlSpMetadataBuilder.ContactInfo;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import static org.elasticsearch.xpack.security.authc.RealmSettings.getRealmType;
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.require;
/**
* CLI tool to generate SAML Metadata for a Service Provider (realm)
*/
public class SamlMetadataCommand extends EnvironmentAwareCommand {
static final String METADATA_SCHEMA = "saml-schema-metadata-2.0.xsd";
private final OptionSpec<String> outputPathSpec;
private final OptionSpec<Void> batchSpec;
private final OptionSpec<String> realmSpec;
private final OptionSpec<String> localeSpec;
private final OptionSpec<String> serviceNameSpec;
private final OptionSpec<String> attributeSpec;
private final OptionSpec<String> orgNameSpec;
private final OptionSpec<String> orgDisplayNameSpec;
private final OptionSpec<String> orgUrlSpec;
private final OptionSpec<Void> contactsSpec;
public static void main(String[] args) throws Exception {
new SamlMetadataCommand().main(args, Terminal.DEFAULT);
}
public SamlMetadataCommand() {
super("Generate Service Provider Metadata for a SAML realm");
outputPathSpec = parser.accepts("out", "path of the xml file that should be generated").withRequiredArg();
batchSpec = parser.accepts("batch", "Do not prompt");
realmSpec = parser.accepts("realm", "name of the elasticsearch realm for which metadata should be generated").withRequiredArg();
localeSpec = parser.accepts("locale", "the locale to be used for elements that require a language").withRequiredArg();
serviceNameSpec = parser.accepts("service-name", "the name to apply to the attribute consuming service").withRequiredArg();
attributeSpec = parser.accepts("attribute", "additional SAML attributes to request").withRequiredArg();
orgNameSpec = parser.accepts("organisation-name", "the name of the organisation operating this service").withRequiredArg();
orgDisplayNameSpec = parser.accepts("organisation-display-name", "the display-name of the organisation operating this service")
.availableIf(orgNameSpec).withRequiredArg();
orgUrlSpec = parser.accepts("organisation-url", "the URL of the organisation operating this service")
.requiredIf(orgNameSpec).withRequiredArg();
contactsSpec = parser.accepts("contacts", "Include contact information in metadata").availableUnless(batchSpec);
}
@Override
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
// OpenSAML prints a lot of _stuff_ at info level, that really isn't needed in a command line tool.
ServerLoggers.setLevel(Loggers.getLogger("org.opensaml"), Level.WARN);
final Logger logger = Loggers.getLogger(getClass());
SamlUtils.initialize(logger);
final EntityDescriptor descriptor = buildEntityDescriptor(terminal, options, env);
final Path xml = writeOutput(terminal, options, descriptor);
validateXml(terminal, xml);
}
// package-protected for testing
EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Environment env) throws Exception {
final boolean batch = options.has(batchSpec);
final RealmConfig realm = findRealm(terminal, options, env);
terminal.println(Terminal.Verbosity.VERBOSE,
"Using realm configuration\n=====\n" + realm.settings().toDelimitedString('\n') + "=====");
final Locale locale = findLocale(options);
terminal.println(Terminal.Verbosity.VERBOSE, "Using locale: " + locale.toLanguageTag());
final SpConfiguration spConfig = SamlRealm.getSpConfiguration(realm);
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(locale, spConfig.getEntityId())
.assertionConsumerServiceUrl(spConfig.getAscUrl())
.singleLogoutServiceUrl(spConfig.getLogoutUrl())
.encryptionCredential(spConfig.getEncryptionCredential())
.signingCredential(spConfig.getSigningConfiguration().getCredential())
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
.nameIdFormat(require(realm, SamlRealmSettings.NAMEID_FORMAT))
.serviceName(option(serviceNameSpec, options, env.settings().get("cluster.name")));
Map<String, String> attributes = getAttributeNames(options, realm);
for (String attr : attributes.keySet()) {
final String name;
String friendlyName;
final String settingName = attributes.get(attr);
final String attributeSource = settingName == null ? "command line" : '"' + settingName + '"';
if (attr.contains(":")) {
name = attr;
if (batch) {
friendlyName = settingName;
} else {
friendlyName = terminal.readText("What is the friendly name for " +
attributeSource
+ " attribute \"" + attr + "\" [default: " +
(settingName == null ? "none" : settingName) +
"] ");
if (Strings.isNullOrEmpty(friendlyName)) {
friendlyName = settingName;
}
}
} else {
if (batch) {
throw new UserException(ExitCodes.CONFIG, "Option " + batchSpec.toString() + " is specified, but attribute "
+ attr + " appears to be a FriendlyName value");
}
friendlyName = attr;
name = requireText(terminal,
"What is the standard (urn) name for " + attributeSource + " attribute \"" + attr + "\" (required): ");
}
terminal.println(Terminal.Verbosity.VERBOSE, "Requesting attribute '" + name + "' (FriendlyName: '" + friendlyName + "')");
builder.withAttribute(friendlyName, name);
}
if (options.has(orgNameSpec) && options.has(orgUrlSpec)) {
String name = orgNameSpec.value(options);
builder.organization(name, option(orgDisplayNameSpec, options, name), orgUrlSpec.value(options));
}
if (options.has(contactsSpec)) {
terminal.println("\nPlease enter the personal details for each contact to be included in the metadata");
do {
final String givenName = requireText(terminal, "What is the given name for the contact: ");
final String surName = requireText(terminal, "What is the surname for the contact: ");
final String displayName = givenName + ' ' + surName;
final String email = requireText(terminal, "What is the email address for " + displayName + ": ");
String type;
while (true) {
type = requireText(terminal, "What is the contact type for " + displayName + ": ");
if (ContactInfo.TYPES.containsKey(type)) {
break;
} else {
terminal.println("Type '" + type + "' is not valid. Valid values are "
+ Strings.collectionToCommaDelimitedString(ContactInfo.TYPES.keySet()));
}
}
builder.withContact(type, givenName, surName, email);
} while (terminal.promptYesNo("Enter details for another contact", true));
}
return builder.build();
}
private Path writeOutput(Terminal terminal, OptionSet options, EntityDescriptor descriptor) throws Exception {
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller();
final Element element = marshaller.marshall(descriptor);
final Path outputFile = resolvePath(option(outputPathSpec, options, "saml-elasticsearch-metadata.xml"));
final Writer writer = Files.newBufferedWriter(outputFile);
SamlUtils.print(element, writer, true);
terminal.println("\nWrote SAML metadata to " + outputFile);
return outputFile;
}
private void validateXml(Terminal terminal, Path xml) throws Exception {
try (InputStream xmlInput = Files.newInputStream(xml)) {
SamlUtils.validate(xmlInput, METADATA_SCHEMA);
terminal.println(Terminal.Verbosity.VERBOSE, "The generated metadata file conforms to the SAML metadata schema");
} catch (SAXException e) {
terminal.println(Terminal.Verbosity.SILENT, "Error - The generated metadata file does not conform to the SAML metadata schema");
terminal.println("While validating " + xml.toString() + " the follow errors were found:");
printExceptions(terminal, e);
throw new UserException(ExitCodes.CODE_ERROR, "Generated metadata is not valid");
}
}
private void printExceptions(Terminal terminal, Throwable throwable) {
terminal.println(" - " + throwable.getMessage());
for (Throwable sup : throwable.getSuppressed()) {
printExceptions(terminal, sup);
}
if (throwable.getCause() != null && throwable.getCause() != throwable) {
printExceptions(terminal, throwable.getCause());
}
}
@SuppressForbidden(reason = "CLI tool working from current directory")
private Path resolvePath(String name) {
return PathUtils.get(name).normalize();
}
private String requireText(Terminal terminal, String prompt) {
String value = null;
while (Strings.isNullOrEmpty(value)) {
value = terminal.readText(prompt);
}
return value;
}
private <T> T option(OptionSpec<T> spec, OptionSet options, T defaultValue) {
if (options.has(spec)) {
return spec.value(options);
} else {
return defaultValue;
}
}
/**
* Map of saml-attribute name to configuration-setting name
*/
private Map<String, String> getAttributeNames(OptionSet options, RealmConfig realm) {
Map<String, String> attributes = new LinkedHashMap<>();
for (String a : attributeSpec.values(options)) {
attributes.put(a, null);
}
final Settings attributeSettings = realm.settings().getByPrefix(SamlRealmSettings.AttributeSetting.ATTRIBUTES_PREFIX);
for (String key : sorted(attributeSettings.keySet())) {
final String attr = attributeSettings.get(key);
attributes.put(attr, key);
}
return attributes;
}
// We sort this Set so that it is deterministic for testing
private SortedSet<String> sorted(Set<String> strings) {
return new TreeSet<>(strings);
}
private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment env) throws UserException {
final Map<String, Settings> realms = RealmSettings.getRealmSettings(env.settings());
if (options.has(realmSpec)) {
final String name = realmSpec.value(options);
final Settings settings = realms.get(name);
if (settings == null) {
throw new UserException(ExitCodes.CONFIG, "No such realm '" + name + "' defined in " + env.configFile());
}
final String realmType = getRealmType(settings);
if (isSamlRealm(realmType)) {
return buildRealm(name, settings, env);
} else {
throw new UserException(ExitCodes.CONFIG, "Realm '" + name + "' is not a SAML realm (is '" + realmType + "')");
}
} else {
final List<Map.Entry<String, Settings>> saml = realms.entrySet().stream()
.filter(entry -> isSamlRealm(getRealmType(entry.getValue())))
.collect(Collectors.toList());
if (saml.isEmpty()) {
throw new UserException(ExitCodes.CONFIG, "There is no SAML realm configured in " + env.configFile());
}
if (saml.size() > 1) {
terminal.println("Using configuration in " + env.configFile());
terminal.println("Found multiple SAML realms: " + saml.stream().map(Map.Entry::getKey).collect(Collectors.joining(", ")));
terminal.println("Use the -" + optionName(realmSpec) + " option to specify an explicit realm");
throw new UserException(ExitCodes.CONFIG,
"Found multiple SAML realms, please specify one with '-" + optionName(realmSpec) + "'");
}
final Map.Entry<String, Settings> entry = saml.get(0);
terminal.println("Building metadata for SAML realm " + entry.getKey());
return buildRealm(entry.getKey(), entry.getValue(), env);
}
}
private String optionName(OptionSpec<?> spec) {
return spec.options().get(0);
}
private RealmConfig buildRealm(String name, Settings settings, Environment env) {
return new RealmConfig(name, settings, env.settings(), env, new ThreadContext(env.settings()));
}
private boolean isSamlRealm(String realmType) {
return SamlRealmSettings.TYPE.equals(realmType);
}
private Locale findLocale(OptionSet options) {
if (options.has(localeSpec)) {
return LocaleUtils.parse(localeSpec.value(options));
} else {
return Locale.getDefault();
}
}
// For testing
OptionParser getParser() {
return parser;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.saml;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.Subject;
/**
* Lightweight (non-XML) representation of a SAML {@code NameID} element
*/
public class SamlNameId {
final String format;
final String value;
final String idpNameQualifier;
final String spNameQualifier;
final String spProvidedId;
public SamlNameId(String format, String value, String idpNameQualifier, String spNameQualifier, String spProvidedId) {
this.format = format;
this.value = value;
this.idpNameQualifier = idpNameQualifier;
this.spNameQualifier = spNameQualifier;
this.spProvidedId = spProvidedId;
}
@Override
public String toString() {
return "NameId(" + format + ")=" + value;
}
public NameID asXml() {
final NameID nameId = SamlUtils.buildObject(NameID.class, NameID.DEFAULT_ELEMENT_NAME);
nameId.setFormat(format);
nameId.setValue(value);
nameId.setNameQualifier(idpNameQualifier);
nameId.setSPNameQualifier(spNameQualifier);
nameId.setSPProvidedID(spProvidedId);
return nameId;
}
static SamlNameId fromXml(NameID name) {
if (name == null) {
return null;
}
return new SamlNameId(name.getFormat(), name.getValue(), name.getNameQualifier(),
name.getSPNameQualifier(), name.getSPProvidedID());
}
static SamlNameId forSubject(Subject subject) {
if (subject == null) {
return null;
}
final NameID name = subject.getNameID();
return fromXml(name);
}
}

Some files were not shown because too many files have changed in this diff Show More