mirror of https://github.com/apache/cloudstack.git
CLOUDSTACK-2312, CLOUDSTACK-2314 : SHA256 timing attack and brute force attack fix
Signed-off-by: Chiradeep Vittal <chiradeep@apache.org>
This commit is contained in:
parent
5b0314fff9
commit
dce3551031
|
|
@ -36,10 +36,11 @@ import com.cloud.utils.exception.CloudRuntimeException;
|
|||
@Local(value={UserAuthenticator.class})
|
||||
public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(SHA256SaltedUserAuthenticator.class);
|
||||
|
||||
private static final String s_defaultPassword = "000000000000000000000000000=";
|
||||
private static final String s_defaultSalt = "0000000000000000000000000000000=";
|
||||
@Inject
|
||||
private UserAccountDao _userAccountDao;
|
||||
private static int s_saltlen = 20;
|
||||
private static final int s_saltlen = 32;
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params)
|
||||
|
|
@ -60,21 +61,29 @@ public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
|||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Retrieving user: " + username);
|
||||
}
|
||||
boolean realUser = true;
|
||||
UserAccount user = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (user == null) {
|
||||
s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
|
||||
return false;
|
||||
realUser = false;
|
||||
}
|
||||
|
||||
try {
|
||||
/* Fake Data */
|
||||
String realPassword = new String(s_defaultPassword);
|
||||
byte[] salt = new String(s_defaultSalt).getBytes();
|
||||
if (realUser) {
|
||||
String storedPassword[] = user.getPassword().split(":");
|
||||
if (storedPassword.length != 2) {
|
||||
s_logger.warn("The stored password for " + username + " isn't in the right format for this authenticator");
|
||||
return false;
|
||||
realUser = false;
|
||||
} else {
|
||||
realPassword = storedPassword[1];
|
||||
salt = Base64.decode(storedPassword[0]);
|
||||
}
|
||||
byte salt[] = Base64.decode(storedPassword[0]);
|
||||
}
|
||||
try {
|
||||
String hashedPassword = encode(password, salt);
|
||||
return storedPassword[1].equals(hashedPassword);
|
||||
/* constantTimeEquals comes first in boolean since we need to thwart timing attacks */
|
||||
return constantTimeEquals(realPassword, hashedPassword) && realUser;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CloudRuntimeException("Unable to hash password", e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
|
@ -109,9 +118,9 @@ public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
|||
|
||||
public String encode(String password, byte[] salt) throws UnsupportedEncodingException, NoSuchAlgorithmException {
|
||||
byte[] passwordBytes = password.getBytes("UTF-8");
|
||||
byte[] hashSource = new byte[passwordBytes.length + s_saltlen];
|
||||
byte[] hashSource = new byte[passwordBytes.length + salt.length];
|
||||
System.arraycopy(passwordBytes, 0, hashSource, 0, passwordBytes.length);
|
||||
System.arraycopy(salt, 0, hashSource, passwordBytes.length, s_saltlen);
|
||||
System.arraycopy(salt, 0, hashSource, passwordBytes.length, salt.length);
|
||||
|
||||
// 2. Hash the password with the salt
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
|
@ -120,4 +129,14 @@ public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator {
|
|||
|
||||
return new String(Base64.encode(digest));
|
||||
}
|
||||
|
||||
private static boolean constantTimeEquals(String a, String b) {
|
||||
byte[] aBytes = a.getBytes();
|
||||
byte[] bBytes = b.getBytes();
|
||||
int result = aBytes.length ^ bBytes.length;
|
||||
for (int i = 0; i < aBytes.length && i < bBytes.length; i++) {
|
||||
result |= aBytes[i] ^ bBytes[i];
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,36 +17,66 @@
|
|||
|
||||
package src.com.cloud.server.auth.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.server.auth.SHA256SaltedUserAuthenticator;
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AuthenticatorTest {
|
||||
|
||||
@Mock
|
||||
UserAccount adminAccount;
|
||||
|
||||
@Mock
|
||||
UserAccount adminAccount20Byte;
|
||||
|
||||
@Mock
|
||||
UserAccountDao _userAccountDao;
|
||||
|
||||
@InjectMocks
|
||||
SHA256SaltedUserAuthenticator authenticator;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
try {
|
||||
authenticator.configure("SHA256", Collections.<String, Object> emptyMap());
|
||||
} catch (ConfigurationException e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
|
||||
when(_userAccountDao.getUserAccount("admin", 0L)).thenReturn(adminAccount);
|
||||
when(_userAccountDao.getUserAccount("admin20Byte", 0L)).thenReturn(adminAccount20Byte);
|
||||
when(_userAccountDao.getUserAccount("fake", 0L)).thenReturn(null);
|
||||
//32 byte salt, and password="password"
|
||||
when(adminAccount.getPassword()).thenReturn("WS3UHhBPKHZeV+G3jnn7G2N3luXgLSfL+2ORDieXa1U=:VhuFOrOU2IpsjKYH8cH1VDaDBh/VivjMcuADjeEbIig=");
|
||||
//20 byte salt, and password="password"
|
||||
when(adminAccount20Byte.getPassword()).thenReturn("QL2NsxVEmRuDaNRkvIyADny7C5w=:JoegiytiWnoBAxmSD/PwBZZYqkr746x2KzPrZNw4NgI=");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncode() throws UnsupportedEncodingException, NoSuchAlgorithmException {
|
||||
SHA256SaltedUserAuthenticator authenticator =
|
||||
new SHA256SaltedUserAuthenticator();
|
||||
|
||||
try {
|
||||
authenticator.configure("SHA256", Collections.<String,Object>emptyMap());
|
||||
} catch (ConfigurationException e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
|
||||
String encodedPassword = authenticator.encode("password");
|
||||
|
||||
|
|
@ -60,4 +90,35 @@ public class AuthenticatorTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthentication() throws UnsupportedEncodingException, NoSuchAlgorithmException {
|
||||
Map<String, Object[]> dummyMap = new HashMap<String, Object[]>();
|
||||
assertEquals("32 byte salt authenticated", true, authenticator.authenticate("admin", "password", 0L, dummyMap));
|
||||
assertEquals("20 byte salt authenticated", true, authenticator.authenticate("admin20Byte", "password", 0L, dummyMap));
|
||||
assertEquals("fake user not authenticated", false, authenticator.authenticate("fake", "fake", 0L, dummyMap));
|
||||
assertEquals("bad password not authenticated", false, authenticator.authenticate("admin", "fake", 0L, dummyMap));
|
||||
assertEquals("20 byte user bad password not authenticated", false, authenticator.authenticate("admin20Byte", "fake", 0L, dummyMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTiming() throws UnsupportedEncodingException, NoSuchAlgorithmException {
|
||||
Map<String, Object[]> dummyMap = new HashMap<String, Object[]>();
|
||||
Double threshold = (double)500000; //half a millisecond
|
||||
|
||||
Long t1 = System.nanoTime();
|
||||
authenticator.authenticate("admin", "password", 0L, dummyMap);
|
||||
Long t2 = System.nanoTime();
|
||||
authenticator.authenticate("admin20Byte", "password", 0L, dummyMap);
|
||||
Long t3 = System.nanoTime();
|
||||
authenticator.authenticate("fake", "fake", 0L, dummyMap);
|
||||
Long t4 = System.nanoTime();
|
||||
authenticator.authenticate("admin", "fake", 0L, dummyMap);
|
||||
Long t5 = System.nanoTime();
|
||||
Long diff1 = t2 - t1;
|
||||
Long diff2 = t3 - t2;
|
||||
Long diff3 = t4 - t3;
|
||||
Long diff4 = t5 - t4;
|
||||
Assert.assertTrue("All computation times within " + threshold / 1000000 + " milisecond",
|
||||
(diff1 <= threshold) && (diff2 <= threshold) && (diff3 <= threshold) && (diff4 <= threshold));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue