From 9136e3e03b6881b32aada896be3241e46cbd33d9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Thu, 12 Feb 2015 03:34:01 +0100 Subject: [PATCH] ADD: (most simple) api for issuing certificates first version. --- src/org/cacert/gigi/Launcher.java | 1 + src/org/cacert/gigi/api/GigiAPI.java | 53 +++++++++++++++++++ src/org/cacert/gigi/pages/LoginPage.java | 39 +++++++++++--- .../account/certs/CertificateIssueForm.java | 4 +- .../account/certs/CertificateRequest.java | 14 +++-- tests/org/cacert/gigi/api/IssueCert.java | 48 +++++++++++++++++ .../pages/account/TestCertificateAdd.java | 2 +- .../cacert/gigi/testUtils/ManagedTest.java | 1 + 8 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 tests/org/cacert/gigi/api/IssueCert.java diff --git a/src/org/cacert/gigi/Launcher.java b/src/org/cacert/gigi/Launcher.java index a7ba8a17..a3000c87 100644 --- a/src/org/cacert/gigi/Launcher.java +++ b/src/org/cacert/gigi/Launcher.java @@ -129,6 +129,7 @@ public class Launcher { secureContextFactory.setNeedClientAuth(false); final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static"); final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api"); + apiContextFactory.setWantClientAuth(true); try { secureContextFactory.start(); staticContextFactory.start(); diff --git a/src/org/cacert/gigi/api/GigiAPI.java b/src/org/cacert/gigi/api/GigiAPI.java index 209ffe56..ae8aee35 100644 --- a/src/org/cacert/gigi/api/GigiAPI.java +++ b/src/org/cacert/gigi/api/GigiAPI.java @@ -2,6 +2,8 @@ package org.cacert.gigi.api; import java.io.IOException; import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; @@ -9,6 +11,15 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CertificateStatus; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.pages.LoginPage; +import org.cacert.gigi.pages.account.certs.CertificateRequest; +import org.cacert.gigi.util.Job; +import org.cacert.gigi.util.PEM; + public class GigiAPI extends HttpServlet { @Override @@ -27,6 +38,48 @@ public class GigiAPI extends HttpServlet { strB.append(buffer, 0, len); } System.out.println(strB); + return; + } + X509Certificate cert = LoginPage.getCertificateFromRequest(req); + if (cert == null) { + resp.sendError(403, "Error, cert authing required."); + return; + } + String serial = LoginPage.extractSerialFormCert(cert); + User u = LoginPage.fetchUserBySerial(serial); + + if (pi.equals("/account/certs/new")) { + + if ( !req.getMethod().equals("POST")) { + resp.sendError(500, "Error, POST required."); + return; + } + if (req.getQueryString() != null) { + resp.sendError(500, "Error, no query String allowed."); + return; + } + String csr = req.getParameter("csr"); + if (csr == null) { + resp.sendError(500, "Error, no CSR found"); + return; + } + try { + CertificateRequest cr = new CertificateRequest(u, csr); + Certificate result = cr.draft(); + Job job = result.issue(null, "2y"); + job.waitFor(60000); + if (result.getStatus() != CertificateStatus.ISSUED) { + resp.sendError(510, "Error, issuing timed out"); + return; + } + resp.getWriter().println(PEM.encode("CERTIFICATE", result.cert().getEncoded())); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + } catch (GigiApiException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } } } } diff --git a/src/org/cacert/gigi/pages/LoginPage.java b/src/org/cacert/gigi/pages/LoginPage.java index ed01ceb6..91b6b1b7 100644 --- a/src/org/cacert/gigi/pages/LoginPage.java +++ b/src/org/cacert/gigi/pages/LoginPage.java @@ -58,9 +58,9 @@ public class LoginPage extends Page { public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException { String redir = (String) req.getSession().getAttribute(LOGIN_RETURNPATH); if (req.getSession().getAttribute("loggedin") == null) { - X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); - if (cert != null && cert[0] != null) { - tryAuthWithCertificate(req, cert[0]); + X509Certificate cert = getCertificateFromRequest(req); + if (cert != null) { + tryAuthWithCertificate(req, cert); } if (req.getMethod().equals("POST")) { try { @@ -118,17 +118,40 @@ public class LoginPage extends Page { } private void tryAuthWithCertificate(HttpServletRequest req, X509Certificate x509Certificate) { - String serial = x509Certificate.getSerialNumber().toString(16).toUpperCase(); + String serial = extractSerialFormCert(x509Certificate); + User user = fetchUserBySerial(serial); + if (user == null) { + return; + } + loginSession(req, user); + req.getSession().setAttribute(CERT_SERIAL, serial); + req.getSession().setAttribute(CERT_ISSUER, x509Certificate.getIssuerDN()); + req.getSession().setAttribute(LOGIN_METHOD, "Certificate"); + } + + public static String extractSerialFormCert(X509Certificate x509Certificate) { + return x509Certificate.getSerialNumber().toString(16).toUpperCase(); + } + + public static User fetchUserBySerial(String serial) { GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `certs` WHERE `serial`=? AND `disablelogin`='0' AND `revoked` is NULL"); ps.setString(1, serial); GigiResultSet rs = ps.executeQuery(); + User user = null; if (rs.next()) { - loginSession(req, User.getById(rs.getInt(1))); - req.getSession().setAttribute(CERT_SERIAL, serial); - req.getSession().setAttribute(CERT_ISSUER, x509Certificate.getIssuerDN()); - req.getSession().setAttribute(LOGIN_METHOD, "Certificate"); + user = User.getById(rs.getInt(1)); } rs.close(); + return user; + } + + public static X509Certificate getCertificateFromRequest(HttpServletRequest req) { + X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); + X509Certificate uc = null; + if (cert != null && cert[0] != null) { + uc = cert[0]; + } + return uc; } private static final Group LOGIN_BLOCKED = Group.getByString("blockedlogin"); diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java index 6f099195..8a3107f0 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java @@ -83,16 +83,18 @@ public class CertificateIssueForm extends Form { if (req.getParameter("CCA") == null) { error.mergeInto(new GigiApiException("You need to accept the CCA.")); } + Certificate result = null; try { result = cr.draft(); } catch (GigiApiException e) { error.mergeInto(e); } - if ( !error.isEmpty()) { + if ( !error.isEmpty() || result == null) { error.format(out, Page.getLanguage(req)); return false; } result.issue(issueDate.getFrom(), issueDate.getTo()).waitFor(60000); + this.result = result; return true; } else { throw new GigiApiException("Error no action."); diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java index fe46244f..63c129e8 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java @@ -10,6 +10,7 @@ import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; @@ -173,11 +174,7 @@ public class CertificateRequest { } } - GigiApiException error = new GigiApiException(); - this.SANs = verifySANs(error, false, SANs); - if ( !error.isEmpty()) { - throw error; - } + this.SANs = SANs; pk = parsed.getSubjectPublicKeyInfo(); String sign = getSignatureAlgorithm(data); guessDigest(sign); @@ -197,7 +194,7 @@ public class CertificateRequest { pk = parsed.getPubkey(); String sign = getSignatureAlgorithm(data); guessDigest(sign); - + this.SANs = new HashSet<>(); this.csr = "SPKAC=" + cleanedSPKAC; this.csrType = CSRType.SPKAC; @@ -314,9 +311,9 @@ public class CertificateRequest { return true; } - private Set verifySANs(GigiApiException error, boolean server, TreeSet parseSANBox) { + private Set verifySANs(GigiApiException error, boolean server, Set sANs2) { Set filteredSANs = new LinkedHashSet<>(); - for (SubjectAlternateName san : parseSANBox) { + for (SubjectAlternateName san : sANs2) { if (san.getType() == SANType.DNS) { if (u.isValidDomain(san.getName()) && server) { if (pDNS == null) { @@ -374,6 +371,7 @@ public class CertificateRequest { subject.put("EMAIL", pMail); } } + this.SANs = verifySANs(error, server, SANs); if (org != null) { subject.put("O", org.getName()); subject.put("C", org.getState()); diff --git a/tests/org/cacert/gigi/api/IssueCert.java b/tests/org/cacert/gigi/api/IssueCert.java new file mode 100644 index 00000000..6a6f320c --- /dev/null +++ b/tests/org/cacert/gigi/api/IssueCert.java @@ -0,0 +1,48 @@ +package org.cacert.gigi.api; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; + +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CSRType; +import org.cacert.gigi.dbObjects.CertificateProfile; +import org.cacert.gigi.testUtils.ClientTest; +import org.cacert.gigi.testUtils.IOUtils; +import org.junit.Test; + +import sun.security.x509.X500Name; + +public class IssueCert extends ClientTest { + + @Test + public void testIssueCert() throws Exception { + KeyPair kp = generateKeypair(); + String key1 = generatePEMCSR(kp, "CN=testmail@example.com"); + Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key1, CSRType.CSR, CertificateProfile.getById(1)); + final PrivateKey pk = kp.getPrivate(); + c.issue(null, "2y").waitFor(60000); + final X509Certificate ce = c.cert(); + HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "api.") + "/account/certs/new").openConnection(); + authenticateClientCert(pk, ce, connection); + connection.setDoOutput(true); + OutputStream os = connection.getOutputStream(); + os.write(("csr=" + URLEncoder.encode(generatePEMCSR(kp, "CN=a b"), "UTF-8")).getBytes()); + os.flush(); + assertEquals(connection.getResponseCode(), 200); + String cert = IOUtils.readURL(new InputStreamReader(connection.getInputStream(), "UTF-8")); + CertificateFactory cf = CertificateFactory.getInstance("X509"); + Collection certs = cf.generateCertificates(new ByteArrayInputStream(cert.getBytes())); + assertEquals("a b", ((X500Name) ((X509Certificate) certs.iterator().next()).getSubjectDN()).getCommonName()); + } +} diff --git a/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java b/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java index 6009095a..b22141ea 100644 --- a/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java +++ b/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java @@ -87,7 +87,7 @@ public class TestCertificateAdd extends ClientTest { String[] res = fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8")); assertArrayEquals(new String[] { - "mail", "a b", "dns:a." + uniq + ".tld\ndns:b." + uniq + ".tld\nemail:" + email + "\n", Digest.SHA384.toString() + "mail", "a b", "email:" + email + "\ndns:a." + uniq + ".tld\ndns:b." + uniq + ".tld\n", Digest.SHA384.toString() }, res); } diff --git a/tests/org/cacert/gigi/testUtils/ManagedTest.java b/tests/org/cacert/gigi/testUtils/ManagedTest.java index 54cb21fb..312c351e 100644 --- a/tests/org/cacert/gigi/testUtils/ManagedTest.java +++ b/tests/org/cacert/gigi/testUtils/ManagedTest.java @@ -178,6 +178,7 @@ public class ManagedTest extends ConfiguredTest { mainProps.setProperty("name.secure", testProps.getProperty("name.secure")); mainProps.setProperty("name.www", testProps.getProperty("name.www")); mainProps.setProperty("name.static", testProps.getProperty("name.static")); + mainProps.setProperty("name.api", testProps.getProperty("name.api")); mainProps.setProperty("https.port", testProps.getProperty("serverPort.https")); mainProps.setProperty("http.port", testProps.getProperty("serverPort.http")); -- 2.39.2