From 3a1808dadcea554995c71ac655938783458a2d30 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Fri, 15 May 2015 00:10:41 +0200 Subject: [PATCH] add: cert-profile loading from config --- .../gigi/database/DatabaseConnection.java | 2 +- .../cacert/gigi/database/tableStructure.sql | 25 +- .../cacert/gigi/database/upgrade/from_3.sql | 10 + .../cacert/gigi/dbObjects/Certificate.java | 2 +- .../gigi/dbObjects/CertificateProfile.java | 241 +++++++++++++++++- src/org/cacert/gigi/dbObjects/User.java | 17 -- .../account/certs/CertificateIssueForm.java | 2 +- .../account/certs/CertificateRequest.java | 2 +- 8 files changed, 248 insertions(+), 53 deletions(-) create mode 100644 src/org/cacert/gigi/database/upgrade/from_3.sql diff --git a/src/org/cacert/gigi/database/DatabaseConnection.java b/src/org/cacert/gigi/database/DatabaseConnection.java index 0d7e22b4..8e20f2a7 100644 --- a/src/org/cacert/gigi/database/DatabaseConnection.java +++ b/src/org/cacert/gigi/database/DatabaseConnection.java @@ -15,7 +15,7 @@ import org.cacert.gigi.database.SQLFileManager.ImportType; public class DatabaseConnection { - public static final int CURRENT_SCHEMA_VERSION = 3; + public static final int CURRENT_SCHEMA_VERSION = 4; public static final int CONNECTION_TIMEOUT = 24 * 60 * 60; diff --git a/src/org/cacert/gigi/database/tableStructure.sql b/src/org/cacert/gigi/database/tableStructure.sql index e41bce89..234ecc06 100644 --- a/src/org/cacert/gigi/database/tableStructure.sql +++ b/src/org/cacert/gigi/database/tableStructure.sql @@ -189,32 +189,13 @@ DROP TABLE IF EXISTS `profiles`; CREATE TABLE `profiles` ( `id` int(3) NOT NULL AUTO_INCREMENT, `keyname` varchar(60) NOT NULL, - `keyUsage` varchar(100) NOT NULL, - `extendedKeyUsage` varchar(100) NOT NULL, - `rootcert` int(2) NOT NULL DEFAULT '1', + `include` varchar(200) NOT NULL, + `requires` varchar(200) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE (`keyname`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; -INSERT INTO `profiles` SET rootcert=0, keyname='client', name='ssl-client (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth'; -INSERT INTO `profiles` SET rootcert=0, keyname='mail', name='mail (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection'; -INSERT INTO `profiles` SET rootcert=0, keyname='client-mail', name='ssl-client + mail (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection'; -INSERT INTO `profiles` SET rootcert=0, keyname='server', name='ssl-server (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth'; - -INSERT INTO `profiles` SET rootcert=1, keyname='client-a', name='ssl-client (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth'; -INSERT INTO `profiles` SET rootcert=1, keyname='mail-a', name='mail (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection'; -INSERT INTO `profiles` SET rootcert=1, keyname='client-mail-a', name='ssl-client + mail(assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection'; -INSERT INTO `profiles` SET rootcert=1, keyname='server-a', name='ssl-server (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth'; -INSERT INTO `profiles` SET rootcert=2, keyname='code-a', name='codesign (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='codeSigning, msCodeInd, msCodeCom'; - -INSERT INTO `profiles` SET rootcert=3, keyname='client-orga', name='ssl-client (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth'; -INSERT INTO `profiles` SET rootcert=3, keyname='mail-orga', name='mail (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection'; -INSERT INTO `profiles` SET rootcert=3, keyname='client-mail-orga', name='ssl-client + mail(orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection'; -INSERT INTO `profiles` SET rootcert=3, keyname='server-orga', name='ssl-server (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth'; -INSERT INTO `profiles` SET rootcert=4, keyname='code-orga', name='codesign (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='codeSigning, msCodeInd, msCodeCom'; - --- 0=unassured, 1=assured, 2=codesign, 3=orga, 4=orga-sign DROP TABLE IF EXISTS `subjectAlternativeNames`; CREATE TABLE `subjectAlternativeNames` ( `certId` int(11) NOT NULL, @@ -347,4 +328,4 @@ CREATE TABLE `schemeVersion` ( `version` int(5) NOT NULL, PRIMARY KEY (`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO schemeVersion(version) VALUES(3); +INSERT INTO schemeVersion(version) VALUES(4); diff --git a/src/org/cacert/gigi/database/upgrade/from_3.sql b/src/org/cacert/gigi/database/upgrade/from_3.sql new file mode 100644 index 00000000..f6c3c089 --- /dev/null +++ b/src/org/cacert/gigi/database/upgrade/from_3.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `profiles`; +CREATE TABLE `profiles` ( + `id` int(3) NOT NULL AUTO_INCREMENT, + `keyname` varchar(60) NOT NULL, + `include` varchar(200) NOT NULL, + `requires` varchar(200) NOT NULL, + `name` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE (`keyname`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; diff --git a/src/org/cacert/gigi/dbObjects/Certificate.java b/src/org/cacert/gigi/dbObjects/Certificate.java index 479a183a..ed3d5b4a 100644 --- a/src/org/cacert/gigi/dbObjects/Certificate.java +++ b/src/org/cacert/gigi/dbObjects/Certificate.java @@ -135,7 +135,7 @@ public class Certificate { private CACertificate ca; public Certificate(User owner, HashMap dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) throws GigiApiException { - if ( !owner.canIssue(profile)) { + if ( !profile.canBeIssuedBy(owner)) { throw new GigiApiException("You are not allowed to issue these certificates."); } this.owner = owner; diff --git a/src/org/cacert/gigi/dbObjects/CertificateProfile.java b/src/org/cacert/gigi/dbObjects/CertificateProfile.java index 0aa45512..7ee20607 100644 --- a/src/org/cacert/gigi/dbObjects/CertificateProfile.java +++ b/src/org/cacert/gigi/dbObjects/CertificateProfile.java @@ -1,6 +1,15 @@ package org.cacert.gigi.dbObjects; +import java.awt.Desktop; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedList; +import java.util.Properties; +import java.util.TreeSet; import org.cacert.gigi.database.DatabaseConnection; import org.cacert.gigi.database.GigiPreparedStatement; @@ -14,17 +23,122 @@ public class CertificateProfile { private final String visibleName; - private final int caId; - private static HashMap byName = new HashMap<>(); private static HashMap byId = new HashMap<>(); - private CertificateProfile(int id, String keyName, String visibleName, int caId) { + private final PropertyTemplate[] pt; + + private final String[] req; + + private CertificateProfile(int id, String keyName, String visibleName, String requires, String include) { this.id = id; this.keyName = keyName; this.visibleName = visibleName; - this.caId = caId; + req = parseConditions(requires); + pt = parsePropertyTemplates(include); + } + + private static class PropertyTemplate implements Comparable { + + boolean required = true; + + boolean multiple = false; + + private String inc; + + public PropertyTemplate(String inc) { + if (inc.endsWith("?") || inc.endsWith("*") || inc.endsWith("+")) { + char sfx = inc.charAt(inc.length() - 1); + if (sfx == '?') { + required = false; + } else if (sfx == '*') { + multiple = true; + required = false; + } else { + multiple = true; + } + inc = inc.substring(0, inc.length() - 1); + } + this.inc = inc; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((inc == null) ? 0 : inc.hashCode()); + result = prime * result + (multiple ? 1231 : 1237); + result = prime * result + (required ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PropertyTemplate other = (PropertyTemplate) obj; + if (inc == null) { + if (other.inc != null) { + return false; + } + } else if ( !inc.equals(other.inc)) { + return false; + } + if (multiple != other.multiple) { + return false; + } + if (required != other.required) { + return false; + } + return true; + } + + @Override + public String toString() { + return inc + (multiple ? (required ? "+" : "*") : (required ? "" : "?")); + } + + @Override + public int compareTo(PropertyTemplate o) { + return toString().compareTo(o.toString()); + } + + } + + private CertificateProfile(File f) throws IOException { + Properties p = new Properties(); + p.load(new FileInputStream(f)); + String[] parts = f.getName().split("\\.")[0].split("-", 2); + id = Integer.parseInt(parts[0]); + keyName = parts[1]; + visibleName = ""; + pt = parsePropertyTemplates(p.getProperty("include")); + req = parseConditions(p.getProperty("requires", "")); + } + + private String[] parseConditions(String property) { + String[] split2 = property.split(","); + if (split2.length == 1 && split2[0].equals("")) { + split2 = new String[0]; + } + return split2; + } + + private PropertyTemplate[] parsePropertyTemplates(String property) { + String[] split = property.split(","); + PropertyTemplate[] pt = new PropertyTemplate[split.length]; + for (int i = 0; i < split.length; i++) { + pt[i] = new PropertyTemplate(split[i]); + } + return pt; } public int getId() { @@ -39,15 +153,44 @@ public class CertificateProfile { return visibleName; } - public int getCAId() { - return caId; - } - static { - GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id, keyname, name, rootcert FROM `profiles`"); + for (File f : new File("config/profiles").listFiles()) { + Properties p = new Properties(); + try { + p.load(new FileInputStream(f)); + } catch (IOException e) { + e.printStackTrace(); + } + String[] parts = f.getName().split("\\.")[0].split("-", 2); + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT keyname, include, requires, name FROM `profiles` WHERE id=?"); + ps.setInt(1, Integer.parseInt(parts[0])); + GigiResultSet rs = ps.executeQuery(); + + if (rs.next()) { + if ( !rs.getString("keyname").equals(parts[1])) { + throw new Error("Config error. Certificate Profile mismatch"); + } + if ( !rs.getString("include").equals(p.getProperty("include"))) { + throw new Error("Config error. Certificate Profile mismatch"); + } + if ( !rs.getString("requires").equals(p.getProperty("requires", ""))) { + throw new Error("Config error. Certificate Profile mismatch"); + } + } else { + GigiPreparedStatement insert = DatabaseConnection.getInstance().prepare("INSERT INTO `profiles` SET keyname=?, include=?, requires=?, name=?, id=?"); + insert.setString(1, parts[1]); + insert.setString(2, p.getProperty("include")); + insert.setString(3, p.getProperty("requires", "")); + insert.setString(4, p.getProperty("name")); + insert.setInt(5, Integer.parseInt(parts[0])); + insert.execute(); + } + + } + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id, keyname, name, requires, include FROM `profiles`"); GigiResultSet rs = ps.executeQuery(); while (rs.next()) { - CertificateProfile cp = new CertificateProfile(rs.getInt("id"), rs.getString("keyName"), rs.getString("name"), rs.getInt("rootcert")); + CertificateProfile cp = new CertificateProfile(rs.getInt("id"), rs.getString("keyName"), rs.getString("name"), rs.getString("requires"), rs.getString("include")); byId.put(cp.getId(), cp); byName.put(cp.getKeyName(), cp); } @@ -66,4 +209,82 @@ public class CertificateProfile { return byId.values().toArray(new CertificateProfile[byId.size()]); } + public static void main(String[] args) throws IOException { + TreeSet pt = new TreeSet<>(); + TreeSet req = new TreeSet<>(); + LinkedList cps = new LinkedList<>(); + for (CertificateProfile cp : byId.values()) { + cps.add(cp); + for (PropertyTemplate p : cp.pt) { + pt.add(p.inc); + } + req.addAll(Arrays.asList(cp.req)); + } + PrintWriter pw = new PrintWriter("profiles.html"); + pw.println("Profiles"); + pw.println(""); + pw.println(""); + pw.println(""); + pw.println(""); + for (String p : pt) { + pw.println(""); + } + pw.println(""); + for (String p : req) { + pw.println(""); + } + pw.println(""); + for (CertificateProfile certificateProfile : cps) { + pw.println(""); + pw.println(""); + pw.println(""); + outer: + for (String p : pt) { + for (PropertyTemplate t : certificateProfile.pt) { + if (t.inc.equals(p)) { + pw.println(""); + continue outer; + } + } + pw.println(""); + } + pw.println(""); + outer: + for (String p : req) { + for (String t : certificateProfile.req) { + if (t.equals(p)) { + pw.println(""); + continue outer; + } + } + pw.println(""); + } + pw.println(""); + } + pw.println("
id " + p + "" + p + "
" + certificateProfile.id + "" + certificateProfile.keyName + "" + (t.required ? (t.multiple ? "+" : "y") : (t.multiple ? "*" : "?")) + "y
"); + Desktop.getDesktop().browse(new File("profiles.html").toURI()); + pw.close(); + } + + public boolean canBeIssuedBy(User u) { + for (String s : req) { + if (s.equals("points>=50")) { + if (u.getAssurancePoints() < 50) { + return false; + } + } else if (s.equals("points>=100")) { + if (u.getAssurancePoints() < 100) { + return false; + } + } else if (s.equals("codesign")) { + if (u.isInGroup(Group.CODESIGNING)) { + return false; + } + } else { + return false; + } + + } + return true; + } } diff --git a/src/org/cacert/gigi/dbObjects/User.java b/src/org/cacert/gigi/dbObjects/User.java index f6b22909..1d752667 100644 --- a/src/org/cacert/gigi/dbObjects/User.java +++ b/src/org/cacert/gigi/dbObjects/User.java @@ -441,21 +441,4 @@ public class User extends CertificateOwner { } } - public boolean canIssue(CertificateProfile p) { - // FIXME: Use descriptive constants - switch (p.getCAId()) { - case 0: - return true; - case 1: - return getAssurancePoints() > 50; - case 2: - return getAssurancePoints() > 50 && isInGroup(Group.getByString("codesigning")); - case 3: - case 4: - return getOrganisations().size() > 0; - default: - return false; - } - } - } diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java index 8a3107f0..dadd4e88 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java @@ -160,7 +160,7 @@ public class CertificateIssueForm extends Form { if (cp == null) { return false; } - } while ( !u.canIssue(cp)); + } while ( !cp.canBeIssuedBy(u)); if (cp.getId() == cr.getProfile().getId()) { vars.put("selected", " selected"); diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java index f066cc3d..9eee2940 100644 --- a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java +++ b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java @@ -342,7 +342,7 @@ public class CertificateRequest { public Certificate draft() throws GigiApiException { GigiApiException error = new GigiApiException(); - if ( !u.canIssue(this.profile)) { + if ( !this.profile.canBeIssuedBy(u)) { this.profile = CertificateProfile.getById(1); error.mergeInto(new GigiApiException("Certificate Profile is invalid.")); throw error; -- 2.39.2