public class DatabaseConnection {
- public static final int CURRENT_SCHEMA_VERSION = 2;
+ public static final int CURRENT_SCHEMA_VERSION = 3;
public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
CREATE TABLE `cacerts` (
`id` int(3) NOT NULL AUTO_INCREMENT,
`keyname` varchar(60) NOT NULL,
- `subroot` int(2) NOT NULL,
+ `parentRoot` int(3) NOT NULL,
`validFrom` datetime NULL DEFAULT NULL,
`validTo` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`),
`version` int(5) NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO schemeVersion(version) VALUES(2);
+INSERT INTO schemeVersion(version) VALUES(3);
--- /dev/null
+DROP TABLE IF EXISTS `cacerts`;
+CREATE TABLE `cacerts` (
+ `id` int(3) NOT NULL AUTO_INCREMENT,
+ `keyname` varchar(60) NOT NULL,
+ `link` varchar(160) NOT NULL,
+ `parentRoot` int(3) NOT NULL,
+ `validFrom` datetime NULL DEFAULT NULL,
+ `validTo` datetime NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE (`keyname`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
--- /dev/null
+package org.cacert.gigi.dbObjects;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+
+public class CACertificate implements IdCachable {
+
+ final String keyname;
+
+ final int id;
+
+ CACertificate parent = null;
+
+ final X509Certificate cert;
+
+ final String link;
+
+ private CACertificate(int id) {
+ this.id = id;
+ GigiPreparedStatement conn = DatabaseConnection.getInstance().prepare("SELECT keyname, parentRoot, link FROM cacerts WHERE id = ?");
+ conn.setInt(1, id);
+ GigiResultSet res = conn.executeQuery();
+ if ( !res.next()) {
+ throw new IllegalArgumentException();
+ }
+ keyname = res.getString("keyname");
+ link = res.getString("link");
+ int parentRoot = res.getInt("parentRoot");
+ if (res.next()) {
+ throw new RuntimeException("DB is broken");
+ }
+ if (parentRoot == id) {
+ parent = this;
+ } else {
+ parent = getById(parentRoot);
+ }
+ try {
+ FileInputStream fis = new FileInputStream("config/ca/" + keyname + ".crt");
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
+ cert = (X509Certificate) cf.generateCertificate(fis);
+ } catch (FileNotFoundException e) {
+ throw new Error(e);
+ } catch (GeneralSecurityException e) {
+ throw new Error(e);
+ }
+ }
+
+ public CACertificate getParent() {
+ return parent;
+ }
+
+ public X509Certificate getCertificate() {
+ return cert;
+ }
+
+ @Override
+ public String toString() {
+ return "CACertificate: " + keyname;
+ }
+
+ static {
+ try {
+ update();
+ } catch (CertificateException e) {
+ throw new Error(e);
+ } catch (FileNotFoundException e) {
+ throw new Error(e);
+ }
+ }
+
+ private static void update() throws CertificateException, FileNotFoundException {
+ File scandir = new File("config/ca");
+ CertificateFactory xf = CertificateFactory.getInstance("X509");
+ HashMap<X500Principal, X509Certificate> map = new HashMap<>();
+ HashMap<X500Principal, String> names = new HashMap<>();
+ for (File f : scandir.listFiles()) {
+ X509Certificate cert = (X509Certificate) xf.generateCertificate(new FileInputStream(f));
+ X500Principal princip = cert.getSubjectX500Principal();
+ map.put(princip, cert);
+ String name = f.getName();
+ names.put(princip, name.substring(0, name.length() - 4));
+ }
+ HashMap<X500Principal, Integer> inserted = new HashMap<>();
+ for (X509Certificate i : map.values()) {
+ if (inserted.containsKey(i.getSubjectX500Principal())) {
+ continue;
+ }
+ Deque<X509Certificate> toInserts = new ArrayDeque<>();
+ toInserts.add(i);
+ while ( !inserted.containsKey(i.getIssuerX500Principal()) && !i.getIssuerX500Principal().equals(i.getSubjectX500Principal())) {
+ i = map.get(i.getIssuerX500Principal());
+ toInserts.addFirst(i);
+ }
+ for (X509Certificate toInsert : toInserts) {
+
+ X500Principal subj = toInsert.getSubjectX500Principal();
+ boolean self = toInsert.getIssuerX500Principal().equals(subj);
+ GigiPreparedStatement q = DatabaseConnection.getInstance().prepare("SELECT id, parentRoot FROM cacerts WHERE keyname=?");
+ q.setString(1, names.get(subj));
+ GigiResultSet res = q.executeQuery();
+ int id;
+ if (res.next()) {
+ id = res.getInt("id");
+ if (res.getInt("parentRoot") != (self ? id : inserted.get(toInsert.getIssuerX500Principal()))) {
+ throw new Error("Invalid DB structure: " + subj + "->" + inserted.get(toInsert.getIssuerX500Principal()) + " vs " + res.getInt("parentRoot"));
+ }
+ } else {
+ String link;
+ String keyname = names.get(subj);
+ if ( !keyname.contains("_")) {
+ link = "http://g2.crt.cacert.org/g2/" + keyname + ".crt";
+ } else {
+ String[] parts = keyname.split("_");
+ link = "http://g2.crt.cacert.org/g2/" + parts[1] + "/" + parts[0] + "-" + parts[2] + ".crt";
+
+ }
+ GigiPreparedStatement q2 = DatabaseConnection.getInstance().prepare("INSERT INTO cacerts SET parentRoot=?, keyname=?, link=?");
+ q2.setInt(1, self ? 0 : inserted.get(toInsert.getIssuerX500Principal()));
+ q2.setString(2, keyname);
+ q2.setString(3, link);
+ q2.execute();
+ id = q2.lastInsertId();
+ if (self) {
+ GigiPreparedStatement q3 = DatabaseConnection.getInstance().prepare("UPDATE cacerts SET parentRoot=?, id=?");
+ q3.setInt(1, id);
+ q3.setInt(2, id);
+ q3.execute();
+ }
+ }
+ inserted.put(subj, id);
+ }
+ }
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ private static ObjectCache<CACertificate> myCache = new ObjectCache<>();
+
+ public String getKeyname() {
+ return keyname;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public static synchronized CACertificate getById(int id) throws IllegalArgumentException {
+ CACertificate em = myCache.get(id);
+ if (em == null) {
+ myCache.put(em = new CACertificate(id));
+ }
+ return em;
+ }
+
+ public boolean isSelfsigned() {
+ return this == getParent();
+ }
+}
private String dnString;
+ private CACertificate ca;
+
public Certificate(User owner, HashMap<String, String> 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.");
if (id == 0) {
return CertificateStatus.DRAFT;
}
- GigiPreparedStatement searcher = DatabaseConnection.getInstance().prepare("SELECT crt_name, created, revoked, serial FROM certs WHERE id=?");
+ GigiPreparedStatement searcher = DatabaseConnection.getInstance().prepare("SELECT crt_name, created, revoked, serial, caid FROM certs WHERE id=?");
searcher.setInt(1, id);
GigiResultSet rs = searcher.executeQuery();
if ( !rs.next()) {
crtName = rs.getString(1);
serial = rs.getString(4);
+ ca = CACertificate.getById(rs.getInt("caid"));
if (rs.getTimestamp(2) == null) {
return CertificateStatus.DRAFT;
}
}
+ public CACertificate getParent() {
+ CertificateStatus status = getStatus();
+ if (status != CertificateStatus.REVOKED && status != CertificateStatus.ISSUED) {
+ throw new IllegalStateException(status + " is not wanted here.");
+ }
+ return ca;
+ }
+
public X509Certificate cert() throws IOException, GeneralSecurityException {
CertificateStatus status = getStatus();
if (status != CertificateStatus.REVOKED && status != CertificateStatus.ISSUED) {
-<a href='<?=$serial?>.crt'><?=_PEM encoded Certificate?></a><br/>
+<a href='<?=$serial?>.crt'><?=_PEM encoded Certificate?></a>
+<? foreach($trustchain) { ?>
+ <?=_issued by?> <a href='<?=$link?>'><?=$name?></a>
+<? } ?><br/>
+<a href='<?=$serial?>.crt?chain'><?=_PEM encoded Certificate Cain?></a><br/>
+<a href='<?=$serial?>.crt?chain&noAnchor'><?=_PEM encoded Certificate Cain (Excluding Anchor)?></a><br/>
<a href='<?=$serial?>.cer'><?=_DER encoded Certificate?></a><br/>
<a href='<?=$serial?>.cer?install'><?=_Install into browser.?></a><br/>
<pre>
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
+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.User;
+import org.cacert.gigi.localisation.Language;
import org.cacert.gigi.output.CertificateIterable;
+import org.cacert.gigi.output.template.IterableDataset;
import org.cacert.gigi.output.template.Template;
import org.cacert.gigi.pages.LoginPage;
import org.cacert.gigi.pages.Page;
public static final String PATH = "/account/certs";
+ static class TrustchainIterable implements IterableDataset {
+
+ CACertificate cert;
+
+ public TrustchainIterable(CACertificate cert) {
+ this.cert = cert;
+ }
+
+ @Override
+ public boolean next(Language l, Map<String, Object> 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");
}
ServletOutputStream out = resp.getOutputStream();
if (crt) {
out.println(PEM.encode("CERTIFICATE", cert.getEncoded()));
+ if (req.getParameter("chain") != null) {
+ CACertificate ca = c.getParent();
+ while ( !ca.isSelfsigned()) {
+ out.println(PEM.encode("CERTIFICATE", ca.getCertificate().getEncoded()));
+ ca = ca.getParent();
+ }
+ if (req.getParameter("noAnchor") == null) {
+ out.println(PEM.encode("CERTIFICATE", ca.getCertificate().getEncoded()));
+ }
+ }
} else if (cer) {
out.write(cert.getEncoded());
}
}
HashMap<String, Object> vars = new HashMap<>();
vars.put("serial", URLEncoder.encode(serial, "UTF-8"));
+ vars.put("trustchain", new TrustchainIterable(c.getParent()));
try {
vars.put("cert", c.cert());
} catch (GeneralSecurityException e) {