From 13c5d42388db46ac71bce2aef38e8a4ac6f66adf Mon Sep 17 00:00:00 2001 From: Larbi Gharib <larbi.gharib@savoirfairelinux.com> Date: Fri, 1 Apr 2022 14:03:38 -0400 Subject: [PATCH] user: allow to refresh certificate GitLab: #81 Change-Id: I694f7690856018899c6adb92939f4f8aaa37081f --- .../java/net/jami/datastore/dao/UserDao.java | 31 ++++--- .../net/jami/datastore/main/DataStore.java | 16 ++++ .../main/java/net/jami/jams/ca/JamsCA.java | 5 ++ .../ca/workers/csr/CertificateWorker.java | 4 + .../workers/csr/builders/DeviceBuilder.java | 5 +- .../ca/workers/csr/builders/UserBuilder.java | 40 +++++++++- .../workers/csr/builders/UserBuilderTest.java | 80 +++++++++++++++++++ .../cryptoengineapi/CertificateAuthority.java | 1 + 8 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 jams-ca/src/test/java/net/jami/jams/ca/workers/csr/builders/UserBuilderTest.java diff --git a/datastore/src/main/java/net/jami/datastore/dao/UserDao.java b/datastore/src/main/java/net/jami/datastore/dao/UserDao.java index b2e03085..4f3723d8 100644 --- a/datastore/src/main/java/net/jami/datastore/dao/UserDao.java +++ b/datastore/src/main/java/net/jami/datastore/dao/UserDao.java @@ -59,17 +59,31 @@ public class UserDao extends AbstractDao<User> { @Override public boolean updateObject(StatementList update, StatementList constraints) { + if(update.getStatements().get(0).getColumn() == "certificate"){ + + SQLConnection connection = DataStore.connectionPool.getConnection(); + try { + String certificate = update.getStatements().get(0).getValue(); + String user = constraints.getStatements().get(0).getValue(); + PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE users SET certificate = ? WHERE username = ?"); + ps.setString(1, certificate); + ps.setString(2, user); + return ps.executeUpdate() != 0; + } catch (Exception e) { + log.error("An error has occurred while trying to update a user certificate: " + e.toString()); + return false; + } finally { + DataStore.connectionPool.returnConnection(connection); + } + + } + String pw = update.getStatements().get(0).getValue(); String salt = ""; if (update.getStatements().size() > 1) salt = update.getStatements().get(1).getValue(); String user = constraints.getStatements().get(0).getValue(); -// String pwReset = "false"; -// -// if (update.getStatements().size() > 1) { -// pwReset = update.getStatements().get(1).getValue(); -// } SQLConnection connection = DataStore.connectionPool.getConnection(); @@ -78,12 +92,7 @@ public class UserDao extends AbstractDao<User> { ps.setString(1, pw); ps.setString(2, salt); ps.setString(3, user); -// ps.executeUpdate(); -// -// ps = connection.getConnection().prepareStatement("UPDATE users SET needsPasswordReset = ? WHERE username = ?"); -// ps.setString(1, pwReset); -// -// ps.setString(2, user); + return ps.executeUpdate() != 0; } catch (Exception e) { log.error("An error has occurred while trying to update a user: " + e.toString()); diff --git a/datastore/src/main/java/net/jami/datastore/main/DataStore.java b/datastore/src/main/java/net/jami/datastore/main/DataStore.java index ae4c229c..8aad8af8 100644 --- a/datastore/src/main/java/net/jami/datastore/main/DataStore.java +++ b/datastore/src/main/java/net/jami/datastore/main/DataStore.java @@ -42,12 +42,16 @@ import net.jami.jams.common.dao.connectivity.ConnectionPool; import net.jami.jams.common.objects.user.User; import net.jami.jams.common.objects.user.UserGroupMapping; import net.jami.jams.common.objects.user.UserProfile; +import net.jami.jams.common.utils.X509Utils; + import org.flywaydb.core.Flyway; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import javax.swing.plaf.nimbus.State; + @Getter @Setter public class DataStore implements AuthenticationSource { @@ -161,6 +165,18 @@ public class DataStore implements AuthenticationSource { return userProfileDao.updateObject(update, null); } + public boolean updateUserCertificate(User user) { + + StatementList update = new StatementList(); + StatementList constraints = new StatementList(); + + update.addStatement(new StatementElement("certificate","=", X509Utils.getPEMStringFromCertificate(user.getCertificate()),"")); + + constraints.addStatement(new StatementElement("username","=", user.getUsername(),"")); + + return userDao.updateObject(update, constraints); + } + @Override public boolean authenticate(String username, String password) { StatementList statementList = new StatementList(); diff --git a/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java b/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java index 00fb29f0..317b1b0a 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java @@ -97,6 +97,11 @@ public class JamsCA implements CertificateAuthority { return CertificateWorker.getSignedCertificate(user); } + @Override + public User getRefreshedCertificate(User user) { + return CertificateWorker.getRefreshedCertificate(user); + } + @Override public Device getSignedCertificate(User user, Device device) { return CertificateWorker.getSignedCertificate(user, device); diff --git a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/CertificateWorker.java b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/CertificateWorker.java index 20211087..845ac793 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/CertificateWorker.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/CertificateWorker.java @@ -48,6 +48,10 @@ public class CertificateWorker { return UserBuilder.generateUser(user); } + public static User getRefreshedCertificate(User user){ + return UserBuilder.refreshUser(user); + } + public static Device getSignedCertificate(User user, Device device){ return DeviceBuilder.generateDevice(user,device); } diff --git a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/DeviceBuilder.java b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/DeviceBuilder.java index d5c470b4..8f54fdce 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/DeviceBuilder.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/DeviceBuilder.java @@ -52,11 +52,12 @@ public class DeviceBuilder { public static Device generateDevice(User user, Device device){ try { + long now = System.currentTimeMillis(); X509v3CertificateBuilder builder = new X509v3CertificateBuilder( new JcaX509CertificateHolder(user.getCertificate()).getSubject(), new BigInteger(256, new SecureRandom()), - new Date(System.currentTimeMillis() - SHIFT), - new Date(System.currentTimeMillis() + JamsCA.deviceLifetime), + new Date(now - SHIFT), + new Date(now + JamsCA.deviceLifetime), device.getCertificationRequest().getSubject(), device.getCertificationRequest().getSubjectPublicKeyInfo() ); diff --git a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/UserBuilder.java b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/UserBuilder.java index 6570601b..7da79d58 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/UserBuilder.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/workers/csr/builders/UserBuilder.java @@ -35,34 +35,66 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Date; import static net.jami.jams.ca.workers.csr.CertificateWorker.SHIFT; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.MessageDigestAlgorithms; + + @Slf4j public class UserBuilder { public static User generateUser(User user) { try { + long now = System.currentTimeMillis(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(4096); KeyPair keyPair = keyPairGenerator.generateKeyPair(); X509v3CertificateBuilder builder = new X509v3CertificateBuilder( new JcaX509CertificateHolder(JamsCA.CA.getCertificate()).getSubject(), - new BigInteger(256, new SecureRandom()), - new Date(System.currentTimeMillis() - SHIFT), - new Date(System.currentTimeMillis() + JamsCA.userLifetime), + new BigInteger(128, new SecureRandom()), + new Date(now - SHIFT), + new Date(now + JamsCA.userLifetime), new X500Name(user.getX509Fields().getDN()), SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()) ); + user.setPrivateKey(keyPair.getPrivate()); user.setCertificate(CertificateSigner.signCertificate(JamsCA.CA.getPrivateKey(), builder, ExtensionLibrary.userExtensions)); - + log.info("New user certificate: Not valid after: " + user.getCertificate().getNotAfter()); return user; } catch (Exception e) { log.error("Could not generate a user certificate: " + e.toString()); return null; } } + + public static User refreshUser(User user) { + return refreshUser(user, JamsCA.userLifetime); + } + + public static User refreshUser(User user, long userLifeTime) { + try { + long now = System.currentTimeMillis(); + X509v3CertificateBuilder builder = new X509v3CertificateBuilder( + new JcaX509CertificateHolder(JamsCA.CA.getCertificate()).getSubject(), + new BigInteger(128, new SecureRandom()), + new Date(now - SHIFT), + new Date(now + userLifeTime), + new X500Name(user.getX509Fields().getDN()), + new JcaX509CertificateHolder(user.getCertificate()).getSubjectPublicKeyInfo() + ); + user.setCertificate(CertificateSigner.signCertificate(JamsCA.CA.getPrivateKey(), builder, ExtensionLibrary.userExtensions)); + log.info("====> Refreshed user certificate: Not valid after: " + user.getCertificate().getNotAfter()); + + return user; + } catch (Exception e) { + log.error("Could not refresh user certificate: " + e.toString()); + return null; + } + } } diff --git a/jams-ca/src/test/java/net/jami/jams/ca/workers/csr/builders/UserBuilderTest.java b/jams-ca/src/test/java/net/jami/jams/ca/workers/csr/builders/UserBuilderTest.java new file mode 100644 index 00000000..468a9b4e --- /dev/null +++ b/jams-ca/src/test/java/net/jami/jams/ca/workers/csr/builders/UserBuilderTest.java @@ -0,0 +1,80 @@ +package net.jami.jams.ca.workers.csr.builders; + +import net.jami.jams.ca.JamsCA; +import lombok.extern.slf4j.Slf4j; + +import net.jami.jams.common.authentication.AuthenticationSourceType; +import net.jami.jams.common.objects.roots.X509Fields; +import net.jami.jams.common.objects.user.AccessLevel; +import net.jami.jams.common.objects.user.User; +import net.jami.jams.common.utils.X509Utils; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Base64; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Assertions; + +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +@Slf4j +class UserBuilderTest { + + + @Test + void generateUserCertificate() { + + User user = new User(); + user.setUsername("TestUser"); + user.setUserType(AuthenticationSourceType.LOCAL); + user.setX509Fields(new X509Fields()); + user.getX509Fields().setCommonName("TestUser's Personal Certificate"); + user = UserBuilder.generateUser(user); + + Assertions.assertNotNull(user.getCertificate(),"User Certificate was not generated!"); + } + + @Test + void refreshUserCertificate() { + + User user = new User(); + user.setUsername("TestUser"); + //user.setJamiId(""); + user.setUserType(AuthenticationSourceType.LOCAL); + user.setX509Fields(new X509Fields()); + user.getX509Fields().setCommonName("TestUser's Personal Certificate"); + user = UserBuilder.generateUser(user); + + Assertions.assertNotNull(user,"User was not generated!"); + + X509Certificate cert = user.getCertificate(); + Assertions.assertNotNull(cert,"User Certificate was not generated!"); + + User refreshedUser = UserBuilder.refreshUser(user, 465_000_000); + Assertions.assertNotNull(refreshedUser,"User was not generated!"); + X509Certificate new_cert = refreshedUser.getCertificate(); + + Assertions.assertArrayEquals(cert.getPublicKey().getEncoded(), new_cert.getPublicKey().getEncoded(), "PK is different"); + + Assertions.assertNotNull(new_cert,"User Certificate was not updated!"); + Assertions.assertEquals(user.getAddress(), refreshedUser.getAddress(), "User address is different"); + + Assertions.assertNotEquals(cert.getNotAfter(), new_cert.getNotAfter()); + try { + Assertions.assertEquals(new JcaX509CertificateHolder(cert).getSubjectPublicKeyInfo().getPublicKey(), + new JcaX509CertificateHolder(new_cert).getSubjectPublicKeyInfo().getPublicKey()); + } catch (Exception e) { + log.error("Error comparing two public keys information: ", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java index dfd933c1..a8de02ac 100644 --- a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java +++ b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java @@ -37,6 +37,7 @@ public interface CertificateAuthority { //Return a signed X509 certificate based on various constraints. void init(String settings, SystemAccount ca, SystemAccount ocsp); User getSignedCertificate(User user); + User getRefreshedCertificate(User user); Device getSignedCertificate(User user, Device device); SystemAccount getSignedCertificate(SystemAccount systemAccount); void revokeCertificate(RevocationRequest revocationRequest); -- GitLab