From 50b8341607e23812216349ef37711e5a85d957c3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Fri, 13 Nov 2015 18:11:04 +0100 Subject: [PATCH] add: split API and add CATS import API --- config/generateTruststoreNRE.sh | 5 +- src/org/cacert/gigi/api/APIPoint.java | 51 +++++++++ src/org/cacert/gigi/api/CATSImport.java | 47 ++++++++ .../cacert/gigi/api/CreateCertificate.java | 48 +++++++++ src/org/cacert/gigi/api/GigiAPI.java | 100 +++--------------- .../cacert/gigi/api/RevokeCertificate.java | 51 +++++++++ .../cacert/gigi/database/SQLFileManager.java | 2 +- src/org/cacert/gigi/dbObjects/CATS.java | 50 +++++++++ .../gigi/dbObjects/CertificateOwner.java | 9 ++ src/org/cacert/gigi/dbObjects/User.java | 3 +- src/org/cacert/gigi/pages/LoginPage.java | 16 ++- .../org/cacert/gigi/api/ImportCATSResult.java | 78 ++++++++++++++ tests/org/cacert/gigi/api/IssueCert.java | 2 +- 13 files changed, 360 insertions(+), 102 deletions(-) create mode 100644 src/org/cacert/gigi/api/APIPoint.java create mode 100644 src/org/cacert/gigi/api/CATSImport.java create mode 100644 src/org/cacert/gigi/api/CreateCertificate.java create mode 100644 src/org/cacert/gigi/api/RevokeCertificate.java create mode 100644 src/org/cacert/gigi/dbObjects/CATS.java create mode 100644 tests/org/cacert/gigi/api/ImportCATSResult.java diff --git a/config/generateTruststoreNRE.sh b/config/generateTruststoreNRE.sh index ee5fe9bf..69a76bef 100755 --- a/config/generateTruststoreNRE.sh +++ b/config/generateTruststoreNRE.sh @@ -15,8 +15,11 @@ function importP(){ import ca/root -noprompt import ca/assured import ca/unassured +import ca/orga +import ca/orgaSign +import ca/codesign -for i in ca/{,un}assured_*; do +for i in ca/*_*_*; do import ${i%.crt} done diff --git a/src/org/cacert/gigi/api/APIPoint.java b/src/org/cacert/gigi/api/APIPoint.java new file mode 100644 index 00000000..5a66ff68 --- /dev/null +++ b/src/org/cacert/gigi/api/APIPoint.java @@ -0,0 +1,51 @@ +package org.cacert.gigi.api; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.cacert.gigi.dbObjects.CertificateOwner; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.pages.LoginPage; + +public abstract class APIPoint { + + public void process(HttpServletRequest req, HttpServletResponse resp) throws IOException { + X509Certificate cert = LoginPage.getCertificateFromRequest(req); + if (cert == null) { + resp.sendError(403, "Error, cert authing required."); + return; + } + String serial = LoginPage.extractSerialFormCert(cert); + CertificateOwner u = CertificateOwner.getByEnabledSerial(serial); + if (u == null) { + resp.sendError(403, "Error, cert authing required."); + return; + } + + 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; + } + process(req, resp, u); + } + + protected void process(HttpServletRequest req, HttpServletResponse resp, CertificateOwner u) throws IOException { + if (u instanceof User) { + process(req, resp, (User) u); + } else { + resp.sendError(500, "Error, requires a User certificate."); + return; + } + } + + protected void process(HttpServletRequest req, HttpServletResponse resp, User u) throws IOException { + + } +} diff --git a/src/org/cacert/gigi/api/CATSImport.java b/src/org/cacert/gigi/api/CATSImport.java new file mode 100644 index 00000000..507a4a00 --- /dev/null +++ b/src/org/cacert/gigi/api/CATSImport.java @@ -0,0 +1,47 @@ +package org.cacert.gigi.api; + +import java.io.IOException; +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.cacert.gigi.dbObjects.CATS; +import org.cacert.gigi.dbObjects.CertificateOwner; +import org.cacert.gigi.dbObjects.Organisation; +import org.cacert.gigi.dbObjects.User; + +public class CATSImport extends APIPoint { + + public static final String PATH = "/cats_import"; + + @Override + public void process(HttpServletRequest req, HttpServletResponse resp, CertificateOwner u) throws IOException { + if ( !(u instanceof Organisation)) { + resp.sendError(500, "Error, invalid cert"); + return; + } + if ( !"CAcert".equals(((Organisation) u).getName())) { + resp.sendError(500, "Error, invalid cert"); + return; + + } + String target = req.getParameter("serial"); + String testType = req.getParameter("variant"); + String date = req.getParameter("date"); + if (target == null || testType == null || date == null) { + resp.sendError(500, "Error, requires serial, variant and date"); + return; + } + // TODO is "byEnabledSerial" desired? + CertificateOwner o = CertificateOwner.getByEnabledSerial(target); + if ( !(o instanceof User)) { + resp.sendError(500, "Error, requires valid serial"); + return; + } + System.out.println("CATS: " + target + ": " + testType); + User targetUser = (User) o; + System.out.println(targetUser.getId()); + CATS.enterResult(targetUser, testType, new Date(Long.parseLong(date))); + } +} diff --git a/src/org/cacert/gigi/api/CreateCertificate.java b/src/org/cacert/gigi/api/CreateCertificate.java new file mode 100644 index 00000000..03cc3572 --- /dev/null +++ b/src/org/cacert/gigi/api/CreateCertificate.java @@ -0,0 +1,48 @@ +package org.cacert.gigi.api; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +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.Job; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.pages.account.certs.CertificateRequest; +import org.cacert.gigi.util.AuthorizationContext; +import org.cacert.gigi.util.PEM; + +public class CreateCertificate extends APIPoint { + + public static final String PATH = "/account/certs/new"; + + @Override + public void process(HttpServletRequest req, HttpServletResponse resp, User u) throws IOException { + String csr = req.getParameter("csr"); + if (csr == null) { + resp.sendError(500, "Error, no CSR found"); + return; + } + try { + CertificateRequest cr = new CertificateRequest(new AuthorizationContext(u, u), csr); + Certificate result = cr.draft(); + Job job = result.issue(null, "2y", u); + 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())); + return; + } catch (GeneralSecurityException e) { + e.printStackTrace(); + } catch (GigiApiException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/org/cacert/gigi/api/GigiAPI.java b/src/org/cacert/gigi/api/GigiAPI.java index caeeeffa..c0d063db 100644 --- a/src/org/cacert/gigi/api/GigiAPI.java +++ b/src/org/cacert/gigi/api/GigiAPI.java @@ -2,8 +2,7 @@ package org.cacert.gigi.api; import java.io.IOException; import java.io.InputStreamReader; -import java.security.GeneralSecurityException; -import java.security.cert.X509Certificate; +import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; @@ -11,20 +10,18 @@ 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.Job; -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.AuthorizationContext; -import org.cacert.gigi.util.PEM; - public class GigiAPI extends HttpServlet { private static final long serialVersionUID = 659963677032635817L; + HashMap api = new HashMap<>(); + + public GigiAPI() { + api.put(CreateCertificate.PATH, new CreateCertificate()); + api.put(RevokeCertificate.PATH, new RevokeCertificate()); + api.put(CATSImport.PATH, new CATSImport()); + } + @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String pi = req.getPathInfo(); @@ -43,83 +40,10 @@ public class GigiAPI extends HttpServlet { 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 (u == null) { - resp.sendError(403, "Error, cert authing required."); - return; - } - - 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(new AuthorizationContext(u, u), csr); - Certificate result = cr.draft(); - Job job = result.issue(null, "2y", u); - 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())); - return; - } catch (GeneralSecurityException e) { - e.printStackTrace(); - } catch (GigiApiException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } else if (pi.equals("/account/certs/revoke")) { - - 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 tserial = req.getParameter("serial"); - if (tserial == null) { - resp.sendError(500, "Error, no Serial found"); - return; - } - try { - Certificate c = Certificate.getBySerial(tserial); - if (c == null || c.getOwner() != u) { - resp.sendError(403, "Access Denied"); - return; - } - Job job = c.revoke(); - job.waitFor(60000); - if (c.getStatus() != CertificateStatus.REVOKED) { - resp.sendError(510, "Error, issuing timed out"); - return; - } - resp.getWriter().println("OK"); - return; - } catch (InterruptedException e) { - e.printStackTrace(); - } + APIPoint p = api.get(pi); + if (p != null) { + p.process(req, resp); } } } diff --git a/src/org/cacert/gigi/api/RevokeCertificate.java b/src/org/cacert/gigi/api/RevokeCertificate.java new file mode 100644 index 00000000..404d73ce --- /dev/null +++ b/src/org/cacert/gigi/api/RevokeCertificate.java @@ -0,0 +1,51 @@ +package org.cacert.gigi.api; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CertificateStatus; +import org.cacert.gigi.dbObjects.Job; +import org.cacert.gigi.dbObjects.User; + +public class RevokeCertificate extends APIPoint { + + public static final String PATH = "/account/certs/revoke"; + + @Override + public void process(HttpServletRequest req, HttpServletResponse resp, User u) throws IOException { + + 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 tserial = req.getParameter("serial"); + if (tserial == null) { + resp.sendError(500, "Error, no Serial found"); + return; + } + try { + Certificate c = Certificate.getBySerial(tserial); + if (c == null || c.getOwner() != u) { + resp.sendError(403, "Access Denied"); + return; + } + Job job = c.revoke(); + job.waitFor(60000); + if (c.getStatus() != CertificateStatus.REVOKED) { + resp.sendError(510, "Error, issuing timed out"); + return; + } + resp.getWriter().println("OK"); + return; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/org/cacert/gigi/database/SQLFileManager.java b/src/org/cacert/gigi/database/SQLFileManager.java index 400884af..16629d54 100644 --- a/src/org/cacert/gigi/database/SQLFileManager.java +++ b/src/org/cacert/gigi/database/SQLFileManager.java @@ -37,7 +37,7 @@ public class SQLFileManager { if (string.equals("")) { continue; } - if ((string.contains("profiles") || string.contains("cacerts")) && type != ImportType.PRODUCTION) { + if ((string.contains("profiles") || string.contains("cacerts") || string.contains("cats_type")) && type != ImportType.PRODUCTION) { continue; } string = DatabaseConnection.preprocessQuery(string); diff --git a/src/org/cacert/gigi/dbObjects/CATS.java b/src/org/cacert/gigi/dbObjects/CATS.java new file mode 100644 index 00000000..fd715ca5 --- /dev/null +++ b/src/org/cacert/gigi/dbObjects/CATS.java @@ -0,0 +1,50 @@ +package org.cacert.gigi.dbObjects; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.HashMap; + +import org.cacert.gigi.database.DatabaseConnection; +import org.cacert.gigi.database.GigiPreparedStatement; +import org.cacert.gigi.database.GigiResultSet; + +public class CATS { + + private static HashMap names = new HashMap<>(); + + public static final String ASSURER_CHALLANGE_NAME = "Assurer's Challange"; + + public static final int ASSURER_CHALLANGE_ID; + + private CATS() { + + } + + static { + GigiResultSet res = DatabaseConnection.getInstance().prepare("SELECT `id`, `type_text` FROM `cats_type`").executeQuery(); + while (res.next()) { + names.put(res.getString(2), res.getInt(1)); + } + ASSURER_CHALLANGE_ID = getID(ASSURER_CHALLANGE_NAME); + } + + public static synchronized int getID(String name) { + Integer i = names.get(name); + if (i == null) { + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `cats_type` SET `type_text`=?"); + ps.setString(1, name); + ps.execute(); + i = ps.lastInsertId(); + names.put(name, i); + } + return i; + } + + public static void enterResult(User user, String testType, Date passDate) { + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `cats_passed` SET `user_id`=?, `variant_id`=?, `pass_date`=?"); + ps.setInt(1, user.getId()); + ps.setInt(2, getID(testType)); + ps.setTimestamp(3, new Timestamp(passDate.getTime())); + ps.execute(); + } +} diff --git a/src/org/cacert/gigi/dbObjects/CertificateOwner.java b/src/org/cacert/gigi/dbObjects/CertificateOwner.java index f9e9fbd5..2adda5ee 100644 --- a/src/org/cacert/gigi/dbObjects/CertificateOwner.java +++ b/src/org/cacert/gigi/dbObjects/CertificateOwner.java @@ -117,4 +117,13 @@ public abstract class CertificateOwner implements IdCachable { return entries.toArray(new String[0]); } + public static CertificateOwner getByEnabledSerial(String serial) { + GigiPreparedStatement prep = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `certs` WHERE serial=? AND `disablelogin`='0' AND `revoked` is NULL"); + prep.setString(1, serial.toLowerCase()); + GigiResultSet res = prep.executeQuery(); + if (res.next()) { + return getById(res.getInt(1)); + } + return null; + } } diff --git a/src/org/cacert/gigi/dbObjects/User.java b/src/org/cacert/gigi/dbObjects/User.java index f67e8192..8d1d40fc 100644 --- a/src/org/cacert/gigi/dbObjects/User.java +++ b/src/org/cacert/gigi/dbObjects/User.java @@ -134,8 +134,9 @@ public class User extends CertificateOwner { } public boolean hasPassedCATS() { - GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `cats_passed` where `user_id`=? AND `variant_id`=1"); + GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `cats_passed` where `user_id`=? AND `variant_id`=?"); query.setInt(1, getId()); + query.setInt(2, CATS.ASSURER_CHALLANGE_ID); try (GigiResultSet rs = query.executeQuery()) { if (rs.next()) { return true; diff --git a/src/org/cacert/gigi/pages/LoginPage.java b/src/org/cacert/gigi/pages/LoginPage.java index 8129715a..58adcda2 100644 --- a/src/org/cacert/gigi/pages/LoginPage.java +++ b/src/org/cacert/gigi/pages/LoginPage.java @@ -16,6 +16,7 @@ import org.cacert.gigi.GigiApiException; import org.cacert.gigi.database.DatabaseConnection; import org.cacert.gigi.database.GigiPreparedStatement; import org.cacert.gigi.database.GigiResultSet; +import org.cacert.gigi.dbObjects.CertificateOwner; import org.cacert.gigi.dbObjects.Group; import org.cacert.gigi.dbObjects.User; import org.cacert.gigi.localisation.Language; @@ -151,17 +152,12 @@ public class LoginPage extends Page { if ( !serial.matches("[A-Fa-f0-9]+")) { throw new Error("serial malformed."); } - GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `certs` WHERE `serial`=? AND `disablelogin`='0' AND `revoked` is NULL"); - ps.setString(1, serial.toLowerCase()); - GigiResultSet rs = ps.executeQuery(); - User user = null; - if (rs.next()) { - user = User.getById(rs.getInt(1)); - } else { - System.out.println("User with serial " + serial + " not found."); + + CertificateOwner o = CertificateOwner.getByEnabledSerial(serial); + if (o == null || !(o instanceof User)) { + return null; } - rs.close(); - return user; + return (User) o; } public static X509Certificate getCertificateFromRequest(HttpServletRequest req) { diff --git a/tests/org/cacert/gigi/api/ImportCATSResult.java b/tests/org/cacert/gigi/api/ImportCATSResult.java new file mode 100644 index 00000000..f8adbff8 --- /dev/null +++ b/tests/org/cacert/gigi/api/ImportCATSResult.java @@ -0,0 +1,78 @@ +package org.cacert.gigi.api; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.CATS; +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CSRType; +import org.cacert.gigi.dbObjects.Certificate.SANType; +import org.cacert.gigi.dbObjects.CertificateProfile; +import org.cacert.gigi.dbObjects.Digest; +import org.cacert.gigi.dbObjects.Group; +import org.cacert.gigi.dbObjects.Organisation; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.testUtils.ClientTest; +import org.junit.Test; + +public class ImportCATSResult extends ClientTest { + + @Test + public void testImportCATS() throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + makeAssurer(id); + Certificate target = new Certificate(u, u, Certificate.buildDN("EMAIL", email), Digest.SHA256, generatePEMCSR(generateKeypair(), "EMAIL=" + email), CSRType.CSR, CertificateProfile.getByName("client"), new Certificate.SubjectAlternateName(SANType.EMAIL, "cats@cacert.org")); + target.issue(null, "2y", u).waitFor(60000); + + grant(u.getEmail(), Group.ORGASSURER); + clearCaches(); + u = User.getById(u.getId()); + Organisation o = new Organisation("CAcert", "NA", "NA", "NA", "contact@cacert.org", u); + KeyPair kp = generateKeypair(); + String key1 = generatePEMCSR(kp, "EMAIL=cats@cacert.org"); + Certificate c = new Certificate(o, u, Certificate.buildDN("EMAIL", "cats@cacert.org"), Digest.SHA256, key1, CSRType.CSR, CertificateProfile.getByName("client-orga"), new Certificate.SubjectAlternateName(SANType.EMAIL, "cats@cacert.org")); + final PrivateKey pk = kp.getPrivate(); + c.issue(null, "2y", u).waitFor(60000); + final X509Certificate ce = c.cert(); + + assertEquals(1, u.getTrainings().length); + apiRequest(target.cert().getSerialNumber().toString(16), "Test Training", pk, ce); + assertEquals(2, u.getTrainings().length); + + User u2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.com", TEST_PASSWORD)); + Certificate target2 = new Certificate(u2, u2, Certificate.buildDN("EMAIL", u2.getEmail()), Digest.SHA256, generatePEMCSR(generateKeypair(), "EMAIL=" + u2.getEmail()), CSRType.CSR, CertificateProfile.getByName("client"), new Certificate.SubjectAlternateName(SANType.EMAIL, "cats@cacert.org")); + target2.issue(null, "2y", u).waitFor(60000); + assertEquals(0, u2.getTrainings().length); + assertFalse(u2.hasPassedCATS()); + apiRequest(target2.cert().getSerialNumber().toString(16), "Test Training", pk, ce); + assertEquals(1, u2.getTrainings().length); + assertFalse(u2.hasPassedCATS()); + apiRequest(target2.cert().getSerialNumber().toString(16), CATS.ASSURER_CHALLANGE_NAME, pk, ce); + assertEquals(2, u2.getTrainings().length); + assertTrue(u2.hasPassedCATS()); + + } + + private void apiRequest(String target, String test, final PrivateKey pk, final X509Certificate ce) throws IOException, MalformedURLException, NoSuchAlgorithmException, KeyManagementException, UnsupportedEncodingException, GeneralSecurityException { + HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "api.") + CATSImport.PATH).openConnection(); + authenticateClientCert(pk, ce, connection); + connection.setDoOutput(true); + OutputStream os = connection.getOutputStream(); + os.write(("serial=" + target + "&variant=" + URLEncoder.encode(test, "UTF-8") + "&date=" + System.currentTimeMillis()).getBytes("UTF-8")); + System.out.println(connection.getResponseCode()); + System.out.println(connection.getResponseMessage()); + } +} diff --git a/tests/org/cacert/gigi/api/IssueCert.java b/tests/org/cacert/gigi/api/IssueCert.java index 65fd3f82..faa8618e 100644 --- a/tests/org/cacert/gigi/api/IssueCert.java +++ b/tests/org/cacert/gigi/api/IssueCert.java @@ -39,7 +39,7 @@ public class IssueCert extends ClientTest { final PrivateKey pk = kp.getPrivate(); c.issue(null, "2y", u).waitFor(60000); final X509Certificate ce = c.cert(); - HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "api.") + "/account/certs/new").openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "api.") + CreateCertificate.PATH).openConnection(); authenticateClientCert(pk, ce, connection); connection.setDoOutput(true); OutputStream os = connection.getOutputStream(); -- 2.39.2