]> WPIA git - gigi.git/blob - src/club/wpia/gigi/pages/main/KeyCompromiseForm.java
add: process to report compromised certificates
[gigi.git] / src / club / wpia / gigi / pages / main / KeyCompromiseForm.java
1 package club.wpia.gigi.pages.main;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.PrintWriter;
6 import java.io.UnsupportedEncodingException;
7 import java.security.GeneralSecurityException;
8 import java.security.KeyFactory;
9 import java.security.PrivateKey;
10 import java.security.Signature;
11 import java.security.cert.CertificateException;
12 import java.security.cert.CertificateFactory;
13 import java.security.cert.X509Certificate;
14 import java.security.interfaces.RSAPrivateKey;
15 import java.security.spec.PKCS8EncodedKeySpec;
16 import java.util.Arrays;
17 import java.util.Base64;
18 import java.util.Map;
19
20 import javax.servlet.http.HttpServletRequest;
21
22 import club.wpia.gigi.GigiApiException;
23 import club.wpia.gigi.dbObjects.Certificate;
24 import club.wpia.gigi.dbObjects.Certificate.CertificateStatus;
25 import club.wpia.gigi.dbObjects.Job;
26 import club.wpia.gigi.localisation.Language;
27 import club.wpia.gigi.output.template.Form;
28 import club.wpia.gigi.output.template.Template;
29 import club.wpia.gigi.output.template.TranslateCommand;
30 import club.wpia.gigi.util.PEM;
31 import club.wpia.gigi.util.RandomToken;
32 import club.wpia.gigi.util.RateLimit;
33 import club.wpia.gigi.util.RateLimit.RateLimitException;
34
35 public class KeyCompromiseForm extends Form {
36
37     private static final Template t = new Template(KeyCompromiseForm.class.getResource("KeyCompromiseForm.templ"));
38
39     // 50 per 5 min
40     public static final RateLimit RATE_LIMIT = new RateLimit(50, 5 * 60 * 1000);
41
42     private final String challenge;
43
44     public static final String CHALLENGE_PREFIX = "This private key has been compromised. Challenge: ";
45
46     public static final TranslateCommand NOT_LOADED = new TranslateCommand("Certificate could not be loaded");
47
48     public static final TranslateCommand NOT_FOUND = new TranslateCommand("Certificate to revoke not found");
49
50     public KeyCompromiseForm(HttpServletRequest hsr) {
51         super(hsr);
52         challenge = RandomToken.generateToken(32);
53     }
54
55     @Override
56     public SubmissionResult submit(HttpServletRequest req) throws GigiApiException {
57         if (RATE_LIMIT.isLimitExceeded(req.getRemoteAddr())) {
58             throw new RateLimitException();
59         }
60         Certificate c = null;
61         X509Certificate cert = null;
62         String serial = req.getParameter("serial");
63         String certData = req.getParameter("cert");
64         if (serial != null && !serial.isEmpty()) {
65             c = fetchCertificate(serial);
66             try {
67                 cert = c.cert();
68             } catch (IOException e) {
69                 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
70             } catch (GeneralSecurityException e) {
71                 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
72             }
73         }
74         if (certData != null && !certData.isEmpty()) {
75             X509Certificate c0;
76             byte[] supplied;
77             try {
78                 supplied = PEM.decode("CERTIFICATE", certData);
79                 c0 = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(supplied));
80             } catch (IllegalArgumentException e1) {
81                 throw new PermamentFormException(new GigiApiException("Your certificate could not be parsed"));
82             } catch (CertificateException e1) {
83                 throw new PermamentFormException(new GigiApiException("Your certificate could not be parsed"));
84             }
85             try {
86                 String ser = c0.getSerialNumber().toString(16);
87                 c = fetchCertificate(ser);
88                 cert = c.cert();
89                 if ( !Arrays.equals(supplied, cert.getEncoded())) {
90                     throw new PermamentFormException(new GigiApiException(NOT_FOUND));
91                 }
92             } catch (IOException e) {
93                 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
94             } catch (GeneralSecurityException e) {
95                 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
96             }
97         }
98         if (c == null) {
99             throw new PermamentFormException(new GigiApiException("No certificate identification information provided"));
100         }
101         if (c.getStatus() == CertificateStatus.REVOKED) {
102             return new SuccessMessageResult(new TranslateCommand("Certificate had already been revoked"));
103         }
104         String inSig = req.getParameter("signature");
105         byte[] signature = null;
106         if (inSig != null && !inSig.isEmpty()) {
107             try {
108                 signature = Base64.getDecoder().decode(inSig);
109             } catch (IllegalArgumentException e) {
110                 throw new PermamentFormException(new GigiApiException("Signature is malformed"));
111             }
112         }
113         String priv = req.getParameter("priv");
114         if (signature == null && priv != null && !priv.isEmpty()) {
115             try {
116                 PKCS8EncodedKeySpec k = new PKCS8EncodedKeySpec(PEM.decode("PRIVATE KEY", priv));
117                 RSAPrivateKey pk = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(k);
118                 signature = sign(pk, challenge);
119             } catch (IllegalArgumentException e) {
120                 throw new PermamentFormException(new GigiApiException("Private Key is malformed"));
121             } catch (GeneralSecurityException e) {
122                 throw new PermamentFormException(new GigiApiException("Private Key is malformed"));
123             }
124         }
125         if (signature == null) {
126             throw new PermamentFormException(new GigiApiException("No verification provided."));
127         }
128
129         try {
130             Signature sig = Signature.getInstance("SHA256withRSA");
131             sig.initVerify(cert.getPublicKey());
132             sig.update(CHALLENGE_PREFIX.getBytes("UTF-8"));
133             sig.update(challenge.getBytes("UTF-8"));
134             if ( !sig.verify(signature)) {
135                 throw new PermamentFormException(new GigiApiException("Verification does not match."));
136             }
137         } catch (GeneralSecurityException e) {
138             throw new PermamentFormException(new GigiApiException("Wasn't able to generate signature."));
139         } catch (UnsupportedEncodingException e) {
140             throw new RuntimeException(e);
141         }
142         Job j = c.revoke(challenge, Base64.getEncoder().encodeToString(signature), "");
143         if ( !j.waitFor(60000)) {
144             throw new PermamentFormException(new GigiApiException("Revocation timed out."));
145         }
146         if (c.getStatus() != CertificateStatus.REVOKED) {
147             throw new PermamentFormException(new GigiApiException("Revocation failed."));
148         }
149         return new SuccessMessageResult(new TranslateCommand("Certificate is revoked."));
150     }
151
152     public static byte[] sign(PrivateKey pk, String challenge) throws GeneralSecurityException {
153         byte[] signature;
154         Signature sig = Signature.getInstance("SHA256withRSA");
155         sig.initSign(pk);
156         try {
157             sig.update(CHALLENGE_PREFIX.getBytes("UTF-8"));
158             sig.update(challenge.getBytes("UTF-8"));
159         } catch (UnsupportedEncodingException e) {
160             throw new RuntimeException(e);
161         }
162         signature = sig.sign();
163         return signature;
164     }
165
166     private Certificate fetchCertificate(String serial) {
167         Certificate c;
168         serial = serial.trim().toLowerCase();
169         int idx = 0;
170         while (idx < serial.length() && serial.charAt(idx) == '0') {
171             idx++;
172         }
173         serial = serial.substring(idx);
174         c = Certificate.getBySerial(serial);
175         if (c == null) {
176             throw new PermamentFormException(new GigiApiException(NOT_FOUND));
177         }
178         return c;
179     }
180
181     @Override
182     protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
183         vars.put("challenge", challenge);
184         vars.put("challengePrefix", CHALLENGE_PREFIX);
185         t.output(out, l, vars);
186     }
187
188 }