add: user client certificate must have a verification within <=24 months
authorINOPIAE <m.maengel@inopiae.de>
Sun, 29 Sep 2019 18:36:55 +0000 (20:36 +0200)
committerINOPIAE <m.maengel@inopiae.de>
Sun, 26 Jan 2020 15:08:16 +0000 (16:08 +0100)
To ensure that the name in a client certificate is valid the last
verification of that name must not be older than 24 months.

fixes issue #134

Change-Id: I9e1434f5114c8d3ad13eb06cc48f90314fa994ad

src/club/wpia/gigi/dbObjects/Name.java
src/club/wpia/gigi/dbObjects/User.java
src/club/wpia/gigi/pages/account/certs/CertificateRequest.java
tests/club/wpia/gigi/dbObjects/TestUser.java
tests/club/wpia/gigi/dbObjects/TestVerifyName.java
tests/club/wpia/gigi/pages/account/TestCertificateRequest.java
tests/club/wpia/gigi/testUtils/ClientBusinessTest.java
tests/club/wpia/gigi/testUtils/ConfiguredTest.java
util-testing/club/wpia/gigi/pages/Manager.java
util-testing/club/wpia/gigi/pages/Manager.templ

index fbcf10e..ce8e7e2 100644 (file)
@@ -1,6 +1,9 @@
 package club.wpia.gigi.dbObjects;
 
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.Map;
 
 import club.wpia.gigi.GigiApiException;
@@ -11,6 +14,7 @@ import club.wpia.gigi.dbObjects.NamePart.NamePartType;
 import club.wpia.gigi.localisation.Language;
 import club.wpia.gigi.output.template.Outputable;
 import club.wpia.gigi.util.HTMLEncoder;
+import club.wpia.gigi.util.TimeConditions;
 
 public class Name implements Outputable, IdCachable {
 
@@ -512,4 +516,26 @@ public class Name implements Outputable, IdCachable {
         }
         return initals.toString();
     }
