1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.quic;
17
18 import io.netty.handler.ssl.ClientAuth;
19 import io.netty.handler.ssl.util.LazyJavaxX509Certificate;
20 import io.netty.handler.ssl.util.LazyX509Certificate;
21 import io.netty.util.NetUtil;
22 import io.netty.util.internal.EmptyArrays;
23 import io.netty.util.internal.ObjectUtil;
24 import org.jetbrains.annotations.Nullable;
25
26 import javax.net.ssl.SNIHostName;
27 import javax.net.ssl.SNIServerName;
28 import javax.net.ssl.SSLEngineResult;
29 import javax.net.ssl.SSLParameters;
30 import javax.net.ssl.SSLPeerUnverifiedException;
31 import javax.net.ssl.SSLSession;
32 import javax.net.ssl.SSLSessionBindingEvent;
33 import javax.net.ssl.SSLSessionBindingListener;
34 import javax.net.ssl.SSLSessionContext;
35 import javax.security.cert.X509Certificate;
36 import java.nio.ByteBuffer;
37 import java.security.Principal;
38 import java.security.cert.Certificate;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.function.Consumer;
45 import java.util.function.LongFunction;
46
47 final class QuicheQuicSslEngine extends QuicSslEngine {
48 QuicheQuicSslContext ctx;
49 private final String peerHost;
50 private final int peerPort;
51 private final QuicheQuicSslSession session = new QuicheQuicSslSession();
52 private volatile Certificate[] localCertificateChain;
53 private List<SNIServerName> sniHostNames;
54 private boolean handshakeFinished;
55 private String applicationProtocol;
56 private boolean sessionReused;
57 final String tlsHostName;
58 volatile QuicheQuicConnection connection;
59
60 volatile Consumer<String> sniSelectedCallback;
61
62 QuicheQuicSslEngine(QuicheQuicSslContext ctx, @Nullable String peerHost, int peerPort) {
63 this.ctx = ctx;
64 this.peerHost = peerHost;
65 this.peerPort = peerPort;
66
67
68 if (ctx.isClient() && isValidHostNameForSNI(peerHost)) {
69 tlsHostName = peerHost;
70 sniHostNames = Collections.singletonList(new SNIHostName(tlsHostName));
71 } else {
72 tlsHostName = null;
73 }
74 }
75
76 long moveTo(String hostname, QuicheQuicSslContext ctx) {
77
78 this.ctx.remove(this);
79 this.ctx = ctx;
80 long added = ctx.add(this);
81 Consumer<String> sniSelectedCallback = this.sniSelectedCallback;
82 if (sniSelectedCallback != null) {
83 sniSelectedCallback.accept(hostname);
84 }
85 return added;
86 }
87
88 @Nullable
89 QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator) {
90 return ctx.createConnection(connectionCreator, this);
91 }
92
93 void setLocalCertificateChain(Certificate[] localCertificateChain) {
94 this.localCertificateChain = localCertificateChain;
95 }
96
97
98
99
100 static boolean isValidHostNameForSNI(@Nullable String hostname) {
101 return hostname != null &&
102 hostname.indexOf('.') > 0 &&
103 !hostname.endsWith(".") &&
104 !NetUtil.isValidIpV4Address(hostname) &&
105 !NetUtil.isValidIpV6Address(hostname);
106 }
107
108 @Override
109 public SSLParameters getSSLParameters() {
110 SSLParameters parameters = super.getSSLParameters();
111 parameters.setServerNames(sniHostNames);
112 return parameters;
113 }
114
115
116
117 public synchronized String getApplicationProtocol() {
118 return applicationProtocol;
119 }
120
121
122
123 public synchronized String getHandshakeApplicationProtocol() {
124 return applicationProtocol;
125 }
126
127 @Override
128 public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) {
129 throw new UnsupportedOperationException();
130 }
131
132 @Override
133 public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) {
134 throw new UnsupportedOperationException();
135 }
136
137 @Override
138 @Nullable
139 public Runnable getDelegatedTask() {
140 return null;
141 }
142
143 @Override
144 public void closeInbound() {
145 throw new UnsupportedOperationException();
146 }
147
148 @Override
149 public boolean isInboundDone() {
150 return false;
151 }
152
153 @Override
154 public void closeOutbound() {
155 throw new UnsupportedOperationException();
156 }
157
158 @Override
159 public boolean isOutboundDone() {
160 return false;
161 }
162
163 @Override
164 public String[] getSupportedCipherSuites() {
165 return ctx.cipherSuites().toArray(new String[0]);
166 }
167
168 @Override
169 public String[] getEnabledCipherSuites() {
170 return getSupportedCipherSuites();
171 }
172
173 @Override
174 public void setEnabledCipherSuites(String[] suites) {
175 throw new UnsupportedOperationException();
176 }
177
178 @Override
179 public String[] getSupportedProtocols() {
180
181 return new String[] { "TLSv1.3" };
182 }
183
184 @Override
185 public String[] getEnabledProtocols() {
186 return getSupportedProtocols();
187 }
188
189 @Override
190 public void setEnabledProtocols(String[] protocols) {
191 throw new UnsupportedOperationException();
192 }
193
194 @Override
195 public SSLSession getSession() {
196 return session;
197 }
198
199 @Override
200 @Nullable
201 public SSLSession getHandshakeSession() {
202 if (handshakeFinished) {
203 return null;
204 }
205 return session;
206 }
207
208 @Override
209 public void beginHandshake() {
210
211 }
212
213 @Override
214 public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
215 if (handshakeFinished) {
216 return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
217 }
218 return SSLEngineResult.HandshakeStatus.NEED_WRAP;
219 }
220
221 @Override
222 public void setUseClientMode(boolean clientMode) {
223 if (clientMode != ctx.isClient()) {
224 throw new UnsupportedOperationException();
225 }
226 }
227
228 @Override
229 public boolean getUseClientMode() {
230 return ctx.isClient();
231 }
232
233 @Override
234 public void setNeedClientAuth(boolean b) {
235 throw new UnsupportedOperationException();
236 }
237
238 @Override
239 public boolean getNeedClientAuth() {
240 return ctx.clientAuth == ClientAuth.REQUIRE;
241 }
242
243 @Override
244 public void setWantClientAuth(boolean b) {
245 throw new UnsupportedOperationException();
246 }
247
248 @Override
249 public boolean getWantClientAuth() {
250 return ctx.clientAuth == ClientAuth.OPTIONAL;
251 }
252
253 @Override
254 public void setEnableSessionCreation(boolean flag) {
255 throw new UnsupportedOperationException();
256 }
257
258 @Override
259 public boolean getEnableSessionCreation() {
260 return false;
261 }
262
263 synchronized void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
264 byte[][] peerCertificateChain,
265 long creationTime, long timeout,
266 byte @Nullable [] applicationProtocol, boolean sessionReused) {
267 if (applicationProtocol == null) {
268 this.applicationProtocol = null;
269 } else {
270 this.applicationProtocol = new String(applicationProtocol);
271 }
272 session.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
273 this.sessionReused = sessionReused;
274 handshakeFinished = true;
275 }
276
277 void removeSessionFromCacheIfInvalid() {
278 session.removeFromCacheIfInvalid();
279 }
280
281 synchronized boolean isSessionReused() {
282 return sessionReused;
283 }
284
285 private final class QuicheQuicSslSession implements SSLSession {
286 private X509Certificate[] x509PeerCerts;
287 private Certificate[] peerCerts;
288 private String protocol;
289 private String cipher;
290 private byte[] id;
291 private long creationTime = -1;
292 private long timeout = -1;
293 private boolean invalid;
294 private long lastAccessedTime = -1;
295
296
297 private Map<String, Object> values;
298
299 private boolean isEmpty(Object @Nullable [] arr) {
300 return arr == null || arr.length == 0;
301 }
302 private boolean isEmpty(byte @Nullable [] arr) {
303 return arr == null || arr.length == 0;
304 }
305
306 void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
307 byte[][] peerCertificateChain, long creationTime, long timeout) {
308 synchronized (QuicheQuicSslEngine.this) {
309 initPeerCerts(peerCertificateChain, peerCertificate);
310 this.id = id;
311 this.cipher = cipher;
312 this.protocol = protocol;
313 this.creationTime = creationTime * 1000L;
314 this.timeout = timeout * 1000L;
315 lastAccessedTime = System.currentTimeMillis();
316 }
317 }
318
319 void removeFromCacheIfInvalid() {
320 if (!isValid()) {
321
322 removeFromCache();
323 }
324 }
325
326 private void removeFromCache() {
327
328 QuicClientSessionCache cache = ctx.getSessionCache();
329 if (cache != null) {
330 cache.removeSession(getPeerHost(), getPeerPort());
331 }
332 }
333
334
335
336
337
338 private void initPeerCerts(byte[][] chain, byte[] clientCert) {
339
340 if (getUseClientMode()) {
341 if (isEmpty(chain)) {
342 peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
343 x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
344 } else {
345 peerCerts = new Certificate[chain.length];
346 x509PeerCerts = new X509Certificate[chain.length];
347 initCerts(chain, 0);
348 }
349 } else {
350
351
352
353
354
355 if (isEmpty(clientCert)) {
356 peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
357 x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
358 } else {
359 if (isEmpty(chain)) {
360 peerCerts = new Certificate[] {new LazyX509Certificate(clientCert)};
361 x509PeerCerts = new X509Certificate[] {new LazyJavaxX509Certificate(clientCert)};
362 } else {
363 peerCerts = new Certificate[chain.length + 1];
364 x509PeerCerts = new X509Certificate[chain.length + 1];
365 peerCerts[0] = new LazyX509Certificate(clientCert);
366 x509PeerCerts[0] = new LazyJavaxX509Certificate(clientCert);
367 initCerts(chain, 1);
368 }
369 }
370 }
371 }
372
373 private void initCerts(byte[][] chain, int startPos) {
374 for (int i = 0; i < chain.length; i++) {
375 int certPos = startPos + i;
376 peerCerts[certPos] = new LazyX509Certificate(chain[i]);
377 x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]);
378 }
379 }
380
381 @Override
382 public byte[] getId() {
383 synchronized (QuicheQuicSslSession.this) {
384 if (id == null) {
385 return EmptyArrays.EMPTY_BYTES;
386 }
387 return id.clone();
388 }
389 }
390
391 @Override
392 public SSLSessionContext getSessionContext() {
393 return ctx.sessionContext();
394 }
395
396 @Override
397 public long getCreationTime() {
398 synchronized (QuicheQuicSslEngine.this) {
399 return creationTime;
400 }
401 }
402
403 @Override
404 public long getLastAccessedTime() {
405 return lastAccessedTime;
406 }
407
408 @Override
409 public void invalidate() {
410 boolean removeFromCache;
411 synchronized (this) {
412 removeFromCache = !invalid;
413 invalid = true;
414 }
415 if (removeFromCache) {
416 removeFromCache();
417 }
418 }
419
420 @Override
421 public boolean isValid() {
422 synchronized (QuicheQuicSslEngine.this) {
423 return !invalid && System.currentTimeMillis() - timeout < creationTime;
424 }
425 }
426
427 @Override
428 public void putValue(String name, Object value) {
429 ObjectUtil.checkNotNull(name, "name");
430 ObjectUtil.checkNotNull(value, "value");
431
432 final Object old;
433 synchronized (this) {
434 Map<String, Object> values = this.values;
435 if (values == null) {
436
437 values = this.values = new HashMap<>(2);
438 }
439 old = values.put(name, value);
440 }
441
442 if (value instanceof SSLSessionBindingListener) {
443
444 ((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name));
445 }
446 notifyUnbound(old, name);
447 }
448
449 @Override
450 @Nullable
451 public Object getValue(String name) {
452 ObjectUtil.checkNotNull(name, "name");
453 synchronized (this) {
454 if (values == null) {
455 return null;
456 }
457 return values.get(name);
458 }
459 }
460
461 @Override
462 public void removeValue(String name) {
463 ObjectUtil.checkNotNull(name, "name");
464
465 final Object old;
466 synchronized (this) {
467 Map<String, Object> values = this.values;
468 if (values == null) {
469 return;
470 }
471 old = values.remove(name);
472 }
473
474 notifyUnbound(old, name);
475 }
476
477 @Override
478 public String[] getValueNames() {
479 synchronized (this) {
480 Map<String, Object> values = this.values;
481 if (values == null || values.isEmpty()) {
482 return EmptyArrays.EMPTY_STRINGS;
483 }
484 return values.keySet().toArray(new String[0]);
485 }
486 }
487
488 private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) {
489 return new SSLSessionBindingEvent(session, name);
490 }
491
492 private void notifyUnbound(@Nullable Object value, String name) {
493 if (value instanceof SSLSessionBindingListener) {
494
495 ((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name));
496 }
497 }
498
499 @Override
500 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
501 synchronized (QuicheQuicSslEngine.this) {
502 if (isEmpty(peerCerts)) {
503 throw new SSLPeerUnverifiedException("peer not verified");
504 }
505 return peerCerts.clone();
506 }
507 }
508
509 @Override
510 public Certificate @Nullable [] getLocalCertificates() {
511 Certificate[] localCerts = localCertificateChain;
512 if (localCerts == null) {
513 return null;
514 }
515 return localCerts.clone();
516 }
517
518 @Override
519 public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
520 synchronized (QuicheQuicSslEngine.this) {
521 if (isEmpty(x509PeerCerts)) {
522 throw new SSLPeerUnverifiedException("peer not verified");
523 }
524 return x509PeerCerts.clone();
525 }
526 }
527
528 @Override
529 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
530 Certificate[] peer = getPeerCertificates();
531
532
533 return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
534 }
535
536 @Override
537 @Nullable
538 public Principal getLocalPrincipal() {
539 Certificate[] local = localCertificateChain;
540 if (local == null || local.length == 0) {
541 return null;
542 }
543 return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
544 }
545
546 @Override
547 public String getCipherSuite() {
548 return cipher;
549 }
550
551 @Override
552 public String getProtocol() {
553 return protocol;
554 }
555
556 @Override
557 @Nullable
558 public String getPeerHost() {
559 return peerHost;
560 }
561
562 @Override
563 public int getPeerPort() {
564 return peerPort;
565 }
566
567 @Override
568 public int getPacketBufferSize() {
569 return -1;
570 }
571
572 @Override
573 public int getApplicationBufferSize() {
574 return -1;
575 }
576
577 @Override
578 public boolean equals(Object o) {
579 if (this == o) {
580 return true;
581 }
582 if (o == null || getClass() != o.getClass()) {
583 return false;
584 }
585 QuicheQuicSslSession that = (QuicheQuicSslSession) o;
586 return Arrays.equals(getId(), that.getId());
587 }
588
589 @Override
590 public int hashCode() {
591 return Arrays.hashCode(getId());
592 }
593 }
594 }