]> WPIA git - gigi.git/blob - src/org/cacert/gigi/pages/account/CertificateIssueForm.java
Use "CN" correctly as dns or as real name.
[gigi.git] / src / org / cacert / gigi / pages / account / CertificateIssueForm.java
1 package org.cacert.gigi.pages.account;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.security.GeneralSecurityException;
6 import java.security.PublicKey;
7 import java.security.interfaces.DSAPublicKey;
8 import java.security.interfaces.ECPublicKey;
9 import java.security.interfaces.RSAPublicKey;
10 import java.sql.SQLException;
11 import java.util.Base64;
12 import java.util.HashMap;
13 import java.util.LinkedHashSet;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.TreeSet;
17
18 import javax.servlet.http.HttpServletRequest;
19
20 import org.cacert.gigi.Certificate;
21 import org.cacert.gigi.Certificate.CSRType;
22 import org.cacert.gigi.Certificate.SANType;
23 import org.cacert.gigi.Certificate.SubjectAlternateName;
24 import org.cacert.gigi.CertificateProfile;
25 import org.cacert.gigi.Digest;
26 import org.cacert.gigi.GigiApiException;
27 import org.cacert.gigi.User;
28 import org.cacert.gigi.crypto.SPKAC;
29 import org.cacert.gigi.localisation.Language;
30 import org.cacert.gigi.output.Form;
31 import org.cacert.gigi.output.template.HashAlgorithms;
32 import org.cacert.gigi.output.template.IterableDataset;
33 import org.cacert.gigi.output.template.Template;
34 import org.cacert.gigi.pages.LoginPage;
35 import org.cacert.gigi.pages.Page;
36 import org.cacert.gigi.util.PEM;
37 import org.cacert.gigi.util.RandomToken;
38
39 import sun.security.pkcs.PKCS9Attribute;
40 import sun.security.pkcs10.PKCS10;
41 import sun.security.pkcs10.PKCS10Attribute;
42 import sun.security.pkcs10.PKCS10Attributes;
43 import sun.security.util.DerInputStream;
44 import sun.security.util.DerValue;
45 import sun.security.x509.AVA;
46 import sun.security.x509.AlgorithmId;
47 import sun.security.x509.CertificateExtensions;
48 import sun.security.x509.DNSName;
49 import sun.security.x509.ExtendedKeyUsageExtension;
50 import sun.security.x509.Extension;
51 import sun.security.x509.GeneralName;
52 import sun.security.x509.GeneralNameInterface;
53 import sun.security.x509.GeneralNames;
54 import sun.security.x509.PKIXExtensions;
55 import sun.security.x509.RDN;
56 import sun.security.x509.RFC822Name;
57 import sun.security.x509.SubjectAlternativeNameExtension;
58 import sun.security.x509.X500Name;
59
60 /**
61  * This class represents a form that is used for issuing certificates. This
62  * class uses "sun.security" and therefore needs "-XDignore.symbol.file"
63  */
64 public class CertificateIssueForm extends Form {
65
66     private static final String DEFAULT_CN = "CAcert WoT User";
67
68     private final static Template t = new Template(CertificateIssueForm.class.getResource("CertificateIssueForm.templ"));
69
70     private final static Template tIni = new Template(CertificateAdd.class.getResource("RequestCertificate.templ"));
71
72     User u;
73
74     private CSRType csrType;
75
76     String csr;
77
78     String spkacChallenge;
79
80     public String CN = DEFAULT_CN;
81
82     Set<SubjectAlternateName> SANs = new LinkedHashSet<>();
83
84     Digest selectedDigest = Digest.getDefault();
85
86     boolean login;
87
88     CertificateProfile profile = CertificateProfile.getById(1);
89
90     public CertificateIssueForm(HttpServletRequest hsr) {
91         super(hsr);
92         u = Page.getUser(hsr);
93         spkacChallenge = RandomToken.generateToken(16);
94     }
95
96     Certificate result;
97
98     public Certificate getResult() {
99         return result;
100     }
101
102     @Override
103     public boolean submit(PrintWriter out, HttpServletRequest req) {
104         String csr = req.getParameter("CSR");
105         String spkac = req.getParameter("SPKAC");
106         try {
107             try {
108                 if (csr != null) {
109                     byte[] data = PEM.decode("(NEW )?CERTIFICATE REQUEST", csr);
110                     PKCS10 parsed = new PKCS10(data);
111                     PKCS10Attributes atts = parsed.getAttributes();
112
113                     for (PKCS10Attribute b : atts.getAttributes()) {
114
115                         if ( !b.getAttributeId().equals((Object) PKCS9Attribute.EXTENSION_REQUEST_OID)) {
116                             // unknown attrib
117                             continue;
118                         }
119
120                         for (RDN r : parsed.getSubjectName().rdns()) {
121                             for (AVA a : r.avas()) {
122                                 if (a.getObjectIdentifier().equals((Object) PKCS9Attribute.EMAIL_ADDRESS_OID)) {
123                                     SANs.add(new SubjectAlternateName(SANType.EMAIL, a.getValueString()));
124                                 } else if (a.getObjectIdentifier().equals((Object) X500Name.commonName_oid)) {
125                                     String value = a.getValueString();
126                                     if (value.contains(".") && !value.contains(" ")) {
127                                         SANs.add(new SubjectAlternateName(SANType.DNS, value));
128                                     } else {
129                                         CN = value;
130                                     }
131                                 } else if (a.getObjectIdentifier().equals((Object) PKIXExtensions.SubjectAlternativeName_Id)) {
132                                     // parse invalid SANs
133                                 }
134                             }
135                         }
136
137                         for (Extension c : ((CertificateExtensions) b.getAttributeValue()).getAllExtensions()) {
138                             if (c instanceof SubjectAlternativeNameExtension) {
139
140                                 SubjectAlternativeNameExtension san = (SubjectAlternativeNameExtension) c;
141                                 GeneralNames obj = san.get(SubjectAlternativeNameExtension.SUBJECT_NAME);
142                                 for (int i = 0; i < obj.size(); i++) {
143                                     GeneralName generalName = obj.get(i);
144                                     GeneralNameInterface peeled = generalName.getName();
145                                     if (peeled instanceof DNSName) {
146                                         SANs.add(new SubjectAlternateName(SANType.DNS, ((DNSName) peeled).getName()));
147                                     } else if (peeled instanceof RFC822Name) {
148                                         SANs.add(new SubjectAlternateName(SANType.EMAIL, ((RFC822Name) peeled).getName()));
149                                     }
150                                 }
151                             } else if (c instanceof ExtendedKeyUsageExtension) {
152                                 ExtendedKeyUsageExtension ekue = (ExtendedKeyUsageExtension) c;
153                                 for (String s : ekue.getExtendedKeyUsage()) {
154                                     if (s.equals("1.3.6.1.5.5.7.3.1")) {
155                                         // server
156                                     } else if (s.equals("1.3.6.1.5.5.7.3.2")) {
157                                         // client
158                                     } else if (s.equals("1.3.6.1.5.5.7.3.3")) {
159                                         // code sign
160                                     } else if (s.equals("1.3.6.1.5.5.7.3.4")) {
161                                         // emailProtection
162                                     } else if (s.equals("1.3.6.1.5.5.7.3.8")) {
163                                         // timestamp
164                                     } else if (s.equals("1.3.6.1.5.5.7.3.9")) {
165                                         // OCSP
166                                     }
167                                 }
168                             } else {
169                                 // Unknown requested extension
170                             }
171                         }
172
173                     }
174                     out.println(parsed.getSubjectName().getCommonName());
175                     out.println(parsed.getSubjectName().getCountry());
176
177                     out.println("CSR DN: " + parsed.getSubjectName() + "<br/>");
178                     PublicKey pk = parsed.getSubjectPublicKeyInfo();
179                     checkKeyStrength(pk, out);
180                     String sign = getSignatureAlgorithm(data);
181                     out.println("<br/>digest: " + sign + "<br/>");
182
183                     this.csr = csr;
184                     this.csrType = CSRType.CSR;
185                 } else if (spkac != null) {
186                     String cleanedSPKAC = spkac.replaceAll("[\r\n]", "");
187                     byte[] data = Base64.getDecoder().decode(cleanedSPKAC);
188                     SPKAC parsed = new SPKAC(data);
189                     if ( !parsed.getChallenge().equals(spkacChallenge)) {
190                         throw new GigiApiException("Challenge mismatch");
191                     }
192                     checkKeyStrength(parsed.getPubkey(), out);
193                     String sign = getSignatureAlgorithm(data);
194                     out.println("<br/>digest: " + sign + "<br/>");
195
196                     // spkacChallenge
197                     this.csr = "SPKAC=" + cleanedSPKAC;
198                     this.csrType = CSRType.SPKAC;
199
200                 } else {
201                     login = "1".equals(req.getParameter("login"));
202                     CN = req.getParameter("CN");
203                     String hashAlg = req.getParameter("hash_alg");
204                     if (hashAlg != null) {
205                         selectedDigest = Digest.valueOf(hashAlg);
206                     }
207                     profile = CertificateProfile.getByName(req.getParameter("profile"));
208
209                     String pDNS = null;
210                     String pMail = null;
211                     Set<SubjectAlternateName> filteredSANs = new LinkedHashSet<>();
212                     boolean server = profile.getKeyName().equals("server");
213                     for (SubjectAlternateName san : parseSANBox(req.getParameter("SANs"))) {
214                         if (san.getType() == SANType.DNS) {
215                             if (u.isValidDomain(san.getName()) && server) {
216                                 if (pDNS == null) {
217                                     pDNS = san.getName();
218                                 }
219                                 filteredSANs.add(san);
220                                 continue;
221                             }
222                         } else if (san.getType() == SANType.EMAIL) {
223                             if (u.isValidEmail(san.getName()) && !server) {
224                                 if (pMail == null) {
225                                     pMail = san.getName();
226                                 }
227                                 filteredSANs.add(san);
228                                 continue;
229                             }
230                         }
231                         outputError(out, req, "The requested Subject alternate name \"%s\" has been removed.",//
232                                 san.getType().toString().toLowerCase() + ":" + san.getName());
233                     }
234                     SANs = filteredSANs;
235                     if ( !u.isValidName(CN) && !server && !CN.equals(DEFAULT_CN)) {
236                         CN = DEFAULT_CN;
237                         outputError(out, req, "The real name entered cannot be verified with your account.");
238                     }
239
240                     final StringBuffer subject = new StringBuffer();
241                     if (server && pDNS != null) {
242                         subject.append("/commonName=");
243                         subject.append(pDNS);
244                         if (pMail != null) {
245                             outputError(out, req, "No email is included in this certificate.");
246                         }
247                         if (CN.equals("")) {
248                             CN = "";
249                             outputError(out, req, "No real name is included in this certificate.");
250                         }
251                     } else {
252                         subject.append("/commonName=");
253                         subject.append(CN);
254                         if (pMail != null) {
255                             subject.append("/emailAddress=");
256                             subject.append(pMail);
257                         }
258                     }
259                     if (req.getParameter("CCA") == null) {
260                         outputError(out, req, "You need to accept the CCA.");
261                     }
262                     if (isFailed(out)) {
263                         return false;
264                     }
265
266                     result = new Certificate(LoginPage.getUser(req).getId(), subject.toString(), selectedDigest.toString(), //
267                             this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()]));
268                     result.issue().waitFor(60000);
269                     return true;
270                 }
271             } catch (IOException e) {
272                 e.printStackTrace();
273             } catch (IllegalArgumentException e) {
274                 e.printStackTrace();
275                 throw new GigiApiException("Certificate Request format is invalid.");
276             } catch (GeneralSecurityException e) {
277                 e.printStackTrace();
278                 throw new GigiApiException("Certificate Request format is invalid.");
279             } catch (InterruptedException e) {
280                 e.printStackTrace();
281             } catch (SQLException e) {
282                 throw new GigiApiException(e);
283             }
284         } catch (GigiApiException e) {
285             e.format(out, Page.getLanguage(req));
286         }
287         return false;
288     }
289
290     private TreeSet<SubjectAlternateName> parseSANBox(String SANs) {
291         String[] SANparts = SANs.split("[\r\n]+|, *");
292         TreeSet<SubjectAlternateName> parsedNames = new TreeSet<>();
293         for (String SANline : SANparts) {
294             String[] parts = SANline.split(":", 2);
295             if (parts.length == 1) {
296                 if (parts[0].trim().equals("")) {
297                     continue;
298                 }
299                 if (parts[0].contains("@")) {
300                     parsedNames.add(new SubjectAlternateName(SANType.EMAIL, parts[0]));
301                 } else {
302                     parsedNames.add(new SubjectAlternateName(SANType.DNS, parts[0]));
303                 }
304                 continue;
305             }
306             try {
307                 SANType t = Certificate.SANType.valueOf(parts[0].toUpperCase());
308                 if (t == null) {
309                     continue;
310                 }
311                 parsedNames.add(new SubjectAlternateName(t, parts[1]));
312             } catch (IllegalArgumentException e) {
313                 // invalid enum type
314                 continue;
315             }
316         }
317         return parsedNames;
318     }
319
320     private void checkKeyStrength(PublicKey pk, PrintWriter out) {
321         out.println("Type: " + pk.getAlgorithm() + "<br/>");
322         if (pk instanceof RSAPublicKey) {
323             out.println("Exponent: " + ((RSAPublicKey) pk).getPublicExponent() + "<br/>");
324             out.println("Length: " + ((RSAPublicKey) pk).getModulus().bitLength());
325         } else if (pk instanceof DSAPublicKey) {
326             DSAPublicKey dpk = (DSAPublicKey) pk;
327             out.println("Length: " + dpk.getY().bitLength() + "<br/>");
328             out.println(dpk.getParams());
329         } else if (pk instanceof ECPublicKey) {
330             ECPublicKey epk = (ECPublicKey) pk;
331             out.println("Length-x: " + epk.getW().getAffineX().bitLength() + "<br/>");
332             out.println("Length-y: " + epk.getW().getAffineY().bitLength() + "<br/>");
333             out.println(epk.getParams().getCurve());
334         }
335     }
336
337     private static String getSignatureAlgorithm(byte[] data) throws IOException {
338         DerInputStream in = new DerInputStream(data);
339         DerValue[] seq = in.getSequence(3);
340         return AlgorithmId.parse(seq[1]).getName();
341     }
342
343     @Override
344     public void output(PrintWriter out, Language l, Map<String, Object> vars) {
345         if (csr == null) {
346             HashMap<String, Object> vars2 = new HashMap<String, Object>(vars);
347             vars2.put("csrf", getCSRFToken());
348             vars2.put("csrf_name", getCsrfFieldName());
349             vars2.put("spkacChallenge", spkacChallenge);
350             tIni.output(out, l, vars2);
351             return;
352         } else {
353             super.output(out, l, vars);
354         }
355     }
356
357     @Override
358     protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
359         HashMap<String, Object> vars2 = new HashMap<String, Object>(vars);
360         vars2.put("CCA", "<a href='/policy/CAcertCommunityAgreement.html'>CCA</a>");
361
362         StringBuffer content = new StringBuffer();
363         for (SubjectAlternateName SAN : SANs) {
364             content.append(SAN.getType().toString().toLowerCase());
365             content.append(':');
366             content.append(SAN.getName());
367             content.append('\n');
368         }
369
370         vars2.put("CN", CN);
371         vars2.put("emails", content.toString());
372         vars2.put("hashs", new HashAlgorithms(selectedDigest));
373         vars2.put("profiles", new IterableDataset() {
374
375             int i = 1;
376
377             @Override
378             public boolean next(Language l, Map<String, Object> vars) {
379                 CertificateProfile cp = CertificateProfile.getById(i++);
380                 if (cp == null) {
381                     return false;
382                 }
383                 if (cp.getId() == profile.getId()) {
384                     vars.put("selected", " selected");
385                 } else {
386                     vars.put("selected", "");
387                 }
388                 vars.put("key", cp.getKeyName());
389                 vars.put("name", cp.getVisibleName());
390                 return true;
391             }
392         });
393         t.output(out, l, vars2);
394     }
395 }