1 package club.wpia.gigi.ocsp;
4 import java.io.FileInputStream;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.OutputStreamWriter;
9 import java.security.GeneralSecurityException;
10 import java.security.InvalidKeyException;
11 import java.security.KeyPair;
12 import java.security.KeyPairGenerator;
13 import java.security.KeyStore;
14 import java.security.MessageDigest;
15 import java.security.NoSuchAlgorithmException;
16 import java.security.PrivateKey;
17 import java.security.PublicKey;
18 import java.security.SecureRandom;
19 import java.security.Signature;
20 import java.security.SignatureException;
21 import java.security.cert.Certificate;
22 import java.security.cert.CertificateFactory;
23 import java.security.cert.X509Certificate;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.HashMap;
29 import club.wpia.gigi.util.PEM;
30 import sun.security.pkcs10.PKCS10;
31 import sun.security.pkcs10.PKCS10Attributes;
32 import sun.security.x509.AlgorithmId;
33 import sun.security.x509.X500Name;
36 * Manages the set of {@link OCSPIssuer}s by updating their OCSP certificates
37 * and renewing the issuers. The Thread executing all the management work has to
38 * be started manually. The {@link #get(AlgorithmId)} method provides a
39 * requested set of issuers.
41 public class OCSPIssuerManager implements Runnable {
43 private final Map<String, KeyPair> openRequests = new HashMap<>();
45 private Map<AlgorithmId, Map<OCSPIssuerId, OCSPIssuer>> map = new HashMap<>();
47 private long nextHousekeeping;
49 private boolean isGood(PrivateKey k, Certificate target, Certificate parent) throws GeneralSecurityException {
50 X509Certificate ocsp = (X509Certificate) target;
51 if ( !ocsp.getExtendedKeyUsage().contains("1.3.6.1.5.5.7.3.9")) {
52 OCSPResponder.log.severe("OCSP cert does not have correct EKU set.");
55 target.verify(parent.getPublicKey());
56 if ( !matches(k, target.getPublicKey())) {
57 OCSPResponder.log.severe("Public key contained in cert does not match.");
63 private boolean matches(PrivateKey k, PublicKey pk) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
64 SecureRandom ref = new SecureRandom();
65 Signature s = Signature.getInstance("SHA512WithRSA");
67 byte[] data = new byte[20];
70 byte[] signature = s.sign();
73 boolean verify = s.verify(signature);
77 private void index(AlgorithmId aid, MessageDigest md, Map<String, OCSPIssuer> toServe, Map<AlgorithmId, Map<OCSPIssuerId, OCSPIssuer>> map) {
78 OCSPResponder.log.fine("Indexing OCSP issuers for " + md);
79 HashMap<OCSPIssuerId, OCSPIssuer> issuers = new HashMap<>();
80 for (OCSPIssuer i : toServe.values()) {
81 issuers.put(new OCSPIssuerId(aid, md, i.getTarget()), i);
83 map.put(aid, Collections.unmodifiableMap(issuers));
87 * Scans for CAs to issue OCSP responses for in the directory f.
90 * The directory to scan recursively.
92 * a keystore with all private keys for all OCSP certificates
93 * (will be used and updated with new ocsp certs)
95 * A map with {@link OCSPIssuer}s to be populated with all
98 private void scanAndUpdateCAs(File f, KeyStore keys, Map<String, OCSPIssuer> toServe) {
99 if (f.isDirectory()) {
100 File[] list = f.listFiles();
105 for (File file : list) {
106 scanAndUpdateCAs(file, keys, toServe);
111 if ( !f.getName().equals("ca.crt")) {
115 String keyName = f.getParentFile().getName();
116 updateCA(f, keyName, keys, toServe);
117 } catch (GeneralSecurityException e) {
119 } catch (IOException e) {
125 private void updateCA(File f, String keyName, KeyStore keys, Map<String, OCSPIssuer> toServe) throws GeneralSecurityException, IOException {
126 X509Certificate parent = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new FileInputStream(f));
128 Certificate[] storedCertificateChain = keys.getCertificateChain(keyName);
130 if (storedCertificateChain == null) {
131 OCSPResponder.log.info("Keystore entry for OCSP certificate for CA " + keyName + " was not found");
133 if ( !storedCertificateChain[1].equals(parent)) {
134 OCSPResponder.log.severe("unexpeced CA certificate in keystore entry for OCSP certificate.");
137 PrivateKey key = (PrivateKey) keys.getKey(keyName, "pass".toCharArray());
138 isGood(key, storedCertificateChain[0], storedCertificateChain[1]);
139 toServe.put(keyName, new OCSPIssuer((X509Certificate) storedCertificateChain[1], (X509Certificate) storedCertificateChain[0], key));
141 boolean hasKeyRequest = openRequests.containsKey(keyName);
142 File ocspCsr = new File(f.getParentFile(), "ocsp.csr");
143 File ocspCrt = new File(f.getParentFile(), "ocsp.crt");
145 KeyPair r = openRequests.get(keyName);
146 if (ocspCrt.exists()) {
147 X509Certificate x = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new FileInputStream(ocspCrt));
148 // attempt to load ocspCrt, try if it matches with the
150 // if it does match: load crt with key into primary
151 // keystore entry. Update Issuer
152 if (r.getPublic().equals(x.getPublicKey()) && isGood(r.getPrivate(), x, parent)) {
153 OCSPResponder.log.info("Loading OCSP Certificate");
154 keys.setKeyEntry(keyName, r.getPrivate(), "pass".toCharArray(), new Certificate[] {
157 openRequests.remove(keyName);
160 toServe.put(keyName, new OCSPIssuer(parent, x, r.getPrivate()));
162 // if it does not match: check CSR
163 OCSPResponder.log.severe("OCSP certificate does not fit.");
166 // Seems the signer should work now.. Let's wait for
170 if (keys.containsAlias(keyName)) {
171 X509Certificate c = (X509Certificate) keys.getCertificate(keyName);
172 Date expiery = c.getNotAfter();
173 Date now = new Date();
174 long deltas = expiery.getTime() - now.getTime();
176 deltas /= 60 * 60 * 24;
177 OCSPResponder.log.fine("Remaining days for OCSP certificate: " + deltas);
178 if (deltas > 30 * 3) {
182 OCSPResponder.log.info("Requesting OCSP certificate");
183 // request a new OCSP certificate with a new RSA-key.
184 requestNewOCSPCert(keyName, ocspCsr, ocspCrt);
186 // assuming this cert will be ready in 30 seconds
187 nextHousekeeping = Math.min(nextHousekeeping, System.currentTimeMillis() + 30 * 1000);
191 private void requestNewOCSPCert(String keyName, File ocspCsr, File ocspCrt) throws IOException, GeneralSecurityException {
192 KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
193 kpg.initialize(4096);
194 KeyPair kp = kpg.generateKeyPair();
195 openRequests.put(keyName, kp);
196 PKCS10 p10 = new PKCS10(kp.getPublic(), new PKCS10Attributes());
197 Signature s = Signature.getInstance("SHA512WithRSA");
198 s.initSign(kp.getPrivate());
199 p10.encodeAndSign(new X500Name("CN=OSCP Responder"), s);
202 String csr = PEM.encode("CERTIFICATE REQUEST", p10.getEncoded());
203 try (FileOutputStream fos = new FileOutputStream(ocspCsr); Writer w = new OutputStreamWriter(fos, "UTF-8")) {
210 File ks = new File("ocsp.pkcs12");
211 File f = new File("ocsp");
212 if ( !ks.exists() || !ks.isFile() || !f.exists() || !f.isDirectory()) {
213 OCSPResponder.log.info("OCSP issuing is not configured");
219 nextHousekeeping = System.currentTimeMillis() + 60 * 60 * 1000;
223 keys = KeyStore.getInstance("PKCS12");
225 if (ks.length() == 0) {
228 try (FileInputStream ks_file = new FileInputStream(ks)) {
229 keys.load(ks_file, "pass".toCharArray());
233 // assuming ocsp is disabled
236 } catch (GeneralSecurityException e) {
238 } catch (IOException e) {
241 Map<String, OCSPIssuer> toServe = new HashMap<>();
243 scanAndUpdateCAs(f, keys, toServe);
244 try (FileOutputStream ks_file = new FileOutputStream(ks)) {
245 keys.store(ks_file, "pass".toCharArray());
246 } catch (GeneralSecurityException e) {
248 } catch (IOException e) {
253 Map<AlgorithmId, Map<OCSPIssuerId, OCSPIssuer>> map = new HashMap<>();
254 index(AlgorithmId.get("SHA-1"), MessageDigest.getInstance("SHA-1"), toServe, map);
255 index(AlgorithmId.get("SHA-256"), MessageDigest.getInstance("SHA-256"), toServe, map);
256 synchronized (this) {
257 this.map = Collections.unmodifiableMap(map);
259 } catch (NoSuchAlgorithmException e) {
262 } catch (Throwable t) {
266 long dt = Math.max(3000, nextHousekeeping - System.currentTimeMillis());
268 } catch (InterruptedException e) {
274 public synchronized Map<OCSPIssuerId, OCSPIssuer> get(AlgorithmId alg) {