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