Add SHA256 as an algorithm option for Remember Me token hashing

Closes gh-8549
This commit is contained in:
Marcus Da Coregio
2022-06-30 14:25:09 -03:00
committed by Marcus Hert Da Coregio
parent 0e291a3295
commit e17fe8ced9
4 changed files with 303 additions and 32 deletions

View File

@@ -16,8 +16,11 @@
package org.springframework.security.test.web;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.util.DigestUtils;
public final class CodecTestUtils {
@@ -52,4 +55,14 @@ public final class CodecTestUtils {
return DigestUtils.md5DigestAsHex(data.getBytes());
}
public static String algorithmHex(String algorithmName, String data) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithmName);
return new String(Hex.encode(digest.digest(data.getBytes())));
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("No " + algorithmName + " algorithm available!");
}
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.test.web.CodecTestUtils;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.RememberMeTokenAlgorithm;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,8 +77,8 @@ public class TokenBasedRememberMeServicesTests {
private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
if (cookieTokens.length == 3) {
String[] cookieTokens = getCookieTokens(cookieAsPlainText);
if (isValidCookieTokensLength(cookieTokens)) {
try {
return Long.parseLong(cookieTokens[1]);
}
@@ -87,15 +88,52 @@ public class TokenBasedRememberMeServicesTests {
return -1;
}
private String generateCorrectCookieContentForToken(long expiryTime, String username, String password, String key) {
private String[] getCookieTokens(String cookieAsPlainText) {
return StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");
}
private String determineAlgorithmNameFromBase64EncodedToken(String validToken) {
String cookieAsPlainText = CodecTestUtils.decodeBase64(validToken);
String[] cookieTokens = getCookieTokens(cookieAsPlainText);
if (isValidCookieTokensLength(cookieTokens)) {
return cookieTokens[2];
}
return null;
}
private boolean isValidCookieTokensLength(String[] cookieTokens) {
return cookieTokens.length == 3 || cookieTokens.length == 4;
}
private String generateCorrectCookieContentForTokenNoAlgorithmName(long expiryTime, String username,
String password, String key) {
return generateCorrectCookieContentForTokenWithAlgorithmName(expiryTime, username, password, key,
RememberMeTokenAlgorithm.MD5);
}
private String generateCorrectCookieContentForTokenNoAlgorithmName(long expiryTime, String username,
String password, String key, RememberMeTokenAlgorithm algorithm) {
// format is:
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" +
// password + ":" + key)
String signatureValue = CodecTestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key);
String signatureValue = CodecTestUtils.algorithmHex(algorithm.getDigestAlgorithm(),
username + ":" + expiryTime + ":" + password + ":" + key);
String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
return CodecTestUtils.encodeBase64(tokenValue);
}
private String generateCorrectCookieContentForTokenWithAlgorithmName(long expiryTime, String username,
String password, String key, RememberMeTokenAlgorithm algorithm) {
// format is:
// username + ":" + expiryTime + ":" + algorithmName + ":" + algorithmHex(username
// + ":" + expiryTime + ":" +
// password + ":" + key)
String signatureValue = CodecTestUtils.algorithmHex(algorithm.getDigestAlgorithm(),
username + ":" + expiryTime + ":" + password + ":" + key);
String tokenValue = username + ":" + expiryTime + ":" + algorithm.name() + ":" + signatureValue;
return CodecTestUtils.encodeBase64(tokenValue);
}
@Test
public void autoLoginReturnsNullIfNoCookiePresented() {
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -119,8 +157,8 @@ public class TokenBasedRememberMeServicesTests {
@Test
public void autoLoginReturnsNullForExpiredCookieAndClearsCookie() {
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis() - 1000000, "someone", "password",
"key"));
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() - 1000000, "someone",
"password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -160,8 +198,8 @@ public class TokenBasedRememberMeServicesTests {
public void autoLoginClearsCookieIfSignatureBlocksDoesNotMatchExpectedValue() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
"WRONG_KEY"));
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "WRONG_KEY"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -188,8 +226,8 @@ public class TokenBasedRememberMeServicesTests {
public void autoLoginClearsCookieIfUserNotFound() {
udsWillThrowNotFound();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
"key"));
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -203,8 +241,8 @@ public class TokenBasedRememberMeServicesTests {
public void autoLoginClearsCookieIfUserServiceMisconfigured() {
udsWillReturnNull();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
"key"));
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -215,8 +253,8 @@ public class TokenBasedRememberMeServicesTests {
public void autoLoginWithValidTokenAndUserSucceeds() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password",
"key"));
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -225,6 +263,68 @@ public class TokenBasedRememberMeServicesTests {
assertThat(result.getPrincipal()).isEqualTo(this.user);
}
@Test
public void autoLoginWhenTokenNoAlgorithmAndDifferentMatchingAlgorithmThenReturnsNullAndClearCookie() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key", RememberMeTokenAlgorithm.MD5));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
Authentication result = this.services.autoLogin(request, response);
Cookie returnedCookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
assertThat(result).isNull();
assertThat(returnedCookie).isNotNull();
assertThat(returnedCookie.getMaxAge()).isZero();
}
@Test
public void autoLoginWhenTokenNoAlgorithmAndSameMatchingAlgorithmThenSucceeds() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForTokenNoAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key", RememberMeTokenAlgorithm.SHA256));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
Authentication result = this.services.autoLogin(request, response);
assertThat(result).isNotNull();
assertThat(result.getPrincipal()).isEqualTo(this.user);
}
@Test
public void autoLoginWhenTokenHasAlgorithmAndSameMatchingAlgorithmThenUsesTokenAlgorithmAndSucceeds() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForTokenWithAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key", RememberMeTokenAlgorithm.SHA256));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
Authentication result = this.services.autoLogin(request, response);
assertThat(result).isNotNull();
assertThat(result.getPrincipal()).isEqualTo(this.user);
}
@Test
public void autoLoginWhenTokenHasAlgorithmAndDifferentMatchingAlgorithmThenUsesTokenAlgorithmAndSucceeds() {
udsWillReturnUser();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForTokenWithAlgorithmName(System.currentTimeMillis() + 1000000, "someone",
"password", "key", RememberMeTokenAlgorithm.SHA256));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(cookie);
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
Authentication result = this.services.autoLogin(request, response);
assertThat(result).isNotNull();
assertThat(result.getPrincipal()).isEqualTo(this.user);
}
@Test
public void testGettersSetters() {
assertThat(this.services.getUserDetailsService()).isEqualTo(this.uds);
@@ -292,6 +392,37 @@ public class TokenBasedRememberMeServicesTests {
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
}
@Test
public void loginSuccessWhenDefaultEncodingAlgorithmThenContainsAlgorithmName() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "true");
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.loginSuccess(request, response,
new TestingAuthenticationToken("someone", "password", "ROLE_ABC"));
Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
assertThat(cookie).isNotNull();
assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
assertThat("MD5").isEqualTo(determineAlgorithmNameFromBase64EncodedToken(cookie.getValue()));
}
@Test
public void loginSuccessWhenCustomEncodingAlgorithmThenContainsAlgorithmName() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "true");
MockHttpServletResponse response = new MockHttpServletResponse();
this.services.setEncodingAlgorithm(RememberMeTokenAlgorithm.SHA256);
this.services.loginSuccess(request, response,
new TestingAuthenticationToken("someone", "password", "ROLE_ABC"));
Cookie cookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
assertThat(cookie).isNotNull();
assertThat(cookie.getMaxAge()).isEqualTo(this.services.getTokenValiditySeconds());
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
assertThat(new Date().before(new Date(determineExpiryTimeFromBased64EncodedToken(cookie.getValue())))).isTrue();
assertThat("SHA256").isEqualTo(determineAlgorithmNameFromBase64EncodedToken(cookie.getValue()));
}
// SEC-933
@Test
public void obtainPasswordReturnsNullForTokenWithNullCredentials() {