Skip to content
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

Jwt Signature validator update #1354

Merged
merged 16 commits into from
Nov 24, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import com.sap.cloud.security.config.Environments;
import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
import com.sap.cloud.security.config.Service;
import com.sap.cloud.security.config.ServiceConstants;
import com.sap.cloud.security.test.SecurityTestRule;
import com.sap.cloud.security.token.Token;
import com.sap.cloud.security.token.validation.CombiningValidator;
import com.sap.cloud.security.token.validation.ValidationResult;
import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -31,7 +33,16 @@ public class XsuaaMultipleBindingsIntegrationTest {
public void createToken_integrationTest_tokenValidation() {
Token token = rule.getPreconfiguredJwtGenerator().createToken();
OAuth2ServiceConfiguration configuration = Environments.readFromInput(XsuaaMultipleBindingsIntegrationTest.class.getResourceAsStream("/vcap_services-multiple.json")).getXsuaaConfiguration();
CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(configuration).build();
OAuth2ServiceConfiguration mockConfig = Mockito.mock(OAuth2ServiceConfiguration.class);
Mockito.when(mockConfig.getClientId()).thenReturn(configuration.getClientId());
Mockito.when(mockConfig.getDomains()).thenReturn(configuration.getDomains());
Mockito.when(mockConfig.getUrl()).thenReturn(configuration.getUrl());
Mockito.when(mockConfig.hasProperty(ServiceConstants.XSUAA.APP_ID)).thenReturn(configuration.hasProperty(ServiceConstants.XSUAA.APP_ID));
Mockito.when(mockConfig.getProperty(ServiceConstants.XSUAA.APP_ID)).thenReturn(configuration.getProperty(ServiceConstants.XSUAA.APP_ID));
Mockito.when(mockConfig.getProperty(ServiceConstants.XSUAA.UAA_DOMAIN)).thenReturn(rule.getWireMockServer().baseUrl());
Mockito.when(mockConfig.getService()).thenReturn(configuration.getService());

CombiningValidator<Token> tokenValidator = JwtValidatorBuilder.getInstance(mockConfig).build();

ValidationResult result = tokenValidator.validate(token);
assertThat(result.isValid()).isTrue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@
*/
package com.sap.cloud.security.test.integration.ssrf;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;

import java.io.IOException;

import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
import com.sap.cloud.security.config.Service;
import com.sap.cloud.security.config.ServiceConstants;
import com.sap.cloud.security.test.RSAKeys;
import com.sap.cloud.security.test.extension.SecurityTestExtension;
import com.sap.cloud.security.token.Token;
import com.sap.cloud.security.token.TokenHeader;
Expand All @@ -28,6 +25,13 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;

/**
* Test cases for <a href=
* "https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">SSRF
Expand Down Expand Up @@ -56,15 +60,25 @@ class JavaSSRFAttackTest {
@CsvSource({
"http://localhost:4242/token_keys, true",
"http://localhost:4242/token_keys@malicious.ondemand.com/token_keys, false",
"http://user@localhost:4242/token_keys, true", // user info in URI is deprecated by Apache HttpClient 5, but working in HttpClient 4.x.x
"http://localhost:4242/token_keys///malicious.ondemand.com/token_keys, false",
})
void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) throws IOException {
OAuth2ServiceConfigurationBuilder configuration = extension.getContext()
.getOAuth2ServiceConfigurationBuilderFromFile("/xsuaa/vcap_services-single.json");
Token token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json")
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.createToken();
void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
OAuth2ServiceConfigurationBuilder configuration =
extension.getContext()
.getOAuth2ServiceConfigurationBuilderFromFile("/xsuaa/vcap_services-single.json")
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, extension.getContext().getWireMockServer().baseUrl());
Token token;
if (isValid) {
token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json")
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.createToken();
} else {
token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json")
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.withPrivateKey(RSAKeys.loadPrivateKey("/random_private_key.txt"))
.createToken();
}
CombiningValidator<Token> tokenValidator = JwtValidatorBuilder
.getInstance(configuration.build())
.withHttpClient(httpClient)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.sap.cloud.security.test.integration.ssrf;

import com.sap.cloud.security.config.Service;
import com.sap.cloud.security.test.RSAKeys;
import com.sap.cloud.security.test.SecurityTest;
import com.sap.cloud.security.test.extension.SecurityTestExtension;
import com.sap.cloud.security.token.TokenHeader;
Expand All @@ -24,6 +25,8 @@
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -32,7 +35,7 @@
* "https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">SSRF
* (Server Side Request Forgery)</a> attacks.
*/
public class SpringSSRFAttackTest {
class SpringSSRFAttackTest {

private RestOperations restOperations = Mockito.spy(new RestTemplate());

Expand All @@ -56,11 +59,21 @@ public class SpringSSRFAttackTest {
"http://malicious.ondemand.com@localhost:4242/token_keys, true",
"http://localhost:4242/token_keys///malicious.ondemand.com/token_keys, false",
})
public void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) throws IOException {
String token = extension.getContext().getPreconfiguredJwtGenerator()
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.createToken()
.getTokenValue();
void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String token;
if (isValid) {
token = extension.getContext().getPreconfiguredJwtGenerator()
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.createToken()
.getTokenValue();
} else {
token = extension.getContext().getPreconfiguredJwtGenerator()
.withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl)
.withPrivateKey(RSAKeys.loadPrivateKey("/random_private_key.txt"))
.createToken()
.getTokenValue();
}
JwtDecoder jwtDecoder = new XsuaaJwtDecoderBuilder(
new XsuaaServiceConfigurationCustom(createXsuaaCredentials()))
.withRestOperations(restOperations)
Expand All @@ -80,7 +93,7 @@ public void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean is

private XsuaaCredentials createXsuaaCredentials() {
XsuaaCredentials xsuaaCredentials = new XsuaaCredentials();
xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_DOMAIN);
xsuaaCredentials.setUaaDomain(extension.getContext().getWireMockServer().baseUrl());
xsuaaCredentials.setClientId(SecurityTest.DEFAULT_CLIENT_ID);
xsuaaCredentials.setXsAppName(SecurityTest.DEFAULT_APP_ID);
return xsuaaCredentials;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void onlineValidation() {
String tokenValue = token.getTokenValue();

BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> tokenValidator.validate(new XsuaaToken(tokenValue)));
LOGGER.info("Online validation result: {}", result.toString());
LOGGER.info("Online validation result: {}", result);
}

@Test
Expand All @@ -70,7 +70,7 @@ void offlineValidation() throws Exception {
String tokenValue = token.getTokenValue();

BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> tokenValidator.validate(new XsuaaToken(tokenValue)));
LOGGER.info("Offline validation result: {}", result.toString());
LOGGER.info("Offline validation result: {}", result);
}

