From: Felix Dörre Date: Sat, 1 Nov 2014 16:23:23 +0000 (+0100) Subject: Update Certificate-DN-API (for escape-safe-strings) X-Git-Url: https://code.wpia.club/?p=gigi.git;a=commitdiff_plain;h=a793cf333e23cba27e2ce4378becc0426f1e186a Update Certificate-DN-API (for escape-safe-strings) --- diff --git a/doc/tableStructure.sql b/doc/tableStructure.sql index 6eb7ae3c..5c79a162 100644 --- a/doc/tableStructure.sql +++ b/doc/tableStructure.sql @@ -139,8 +139,6 @@ CREATE TABLE `certs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `memid` int(11) NOT NULL DEFAULT '0', `serial` varchar(50) NOT NULL DEFAULT '', - `CN` varchar(255) NOT NULL DEFAULT '', - `subject` varchar(1024) NOT NULL, `keytype` char(2) NOT NULL DEFAULT 'NS', `codesign` tinyint(1) NOT NULL DEFAULT '0', `md` enum('md5','sha1','sha256','sha512') NOT NULL DEFAULT 'sha512', @@ -168,6 +166,16 @@ CREATE TABLE `certs` ( KEY `emailcrt` (`crt_name`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `certAvas`; +CREATE TABLE `certAvas` ( + `certid` int(11) NOT NULL, + `name` varchar(20) NOT NULL, + `value` varchar(255) NOT NULL, + + PRIMARY KEY (`certid`, `name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + DROP TABLE IF EXISTS `clientcerts`; CREATE TABLE `clientcerts` ( `id` int(11) NOT NULL, diff --git a/src/org/cacert/gigi/dbObjects/Certificate.java b/src/org/cacert/gigi/dbObjects/Certificate.java index 0c63c2cd..6971b5e9 100644 --- a/src/org/cacert/gigi/dbObjects/Certificate.java +++ b/src/org/cacert/gigi/dbObjects/Certificate.java @@ -11,8 +11,10 @@ import java.security.cert.X509Certificate; import java.sql.Date; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map.Entry; import org.cacert.gigi.GigiApiException; import org.cacert.gigi.database.DatabaseConnection; @@ -112,8 +114,6 @@ public class Certificate { private String serial; - private String dn; - private String md; private String csrName; @@ -128,12 +128,20 @@ public class Certificate { private CertificateProfile profile; - public Certificate(User owner, String dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) throws GigiApiException { + private HashMap dn; + + private String dnString; + + public Certificate(User owner, HashMap dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) throws GigiApiException { if ( !owner.canIssue(profile)) { throw new GigiApiException("You are not allowed to issue these certificates."); } this.owner = owner; this.dn = dn; + if (dn.size() == 0) { + throw new GigiApiException("DN must not be empty"); + } + dnString = stringifyDN(dn); this.md = md; this.csr = csr; this.csrType = csrType; @@ -142,14 +150,16 @@ public class Certificate { } private Certificate(String serial) { - GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id,subject, md, csr_name, crt_name,memid, profile FROM `certs` WHERE serial=?"); + // + String concat = "group_concat(concat('/', `name`, '=', REPLACE(REPLACE(value, '\\\\', '\\\\\\\\'), '/', '\\\\/')))"; + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT certs.id, " + concat + " as subject, md, csr_name, crt_name,memid, profile FROM `certs` LEFT JOIN certAvas ON certAvas.certid=certs.id WHERE serial=? GROUP BY certs.id"); ps.setString(1, serial); GigiResultSet rs = ps.executeQuery(); if ( !rs.next()) { throw new IllegalArgumentException("Invalid mid " + serial); } this.id = rs.getInt(1); - dn = rs.getString(2); + dnString = rs.getString(2); md = rs.getString(3); csrName = rs.getString(4); crtName = rs.getString(5); @@ -238,21 +248,14 @@ public class Certificate { } Notary.writeUserAgreement(owner, "CCA", "issue certificate", "", true, 0); - GigiPreparedStatement inserter = DatabaseConnection.getInstance().prepare("INSERT INTO certs SET md=?, subject=?, csr_type=?, crt_name='', memid=?, profile=?"); + GigiPreparedStatement inserter = DatabaseConnection.getInstance().prepare("INSERT INTO certs SET md=?, csr_type=?, crt_name='', memid=?, profile=?"); inserter.setString(1, md); - inserter.setString(2, dn); - inserter.setString(3, csrType.toString()); - inserter.setInt(4, owner.getId()); - inserter.setInt(5, profile.getId()); + inserter.setString(2, csrType.toString()); + inserter.setInt(3, owner.getId()); + inserter.setInt(4, profile.getId()); inserter.execute(); id = inserter.lastInsertId(); - File csrFile = KeyStorage.locateCsr(id); - csrName = csrFile.getPath(); - FileOutputStream fos = new FileOutputStream(csrFile); - fos.write(csr.getBytes()); - fos.close(); - // TODO draft to insert SANs GigiPreparedStatement san = DatabaseConnection.getInstance().prepare("INSERT INTO subjectAlternativeNames SET certId=?, contents=?, type=?"); for (SubjectAlternateName subjectAlternateName : sans) { san.setInt(1, id); @@ -261,6 +264,19 @@ public class Certificate { san.execute(); } + GigiPreparedStatement insertAVA = DatabaseConnection.getInstance().prepare("INSERT certAvas SET certid=?, name=?, value=?"); + insertAVA.setInt(1, id); + for (Entry e : dn.entrySet()) { + insertAVA.setString(2, e.getKey()); + insertAVA.setString(3, e.getValue()); + insertAVA.execute(); + } + File csrFile = KeyStorage.locateCsr(id); + csrName = csrFile.getPath(); + FileOutputStream fos = new FileOutputStream(csrFile); + fos.write(csr.getBytes()); + fos.close(); + GigiPreparedStatement updater = DatabaseConnection.getInstance().prepare("UPDATE certs SET csr_name=? WHERE id=?"); updater.setString(1, csrName); updater.setInt(2, id); @@ -311,7 +327,7 @@ public class Certificate { } public String getDistinguishedName() { - return dn; + return dnString; } public String getMessageDigest() { @@ -340,4 +356,25 @@ public class Certificate { return null; } + public static String escapeAVA(String value) { + + return value.replace("\\", "\\\\").replace("/", "\\/"); + } + + public static String stringifyDN(HashMap contents) { + StringBuffer res = new StringBuffer(); + for (Entry i : contents.entrySet()) { + res.append("/" + i.getKey() + "="); + res.append(escapeAVA(i.getValue())); + } + return res.toString(); + } + + public static HashMap buildDN(String... contents) { + HashMap res = new HashMap<>(); + for (int i = 0; i + 1 < contents.length; i += 2) { + res.put(contents[i], contents[i + 1]); + } + return res; + } } diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java index f6b35f96..8076afe9 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java @@ -133,11 +133,6 @@ public class CertificateIssueForm extends Form { return result; } - public static String escapeAVA(String value) { - - return value.replace("\\", "\\\\").replace("/", "\\/"); - } - @Override public boolean submit(PrintWriter out, HttpServletRequest req) { String csr = req.getParameter("CSR"); @@ -298,10 +293,9 @@ public class CertificateIssueForm extends Form { outputError(out, req, "The real name entered cannot be verified with your account."); } - final StringBuffer subject = new StringBuffer(); + HashMap subject = new HashMap<>(); if (server && pDNS != null) { - subject.append("/commonName="); - subject.append(escapeAVA(pDNS)); + subject.put("CN", pDNS); if (pMail != null) { outputError(out, req, "No email is included in this certificate."); } @@ -310,24 +304,17 @@ public class CertificateIssueForm extends Form { outputError(out, req, "No real name is included in this certificate."); } } else { - subject.append("/commonName="); - subject.append(escapeAVA(CN)); + subject.put("CN", CN); if (pMail != null) { - subject.append("/emailAddress="); - subject.append(escapeAVA(pMail)); + subject.put("EMAIL", pMail); } } if (org != null) { - subject.append("/O="); - subject.append(escapeAVA(org.getName())); - subject.append("/C="); - subject.append(escapeAVA(org.getState())); - subject.append("/ST="); - subject.append(escapeAVA(org.getProvince())); - subject.append("/L="); - subject.append(escapeAVA(org.getCity())); - subject.append("/OU="); - subject.append(escapeAVA(ou)); + subject.put("O", org.getName()); + subject.put("C", org.getState()); + subject.put("ST", org.getProvince()); + subject.put("L", org.getCity()); + subject.put("OU", ou); } if (req.getParameter("CCA") == null) { outputError(out, req, "You need to accept the CCA."); @@ -336,7 +323,7 @@ public class CertificateIssueForm extends Form { return false; } - result = new Certificate(LoginPage.getUser(req), subject.toString(), selectedDigest.toString(), // + result = new Certificate(LoginPage.getUser(req), subject, selectedDigest.toString(), // this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()])); result.issue(issueDate.getFrom(), issueDate.getTo()).waitFor(60000); return true; diff --git a/tests/org/cacert/gigi/TestCertificate.java b/tests/org/cacert/gigi/TestCertificate.java index f494befb..fd58dc3c 100644 --- a/tests/org/cacert/gigi/TestCertificate.java +++ b/tests/org/cacert/gigi/TestCertificate.java @@ -31,7 +31,7 @@ public class TestCertificate extends ManagedTest { public void testClientCertLoginStates() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException { KeyPair kp = generateKeypair(); String key1 = generatePEMCSR(kp, "CN=testmail@example.com"); - Certificate c = new Certificate(u, "/CN=testmail@example.com", "sha256", key1, CSRType.CSR, CertificateProfile.getById(1)); + 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(); @@ -42,7 +42,7 @@ public class TestCertificate extends ManagedTest { public void testSANs() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException { KeyPair kp = generateKeypair(); String key = generatePEMCSR(kp, "CN=testmail@example.com"); - Certificate c = new Certificate(u, "/CN=testmail@example.com", "sha256", key, CSRType.CSR, CertificateProfile.getById(1),// + Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key, CSRType.CSR, CertificateProfile.getById(1),// new SubjectAlternateName(SANType.EMAIL, "testmail@example.com"), new SubjectAlternateName(SANType.DNS, "testmail.example.com")); testFails(CertificateStatus.DRAFT, c); @@ -93,7 +93,7 @@ public class TestCertificate extends ManagedTest { public void testCertLifeCycle() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException { KeyPair kp = generateKeypair(); String key = generatePEMCSR(kp, "CN=testmail@example.com"); - Certificate c = new Certificate(u, "/CN=testmail@example.com", "sha256", key, CSRType.CSR, CertificateProfile.getById(1)); + Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key, CSRType.CSR, CertificateProfile.getById(1)); final PrivateKey pk = kp.getPrivate(); testFails(CertificateStatus.DRAFT, c); diff --git a/tests/org/cacert/gigi/TestCrossDomainAccess.java b/tests/org/cacert/gigi/TestCrossDomainAccess.java index f20ee261..28c0b20a 100644 --- a/tests/org/cacert/gigi/TestCrossDomainAccess.java +++ b/tests/org/cacert/gigi/TestCrossDomainAccess.java @@ -50,7 +50,7 @@ public class TestCrossDomainAccess extends ManagedTest { int id = createVerifiedUser("Kurti", "Hansel", email, TEST_PASSWORD); KeyPair kp = generateKeypair(); String key1 = generatePEMCSR(kp, "CN=" + email); - Certificate c = new Certificate(User.getById(id), "/CN=" + email, "sha256", key1, CSRType.CSR, CertificateProfile.getById(1)); + Certificate c = new Certificate(User.getById(id), Certificate.buildDN("CN", email), "sha256", key1, CSRType.CSR, CertificateProfile.getById(1)); final PrivateKey pk = kp.getPrivate(); c.issue(null, "2y").waitFor(60000); final X509Certificate ce = c.cert(); diff --git a/tests/org/cacert/gigi/TestSeparateSessionScope.java b/tests/org/cacert/gigi/TestSeparateSessionScope.java index 1e583b78..20dd6ae9 100644 --- a/tests/org/cacert/gigi/TestSeparateSessionScope.java +++ b/tests/org/cacert/gigi/TestSeparateSessionScope.java @@ -27,7 +27,7 @@ public class TestSeparateSessionScope extends ManagedTest { String cookie = login(mail, TEST_PASSWORD); KeyPair kp = generateKeypair(); String csr = generatePEMCSR(kp, "CN=felix@dogcraft.de"); - Certificate c = new Certificate(User.getById(user), "/CN=testmail@example.com", "sha256", csr, CSRType.CSR, CertificateProfile.getById(1)); + Certificate c = new Certificate(User.getById(user), Certificate.buildDN("CN", "testmail@example.com"), "sha256", csr, CSRType.CSR, CertificateProfile.getById(1)); final PrivateKey pk = kp.getPrivate(); c.issue(null, "2y").waitFor(60000); final X509Certificate ce = c.cert(); diff --git a/tests/org/cacert/gigi/ping/TestSSL.java b/tests/org/cacert/gigi/ping/TestSSL.java index 71ee55eb..007d570b 100644 --- a/tests/org/cacert/gigi/ping/TestSSL.java +++ b/tests/org/cacert/gigi/ping/TestSSL.java @@ -139,7 +139,7 @@ public class TestSSL extends PingTest { private void createCertificate(String test, CertificateProfile profile) throws GeneralSecurityException, IOException, SQLException, InterruptedException, GigiApiException { kp = generateKeypair(); String csr = generatePEMCSR(kp, "CN=" + test); - c = new Certificate(User.getById(userid), "/CN=" + test, "sha256", csr, CSRType.CSR, profile); + c = new Certificate(User.getById(userid), Certificate.buildDN("CN", test), "sha256", csr, CSRType.CSR, profile); c.issue(null, "2y").waitFor(60000); } diff --git a/util/org/cacert/gigi/util/SimpleSigner.java b/util/org/cacert/gigi/util/SimpleSigner.java index d3b168a9..04602d82 100644 --- a/util/org/cacert/gigi/util/SimpleSigner.java +++ b/util/org/cacert/gigi/util/SimpleSigner.java @@ -18,12 +18,14 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.Properties; import java.util.TimeZone; import org.cacert.gigi.database.DatabaseConnection; import org.cacert.gigi.database.GigiPreparedStatement; import org.cacert.gigi.database.GigiResultSet; +import org.cacert.gigi.dbObjects.Certificate; import org.cacert.gigi.dbObjects.Certificate.CSRType; import org.cacert.gigi.output.DateSelector; @@ -77,7 +79,7 @@ public class SimpleSigner { throw new IllegalStateException("already running"); } running = true; - readyCerts = DatabaseConnection.getInstance().prepare("SELECT certs.id AS id, certs.csr_name, certs.subject, jobs.id AS jobid, csr_type, md, keyUsage, extendedKeyUsage, executeFrom, executeTo, rootcert FROM jobs " + // + readyCerts = DatabaseConnection.getInstance().prepare("SELECT certs.id AS id, certs.csr_name, jobs.id AS jobid, csr_type, md, keyUsage, extendedKeyUsage, executeFrom, executeTo, rootcert FROM jobs " + // "INNER JOIN certs ON certs.id=jobs.targetId " + // "INNER JOIN profiles ON profiles.id=certs.profile " + // "WHERE jobs.state='open' "// @@ -257,7 +259,17 @@ public class SimpleSigner { } else if (rootcert == 1) { ca = "assured"; } - + HashMap subj = new HashMap<>(); + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT name, value FROM certAvas WHERE certId=?"); + ps.setInt(1, rs.getInt("id")); + GigiResultSet rs2 = ps.executeQuery(); + while (rs2.next()) { + subj.put(rs2.getString("name"), rs2.getString("value")); + } + if (subj.size() == 0) { + subj.put("CN", ""); + System.out.println("WARNING: DN was empty"); + } String[] call = new String[] { "openssl", "ca",// "-in", @@ -280,7 +292,7 @@ public class SimpleSigner { "../" + f.getName(),// "-subj", - rs.getString("subject"),// + Certificate.stringifyDN(subj),// "-config", "../selfsign.config"//