The client certificate is now exposed in QuicheConnection, so that it can be returned by QuicStreamEndPoint.getSslSessionData(). Not much else is exposed by Quiche, so not much else that we can provide to applications, for example no TLS session id, no cipher suite, etc. Fixed --enable-native-access command line option to run tests, as the foreign dependency is in the class-path. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
9d0a457e59
commit
8e6ab939f5
|
@ -13,10 +13,14 @@
|
|||
|
||||
package org.eclipse.jetty.quic.common;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EventListener;
|
||||
|
@ -423,6 +427,31 @@ public abstract class QuicSession extends ContainerLifeCycle
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the peer certificates chain.</p>
|
||||
* <p>Due to current Quiche C API limitations (that the Rust version does not have),
|
||||
* only the last certificate in the chain is returned.
|
||||
* This may change in the future when the C APIs are aligned to the Rust APIs.</p>
|
||||
*
|
||||
* @return the peer certificates chain (currently only the last certificate in the chain)
|
||||
*/
|
||||
public X509Certificate[] getPeerCertificates()
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] encoded = quicheConnection.getPeerCertificate();
|
||||
if (encoded == null)
|
||||
return null;
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X509");
|
||||
X509Certificate certificate = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(encoded));
|
||||
return new X509Certificate[]{certificate};
|
||||
}
|
||||
catch (CertificateException x)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.quic.common;
|
|||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
|
@ -221,6 +222,15 @@ public class QuicStreamEndPoint extends AbstractEndPoint
|
|||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslSessionData getSslSessionData()
|
||||
{
|
||||
X509Certificate[] peerCertificates = getQuicSession().getPeerCertificates();
|
||||
if (peerCertificates == null)
|
||||
return null;
|
||||
return SslSessionData.from(null, null, null, peerCertificates);
|
||||
}
|
||||
|
||||
public void onWritable()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -152,6 +152,8 @@ public abstract class QuicheConnection
|
|||
|
||||
public abstract CloseInfo getLocalCloseInfo();
|
||||
|
||||
public abstract byte[] getPeerCertificate();
|
||||
|
||||
public static class CloseInfo
|
||||
{
|
||||
private final long error;
|
||||
|
|
|
@ -518,6 +518,7 @@ public class ForeignQuicheConnection extends QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPeerCertificate()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -532,7 +533,7 @@ public class ForeignQuicheConnection extends QuicheConnection
|
|||
quiche_h.quiche_conn_peer_cert(quicheConn, outSegment, outLenSegment);
|
||||
|
||||
long outLen = outLenSegment.get(NativeHelper.C_LONG, 0L);
|
||||
if (outLen == 0L)
|
||||
if (outLen <= 0L)
|
||||
return null;
|
||||
byte[] out = new byte[(int)outLen];
|
||||
// dereference outSegment pointer
|
||||
|
|
|
@ -413,6 +413,7 @@ public class JnaQuicheConnection extends QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPeerCertificate()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -424,6 +425,8 @@ public class JnaQuicheConnection extends QuicheConnection
|
|||
size_t_pointer out_len = new size_t_pointer();
|
||||
LibQuiche.INSTANCE.quiche_conn_peer_cert(quicheConn, out, out_len);
|
||||
int len = out_len.getPointee().intValue();
|
||||
if (len <= 0)
|
||||
return null;
|
||||
return out.getValueAsBytes(len);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
<configuration>
|
||||
<argLine>@{argLine}
|
||||
${jetty.surefire.argLine}
|
||||
--enable-native-access org.eclipse.jetty.quic.quiche.foreign</argLine>
|
||||
--enable-native-access=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
|
|
@ -102,6 +102,14 @@ public class AbstractTest
|
|||
return transports;
|
||||
}
|
||||
|
||||
public static Collection<Transport> transportsSecure()
|
||||
{
|
||||
EnumSet<Transport> transports = EnumSet.of(Transport.HTTPS, Transport.H2, Transport.H3);
|
||||
if ("ci".equals(System.getProperty("env")))
|
||||
transports.remove(Transport.H3);
|
||||
return transports;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void prepare()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.test.client.transport;
|
||||
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class NeedClientAuthTest extends AbstractTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsSecure")
|
||||
public void testNeedClientAuth(Transport transport) throws Exception
|
||||
{
|
||||
prepareServer(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
// Verify that the request attribute is present.
|
||||
assertNotNull(request.getAttribute(ServletContextRequest.PEER_CERTIFICATES));
|
||||
}
|
||||
});
|
||||
sslContextFactoryServer.setNeedClientAuth(true);
|
||||
server.start();
|
||||
|
||||
startClient(transport, httpClient ->
|
||||
{
|
||||
// Configure the SslContextFactory to send a certificate to the server.
|
||||
SslContextFactory.Client clientSSL = httpClient.getSslContextFactory();
|
||||
clientSSL.setKeyStorePath("src/test/resources/keystore.p12");
|
||||
clientSSL.setKeyStorePassword("storepwd");
|
||||
clientSSL.setCertAlias("mykey");
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest(newURI(transport)).send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue