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
parent 5dff157755
commit f45c4d4b8e
4 changed files with 344 additions and 35 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

@@ -33,9 +33,12 @@ 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.test.util.ReflectionTestUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@@ -47,6 +50,7 @@ import static org.mockito.Mockito.mock;
* .
*
* @author Ben Alex
* @author Marcus Da Coregio
*/
public class TokenBasedRememberMeServicesTests {
@@ -77,8 +81,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]);
}
@@ -88,15 +92,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();
@@ -120,8 +161,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();
@@ -161,8 +202,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();
@@ -189,8 +230,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();
@@ -204,8 +245,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();
@@ -216,8 +257,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();
@@ -226,6 +267,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);
@@ -293,6 +396,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 = new TokenBasedRememberMeServices("key", this.uds, 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() {
@@ -318,4 +452,19 @@ public class TokenBasedRememberMeServicesTests {
assertThat(CodecTestUtils.isBase64(cookie.getValue().getBytes())).isTrue();
}
@Test
public void constructorWhenEncodingAlgorithmNullThenException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new TokenBasedRememberMeServices("key", this.uds, null))
.withMessage("encodingAlgorithm cannot be null");
}
@Test
public void constructorWhenNoEncodingAlgorithmSpecifiedThenMd5() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("key", this.uds);
RememberMeTokenAlgorithm encodingAlgorithm = (RememberMeTokenAlgorithm) ReflectionTestUtils
.getField(rememberMeServices, "encodingAlgorithm");
assertThat(encodingAlgorithm).isSameAs(RememberMeTokenAlgorithm.MD5);
}
}