diff --git a/hopsworks-admin/pom.xml b/hopsworks-admin/pom.xml
index 0593bc7137..4060ae256a 100644
--- a/hopsworks-admin/pom.xml
+++ b/hopsworks-admin/pom.xml
@@ -72,6 +72,7 @@
+
io.hops.hopsworks
hopsworks-rest-utils
@@ -79,6 +80,13 @@
provided
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
+
+
io.hops.hopsworks
hopsworks-common
diff --git a/hopsworks-admin/src/main/java/io/hops/hopsworks/admin/maintenance/LoggedMaintenanceHelper.java b/hopsworks-admin/src/main/java/io/hops/hopsworks/admin/maintenance/LoggedMaintenanceHelper.java
index cefca1fff2..2b95e23991 100644
--- a/hopsworks-admin/src/main/java/io/hops/hopsworks/admin/maintenance/LoggedMaintenanceHelper.java
+++ b/hopsworks-admin/src/main/java/io/hops/hopsworks/admin/maintenance/LoggedMaintenanceHelper.java
@@ -15,10 +15,10 @@
*/
package io.hops.hopsworks.admin.maintenance;
-import io.hops.hopsworks.common.security.CertificatesMgmService;
import io.hops.hopsworks.common.util.Settings;
import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
import io.hops.hopsworks.persistence.entity.util.VariablesVisibility;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import javax.ejb.EJB;
import javax.ejb.Stateless;
@@ -36,7 +36,7 @@ public class LoggedMaintenanceHelper {
@EJB
private Settings settings;
@EJB
- private CertificatesMgmService certificatesMgmService;
+ private MasterPasswordService masterPasswordService;
public void updateVariable(String varName, String varValue,
@@ -47,8 +47,8 @@ public void updateVariable(String varName, String varValue,
public void changeMasterEncryptionPassword(String currentPassword, String newPassword, HttpServletRequest request)
throws IOException, EncryptionMasterPasswordException {
String userEmail = request.getUserPrincipal().getName();
- certificatesMgmService.checkPassword(currentPassword, userEmail);
- Integer opId = certificatesMgmService.initUpdateOperation();
- certificatesMgmService.resetMasterEncryptionPassword(opId, newPassword, userEmail);
+ masterPasswordService.checkPassword(currentPassword, userEmail);
+ Integer opId = masterPasswordService.initUpdateOperation();
+ masterPasswordService.resetMasterEncryptionPassword(opId, newPassword, userEmail);
}
}
diff --git a/hopsworks-api/pom.xml b/hopsworks-api/pom.xml
index 7e6b5cff25..0b48e898e0 100644
--- a/hopsworks-api/pom.xml
+++ b/hopsworks-api/pom.xml
@@ -173,6 +173,13 @@
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
+
+
org.glassfish.jersey.core
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java
index c0db174202..fce6a35d52 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/admin/SystemAdminService.java
@@ -65,6 +65,7 @@
import io.hops.hopsworks.persistence.entity.user.Users;
import io.hops.hopsworks.persistence.entity.util.Variables;
import io.hops.hopsworks.restutils.RESTCodes;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -103,6 +104,8 @@ public class SystemAdminService {
@EJB
private CertificatesMgmService certificatesMgmService;
@EJB
+ private MasterPasswordService masterPasswordService;
+ @EJB
private NoCacheResponse noCacheResponse;
@EJB
private Settings settings;
@@ -134,9 +137,9 @@ public Response changeMasterEncryptionPassword(@Context SecurityContext sc,
LOGGER.log(Level.FINE, "Requested master encryption password change");
try {
Users user = jWTHelper.getUserPrincipal(sc);
- certificatesMgmService.checkPassword(oldPassword, user.getEmail());
- Integer operationId = certificatesMgmService.initUpdateOperation();
- certificatesMgmService.resetMasterEncryptionPassword(operationId, newPassword, user.getEmail());
+ masterPasswordService.checkPassword(oldPassword, user.getEmail());
+ Integer operationId = masterPasswordService.initUpdateOperation();
+ masterPasswordService.resetMasterEncryptionPassword(operationId, newPassword, user.getEmail());
RESTApiJsonResponse response = noCacheResponse.buildJsonResponse(Response.Status.CREATED,
String.valueOf(operationId));
@@ -154,7 +157,7 @@ public Response changeMasterEncryptionPassword(@Context SecurityContext sc,
@GET
@Path("/encryptionPass/{opId}")
public Response getUpdatePasswordStatus(@PathParam("opId") Integer operationId, @Context SecurityContext sc) {
- CertificatesMgmService.UPDATE_STATUS status = certificatesMgmService.getOperationStatus(operationId);
+ MasterPasswordService.UPDATE_STATUS status = masterPasswordService.getOperationStatus(operationId);
switch (status) {
case OK:
return noCacheResponse.getNoCacheCORSResponseBuilder(Response.Status.OK).build();
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java
index 1c0efef899..15821537e0 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/filter/AuthFilter.java
@@ -25,6 +25,7 @@
import io.hops.hopsworks.jwt.AlgorithmFactory;
import io.hops.hopsworks.jwt.JWTController;
import io.hops.hopsworks.jwt.annotation.JWTRequired;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.jwt.filter.JWTFilter;
import io.hops.hopsworks.restutils.JsonResponse;
@@ -65,7 +66,7 @@ public class AuthFilter extends JWTFilter {
private ResourceInfo resourceInfo;
@Override
- public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException {
+ public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return algorithmFactory.getAlgorithm(jwt);
}
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java
index 86f7ede3e1..e102e736e4 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTHelper.java
@@ -33,6 +33,7 @@
import io.hops.hopsworks.jwt.exception.InvalidationException;
import io.hops.hopsworks.jwt.exception.JWTException;
import io.hops.hopsworks.jwt.exception.NotRenewableException;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.jwt.exception.VerificationException;
import io.hops.hopsworks.persistence.entity.project.Project;
@@ -163,8 +164,7 @@ public String getAuthToken(ContainerRequestContext req) {
* @throws DuplicateSigningKeyException
*/
public String createToken(Users user, String issuer, Map claims) throws NoSuchAlgorithmException,
- SigningKeyNotFoundException,
- DuplicateSigningKeyException {
+ SigningKeyNotFoundException, DuplicateSigningKeyException, SigningKeyEncryptionException {
String[] audience = null;
Date expiresAt = null;
@@ -204,7 +204,8 @@ public String createOneTimeToken(Users user, String issuer, Map
try {
token = createOneTimeToken(user, roles, issuer, audience, now, expiresAt,
Constants.ONE_TIME_JWT_SIGNING_KEY_NAME, claims, false);
- } catch (NoSuchAlgorithmException | SigningKeyNotFoundException | DuplicateSigningKeyException ex) {
+ } catch (NoSuchAlgorithmException | SigningKeyNotFoundException | DuplicateSigningKeyException |
+ SigningKeyEncryptionException ex) {
Logger.getLogger(JWTHelper.class.getName()).log(Level.SEVERE, null, ex);
}
return token;
@@ -212,7 +213,8 @@ public String createOneTimeToken(Users user, String issuer, Map
public String createOneTimeToken(Users user, String[] roles, String issuer, String[] audience, Date notBefore,
Date expiresAt, String keyName, Map claims, boolean createNewKey)
- throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException {
+ throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException,
+ SigningKeyEncryptionException {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(Constants.ONE_TIME_JWT_SIGNATURE_ALGORITHM);
claims = jwtController.addDefaultClaimsIfMissing(claims, false, 0, roles);
@@ -233,7 +235,8 @@ public String createOneTimeToken(Users user, String[] roles, String issuer, Stri
* @throws DuplicateSigningKeyException
*/
public String createToken(Users user, String[] audience, String issuer, Date expiresAt, Map claims)
- throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException {
+ throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException,
+ SigningKeyEncryptionException {
SignatureAlgorithm alg = SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg());
String[] roles = userController.getUserRoles(user).toArray(new String[0]);
@@ -253,7 +256,7 @@ public String createToken(Users user, String[] audience, String issuer, Date exp
* @throws DuplicateSigningKeyException
*/
public JWTResponseDTO createToken(JWTRequestDTO jWTRequestDTO, String issuer) throws NoSuchAlgorithmException,
- SigningKeyNotFoundException, DuplicateSigningKeyException {
+ SigningKeyNotFoundException, DuplicateSigningKeyException, SigningKeyEncryptionException {
if (jWTRequestDTO == null || jWTRequestDTO.getKeyName() == null || jWTRequestDTO.getKeyName().isEmpty()
|| jWTRequestDTO.getAudiences() == null || jWTRequestDTO.getAudiences().length == 0
|| jWTRequestDTO.getSubject() == null || jWTRequestDTO.getSubject().isEmpty()) {
@@ -302,9 +305,8 @@ public boolean validToken(HttpServletRequest req, String issuer) {
* @throws NotRenewableException
* @throws InvalidationException
*/
- public JWTResponseDTO renewToken(JsonWebTokenDTO jsonWebTokenDTO, boolean invalidate,
- Map claims)
- throws SigningKeyNotFoundException, NotRenewableException, InvalidationException {
+ public JWTResponseDTO renewToken(JsonWebTokenDTO jsonWebTokenDTO, boolean invalidate, Map claims)
+ throws SigningKeyNotFoundException, NotRenewableException, InvalidationException, SigningKeyEncryptionException {
if (jsonWebTokenDTO == null || jsonWebTokenDTO.getToken() == null || jsonWebTokenDTO.getToken().isEmpty()) {
throw new IllegalArgumentException("No token provided.");
}
@@ -414,8 +416,8 @@ public void deleteSigningKeyByName(String keyName) {
* @throws SigningKeyNotFoundException
* @throws VerificationException
*/
- public DecodedJWT verifyOneTimeToken(String token, String issuer) throws SigningKeyNotFoundException,
- VerificationException {
+ public DecodedJWT verifyOneTimeToken(String token, String issuer) throws SigningKeyNotFoundException,
+ VerificationException, SigningKeyEncryptionException {
DecodedJWT jwt = null;
if (token == null || token.trim().isEmpty()) {
throw new VerificationException("Token not provided.");
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java
index 06e94ce218..7c42d49bdf 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/jwt/JWTResource.java
@@ -27,6 +27,7 @@
import io.hops.hopsworks.jwt.exception.InvalidationException;
import io.hops.hopsworks.jwt.exception.JWTException;
import io.hops.hopsworks.jwt.exception.NotRenewableException;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.persistence.entity.user.Users;
import io.hops.hopsworks.restutils.RESTCodes;
@@ -75,7 +76,7 @@ public class JWTResource {
@POST
@ApiOperation(value = "Create application token", response = JWTResponseDTO.class)
public Response createToken(JWTRequestDTO jWTRequestDTO, @Context SecurityContext sc) throws NoSuchAlgorithmException,
- SigningKeyNotFoundException, DuplicateSigningKeyException {
+ SigningKeyNotFoundException, DuplicateSigningKeyException, SigningKeyEncryptionException {
JWTResponseDTO jWTResponseDTO = jWTHelper.createToken(jWTRequestDTO, settings.getJWTIssuer());
return Response.ok().entity(jWTResponseDTO).build();
}
@@ -83,7 +84,7 @@ public Response createToken(JWTRequestDTO jWTRequestDTO, @Context SecurityContex
@PUT
@ApiOperation(value = "Renew application token", response = JWTResponseDTO.class)
public Response renewToken(JsonWebTokenDTO jsonWebTokenDTO, @Context SecurityContext sc)
- throws SigningKeyNotFoundException, NotRenewableException, InvalidationException {
+ throws SigningKeyNotFoundException, NotRenewableException, InvalidationException, SigningKeyEncryptionException {
JWTResponseDTO jWTResponseDTO = jWTHelper.renewToken(jsonWebTokenDTO, true, new HashMap<>(3));
return Response.ok().entity(jWTResponseDTO).build();
}
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java
index 9eef312b43..026931fb87 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/user/AuthService.java
@@ -60,6 +60,7 @@
import io.hops.hopsworks.jwt.annotation.JWTRequired;
import io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException;
import io.hops.hopsworks.jwt.exception.InvalidationException;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.persistence.entity.user.Users;
import io.hops.hopsworks.persistence.entity.util.FormatUtils;
@@ -156,8 +157,7 @@ public Response jwtSession(@Context SecurityContext sc) {
@JWTNotRequired
public Response login(@FormParam("email") String email, @FormParam("password") String password,
@FormParam("otp") String otp, @Context HttpServletRequest req) throws UserException, SigningKeyNotFoundException,
- NoSuchAlgorithmException,
- LoginException, DuplicateSigningKeyException {
+ NoSuchAlgorithmException, LoginException, DuplicateSigningKeyException, SigningKeyEncryptionException {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email was not provided");
@@ -204,7 +204,7 @@ public Response logout(@Context HttpServletRequest req) throws UserException, In
@JWTNotRequired
public Response serviceLogin(@FormParam("email") String email, @FormParam("password") String password,
@Context HttpServletRequest request) throws UserException, GeneralSecurityException, SigningKeyNotFoundException,
- DuplicateSigningKeyException, HopsSecurityException {
+ DuplicateSigningKeyException, HopsSecurityException, SigningKeyEncryptionException {
if (Strings.isNullOrEmpty(email)) {
throw new IllegalArgumentException("Email cannot be null or empty");
}
@@ -418,7 +418,7 @@ private void logoutSession(HttpServletRequest req) throws UserException {
}
private Response login(Users user, String password, HttpServletRequest req) throws UserException,
- SigningKeyNotFoundException, NoSuchAlgorithmException, DuplicateSigningKeyException {
+ SigningKeyNotFoundException, NoSuchAlgorithmException, DuplicateSigningKeyException, SigningKeyEncryptionException {
RESTApiJsonResponse json = new RESTApiJsonResponse();
if (user.getBbcGroupCollection() == null || user.getBbcGroupCollection().isEmpty()) {
throw new UserException(RESTCodes.UserErrorCode.NO_ROLE_FOUND, Level.FINE,
diff --git a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java
index b4c229f74a..32758a1b26 100644
--- a/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java
+++ b/hopsworks-api/src/main/java/io/hops/hopsworks/api/util/DownloadService.java
@@ -57,6 +57,7 @@
import io.hops.hopsworks.exceptions.DatasetException;
import io.hops.hopsworks.exceptions.ProjectException;
import io.hops.hopsworks.jwt.annotation.JWTRequired;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.jwt.exception.VerificationException;
import io.hops.hopsworks.persistence.entity.dataset.Dataset;
@@ -171,7 +172,7 @@ public Response getDownloadToken(@PathParam("path") String path, @QueryParam("ty
@ApiOperation(value = "Download file.", response = StreamingOutput.class)
public Response downloadFromHDFS(@PathParam("path") String path, @QueryParam("token") String token,
@QueryParam("type") DatasetType datasetType, @Context SecurityContext sc) throws DatasetException,
- SigningKeyNotFoundException, VerificationException, ProjectException {
+ SigningKeyNotFoundException, VerificationException, ProjectException, SigningKeyEncryptionException {
if(!settings.isDownloadAllowed()){
throw new DatasetException(RESTCodes.DatasetErrorCode.DOWNLOAD_NOT_ALLOWED, Level.FINEST);
}
diff --git a/hopsworks-ca/pom.xml b/hopsworks-ca/pom.xml
index cbfd5ac656..50a21a5648 100644
--- a/hopsworks-ca/pom.xml
+++ b/hopsworks-ca/pom.xml
@@ -72,6 +72,12 @@
hopsworks-rest-utils
${project.version}
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
+
io.hops.hopsworks
hopsworks-jwt
diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java
index 5426b0ddea..854e4b5761 100644
--- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java
+++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/filter/AuthFilter.java
@@ -20,6 +20,7 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import io.hops.hopsworks.ca.api.exception.mapper.CAJsonResponse;
import io.hops.hopsworks.ca.controllers.CAConf;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.restutils.JsonResponse;
import io.hops.hopsworks.restutils.RESTCodes;
import io.hops.hopsworks.jwt.AlgorithmFactory;
@@ -62,7 +63,7 @@ public class AuthFilter extends JWTFilter {
private UriInfo uriInfo;
@Override
- public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException {
+ public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return algorithmFactory.getAlgorithm(jwt);
}
diff --git a/hopsworks-common/pom.xml b/hopsworks-common/pom.xml
index 16f64ded62..496a913b03 100644
--- a/hopsworks-common/pom.xml
+++ b/hopsworks-common/pom.xml
@@ -69,6 +69,13 @@
provided
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
+
+
io.hops.hopsworks
hopsworks-jwt
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/elastic/ElasticJWTController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/elastic/ElasticJWTController.java
index 968310a603..0a8b74234f 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/elastic/ElasticJWTController.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/elastic/ElasticJWTController.java
@@ -15,6 +15,7 @@
*/
package io.hops.hopsworks.common.elastic;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.persistence.entity.project.Project;
import io.hops.hopsworks.persistence.entity.project.team.ProjectRoleTypes;
import io.hops.hopsworks.common.dao.project.team.ProjectTeamFacade;
@@ -54,7 +55,7 @@ public String getSigningKeyForELK() throws ElasticException {
SignatureAlgorithm alg = SignatureAlgorithm.valueOf(settings.getJWTSignatureAlg());
try {
return jwtController.getSigningKeyForELK(alg);
- } catch (NoSuchAlgorithmException e) {
+ } catch (NoSuchAlgorithmException | SigningKeyEncryptionException e) {
throw new ElasticException(RESTCodes.ElasticErrorCode.SIGNING_KEY_ERROR,
Level.SEVERE, "Failed to get elk signing key", e.getMessage(),
e);
@@ -98,7 +99,8 @@ private String createTokenForELK(String project, Optional projectInodeId,
}
return jwtController.createTokenForELK(project, settings.getJWTIssuer()
, claims, expiresAt, alg);
- } catch (DuplicateSigningKeyException | NoSuchAlgorithmException | SigningKeyNotFoundException e) {
+ } catch (DuplicateSigningKeyException | NoSuchAlgorithmException | SigningKeyNotFoundException |
+ SigningKeyEncryptionException e) {
throw new ElasticException(RESTCodes.ElasticErrorCode.JWT_NOT_CREATED,
Level.SEVERE, "Failed to create jwt token for elk", e.getMessage(), e);
}
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificateMaterializer.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificateMaterializer.java
index c90c1a32be..d3b7d65ab8 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificateMaterializer.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificateMaterializer.java
@@ -51,6 +51,7 @@
import io.hops.hopsworks.common.util.HopsUtils;
import io.hops.hopsworks.common.util.Settings;
import io.hops.hopsworks.exceptions.CryptoPasswordNotFoundException;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import org.apache.commons.collections.Bag;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.bag.HashBag;
@@ -125,7 +126,7 @@ public class CertificateMaterializer {
@EJB
private UserFacade userFacade;
@EJB
- private CertificatesMgmService certificatesMgmService;
+ private MasterPasswordService masterPasswordService;
@EJB
private RemoteMaterialReferencesFacade remoteMaterialReferencesFacade;
@EJB
@@ -1206,7 +1207,7 @@ private char[] decryptMaterialPassword(String certificateIdentifier, String encr
String userPassword = user.getPassword();
try {
- String decryptedPassword = HopsUtils.decrypt(userPassword, encryptedPassword, certificatesMgmService
+ String decryptedPassword = HopsUtils.decrypt(userPassword, encryptedPassword, masterPasswordService
.getMasterEncryptionPassword());
return decryptedPassword.toCharArray();
} catch (Exception ex) {
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesController.java
index 5e493c6137..13c7914563 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesController.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesController.java
@@ -49,6 +49,7 @@
import io.hops.hopsworks.exceptions.HopsSecurityException;
import io.hops.hopsworks.restutils.RESTCodes;
import io.hops.hopsworks.common.util.HopsUtils;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
@@ -114,6 +115,10 @@ public class CertificatesController {
private CertsFacade certsFacade;
@EJB
private CertificatesMgmService certificatesMgmService;
+ @EJB
+ private MasterPasswordService masterPasswordService;
+ @EJB
+ private Settings settings;
@Inject
@Any
private Instance certificateHandlers;
@@ -163,7 +168,7 @@ public void init() {
public Future generateCertificates(Project project, Users user) throws Exception {
String userKeyPwd = HopsUtils.randomString(64);
String encryptedKey = HopsUtils.encrypt(user.getPassword(), userKeyPwd,
- certificatesMgmService.getMasterEncryptionPassword());
+ masterPasswordService.getMasterEncryptionPassword());
Pair userKeystores =
generateStores(project.getName() + Settings.HOPS_USERNAME_SEPARATOR + user.getUsername(),
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesMgmService.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesMgmService.java
index a17ab84ab5..03af150e46 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesMgmService.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/CertificatesMgmService.java
@@ -38,227 +38,29 @@
*/
package io.hops.hopsworks.common.security;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import io.hops.hopsworks.common.dao.certificates.CertsFacade;
+import io.hops.hopsworks.common.dao.command.SystemCommandFacade;
+import io.hops.hopsworks.common.dao.host.HostsFacade;
import io.hops.hopsworks.persistence.entity.command.Operation;
import io.hops.hopsworks.persistence.entity.command.SystemCommand;
-import io.hops.hopsworks.common.dao.command.SystemCommandFacade;
-import io.hops.hopsworks.common.dao.dela.certs.ClusterCertificateFacade;
import io.hops.hopsworks.persistence.entity.host.Hosts;
-import io.hops.hopsworks.common.dao.host.HostsFacade;
-import io.hops.hopsworks.common.dao.user.UserFacade;
-import io.hops.hopsworks.persistence.entity.user.Users;
-import io.hops.hopsworks.common.message.MessageController;
-import io.hops.hopsworks.common.util.Settings;
-import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.io.FileUtils;
-import javax.annotation.PostConstruct;
-import javax.ejb.AccessTimeout;
-import javax.ejb.Asynchronous;
-import javax.ejb.ConcurrencyManagement;
-import javax.ejb.ConcurrencyManagementType;
import javax.ejb.EJB;
-import javax.ejb.Lock;
-import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
-import javax.enterprise.inject.Any;
-import javax.enterprise.inject.Instance;
-import javax.inject.Inject;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
import java.util.logging.Logger;
@Singleton
-@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class CertificatesMgmService {
private final Logger LOG = Logger.getLogger(CertificatesMgmService.class.getName());
- @EJB
- private Settings settings;
- @EJB
- private UserFacade userFacade;
- @EJB
- private CertsFacade certsFacade;
- @EJB
- private ClusterCertificateFacade clusterCertificateFacade;
- @EJB
- private MessageController messageController;
@EJB
private SystemCommandFacade systemCommandFacade;
@EJB
private HostsFacade hostsFacade;
- @Inject
- @Any
- private Instance handlers;
- public enum UPDATE_STATUS {
- OK,
- WORKING,
- FAILED,
- NOT_FOUND
- }
-
- private File masterPasswordFile;
- private final Map handlersResult = new HashMap<>();
- private Cache updateStatus;
- private Random rand;
-
public CertificatesMgmService() {
-
- }
-
- @PostConstruct
- public void init() {
- masterPasswordFile = new File(settings.getHopsworksMasterEncPasswordFile());
- if (!masterPasswordFile.exists()) {
- throw new IllegalStateException("Master encryption file does not exist");
- }
-
- try {
- PosixFileAttributeView fileView = Files.getFileAttributeView(masterPasswordFile.toPath(),
- PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
- Set filePermissions = fileView.readAttributes().permissions();
- boolean ownerRead = filePermissions.contains(PosixFilePermission.OWNER_READ);
- boolean ownerWrite = filePermissions.contains(PosixFilePermission
- .OWNER_WRITE);
- boolean ownerExecute = filePermissions.contains(PosixFilePermission
- .OWNER_EXECUTE);
-
- boolean groupRead = filePermissions.contains(PosixFilePermission.GROUP_READ);
- boolean groupWrite = filePermissions.contains(PosixFilePermission
- .GROUP_WRITE);
- boolean groupExecute = filePermissions.contains(PosixFilePermission
- .GROUP_EXECUTE);
-
- boolean othersRead = filePermissions.contains(PosixFilePermission
- .OTHERS_READ);
- boolean othersWrite = filePermissions.contains(PosixFilePermission
- .OTHERS_WRITE);
- boolean othersExecute = filePermissions.contains(PosixFilePermission
- .OTHERS_EXECUTE);
-
- // Permissions should be 700
- if ((ownerRead && ownerWrite && ownerExecute)
- && (!groupRead && !groupWrite && !groupExecute)
- && (!othersRead && !othersWrite && !othersExecute)) {
- String owner = fileView.readAttributes().owner().getName();
- String group = fileView.readAttributes().group().getName();
- String permStr = PosixFilePermissions.toString(filePermissions);
- LOG.log(Level.INFO, "Passed permissions check for file " + masterPasswordFile.getAbsolutePath()
- + ". Owner: " + owner + " Group: " + group + " Permissions: " + permStr);
- } else {
- throw new IllegalStateException("Wrong permissions for file " + masterPasswordFile.getAbsolutePath()
- + ", it should be 700");
- }
-
- updateStatus = CacheBuilder.newBuilder()
- .maximumSize(100)
- .expireAfterWrite(12L, TimeUnit.HOURS)
- .build();
- rand = new Random();
- } catch (UnsupportedOperationException ex) {
- LOG.log(Level.WARNING, "Associated filesystem is not POSIX compliant. " +
- "Continue without checking the permissions of " + masterPasswordFile.getAbsolutePath()
- + " This might be a security problem.");
- } catch (IOException ex) {
- throw new IllegalStateException("Error while getting POSIX permissions of " + masterPasswordFile
- .getAbsolutePath());
- }
- }
-
- @Lock(LockType.READ)
- @AccessTimeout(value = 3, unit = TimeUnit.SECONDS)
- public String getMasterEncryptionPassword() throws IOException {
- return FileUtils.readFileToString(masterPasswordFile).trim();
- }
-
- /**
- * Validates the provided password against the configured one
- * @param providedPassword Password to validate
- * @param userRequestedEmail User requested the password check
- * @throws IOException
- * @throws EncryptionMasterPasswordException
- */
- @Lock(LockType.READ)
- @AccessTimeout(value = 3, unit = TimeUnit.SECONDS)
- public void checkPassword(String providedPassword, String userRequestedEmail)
- throws IOException, EncryptionMasterPasswordException {
- String sha = DigestUtils.sha256Hex(providedPassword);
- if (!getMasterEncryptionPassword().equals(sha)) {
- Users user = userFacade.findByEmail(userRequestedEmail);
- String logMsg = "*** Attempt to change master encryption password with wrong credentials";
- if (user != null) {
- LOG.log(Level.INFO, logMsg + " by user <" + user.getUsername() + ">");
- } else {
- LOG.log(Level.INFO, logMsg);
- }
- throw new EncryptionMasterPasswordException("Provided password is incorrect");
- }
- }
-
- public Integer initUpdateOperation() {
- Integer operationId = rand.nextInt();
- updateStatus.put(operationId, UPDATE_STATUS.WORKING);
- return operationId;
- }
-
- public UPDATE_STATUS getOperationStatus(Integer operationId) {
- UPDATE_STATUS status = updateStatus.getIfPresent(operationId);
- return status != null ? status : UPDATE_STATUS.NOT_FOUND;
- }
-
- /**
- * Decrypt secrets using the old master password and encrypt them with the new
- * Both for project specific and project generic certificates
- * @param newMasterPasswd new master encryption password
- * @param userRequested User requested password change
- */
- @SuppressWarnings("unchecked")
- @Asynchronous
- @Lock(LockType.WRITE)
- @AccessTimeout(value = 500)
- public void resetMasterEncryptionPassword(Integer operationId, String newMasterPasswd, String userRequested) {
- try {
- String newDigest = DigestUtils.sha256Hex(newMasterPasswd);
- callUpdateHandlers(newDigest);
- updateMasterEncryptionPassword(newDigest);
- StringBuilder successLog = gatherLogs();
- sendSuccessfulMessage(successLog, userRequested);
- updateStatus.put(operationId, UPDATE_STATUS.OK);
- LOG.log(Level.INFO, "Master encryption password changed!");
- } catch (EncryptionMasterPasswordException ex) {
- String errorMsg = "*** Master encryption password update failed!!! Rolling back...";
- LOG.log(Level.SEVERE, errorMsg, ex);
- updateStatus.put(operationId, UPDATE_STATUS.FAILED);
- callRollbackHandlers();
- sendUnsuccessfulMessage(errorMsg + "\n" + ex.getMessage(), userRequested);
- } catch (IOException ex) {
- String errorMsg = "*** Failed to write new encryption password to file: " + masterPasswordFile.getAbsolutePath()
- + ". Rolling back...";
- LOG.log(Level.SEVERE, errorMsg, ex);
- updateStatus.put(operationId, UPDATE_STATUS.FAILED);
- callRollbackHandlers();
- sendUnsuccessfulMessage(errorMsg + "\n" + ex.getMessage(), userRequested);
- } finally {
- handlersResult.clear();
- }
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
@@ -269,52 +71,4 @@ public void issueServiceKeyRotationCommand() {
systemCommandFacade.persist(rotateCommand);
}
}
-
- private void callUpdateHandlers(String newDigest) throws EncryptionMasterPasswordException, IOException {
- for (MasterPasswordHandler handler : handlers) {
- MasterPasswordChangeResult result = handler.perform(getMasterEncryptionPassword(), newDigest);
- handlersResult.put(handler.getClass(), result);
- if (result.getCause() != null) {
- throw result.getCause();
- }
- }
- }
-
- private void callRollbackHandlers() {
- for (MasterPasswordHandler handler : handlers) {
- MasterPasswordChangeResult result = handlersResult.get(handler.getClass());
- if (result != null) {
- handler.rollback(result);
- }
- }
- }
-
- private StringBuilder gatherLogs() {
- StringBuilder successLog = new StringBuilder();
- for (MasterPasswordChangeResult result : handlersResult.values()) {
- if (result.getSuccessLog() != null) {
- successLog.append(result.getSuccessLog());
- successLog.append("\n\n");
- }
- }
- return successLog;
- }
-
- private void updateMasterEncryptionPassword(String newPassword) throws IOException {
- FileUtils.writeStringToFile(masterPasswordFile, newPassword);
- }
-
- private void sendSuccessfulMessage(StringBuilder successLog, String userRequested) {
- sendInbox(successLog.toString(), "Changed successfully", userRequested);
- }
-
- private void sendUnsuccessfulMessage(String message, String userRequested) {
- sendInbox(message, "Change failed!", userRequested);
- }
-
- private void sendInbox(String message, String preview, String userRequested) {
- Users to = userFacade.findByEmail(userRequested);
- Users from = userFacade.findByEmail(settings.getAdminEmail());
- messageController.send(to, from, "Master encryption password changed", preview, message, "");
- }
}
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/DelaCertsMasterPasswordHandler.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/DelaCertsMasterPasswordHandler.java
index fab2f1c7e2..c1a6b7fbae 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/DelaCertsMasterPasswordHandler.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/DelaCertsMasterPasswordHandler.java
@@ -42,6 +42,8 @@
import io.hops.hopsworks.common.dao.dela.certs.ClusterCertificateFacade;
import io.hops.hopsworks.common.util.Settings;
import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
+import io.hops.hopsworks.security.password.MasterPasswordChangeResult;
+import io.hops.hopsworks.security.password.MasterPasswordHandler;
import javax.ejb.EJB;
import javax.ejb.Stateless;
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/PSUserCertsMasterPasswordHandler.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/PSUserCertsMasterPasswordHandler.java
index 539d6e0546..4371cff82a 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/PSUserCertsMasterPasswordHandler.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/PSUserCertsMasterPasswordHandler.java
@@ -44,6 +44,8 @@
import io.hops.hopsworks.persistence.entity.user.Users;
import io.hops.hopsworks.common.hdfs.HdfsUsersController;
import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
+import io.hops.hopsworks.security.password.MasterPasswordChangeResult;
+import io.hops.hopsworks.security.password.MasterPasswordHandler;
import javax.ejb.EJB;
import javax.ejb.Stateless;
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsController.java
index dc06d5268f..2add2a2ead 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsController.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsController.java
@@ -27,15 +27,14 @@
import io.hops.hopsworks.common.dao.user.security.secrets.SecretPlaintext;
import io.hops.hopsworks.common.dao.user.security.secrets.SecretsFacade;
import io.hops.hopsworks.persistence.entity.user.security.secrets.VisibilityType;
-import io.hops.hopsworks.common.project.ProjectController;
-import io.hops.hopsworks.common.security.CertificatesMgmService;
-import io.hops.hopsworks.common.security.SymmetricEncryptionDescriptor;
-import io.hops.hopsworks.common.security.SymmetricEncryptionService;
import io.hops.hopsworks.common.util.DateUtils;
import io.hops.hopsworks.exceptions.ProjectException;
import io.hops.hopsworks.exceptions.ServiceException;
import io.hops.hopsworks.exceptions.UserException;
import io.hops.hopsworks.restutils.RESTCodes;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionDescriptor;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionService;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import javax.ejb.EJB;
import javax.ejb.Stateless;
@@ -64,12 +63,10 @@ public class SecretsController {
@EJB
private SymmetricEncryptionService symmetricEncryptionService;
@EJB
- private CertificatesMgmService certificatesMgmService;
+ private MasterPasswordService masterPasswordService;
@EJB
private UserFacade userFacade;
@EJB
- private ProjectController projectController;
- @EJB
private ProjectFacade projectFacade;
/**
@@ -299,7 +296,7 @@ private SecretPlaintext constructSecretView(Users user, Secret ciphered) {
*/
private SecretPlaintext decrypt(Users user, Secret ciphered)
throws IOException, GeneralSecurityException {
- String password = certificatesMgmService.getMasterEncryptionPassword();
+ String password = masterPasswordService.getMasterEncryptionPassword();
// [salt(64),iv(12),payload)]
byte[][] split = symmetricEncryptionService.splitPayloadFromCryptoPrimitives(ciphered.getSecret());
@@ -329,7 +326,7 @@ private SecretPlaintext decrypt(Users user, Secret ciphered)
* @throws GeneralSecurityException
*/
private byte[] encryptSecret(String secret) throws IOException, GeneralSecurityException {
- String password = certificatesMgmService.getMasterEncryptionPassword();
+ String password = masterPasswordService.getMasterEncryptionPassword();
SymmetricEncryptionDescriptor descriptor = new SymmetricEncryptionDescriptor.Builder()
.setInput(string2bytes(secret))
.setPassword(password)
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsPasswordHandler.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsPasswordHandler.java
index 175771b22b..de50f4d02e 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsPasswordHandler.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/security/secrets/SecretsPasswordHandler.java
@@ -16,14 +16,14 @@
package io.hops.hopsworks.common.security.secrets;
-import io.hops.hopsworks.persistence.entity.user.security.secrets.Secret;
-import io.hops.hopsworks.persistence.entity.user.security.secrets.SecretId;
import io.hops.hopsworks.common.dao.user.security.secrets.SecretsFacade;
-import io.hops.hopsworks.common.security.MasterPasswordChangeResult;
-import io.hops.hopsworks.common.security.MasterPasswordHandler;
-import io.hops.hopsworks.common.security.SymmetricEncryptionDescriptor;
-import io.hops.hopsworks.common.security.SymmetricEncryptionService;
import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
+import io.hops.hopsworks.persistence.entity.user.security.secrets.Secret;
+import io.hops.hopsworks.persistence.entity.user.security.secrets.SecretId;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionDescriptor;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionService;
+import io.hops.hopsworks.security.password.MasterPasswordChangeResult;
+import io.hops.hopsworks.security.password.MasterPasswordHandler;
import javax.ejb.EJB;
import javax.ejb.Stateless;
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java
index 9c575d0d86..bc5c2857a3 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/user/AuthController.java
@@ -39,28 +39,28 @@
package io.hops.hopsworks.common.user;
import io.hops.hopsworks.common.dao.certificates.CertsFacade;
-import io.hops.hopsworks.persistence.entity.certificates.UserCerts;
-import io.hops.hopsworks.persistence.entity.project.Project;
import io.hops.hopsworks.common.dao.project.ProjectFacade;
-import io.hops.hopsworks.persistence.entity.user.BbcGroup;
import io.hops.hopsworks.common.dao.user.BbcGroupFacade;
import io.hops.hopsworks.common.dao.user.UserFacade;
-import io.hops.hopsworks.persistence.entity.user.Users;
import io.hops.hopsworks.common.dao.user.security.audit.AccountAuditFacade;
-import io.hops.hopsworks.persistence.entity.user.security.ua.SecurityQuestion;
-import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountStatus;
-import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountType;
import io.hops.hopsworks.common.dao.user.security.ua.UserAccountsEmailMessages;
import io.hops.hopsworks.common.security.utils.Secret;
import io.hops.hopsworks.common.security.utils.SecurityUtils;
-import io.hops.hopsworks.common.util.HttpUtil;
-import io.hops.hopsworks.persistence.entity.user.security.ua.ValidationKeyType;
-import io.hops.hopsworks.restutils.RESTCodes;
-import io.hops.hopsworks.exceptions.UserException;
-import io.hops.hopsworks.common.security.CertificatesMgmService;
import io.hops.hopsworks.common.util.EmailBean;
import io.hops.hopsworks.common.util.HopsUtils;
+import io.hops.hopsworks.common.util.HttpUtil;
import io.hops.hopsworks.common.util.Settings;
+import io.hops.hopsworks.exceptions.UserException;
+import io.hops.hopsworks.persistence.entity.certificates.UserCerts;
+import io.hops.hopsworks.persistence.entity.project.Project;
+import io.hops.hopsworks.persistence.entity.user.BbcGroup;
+import io.hops.hopsworks.persistence.entity.user.Users;
+import io.hops.hopsworks.persistence.entity.user.security.ua.SecurityQuestion;
+import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountStatus;
+import io.hops.hopsworks.persistence.entity.user.security.ua.UserAccountType;
+import io.hops.hopsworks.persistence.entity.user.security.ua.ValidationKeyType;
+import io.hops.hopsworks.restutils.RESTCodes;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import javax.ejb.EJB;
import javax.ejb.EJBException;
@@ -98,7 +98,7 @@ public class AuthController {
@EJB
private ProjectFacade projectFacade;
@EJB
- private CertificatesMgmService certificatesMgmService;
+ private MasterPasswordService masterPasswordService;
@EJB
private SecurityUtils securityUtils;
@EJB
@@ -510,7 +510,7 @@ private void resetProjectCertPassword(Users p, String oldPass) {
try {
for (Project project : projects) {
UserCerts userCert = userCertsFacade.findUserCert(project.getName(), p.getUsername());
- String masterEncryptionPassword = certificatesMgmService.getMasterEncryptionPassword();
+ String masterEncryptionPassword = masterPasswordService.getMasterEncryptionPassword();
String certPassword = HopsUtils.decrypt(oldPass, userCert.getUserKeyPwd(), masterEncryptionPassword);
//Encrypt it with new password and store it in the db
String newSecret = HopsUtils.encrypt(p.getPassword(), certPassword, masterEncryptionPassword);
diff --git a/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java b/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java
index afdd1364db..c05bef5c28 100644
--- a/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java
+++ b/hopsworks-common/src/main/java/io/hops/hopsworks/common/util/Settings.java
@@ -54,6 +54,7 @@
import io.hops.hopsworks.exceptions.ProvenanceException;
import io.hops.hopsworks.persistence.entity.util.VariablesVisibility;
import io.hops.hopsworks.restutils.RESTLogLevel;
+import io.hops.hopsworks.security.util.SecuritySettings;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
@@ -104,6 +105,8 @@ public class Settings implements Serializable {
private ProjectUtils projectUtils;
@EJB
private OSProcessExecutor osProcessExecutor;
+ @EJB
+ private SecuritySettings securitySettings;
@PersistenceContext(unitName = "kthfsPU")
private EntityManager em;
@@ -728,6 +731,7 @@ private void checkCache() {
public synchronized void refreshCache() {
cached = false;
populateCache();
+ securitySettings.refreshCache();
}
public synchronized void updateVariable(String variableName, String variableValue, VariablesVisibility visibility) {
diff --git a/hopsworks-dela/pom.xml b/hopsworks-dela/pom.xml
index 084a465dc7..4c1a2f1c4c 100644
--- a/hopsworks-dela/pom.xml
+++ b/hopsworks-dela/pom.xml
@@ -73,6 +73,20 @@
io.hops.hopsworks
hopsworks-persistence
${project.version}
+
+
+
+ *
+ *
+
+
+
+
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
diff --git a/hopsworks-dela/src/main/java/io/hops/hopsworks/dela/DelaSetupWorker.java b/hopsworks-dela/src/main/java/io/hops/hopsworks/dela/DelaSetupWorker.java
index e7a2fd5788..e3c19fd7e6 100644
--- a/hopsworks-dela/src/main/java/io/hops/hopsworks/dela/DelaSetupWorker.java
+++ b/hopsworks-dela/src/main/java/io/hops/hopsworks/dela/DelaSetupWorker.java
@@ -41,23 +41,19 @@
import com.google.gson.Gson;
import io.hops.hopsworks.common.dao.dela.certs.ClusterCertificateFacade;
import io.hops.hopsworks.common.dela.AddressJSON;
-import io.hops.hopsworks.restutils.RESTCodes;
-import io.hops.hopsworks.common.security.CertificatesMgmService;
import io.hops.hopsworks.common.util.OSProcessExecutor;
import io.hops.hopsworks.common.util.Settings;
import io.hops.hopsworks.dela.dto.hopssite.ClusterServiceDTO;
-import io.hops.hopsworks.exceptions.DelaException;
import io.hops.hopsworks.dela.hopssite.HopssiteController;
+import io.hops.hopsworks.exceptions.DelaException;
+import io.hops.hopsworks.restutils.RESTCodes;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import io.hops.hopsworks.util.CertificateHelper;
import io.hops.hopsworks.util.SettingsHelper;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.KeyStore;
-import java.util.List;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.javatuples.Pair;
+import org.javatuples.Triplet;
+
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@@ -69,9 +65,14 @@
import javax.ejb.TimerService;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.javatuples.Pair;
-import org.javatuples.Triplet;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.KeyStore;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
@Startup
@Singleton
@@ -92,7 +93,7 @@ public class DelaSetupWorker {
@EJB
private TransferDelaController delaCtrl;
@EJB
- private CertificatesMgmService certificatesMgmService;
+ private MasterPasswordService masterPasswordService;
@EJB
private OSProcessExecutor osProcessExecutor;
@@ -174,7 +175,7 @@ private void setup(Timer timer) {
if (clusterName.isPresent()) {
Optional> keystoreAux
= CertificateHelper.loadKeystoreFromDB(masterPswd.get(), clusterName.get(), clusterCertFacade,
- certificatesMgmService);
+ masterPasswordService);
if (keystoreAux.isPresent()) {
setupComplete(keystoreAux.get(), timer);
return;
@@ -182,7 +183,7 @@ private void setup(Timer timer) {
}
Optional> keystoreAux
- = CertificateHelper.loadKeystoreFromFile(masterPswd.get(), settings, clusterCertFacade, certificatesMgmService,
+ = CertificateHelper.loadKeystoreFromFile(masterPswd.get(), settings, clusterCertFacade, masterPasswordService,
osProcessExecutor);
if (keystoreAux.isPresent()) {
setupComplete(keystoreAux.get(), timer);
diff --git a/hopsworks-dela/src/main/java/io/hops/hopsworks/util/CertificateHelper.java b/hopsworks-dela/src/main/java/io/hops/hopsworks/util/CertificateHelper.java
index 1f1afbcafe..297d3b0db4 100644
--- a/hopsworks-dela/src/main/java/io/hops/hopsworks/util/CertificateHelper.java
+++ b/hopsworks-dela/src/main/java/io/hops/hopsworks/util/CertificateHelper.java
@@ -41,12 +41,12 @@
import com.google.common.io.ByteStreams;
import io.hops.hopsworks.common.dao.dela.certs.ClusterCertificateFacade;
-import io.hops.hopsworks.common.security.CertificatesMgmService;
import io.hops.hopsworks.common.util.HopsUtils;
import io.hops.hopsworks.common.util.LocalhostServices;
import io.hops.hopsworks.common.util.OSProcessExecutor;
import io.hops.hopsworks.common.util.Settings;
import io.hops.hopsworks.persistence.entity.dela.certs.ClusterCertificate;
+import io.hops.hopsworks.security.password.MasterPasswordService;
import org.apache.commons.io.FileUtils;
import org.javatuples.Triplet;
@@ -71,7 +71,7 @@ public class CertificateHelper {
private final static Logger LOG = Logger.getLogger(CertificateHelper.class.getName());
public static Optional> loadKeystoreFromFile(String masterPswd, Settings settings,
- ClusterCertificateFacade certFacade, CertificatesMgmService certificatesMgmService,
+ ClusterCertificateFacade certFacade, MasterPasswordService masterPasswordService,
OSProcessExecutor osProcessExecutor) {
String certPath = settings.getHopsSiteCert();
String intermediateCertPath = settings.getHopsSiteIntermediateCert();
@@ -80,7 +80,7 @@ public static Optional> loadKeystoreFromFile
try {
String certPswd = HopsUtils.randomString(64);
String encryptedCertPswd = HopsUtils.encrypt(masterPswd, certPswd,
- certificatesMgmService.getMasterEncryptionPassword());
+ masterPasswordService.getMasterEncryptionPassword());
File certFile = readFile(certPath);
File intermediateCertFile = readFile(intermediateCertPath);
String clusterName = getClusterName(certFile);
@@ -111,14 +111,14 @@ public static Optional> loadKeystoreFromFile
}
public static Optional> loadKeystoreFromDB(String masterPswd, String clusterName,
- ClusterCertificateFacade certFacade, CertificatesMgmService certificatesMgmService) {
+ ClusterCertificateFacade certFacade, MasterPasswordService masterPasswordService) {
try {
Optional cert = certFacade.getClusterCert(clusterName);
if (!cert.isPresent()) {
return Optional.empty();
}
String certPswd = HopsUtils.decrypt(masterPswd, cert.get().getCertificatePassword(),
- certificatesMgmService.getMasterEncryptionPassword());
+ masterPasswordService.getMasterEncryptionPassword());
KeyStore keystore, truststore;
try (ByteArrayInputStream keystoreIS = new ByteArrayInputStream(cert.get().getClusterKey());
ByteArrayInputStream truststoreIS = new ByteArrayInputStream(cert.get().getClusterCert())) {
diff --git a/hopsworks-ear/pom.xml b/hopsworks-ear/pom.xml
index f6812bffec..57b8b836d8 100644
--- a/hopsworks-ear/pom.xml
+++ b/hopsworks-ear/pom.xml
@@ -100,6 +100,12 @@
${project.version}
ejb
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ ejb
+
io.hops.hopsworks
hopsworks-kmon
diff --git a/hopsworks-jwt/pom.xml b/hopsworks-jwt/pom.xml
index 60b722ab9f..dcf5eac5bb 100644
--- a/hopsworks-jwt/pom.xml
+++ b/hopsworks-jwt/pom.xml
@@ -46,6 +46,18 @@
hopsworks-persistence
${project.version}
+
+ io.hops.hopsworks
+ hopsworks-rest-utils
+ ${project.version}
+ provided
+
+
+ io.hops.hopsworks
+ hopsworks-security
+ ${project.version}
+ provided
+
com.auth0
java-jwt
@@ -62,6 +74,13 @@
commons-lang3
3.8.1
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
+
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/AlgorithmFactory.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/AlgorithmFactory.java
index 3ef5fada67..fa2b54ece0 100644
--- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/AlgorithmFactory.java
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/AlgorithmFactory.java
@@ -19,38 +19,57 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;
-import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
-import io.hops.hopsworks.jwt.dao.JwtSigningKeyFacade;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
+
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+import javax.ws.rs.NotSupportedException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
-import java.util.Base64;
-import javax.ejb.EJB;
-import javax.ejb.Stateless;
-import javax.ws.rs.NotSupportedException;
@Stateless
public class AlgorithmFactory {
-
+
@EJB
- private JwtSigningKeyFacade jwtSigningKeyFacade;
+ private SigningKeyEncryptionService signingKeyEncryptionService;
- public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException {
+ public Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return getAlgorithm(jwt.getAlgorithm(), jwt.getKeyId());
}
- public Algorithm getAlgorithm(JsonWebToken jwt) throws SigningKeyNotFoundException {
+ public Algorithm getAlgorithm(JsonWebToken jwt) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return getAlgorithm(jwt.getAlgorithm(), jwt.getKeyId());
}
- public Algorithm getAlgorithm(String algorithm, String keyId) throws SigningKeyNotFoundException {
+ public Algorithm getAlgorithm(String algorithm, String keyId) throws SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
SignatureAlgorithm alg = SignatureAlgorithm.valueOf(algorithm);
return getAlgorithm(alg, keyId);
}
-
- public Algorithm getAlgorithm(SignatureAlgorithm algorithm, String keyId) throws SigningKeyNotFoundException {
+
+ public Algorithm getAlgorithm(String algorithm, byte[] key) {
+ SignatureAlgorithm alg = SignatureAlgorithm.valueOf(algorithm);
+ return getHSAlgorithm(alg, key);
+ }
+
+ public Algorithm getHSAlgorithm(SignatureAlgorithm algorithm, byte[] key) {
+ switch (algorithm) {
+ case HS256:
+ return Algorithm.HMAC256(key);
+ case HS384:
+ return Algorithm.HMAC384(key);
+ case HS512:
+ return Algorithm.HMAC512(key);
+ default:
+ throw new NotSupportedException("Algorithm not supported.");
+ }
+ }
+
+ public Algorithm getAlgorithm(SignatureAlgorithm algorithm, String keyId) throws SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
switch (algorithm) {
case ES256:
return getES256Algorithm(keyId);
@@ -90,29 +109,29 @@ private Algorithm getES512Algorithm(String keyId) {
return Algorithm.ECDSA512(keyProvider);
}
- private byte[] getSigningKey(String keyId) throws SigningKeyNotFoundException {
+ private byte[] getSigningKey(String keyId) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
Integer id;
try {
id = Integer.parseInt(keyId);
} catch (NumberFormatException e) {
throw new SigningKeyNotFoundException("Signing key not found. The key id should be integer.");
}
- JwtSigningKey signingKey = jwtSigningKeyFacade.find(id);
+ DecryptedSigningKey signingKey = signingKeyEncryptionService.getSigningKey(id);
if (signingKey == null) {
throw new SigningKeyNotFoundException("Signing key not found.");
}
- return Base64.getDecoder().decode(signingKey.getSecret());
+ return signingKey.getDecryptedSecret();
}
- private Algorithm getHS256Algorithm(String keyId) throws SigningKeyNotFoundException {
+ private Algorithm getHS256Algorithm(String keyId) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return Algorithm.HMAC256(getSigningKey(keyId));
}
- private Algorithm getHS384Algorithm(String keyId) throws SigningKeyNotFoundException {
+ private Algorithm getHS384Algorithm(String keyId) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return Algorithm.HMAC384(getSigningKey(keyId));
}
- private Algorithm getHS512Algorithm(String keyId) throws SigningKeyNotFoundException {
+ private Algorithm getHS512Algorithm(String keyId) throws SigningKeyNotFoundException, SigningKeyEncryptionException {
return Algorithm.HMAC512(getSigningKey(keyId));
}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/DecryptedSigningKey.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/DecryptedSigningKey.java
new file mode 100644
index 0000000000..2325aaf99c
--- /dev/null
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/DecryptedSigningKey.java
@@ -0,0 +1,71 @@
+/*
+ * This file is part of Hopsworks
+ * Copyright (C) 2020, Logical Clocks AB. All rights reserved
+ *
+ * Hopsworks is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program.
+ * If not, see .
+ */
+package io.hops.hopsworks.jwt;
+
+import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
+
+import java.util.Base64;
+import java.util.Date;
+
+public class DecryptedSigningKey {
+ private Integer id;
+ private String name;
+ private Date createdOn;
+ private byte[] decryptedSecret;
+
+ public DecryptedSigningKey(JwtSigningKey jwtSigningKey, byte[] decryptedSecret) {
+ this.id = jwtSigningKey.getId();
+ this.name = jwtSigningKey.getName();
+ this.createdOn = jwtSigningKey.getCreatedOn();
+ this.decryptedSecret = decryptedSecret;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getCreatedOn() {
+ return createdOn;
+ }
+
+ public void setCreatedOn(Date createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public byte[] getDecryptedSecret() {
+ return decryptedSecret;
+ }
+
+ public void setDecryptedSecret(byte[] decryptedSecret) {
+ this.decryptedSecret = decryptedSecret;
+ }
+
+ public String getDecryptedSecretBase64Encoded() {
+ return Base64.getEncoder().encodeToString(decryptedSecret);
+ }
+}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java
index 3167633125..bdb62242d7 100644
--- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/JWTController.java
@@ -49,6 +49,7 @@
import io.hops.hopsworks.jwt.exception.InvalidationException;
import io.hops.hopsworks.jwt.exception.JWTException;
import io.hops.hopsworks.jwt.exception.NotRenewableException;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
import io.hops.hopsworks.jwt.exception.VerificationException;
@@ -72,6 +73,8 @@ public class JWTController {
private AlgorithmFactory algorithmFactory;
@EJB
private JwtSigningKeyFacade jwtSigningKeyFacade;
+ @EJB
+ private SigningKeyEncryptionService signingKeyEncryptionService;
/**
* Create a jwt.
@@ -80,7 +83,8 @@ public class JWTController {
* @return three Base64-URL strings separated by dots
* @throws io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException
*/
- public String createToken(JsonWebToken jwt, Map claims) throws SigningKeyNotFoundException {
+ public String createToken(JsonWebToken jwt, Map claims) throws SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
return createToken(jwt.getKeyId(), jwt.getIssuer(), jwt.getAudience().toArray(new String[0]), jwt.
getExpiresAt(), jwt.getNotBefore(), jwt.getSubject(), claims, jwt.getAlgorithm());
}
@@ -100,8 +104,8 @@ public String createToken(JsonWebToken jwt, Map claims) throws S
* @throws io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException
*/
public String createToken(String keyId, String issuer, String[] audience, Date expiresAt, Date notBefore,
- String subject, Map claims, SignatureAlgorithm algorithm) throws
- SigningKeyNotFoundException {
+ String subject, Map claims, SignatureAlgorithm algorithm) throws SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
JWTCreator.Builder jwtBuilder = JWT.create()
.withKeyId(keyId)
.withIssuer(issuer)
@@ -170,16 +174,17 @@ private JWTCreator.Builder addClaims(JWTCreator.Builder jwtCreator, Map claims, SignatureAlgorithm algorithm)
- throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException {
- JwtSigningKey signingKey;
+ Date notBefore, String subject, Map claims, SignatureAlgorithm algorithm)
+ throws NoSuchAlgorithmException, SigningKeyNotFoundException, DuplicateSigningKeyException,
+ SigningKeyEncryptionException {
+ Integer id;
if (createNewKey) {
- signingKey = createNewSigningKey(keyName, algorithm);
+ id = createNewSigningKey(keyName, algorithm).getId();
} else {
- signingKey = getOrCreateSigningKey(keyName, algorithm);
+ id = getOrCreateSigningKey(keyName, algorithm).getId();
}
- return createToken(signingKey.getId().toString(), issuer, audience, expiresAt, notBefore, subject,
+ return createToken(id.toString(), issuer, audience, expiresAt, notBefore, subject,
claims, algorithm);
}
@@ -265,7 +270,8 @@ public DecodedJWT decodeToken(String token) {
* @throws SigningKeyNotFoundException
* @throws VerificationException
*/
- public DecodedJWT verifyToken(String token, String issuer) throws SigningKeyNotFoundException, VerificationException {
+ public DecodedJWT verifyToken(String token, String issuer) throws SigningKeyNotFoundException, VerificationException,
+ SigningKeyEncryptionException {
DecodedJWT jwt = JWT.decode(token);
issuer = issuer == null || issuer.isEmpty() ? jwt.getIssuer() : issuer;
int expLeeway = getExpLeewayClaim(jwt);
@@ -287,7 +293,7 @@ public DecodedJWT verifyToken(String token, String issuer) throws SigningKeyNotF
* @throws InvalidationException
*/
public DecodedJWT verifyOneTimeToken(String token, String issuer) throws SigningKeyNotFoundException,
- VerificationException, InvalidationException {
+ VerificationException, InvalidationException, SigningKeyEncryptionException {
DecodedJWT jwt = verifyToken(token, issuer);
invalidateJWT(jwt.getId(), jwt.getExpiresAt(), getExpLeewayClaim(jwt));
return jwt;
@@ -305,7 +311,7 @@ public DecodedJWT verifyOneTimeToken(String token, String issuer) throws Signing
* @throws VerificationException
*/
public DecodedJWT verifyToken(String token, String issuer, Set audiences, Set roles) throws
- SigningKeyNotFoundException, VerificationException {
+ SigningKeyNotFoundException, VerificationException, SigningKeyEncryptionException {
JsonWebToken jwt = new JsonWebToken(JWT.decode(token));
issuer = issuer == null || issuer.isEmpty() ? jwt.getIssuer() : issuer;
DecodedJWT djwt = verifyToken(token, issuer, jwt.getExpLeeway(), algorithmFactory.getAlgorithm(jwt));
@@ -380,7 +386,7 @@ private boolean isTokenInvalidated(String id) {
* @throws InvalidationException
*/
public String autoRenewToken(String token) throws SigningKeyNotFoundException,
- NotRenewableException, InvalidationException {
+ NotRenewableException, InvalidationException, SigningKeyEncryptionException {
DecodedJWT jwt = verifyTokenForRenewal(token);
boolean isRenewable = getRenewableClaim(jwt);
if (!isRenewable) {
@@ -407,9 +413,8 @@ public String autoRenewToken(String token) throws SigningKeyNotFoundException,
return renewedToken;
}
- public String renewToken(String token, Date newExp, Date notBefore, boolean invalidate,
- Map claims)
- throws SigningKeyNotFoundException, NotRenewableException, InvalidationException {
+ public String renewToken(String token, Date newExp, Date notBefore, boolean invalidate, Map claims)
+ throws SigningKeyNotFoundException, NotRenewableException, InvalidationException, SigningKeyEncryptionException {
return renewToken(token, newExp, notBefore, invalidate, claims, false);
}
@@ -427,8 +432,8 @@ public String renewToken(String token, Date newExp, Date notBefore, boolean inva
* @throws InvalidationException
*/
public String renewToken(String token, Date newExp, Date notBefore, boolean invalidate,
- Map claims, boolean force)
- throws SigningKeyNotFoundException, NotRenewableException, InvalidationException {
+ Map claims, boolean force) throws SigningKeyNotFoundException, NotRenewableException,
+ InvalidationException, SigningKeyEncryptionException {
DecodedJWT jwt = verifyTokenForRenewal(token);
if (!force) {
Date currentTime = new Date();
@@ -502,7 +507,7 @@ public void invalidateServiceToken(String serviceToken2invalidate, String defaul
Claim signingKeyID = serviceJWT2invalidate.getClaim(Constants.SERVICE_JWT_RENEWAL_KEY_ID);
if (signingKeyID != null && !signingKeyID.isNull()) {
// Do not use Claim.asInt, it returns null
- JwtSigningKey signingKey = findSigningKeyById(Integer.parseInt(signingKeyID.asString()));
+ JwtSigningKey signingKey = jwtSigningKeyFacade.find(Integer.parseInt(signingKeyID.asString()));
if (signingKey != null && defaultJWTSigningKeyName != null) {
if (!defaultJWTSigningKeyName.equals(signingKey.getName())
&& !ONE_TIME_JWT_SIGNING_KEY_NAME.equals(signingKey.getName())) {
@@ -518,8 +523,8 @@ public String getSignKeyID(String token) {
}
public String[] generateOneTimeTokens4ServiceJWTRenewal(JsonWebToken jwtSpecs, Map claims,
- String defaultJWTSigningKeyName)
- throws NoSuchAlgorithmException, SigningKeyNotFoundException {
+ String defaultJWTSigningKeyName) throws NoSuchAlgorithmException, SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
String[] renewalTokens = new String[5];
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(Constants.ONE_TIME_JWT_SIGNATURE_ALGORITHM);
String[] audienceArray = jwtSpecs.getAudience().toArray(new String[1]);
@@ -591,7 +596,8 @@ public Map addDefaultClaimsIfMissing(Map userCla
return userClaims;
}
- private DecodedJWT verifyTokenForRenewal(String token) throws SigningKeyNotFoundException, NotRenewableException {
+ private DecodedJWT verifyTokenForRenewal(String token) throws SigningKeyNotFoundException, NotRenewableException,
+ SigningKeyEncryptionException {
DecodedJWT jwt;
try {
jwt = verifyToken(token, null);
@@ -659,8 +665,9 @@ public String generateJti() {
* @return
* @throws NoSuchAlgorithmException
*/
- public JwtSigningKey getOrCreateSigningKey(String keyName, SignatureAlgorithm alg) throws NoSuchAlgorithmException {
- return jwtSigningKeyFacade.getOrCreateSigningKey(keyName, alg);
+ public DecryptedSigningKey getOrCreateSigningKey(String keyName, SignatureAlgorithm alg)
+ throws NoSuchAlgorithmException, SigningKeyEncryptionException {
+ return signingKeyEncryptionService.getOrCreateSigningKey(keyName, alg);
}
/**
@@ -672,9 +679,9 @@ public JwtSigningKey getOrCreateSigningKey(String keyName, SignatureAlgorithm al
* @throws NoSuchAlgorithmException
* @throws io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException
*/
- public JwtSigningKey createNewSigningKey(String keyName, SignatureAlgorithm alg) throws NoSuchAlgorithmException,
- DuplicateSigningKeyException {
- return jwtSigningKeyFacade.createNewSigningKey(keyName, alg);
+ public DecryptedSigningKey createNewSigningKey(String keyName, SignatureAlgorithm alg)
+ throws NoSuchAlgorithmException, DuplicateSigningKeyException, SigningKeyEncryptionException {
+ return signingKeyEncryptionService.createSigningKey(keyName, alg);
}
/**
@@ -683,11 +690,9 @@ public JwtSigningKey createNewSigningKey(String keyName, SignatureAlgorithm alg)
* @param keyName a unique name given to signing key when created.
*/
public void deleteSigningKey(String keyName) {
- jwtSigningKeyFacade.remove(keyName);
- }
-
- public JwtSigningKey findSigningKeyById(Integer id) {
- return jwtSigningKeyFacade.find(id);
+ JwtSigningKey jsk = jwtSigningKeyFacade.findByName(keyName);
+ signingKeyEncryptionService.removeFromCache(jsk);
+ jwtSigningKeyFacade.remove(jsk);
}
/**
@@ -716,8 +721,9 @@ public boolean markOldSigningKeys() {
removeMarkedKeys();//remove if there is an old marked but not deleted.
jwtSigningKeyFacade.renameSigningKey(jwtSigningKey, Constants.OLD_ONE_TIME_JWT_SIGNING_KEY_NAME);
try {
- jwtSigningKeyFacade.getOrCreateSigningKey(Constants.ONE_TIME_JWT_SIGNING_KEY_NAME, SignatureAlgorithm.HS256);
- } catch (NoSuchAlgorithmException ex) {
+ signingKeyEncryptionService.getOrCreateSigningKey(Constants.ONE_TIME_JWT_SIGNING_KEY_NAME,
+ SignatureAlgorithm.HS256);
+ } catch (NoSuchAlgorithmException | SigningKeyEncryptionException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
return true;
@@ -728,6 +734,7 @@ public boolean markOldSigningKeys() {
public void removeMarkedKeys() {
JwtSigningKey jwtSigningKey = jwtSigningKeyFacade.findByName(Constants.OLD_ONE_TIME_JWT_SIGNING_KEY_NAME);
if (jwtSigningKey != null) {
+ signingKeyEncryptionService.removeFromCache(jwtSigningKey);
jwtSigningKeyFacade.remove(jwtSigningKey);
}
}
@@ -739,8 +746,9 @@ public void removeMarkedKeys() {
* @return
* @throws NoSuchAlgorithmException
*/
- public String getSigningKeyForELK(SignatureAlgorithm alg) throws NoSuchAlgorithmException {
- return getOrCreateSigningKey(Constants.ELK_SIGNING_KEY_NAME, alg).getSecret();
+ public String getSigningKeyForELK(SignatureAlgorithm alg) throws NoSuchAlgorithmException,
+ SigningKeyEncryptionException {
+ return getOrCreateSigningKey(Constants.ELK_SIGNING_KEY_NAME, alg).getDecryptedSecretBase64Encoded();
}
/**
@@ -750,10 +758,9 @@ public String getSigningKeyForELK(SignatureAlgorithm alg) throws NoSuchAlgorithm
* @throws NoSuchAlgorithmException
* @throws SigningKeyNotFoundException
*/
- public String createTokenForELK(String subjectName, String issuer,
- Map claims, Date expiresAt, SignatureAlgorithm alg)
- throws DuplicateSigningKeyException, NoSuchAlgorithmException,
- SigningKeyNotFoundException {
+ public String createTokenForELK(String subjectName, String issuer, Map claims, Date expiresAt,
+ SignatureAlgorithm alg) throws DuplicateSigningKeyException, NoSuchAlgorithmException, SigningKeyNotFoundException,
+ SigningKeyEncryptionException {
return createToken(Constants.ELK_SIGNING_KEY_NAME,
false, issuer, null, expiresAt, null, subjectName.toLowerCase(),
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyEncryptionService.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyEncryptionService.java
new file mode 100644
index 0000000000..5fb76d5cf5
--- /dev/null
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyEncryptionService.java
@@ -0,0 +1,224 @@
+/*
+ * This file is part of Hopsworks
+ * Copyright (C) 2020, Logical Clocks AB. All rights reserved
+ *
+ * Hopsworks is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program.
+ * If not, see .
+ */
+package io.hops.hopsworks.jwt;
+
+import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionDescriptor;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionService;
+import io.hops.hopsworks.jwt.dao.JwtSigningKeyFacade;
+import io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
+import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
+import io.hops.hopsworks.security.password.MasterPasswordService;
+
+import javax.ejb.EJB;
+import javax.ejb.Singleton;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Singleton
+public class SigningKeyEncryptionService {
+ private final static Logger LOGGER = Logger.getLogger(SigningKeyEncryptionService.class.getName());
+
+ private ConcurrentHashMap signingKeys = new ConcurrentHashMap<>();
+ @EJB
+ private JwtSigningKeyFacade jwtSigningKeyFacade;
+ @EJB
+ private SigningKeyGenerator signingKeyGenerator;
+ @EJB
+ private SymmetricEncryptionService symmetricEncryptionService;
+ @EJB
+ private MasterPasswordService masterPasswordService;
+
+ public SigningKeyEncryptionService() {
+ }
+
+ //for test
+ public SigningKeyEncryptionService(JwtSigningKeyFacade jwtSigningKeyFacade, SigningKeyGenerator signingKeyGenerator
+ , SymmetricEncryptionService symmetricEncryptionService, MasterPasswordService masterPasswordService) {
+ this.jwtSigningKeyFacade = jwtSigningKeyFacade;
+ this.signingKeyGenerator = signingKeyGenerator;
+ this.symmetricEncryptionService = symmetricEncryptionService;
+ this.masterPasswordService = masterPasswordService;
+ }
+
+ /**
+ * Clears decrypted signing keys from the cache.
+ */
+ public void invalidateCache() {
+ if (signingKeys != null && !signingKeys.isEmpty()) {
+ signingKeys.clear();
+ LOGGER.log(Level.INFO, "Decrypted signing key cache cleared.");
+ }
+ }
+
+ public DecryptedSigningKey removeFromCache(JwtSigningKey jwtSigningKey) {
+ return signingKeys.remove(jwtSigningKey.getId());
+ }
+
+ private DecryptedSigningKey decryptAndSave(JwtSigningKey jwtSigningKey) throws SigningKeyEncryptionException {
+ byte[] decryptedSecret = decrypt(jwtSigningKey.getSecret());
+ return save(decryptedSecret, jwtSigningKey);
+ }
+
+ private DecryptedSigningKey save(byte[] decryptedSecret, JwtSigningKey jwtSigningKey) {
+ DecryptedSigningKey decryptedSigningKey = new DecryptedSigningKey(jwtSigningKey, decryptedSecret);
+ signingKeys.put(jwtSigningKey.getId(), decryptedSigningKey);
+ return decryptedSigningKey;
+ }
+
+ /**
+ * Get DecryptedSigningKey from cache if it exists else gets it from database
+ * @param id
+ * @return
+ */
+ public DecryptedSigningKey getSigningKey(Integer id) throws SigningKeyEncryptionException,
+ SigningKeyNotFoundException {
+ DecryptedSigningKey decryptedSigningKey = signingKeys.get(id);
+ if (decryptedSigningKey != null) {
+ return decryptedSigningKey;
+ }
+ return getSigningKeyFromDb(id);
+ }
+
+ public DecryptedSigningKey getSigningKey(JwtSigningKey jwtSigningKey) throws SigningKeyEncryptionException {
+ DecryptedSigningKey decryptedSigningKey = signingKeys.get(jwtSigningKey.getId());
+ if (decryptedSigningKey != null) {
+ return decryptedSigningKey;
+ }
+ return decryptAndSave(jwtSigningKey);
+ }
+
+ /**
+ * Get SigningKey from database and decrypt.
+ * @param id
+ * @return
+ */
+ public DecryptedSigningKey getSigningKeyFromDb(Integer id) throws SigningKeyEncryptionException,
+ SigningKeyNotFoundException {
+ JwtSigningKey jwtSigningKey = jwtSigningKeyFacade.find(id);
+ if (jwtSigningKey == null) {
+ throw new SigningKeyNotFoundException("Signing key not found.");
+ }
+ return decryptAndSave(jwtSigningKey);
+ }
+
+ /**
+ * Create new signing key and saves it encrypted with the master password.
+ * @param keyName
+ * @param alg
+ * @return
+ * @throws DuplicateSigningKeyException
+ * @throws NoSuchAlgorithmException
+ */
+ public DecryptedSigningKey createSigningKey(String keyName, SignatureAlgorithm alg)
+ throws DuplicateSigningKeyException, NoSuchAlgorithmException, SigningKeyEncryptionException {
+ JwtSigningKey signingKey = jwtSigningKeyFacade.findByName(keyName);
+ if (signingKey != null) {
+ throw new DuplicateSigningKeyException("A signing key with the same name already exists.");
+ }
+ return createNewSigningKey(keyName, alg);
+ }
+
+ /**
+ * Gets signing key by name if it exists else creates a new and saves it encrypted.
+ * @param keyName
+ * @param alg
+ * @return
+ * @throws NoSuchAlgorithmException
+ */
+ public DecryptedSigningKey getOrCreateSigningKey(String keyName, SignatureAlgorithm alg)
+ throws NoSuchAlgorithmException, SigningKeyEncryptionException {
+ JwtSigningKey signingKey = jwtSigningKeyFacade.findByName(keyName);
+ if (signingKey == null) {
+ return createNewSigningKey(keyName, alg);
+ }
+ return getSigningKey(signingKey);
+ }
+
+ private DecryptedSigningKey createNewSigningKey(String keyName, SignatureAlgorithm alg)
+ throws NoSuchAlgorithmException, SigningKeyEncryptionException {
+ byte[] signingKey = signingKeyGenerator.getSigningKey(alg.getJcaName());
+ String encryptedSecret = encrypt(signingKey);
+ JwtSigningKey jwtSigningKey = new JwtSigningKey(encryptedSecret, keyName);
+ jwtSigningKeyFacade.persist(jwtSigningKey);
+ jwtSigningKey = jwtSigningKeyFacade.findByName(keyName);
+ return save(signingKey, jwtSigningKey);
+ }
+
+ private byte[] decrypt(String secret) throws SigningKeyEncryptionException {
+ try {
+ //get master password and decrypt
+ String password = masterPasswordService.getMasterEncryptionPassword();
+ return decrypt(secret, password);
+ } catch (IOException e) {
+ throw new SigningKeyEncryptionException("Failed to decrypt signing key. ", e);
+ }
+ }
+
+ public byte[] decrypt(String secret, String masterPassword) throws SigningKeyEncryptionException {
+ try {
+ // [salt(64),iv(12),payload)]
+ byte[][] split = symmetricEncryptionService.splitPayloadFromCryptoPrimitives(Base64.getDecoder().decode(secret));
+ SymmetricEncryptionDescriptor descriptor = new SymmetricEncryptionDescriptor.Builder()
+ .setPassword(masterPassword)
+ .setSalt(split[0])
+ .setIV(split[1])
+ .setInput(split[2])
+ .build();
+ descriptor = symmetricEncryptionService.decrypt(descriptor);
+ return descriptor.getOutput();
+ } catch (GeneralSecurityException e) {
+ throw new SigningKeyEncryptionException("Failed to decrypt signing key. ", e);
+ }
+ }
+
+ private String encrypt(byte[] secret) throws SigningKeyEncryptionException {
+ try {
+ String password = masterPasswordService.getMasterEncryptionPassword();
+ return encrypt(secret, password);
+ } catch (IOException e) {
+ throw new SigningKeyEncryptionException("Failed to encrypt signing key. ", e);
+ }
+ }
+
+ public String encrypt(byte[] secret, String masterPassword) throws SigningKeyEncryptionException {
+ try {
+ SymmetricEncryptionDescriptor descriptor = new SymmetricEncryptionDescriptor.Builder()
+ .setInput(secret)
+ .setPassword(masterPassword)
+ .build();
+ descriptor = symmetricEncryptionService.encrypt(descriptor);
+ byte[] encrypted =
+ symmetricEncryptionService.mergePayloadWithCryptoPrimitives(descriptor.getSalt(), descriptor.getIv(),
+ descriptor.getOutput());
+ return Base64.getEncoder().encodeToString(encrypted);
+ } catch (GeneralSecurityException e) {
+ throw new SigningKeyEncryptionException("Failed to encrypt signing key. ", e);
+ }
+ }
+
+ public String getNewEncryptedSecret(String secret, String oldMasterPassword, String newMasterPassword)
+ throws SigningKeyEncryptionException {
+ return encrypt(decrypt(secret, oldMasterPassword), newMasterPassword);
+ }
+
+}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyGenerator.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyGenerator.java
index 7e34043ae4..b750a8edce 100644
--- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyGenerator.java
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyGenerator.java
@@ -16,7 +16,6 @@
package io.hops.hopsworks.jwt;
import java.security.NoSuchAlgorithmException;
-import java.util.Base64;
import javax.crypto.KeyGenerator;
import javax.ejb.Singleton;
@@ -31,12 +30,10 @@ public class SigningKeyGenerator {
* @return base64Encoded string
* @throws NoSuchAlgorithmException
*/
- public String getSigningKey(String algorithm) throws NoSuchAlgorithmException {
+ public byte[] getSigningKey(String algorithm) throws NoSuchAlgorithmException {
if (keyGenerator == null || !keyGenerator.getAlgorithm().equals(algorithm)) {
keyGenerator = KeyGenerator.getInstance(algorithm);
}
- byte[] keyBytes = keyGenerator.generateKey().getEncoded();
- String base64Encoded = Base64.getEncoder().encodeToString(keyBytes);
- return base64Encoded;
+ return keyGenerator.generateKey().getEncoded();
}
}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyMasterPasswordHandler.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyMasterPasswordHandler.java
new file mode 100644
index 0000000000..e7b11b82c5
--- /dev/null
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/SigningKeyMasterPasswordHandler.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of Hopsworks
+ * Copyright (C) 2020, Logical Clocks AB. All rights reserved
+ *
+ * Hopsworks is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program.
+ * If not, see .
+ */
+package io.hops.hopsworks.jwt;
+
+import io.hops.hopsworks.exceptions.EncryptionMasterPasswordException;
+import io.hops.hopsworks.jwt.dao.JwtSigningKeyFacade;
+import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
+import io.hops.hopsworks.security.password.MasterPasswordChangeResult;
+import io.hops.hopsworks.security.password.MasterPasswordHandler;
+
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Stateless
+@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+public class SigningKeyMasterPasswordHandler implements MasterPasswordHandler {
+ private final Logger LOGGER = Logger.getLogger(SigningKeyMasterPasswordHandler.class.getName());
+ @EJB
+ private JwtSigningKeyFacade jwtSigningKeyFacade;
+ @EJB
+ private SigningKeyEncryptionService signingKeyEncryptionService;
+
+ @Override
+ public void pre() {
+
+ }
+
+ @Override
+ public MasterPasswordChangeResult perform(String oldPassword, String newPassword) {
+ StringBuilder successLog = new StringBuilder();
+ //signingKeyEncryptionService.invalidateCache();
+ Map items2rollback = new HashMap<>();
+ for (JwtSigningKey key : jwtSigningKeyFacade.findAll()) {
+ try {
+ String newSecret = signingKeyEncryptionService.getNewEncryptedSecret(key.getSecret(), oldPassword, newPassword);
+ key.setSecret(newSecret);
+ jwtSigningKeyFacade.merge(key);
+ items2rollback.putIfAbsent(key.getId(), key.getSecret());
+ successLog.append("Updated jwt signing key: ").append(key.getName()).append("\n");
+ } catch (Exception ex) {
+ String errorMsg = "Something went wrong while updating master encryption password for jwt signing key. Update" +
+ " failed on key: " + key.getName();
+ LOGGER.log(Level.SEVERE, errorMsg + " rolling back...", ex);
+ return new MasterPasswordChangeResult<>(items2rollback, new EncryptionMasterPasswordException(errorMsg));
+ }
+ }
+ return new MasterPasswordChangeResult<>(successLog, items2rollback, null);
+ }
+
+ @Override
+ public void rollback(MasterPasswordChangeResult result) {
+
+ }
+
+ @Override
+ public void post() {
+
+ }
+}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/dao/JwtSigningKeyFacade.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/dao/JwtSigningKeyFacade.java
index 1d5824b92b..be6e3dfeb5 100644
--- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/dao/JwtSigningKeyFacade.java
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/dao/JwtSigningKeyFacade.java
@@ -15,28 +15,20 @@
*/
package io.hops.hopsworks.jwt.dao;
-import io.hops.hopsworks.jwt.SignatureAlgorithm;
-import io.hops.hopsworks.jwt.SigningKeyGenerator;
-import io.hops.hopsworks.jwt.exception.DuplicateSigningKeyException;
import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
-import java.security.NoSuchAlgorithmException;
-import java.util.List;
-import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
+import java.util.List;
@Stateless
public class JwtSigningKeyFacade {
@PersistenceContext(unitName = "kthfsPU")
private EntityManager em;
-
- @EJB
- private SigningKeyGenerator signingKeyGenerator;
public JwtSigningKey find(Integer id) {
return em.find(JwtSigningKey.class, id);
@@ -66,40 +58,13 @@ public void renameSigningKey (JwtSigningKey signingKey, String newName) {
signingKey.setName(newName);
em.merge(signingKey);
}
-
- public JwtSigningKey getOrCreateSigningKey(String keyName, SignatureAlgorithm alg) throws NoSuchAlgorithmException {
- JwtSigningKey signingKey = this.findByName(keyName);
- if (signingKey == null) {
- signingKey = this.createSigningKey(keyName, alg);
- }
- return signingKey;
- }
-
- public JwtSigningKey createNewSigningKey(String keyName, SignatureAlgorithm alg) throws NoSuchAlgorithmException,
- DuplicateSigningKeyException {
- JwtSigningKey signingKey = this.findByName(keyName);
- if (signingKey != null) {
- // throwing DuplicateSigningKeyException to catch parent exception (JWTException) and
- throw new DuplicateSigningKeyException("A signing key with the same name already exists.");
- }
- return this.createSigningKey(keyName, alg);
- }
-
- private JwtSigningKey createSigningKey(String keyName, SignatureAlgorithm alg) throws NoSuchAlgorithmException {
- JwtSigningKey signingKey;
- String base64Encoded = signingKeyGenerator.getSigningKey(alg.getJcaName());
- signingKey = new JwtSigningKey(base64Encoded, keyName);
- persist(signingKey);
- JwtSigningKey newSigningKey = findByName(keyName);
- return newSigningKey;
- }
- public void persist(JwtSigningKey invalidJwt) {
- em.persist(invalidJwt);
+ public void persist(JwtSigningKey jwtSigningKey) {
+ em.persist(jwtSigningKey);
}
- public JwtSigningKey merge(JwtSigningKey invalidJwt) {
- return em.merge(invalidJwt);
+ public JwtSigningKey merge(JwtSigningKey jwtSigningKey) {
+ return em.merge(jwtSigningKey);
}
public void remove(JwtSigningKey jwtSigningKey) {
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/exception/SigningKeyEncryptionException.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/exception/SigningKeyEncryptionException.java
new file mode 100644
index 0000000000..c7cc7d21b5
--- /dev/null
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/exception/SigningKeyEncryptionException.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of Hopsworks
+ * Copyright (C) 2020, Logical Clocks AB. All rights reserved
+ *
+ * Hopsworks is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program.
+ * If not, see .
+ */
+package io.hops.hopsworks.jwt.exception;
+
+public class SigningKeyEncryptionException extends JWTException {
+ public SigningKeyEncryptionException() {
+ }
+
+ public SigningKeyEncryptionException(String message) {
+ super(message);
+ }
+
+ public SigningKeyEncryptionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SigningKeyEncryptionException(Throwable cause) {
+ super(cause);
+ }
+
+ public SigningKeyEncryptionException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/filter/JWTFilter.java b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/filter/JWTFilter.java
index a2f872f9fa..0401a3a376 100644
--- a/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/filter/JWTFilter.java
+++ b/hopsworks-jwt/src/main/java/io/hops/hopsworks/jwt/filter/JWTFilter.java
@@ -37,6 +37,8 @@
import static io.hops.hopsworks.jwt.Constants.EXPIRY_LEEWAY;
import static io.hops.hopsworks.jwt.Constants.ROLES;
import static io.hops.hopsworks.jwt.Constants.WWW_AUTHENTICATE_VALUE;
+
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
import io.hops.hopsworks.jwt.exception.SigningKeyNotFoundException;
public abstract class JWTFilter implements ContainerRequestFilter {
@@ -133,7 +135,8 @@ private boolean intersect(Collection list1, Collection list2) {
return !set1.isEmpty();
}
- public abstract Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException;
+ public abstract Algorithm getAlgorithm(DecodedJWT jwt) throws SigningKeyNotFoundException,
+ SigningKeyEncryptionException;
public abstract Set allowedRoles();
diff --git a/hopsworks-jwt/src/test/java/io/hops/hopsworks/jwt/TestSigningKeyEncryptionService.java b/hopsworks-jwt/src/test/java/io/hops/hopsworks/jwt/TestSigningKeyEncryptionService.java
new file mode 100644
index 0000000000..69f42832f9
--- /dev/null
+++ b/hopsworks-jwt/src/test/java/io/hops/hopsworks/jwt/TestSigningKeyEncryptionService.java
@@ -0,0 +1,127 @@
+/*
+ * This file is part of Hopsworks
+ * Copyright (C) 2020, Logical Clocks AB. All rights reserved
+ *
+ * Hopsworks is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program.
+ * If not, see .
+ */
+package io.hops.hopsworks.jwt;
+
+import io.hops.hopsworks.jwt.dao.JwtSigningKeyFacade;
+import io.hops.hopsworks.jwt.exception.SigningKeyEncryptionException;
+import io.hops.hopsworks.persistence.entity.jwt.JwtSigningKey;
+import io.hops.hopsworks.security.encryption.SymmetricEncryptionService;
+import io.hops.hopsworks.security.password.MasterPasswordService;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Random;
+
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+
+public class TestSigningKeyEncryptionService {
+
+ private SigningKeyEncryptionService signingKeyEncryptionService;
+ private JwtSigningKeyFacade jwtSigningKeyFacade;
+ private SymmetricEncryptionService symmetricEncryptionService;
+ private SigningKeyGenerator signingKeyGenerator;
+ private MasterPasswordService masterPasswordService;
+
+ byte[] signingKey1;
+ byte[] signingKey2;
+ private String masterPassword;
+ private Random random = new Random();
+
+ @Before
+ public void beforeTest() throws IOException, SigningKeyEncryptionException, NoSuchAlgorithmException {
+
+ jwtSigningKeyFacade = Mockito.mock(JwtSigningKeyFacade.class);
+ masterPasswordService = Mockito.mock(MasterPasswordService.class);
+ signingKeyGenerator = new SigningKeyGenerator();
+ symmetricEncryptionService = new SymmetricEncryptionService();
+ symmetricEncryptionService.init();
+ Mockito.when(masterPasswordService.getMasterEncryptionPassword()).thenReturn("master password");
+
+ signingKeyEncryptionService = new SigningKeyEncryptionService(jwtSigningKeyFacade, signingKeyGenerator,
+ symmetricEncryptionService, masterPasswordService);
+
+ masterPassword = masterPasswordService.getMasterEncryptionPassword();
+ signingKey1 = signingKeyGenerator.getSigningKey(SignatureAlgorithm.HS512.getJcaName());
+
+ createJwtSigningKey(1, "api", signingKey1);
+
+ signingKey2 = signingKeyGenerator.getSigningKey(SignatureAlgorithm.HS512.getJcaName());
+
+ createJwtSigningKey(2, "service", signingKey2);
+
+ }
+
+ @Test
+ public void testGetDecryptedSigningKey() throws Exception {
+ DecryptedSigningKey decryptedSigningKey = signingKeyEncryptionService.getSigningKey(1);
+ assertTrue (Arrays.equals(decryptedSigningKey.getDecryptedSecret(), signingKey1));
+
+ decryptedSigningKey = signingKeyEncryptionService.getSigningKey(2);
+ assertTrue (Arrays.equals(decryptedSigningKey.getDecryptedSecret(), signingKey2));
+ }
+
+ @Test
+ public void testGetDecryptedSigningKeyByName() throws Exception {
+ DecryptedSigningKey decryptedSigningKey =
+ signingKeyEncryptionService.getOrCreateSigningKey("api", SignatureAlgorithm.HS512);
+ assertTrue(Arrays.equals(decryptedSigningKey.getDecryptedSecret(), signingKey1));
+
+ decryptedSigningKey = signingKeyEncryptionService.getOrCreateSigningKey("service", SignatureAlgorithm.HS512);
+ assertTrue(Arrays.equals(decryptedSigningKey.getDecryptedSecret(), signingKey2));
+ }
+
+ @Test
+ public void testCreateSigningKey() throws Exception {
+ Integer id = whenPersist();
+ DecryptedSigningKey decryptedSigningKey = signingKeyEncryptionService.getOrCreateSigningKey("oneTime",
+ SignatureAlgorithm.HS512);
+ DecryptedSigningKey decryptedSigningKey1 = signingKeyEncryptionService.getSigningKey(id);
+ assertTrue (Arrays.equals(decryptedSigningKey.getDecryptedSecret(), decryptedSigningKey1.getDecryptedSecret()));
+ }
+
+ private Integer whenPersist() {
+ Integer id = random.ints(3,10000).findFirst().getAsInt();
+ doAnswer(new Answer