private CombiningValidator<Token> createOfflineTokenValidator() throws IOException {
Expand All @@ -90,7 +90,7 @@ private CombiningValidator<Token> createOnlineTokenValidator() {

private OAuth2ServiceConfigurationBuilder createConfigurationBuilder() {
return OAuth2ServiceConfigurationBuilder.forService(XSUAA)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTest.DEFAULT_DOMAIN)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, securityTest.getWireMockServer().baseUrl())
.withProperty(ServiceConstants.XSUAA.APP_ID, SecurityTest.DEFAULT_APP_ID)
.withClientId(SecurityTest.DEFAULT_CLIENT_ID);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@
*/
package com.sap.cloud.security.test.performance;

import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
import com.sap.cloud.security.config.ServiceConstants;
import com.sap.cloud.security.spring.token.authentication.JwtDecoderBuilder;
import com.sap.cloud.security.test.SecurityTest;
import com.sap.cloud.security.test.performance.util.BenchmarkUtil;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.jwt.JwtDecoder;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static com.sap.cloud.security.config.Service.IAS;
import static com.sap.cloud.security.config.Service.XSUAA;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -57,7 +52,7 @@ void onlineValidation() {
assertThat(jwtDecoder.decode(token)).isNotNull();

BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token));
LOGGER.info("Online validation result (xsuaa): {}", result.toString());
LOGGER.info("Online validation result (xsuaa): {}", result);
}

