Skip to content

Commit

Permalink
Issue 21826: Add prototype code for creating private_key_jwt client a…
Browse files Browse the repository at this point in the history
…uthentication

- Adds logic to the OIDC token endpoint request code to add `private_key_jwt` related parameters when using that token endpoint auth method
- Adds a large part of the logic to the `PrivateKeyJwtAuthMethod` class to create the JWT for client authentication
    - Note: Still missing logic to obtain the key itself using the keyAliasName

For OpenLiberty#21826
  • Loading branch information
ayoho committed Apr 26, 2023
1 parent 80482ac commit f3775a1
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 21 deletions.
8 changes: 3 additions & 5 deletions dev/com.ibm.ws.security.openidconnect.clients.common/bnd.bnd
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#*******************************************************************************
# Copyright (c) 2018, 2022 IBM Corporation and others.
# Copyright (c) 2018, 2023 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License 2.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# IBM Corporation - initial API and implementation
#*******************************************************************************
-include= ~../cnf/resources/bnd/bundle.props

Expand Down Expand Up @@ -39,7 +36,8 @@ Export-Package: \
com.ibm.ws.security.openidconnect.token, \
com.ibm.ws.security.openidconnect.jwk, \
com.ibm.ws.security.openidconnect.token.impl, \
com.ibm.ws.security.openidconnect.common
com.ibm.ws.security.openidconnect.common, \
com.ibm.ws.security.openidconnect.common.token.auth

Import-Package: \
!*.internal.*, \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.ibm.ws.security.common.ssl.NoSSLSocketFactoryException;
import com.ibm.ws.security.common.structures.BoundedHashMap;
import com.ibm.ws.security.openidconnect.client.jose4j.util.Jose4jUtil;
import com.ibm.ws.security.openidconnect.common.token.auth.TokenEndpointAuthMethod;
import com.ibm.ws.webcontainer.security.AuthResult;
import com.ibm.ws.webcontainer.security.ProviderAuthenticationResult;
import com.ibm.wsspi.ssl.SSLSupport;
Expand Down Expand Up @@ -145,14 +146,18 @@ ProviderAuthenticationResult sendTokenRequestAndValidateResult(OidcClientRequest
String message = Tr.formatMessage(tc, "OIDC_CLIENT_NULL_TOKEN_ENDPOINT", clientId);
throw new MalformedURLException(message);
}

Builder tokenRequestBuilder = new TokenRequestor.Builder(url, clientId, clientConfig.getClientSecret(), redirectUrl, authzCode);
tokenRequestBuilder.sslSocketFactory(sslSocketFactory);
tokenRequestBuilder.grantType(clientConfig.getGrantType());
tokenRequestBuilder.isHostnameVerification(clientConfig.isHostNameVerificationEnabled());
tokenRequestBuilder.authMethod(clientConfig.getTokenEndpointAuthMethod());
tokenRequestBuilder.resources(OIDCClientAuthenticatorUtil.getResources(clientConfig));
tokenRequestBuilder.customParams(getTokenRequestCustomParameters());
tokenRequestBuilder.useSystemPropertiesForHttpClientConnections(clientConfig.getUseSystemPropertiesForHttpClientConnections());
String tokenEndpointAuthMethod = clientConfig.getTokenEndpointAuthMethod();
tokenRequestBuilder.authMethod(tokenEndpointAuthMethod);
setAuthMethodSpecificSettings(tokenRequestBuilder, tokenEndpointAuthMethod);

TokenRequestor tokenRequestor = tokenRequestBuilder.build();

TokenResponse tokenResponse = tokenRequestor.requestTokens();
Expand Down Expand Up @@ -185,6 +190,14 @@ HashMap<String, String> addPkceParameters(HashMap<String, String> parameters) {
return parameters;
}

void setAuthMethodSpecificSettings(Builder tokenRequestBuilder, String tokenEndpointAuthMethod) {
TokenEndpointAuthMethod authMethod = TokenEndpointAuthMethod.getInstance(tokenEndpointAuthMethod, clientConfig);
if (authMethod == null) {
return;
}
authMethod.setAuthMethodSpecificSettings(tokenRequestBuilder);
}

