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

No way to set keystore for JSR 356 websocket clients, needed for SSL client authentication #155

Open
jmcc0nn3ll opened this issue Feb 16, 2016 · 32 comments
Assignees
Labels
Bug For general bugs on Jetty side Specification For all industry Specifications (IETF / Servlet / etc)

Comments

@jmcc0nn3ll
Copy link
Contributor

migrated from Bugzilla #442926
status ASSIGNED severity normal in component websocket for 9.3.x
Reported in version 9.2.2 on platform All
Assigned to: Joakim Erdfelt

On 2014-08-29 17:11:16 -0400, Stephen McCracken wrote:

I'm trying to write a websocket client that uses the standard JSR 356 APIs and the corresponding jetty implementation in javax-websocket-client-impl (9.2.2.v20140723). Without SSL client authentication, everything works. However, when I set up the jetty server to require client authentication (jetty.ssl.needClientAuth=true and jetty.ssl.wantClientAuth=true in start.d/ssl.ini), the client cannot connect.

If I run the jetty server with -Djavax.net.debug=SSL, the source of the problem seems to be that the client is not providing a certificate chain.

*** Certificate chain

qtp939427899-19, fatal error: 42: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain

Based on internet searching, this probably means that the client keystore is missing or has the wrong content.

Based on an inspection of the code for the following classes:

  • org.eclipse.jetty.util.ssl.SslContextFactory and
  • org.eclipse.jetty.websocket.jsr356.ClientContainer
  • org.eclipse.jetty.websocket.client.WebSocketClient
    I do not see a way to configure the client keystore.

I have tried several ways that did not work. Defining the following properties on the client command line did not help:
-Djavax.net.ssl.keyStore
-Djavax.net.ssl.keyStorePassword
-Djetty.keystore
-Djetty.keystore.password

I also tried manually configuring the SslContextFactory, but it seems that I can't get access to it until "too late". The following code produces the exception below it.

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
if (container instanceof ClientContainer) {
ClientContainer jettyClientContainer = (ClientContainer) container;
jettyClientContainer.getClient().getSslContextFactory().setKeyStorePath(keystorePath);
jettyClientContainer.getClient().getSslContextFactory().setKeyStorePassword(keystorePassword);
}

java.lang.IllegalStateException: Cannot modify configuration when STARTED
at org.eclipse.jetty.util.ssl.SslContextFactory.checkNotStarted(SslContextFactory.java:1114)
at org.eclipse.jetty.util.ssl.SslContextFactory.setKeyStorePath(SslContextFactory.java:439)
at org.example.WsTestClient.main(WsTestClient.java:49)

On 2014-08-29 18:16:51 -0400, Stephen McCracken wrote:

Tyrus claims to pick up a default keystore, but requires non-JSR classes to configure it in code.
https://tyrus.java.net/documentation/1.8.2/user-guide.html#d0e1128

On 2014-09-22 15:52:15 -0400, Joakim Erdfelt wrote:

That is absolutely 100% correct.
There is no way.

There is an open / unresolved spec bug for this at https://java.net/jira/browse/WEBSOCKET_SPEC-210

On 2015-12-28 17:06:36 -0500, Peter Robbins wrote:

This also impacts when you need to programmatically provide truststore to ClientContainer.

public ClientContainer(WebSocketContainerScope scope)
{
    boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");

    this.scopeDelegate = scope;
    client = new WebSocketClient(scope, new SslContextFactory(trustAll));
    client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
    SessionFactory sessionFactory = new JsrSessionFactory(this,this,client);
    client.setSessionFactory(sessionFactory);
    addBean(client);

    this.endpointClientMetadataCache = new ConcurrentHashMap<>();
    this.decoderFactory = new DecoderFactory(this,PrimitiveDecoderMetadataSet.INSTANCE);
    this.encoderFactory = new EncoderFactory(this,PrimitiveEncoderMetadataSet.INSTANCE);

    ShutdownThread.register(this);
}

WebSocketContainerScope has the getSslContextFactory() method that would allow to inject an SslContextFactory using a the proper truststore.

This line overrides that:
client = new WebSocketClient(scope, new SslContextFactory(trustAll));

This is seemingly done to consume the org.eclipse.jetty.websocket.jsr356.ssl-trust-all system property. I think that should be consumed somewhere other than the ClientContainer constructor. Perhaps somewhere in SimpleContainerScope?

@joakime
Copy link
Contributor

joakime commented Mar 7, 2016

Hopefully, the next version of JSR356 (javax.websocket) will address this flaw in the API.

@mohanrao
Copy link

mohanrao commented Mar 7, 2016