@Test
Expand All @@ -67,17 +62,7 @@ void onlineIasValidation() {
assertThat(jwtDecoder.decode(token)).isNotNull();

BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token));
LOGGER.info("Online validation result (identity): {}", result.toString());
}

// @Test
void offlineValidation() throws Exception {
String token = securityTest.createToken().getTokenValue();
JwtDecoder jwtDecoder = createOfflineJwtDecoder();
assertThat(jwtDecoder.decode(token)).isNotNull();

BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token));
LOGGER.info("Offline validation result: {}", result.toString());
LOGGER.info("Online validation result (identity): {}", result);
}

private JwtDecoder createOnlineJwtDecoder() {
Expand All @@ -86,20 +71,9 @@ private JwtDecoder createOnlineJwtDecoder() {
.withXsuaaServiceConfiguration(createXsuaaConfigurationBuilder().build()).build();
}

private JwtDecoder createOfflineJwtDecoder() throws IOException {
final String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8)
.replace("\n", "");
OAuth2ServiceConfiguration configuration = createXsuaaConfigurationBuilder()
.withProperty("verificationkey", publicKey)
.build();
return new JwtDecoderBuilder()
.withIasServiceConfiguration(createIasConfigurationBuilder().build())
.withXsuaaServiceConfiguration(configuration).build();
}

