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