]> WPIA git - gigi.git/blob - util/org/cacert/gigi/util/SimpleSigner.java
Update Certificate-DN-API (for escape-safe-strings)
[gigi.git] / util / org / cacert / gigi / util / SimpleSigner.java
1 package org.cacert.gigi.util;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileReader;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.PrintWriter;
11 import java.math.BigInteger;
12 import java.security.GeneralSecurityException;
13 import java.security.cert.CertificateFactory;
14 import java.security.cert.X509Certificate;
15 import java.sql.SQLException;
16 import java.sql.Timestamp;
17 import java.text.ParseException;
18 import java.text.SimpleDateFormat;
19 import java.util.Calendar;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Properties;
23 import java.util.TimeZone;
24
25 import org.cacert.gigi.database.DatabaseConnection;
26 import org.cacert.gigi.database.GigiPreparedStatement;
27 import org.cacert.gigi.database.GigiResultSet;
28 import org.cacert.gigi.dbObjects.Certificate;
29 import org.cacert.gigi.dbObjects.Certificate.CSRType;
30 import org.cacert.gigi.output.DateSelector;
31
32 public class SimpleSigner {
33
34     private static GigiPreparedStatement warnMail;
35
36     private static GigiPreparedStatement updateMail;
37
38     private static GigiPreparedStatement readyCerts;
39
40     private static GigiPreparedStatement getSANSs;
41
42     private static GigiPreparedStatement revoke;
43
44     private static GigiPreparedStatement revokeCompleted;
45
46     private static GigiPreparedStatement finishJob;
47
48     private static boolean running = true;
49
50     private static Thread runner;
51
52     private static SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss'Z'");
53
54     static {
55         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
56         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
57     }
58
59     public static void main(String[] args) throws IOException, SQLException, InterruptedException {
60         Properties p = new Properties();
61         p.load(new FileReader("config/gigi.properties"));
62         DatabaseConnection.init(p);
63
64         runSigner();
65     }
66
67     public synchronized static void stopSigner() throws InterruptedException {
68         if (runner == null) {
69             throw new IllegalStateException("already stopped");
70         }
71         running = false;
72         runner.interrupt();
73         runner.join();
74         runner = null;
75     }
76
77     public synchronized static void runSigner() throws SQLException, IOException, InterruptedException {
78         if (runner != null) {
79             throw new IllegalStateException("already running");
80         }
81         running = true;
82         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 " + //
83                 "INNER JOIN certs ON certs.id=jobs.targetId " + //
84                 "INNER JOIN profiles ON profiles.id=certs.profile " + //
85                 "WHERE jobs.state='open' "//
86                 + "AND task='sign'");
87
88         getSANSs = DatabaseConnection.getInstance().prepare("SELECT contents, type FROM subjectAlternativeNames " + //
89                 "WHERE certId=?");
90
91         updateMail = DatabaseConnection.getInstance().prepare("UPDATE certs SET crt_name=?," + " created=NOW(), serial=? WHERE id=?");
92         warnMail = DatabaseConnection.getInstance().prepare("UPDATE jobs SET warning=warning+1, state=IF(warning<3, 'open','error') WHERE id=?");
93
94         revoke = DatabaseConnection.getInstance().prepare("SELECT certs.id, certs.csr_name,jobs.id FROM jobs INNER JOIN certs ON jobs.targetId=certs.id" + " WHERE jobs.state='open' AND task='revoke'");
95         revokeCompleted = DatabaseConnection.getInstance().prepare("UPDATE certs SET revoked=NOW() WHERE id=?");
96
97         finishJob = DatabaseConnection.getInstance().prepare("UPDATE jobs SET state='done' WHERE id=?");
98
99         runner = new Thread() {
100
101             @Override
102             public void run() {
103                 work();
104             }
105
106         };
107         runner.start();
108     }
109
110     private static void work() {
111         try {
112             gencrl();
113         } catch (IOException e2) {
114             e2.printStackTrace();
115         } catch (InterruptedException e2) {
116             e2.printStackTrace();
117         }
118         while (running) {
119             try {
120                 signCertificates();
121                 revokeCertificates();
122                 Thread.sleep(5000);
123             } catch (IOException e) {
124                 e.printStackTrace();
125             } catch (SQLException e) {
126                 e.printStackTrace();
127             } catch (InterruptedException e1) {
128             }
129         }
130     }
131
132     private static void revokeCertificates() throws SQLException, IOException, InterruptedException {
133         GigiResultSet rs = revoke.executeQuery();
134         boolean worked = false;
135         while (rs.next()) {
136             int id = rs.getInt(1);
137             File crt = KeyStorage.locateCrt(id);
138             String[] call = new String[] {
139                     "openssl", "ca",//
140                     "-cert",
141                     "../unassured.crt",//
142                     "-keyfile",
143                     "../unassured.key",//
144                     "-revoke",
145                     "../../" + crt.getPath(),//
146                     "-batch",//
147                     "-config",
148                     "../selfsign.config"
149
150             };
151             Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
152             System.out.println("revoking: " + crt.getPath());
153             if (p1.waitFor() == 0) {
154                 worked = true;
155                 revokeCompleted.setInt(1, id);
156                 revokeCompleted.execute();
157                 finishJob.setInt(1, rs.getInt(3));
158                 finishJob.execute();
159             } else {
160                 System.out.println("Failed");
161             }
162         }
163         if (worked) {
164             gencrl();
165         }
166     }
167
168     private static void gencrl() throws IOException, InterruptedException {
169         String[] call = new String[] {
170                 "openssl", "ca",//
171                 "-cert",
172                 "../unassured.crt",//
173                 "-keyfile",
174                 "../unassured.key",//
175                 "-gencrl",//
176                 "-crlhours",//
177                 "12",//
178                 "-out",
179                 "../unassured.crl",//
180                 "-config",
181                 "../selfsign.config"
182
183         };
184         Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
185         if (p1.waitFor() != 0) {
186             System.out.println("Error while generating crl.");
187         }
188     }
189
190     private static int counter = 0;
191
192     private static void signCertificates() throws SQLException {
193         GigiResultSet rs = readyCerts.executeQuery();
194
195         Calendar c = Calendar.getInstance();
196         c.setTimeZone(TimeZone.getTimeZone("UTC"));
197
198         while (rs.next()) {
199             String csrname = rs.getString("csr_name");
200             int id = rs.getInt("id");
201             System.out.println("sign: " + csrname);
202             try {
203                 String csrType = rs.getString("csr_type");
204                 CSRType ct = CSRType.valueOf(csrType);
205                 File crt = KeyStorage.locateCrt(id);
206
207                 String keyUsage = rs.getString("keyUsage");
208                 String ekeyUsage = rs.getString("extendedKeyUsage");
209
210                 Timestamp from = rs.getTimestamp("executeFrom");
211                 String length = rs.getString("executeTo");
212                 Date fromDate;
213                 Date toDate;
214                 if (from == null) {
215                     fromDate = new Date(System.currentTimeMillis());
216                 } else {
217                     fromDate = new Date(from.getTime());
218                 }
219                 if (length.endsWith("m") || length.endsWith("y")) {
220                     String num = length.substring(0, length.length() - 1);
221                     int inter = Integer.parseInt(num);
222                     c.setTime(fromDate);
223                     if (length.endsWith("m")) {
224                         c.add(Calendar.MONTH, inter);
225                     } else {
226                         c.add(Calendar.YEAR, inter);
227                     }
228                     toDate = c.getTime();
229                 } else {
230                     toDate = DateSelector.getDateFormat().parse(length);
231                 }
232
233                 getSANSs.setInt(1, id);
234                 GigiResultSet san = getSANSs.executeQuery();
235
236                 File f = new File("keys", "SANFile" + System.currentTimeMillis() + (counter++) + ".cfg");
237                 PrintWriter cfg = new PrintWriter(f);
238                 boolean first = true;
239                 while (san.next()) {
240                     if ( !first) {
241                         cfg.print(", ");
242                     } else {
243                         cfg.print("subjectAltName=");
244                     }
245                     first = false;
246                     cfg.print(san.getString("type"));
247                     cfg.print(":");
248                     cfg.print(san.getString("contents"));
249                 }
250                 cfg.println();
251                 cfg.println("keyUsage=critical," + keyUsage);
252                 cfg.println("extendedKeyUsage=critical," + ekeyUsage);
253                 cfg.close();
254
255                 int rootcert = rs.getInt("rootcert");
256                 String ca = "unassured";
257                 if (rootcert == 0) {
258                     ca = "unassured";
259                 } else if (rootcert == 1) {
260                     ca = "assured";
261                 }
262                 HashMap<String, String> subj = new HashMap<>();
263                 GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT name, value FROM certAvas WHERE certId=?");
264                 ps.setInt(1, rs.getInt("id"));
265                 GigiResultSet rs2 = ps.executeQuery();
266                 while (rs2.next()) {
267                     subj.put(rs2.getString("name"), rs2.getString("value"));
268                 }
269                 if (subj.size() == 0) {
270                     subj.put("CN", "<empty>");
271                     System.out.println("WARNING: DN was empty");
272                 }
273                 String[] call = new String[] {
274                         "openssl", "ca",//
275                         "-in",
276                         "../../" + csrname,//
277                         "-cert",
278                         "../" + ca + ".crt",//
279                         "-keyfile",
280                         "../" + ca + ".key",//
281                         "-out",
282                         "../../" + crt.getPath(),//
283                         "-utf8",
284                         "-startdate",
285                         sdf.format(fromDate),//
286                         "-enddate",
287                         sdf.format(toDate),//
288                         "-batch",//
289                         "-md",
290                         rs.getString("md"),//
291                         "-extfile",
292                         "../" + f.getName(),//
293
294                         "-subj",
295                         Certificate.stringifyDN(subj),//
296                         "-config",
297                         "../selfsign.config"//
298
299                 };
300                 if (ct == CSRType.SPKAC) {
301                     call[2] = "-spkac";
302                 }
303                 Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
304
305                 int waitFor = p1.waitFor();
306                 f.delete();
307                 if (waitFor == 0) {
308                     try (InputStream is = new FileInputStream(crt)) {
309                         CertificateFactory cf = CertificateFactory.getInstance("X.509");
310                         X509Certificate crtp = (X509Certificate) cf.generateCertificate(is);
311                         BigInteger serial = crtp.getSerialNumber();
312                         updateMail.setString(1, crt.getPath());
313                         updateMail.setString(2, serial.toString(16));
314                         updateMail.setInt(3, id);
315                         updateMail.execute();
316
317                         finishJob.setInt(1, rs.getInt("jobid"));
318                         finishJob.execute();
319                         System.out.println("signed: " + id);
320                         continue;
321                     }
322                 } else {
323                     BufferedReader br = new BufferedReader(new InputStreamReader(p1.getErrorStream()));
324                     String s;
325                     while ((s = br.readLine()) != null) {
326                         System.out.println(s);
327                     }
328                 }
329             } catch (GeneralSecurityException e) {
330                 e.printStackTrace();
331             } catch (IOException e) {
332                 e.printStackTrace();
333             } catch (ParseException e) {
334                 e.printStackTrace();
335             } catch (InterruptedException e1) {
336                 e1.printStackTrace();
337             }
338             System.out.println("Error with: " + id);
339             warnMail.setInt(1, rs.getInt("jobid"));
340             warnMail.execute();
341
342         }
343         rs.close();
344     }
345 }