Skip to content

Commit

Permalink
Jwt Signature validator update (#1354)
Browse files Browse the repository at this point in the history
  • Loading branch information
liga-oz committed Nov 24, 2023
1 parent 911aef5 commit 7ce9601
Show file tree
Hide file tree
Showing 37 changed files with 279 additions and 466 deletions.
Expand Up @@ -27,7 +27,7 @@
import static com.sap.cloud.security.config.Service.XSUAA;
import static com.sap.cloud.security.config.ServiceConstants.XSUAA.VERIFICATION_KEY;
import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_CLIENT_ID;
import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_DOMAIN;
import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_UAA_DOMAIN;
import static org.assertj.core.api.Assertions.assertThat;

/**
Expand All @@ -51,7 +51,7 @@ public void xsuaaTokenValidationSucceeds_withXsuaaCombiningValidator() {
}

@Test
public void xsaTokenValidationSucceeds_withXsuaaCombiningValidator() throws IOException {
public void xsaTokenValidationSucceeds_withXsuaaCombiningValidator() {
OAuth2ServiceConfiguration configuration = rule.getOAuth2ServiceConfigurationBuilderFromFile(
"/xsa-simple/vcap_services-single.json")
.runInLegacyMode(true)
Expand Down Expand Up @@ -105,7 +105,7 @@ public void createToken_withCorrectVerificationKey_tokenIsValid() throws IOExcep
String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8);
OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder
.forService(XSUAA)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, DEFAULT_DOMAIN)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, DEFAULT_UAA_DOMAIN)
.withClientId(DEFAULT_CLIENT_ID)
.withProperty(VERIFICATION_KEY, publicKey)
.build();
Expand Down
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
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
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
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
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
Expand Up @@ -77,7 +77,7 @@ private static XsuaaCredentials createXsuaaCredentials() throws IOException {
final String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8);

XsuaaCredentials xsuaaCredentials = new XsuaaCredentials();
xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_DOMAIN);
xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_UAA_DOMAIN);
xsuaaCredentials.setClientId(SecurityTest.DEFAULT_CLIENT_ID);
xsuaaCredentials.setXsAppName(SecurityTest.DEFAULT_APP_ID);
xsuaaCredentials.setVerificationKey(publicKey.replace("\n", ""));
Expand Down
28 changes: 28 additions & 0 deletions java-security-it/src/test/resources/random_private_key.txt
@@ -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-----
2 changes: 1 addition & 1 deletion java-security-test/README.md
Expand Up @@ -66,7 +66,7 @@ import static com.sap.cloud.security.test.SecurityTest.*;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"xsuaa.uaadomain=" + DEFAULT_DOMAIN,
"xsuaa.uaadomain=" + DEFAULT_UAA_DOMAIN,
"xsuaa.xsappname=" + DEFAULT_APP_ID,
"xsuaa.clientid=" + DEFAULT_CLIENT_ID })
@ExtendWith(XsuaaExtension.class)
Expand Down
Expand Up @@ -22,6 +22,7 @@ public class ApplicationServerOptions {

private final TokenAuthenticator tokenAuthenticator;
private final int port;
private static int tokenKeysPort;

private ApplicationServerOptions(TokenAuthenticator tokenAuthenticator) {
this(tokenAuthenticator, 0);
Expand Down Expand Up @@ -57,7 +58,8 @@ public static ApplicationServerOptions forXsuaaService(String appId, String clie
* the identity service
* @return the application server options.
*/
public static ApplicationServerOptions forService(Service service) {
public static ApplicationServerOptions forService(Service service, int jwksPort) {
tokenKeysPort = jwksPort;
return switch (service) {
case XSUAA -> forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID);
case IAS -> new ApplicationServerOptions(new IasTokenAuthenticator()
Expand All @@ -72,6 +74,21 @@ public static ApplicationServerOptions forService(Service service) {

}

public static ApplicationServerOptions forService(Service service) {
return switch (service) {
case XSUAA -> forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID);
case IAS -> new ApplicationServerOptions(new IasTokenAuthenticator()
.withServiceConfiguration(OAuth2ServiceConfigurationBuilder.forService(Service.IAS)
.withClientId(SecurityTestRule.DEFAULT_CLIENT_ID)
.withUrl("http://localhost")
.withDomains("localhost")
.build()));
default ->
throw new UnsupportedOperationException("Identity Service " + service + " is not yet supported.");
};

}

/**
* Use this method to configure a custom {@link TokenAuthenticator} that will be
* used in the application server to authenticate the user via tokens retrieved
Expand Down Expand Up @@ -107,10 +124,11 @@ public int getPort() {
}

private static OAuth2ServiceConfiguration createServiceConfiguration(String appId, String clientId) {
String portPath = tokenKeysPort != 0 ? ":" + tokenKeysPort : "";
return OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
.withClientId(clientId)
.withProperty(ServiceConstants.XSUAA.APP_ID, appId)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTestRule.DEFAULT_DOMAIN)
.withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTestRule.DEFAULT_UAA_DOMAIN + portPath)
.build();
}

Expand Down

0 comments on commit 7ce9601

Please sign in to comment.