1 package club.wpia.gigi.pages.main;
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;
20 import javax.servlet.http.HttpServletRequest;
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;
35 public class KeyCompromiseForm extends Form {
37 private static final Template t = new Template(KeyCompromiseForm.class.getResource("KeyCompromiseForm.templ"));
40 public static final RateLimit RATE_LIMIT = new RateLimit(50, 5 * 60 * 1000);
42 private final String challenge;
44 public static final String CHALLENGE_PREFIX = "This private key has been compromised. Challenge: ";
46 public static final TranslateCommand NOT_LOADED = new TranslateCommand("Certificate could not be loaded");
48 public static final TranslateCommand NOT_FOUND = new TranslateCommand("Certificate to revoke not found");
50 public KeyCompromiseForm(HttpServletRequest hsr) {
52 challenge = RandomToken.generateToken(16);
56 public SubmissionResult submit(HttpServletRequest req) throws GigiApiException {
57 if (RATE_LIMIT.isLimitExceeded(req.getRemoteAddr())) {
58 throw new RateLimitException();
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);
68 } catch (IOException e) {
69 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
70 } catch (GeneralSecurityException e) {
71 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
74 if (certData != null && !certData.isEmpty()) {
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"));
86 String ser = c0.getSerialNumber().toString(16);
87 c = fetchCertificate(ser);
89 if ( !Arrays.equals(supplied, cert.getEncoded())) {
90 throw new PermamentFormException(new GigiApiException(NOT_FOUND));
92 } catch (IOException e) {
93 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
94 } catch (GeneralSecurityException e) {
95 throw new PermamentFormException(new GigiApiException(NOT_LOADED));
99 throw new PermamentFormException(new GigiApiException("No certificate identification information provided"));
101 if (c.getStatus() == CertificateStatus.REVOKED) {
102 return new SuccessMessageResult(new TranslateCommand("Certificate already had been revoked"));
104 String inSig = req.getParameter("signature");
105 byte[] signature = null;
106 if (inSig != null && !inSig.isEmpty()) {
108 signature = Base64.getDecoder().decode(inSig);
109 } catch (IllegalArgumentException e) {
110 throw new PermamentFormException(new GigiApiException("Signature is malformed"));
113 String priv = req.getParameter("priv");
114 if (signature == null && priv != null && !priv.isEmpty()) {
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"));
125 if (signature == null) {
126 throw new PermamentFormException(new GigiApiException("No verification provided."));
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."));
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);
142 Job j = c.revoke(challenge, Base64.getEncoder().encodeToString(signature), "");
143 if ( !j.waitFor(60000)) {
144 throw new PermamentFormException(new GigiApiException("Revocation timed out."));
146 if (c.getStatus() != CertificateStatus.REVOKED) {
147 throw new PermamentFormException(new GigiApiException("Revocation failed."));
149 return new SuccessMessageResult(new TranslateCommand("Certificate is revoked."));
152 public static byte[] sign(PrivateKey pk, String challenge) throws GeneralSecurityException {
154 Signature sig = Signature.getInstance("SHA256withRSA");
157 sig.update(CHALLENGE_PREFIX.getBytes("UTF-8"));
158 sig.update(challenge.getBytes("UTF-8"));
159 } catch (UnsupportedEncodingException e) {
160 throw new RuntimeException(e);
162 signature = sig.sign();
166 private Certificate fetchCertificate(String serial) {
168 serial = serial.trim().toLowerCase();
170 while (idx < serial.length() && serial.charAt(idx) == '0') {
173 serial = serial.substring(idx);
174 c = Certificate.getBySerial(serial);
176 throw new PermamentFormException(new GigiApiException(NOT_FOUND));
182 protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
183 vars.put("challenge", challenge);
184 t.output(out, l, vars);