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