// refactored from Oauth SendErrorJson. Only usable for sending an http400.
private void sendErrorJSON(int statusCode, String errorCode, String errorDescription) {
final String error = "error";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2023 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package com.ibm.ws.security.openidconnect.common.token.auth;

import java.security.Key;

import com.ibm.ws.security.openidconnect.clients.common.ConvergedClientConfig;

import io.openliberty.security.oidcclientcore.token.TokenRequestor.Builder;

public class PrivateKeyJwtAuthMethod extends TokenEndpointAuthMethod {

public static final String AUTH_METHOD = "private_key_jwt";

private final ConvergedClientConfig clientConfig;

public PrivateKeyJwtAuthMethod(ConvergedClientConfig clientConfig) {
this.clientConfig = clientConfig;
}

@Override
public void setAuthMethodSpecificSettings(Builder tokenRequestBuilder) {
tokenRequestBuilder.clientAssertionSigningAlgorithm(clientConfig.getTokenEndpointAuthSigningAlgorithm());
Key clientAssertionSigningKey = getKeyForPrivateKeyJwtClientAssertion();
tokenRequestBuilder.clientAssertionSigningKey(clientAssertionSigningKey);
}

private Key getKeyForPrivateKeyJwtClientAssertion() {
// TODO
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2023 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package com.ibm.ws.security.openidconnect.common.token.auth;

import com.ibm.ws.security.openidconnect.clients.common.ConvergedClientConfig;

import io.openliberty.security.oidcclientcore.token.TokenRequestor.Builder;

public abstract class TokenEndpointAuthMethod {

public static TokenEndpointAuthMethod getInstance(String authMethod, ConvergedClientConfig clientConfig) {
if (authMethod == null || authMethod.isEmpty()) {
return null;
}
if (PrivateKeyJwtAuthMethod.AUTH_METHOD.equals(authMethod)) {
return new PrivateKeyJwtAuthMethod(clientConfig);
}
return null;
}

public abstract void setAuthMethodSpecificSettings(Builder tokenRequestBuilder);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2023 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
/**
* @version 1.0
*/
@org.osgi.annotation.versioning.Version("1.0")
@TraceOptions(traceGroup = TraceConstants.TRACE_GROUP, messageBundle = TraceConstants.MESSAGE_BUNDLE)
package com.ibm.ws.security.openidconnect.common.token.auth;

import com.ibm.websphere.ras.annotation.TraceOptions;
import com.ibm.ws.security.openidconnect.common.TraceConstants;
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ protected void setAllConfigAttributes(Map<String, Object> props) throws SocialLo
userInfoEndpointEnabled = configUtils.getBooleanConfigAttribute(props, KEY_USERINFO_ENDPOINT_ENABLED, userInfoEndpointEnabled);
signatureAlgorithm = configUtils.getConfigAttribute(props, KEY_SIGNATURE_ALGORITHM);
tokenEndpointAuthMethod = configUtils.getConfigAttribute(props, KEY_tokenEndpointAuthMethod);
if (!"private_key_jwt".equals(tokenEndpointAuthMethod) && (clientSecret == null || clientSecret.isEmpty())) {
// TODO - log new warning message letting them know a client secret is required unless they're using the private_key_jwt auth method
}
tokenEndpointAuthSigningAlgorithm = configUtils.getConfigAttribute(props, CFG_KEY_TOKEN_ENDPOINT_AUTH_SIGNING_ALGORITHM);
keyAliasName = configUtils.getConfigAttribute(props, KEY_keyAliasName);
scope = configUtils.getConfigAttribute(props, KEY_scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,10 @@ TOKEN_RESPONSE_MISSING_PARAMETER=CWWKS2429E: The token response from the OpenID
TOKEN_RESPONSE_MISSING_PARAMETER.explanation=Either the OpenID Connect provider did not return a required parameter or failed to process the token request.
TOKEN_RESPONSE_MISSING_PARAMETER.useraction=Verify that the configured token endpoint is correct and capable of responding to OAuth token requests.

PRIVATE_KEY_JWT_AUTH_ERROR=CWWKS2430E: The [{0}] OpenID Connect client failed to build a JSON Web Token to use for client authentication. {1}
PRIVATE_KEY_JWT_AUTH_ERROR.explanation=The OpenID Connect client is configured to use JSON Web Tokens for client authentication, but an error occurred while creating the token.
PRIVATE_KEY_JWT_AUTH_ERROR.useraction=For more information, see the error in the message.

PRIVATE_KEY_JWT_MISSING_SIGNING_KEY=CWWKS2431E: The [{0}] OpenID Connect client is missing the key that is needed to sign the token that is used for client authentication.
PRIVATE_KEY_JWT_MISSING_SIGNING_KEY.explanation=The OpenID Connect client configuration is missing data, or a key cannot be found with the key alias name in the SSL configuration that is specified in the OpenID Connect client.
PRIVATE_KEY_JWT_MISSING_SIGNING_KEY.useraction=Ensure that the OpenID Connect client has a key alias name and SSL reference configured. Verify that the keystore that is referenced by the SSL configuration contains a key whose alias matches the key alias name in the OpenID Connect client configuration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package io.openliberty.security.oidcclientcore.exceptions;

import com.ibm.websphere.ras.Tr;
import com.ibm.websphere.ras.TraceComponent;

public class PrivateKeyJwtAuthException extends Exception {

public static final TraceComponent tc = Tr.register(PrivateKeyJwtAuthException.class);

private static final long serialVersionUID = 1L;

private final String clientId;
private final String nlsMessage;

public PrivateKeyJwtAuthException(String clientId, String nlsMessage) {
this.clientId = clientId;
this.nlsMessage = nlsMessage;
}

@Override
public String getMessage() {
return Tr.formatMessage(tc, "PRIVATE_KEY_JWT_AUTH_ERROR", clientId, nlsMessage);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package io.openliberty.security.oidcclientcore.exceptions;

import com.ibm.websphere.ras.Tr;
import com.ibm.websphere.ras.TraceComponent;

public class PrivateKeyJwtAuthMissingKeyException extends Exception {

public static final TraceComponent tc = Tr.register(PrivateKeyJwtAuthMissingKeyException.class);

private static final long serialVersionUID = 1L;

private final String clientId;

public PrivateKeyJwtAuthMissingKeyException(String clientId) {
this.clientId = clientId;
}

@Override
public String getMessage() {
return Tr.formatMessage(tc, "PRIVATE_KEY_JWT_MISSING_SIGNING_KEY", clientId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*******************************************************************************/
package io.openliberty.security.oidcclientcore.token;

import java.security.Key;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -26,6 +27,8 @@
import com.ibm.websphere.ras.annotation.Sensitive;
import com.ibm.ws.kernel.productinfo.ProductInfo;

import io.openliberty.security.oidcclientcore.exceptions.PrivateKeyJwtAuthException;
import io.openliberty.security.oidcclientcore.exceptions.TokenRequestException;
import io.openliberty.security.oidcclientcore.http.OidcClientHttpUtil;
import io.openliberty.security.oidcclientcore.token.auth.PrivateKeyJwtAuthMethod;

Expand All @@ -49,12 +52,14 @@ public class TokenRequestor {
private final List<NameValuePair> params;
private final HashMap<String, String> customParams;
private final boolean useSystemPropertiesForHttpClientConnections;
private final String clientAssertionSigningAlgorithm;
private final Key clientAssertionSigningKey;

OidcClientHttpUtil oidcClientHttpUtil = OidcClientHttpUtil.getInstance();

private static boolean issuedBetaMessage = false;

private TokenRequestor(Builder builder) {
private TokenRequestor(Builder builder) throws TokenRequestException {
this.tokenEndpoint = builder.tokenEndpoint;
this.clientId = builder.clientId;
this.clientSecret = builder.clientSecret;
Expand All @@ -68,14 +73,16 @@ private TokenRequestor(Builder builder) {
this.resources = builder.resources;
this.customParams = builder.customParams;
this.useSystemPropertiesForHttpClientConnections = builder.useSystemPropertiesForHttpClientConnections;
this.clientAssertionSigningAlgorithm = builder.clientAssertionSigningAlgorithm;
this.clientAssertionSigningKey = builder.clientAssertionSigningKey;

List<NameValuePair> params = getBasicParams();
mergeCustomParams(params, customParams);
this.params = params;
}

@Sensitive
private List<NameValuePair> getBasicParams() {
private List<NameValuePair> getBasicParams() throws TokenRequestException {
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(TokenConstants.GRANT_TYPE, grantType));
if (resources != null) {
Expand All @@ -99,7 +106,7 @@ private List<NameValuePair> getBasicParams() {
return params;
}

void addPrivateKeyJwtParameters(List<NameValuePair> params) {
void addPrivateKeyJwtParameters(List<NameValuePair> params) throws TokenRequestException {
if (!isRunningBetaMode()) {
return;
}
Expand All @@ -121,9 +128,13 @@ boolean isRunningBetaMode() {
}
}

private String buildClientAssertionJwt() {
PrivateKeyJwtAuthMethod pkjAuthMethod = new PrivateKeyJwtAuthMethod(clientId, tokenEndpoint);
return pkjAuthMethod.createPrivateKeyJwt();
private String buildClientAssertionJwt() throws TokenRequestException {
PrivateKeyJwtAuthMethod pkjAuthMethod = new PrivateKeyJwtAuthMethod(clientId, tokenEndpoint, clientAssertionSigningAlgorithm, clientAssertionSigningKey);
try {
return pkjAuthMethod.createPrivateKeyJwt();
} catch (PrivateKeyJwtAuthException e) {
throw new TokenRequestException(clientId, e.getMessage(), e);
}
}

private void mergeCustomParams(@Sensitive List<NameValuePair> params, HashMap<String, String> customParams) {
Expand Down Expand Up @@ -175,6 +186,8 @@ public static class Builder {
private String resources = null;
private HashMap<String, String> customParams = null;
private boolean useSystemPropertiesForHttpClientConnections = false;
private String clientAssertionSigningAlgorithm = "RS256";
private Key clientAssertionSigningKey = null;

public Builder(String tokenEndpoint, String clientId, @Sensitive String clientSecret, String redirectUri, String code) {
this.tokenEndpoint = tokenEndpoint;
Expand Down Expand Up @@ -224,7 +237,17 @@ public Builder useSystemPropertiesForHttpClientConnections(boolean useSystemProp
return this;
}

public TokenRequestor build() {
public Builder clientAssertionSigningAlgorithm(String clientAssertionSigningAlgorithm) {
this.clientAssertionSigningAlgorithm = clientAssertionSigningAlgorithm;
return this;
}

public Builder clientAssertionSigningKey(Key clientAssertionSigningKey) {
this.clientAssertionSigningKey = clientAssertionSigningKey;
return this;
}

public TokenRequestor build() throws TokenRequestException {
return new TokenRequestor(this);
};
}
Expand Down
Loading

0 comments on commit f3775a1

Please sign in to comment.