]> WPIA git - gigi.git/blob - src/org/cacert/gigi/Certificate.java
Implement certificate profiles in java code.
[gigi.git] / src / org / cacert / gigi / Certificate.java
1 package org.cacert.gigi;
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.PreparedStatement;
12 import java.sql.ResultSet;
13 import java.sql.SQLException;
14 import java.util.Arrays;
15 import java.util.Collections;
16 import java.util.LinkedList;
17 import java.util.List;
18
19 import org.cacert.gigi.database.DatabaseConnection;
20 import org.cacert.gigi.util.Job;
21 import org.cacert.gigi.util.Job.JobType;
22 import org.cacert.gigi.util.KeyStorage;
23 import org.cacert.gigi.util.Notary;
24
25 public class Certificate {
26
27     public enum SANType {
28         EMAIL("email"), DNS("DNS");
29
30         private final String opensslName;
31
32         private SANType(String opensslName) {
33             this.opensslName = opensslName;
34         }
35
36         public String getOpensslName() {
37             return opensslName;
38         }
39     }
40
41     public static class SubjectAlternateName {
42
43         private SANType type;
44
45         private String name;
46
47         public SubjectAlternateName(SANType type, String name) {
48             this.type = type;
49             this.name = name;
50         }
51
52         public String getName() {
53             return name;
54         }
55
56         public SANType getType() {
57             return type;
58         }
59
60     }
61
62     public enum CSRType {
63         CSR, SPKAC;
64     }
65
66     private int id;
67
68     private int ownerId;
69
70     private String serial;
71
72     private String dn;
73
74     private String md;
75
76     private String csrName;
77
78     private String crtName;
79
80     private String csr = null;
81
82     private CSRType csrType;
83
84     private List<SubjectAlternateName> sans;
85
86     private CertificateProfile profile;
87
88     public Certificate(int ownerId, String dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) {
89         this.ownerId = ownerId;
90         this.dn = dn;
91         this.md = md;
92         this.csr = csr;
93         this.csrType = csrType;
94         this.profile = profile;
95         this.sans = Arrays.asList(sans);
96     }
97
98     private Certificate(String serial) {
99         try {
100             PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id,subject, md, csr_name, crt_name,memid, profile FROM `certs` WHERE serial=?");
101             ps.setString(1, serial);
102             ResultSet rs = ps.executeQuery();
103             if ( !rs.next()) {
104                 throw new IllegalArgumentException("Invalid mid " + serial);
105             }
106             this.id = rs.getInt(1);
107             dn = rs.getString(2);
108             md = rs.getString(3);
109             csrName = rs.getString(4);
110             crtName = rs.getString(5);
111             ownerId = rs.getInt(6);
112             profile = CertificateProfile.getById(rs.getInt(7));
113             this.serial = serial;
114
115             PreparedStatement ps2 = DatabaseConnection.getInstance().prepare("SELECT contents, type FROM `subjectAlternativeNames` WHERE certId=?");
116             ps2.setInt(1, id);
117             ResultSet rs2 = ps2.executeQuery();
118             sans = new LinkedList<>();
119             while (rs2.next()) {
120                 sans.add(new SubjectAlternateName(SANType.valueOf(rs2.getString("type").toUpperCase()), rs2.getString("contents")));
121             }
122             rs2.close();
123
124             rs.close();
125         } catch (SQLException e) {
126             e.printStackTrace();
127         }
128     }
129
130     public enum CertificateStatus {
131         /**
132          * This certificate is not in the database, has no id and only exists as
133          * this java object.
134          */
135         DRAFT(),
136         /**
137          * The certificate has been signed. It is stored in the database.
138          * {@link Certificate#cert()} is valid.
139          */
140         ISSUED(),
141
142         /**
143          * The certificate has been revoked.
144          */
145         REVOKED(),
146
147         /**
148          * If this certificate cannot be updated because an error happened in
149          * the signer.
150          */
151         ERROR();
152
153         private CertificateStatus() {}
154
155     }
156
157     public CertificateStatus getStatus() throws SQLException {
158         if (id == 0) {
159             return CertificateStatus.DRAFT;
160         }
161         PreparedStatement searcher = DatabaseConnection.getInstance().prepare("SELECT crt_name, created, revoked, serial FROM certs WHERE id=?");
162         searcher.setInt(1, id);
163         ResultSet rs = searcher.executeQuery();
164         if ( !rs.next()) {
165             throw new IllegalStateException("Certificate not in Database");
166         }
167
168         crtName = rs.getString(1);
169         serial = rs.getString(4);
170         if (rs.getTime(2) == null) {
171             return CertificateStatus.DRAFT;
172         }
173         if (rs.getTime(2) != null && rs.getTime(3) == null) {
174             return CertificateStatus.ISSUED;
175         }
176         return CertificateStatus.REVOKED;
177     }
178
179     public Job issue() throws IOException, SQLException {
180         if (getStatus() != CertificateStatus.DRAFT) {
181             throw new IllegalStateException();
182         }
183         Notary.writeUserAgreement(ownerId, "CCA", "issue certificate", "", true, 0);
184
185         PreparedStatement inserter = DatabaseConnection.getInstance().prepare("INSERT INTO certs SET md=?, subject=?, csr_type=?, crt_name='', memid=?, profile=?");
186         inserter.setString(1, md);
187         inserter.setString(2, dn);
188         inserter.setString(3, csrType.toString());
189         inserter.setInt(4, ownerId);
190         inserter.setInt(5, profile.getId());
191         inserter.execute();
192         id = DatabaseConnection.lastInsertId(inserter);
193         File csrFile = KeyStorage.locateCsr(id);
194         csrName = csrFile.getPath();
195         FileOutputStream fos = new FileOutputStream(csrFile);
196         fos.write(csr.getBytes());
197         fos.close();
198
199         // TODO draft to insert SANs
200         PreparedStatement san = DatabaseConnection.getInstance().prepare("INSERT INTO subjectAlternativeNames SET certId=?, contents=?, type=?");
201         for (SubjectAlternateName subjectAlternateName : sans) {
202             san.setInt(1, id);
203             san.setString(2, subjectAlternateName.getName());
204             san.setString(3, subjectAlternateName.getType().getOpensslName());
205             san.execute();
206         }
207
208         PreparedStatement updater = DatabaseConnection.getInstance().prepare("UPDATE certs SET csr_name=? WHERE id=?");
209         updater.setString(1, csrName);
210         updater.setInt(2, id);
211         updater.execute();
212         return Job.submit(this, JobType.SIGN);
213
214     }
215
216     public Job revoke() throws SQLException {
217         if (getStatus() != CertificateStatus.ISSUED) {
218             throw new IllegalStateException();
219         }
220         return Job.submit(this, JobType.REVOKE);
221
222     }
223
224     public X509Certificate cert() throws IOException, GeneralSecurityException, SQLException {
225         CertificateStatus status = getStatus();
226         if (status != CertificateStatus.ISSUED) {
227             throw new IllegalStateException(status + " is not wanted here.");
228         }
229         InputStream is = null;
230         X509Certificate crt = null;
231         try {
232             is = new FileInputStream(crtName);
233             CertificateFactory cf = CertificateFactory.getInstance("X.509");
234             crt = (X509Certificate) cf.generateCertificate(is);
235         } finally {
236             if (is != null) {
237                 is.close();
238             }
239         }
240         return crt;
241     }
242
243     public Certificate renew() {
244         return null;
245     }
246
247     public int getId() {
248         return id;
249     }
250
251     public String getSerial() {
252         try {
253             getStatus();
254         } catch (SQLException e) {
255             e.printStackTrace();
256         } // poll changes
257         return serial;
258     }
259
260     public String getDistinguishedName() {
261         return dn;
262     }
263
264     public String getMessageDigest() {
265         return md;
266     }
267
268     public int getOwnerId() {
269         return ownerId;
270     }
271
272     public List<SubjectAlternateName> getSans() {
273         return Collections.unmodifiableList(sans);
274     }
275
276     public CertificateProfile getProfile() {
277         return profile;
278     }
279
280     public static Certificate getBySerial(String serial) {
281         // TODO caching?
282         try {
283             return new Certificate(serial);
284         } catch (IllegalArgumentException e) {
285
286         }
287         return null;
288     }
289
290 }