Skip to content

Oidc additional client auth types (#58708) #62289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ public class OpenIdConnectRealmSettings {
private OpenIdConnectRealmSettings() {
}

private static final List<String> SUPPORTED_SIGNATURE_ALGORITHMS = Collections.unmodifiableList(
Arrays.asList("HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"));
private static final List<String> RESPONSE_TYPES = Arrays.asList("code", "id_token", "id_token token");
public static final List<String> SUPPORTED_SIGNATURE_ALGORITHMS =
org.elasticsearch.common.collect.List.of(
"HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512");
private static final List<String> RESPONSE_TYPES = org.elasticsearch.common.collect.List.of(
"code", "id_token", "id_token token");
public static final List<String> CLIENT_AUTH_METHODS = org.elasticsearch.common.collect.List.of(
"client_secret_basic", "client_secret_post", "client_secret_jwt");
public static final List<String> SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS = org.elasticsearch.common.collect.List.of(
"HS256", "HS384", "HS512");
public static final String TYPE = "oidc";

public static final Setting.AffixSetting<String> RP_CLIENT_ID
Expand Down Expand Up @@ -78,7 +84,22 @@ private OpenIdConnectRealmSettings() {
public static final Setting.AffixSetting<List<String>> RP_REQUESTED_SCOPES = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "rp.requested_scopes",
key -> Setting.listSetting(key, Collections.singletonList("openid"), Function.identity(), Setting.Property.NodeScope));

public static final Setting.AffixSetting<String> RP_CLIENT_AUTH_METHOD
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.client_auth_method",
key -> new Setting<>(key, "client_secret_basic", Function.identity(), v -> {
if (CLIENT_AUTH_METHODS.contains(v) == false) {
throw new IllegalArgumentException(
"Invalid value [" + v + "] for [" + key + "]. Allowed values are " + CLIENT_AUTH_METHODS + "}]");
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.client_auth_jwt_signature_algorithm",
key -> new Setting<>(key, "HS384", Function.identity(), v -> {
if (SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS.contains(v) == false) {
throw new IllegalArgumentException(
"Invalid value [" + v + "] for [" + key + "]. Allowed values are " + SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS + "}]");
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> OP_AUTHORIZATION_ENDPOINT
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.authorization_endpoint",
key -> Setting.simpleString(key, v -> {
Expand Down Expand Up @@ -194,8 +215,9 @@ public Iterator<Setting<?>> settings() {
public static Set<Setting.AffixSetting<?>> getSettings() {
final Set<Setting.AffixSetting<?>> set = Sets.newHashSet(
RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET, RP_SIGNATURE_ALGORITHM,
RP_POST_LOGOUT_REDIRECT_URI, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT,
OP_ENDSESSION_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH, POPULATE_USER_METADATA, HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT,
RP_POST_LOGOUT_REDIRECT_URI, RP_CLIENT_AUTH_METHOD, RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM, OP_AUTHORIZATION_ENDPOINT,
OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ENDSESSION_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH,
POPULATE_USER_METADATA, HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT,
HTTP_SOCKET_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS, HTTP_PROXY_HOST, HTTP_PROXY_PORT,
HTTP_PROXY_SCHEME, ALLOWED_CLOCK_SKEW);
set.addAll(DelegatedAuthorizationSettings.getSettings(TYPE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.id.State;
import com.nimbusds.oauth2.sdk.token.AccessToken;
Expand Down Expand Up @@ -85,6 +87,7 @@
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings;
import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
import org.elasticsearch.xpack.core.ssl.SSLService;

Expand Down Expand Up @@ -463,19 +466,36 @@ private void exchangeCodeForToken(AuthorizationCode code, ActionListener<Tuple<A
try {
final AuthorizationCodeGrant codeGrant = new AuthorizationCodeGrant(code, rpConfig.getRedirectUri());
final HttpPost httpPost = new HttpPost(opConfig.getTokenEndpoint());
httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
final List<NameValuePair> params = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : codeGrant.toParameters().entrySet()) {
// All parameters of AuthorizationCodeGrant are singleton lists
params.add(new BasicNameValuePair(entry.getKey(), entry.getValue().get(0)));
}
if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials(URLEncoder.encode(rpConfig.getClientId().getValue(), StandardCharsets.UTF_8.name()),
URLEncoder.encode(rpConfig.getClientSecret().toString(), StandardCharsets.UTF_8.name()));
httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null));
} else if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
params.add(new BasicNameValuePair("client_id", rpConfig.getClientId().getValue()));
params.add(new BasicNameValuePair("client_secret", rpConfig.getClientSecret().toString()));
} else if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
ClientSecretJWT clientSecretJWT = new ClientSecretJWT(rpConfig.getClientId(), opConfig.getTokenEndpoint(),
rpConfig.getClientAuthenticationJwtAlgorithm(), new Secret(rpConfig.getClientSecret().toString()));
for (Map.Entry<String, List<String>> entry : clientSecretJWT.toParameters().entrySet()) {
// Both client_assertion and client_assertion_type are singleton lists
params.add(new BasicNameValuePair(entry.getKey(), entry.getValue().get(0)));
}
} else {
tokensListener.onFailure(new ElasticsearchSecurityException("Failed to exchange code for Id Token using Token Endpoint." +
"Expected client authentication method to be one of " + OpenIdConnectRealmSettings.CLIENT_AUTH_METHODS
+ " but was [" + rpConfig.getClientAuthenticationMethod() + "]"));
}
httpPost.setEntity(new UrlEncodedFormEntity(params));
httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials(URLEncoder.encode(rpConfig.getClientId().getValue(), StandardCharsets.UTF_8.name()),
URLEncoder.encode(rpConfig.getClientSecret().toString(), StandardCharsets.UTF_8.name()));
httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null));
SpecialPermission.check();
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {

httpClient.execute(httpPost, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse result) {
Expand All @@ -496,7 +516,7 @@ public void cancelled() {
});
return null;
});
} catch (AuthenticationException | UnsupportedEncodingException e) {
} catch (AuthenticationException | UnsupportedEncodingException | JOSEException e) {
tokensListener.onFailure(
new ElasticsearchSecurityException("Failed to exchange code for Id Token using the Token Endpoint.", e));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.id.State;
Expand Down Expand Up @@ -73,6 +74,8 @@
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.OP_USERINFO_ENDPOINT;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.POPULATE_USER_METADATA;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.PRINCIPAL_CLAIM;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_AUTH_METHOD;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_ID;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_SECRET;
import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_POST_LOGOUT_REDIRECT_URI;
Expand Down Expand Up @@ -266,9 +269,11 @@ private RelyingPartyConfiguration buildRelyingPartyConfiguration(RealmConfig con
requestedScope.add("openid");
}
final JWSAlgorithm signatureAlgorithm = JWSAlgorithm.parse(require(config, RP_SIGNATURE_ALGORITHM));

final ClientAuthenticationMethod clientAuthenticationMethod =
ClientAuthenticationMethod.parse(require(config, RP_CLIENT_AUTH_METHOD));
final JWSAlgorithm clientAuthJwtAlgorithm = JWSAlgorithm.parse(require(config, RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM));
return new RelyingPartyConfiguration(clientId, clientSecret, redirectUri, responseType, requestedScope,
signatureAlgorithm, postLogoutRedirectUri);
signatureAlgorithm, clientAuthenticationMethod, clientAuthJwtAlgorithm, postLogoutRedirectUri);
}

private OpenIdConnectProviderConfiguration buildOpenIdConnectProviderConfiguration(RealmConfig config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.id.ClientID;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.SecureString;
Expand All @@ -26,15 +27,22 @@ public class RelyingPartyConfiguration {
private final Scope requestedScope;
private final JWSAlgorithm signatureAlgorithm;
private final URI postLogoutRedirectUri;
private final ClientAuthenticationMethod clientAuthenticationMethod;
private final JWSAlgorithm clientAuthenticationJwtAlgorithm;

public RelyingPartyConfiguration(ClientID clientId, SecureString clientSecret, URI redirectUri, ResponseType responseType,
Scope requestedScope, JWSAlgorithm algorithm, @Nullable URI postLogoutRedirectUri) {
Scope requestedScope, JWSAlgorithm algorithm, ClientAuthenticationMethod clientAuthenticationMethod,
JWSAlgorithm clientAuthenticationJwtAlgorithm, @Nullable URI postLogoutRedirectUri) {
this.clientId = Objects.requireNonNull(clientId, "clientId must be provided");
this.clientSecret = Objects.requireNonNull(clientSecret, "clientSecret must be provided");
this.redirectUri = Objects.requireNonNull(redirectUri, "redirectUri must be provided");
this.responseType = Objects.requireNonNull(responseType, "responseType must be provided");
this.requestedScope = Objects.requireNonNull(requestedScope, "responseType must be provided");
this.signatureAlgorithm = Objects.requireNonNull(algorithm, "algorithm must be provided");
this.clientAuthenticationMethod = Objects.requireNonNull(clientAuthenticationMethod,
"clientAuthenticationMethod must be provided");
this.clientAuthenticationJwtAlgorithm = Objects.requireNonNull(clientAuthenticationJwtAlgorithm,
"clientAuthenticationJwtAlgorithm must be provided");
this.postLogoutRedirectUri = postLogoutRedirectUri;
}

Expand Down Expand Up @@ -65,4 +73,12 @@ public JWSAlgorithm getSignatureAlgorithm() {
public URI getPostLogoutRedirectUri() {
return postLogoutRedirectUri;
}

public ClientAuthenticationMethod getClientAuthenticationMethod() {
return clientAuthenticationMethod;
}

public JWSAlgorithm getClientAuthenticationJwtAlgorithm() {
return clientAuthenticationJwtAlgorithm;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.nimbusds.jwt.proc.BadJWTException;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
Expand Down Expand Up @@ -892,8 +893,11 @@ private RelyingPartyConfiguration getDefaultRpConfig() throws URISyntaxException
new ResponseType("id_token", "token"),
new Scope("openid"),
JWSAlgorithm.RS384,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
JWSAlgorithm.HS384,
new URI("https://rp.elastic.co/successfull_logout"));
}

private RelyingPartyConfiguration getRpConfig(String alg) throws URISyntaxException {
return new RelyingPartyConfiguration(
new ClientID("rp-my"),
Expand All @@ -902,6 +906,8 @@ private RelyingPartyConfiguration getRpConfig(String alg) throws URISyntaxExcept
new ResponseType("id_token", "token"),
new Scope("openid"),
JWSAlgorithm.parse(alg),
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
JWSAlgorithm.HS384,
new URI("https://rp.elastic.co/successfull_logout"));
}

Expand All @@ -913,6 +919,8 @@ private RelyingPartyConfiguration getRpConfigNoAccessToken(String alg) throws UR
new ResponseType("id_token"),
new Scope("openid"),
JWSAlgorithm.parse(alg),
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
JWSAlgorithm.HS384,
new URI("https://rp.elastic.co/successfull_logout"));
}

Expand Down
Loading