]> WPIA git - gigi.git/blob - src/org/cacert/gigi/dbObjects/Certificate.java
Test+implement: object cache for email and domain.
[gigi.git] / src / org / cacert / gigi / dbObjects / Certificate.java
1 package org.cacert.gigi.dbObjects;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.security.GeneralSecurityException;
9 import java.security.cert.CertificateFactory;
10 import java.security.cert.X509Certificate;
11 import java.sql.Date;
12 import java.sql.PreparedStatement;
13 import java.sql.ResultSet;
14 import java.sql.SQLException;
15 import java.util.Arrays;
16 import java.util.Collections;
17 import java.util.LinkedList;
18 import java.util.List;
19
20 import org.cacert.gigi.GigiApiException;
21 import org.cacert.gigi.database.DatabaseConnection;
22 import org.cacert.gigi.util.Job;
23 import org.cacert.gigi.util.KeyStorage;
24 import org.cacert.gigi.util.Notary;
25
26 public class Certificate {
27
28     public enum SANType {
29         EMAIL("email"), DNS("DNS");
30
31         private final String opensslName;
32
33         private SANType(String opensslName) {
34             this.opensslName = opensslName;
35         }
36
37         public String getOpensslName() {
38             return opensslName;
39         }
40     }
41
42     public static class SubjectAlternateName implements Comparable<SubjectAlternateName> {
43
44         private SANType type;
45
46         private String name;
47
48         public SubjectAlternateName(SANType type, String name) {
49             this.type = type;
50             this.name = name;
51         }
52
53         public String getName() {
54             return name;
55         }
56
57         public SANType getType() {
58             return type;
59         }
60
61         @Override
62         public int compareTo(SubjectAlternateName o) {
63             int i = type.compareTo(o.type);
64             if (i != 0) {
65                 return i;
66             }
67             return name.compareTo(o.name);
68         }
69
70         @Override
71         public int hashCode() {
72             final int prime = 31;
73             int result = 1;
74             result = prime * result + ((name == null) ? 0 : name.hashCode());
75             result = prime * result + ((type == null) ? 0 : type.hashCode());
76             return result;
77         }
78
79         @Override
80         public boolean equals(Object obj) {
81             if (this == obj) {
82                 return true;
83             }
84             if (obj == null) {
85                 return false;
86             }
87             if (getClass() != obj.getClass()) {
88                 return false;
89             }
90             SubjectAlternateName other = (SubjectAlternateName) obj;
91             if (name == null) {
92                 if (other.name != null) {
93                     return false;
94                 }
95             } else if ( !name.equals(other.name)) {
96                 return false;
97             }
98             if (type != other.type) {
99                 return false;
100             }
101             return true;
102         }
103
104     }
105
106     public enum CSRType {
107         CSR, SPKAC;
108     }
109
110     private int id;
111
112     private int ownerId;
113
114     private String serial;
115
116     private String dn;
117
118     private String md;
119
120     private String csrName;
121
122     private String crtName;
123
124     private String csr = null;
125
126     private CSRType csrType;
127
128     private List<SubjectAlternateName> sans;
129
130     private CertificateProfile profile;
131
132     public Certificate(int ownerId, String dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) {
133         this.ownerId = ownerId;
134         this.dn = dn;
135         this.md = md;
136         this.csr = csr;
137         this.csrType = csrType;
138         this.profile = profile;
139         this.sans = Arrays.asList(sans);
140     }
141
142     private Certificate(String serial) {
143         try {
144             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id,subject, md, csr_name, crt_name,memid, profile FROM `certs` WHERE serial=?");
145             ps.setString(1, serial);
146             ResultSet rs = ps.executeQuery();
147             if ( !rs.next()) {
148                 throw new IllegalArgumentException("Invalid mid " + serial);
149             }
150             this.id = rs.getInt(1);
151             dn = rs.getString(2);
152             md = rs.getString(3);
153             csrName = rs.getString(4);
154             crtName = rs.getString(5);
155             ownerId = rs.getInt(6);
156             profile = CertificateProfile.getById(rs.getInt(7));
157             this.serial = serial;
158
159             PreparedStatement ps2 = DatabaseConnection.getInstance().prepare("SELECT contents, type FROM `subjectAlternativeNames` WHERE certId=?");
160             ps2.setInt(1, id);
161             ResultSet rs2 = ps2.executeQuery();
162             sans = new LinkedList<>();
163             while (rs2.next()) {
164                 sans.add(new SubjectAlternateName(SANType.valueOf(rs2.getString("type").toUpperCase()), rs2.getString("contents")));
165             }
166             rs2.close();
167
168             rs.close();
169         } catch (SQLException e) {
170             e.printStackTrace();
171         }
172     }
173
174     public enum CertificateStatus {
175         /**
176          * This certificate is not in the database, has no id and only exists as
177          * this java object.
178          */
179         DRAFT(),
180         /**
181          * The certificate has been signed. It is stored in the database.
182          * {@link Certificate#cert()} is valid.
183          */
184         ISSUED(),
185
186         /**
187          * The certificate has been revoked.
188          */
189         REVOKED(),
190
191         /**
192          * If this certificate cannot be updated because an error happened in
193          * the signer.
194          */
195         ERROR();
196
197         private CertificateStatus() {}
198
199     }
200
201     public CertificateStatus getStatus() throws SQLException {
202         if (id == 0) {
203             return CertificateStatus.DRAFT;
204         }
205         PreparedStatement searcher = DatabaseConnection.getInstance().prepare("SELECT crt_name, created, revoked, serial FROM certs WHERE id=?");
206         searcher.setInt(1, id);
207         ResultSet rs = searcher.executeQuery();
208         if ( !rs.next()) {
209             throw new IllegalStateException("Certificate not in Database");
210         }
211
212         crtName = rs.getString(1);
213         serial = rs.getString(4);
214         if (rs.getTime(2) == null) {
215             return CertificateStatus.DRAFT;
216         }
217         if (rs.getTime(2) != null && rs.getTime(3) == null) {
218             return CertificateStatus.ISSUED;
219         }
220         return CertificateStatus.REVOKED;
221     }
222
223     /**
224      * @param start
225      *            the date from which on the certificate should be valid. (or
226      *            null if it should be valid instantly)
227      * @param period
228      *            the period for which the date should be valid. (a
229      *            <code>yyyy-mm-dd</code> or a "2y" (2 calendar years), "6m" (6
230      *            months)
231      * @return A job which can be used to monitor the progress of this task.
232      * @throws IOException
233      *             for problems with writing the CSR/SPKAC
234      * @throws SQLException
235      *             for problems with writing to the DB
236      * @throws GigiApiException
237      *             if the period is bogus
238      */
239     public Job issue(Date start, String period) throws IOException, SQLException, GigiApiException {
240         if (getStatus() != CertificateStatus.DRAFT) {
241             throw new IllegalStateException();
242         }
243         Notary.writeUserAgreement(ownerId, "CCA", "issue certificate", "", true, 0);
244
245         PreparedStatement inserter = DatabaseConnection.getInstance().prepare("INSERT INTO certs SET md=?, subject=?, csr_type=?, crt_name='', memid=?, profile=?");
246         inserter.setString(1, md);
247         inserter.setString(2, dn);
248         inserter.setString(3, csrType.toString());
249         inserter.setInt(4, ownerId);
250         inserter.setInt(5, profile.getId());
251         inserter.execute();
252         id = DatabaseConnection.lastInsertId(inserter);
253         File csrFile = KeyStorage.locateCsr(id);
254         csrName = csrFile.getPath();
255         FileOutputStream fos = new FileOutputStream(csrFile);
256         fos.write(csr.getBytes());
257         fos.close();
258
259         // TODO draft to insert SANs
260         PreparedStatement san = DatabaseConnection.getInstance().prepare("INSERT INTO subjectAlternativeNames SET certId=?, contents=?, type=?");
261         for (SubjectAlternateName subjectAlternateName : sans) {
262             san.setInt(1, id);
263             san.setString(2, subjectAlternateName.getName());
264             san.setString(3, subjectAlternateName.getType().getOpensslName());
265             san.execute();
266         }
267
268         PreparedStatement updater = DatabaseConnection.getInstance().prepare("UPDATE certs SET csr_name=? WHERE id=?");
269         updater.setString(1, csrName);
270         updater.setInt(2, id);
271         updater.execute();
272         return Job.sign(this, start, period);
273
274     }
275
276     public Job revoke() throws SQLException {
277         if (getStatus() != CertificateStatus.ISSUED) {
278             throw new IllegalStateException();
279         }
280         return Job.revoke(this);
281
282     }
283
284     public X509Certificate cert() throws IOException, GeneralSecurityException, SQLException {
285         CertificateStatus status = getStatus();
286         if (status != CertificateStatus.ISSUED) {
287             throw new IllegalStateException(status + " is not wanted here.");
288         }
289         InputStream is = null;
290         X509Certificate crt = null;
291         try {
292             is = new FileInputStream(crtName);
293             CertificateFactory cf = CertificateFactory.getInstance("X.509");
294             crt = (X509Certificate) cf.generateCertificate(is);
295         } finally {
296             if (is != null) {
297                 is.close();
298             }
299         }
300         return crt;
301     }
302
303     public Certificate renew() {
304         return null;
305     }
306
307     public int getId() {
308         return id;
309     }
310
311     public String getSerial() {
312         try {
313             getStatus();
314         } catch (SQLException e) {
315             e.printStackTrace();
316         } // poll changes
317         return serial;
318     }
319
320     public String getDistinguishedName() {
321         return dn;
322     }
323
324     public String getMessageDigest() {
325         return md;
326     }
327
328     public int getOwnerId() {
329         return ownerId;
330     }
331
332     public List<SubjectAlternateName> getSANs() {
333         return Collections.unmodifiableList(sans);
334     }
335
336     public CertificateProfile getProfile() {
337         return profile;
338     }
339
340     public static Certificate getBySerial(String serial) {
341         // TODO caching?
342         try {
343             return new Certificate(serial);
344         } catch (IllegalArgumentException e) {
345
346         }
347         return null;
348     }
349
350 }