Skip to content

Commit faaa5ad

Browse files
(cherry picked from commit 2df8acd) Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com>
1 parent bfcc566 commit faaa5ad

File tree

2 files changed

+174
-9
lines changed

2 files changed

+174
-9
lines changed

src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,28 +79,46 @@ public boolean exists(User user) {
7979
if(securityRoles != null) {
8080
user.addSecurityRoles(securityRoles);
8181
}
82-
82+
8383
user.addAttributes(attributeMap);
8484
return true;
8585
}
8686

8787
return false;
8888
}
8989

90+
/**
91+
* A helper function used to verify that both invalid and valid usernames have a hashing check during testing.
92+
* @param hash A string hash of the stored user's password.
93+
* @param array A char array of the provided password
94+
* @return Whether the hash matches the provided password
95+
*/
96+
public boolean passwordMatchesHash(String hash, char[] array) {
97+
return OpenBSDBCrypt.checkPassword(hash, array);
98+
}
99+
90100
@Override
91101
public User authenticate(final AuthCredentials credentials) {
92102

103+
boolean userExists;
104+
93105
if (internalUsersModel == null) {
94106
throw new OpenSearchSecurityException("Internal authentication backend not configured. May be OpenSearch is not initialized.");
95107
}
96108

109+
final byte[] password;
110+
String hash;
97111
if(!internalUsersModel.exists(credentials.getUsername())) {
98-
throw new OpenSearchSecurityException(credentials.getUsername() + " not found");
112+
userExists = false;
113+
password = credentials.getPassword();
114+
hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not found and invalid password
115+
} else {
116+
userExists = true;
117+
password = credentials.getPassword();
118+
hash = internalUsersModel.getHash(credentials.getUsername());
99119
}
100120

101-
final byte[] password = credentials.getPassword();
102-
103-
if(password == null || password.length == 0) {
121+
if (password == null || password.length == 0) {
104122
throw new OpenSearchSecurityException("empty passwords not supported");
105123
}
106124

@@ -112,24 +130,26 @@ public User authenticate(final AuthCredentials credentials) {
112130
Arrays.fill(password, (byte)0);
113131

114132
try {
115-
if (OpenBSDBCrypt.checkPassword(internalUsersModel.getHash(credentials.getUsername()), array)) {
133+
if (passwordMatchesHash(hash, array) && userExists) {
116134
final List<String> roles = internalUsersModel.getBackenRoles(credentials.getUsername());
117135
final Map<String, String> customAttributes = internalUsersModel.getAttributes(credentials.getUsername());
118136
if(customAttributes != null) {
119137
for(Entry<String, String> attributeName: customAttributes.entrySet()) {
120138
credentials.addAttribute("attr.internal."+attributeName.getKey(), attributeName.getValue());
121139
}
122140
}
123-
141+
124142
final User user = new User(credentials.getUsername(), roles, credentials);
125-
143+
126144
final List<String> securityRoles = internalUsersModel.getSecurityRoles(credentials.getUsername());
127145
if(securityRoles != null) {
128146
user.addSecurityRoles(securityRoles);
129147
}
130-
131148
return user;
132149
} else {
150+
if (!userExists) {
151+
throw new OpenSearchSecurityException(credentials.getUsername() + " not found");
152+
}
133153
throw new OpenSearchSecurityException("password does not match");
134154
}
135155
} finally {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.auth;
13+
14+
import java.nio.ByteBuffer;
15+
import java.nio.CharBuffer;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.Arrays;
18+
19+
import org.junit.Assert;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
import org.mockito.Mockito;
23+
24+
import org.opensearch.OpenSearchSecurityException;
25+
import org.opensearch.security.auth.internal.InternalAuthenticationBackend;
26+
import org.opensearch.security.securityconf.InternalUsersModel;
27+
import org.opensearch.security.user.AuthCredentials;
28+
29+
import static org.mockito.Mockito.doReturn;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.spy;
32+
import static org.mockito.Mockito.verify;
33+
import static org.mockito.Mockito.when;
34+
import static org.mockito.internal.verification.VerificationModeFactory.times;
35+
36+
public class InternalAuthBackendTests {
37+
38+
private InternalUsersModel internalUsersModel;
39+
40+
private InternalAuthenticationBackend internalAuthenticationBackend;
41+
42+
@Before
43+
public void internalAuthBackendTestsSetup() {
44+
internalAuthenticationBackend = spy(new InternalAuthenticationBackend());
45+
internalUsersModel = mock(InternalUsersModel.class);
46+
internalAuthenticationBackend.onInternalUsersModelChanged(internalUsersModel);
47+
}
48+
49+
private char[] createArrayFromPasswordBytes(byte[] password) {
50+
ByteBuffer wrap = ByteBuffer.wrap(password);
51+
CharBuffer buf = StandardCharsets.UTF_8.decode(wrap);
52+
char[] array = new char[buf.limit()];
53+
buf.get(array);
54+
Arrays.fill(password, (byte)0);
55+
return array;
56+
}
57+
58+
@Test
59+
public void testHashActionWithValidUserValidPassword() {
60+
61+
// Make authentication info for valid username with valid password
62+
final String validPassword = "admin";
63+
final byte[] validPasswordBytes = validPassword.getBytes();
64+
65+
final AuthCredentials validUsernameAuth = new AuthCredentials("admin", validPasswordBytes);
66+
67+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
68+
69+
char[] array = createArrayFromPasswordBytes(validPasswordBytes);
70+
71+
72+
when(internalUsersModel.getHash(validUsernameAuth.getUsername())).thenReturn(hash);
73+
when(internalUsersModel.exists(validUsernameAuth.getUsername())).thenReturn(true);
74+
doReturn(true).when(internalAuthenticationBackend).passwordMatchesHash(Mockito.any(String.class), Mockito.any(char[].class));
75+
76+
//Act
77+
internalAuthenticationBackend.authenticate(validUsernameAuth);
78+
79+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
80+
verify(internalUsersModel, times(1)).getBackenRoles(validUsernameAuth.getUsername());
81+
}
82+
83+
@Test
84+
public void testHashActionWithValidUserInvalidPassword() {
85+
86+
// Make authentication info for valid with bad password
87+
final String gibberishPassword = "ajdhflkasdjfaklsdf";
88+
final byte[] gibberishPasswordBytes = gibberishPassword.getBytes();
89+
final AuthCredentials validUsernameAuth = new AuthCredentials("admin", gibberishPasswordBytes);
90+
91+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
92+
93+
char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes);
94+
95+
when(internalUsersModel.getHash("admin")).thenReturn(hash);
96+
when(internalUsersModel.exists("admin")).thenReturn(true);
97+
98+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
99+
() -> internalAuthenticationBackend.authenticate(validUsernameAuth));
100+
assert(ex.getMessage().contains("password does not match"));
101+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
102+
}
103+
104+
@Test
105+
public void testHashActionWithInvalidUserValidPassword() {
106+
107+
// Make authentication info for valid and invalid usernames both with bad passwords
108+
final String validPassword = "admin";
109+
final byte[] validPasswordBytes = validPassword.getBytes();
110+
final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", validPasswordBytes);
111+
112+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
113+
114+
char[] array = createArrayFromPasswordBytes(validPasswordBytes);
115+
116+
when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false);
117+
when(internalAuthenticationBackend.passwordMatchesHash(hash, array)).thenReturn(true); //Say that the password is correct
118+
119+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
120+
() -> internalAuthenticationBackend.authenticate(invalidUsernameAuth));
121+
assert(ex.getMessage().contains("not found"));
122+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
123+
}
124+
125+
@Test
126+
public void testHashActionWithInvalidUserInvalidPassword() {
127+
128+
// Make authentication info for valid and invalid usernames both with bad passwords
129+
final String gibberishPassword = "ajdhflkasdjfaklsdf";
130+
final byte[] gibberishPasswordBytes = gibberishPassword.getBytes();
131+
final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", gibberishPasswordBytes);
132+
133+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
134+
135+
char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes);
136+
137+
when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false);
138+
139+
140+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
141+
() -> internalAuthenticationBackend.authenticate(invalidUsernameAuth));
142+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
143+
assert(ex.getMessage().contains("not found"));
144+
}
145+
}

0 commit comments

Comments
 (0)