Skip to content

Commit

Permalink
Api Keys are now stored hashed in DB
Browse files Browse the repository at this point in the history
Only at creation are once returned in plain

Signed-off-by: Thomas Schauer-Köckeis <[email protected]>
  • Loading branch information
Gepardgame committed Oct 14, 2024
1 parent aa99025 commit 14c1e97
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 10 deletions.
4 changes: 4 additions & 0 deletions alpine-infra/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
</dependency>
<!-- Unit Tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package alpine.persistence;

import alpine.Config;
import alpine.common.logging.Logger;
import alpine.event.LdapSyncEvent;
import alpine.event.framework.EventService;
Expand All @@ -40,8 +41,12 @@

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.mindrot.jbcrypt.BCrypt;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -56,6 +61,8 @@
public class AlpineQueryManager extends AbstractAlpineQueryManager {

private static final Logger LOGGER = Logger.getLogger(AlpineQueryManager.class);
private static final int ROUNDS = Config.getInstance().getPropertyAsInt(Config.AlpineKey.BCRYPT_ROUNDS);
private static final int SUFFIX_LENGTH = 5;

/**
* Default constructor.
Expand Down Expand Up @@ -98,9 +105,10 @@ public AlpineQueryManager(final PersistenceManager pm, final AlpineRequest reque
*/
public ApiKey getApiKey(final String key) {
return callInTransaction(() -> {
final Query<ApiKey> query = pm.newQuery(ApiKey.class, "key == :key");
query.setParameters(key);
return executeAndCloseUnique(query);
final Query<ApiKey> query = pm.newQuery(ApiKey.class, "suffix == :suffix");
query.setParameters(key.substring(key.length() - SUFFIX_LENGTH));
ApiKey apiKey = executeAndCloseUnique(query);
return BCrypt.checkpw(key.substring(0, key.length() - SUFFIX_LENGTH), apiKey.getKey()) ? apiKey : null;
});
}

Expand All @@ -114,7 +122,13 @@ public ApiKey getApiKey(final String key) {
*/
public ApiKey regenerateApiKey(final ApiKey apiKey) {
return callInTransaction(() -> {
apiKey.setKey(ApiKeyGenerator.generate());
String clearKey = ApiKeyGenerator.generate();
String hashedKey = BCrypt.hashpw(new String(Base64.getDecoder().decode(clearKey)), BCrypt.gensalt(ROUNDS))
.toCharArray().toString();
apiKey.setKey(hashedKey);
apiKey.setSuffix(ApiKeyGenerator.generate(SUFFIX_LENGTH));
pm.makeTransient(apiKey);
apiKey.setKey(clearKey + apiKey.getSuffix());
return apiKey;
});
}
Expand All @@ -128,10 +142,17 @@ public ApiKey regenerateApiKey(final ApiKey apiKey) {
public ApiKey createApiKey(final Team team) {
return callInTransaction(() -> {
final var apiKey = new ApiKey();
apiKey.setKey(ApiKeyGenerator.generate());
String clearKey = ApiKeyGenerator.generate();
String hashedKey = BCrypt.hashpw(new String(Base64.getDecoder().decode(clearKey)), BCrypt.gensalt(ROUNDS))
.toCharArray().toString();
apiKey.setKey(hashedKey);
apiKey.setSuffix(ApiKeyGenerator.generate(SUFFIX_LENGTH));
apiKey.setCreated(new Date());
apiKey.setTeams(List.of(team));
return pm.makePersistent(apiKey);
pm.makePersistent(apiKey);
pm.makeTransient(apiKey);
apiKey.setKey(clearKey + apiKey.getSuffix());
return apiKey;
});
}

Expand Down
20 changes: 16 additions & 4 deletions alpine-model/src/main/java/alpine/model/ApiKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class ApiKey implements Serializable, Principal {
@Size(min = 32, max = 255)
@Pattern(regexp = RegexSequence.Definition.WORD_CHARS,
message = "The API key must contain only alpha, numeric and/or underscore characters")
@JsonIgnore
private String key;

@Persistent
Expand All @@ -86,6 +87,11 @@ public class ApiKey implements Serializable, Principal {
@JsonIgnore
private List<Team> teams;

@Persistent
@Unique
@Column(name = "SUFFIX")
private String suffix;

public long getId() {
return id;
}
Expand Down Expand Up @@ -115,9 +121,9 @@ public String getMaskedKey() {
if (key.startsWith(prefix))
maskedKey.append(prefix);

// mask all characters except the last four
maskedKey.append("*".repeat(key.length() - maskedKey.length() - 4));
maskedKey.append(key.substring(key.length() - 4));
// mask all characters except for the suffix
maskedKey.append("*".repeat(key.length() - maskedKey.length() - suffix.length()));
maskedKey.append(suffix);

return maskedKey.toString();
}
Expand Down Expand Up @@ -166,5 +172,11 @@ public void setTeams(List<Team> teams) {
this.teams = teams;
}

}
public String getSuffix() {
return suffix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}

0 comments on commit 14c1e97

Please sign in to comment.