Skip to content

Commit

Permalink
Issue 21826: Add logic for obtaining signing key for private_key_jwt
Browse files Browse the repository at this point in the history
Adds logic for retrieving the private key to use from the configured keystore.

For OpenLiberty#21826
  • Loading branch information
ayoho committed Apr 26, 2023
1 parent 109b741 commit d0adf50
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,6 @@ OIDC_CLIENT_BAD_REQUEST_MALFORMED_URL_IN_COOKIE=CWWKS1532E: A request to [{0}] i
OIDC_CLIENT_BAD_REQUEST_MALFORMED_URL_IN_COOKIE.explanation=A request was received that includes a malformed cookie.
OIDC_CLIENT_BAD_REQUEST_MALFORMED_URL_IN_COOKIE.useraction=Verify the OpenID Connect provider and client configurations. The malformed cookie can be caused by cookie modification at the user agent with a host name that differs from the host name of the redirect that is registered with the provider. If the host name is expected, then add it to the wasReqURLRedirectDomainNames attribute of the webAppSecurity element in server.xml.

# 1533-1544 used in clients.common bundle, do not use here.
# 1533-1555 used in clients.common bundle, do not use here.

# STOP HERE, OIDC SERVER HAS RESERVED 1600 - 1649
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.PrivilegedExceptionAction;
import java.security.PublicKey;
import java.security.cert.CertificateException;
Expand Down Expand Up @@ -72,6 +73,7 @@
import com.ibm.ws.security.openidconnect.clients.common.OidcCommonClientRequest;
import com.ibm.ws.security.openidconnect.clients.common.OidcSessionCache;
import com.ibm.ws.security.openidconnect.clients.common.OidcUtil;
import com.ibm.ws.security.openidconnect.clients.common.token.auth.PrivateKeyJwtAuthMethod;
import com.ibm.ws.ssl.KeyStoreService;
import com.ibm.wsspi.kernel.service.location.WsLocationAdmin;
import com.ibm.wsspi.kernel.service.utils.AtomicServiceReference;
Expand Down Expand Up @@ -1933,4 +1935,10 @@ public String getPkceCodeChallengeMethod() {
return pkceCodeChallengeMethod;
}

}
@Sensitive
@Override
public PrivateKey getPrivateKeyForClientAuthentication() throws Exception {
return PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, getKeyStoreRef(), keyStoreServiceRef.getService());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -310,5 +310,17 @@ ERROR_SENDING_AUTHORIZATION_REQUEST=CWWKS1553E: The {0} OpenID Connect client en
ERROR_SENDING_AUTHORIZATION_REQUEST.explanation=The OpenID Connect client configuration might be missing information, or the client encountered an error while communicating with the OpenID Connect provider.
ERROR_SENDING_AUTHORIZATION_REQUEST.useraction=See the error in the message for more information.

TOKEN_ENDPOINT_AUTH_METHOD_SETTINGS_ERROR=CWWKS1554E: The [{0}] OpenID Connect client encountered an error while setting up client authentication for the token endpoint for the [{1}] authentication method. {2}
TOKEN_ENDPOINT_AUTH_METHOD_SETTINGS_ERROR.explanation=The OpenID Connect token endpoint requires client authentication, but the OpenID Connect client might be missing information, or the client encountered another error while setting up the authentication data.
TOKEN_ENDPOINT_AUTH_METHOD_SETTINGS_ERROR.useraction=See the error in the message for more information.

PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME=CWWKS1555E: The [{0}] OpenID Connect client does not have a key alias name configured, so the client cannot locate the key to use to sign the JSON Web Token that is used for client authentication.
PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME.explanation=The OpenID Connect client must configure a key alias name to define what key to use to sign the JWT.
PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME.useraction=Verify that the OpenID Connect client has a key alias name configured.

PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF=CWWKS1556E: The [{0}] OpenID Connect client does not have a keystore reference configured, so the client cannot locate the key to use to sign the JSON Web Token that is used for client authentication.
PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF.explanation=The OpenID Connect client must configure a keystore reference to define where to find the key to use to sign the JWT.
PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF.useraction=Verify that the OpenID Connect client has a keystore reference configured.

# STOP HERE, OIDC SERVER HAS RESERVED 1600 - 1649

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.ibm.ws.security.common.structures.BoundedHashMap;
import com.ibm.ws.security.openidconnect.client.jose4j.util.Jose4jUtil;
import com.ibm.ws.security.openidconnect.clients.common.token.auth.TokenEndpointAuthMethod;
import com.ibm.ws.security.openidconnect.clients.common.token.auth.TokenEndpointAuthMethodSettingsException;
import com.ibm.ws.security.openidconnect.pkce.ProofKeyForCodeExchangeHelper;
import com.ibm.ws.webcontainer.security.AuthResult;
import com.ibm.ws.webcontainer.security.ProviderAuthenticationResult;
Expand Down Expand Up @@ -191,7 +192,7 @@ HashMap<String, String> addPkceParameters(String state, HashMap<String, String>
return pkceHelper.addCodeVerifierToTokenRequestParameters(state, parameters);
}

