From 1d08cc98005de07c416207536bcae3592fbc7b73 Mon Sep 17 00:00:00 2001 From: INOPIAE Date: Mon, 15 Aug 2016 11:38:16 +0200 Subject: [PATCH 1/1] add: enable support to search for certificates fixes issue #108 Change-Id: I712e0f0a69279cf37c10aa03de282604ced713ac --- src/org/cacert/gigi/Gigi.java | 5 +- .../cacert/gigi/dbObjects/Certificate.java | 26 ++++ .../cacert/gigi/dbObjects/SupportedUser.java | 9 ++ .../gigi/output/TrustchainIterable.java | 31 ++++ .../account/certs/CertificateDisplay.templ | 136 +++++++++++++++-- .../pages/account/certs/Certificates.java | 143 ++++++++++++++---- .../account/certs/RevokeSingleCertForm.java | 44 ++++++ .../account/certs/RevokeSingleCertForm.templ | 1 + .../pages/admin/support/FindCertForm.java | 69 +++++++++ .../pages/admin/support/FindCertForm.templ | 23 +++ .../pages/admin/support/FindCertPage.java | 85 +++++++++++ .../pages/admin/support/FindCertPage.templ | 16 ++ 12 files changed, 546 insertions(+), 42 deletions(-) create mode 100644 src/org/cacert/gigi/output/TrustchainIterable.java create mode 100644 src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.java create mode 100644 src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.templ create mode 100644 src/org/cacert/gigi/pages/admin/support/FindCertForm.java create mode 100644 src/org/cacert/gigi/pages/admin/support/FindCertForm.templ create mode 100644 src/org/cacert/gigi/pages/admin/support/FindCertPage.java create mode 100644 src/org/cacert/gigi/pages/admin/support/FindCertPage.templ diff --git a/src/org/cacert/gigi/Gigi.java b/src/org/cacert/gigi/Gigi.java index 1108aa04..7740f803 100644 --- a/src/org/cacert/gigi/Gigi.java +++ b/src/org/cacert/gigi/Gigi.java @@ -58,6 +58,7 @@ import org.cacert.gigi.pages.account.certs.Certificates; import org.cacert.gigi.pages.account.domain.DomainOverview; import org.cacert.gigi.pages.account.mail.MailOverview; import org.cacert.gigi.pages.admin.TTPAdminPage; +import org.cacert.gigi.pages.admin.support.FindCertPage; import org.cacert.gigi.pages.admin.support.FindUserByDomainPage; import org.cacert.gigi.pages.admin.support.FindUserByEmailPage; import org.cacert.gigi.pages.admin.support.SupportEnterTicketPage; @@ -138,7 +139,7 @@ public final class Gigi extends HttpServlet { putPage("/secure", new TestSecure(), null); putPage(Verify.PATH, new Verify(), null); - putPage(Certificates.PATH + "/*", new Certificates(), "Certificates"); + putPage(Certificates.PATH + "/*", new Certificates(false), "Certificates"); putPage(RegisterPage.PATH, new RegisterPage(), "SomeCA.org"); putPage(CertificateAdd.PATH, new CertificateAdd(), "Certificates"); putPage(MailOverview.DEFAULT_PATH, new MailOverview(), "Certificates"); @@ -155,6 +156,7 @@ public final class Gigi extends HttpServlet { putPage(SupportEnterTicketPage.PATH, new SupportEnterTicketPage(), "Support Console"); putPage(FindUserByEmailPage.PATH, new FindUserByEmailPage(), "Support Console"); putPage(FindUserByDomainPage.PATH, new FindUserByDomainPage(), "Support Console"); + putPage(FindCertPage.PATH, new FindCertPage(), "Support Console"); putPage(SupportUserDetailsPage.PATH + "*", new SupportUserDetailsPage(), null); putPage(ChangePasswordPage.PATH, new ChangePasswordPage(), "My Account"); @@ -171,6 +173,7 @@ public final class Gigi extends HttpServlet { putPage(MyDetails.PATH, new MyDetails(), "My Account"); putPage(UserTrainings.SUPPORT_PATH, new UserTrainings(true), null); putPage(Points.SUPPORT_PATH, new Points(true), null); + putPage(Certificates.SUPPORT_PATH + "/*", new Certificates(true), null); putPage(PasswordResetPage.PATH, new PasswordResetPage(), null); putPage(LogoutPage.PATH, new LogoutPage(), null); diff --git a/src/org/cacert/gigi/dbObjects/Certificate.java b/src/org/cacert/gigi/dbObjects/Certificate.java index 37ba66b7..b0c85e96 100644 --- a/src/org/cacert/gigi/dbObjects/Certificate.java +++ b/src/org/cacert/gigi/dbObjects/Certificate.java @@ -467,4 +467,30 @@ public class Certificate implements IdCachable { return res.next(); } } + + public static Certificate[] findBySerialPattern(String serial) { + try (GigiPreparedStatement prep = new GigiPreparedStatement("SELECT `id` FROM `certs` WHERE `serial` LIKE ? GROUP BY `id` LIMIT 100", true)) { + prep.setString(1, serial); + return fetchCertsToArray(prep); + } + } + + public static Certificate[] findBySANPattern(String request, SANType type) { + try (GigiPreparedStatement prep = new GigiPreparedStatement("SELECT `certId` FROM `subjectAlternativeNames` WHERE `contents` LIKE ? and `type`=?::`SANType` GROUP BY `certId` LIMIT 100", true)) { + prep.setString(1, request); + prep.setString(2, type.getOpensslName()); + return fetchCertsToArray(prep); + } + } + + private static Certificate[] fetchCertsToArray(GigiPreparedStatement prep) { + GigiResultSet res = prep.executeQuery(); + res.last(); + Certificate[] certs = new Certificate[res.getRow()]; + res.beforeFirst(); + for (int i = 0; res.next(); i++) { + certs[i] = Certificate.getById(res.getInt(1)); + } + return certs; + } } diff --git a/src/org/cacert/gigi/dbObjects/SupportedUser.java b/src/org/cacert/gigi/dbObjects/SupportedUser.java index 47c17e83..a663215a 100644 --- a/src/org/cacert/gigi/dbObjects/SupportedUser.java +++ b/src/org/cacert/gigi/dbObjects/SupportedUser.java @@ -47,6 +47,15 @@ public class SupportedUser { } } + public void revokeCertificate(Certificate cert) throws GigiApiException { + + // TODO Check for open jobs! + if (cert.getStatus() == CertificateStatus.ISSUED) { + writeSELog("SE Revoke certificate"); + cert.revoke().waitFor(60000); + } + } + private void writeSELog(String type) throws GigiApiException { if (ticket == null) { throw new GigiApiException("No ticket set!"); diff --git a/src/org/cacert/gigi/output/TrustchainIterable.java b/src/org/cacert/gigi/output/TrustchainIterable.java new file mode 100644 index 00000000..2cee5fd5 --- /dev/null +++ b/src/org/cacert/gigi/output/TrustchainIterable.java @@ -0,0 +1,31 @@ +package org.cacert.gigi.output; + +import java.util.Map; + +import org.cacert.gigi.dbObjects.CACertificate; +import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.template.IterableDataset; + +public class TrustchainIterable implements IterableDataset { + + CACertificate cert; + + public TrustchainIterable(CACertificate cert) { + this.cert = cert; + } + + @Override + public boolean next(Language l, Map vars) { + if (cert == null) { + return false; + } + vars.put("name", cert.getKeyname()); + vars.put("link", cert.getLink()); + if (cert.isSelfsigned()) { + cert = null; + return true; + } + cert = cert.getParent(); + return true; + } +} diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ b/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ index 0d559cdf..c77de8f3 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ +++ b/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ @@ -1,12 +1,124 @@ - - - -
-
-
-
-
-.
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
:
:
:
:
:
: + + + +
: + + + +
+
+
+
+
+ .
+
:
:
:
:
:
:
:
:
:
:
:
:
: + + + +
+ + + diff --git a/src/org/cacert/gigi/pages/account/certs/Certificates.java b/src/org/cacert/gigi/pages/account/certs/Certificates.java index 9d05db4f..45ca5637 100644 --- a/src/org/cacert/gigi/pages/account/certs/Certificates.java +++ b/src/org/cacert/gigi/pages/account/certs/Certificates.java @@ -4,22 +4,31 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.cacert.gigi.dbObjects.CACertificate; import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CertificateStatus; +import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName; +import org.cacert.gigi.dbObjects.CertificateOwner; +import org.cacert.gigi.dbObjects.Organisation; +import org.cacert.gigi.dbObjects.SupportedUser; +import org.cacert.gigi.dbObjects.User; import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.TrustchainIterable; import org.cacert.gigi.output.template.Form; import org.cacert.gigi.output.template.IterableDataset; import org.cacert.gigi.output.template.Template; import org.cacert.gigi.pages.HandlesMixedRequest; import org.cacert.gigi.pages.LoginPage; import org.cacert.gigi.pages.Page; +import org.cacert.gigi.util.AuthorizationContext; import org.cacert.gigi.util.CertExporter; import org.cacert.gigi.util.PEM; @@ -29,33 +38,13 @@ public class Certificates extends Page implements HandlesMixedRequest { public static final String PATH = "/account/certs"; - static class TrustchainIterable implements IterableDataset { + public static final String SUPPORT_PATH = "/support/certs"; - CACertificate cert; + private final boolean support; - public TrustchainIterable(CACertificate cert) { - this.cert = cert; - } - - @Override - public boolean next(Language l, Map vars) { - if (cert == null) { - return false; - } - vars.put("name", cert.getKeyname()); - vars.put("link", cert.getLink()); - if (cert.isSelfsigned()) { - cert = null; - return true; - } - cert = cert.getParent(); - return true; - } - - } - - public Certificates() { - super("Certificates"); + public Certificates(boolean support) { + super(support ? "Support Certificates" : "Certificates"); + this.support = support; } @Override @@ -113,11 +102,18 @@ public class Certificates extends Page implements HandlesMixedRequest { if (req.getQueryString() != null && !req.getQueryString().equals("") && !req.getQueryString().equals("withRevoked")) { return;// Block actions by get parameters. } + if (support && "revoke".equals(req.getParameter("action"))) { + if (Form.getForm(req, RevokeSingleCertForm.class).submitProtected(resp.getWriter(), req)) { + resp.sendRedirect(req.getPathInfo()); + return; + } + } if ( !req.getPathInfo().equals(PATH)) { resp.sendError(500); return; } Form.getForm(req, CertificateModificationForm.class).submit(resp.getWriter(), req); + doGet(req, resp); } @@ -130,15 +126,93 @@ public class Certificates extends Page implements HandlesMixedRequest { String serial = pi; Certificate c = Certificate.getBySerial(serial); - if (c == null || LoginPage.getAuthorizationContext(req).getTarget().getId() != c.getOwner().getId()) { + Language l = LoginPage.getLanguage(req); + + if ( !support && (c == null || LoginPage.getAuthorizationContext(req).getTarget().getId() != c.getOwner().getId())) { resp.sendError(404); return; } HashMap vars = new HashMap<>(); vars.put("serial", URLEncoder.encode(serial, "UTF-8")); - vars.put("trustchain", new TrustchainIterable(c.getParent())); + + CertificateStatus st = c.getStatus(); + + if (support) { + vars.put("support", "support"); + CertificateOwner user = c.getOwner(); + if (st == CertificateStatus.ISSUED) { + if (user instanceof User) { + vars.put("revokeForm", new RevokeSingleCertForm(req, c, new SupportedUser((User) user, getUser(req), LoginPage.getAuthorizationContext(req).getSupporterTicketId()))); + } + } + } + + CertificateOwner co = c.getOwner(); + int ownerId = co.getId(); + vars.put("certid", c.getStatus()); + if (co instanceof Organisation) { + vars.put("type", l.getTranslation("Organisation Acount")); + vars.put("name", Organisation.getById(ownerId).getName()); + vars.put("link", ""); // TODO + } else { + vars.put("type", l.getTranslation("Personal Account")); + vars.put("name", User.getById(ownerId).getPreferredName()); + vars.put("link", "/support/user/" + ownerId + "/"); + } + vars.put("status", c.getStatus()); + vars.put("DN", c.getDistinguishedName()); + vars.put("digest", c.getMessageDigest()); + vars.put("profile", c.getProfile().getVisibleName()); + vars.put("fingerprint", "TBD"); // TODO function needs to be + // implemented in Certificate.java try { - vars.put("cert", PEM.encode("CERTIFICATE", c.cert().getEncoded())); + + if (st == CertificateStatus.ISSUED || st == CertificateStatus.REVOKED) { + X509Certificate certx = c.cert(); + vars.put("issued", certx.getNotBefore()); + vars.put("expire", certx.getNotAfter()); + vars.put("cert", PEM.encode("CERTIFICATE", c.cert().getEncoded())); + } else { + vars.put("issued", l.getTranslation("N/A")); + vars.put("expire", l.getTranslation("N/A")); + vars.put("cert", l.getTranslation("N/A")); + } + if (st == CertificateStatus.REVOKED) { + vars.put("revoked", c.getRevocationDate()); + } else { + vars.put("revoked", l.getTranslation("N/A")); + } + if (st == CertificateStatus.ISSUED || st == CertificateStatus.REVOKED) { + vars.put("trustchain", new TrustchainIterable(c.getParent())); + try { + vars.put("cert", PEM.encode("CERTIFICATE", c.cert().getEncoded())); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + } + } else { + vars.put("trustchain", l.getTranslation("N/A")); + vars.put("cert", l.getTranslation("N/A")); + } + final List san = c.getSANs(); + vars.put("san", new IterableDataset() { + + int j = 0; + + @Override + public boolean next(Language l, Map vars) { + if (j == san.size()) { + return false; + } + vars.put("entry", san.get(j).getName() + (j < san.size() - 1 ? ", " : "")); + j++; + return true; + } + }); + if (c.isLoginEnabled()) { + vars.put("login", l.getTranslation("Yes")); + } else { + vars.put("login", l.getTranslation("No")); + } } catch (GeneralSecurityException e) { e.printStackTrace(); } @@ -151,4 +225,15 @@ public class Certificates extends Page implements HandlesMixedRequest { new CertificateModificationForm(req, req.getParameter("withRevoked") != null).output(out, getLanguage(req), vars); } + @Override + public boolean isPermitted(AuthorizationContext ac) { + if (ac == null) { + return false; + } + if (support) { + return ac.canSupport(); + } else { + return true; + } + } } diff --git a/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.java b/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.java new file mode 100644 index 00000000..5219081a --- /dev/null +++ b/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.java @@ -0,0 +1,44 @@ +package org.cacert.gigi.pages.account.certs; + +import java.io.PrintWriter; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.SupportedUser; +import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.template.Form; +import org.cacert.gigi.output.template.Template; + +public class RevokeSingleCertForm extends Form { + + private static final Template t = new Template(RevokeSingleCertForm.class.getResource("RevokeSingleCertForm.templ")); + + private Certificate c; + + private SupportedUser target; + + public RevokeSingleCertForm(HttpServletRequest hsr, Certificate c, SupportedUser target) { + super(hsr); + this.c = c; + this.target = target; + } + + @Override + public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException { + if (target != null) { + target.revokeCertificate(c); + } else { + c.revoke().waitFor(60000); + } + return true; + } + + @Override + protected void outputContent(PrintWriter out, Language l, Map vars) { + t.output(out, l, vars); + } + +} diff --git a/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.templ b/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.templ new file mode 100644 index 00000000..aaad4886 --- /dev/null +++ b/src/org/cacert/gigi/pages/account/certs/RevokeSingleCertForm.templ @@ -0,0 +1 @@ + diff --git a/src/org/cacert/gigi/pages/admin/support/FindCertForm.java b/src/org/cacert/gigi/pages/admin/support/FindCertForm.java new file mode 100644 index 00000000..07d9b929 --- /dev/null +++ b/src/org/cacert/gigi/pages/admin/support/FindCertForm.java @@ -0,0 +1,69 @@ +package org.cacert.gigi.pages.admin.support; + +import java.io.PrintWriter; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.SANType; +import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.template.Form; +import org.cacert.gigi.output.template.SprintfCommand; +import org.cacert.gigi.output.template.Template; + +public class FindCertForm extends Form { + + private static final Template t = new Template(FindCertForm.class.getResource("FindCertForm.templ")); + + private final String SERIAL = "serial"; + + private String certType = SERIAL; + + public Certificate certs[]; + + public FindCertForm(HttpServletRequest hsr) { + super(hsr); + } + + @Override + public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException { + this.certType = req.getParameter("certType"); + String request = req.getParameter("cert").trim(); + + if ( !SERIAL.equals(certType) && !SANType.EMAIL.getOpensslName().equals(certType) && !SANType.DNS.getOpensslName().equals(certType)) { + throw new GigiApiException("Invalid search type."); + } + + if (SERIAL.equals(certType)) { + certs = Certificate.findBySerialPattern(request); + if (certs.length <= 0) { + throw new GigiApiException(SprintfCommand.createSimple("No certificate found matching serial number {0}", request)); + } + } + + if (SANType.EMAIL.getOpensslName().equals(certType) || SANType.DNS.getOpensslName().equals(certType)) { + SANType stype = SANType.valueOf(certType.toUpperCase()); + certs = Certificate.findBySANPattern(request, stype); + if (certs.length <= 0) { + throw new GigiApiException(SprintfCommand.createSimple("No certificate found matching {0}", request)); + } + } + return true; + } + + @Override + protected void outputContent(PrintWriter out, Language l, Map vars) { + vars.put("serial", !SERIAL.equals(certType) ? "" : "checked"); + vars.put("email", !SANType.EMAIL.getOpensslName().equals(certType) ? "" : "checked"); + vars.put("dns", !SANType.DNS.getOpensslName().equals(certType) ? "" : "checked"); + + t.output(out, l, vars); + } + + public Certificate[] getCerts() { + return certs; + } + +} diff --git a/src/org/cacert/gigi/pages/admin/support/FindCertForm.templ b/src/org/cacert/gigi/pages/admin/support/FindCertForm.templ new file mode 100644 index 00000000..678bb63d --- /dev/null +++ b/src/org/cacert/gigi/pages/admin/support/FindCertForm.templ @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + +
: + > + > + > +
:
\ No newline at end of file diff --git a/src/org/cacert/gigi/pages/admin/support/FindCertPage.java b/src/org/cacert/gigi/pages/admin/support/FindCertPage.java new file mode 100644 index 00000000..fb9d14f7 --- /dev/null +++ b/src/org/cacert/gigi/pages/admin/support/FindCertPage.java @@ -0,0 +1,85 @@ +package org.cacert.gigi.pages.admin.support; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName; +import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.ArrayIterable; +import org.cacert.gigi.output.template.Form; +import org.cacert.gigi.output.template.IterableDataset; +import org.cacert.gigi.output.template.SprintfCommand; +import org.cacert.gigi.pages.LoginPage; +import org.cacert.gigi.pages.Page; +import org.cacert.gigi.pages.account.certs.Certificates; +import org.cacert.gigi.util.AuthorizationContext; + +public class FindCertPage extends Page { + + public static final String PATH = "/support/find/certs"; + + public FindCertPage() { + super("Find Certificate"); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + new FindCertForm(req).output(resp.getWriter(), Page.getLanguage(req), new HashMap()); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + FindCertForm form = Form.getForm(req, FindCertForm.class); + if (form.submitProtected(resp.getWriter(), req)) { + final Certificate[] certs = form.getCerts(); + if (certs.length == 1) { + resp.sendRedirect(Certificates.SUPPORT_PATH + certs[0].getSerial() + "/"); + } else { + HashMap vars = new HashMap(); + Language l = LoginPage.getLanguage(req); + if (certs.length >= 100) { + vars.put("limit", l.getTranslation("100 or more entries available, only the first 100 are displayed.")); + } else { + vars.put("limit", SprintfCommand.createSimple("{0} entries found", certs.length)); + } + vars.put("certtable", new ArrayIterable(certs) { + + @Override + public void apply(Certificate t, Language l, Map vars) { + vars.put("id", t.getId()); + vars.put("serial", t.getSerial()); + + final List san = t.getSANs(); + vars.put("san", new IterableDataset() { + + int j = 0; + + @Override + public boolean next(Language l, Map vars) { + if (j == san.size()) { + return false; + } + vars.put("entry", san.get(j).getName() + (j < san.size() - 1 ? ", " : "")); + j++; + return true; + } + + }); + } + }); + getDefaultTemplate().output(resp.getWriter(), getLanguage(req), vars); + } + } + } + + @Override + public boolean isPermitted(AuthorizationContext ac) { + return ac != null && ac.canSupport(); + } +} diff --git a/src/org/cacert/gigi/pages/admin/support/FindCertPage.templ b/src/org/cacert/gigi/pages/admin/support/FindCertPage.templ new file mode 100644 index 00000000..d8f61c11 --- /dev/null +++ b/src/org/cacert/gigi/pages/admin/support/FindCertPage.templ @@ -0,0 +1,16 @@ +

:

+ + + + + + + + + + +
Id
+ + + +
-- 2.39.2