`task` enum('sign','revoke') NOT NULL,
`state` enum('open', 'done', 'error') NOT NULL DEFAULT 'open',
`warning` int(2) NOT NULL DEFAULT '0',
+ `executeFrom` DATE,
+ `executeTo` VARCHAR(11),
PRIMARY KEY (`id`),
KEY `state` (`state`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.cacert.gigi.database.DatabaseConnection;
import org.cacert.gigi.util.Job;
-import org.cacert.gigi.util.Job.JobType;
import org.cacert.gigi.util.KeyStorage;
import org.cacert.gigi.util.Notary;
return CertificateStatus.REVOKED;
}
- public Job issue() throws IOException, SQLException {
+ /**
+ * @param start
+ * the date from which on the certificate should be valid. (or
+ * null if it should be valid instantly)
+ * @param period
+ * the period for which the date should be valid. (a
+ * <code>yyyy-mm-dd</code> or a "2y" (2 calendar years), "6m" (6
+ * months)
+ * @return A job which can be used to monitor the progress of this task.
+ * @throws IOException
+ * for problems with writing the CSR/SPKAC
+ * @throws SQLException
+ * for problems with writing to the DB
+ * @throws GigiApiException
+ * if the period is bogus
+ */
+ public Job issue(Date start, String period) throws IOException, SQLException, GigiApiException {
if (getStatus() != CertificateStatus.DRAFT) {
throw new IllegalStateException();
}
updater.setString(1, csrName);
updater.setInt(2, id);
updater.execute();
- return Job.submit(this, JobType.SIGN);
+ return Job.sign(this, start, period);
}
if (getStatus() != CertificateStatus.ISSUED) {
throw new IllegalStateException();
}
- return Job.submit(this, JobType.REVOKE);
+ return Job.revoke(this);
}
import java.security.cert.Certificate;
import java.util.List;
import java.util.Properties;
+import java.util.TimeZone;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SNIHostName;
public class Launcher {
public static void main(String[] args) throws Exception {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
GigiConfig conf = GigiConfig.parse(System.in);
ServerConstants.init(conf.getMainProps());
initEmails(conf);
--- /dev/null
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.sql.Date;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class CertificateValiditySelector implements Outputable {
+
+ private static ThreadLocal<SimpleDateFormat> fmt = new ThreadLocal<>();
+
+ private static final int DAY = 1000 * 60 * 60 * 24;
+
+ private Date from;
+
+ private String val = "2y";
+
+ public CertificateValiditySelector() {
+
+ }
+
+ public static SimpleDateFormat getDateFormat() {
+ SimpleDateFormat local = fmt.get();
+ if (local == null) {
+ local = new SimpleDateFormat("yyyy-MM-dd");
+ local.setTimeZone(TimeZone.getTimeZone("UTC"));
+ fmt.set(local);
+ }
+ return local;
+ }
+
+ @Override
+ public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+ out.print("<select name='validFrom'><option value='now'");
+ if (from == null) {
+ out.print(" selected='selected'");
+ }
+ out.print(">");
+ out.print(l.getTranslation("now"));
+ out.print("</option>");
+ long base = getCurrentDayBase();
+ for (int i = 0; i < 14; i++) {
+ long date = base + DAY * i;
+ String d = getDateFormat().format(new Date(date));
+ out.print("<option value='");
+ out.print(d);
+ out.print("'");
+ if (from != null && from.getTime() == date) {
+ out.print(" selected='selected'");
+ }
+ out.print(">");
+ out.print(d);
+ out.println("</option>");
+ }
+ out.println("</select>");
+
+ out.print("<input type='text' name='validity' value='");
+ out.print(HTMLEncoder.encodeHTML(val));
+ out.println("'>");
+
+ if (from == null) {
+ return;
+ }
+
+ }
+
+ private long getCurrentDayBase() {
+ long base = System.currentTimeMillis();
+ base -= base % DAY;
+ base += DAY;
+ return base;
+ }
+
+ public void update(HttpServletRequest r) throws GigiApiException {
+ String from = r.getParameter("validFrom");
+
+ GigiApiException gae = new GigiApiException();
+ try {
+ saveStartDate(from);
+ } catch (GigiApiException e) {
+ gae.mergeInto(e);
+ }
+ try {
+ String validity = r.getParameter("validity");
+ if (validity != null) {
+ checkValidityLength(validity);
+ val = validity;
+ }
+ } catch (GigiApiException e) {
+ gae.mergeInto(e);
+ }
+ if ( !gae.isEmpty()) {
+ throw gae;
+ }
+
+ }
+
+ public static void checkValidityLength(String newval) throws GigiApiException {
+ if (newval.endsWith("y") || newval.endsWith("m")) {
+ if (newval.length() > 10) { // for database
+ throw new GigiApiException("The validity interval entered is invalid.");
+ }
+ String num = newval.substring(0, newval.length() - 1);
+ try {
+ int len = Integer.parseInt(num);
+ if (len <= 0) {
+ throw new GigiApiException("The validity interval entered is invalid.");
+ }
+ } catch (NumberFormatException e) {
+ throw new GigiApiException("The validity interval entered is invalid.");
+ }
+ } else {
+ try {
+ getDateFormat().parse(newval);
+ } catch (ParseException e) {
+ throw new GigiApiException("The validity interval entered is invalid.");
+ }
+ }
+ }
+
+ private void saveStartDate(String from) throws GigiApiException {
+ if (from == null || "now".equals(from)) {
+ this.from = null;
+ } else {
+ try {
+ this.from = new Date(getDateFormat().parse(from).getTime());
+ } catch (ParseException e) {
+ throw new GigiApiException("The validity start date entered is invalid.");
+ }
+ }
+ }
+
+ public Date getFrom() {
+ return from;
+ }
+
+ public String getTo() {
+ return val;
+ }
+
+}
import org.cacert.gigi.User;
import org.cacert.gigi.crypto.SPKAC;
import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.CertificateValiditySelector;
import org.cacert.gigi.output.Form;
import org.cacert.gigi.output.template.HashAlgorithms;
import org.cacert.gigi.output.template.IterableDataset;
private Digest selectedDigest = Digest.getDefault();
+ CertificateValiditySelector issueDate = new CertificateValiditySelector();
+
private boolean login;
private CertificateProfile profile = CertificateProfile.getById(1);
} else {
login = "1".equals(req.getParameter("login"));
+ issueDate.update(req);
CN = req.getParameter("CN");
String hashAlg = req.getParameter("hash_alg");
if (hashAlg != null) {
result = new Certificate(LoginPage.getUser(req).getId(), subject.toString(), selectedDigest.toString(), //
this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()]));
- result.issue().waitFor(60000);
+ result.issue(issueDate.getFrom(), issueDate.getTo()).waitFor(60000);
return true;
}
} catch (IOException e) {
}
vars2.put("CN", CN);
+ vars2.put("validity", issueDate);
vars2.put("emails", content.toString());
vars2.put("hashs", new HashAlgorithms(selectedDigest));
vars2.put("profiles", new IterableDataset() {
<label for="hash_alg_<?=$algorithm?>"><div class='name'><?=$name?></div><? if($info) { ?> <div class='addinfo'> <?=$info?></div><? } ?></label><div class='elements'></div>
<? } ?>
</td>
+ </tr>
+ <tr class="expert">
+ <td><?=_Valid period?></td>
+ <td>
+ <?=$validity?>
+ </td>
</tr>
<tr>
<td class='check'>
package org.cacert.gigi.util;
+import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.cacert.gigi.Certificate;
+import org.cacert.gigi.GigiApiException;
import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.output.CertificateValiditySelector;
public class Job {
}
}
- public static Job submit(Certificate targetId, JobType type) throws SQLException {
+ public static Job sign(Certificate targetId, Date start, String period) throws SQLException, GigiApiException {
+ CertificateValiditySelector.checkValidityLength(period);
+ PreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `jobs` SET targetId=?, task=?, executeFrom=?, executeTo=?");
+ ps.setInt(1, targetId.getId());
+ ps.setString(2, JobType.SIGN.getName());
+ ps.setDate(3, start);
+ ps.setString(4, period);
+ ps.execute();
+ return new Job(DatabaseConnection.lastInsertId(ps));
+ }
+
+ public static Job revoke(Certificate targetId) throws SQLException {
+
PreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `jobs` SET targetId=?, task=?");
ps.setInt(1, targetId.getId());
- ps.setString(2, type.getName());
+ ps.setString(2, JobType.REVOKE.getName());
ps.execute();
return new Job(DatabaseConnection.lastInsertId(ps));
}
public class TestCertificate extends ManagedTest {
@Test
- public void testClientCertLoginStates() throws IOException, GeneralSecurityException, SQLException, InterruptedException {
+ public void testClientCertLoginStates() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
KeyPair kp = generateKeypair();
String key1 = generatePEMCSR(kp, "CN=testmail@example.com");
Certificate c = new Certificate(1, "/CN=testmail@example.com", "sha256", key1, CSRType.CSR, CertificateProfile.getById(1));
final PrivateKey pk = kp.getPrivate();
- c.issue().waitFor(60000);
+ c.issue(null, "2y").waitFor(60000);
final X509Certificate ce = c.cert();
assertNotNull(login(pk, ce));
}
@Test
- public void testSANs() throws IOException, GeneralSecurityException, SQLException, InterruptedException {
+ public void testSANs() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
KeyPair kp = generateKeypair();
String key = generatePEMCSR(kp, "CN=testmail@example.com");
Certificate c = new Certificate(1, "/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);
- c.issue().waitFor(60000);
+ c.issue(null, "2y").waitFor(60000);
X509Certificate cert = c.cert();
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
assertEquals(2, sans.size());
}
@Test
- public void testCertLifeCycle() throws IOException, GeneralSecurityException, SQLException, InterruptedException {
+ public void testCertLifeCycle() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
KeyPair kp = generateKeypair();
String key = generatePEMCSR(kp, "CN=testmail@example.com");
Certificate c = new Certificate(1, "/CN=testmail@example.com", "sha256", key, CSRType.CSR, CertificateProfile.getById(1));
final PrivateKey pk = kp.getPrivate();
testFails(CertificateStatus.DRAFT, c);
- c.issue().waitFor(60000);
+ c.issue(null, "2y").waitFor(60000);
testFails(CertificateStatus.ISSUED, c);
X509Certificate cert = c.cert();
}
- private void testFails(CertificateStatus status, Certificate c) throws IOException, GeneralSecurityException, SQLException {
+ private void testFails(CertificateStatus status, Certificate c) throws IOException, GeneralSecurityException, SQLException, GigiApiException {
assertEquals(status, c.getStatus());
if (status != CertificateStatus.ISSUED) {
try {
}
if (status != CertificateStatus.DRAFT) {
try {
- c.issue();
+ c.issue(null, "2y");
fail(status + " is in invalid state");
} catch (IllegalStateException ise) {
public class TestSeparateSessionScope extends ManagedTest {
@Test
- public void testSeparateScope() throws IOException, GeneralSecurityException, SQLException, InterruptedException {
+ public void testSeparateScope() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
String mail = "thisgo" + createUniqueName() + "@example.com";
int user = createAssuranceUser("test", "tugo", mail, TEST_PASSWORD);
String cookie = login(mail, TEST_PASSWORD);
String csr = generatePEMCSR(kp, "CN=felix@dogcraft.de");
Certificate c = new Certificate(user, "/CN=testmail@example.com", "sha256", csr, CSRType.CSR, CertificateProfile.getById(1));
final PrivateKey pk = kp.getPrivate();
- c.issue().waitFor(60000);
+ c.issue(null, "2y").waitFor(60000);
final X509Certificate ce = c.cert();
String scookie = login(pk, ce);
import java.security.GeneralSecurityException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.Arrays;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.Properties;
+import java.util.TimeZone;
import org.cacert.gigi.Certificate.CSRType;
import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.output.CertificateValiditySelector;
public class SimpleSigner {
private static Thread runner;
+ private static SimpleDateFormat sdf = new SimpleDateFormat("YYMMddHHmmss'Z'");
+
+ static {
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
public static void main(String[] args) throws IOException, SQLException, InterruptedException {
Properties p = new Properties();
p.load(new FileReader("config/gigi.properties"));
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, rootcert FROM jobs " + //
+ 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 " + //
"INNER JOIN certs ON certs.id=jobs.targetId " + //
"INNER JOIN profiles ON profiles.id=certs.profile " + //
"WHERE jobs.state='open' "//
private static int counter = 0;
- private static void signCertificates() throws SQLException, IOException, InterruptedException {
+ private static void signCertificates() throws SQLException {
ResultSet rs = readyCerts.executeQuery();
while (rs.next()) {
String csrname = rs.getString("csr_name");
- System.out.println("sign: " + csrname);
int id = rs.getInt("id");
- String csrType = rs.getString("csr_type");
- CSRType ct = CSRType.valueOf(csrType);
- File crt = KeyStorage.locateCrt(id);
-
- String keyUsage = rs.getString("keyUsage");
- String ekeyUsage = rs.getString("extendedKeyUsage");
- getSANSs.setInt(1, id);
- ResultSet san = getSANSs.executeQuery();
-
- File f = new File("keys", "SANFile" + System.currentTimeMillis() + (counter++) + ".cfg");
- PrintWriter cfg = new PrintWriter(f);
- boolean first = true;
- while (san.next()) {
- if ( !first) {
- cfg.print(", ");
+ System.out.println("sign: " + csrname);
+ try {
+ String csrType = rs.getString("csr_type");
+ CSRType ct = CSRType.valueOf(csrType);
+ File crt = KeyStorage.locateCrt(id);
+
+ String keyUsage = rs.getString("keyUsage");
+ String ekeyUsage = rs.getString("extendedKeyUsage");
+ java.sql.Date from = rs.getDate("executeFrom");
+ String length = rs.getString("executeTo");
+ Date fromDate;
+ Date toDate;
+ if (from == null) {
+ fromDate = new Date(System.currentTimeMillis());
} else {
- cfg.print("subjectAltName=");
+ fromDate = new Date(from.getTime());
+ }
+ if (length.endsWith("m") || length.endsWith("y")) {
+ String num = length.substring(0, length.length() - 1);
+ int inter = Integer.parseInt(num);
+ Calendar c = Calendar.getInstance();
+ c.setTimeZone(TimeZone.getTimeZone("UTC"));
+ c.setTime(fromDate);
+ if (length.endsWith("m")) {
+ c.add(Calendar.MONTH, inter);
+ } else {
+ c.add(Calendar.YEAR, inter);
+ }
+ toDate = c.getTime();
+ } else {
+ toDate = CertificateValiditySelector.getDateFormat().parse(length);
+ }
+ System.out.println(from);
+ System.out.println(sdf.format(fromDate));
+
+ getSANSs.setInt(1, id);
+ ResultSet san = getSANSs.executeQuery();
+
+ File f = new File("keys", "SANFile" + System.currentTimeMillis() + (counter++) + ".cfg");
+ PrintWriter cfg = new PrintWriter(f);
+ boolean first = true;
+ while (san.next()) {
+ if ( !first) {
+ cfg.print(", ");
+ } else {
+ cfg.print("subjectAltName=");
+ }
+ first = false;
+ cfg.print(san.getString("type"));
+ cfg.print(":");
+ cfg.print(san.getString("contents"));
+ }
+ cfg.println();
+ cfg.println("keyUsage=" + keyUsage);
+ cfg.println("extendedKeyUsage=" + ekeyUsage);
+ cfg.close();
+
+ int rootcert = rs.getInt("rootcert");
+ String ca = "unassured";
+ if (rootcert == 0) {
+ ca = "unassured";
+ } else if (rootcert == 1) {
+ ca = "assured";
}
- first = false;
- cfg.print(san.getString("type"));
- cfg.print(":");
- cfg.print(san.getString("contents"));
- }
- cfg.println();
- cfg.println("keyUsage=" + keyUsage);
- cfg.println("extendedKeyUsage=" + ekeyUsage);
- cfg.close();
- int rootcert = rs.getInt("rootcert");
- String ca = "unassured";
- if (rootcert == 0) {
- ca = "unassured";
- } else if (rootcert == 1) {
- ca = "assured";
- }
-
- String[] call = new String[] {
- "openssl", "ca",//
- "-in",
- "../../" + csrname,//
- "-cert",
- "../" + ca + ".crt",//
- "-keyfile",
- "../" + ca + ".key",//
- "-out",
- "../../" + crt.getPath(),//
- "-utf8",
- "-days",
- "356",//
- "-batch",//
- "-md",
- rs.getString("md"),//
- "-extfile",
- "../" + f.getName(),//
-
- "-subj",
- rs.getString("subject"),//
- "-config",
- "../selfsign.config"//
-
- };
- if (ct == CSRType.SPKAC) {
- call[2] = "-spkac";
- }
- Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
- int waitFor = p1.waitFor();
- f.delete();
- if (waitFor == 0) {
- try (InputStream is = new FileInputStream(crt)) {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- X509Certificate crtp = (X509Certificate) cf.generateCertificate(is);
- BigInteger serial = crtp.getSerialNumber();
- updateMail.setString(1, crt.getPath());
- updateMail.setString(2, serial.toString(16));
- updateMail.setInt(3, id);
- updateMail.execute();
-
- finishJob.setInt(1, rs.getInt("jobid"));
- finishJob.execute();
- System.out.println("signed: " + id);
- continue;
- } catch (GeneralSecurityException e) {
- e.printStackTrace();
+ String[] call = new String[] {
+ "openssl", "ca",//
+ "-in",
+ "../../" + csrname,//
+ "-cert",
+ "../" + ca + ".crt",//
+ "-keyfile",
+ "../" + ca + ".key",//
+ "-out",
+ "../../" + crt.getPath(),//
+ "-utf8",
+ "-startdate",
+ sdf.format(fromDate),//
+ "-enddate",
+ sdf.format(toDate),//
+ "-batch",//
+ "-md",
+ rs.getString("md"),//
+ "-extfile",
+ "../" + f.getName(),//
+
+ "-subj",
+ rs.getString("subject"),//
+ "-config",
+ "../selfsign.config"//
+
+ };
+ if (ct == CSRType.SPKAC) {
+ call[2] = "-spkac";
}
- System.out.println("ERROR Afterwards: " + id);
- warnMail.setInt(1, rs.getInt("jobid"));
- warnMail.execute();
- } else {
- BufferedReader br = new BufferedReader(new InputStreamReader(p1.getErrorStream()));
- String s;
- while ((s = br.readLine()) != null) {
- System.out.println(s);
+ Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
+
+ int waitFor = p1.waitFor();
+ f.delete();
+ if (waitFor == 0) {
+ try (InputStream is = new FileInputStream(crt)) {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate crtp = (X509Certificate) cf.generateCertificate(is);
+ BigInteger serial = crtp.getSerialNumber();
+ updateMail.setString(1, crt.getPath());
+ updateMail.setString(2, serial.toString(16));
+ updateMail.setInt(3, id);
+ updateMail.execute();
+
+ finishJob.setInt(1, rs.getInt("jobid"));
+ finishJob.execute();
+ System.out.println("signed: " + id);
+ continue;
+ }
+ } else {
+ BufferedReader br = new BufferedReader(new InputStreamReader(p1.getErrorStream()));
+ String s;
+ while ((s = br.readLine()) != null) {
+ System.out.println(s);
+ }
}
- System.out.println(Arrays.toString(call));
- System.out.println("ERROR: " + id);
- warnMail.setInt(1, rs.getInt("jobid"));
- warnMail.execute();
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
}
+ System.out.println("Error with: " + id);
+ warnMail.setInt(1, rs.getInt("jobid"));
+ warnMail.execute();
}
rs.close();