private OAuth2ServiceConfigurationBuilder createXsuaaConfigurationBuilder() {
return OAuth2ServiceConfigurationBuilder.forService(XSUAA)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTest.DEFAULT_DOMAIN)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, securityTest.getWireMockServer().baseUrl())
.withProperty(ServiceConstants.XSUAA.APP_ID, SecurityTest.DEFAULT_APP_ID)
.withClientId(SecurityTest.DEFAULT_CLIENT_ID);
}
Expand Down
28 changes: 28 additions & 0 deletions java-security-it/src/test/resources/random_private_key.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbVBpnMyO0R8d2
Kasc/f/Ziv7XPrzl6I5SXDJtFUb2LzCyA5SH49Qa5AvyGC6UtlZdTkxvAEtMQIAQ
xBtxFM1VOiWSriLrsQ/ol6wckgUANsrU2LQq4xw+6LI4u8MqIMQydgbVc/dfdYI1
+wJVP1ihT6VYitmv9mwi9CuLyNzOvhGTKdtMGw9oA7KA9SWKmoOulp0w7WaiY0Jt
5r+joY+ffwvETDrT0i1+AMaEvp//JWJ3mkXNlBZv72XqYK4nDDSGeE7qC3pG/3w5
YO3L0bR+tYA/IR+4hb0H6ZH/a8aHJT0httam8VeLL1FVtuwznfxMKN3kkXZ0m/HL
bhp10LihAgMBAAECggEAZVtbI052lPRlztBf7To9kqolozU4NFotTMcGzLGerZSb
lP3LFWVwid+Xf/GRq87Tym0GaURq3iYUq1wcgAzP9DZOQEnLVbsjo2YdlEMgakRW
1M9XucibLN3RNj4nmzzoafkkenMCz9KxFiJmIlSEtDZxsbZhWHZXl/N22u9GTs0o
KQNzroxI+SKxWcfrmJkOx3vL9++47/LY+Rw6dL+hkUxdxMLuhYUcYziNvRfV9o0Y
Ag7Pl85xL3N8HkHr5ELL0RKHyk+vKbZ9xhAH50mxTZG8tAj9Ds0v3hQJrTmuyAS3
ZJkqkhIJtWHmhLYiKju9ObLXtVgm8wdg8+vq/u1utQKBgQDhEf6Sy0+DhEYTMLN+
ioVf/rBXl8QgXbDkEoHMp+FhuYK3CdlD+pgaJq+KUc6RnHb0GeDPBcZkhRlTLxU0
HtykDQFa4mcXIJaSKY8WHCF3hJLUnXYgQW+0oufXEDCORuzqgcUbEHnYpjuuzkCj
FqjCkH4lNdvW8IJ56rpjBaWyRwKBgQCwrJVWLPPZMuwXHlkM1ytAC+dsq/1cRo3D
by766k5u/J6xwlc3bM0LG6pHuXruBxkdKAeAkfmwCc4JSXR4JS3JNmYuQ7wbmDWp
20ABv9qFbTIt1rtEkjhV8bmamfe5qZL/0lza2KcQOZGr1wtzV/Vg384gm5oy1FSi
0isU+sCJ1wKBgQCFIAuf8Dm75MU+HJROyMhTG2ZaqR4Mtt4mSPwVfUdGcl/qvByS
pOrKrQ8vlWvFnPKPN69NRHEwi7mLBlJYXdjMABVJGJk5iMEG+yXzQfhZpUTkFa8F
LS9RfPn8r0rJHRKNMuzPMVOg3dJ3du+sh36SdrzmbZD29ZN3YWuVnoV/iQKBgDQM
5IJbBAx9gCjffATYb5mS6D+P/DjvYFyvqPurhCgWrPpZ8zAVEeOv5t7yulDeLnv0
iyFJ4HIIsXby+SlcarzZFgmTUxweH9FHEvhw+YRNw3bVyJ5PJeHMMY5mxiEg4HoW
E9017yJMk6o41NrKkzRTO3tH3IoVHEpL+P1ZUthJAoGALDuPKfHiuXuxxGF4oeyH
KFFDIr991nBxUC1tB8Lff5ZStfbzTnjbzCRogsQ/pu1tBaoMQjpHhTnI3hbe/Iwf
VffTGJTxapTiEwQuSY2OaSgHtUrz4qurHos+uVTWni8TuXfqkeoc1aIr4D7ulzPN
O71jgCLNQW5OZD7MSn21eeU=
-----END RSA PRIVATE KEY-----
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public void setup() throws Exception {
// TODO return JSON Media type
OAuth2ServiceEndpointsProvider endpointsProvider = new XsuaaDefaultEndpoints(
String.format(LOCALHOST_PATTERN, wireMockServer.port()), null);
wireMockServer.stubFor(get(urlEqualTo(endpointsProvider.getJwksUri().getPath()))
wireMockServer.stubFor(get(urlPathEqualTo(endpointsProvider.getJwksUri().getPath()))
.willReturn(aResponse().withBody(createDefaultTokenKeyResponse())
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.value())));
wireMockServer.stubFor(get(urlEqualTo(DISCOVERY_ENDPOINT_DEFAULT))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

import static com.sap.cloud.security.config.Service.IAS;
import static com.sap.cloud.security.config.Service.XSUAA;
import static com.sap.cloud.security.config.ServiceConstants.XSUAA.UAA_DOMAIN;

/**
* Class used to build a token validator for an OAuth service configuration
Expand Down Expand Up @@ -219,10 +218,6 @@ private List<Validator<Token>> createDefaultValidators() {
OAuth2TokenKeyServiceWithCache tokenKeyServiceWithCache = getTokenKeyServiceWithCache();
Optional.ofNullable(tokenKeyCacheConfiguration).ifPresent(tokenKeyServiceWithCache::withCacheConfiguration);
if (configuration.getService() == XSUAA) {
if (!configuration.isLegacyMode()) {
defaultValidators.add(new XsuaaJkuValidator(configuration.getProperty(UAA_DOMAIN)));
}

signatureValidator = new XsuaaJwtSignatureValidator(configuration, tokenKeyServiceWithCache, getOidcConfigurationServiceWithCache());
} else if (configuration.getService() == IAS) {
if(configuration.getDomains() != null && !configuration.getDomains().isEmpty()) {
Expand Down
Loading