void setAuthMethodSpecificSettings(Builder tokenRequestBuilder, String tokenEndpointAuthMethod) {
void setAuthMethodSpecificSettings(Builder tokenRequestBuilder, String tokenEndpointAuthMethod) throws TokenEndpointAuthMethodSettingsException {
TokenEndpointAuthMethod authMethod = TokenEndpointAuthMethod.getInstance(tokenEndpointAuthMethod, clientConfig);
if (authMethod == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package com.ibm.ws.security.openidconnect.clients.common;

import java.security.Key;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;

Expand Down Expand Up @@ -140,4 +141,7 @@ public interface ConvergedClientConfig extends JwtConsumerConfig {

public String getPkceCodeChallengeMethod();

@Sensitive
public PrivateKey getPrivateKeyForClientAuthentication() throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
*******************************************************************************/
package com.ibm.ws.security.openidconnect.clients.common.token.auth;

import java.security.Key;
import java.security.PrivateKey;

import com.ibm.websphere.ras.Tr;
import com.ibm.websphere.ras.TraceComponent;
import com.ibm.websphere.ras.annotation.Sensitive;
import com.ibm.ws.security.openidconnect.clients.common.ConvergedClientConfig;
import com.ibm.ws.ssl.KeyStoreService;

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

public class PrivateKeyJwtAuthMethod extends TokenEndpointAuthMethod {

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

public static final String AUTH_METHOD = "private_key_jwt";

private final ConvergedClientConfig clientConfig;
Expand All @@ -25,16 +31,27 @@ public PrivateKeyJwtAuthMethod(ConvergedClientConfig clientConfig) {
this.clientConfig = clientConfig;
}

@Override
public void setAuthMethodSpecificSettings(Builder tokenRequestBuilder) {
tokenRequestBuilder.clientAssertionSigningAlgorithm(clientConfig.getTokenEndpointAuthSigningAlgorithm());
Key clientAssertionSigningKey = getKeyForPrivateKeyJwtClientAssertion();
tokenRequestBuilder.clientAssertionSigningKey(clientAssertionSigningKey);
@Sensitive
public static PrivateKey getPrivateKeyForClientAuthentication(String clientId, String keyAliasName, String keyStoreRef, KeyStoreService keyStoreService) throws Exception {
if (keyAliasName == null || keyAliasName.isEmpty()) {
String errorMsg = Tr.formatMessage(tc, "PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME", clientId);
throw new Exception(errorMsg);
}
if (keyStoreRef == null || keyStoreRef.isEmpty()) {
String errorMsg = Tr.formatMessage(tc, "PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF", clientId);
throw new Exception(errorMsg);
}
return keyStoreService.getPrivateKeyFromKeyStore(keyStoreRef, keyAliasName, null);
}

private Key getKeyForPrivateKeyJwtClientAssertion() {
// TODO
return null;
@Override
public void setAuthMethodSpecificSettings(Builder tokenRequestBuilder) throws TokenEndpointAuthMethodSettingsException {
try {
tokenRequestBuilder.clientAssertionSigningAlgorithm(clientConfig.getTokenEndpointAuthSigningAlgorithm());
tokenRequestBuilder.clientAssertionSigningKey(clientConfig.getPrivateKeyForClientAuthentication());
} catch (Exception e) {
throw new TokenEndpointAuthMethodSettingsException(clientConfig.getClientId(), clientConfig.getTokenEndpointAuthMethod(), e.getMessage());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public static TokenEndpointAuthMethod getInstance(String authMethod, ConvergedCl
return null;
}

public abstract void setAuthMethodSpecificSettings(Builder tokenRequestBuilder);
public abstract void setAuthMethodSpecificSettings(Builder tokenRequestBuilder) throws TokenEndpointAuthMethodSettingsException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* 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.clients.common.token.auth;

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

public class TokenEndpointAuthMethodSettingsException extends Exception {

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

private static final long serialVersionUID = 1L;

private final String clientId;
private final String authMethod;
private final String nlsMessage;

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*******************************************************************************
* 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.clients.common.token.auth;

import static org.junit.Assert.fail;

import java.security.PrivateKey;
import java.util.regex.Pattern;

import org.jmock.Expectations;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.ibm.ws.security.openidconnect.clients.common.ConvergedClientConfig;
import com.ibm.ws.security.test.common.CommonTestClass;
import com.ibm.ws.ssl.KeyStoreService;

import io.openliberty.security.oidcclientcore.token.TokenRequestor.Builder;
import test.common.SharedOutputManager;

public class PrivateKeyJwtAuthMethodTest extends CommonTestClass {

private static SharedOutputManager outputMgr = SharedOutputManager.getInstance();

private static final String CWWKS1554E_TOKEN_ENDPOINT_AUTH_METHOD_SETTINGS_ERROR = "CWWKS1554E";
private static final String CWWKS1555E_PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME = "CWWKS1555E";
private static final String CWWKS1556E_PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF = "CWWKS1556E";

private final ConvergedClientConfig clientConfig = mockery.mock(ConvergedClientConfig.class);
private final KeyStoreService keyStoreService = mockery.mock(KeyStoreService.class);
private final PrivateKey privateKey = mockery.mock(PrivateKey.class);

private final String tokenEndpointUrl = "https://localhost/op/token";
private final String clientId = "myClientId";
private final String clientSecret = "some super secret password";
private final String redirectUri = "https://localhost/rp/redirect";
private String code = "";

Builder tokenRequestBuilder;
PrivateKeyJwtAuthMethod authMethod;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
outputMgr.captureStreams();
}

@Before
public void setUp() throws Exception {
System.out.println("Entering test: " + testName.getMethodName());
code = testName.getMethodName();
tokenRequestBuilder = new Builder(tokenEndpointUrl, clientId, clientSecret, redirectUri, code);

authMethod = new PrivateKeyJwtAuthMethod(clientConfig);
}

@After
public void tearDown() throws Exception {
System.out.println("Exiting test: " + testName.getMethodName());
mockery.assertIsSatisfied();
}

@AfterClass
public static void tearDownAfterClass() throws Exception {
outputMgr.dumpStreams();
outputMgr.restoreStreams();
}

@Test
public void test_getPrivateKeyForClientAuthentication_nullKeyAliasName() throws Exception {
String keyAliasName = null;
String keyStoreRef = "ref";
try {
PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, keyStoreRef, keyStoreService);
fail("Should have thrown an exception, but didn't.");
} catch (Exception e) {
verifyException(e, CWWKS1555E_PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME);
}
}

@Test
public void test_getPrivateKeyForClientAuthentication_emptyKeyAliasName() throws Exception {
String keyAliasName = "";
String keyStoreRef = "ref";
try {
PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, keyStoreRef, keyStoreService);
fail("Should have thrown an exception, but didn't.");
} catch (Exception e) {
verifyException(e, CWWKS1555E_PRIVATE_KEY_JWT_MISSING_KEY_ALIAS_NAME);
}
}

@Test
public void test_getPrivateKeyForClientAuthentication_nullKeyStoreRef() throws Exception {
String keyAliasName = "alias";
String keyStoreRef = null;
try {
PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, keyStoreRef, keyStoreService);
fail("Should have thrown an exception, but didn't.");
} catch (Exception e) {
verifyException(e, CWWKS1556E_PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF);
}
}

@Test
public void test_getPrivateKeyForClientAuthentication_emptyKeyStoreRef() throws Exception {
String keyAliasName = "alias";
String keyStoreRef = "";
try {
PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, keyStoreRef, keyStoreService);
fail("Should have thrown an exception, but didn't.");
} catch (Exception e) {
verifyException(e, CWWKS1556E_PRIVATE_KEY_JWT_MISSING_KEYSTORE_REF);
}
}

@Test
public void test_getPrivateKeyForClientAuthentication() throws Exception {
String keyAliasName = "alias";
String keyStoreRef = "ref";
mockery.checking(new Expectations() {
{
one(keyStoreService).getPrivateKeyFromKeyStore(keyStoreRef, keyAliasName, null);
}
});
PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, keyStoreRef, keyStoreService);
}

@Test
public void test_setAuthMethodSpecificSettings_gettingKeyThrowsException() throws Exception {
mockery.checking(new Expectations() {
{
one(clientConfig).getTokenEndpointAuthSigningAlgorithm();
will(returnValue("RS256"));
one(clientConfig).getPrivateKeyForClientAuthentication();
will(throwException(new Exception(defaultExceptionMsg)));
one(clientConfig).getClientId();
will(returnValue(clientId));
one(clientConfig).getTokenEndpointAuthMethod();
will(returnValue(PrivateKeyJwtAuthMethod.AUTH_METHOD));
}
});
try {
authMethod.setAuthMethodSpecificSettings(tokenRequestBuilder);
fail("Should have thrown an exception, but didn't.");
} catch (TokenEndpointAuthMethodSettingsException e) {
verifyException(e, CWWKS1554E_TOKEN_ENDPOINT_AUTH_METHOD_SETTINGS_ERROR + ".+" + Pattern.quote(defaultExceptionMsg));
}
}

@Test
public void test_setAuthMethodSpecificSettings() throws Exception {
mockery.checking(new Expectations() {
{
one(clientConfig).getTokenEndpointAuthSigningAlgorithm();
will(returnValue("RS256"));
one(clientConfig).getPrivateKeyForClientAuthentication();
will(returnValue(privateKey));
}
});
authMethod.setAuthMethodSpecificSettings(tokenRequestBuilder);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -39,6 +42,7 @@
import com.ibm.ws.security.openidconnect.clients.common.InMemoryOidcSessionCache;
import com.ibm.ws.security.openidconnect.clients.common.OidcClientConfig;
import com.ibm.ws.security.openidconnect.clients.common.OidcSessionCache;
import com.ibm.ws.security.openidconnect.clients.common.token.auth.PrivateKeyJwtAuthMethod;
import com.ibm.ws.security.social.SocialLoginConfig;
import com.ibm.ws.security.social.SocialLoginService;
import com.ibm.ws.security.social.TraceConstants;
Expand Down Expand Up @@ -966,4 +970,10 @@ public String getPkceCodeChallengeMethod() {
return pkceCodeChallengeMethod;
}

}
@Sensitive
@Override
public PrivateKey getPrivateKeyForClientAuthentication() throws Exception {
return PrivateKeyJwtAuthMethod.getPrivateKeyForClientAuthentication(clientId, keyAliasName, getKeyStoreRef(), JwtUtils.getKeyStoreService());
}

}
Loading

0 comments on commit d0adf50

Please sign in to comment.