+
+    public boolean isValidVerification() {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT COUNT(id) FROM `notary` WHERE `to` = ? AND `deleted` IS NULL AND (`expire` IS NULL OR `expire` > CURRENT_TIMESTAMP) AND `date` > ?")) {
+            query.setInt(1, getId());
+            query.setString(2, date);
+            GigiResultSet rs = query.executeQuery();
+
+            if (rs.next()) {
+                if (rs.getInt(1) > 0) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
 }
index 714bfc3..3d88aa6 100644 (file)
@@ -338,6 +338,15 @@ public class User extends CertificateOwner {
         return false;
     }
 
+    public boolean isValidNameVerification(String name) {
+        for (Name n : getNames()) {
+            if (n.matches(name) && n.isValidVerification()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void updateDefaultEmail(EmailAddress newMail) throws GigiApiException {
         for (EmailAddress email : getEmails()) {
             if (email.getAddress().equals(newMail.getAddress())) {
index d9d090c..28a5b09 100644 (file)
@@ -443,7 +443,7 @@ public class CertificateRequest {
                 subject.put("OU", ou);
             }
         }
-        System.out.println(subject);
+
         if ( !error.isEmpty()) {
             throw error;
         }
@@ -493,7 +493,11 @@ public class CertificateRequest {
             User u = (User) ctx.getTarget();
             if (name != null && u.isValidName(name)) {
                 if (realIsOK) {
-                    verifiedCN = name;
+                    if (u.isValidNameVerification(name)) {
+                        verifiedCN = name;
+                    } else {
+                        error.mergeInto(new GigiApiException(SprintfCommand.createSimple("The entered name needs a valid verification within the last {0} months.", TimeConditions.getInstance().getVerificationMonths())));
+                    }
                 } else {
                     error.mergeInto(new GigiApiException("Your real name is not allowed in this certificate."));
                     if (defaultIsOK) {
index e7aa70c..7d7b6db 100644 (file)
@@ -134,4 +134,19 @@ public class TestUser extends ClientBusinessTest {
         assertThat(result, ArrayContains.contains(CoreMatchers.equalTo(type)));
     }
 
+    @Test
+    public void testValidVerification() throws GigiApiException {
+        User u0 = User.getById(createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD));
+        assertFalse(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        add100Points(u0.getId());
+        assertTrue(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        setVerificationDateToPast(u0.getPreferredName());
+        assertFalse(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        add100Points(u0.getId());
+        assertTrue(u0.isValidNameVerification(u0.getPreferredName().toString()));
+    }
+
 }
index 7f3af26..7ad2c7f 100644 (file)
@@ -5,13 +5,9 @@ import static org.junit.Assert.*;
 import org.junit.Test;
 
 import club.wpia.gigi.GigiApiException;
-import club.wpia.gigi.dbObjects.Country;
-import club.wpia.gigi.dbObjects.Name;
-import club.wpia.gigi.dbObjects.NamePart;
-import club.wpia.gigi.dbObjects.User;
-import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.dbObjects.Country.CountryCodeType;
 import club.wpia.gigi.dbObjects.NamePart.NamePartType;
+import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.testUtils.ClientBusinessTest;
 import club.wpia.gigi.util.Notary;
 
@@ -35,4 +31,20 @@ public class TestVerifyName extends ClientBusinessTest {
         assertEquals(10, n4.getVerificationPoints());
         assertEquals(10, u.getMaxVerifyPoints());
     }
+
+    @Test
+    public void testValidVerification() throws GigiApiException {
+        User u0 = User.getById(createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD));
+        assertFalse(u0.getPreferredName().isValidVerification());
+
+        add100Points(u0.getId());
+        assertTrue(u0.getPreferredName().isValidVerification());
+
+        setVerificationDateToPast(u0.getPreferredName());
+        assertFalse(u0.getPreferredName().isValidVerification());
+
+        add100Points(u0.getId());
+        assertTrue(u0.getPreferredName().isValidVerification());
+    }
+
 }
index bb6c575..372f2bf 100644 (file)
@@ -15,6 +15,7 @@ import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.EmailAddress;
 import club.wpia.gigi.dbObjects.Group;
 import club.wpia.gigi.pages.account.certs.CertificateRequest;
+import club.wpia.gigi.testUtils.ClientBusinessTest;
 import club.wpia.gigi.testUtils.ClientTest;
 import club.wpia.gigi.util.AuthorizationContext;
 import club.wpia.gigi.util.TimeConditions;
@@ -135,4 +136,19 @@ public class TestCertificateRequest extends ClientTest {
         }
 
     }
+
+    @Test
+    public void testVerificationInPast() throws IOException, GeneralSecurityException, GigiApiException {
+
+        ClientBusinessTest.setVerificationDateToPast(u.getPreferredName());
+        try {
+            CertificateRequest cr = new CertificateRequest(ac, generatePEMCSR(kp, "CN=a ab"));
+            cr.update(u.getPreferredName().toString(), "SHA512", "client-a", null, null, "email:" + email);
+            cr.draft();
+            fail();
+        } catch (GigiApiException e) {
+            assertThat(e.getMessage(), containsString("The entered name needs a valid verification within the last"));
+        }
+
+    }
 }
index 023d55e..3a61569 100644 (file)
@@ -1,8 +1,14 @@
 package club.wpia.gigi.testUtils;
 
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
 import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.User;
+import club.wpia.gigi.util.TimeConditions;
 
 public class ClientBusinessTest extends BusinessTest {
 
@@ -21,4 +27,18 @@ public class ClientBusinessTest extends BusinessTest {
             throw new Error(e);
         }
     }
+
+    public static void setVerificationDateToPast(Name name) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `notary` SET `date`=? WHERE `to`=? AND `date`>?");
+        ps.setString(1, date);
+        ps.setInt(2, name.getId());
+        ps.setString(3, date);
+        ps.execute();
+        ps.close();
+    }
 }
index 322af0b..43be01d 100644 (file)
@@ -360,9 +360,11 @@ public abstract class ConfiguredTest {
     }
 
     public static void add100Points(int uid) {
-        try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100'")) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100', `date`=?")) {
             ps2.setInt(1, uid);
             ps2.setInt(2, User.getById(uid).getPreferredName().getId());
+            ps2.setString(3, sdf.format(new Date(System.currentTimeMillis())));
             ps2.execute();
         }
     }
index f68d307..0ca6119 100644 (file)
@@ -48,6 +48,7 @@ import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.dbObjects.DomainPingType;
 import club.wpia.gigi.dbObjects.EmailAddress;
 import club.wpia.gigi.dbObjects.Group;
+import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.NamePart;
 import club.wpia.gigi.dbObjects.NamePart.NamePartType;
 import club.wpia.gigi.dbObjects.User;
@@ -447,6 +448,17 @@ public class Manager extends Page {
 
             resp.getWriter().println("User has been verified " + verifications + " times." + info);
 
+        } else if (req.getParameter("verifyexpire") != null) {
+            String mail = req.getParameter("verifyEmail");
+            User byEmail = User.getByEmail(mail);
+            if (byEmail == null) {
+                resp.getWriter().println("User not found.");
+                return;
+            } else {
+                setVerificationDateToPast(byEmail.getPreferredName());
+            }
+
+            resp.getWriter().println("Verification set to time past the limit.");
         } else if (req.getParameter("letverify") != null) {
             String mail = req.getParameter("letverifyEmail");
             User byEmail = User.getByEmail(mail);
@@ -628,4 +640,18 @@ public class Manager extends Page {
 
         form.output(resp.getWriter(), getLanguage(req), vars);
     }
+
+    private static void setVerificationDateToPast(Name name) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `notary` SET `date`=? WHERE `to`=? AND `date`>?");
+        ps.setString(1, date);
+        ps.setInt(2, name.getId());
+        ps.setString(3, date);
+        ps.execute();
+        ps.close();
+    }
 }
index 89627ae..f6e1519 100644 (file)
@@ -70,6 +70,7 @@ Email: <input type="text" name="verifyEmail"/>
 </td><td>
 Verification Points to issue to preferred name: </br>
 <input type="text" name="verificationPoints" value="100"/> <input type="submit" value="Add Points" name="verify"/>
+<input type="submit" value="Set Verification date past limit" name="verifyexpire"/>
 </td></tr>
 
 <tr><td>