I agree with @jmcc0nn3ll to provide at least a fix the overloaded constructor in the next version

@joakime
Copy link
Contributor

joakime commented Mar 7, 2016

Since the only sanctioned way to create a JSR356 WebSocket client is to call the javax.websocket.ContainerProvider.getWebSocketContainer() ...

example:

    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
    EchoEndpoint echoer = new EchoEndpoint();
    Session session = container.connectToServer(echoer,URI.create("ws://remote/echo");
    session.getBasicRemote().sendText("Echo");

... there's no opportunity to use a new constructor version, even if it existed.

The functionality you are looking for simply does not exist for the JSR356 API.

It does, however, exist for the Jetty Native Websocket API (which is the original, non-JSR356, more capable, WebSocket API Jetty provides)

@pdubs10
Copy link

pdubs10 commented Mar 8, 2016

I can explain the use case where I ran into this.

I need ajavax.websocket.WebSocketContainer to pass into org.springframework.web.socket.client.standard.StandardWebSocketClient and I can build up a simple org.eclipse.jetty.websocket.jsr356.ClientContainer with a WebSocketContainerScope (SimpleContainerScope) that specifies the SslContextFactory, BUT the constructor does not use the SslContextFactory from the WebSocketContainer scope and instead creates a new one on the third line of the contructor in order to consume the org.eclipse.jetty.websocket.jsr356.ssl-trust-all system property.

public ClientContainer(WebSocketContainerScope scope)
{
    boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");

    this.scopeDelegate = scope;
    client = new WebSocketClient(scope, new SslContextFactory(trustAll));
    client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
    SessionFactory sessionFactory = new JsrSessionFactory(this,this,client);
    client.setSessionFactory(sessionFactory);
    addBean(client);

    this.endpointClientMetadataCache = new ConcurrentHashMap<>();
    this.decoderFactory = new DecoderFactory(this,PrimitiveDecoderMetadataSet.INSTANCE);
    this.encoderFactory = new EncoderFactory(this,PrimitiveEncoderMetadataSet.INSTANCE);

    ShutdownThread.register(this);
}

It's more an issue when plugging into other libraries that use JSR356 API than using pure JSR356 API websockets.

@joakime
Copy link
Contributor

joakime commented May 9, 2017

Since JSR356 Client uses WebSocketClient, and WebSocketClient uses the internal HttpClient, the decisions being made in Issue #1528 should provide a technique to configure any HttpClient specific features, even on JSR356.

@joakime
Copy link
Contributor

joakime commented Jul 19, 2017

jetty-9.4.x (jetty-9.4.7-SNAPSHOT) and master (jetty-10.x) branches have support for this via configuration of the internal HttpClient now.

@bahadirdanisik
Copy link

jetty-9.4.x (jetty-9.4.7-SNAPSHOT) and master (jetty-10.x) branches have support for this via configuration of the internal HttpClient now.

Do you have any examples how to configure client authentication in http client? (For websockets).

@joakime
Copy link
Contributor

joakime commented Sep 18, 2018

The AuthenticationStore is probably were you want to look.
https://www.eclipse.org/jetty/documentation/current/http-client-authentication.html

Feel free to open a new issue, show some sample code and what you are attempting to do.

@bahadirdanisik
Copy link

Sorry I was not clear. I am looking an example for SSL client authentication. When you set the "needClientAuth" to true on SslContextFactory, client is expected to send its certificate, but as mentioned in this ticket, it is not. As this ticket is already closed, I am looking at how to configure jetty http client to provide a custom ssl context factory or any other way to take the key store into account and send the certificate.

Thanks.

@bahadirdanisik
Copy link

Can you please also show me an example, how to configure the jetty http client programatically which is used to make websocket calls?

@joakime
Copy link
Contributor

joakime commented Sep 18, 2018

That is unrelated to this closed issue.
Please open a new issue.

@bahadirdanisik
Copy link

Thank you joakime, but I will probably create the same ticket as this one. "No way to set keystore for JSR 356 websocket clients, needed for SSL client authentication #155". So, wondering how is this ticket closed? I am looking the solution for this ticket. How do I set the keystore here?

@joakime
Copy link
Contributor

joakime commented Sep 18, 2018

you are asking about behaviors before even the HTTP level.
client certificate auth has nothing to do with websocket.
once the client certificate auth is valid/verified on the server, then the HTTP protocol begins, which asks to upgrade, then the websocket upgrade can proceed.

@bahadirdanisik
Copy link

I don't think you are getting my questions. What I am asking is how to force the http client used internally to send certificate during tls handshake. This was the original issue also reported in this ticket. ("No way to set keystore"). The client is not sending its certificate to the server.

@sbordet
Copy link
Contributor

sbordet commented Sep 18, 2018

@bahadirdanisik to force HttpClient to send the certificate you need to set SslContextFactory.needClientAuth=true on the server.
It's the server that asks the client to send the certificate.

@bahadirdanisik
Copy link

Yes, I know and already set the needClientAuth=true. This is asking client to send the cert, but the client does not send anything as indicated in this ticket. The httpClient used by Jetty does not honor the keystore settings. It does not send the certificate to the server.

@sbordet
Copy link
Contributor

sbordet commented Sep 19, 2018

Please pack a reproducible test case and attach it to this issue.

@sbordet sbordet reopened this Sep 19, 2018
@bahadirdanisik
Copy link

Any update for this ticket?
Thanks.

@sbordet
Copy link
Contributor

sbordet commented Mar 26, 2019

@bahadirdanisik no updates because there is nothing to do.

It is already possible to configure the client and the server so that the client sends to the server a certificate to authenticate itself, and we know that that works: we have tests and other people using this setup.

You have been asked a reproducible case for your issue, but you did not.

@horiavmuntean
Copy link

I did not find a way to make a jetty websocket client send a ssl client certificate for auth over websocket to the server (as opposed to a websocket client from the browser that does it to the same server - so the server is configured to ask for client certs)

Please @sbordet can you give any references/examples (you said - we have tests) with a jetty websocket client being able to send a ssl client certificate for auth to a server (any server) ?

Thanks

@joakime
Copy link
Contributor

joakime commented Apr 25, 2019

@horiavmuntean this issue is for the JSR356 WebSocket Client in Jetty (using the javax.websocket.WebSocketContainer API).

What you are asking about ("jetty websocket client") is a different client (org.eclipse.jetty.websocket.client.WebSocketClient API).

Can you open a new issue with what you have attempted so far, some example code on how you setup the WebSocketClient, what results you have (logs are great here), and what you expect.

@horiavmuntean
Copy link

Hi @joakime , thanks for clarifications.
In the meantime I "solved" the problem by upgrading from 9.2.2 to 9.4.17.

@melowe
Copy link

melowe commented Aug 22, 2019

Hi. I've been having the same issue (or at least it looks that way) trying to get SSL working with the jsr356 websockets jetty support. As I've seen in various comments on the many threads on this issue I configure a HttpClient pass in the same SSLContext, SslContextFactory.setSslContext(sslContext) and then new HttpClient(SslContextFactory).

Finding the exact means of addressing this is proving quite laborious.
The error I get is

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXV

Nothing I try seems to deal with this.

The SSLContext we build we use to create jaxrs client so that (at least for rest) works.

The jetty version I"m using is 9.4.20.v20190813 which I thought (from on of the threads) these problems had been fixed.

In case it helps the code looks like this.

final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
final SslContextFactory ssl = new SslContextFactory.Client();
final SSLContext sslContext = //our factory creates the sslcontext;
ssl.setSslContext(sslContext);

HttpClient httpClient = new HttpClient(ssl);
context.setAttribute(HTTPCLIENT_ATTRIBUTE, httpClient);
ServerContainer websocketsContainer = WebSocketServerContainerInitializer.initialize(context);

I see in initialise(context) that the http client is read from the context and then used to eventually create the WebSocketClient. Providing the HttpClient that this doesn't seem to work at all, am I missing something?

Thanks

Mark

@sbordet
Copy link
Contributor

sbordet commented Aug 22, 2019

unable to find valid certification path to requested target

@melowe this says that the server sent down to the client a certificate that is either self-signed or the client does not know how to trust - it's a normal TLS failure due to the client TLS configuration being misconfigured.

@joakime
Copy link
Contributor

joakime commented Aug 22, 2019

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXV

If you are getting the above you are definitely using SSL/TLS for your connection. (it's trying to handshake)
You just have a SSL/TLS configuration issue to work out.

@joakime
Copy link
Contributor

joakime commented Aug 22, 2019

@melowe I went ahead and added an example of configuring javax.websocket client for SSL on Jetty (in commit 5bcbe0f)

See directory: javax-websocket-client-impl - /examples/

And the main entry point : SecureWebSocketContainerExample.java

This is just the javax.websocket equivalent of the same code at jetty-client - /examples/SimpleSecureEchoClient.java

@melowe
Copy link

melowe commented Aug 23, 2019

thanks joakime.

so I found the issue(s). when the dust settles I see if i can send some patches.

JettyClientContainerProvider doesn't have any direct means of injecting the sslContext. Adding a static reference to the JettyClientContainerProvider and some like

 SimpleContainerScope containerScope = new SimpleContainerScope(WebSocketPolicy.newClientPolicy());

if (sslContext != null) {
            final SslContextFactory ssl = new SslContextFactory.Client();
            ssl.setSslContext(sslContext);
            containerScope.setSslContextFactory(ssl);
}

ClientContainer clientContainer = new ClientContainer(containerScope);

This makes ContainerProvider.getContainer() work. This does create a different issue where the injection via a static function of the sslContext means these cannot be created in start up, but need to be created as there's no way of knowing whether the context has been provided or not.

The previous way of providing the http client as a server attribute to the WebSocketServerContainerInitializer works fine.

The provision of the objects provided in the xml is the part of your example that would make the difference but we're not using xml config so we had to find an alternative. The configurator and the non annotated endpoint seem to make no difference.

@joakime
Copy link
Contributor

joakime commented Aug 23, 2019

Hmm ... SimpleContainerScope is an internal class, and its been removed in Jetty 10 already.

Let me make a new ClientContainer(HttpClient) constructor that can be used to configure the HttpClient side (for ssl, and proxies).
But that means you cannot use javax.websocket.ContainerProvider.getWebSocketContainer().

Would this be acceptable?

joakime added a commit that referenced this issue Aug 23, 2019
+ This helps with programmatic configuration of HttpClient
  layer (for SSL and Proxy).

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
@joakime
Copy link
Contributor

joakime commented Aug 23, 2019

@melowe
Copy link

melowe commented Aug 23, 2019

So injecting the http client rather than the scope seems fine. But I'm not sure how to deal with the javax.websocket.ContainerProvider you end up with a race condition between when the clients are created and when the addition of the SSLContext is added. I guess some block until initialised, initialisation being JettyClientContainerProvider.configured() or something.. and getContainer() can block with a timeout or something..

@joakime
Copy link
Contributor

joakime commented Aug 23, 2019

See previous #155 (comment) for example on how PR #4019 would work.

But I'm not sure how to deal with the javax.websocket.ContainerProvider you end up with a race condition between when the clients are created and when the addition of the SSLContext is added.

This is why the XML configuration was provided as an option.
Your XML can choose do anything it wants, just as long as it <Configure class="org.eclipse.jetty.client.HttpClient">, go load a ssl configuration from a DB, access a singleton to get your tls configuration, have it use a shared thread pool, etc...

You are correct, you cannot modify the SSL configuration (even the SSLContext) once the javax.websocket.WebSocketContainer is created.
The javax.websocket.ContainerProvider.getWebSocketContainer() is a java.util.ServiceLoader that calls JettyClientContainerProvider and should not block or timeout.

You could even have that XML delegate to your own class, block in there if you want to.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure id="myHttpClient" class="org.eclipse.jetty.client.HttpClient">
  <Arg>
    <New class="org.eclipse.jetty.util.ssl.SslContextFactory$Client" />
  </Arg>
  <Call class="examples.CustomHttpClientConfig" name="configure">
    <Arg><Ref refid="myHttpClient"/></Arg>
  </Call>
</Configure>

Where the class looks like ...

package examples;

import org.eclipse.jetty.client.HttpClient;

public class CustomHttpClientConfig
{
    public static void configure(HttpClient httpClient)
    {
        httpClient.getSslContextFactory().setExcludeCipherSuites(); // echo.websocket.org uses WEAK cipher suites
        httpClient.setIdleTimeout(5000);
    }
}

... a race condition between when the clients are created ...

This is a red flag!

You should only use that call once; either once for your JVM, or once per deployed WebApp, as its a heavyweight component.
There should never be multiple accesses to javax.websocket.ContainerProvider.getWebSocketContainer().
It will return a new Client container on every call. (This is important to recognize)

@melowe
Copy link

melowe commented Aug 25, 2019

So the only means of injecting the sslcontext into the HttpClient (via scope or otherwise) is by using the xml config. The point that the Container should be present and correct when getContainer is called is fair enough. I didn't find anything in the spec regarding how implementations should not block etc, but blocking is less than ideal.

I think we'll need to resolve this but providing our own JettyClientContainerProvider that can load the SSLContext config internally or even a HttpClientFactory that can read the SSLContext.

I've been looking in the jetty code, is there no equivalent of the xml http client config that can be done programmatically?

joakime added a commit that referenced this issue Aug 28, 2019
…lient-ssl-init

Issue #155 - Adding public ClientContainer(HttpClient) constructor
@joakime joakime added the Specification For all industry Specifications (IETF / Servlet / etc) label Mar 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side Specification For all industry Specifications (IETF / Servlet / etc)
Projects
None yet
Development

No branches or pull requests

8 participants