1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.pkitesting;
17
18 import com.sun.net.httpserver.HttpServer;
19
20 import java.io.OutputStream;
21 import java.math.BigInteger;
22 import java.net.InetAddress;
23 import java.net.InetSocketAddress;
24 import java.net.URI;
25 import java.security.cert.X509Certificate;
26 import java.time.Instant;
27 import java.util.Collections;
28 import java.util.Map;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.ForkJoinPool;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34
35
36
37
38
39
40
41
42
43
44
45 public final class RevocationServer {
46 private static volatile RevocationServer instance;
47
48 private final HttpServer crlServer;
49 private final String crlBaseAddress;
50 private final AtomicInteger issuerCounter;
51 private final ConcurrentMap<X509Certificate, CrlInfo> issuers;
52 private final ConcurrentMap<String, CrlInfo> paths;
53
54
55
56
57
58
59
60 public static RevocationServer getInstance() throws Exception {
61 if (instance != null) {
62 return instance;
63 }
64 synchronized (RevocationServer.class) {
65 RevocationServer server = instance;
66 if (server == null) {
67 server = new RevocationServer();
68 server.start();
69 instance = server;
70 }
71 return server;
72 }
73 }
74
75 private RevocationServer() throws Exception {
76
77 crlServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
78 crlBaseAddress = "http://localhost:" + crlServer.getAddress().getPort();
79 issuerCounter = new AtomicInteger();
80 issuers = new ConcurrentHashMap<>();
81 paths = new ConcurrentHashMap<>();
82 crlServer.createContext("/", exchange -> {
83 if ("GET".equals(exchange.getRequestMethod())) {
84 String path = exchange.getRequestURI().getPath();
85 CrlInfo info = paths.get(path);
86 if (info == null) {
87 exchange.sendResponseHeaders(404, 0);
88 exchange.close();
89 return;
90 }
91 byte[] crl = generateCrl(info);
92 exchange.getResponseHeaders().put("Content-Type", Collections.singletonList("application/pkix-crl"));
93 exchange.sendResponseHeaders(200, crl.length);
94 try (OutputStream out = exchange.getResponseBody()) {
95 out.write(crl);
96 out.flush();
97 }
98 } else {
99 exchange.sendResponseHeaders(405, 0);
100 }
101 exchange.close();
102 });
103 }
104
105 private void start() {
106 if (Thread.currentThread().isDaemon()) {
107 crlServer.start();
108 } else {
109
110
111
112
113 ForkJoinPool.commonPool().execute(crlServer::start);
114 }
115 }
116
117
118
119
120
121
122 public void register(X509Bundle issuer) {
123 issuers.computeIfAbsent(issuer.getCertificate(), bundle -> {
124 String path = "/crl/" + issuerCounter.incrementAndGet() + ".crl";
125 URI uri = URI.create(crlBaseAddress + path);
126 CrlInfo info = new CrlInfo(issuer, uri);
127 paths.put(path, info);
128 return info;
129 });
130 }
131
132
133
134
135
136
137
138
139
140 public void revoke(X509Bundle cert, Instant time) {
141 X509Certificate[] certPath = cert.getCertificatePathWithRoot();
142 X509Certificate issuer = certPath.length == 1 ? certPath[0] : certPath[1];
143 CrlInfo info = issuers.get(issuer);
144 if (info != null) {
145 info.revokedCerts.put(cert.getCertificate().getSerialNumber(), time);
146 } else {
147 throw new IllegalArgumentException("Not a registered issuer: " + issuer.getSubjectX500Principal());
148 }
149 }
150
151
152
153
154
155
156
157 public URI getCrlUri(X509Bundle issuer) {
158 CrlInfo info = issuers.get(issuer.getCertificate());
159 if (info != null) {
160 return info.uri;
161 }
162 return null;
163 }
164
165 private static byte[] generateCrl(CrlInfo info) {
166 X509Bundle issuer = info.issuer;
167 Map<BigInteger, Instant> certs = info.revokedCerts;
168 Instant now = Instant.now();
169 CertificateList list = new CertificateList(issuer, now, now, certs.entrySet());
170 try {
171 Signed signed = new Signed(list.getEncoded(), issuer);
172 return signed.getEncoded();
173 } catch (Exception e) {
174 throw new IllegalStateException("Failed to sign CRL", e);
175 }
176 }
177
178 private static final class CrlInfo {
179 private final X509Bundle issuer;
180 private final URI uri;
181 private final Map<BigInteger, Instant> revokedCerts;
182
183 CrlInfo(X509Bundle issuer, URI uri) {
184 this.issuer = issuer;
185 this.uri = uri;
186 revokedCerts = new ConcurrentHashMap<>();
187 }
188 }
189 }