Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #11892 - mtls not working with http/3. #11900

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -423,6 +427,31 @@ private void finishOutwardClose(Throwable failure)
}
}

/**
* <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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe javadoc that only the top/last/single certificate will be returned.

{
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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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;

Expand Down Expand Up @@ -221,6 +222,15 @@ public Object getTransport()
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ public final int feedClearBytesForStream(long streamId, ByteBuffer buffer) throw

public abstract CloseInfo getLocalCloseInfo();

public abstract byte[] getPeerCertificate();

public static class CloseInfo
{
private final long error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ public static ForeignQuicheConnection tryAccept(QuicheConfig quicheConfig, Token
}
}

@Override
public byte[] getPeerCertificate()
{
try (AutoLock ignore = lock.lock())
Expand All @@ -532,7 +533,7 @@ public byte[] getPeerCertificate()
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ public void enableQlog(String filename, String title, String desc) throws IOExce
}
}

@Override
public byte[] getPeerCertificate()
{
try (AutoLock ignore = lock.lock())
Expand All @@ -424,6 +425,8 @@ public byte[] getPeerCertificate()
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ public static Collection<Transport> transportsWithPushSupport()
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()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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