Giter Club home page Giter Club logo

saltyrtc-client-java's Introduction

SaltyRTC Java Client

Build status Codacy Java Version License CII Best Practices Chat on Gitter

This is a SaltyRTC v1 implementation for Java 8+.

Installing

The package is available on Maven Central.

Gradle:

compile 'org.saltyrtc:saltyrtc-client:0.14.1'

Maven:

<dependency>
  <groupId>org.saltyrtc</groupId>
  <artifactId>saltyrtc-client</artifactId>
  <version>0.14.1</version>
  <type>pom</type>
</dependency>

Usage / Documentation

Documentation can be found at https://saltyrtc.github.io/saltyrtc-client-java/.

Plase note that instances of this library are not considered thread-safe. Thus, an application using more than one thread needs to take care of synchronisation itself.

Manual Testing

To try a development version of the library, you can build a local version to the local maven repository (usually at $HOME/.m2/repository/):

./gradlew publishToMavenLocal

Include it in your project like this:

repositories {
    ...
    mavenLocal()
}

Coding Guidelines

Unfortunately we cannot use all Java 8 features, in order to be compatible with Android API <24. Please avoid using the following APIs:

  • java.lang.annotation.Repeatable
  • AnnotatedElement.getAnnotationsByType(Class)
  • java.util.stream
  • java.lang.FunctionalInterface
  • java.lang.reflect.Method.isDefault()
  • java.util.function
  • java.util.Optional

The CI tests contains a script to ensure that these APIs aren't being called. You can also run it manually:

bash .circleci/check_android_support.sh

Automated Testing

1. Preparing the Server

First, clone the saltyrtc-server-python repository.

git clone https://github.com/saltyrtc/saltyrtc-server-python
cd saltyrtc-server-python

Then create a test certificate for localhost, valid for 5 years.

openssl req -new -newkey rsa:1024 -nodes -sha256 \
    -out saltyrtc.csr -keyout saltyrtc.key \
    -subj '/C=CH/O=SaltyRTC/CN=localhost/'
openssl x509 -req -days 1825 \
    -in saltyrtc.csr \
    -signkey saltyrtc.key -out saltyrtc.crt

Create a Java keystore containing this certificate.

keytool -import -trustcacerts -alias root \
    -file saltyrtc.crt -keystore saltyrtc.jks \
    -storetype JKS -storepass saltyrtc -noprompt

Create a Python virtualenv with dependencies:

python3 -m virtualenv venv
venv/bin/pip install .[logging]

Finally, start the server with the following test permanent key:

export SALTYRTC_SERVER_PERMANENT_KEY=0919b266ce1855419e4066fc076b39855e728768e3afa773105edd2e37037c20 # Public: 09a59a5fa6b45cb07638a3a6e347ce563a948b756fd22f9527465f7c79c2a864
venv/bin/saltyrtc-server -v 5 serve -p 8765 \
    -sc saltyrtc.crt -sk saltyrtc.key \
    -k $SALTYRTC_SERVER_PERMANENT_KEY

2. Running Tests

Make sure that the certificate keystore from the server is copied or symlinked to this repository:

ln -s path/to/saltyrtc-server-python/saltyrtc.jks

With the server started in the background and the saltyrtc.jks file in the current directory, run the tests:

./gradlew test

Security

Signing

Releases are signed with the following PGP ED25519 public key:

sec   ed25519 2021-05-05 [SC] [expires: 2025-05-04]
      27655CDD319B686A73661526DCD186BEB204C8FD
uid           SaltyRTC (Release signing key)

Responsible Disclosure / Reporting Security Issues

Please report security issues directly to one or both of the following contacts:

License

Copyright (c) 2016-2021 Threema GmbH

Licensed under the Apache License, Version 2.0, <see LICENSE-APACHE file>
or the MIT license <see LICENSE-MIT file>, at your option. This file may not be
copied, modified, or distributed except according to those terms.

saltyrtc-client-java's People

Contributors

dbrgn avatar dependabot[bot] avatar lgrahl avatar ovalseven8 avatar rugk avatar threema-danilo avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

saltyrtc-client-java's Issues

Properly handle protocol errors

https://github.com/saltyrtc/saltyrtc-meta/blob/master/Protocol.md#client-to-client-messages

Compared to client-to-server messages, protocol errors for client-to-client message MUST be handled differently. In case that any check fails, the procedure below MUST be followed unless otherwise stated:

  • If the other client is not authenticated yet, an initiator SHALL drop the corresponding responder by sending a 'drop-responder' message with the responder's address in the id field to the server and a close code of 3001 (Protocol Error) in the reason field. A responder SHALL close the connection to the server with a close code of 3001.
  • If the other client is authenticated and the error cause is not a 'send-error' message from the server, the client SHALL send a 'close' message to the other client containing the close code 3001 (Protocol Error). In any case, both clients SHALL terminate the connection to the server (normal close code).

Wrong send-error validation

this.id = ValidationHelper.validateByteArray(map.get("id"), 32, "id");

03-28 21:03:50.410 19412  8296 W System.err: org.saltyrtc.client.exceptions.ValidationError: id must be 32 bytes long, not 8
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.helpers.ValidationHelper.validateByteArray(ValidationHelper.java:47)
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.messages.s2c.SendError.<init>(SendError.java:31)
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.helpers.MessageReader.read(MessageReader.java:98)
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.helpers.MessageReader.read(MessageReader.java:45)
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.signaling.ResponderSignaling.onPeerHandshakeMessage(ResponderSignaling.java:372)
03-28 21:03:50.419 19412  8296 W System.err: 	at org.saltyrtc.client.signaling.Signaling$1.onBinaryMessage(Signaling.java:383)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ListenerManager.callOnBinaryMessage(ListenerManager.java:385)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ReadingThread.callOnBinaryMessage(ReadingThread.java:276)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ReadingThread.handleBinaryFrame(ReadingThread.java:996)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ReadingThread.handleFrame(ReadingThread.java:755)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ReadingThread.main(ReadingThread.java:108)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.ReadingThread.runMain(ReadingThread.java:64)
03-28 21:03:50.420 19412  8296 W System.err: 	at com.neovisionaries.ws.client.WebSocketThread.run(WebSocketThread.java:45)

https://github.com/saltyrtc/saltyrtc-meta/blob/master/Protocol.md#send-error-message

Events

Current events:

  • ConnectedEvent
  • ConnectionClosedEvent
  • ConnectionErrorEvent
  • HandoverEvent
  • DataEvent

We should probably replace them with the following:

  • SignalingStateChangedEvent
  • SignalingChannelChangedEvent
  • DataEvent

Signaling messages after handshake encrypted twice

Signaling messages after the handshake are encrypted twice, because the Signaling.send method passes already encrypted bytes to the Task.sendSignalingMessage method where they're encrypted again.

We can't simply send the bytes through the task channel as-is, because the nonce structure is different. Therefore the client library needs to pass unencrypted message bytes to the task.

Create CryptoProvider interface

Create an interface that proxies all NaCl calls, so that users can use their own favorite implementation.

Depends on #89 (default methods).

Handover to data channel

Doesn't seem to work properly yet:

SaltyRTC.RSignaling: Trying to send data message, but connection state is CLOSED

This was probably a message that SaltyRTC was trying to send through the already closed websocket.

How to connect clients across NAT?

How to connect clients across NAT?

Client 1 -> Private IP 1 (Initiator)
Client 2 -> Private IP 2 (Receiver)

Also, how is ICE server configured?

Websocket timeout

I've seen this exception more than once:

SaltyRTC.RSigna: A WebSocket connect error occured: Failed to connect to '<domain>:443': failed to connect to <domain>/<ipv6> (port 443) after 2000ms                 
                 com.neovisionaries.ws.client.WebSocketException: Failed to connect to '<domain>:443': failed to connect to <domain>/<ipv6> (port 443) after 2000ms
                     at com.neovisionaries.ws.client.SocketConnector.doConnect(SocketConnector.java:119)
                     at com.neovisionaries.ws.client.SocketConnector.connect(SocketConnector.java:81)
                     at com.neovisionaries.ws.client.WebSocket.connect(WebSocket.java:2031)
                     at com.neovisionaries.ws.client.ConnectThread.run(ConnectThread.java:37)
                  Caused by: java.net.SocketTimeoutException: failed to connect to <domain>/<ipv6> (port 443) after 2000ms
                     at libcore.io.IoBridge.connectErrno(IoBridge.java:169)
                     at libcore.io.IoBridge.connect(IoBridge.java:122)
                     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)
                     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:456)
                     at java.net.Socket.connect(Socket.java:882)
                     at com.neovisionaries.ws.client.SocketConnector.doConnect(SocketConnector.java:110)
                     at com.neovisionaries.ws.client.SocketConnector.connect(SocketConnector.java:81at com.neovisionaries.ws.client.WebSocket.connect(WebSocket.java:2031at com.neovisionaries.ws.client.ConnectThread.run(ConnectThread.java:37SaltyRTC.RSigna: Could not connect to websocket: Failed to connect to '<domain>:443': failed to connect to <domain>/<ipv6> (port 443) after 2000ms
  • Timeout should be longer than 2000ms by default
  • Timeout should be configurable by the user application
  • Maybe add a retry, or at least pass the failed state up to the user application

Refactor signaling class

There's some state checking being done, e.g. if (receiver == SALTYRTC_ADDR_SERVER) .... Replace that with polymorphism based on the new Server class.

Handshake attempts after 'task' state trigger a protocol error

When a new responder connects, not only is the new-responder message being ignored, but also the responder that is trying to initiate a handshake triggers a protocol error because the initiator cannot find the responder in its map of responders.

Unfortunately, this implementation of the client has not been designed to fully reset its state to be able to do another handshake. The proposed solution is to drop the responder.

Furthermore, a new-initiator message also results in a protocol error after the handshake has been completed.

Determining of first common task

The initiator SHALL continue by comparing the provided tasks to its own Array of available tasks and MUST choose the first common task from both Arrays.

In the following case, which one is the first common task?

initiator: ['webrtc', 'ortc']
responder: ['ortc', 'webrtc']

I assume it's webrtc, as the initiator is the one that chooses, so it gets the pick.

What about this wording?

The initiator SHALL continue by comparing the provided tasks to its own Array of available tasks. It MUST choose the first task in its own list of available tasks that is also contained in the list of provided tasks.

That would mean that in the example above, webrtc is chosen.

It would also mean that in the following case...

initiator: ['webrtc', 'ortc', 'email']
responder: ['ortc', 'websockets', 'email', 'sms', 'carrier-pigeons', 'webrtc']

...webrtc would be chosen, even though webrtc is the least preferred task of the responder, and even though ortc would look like a good match in this case.

@lgrahl what are your thoughts?

Is this client thread-safe?

I get null pointer exceptions when I connect/disconnect simultaneously:

[ConnectThread] ERROR SaltyRTC.ISignaling - WebSocket callback error: java.lang.NullPointerException
java.lang.NullPointerException
	at org.saltyrtc.client.signaling.Signaling$1.onConnectError(Signaling.java:330)
	at com.neovisionaries.ws.client.ListenerManager.callOnConnectError(ListenerManager.java:206)
	at com.neovisionaries.ws.client.ConnectThread.handleError(ConnectThread.java:46)
	at com.neovisionaries.ws.client.ConnectThread.runMain(ConnectThread.java:36)
	at com.neovisionaries.ws.client.WebSocketThread.run(WebSocketThread.java:45)

It's this code:

Signaling.this.ws.recreate(Signaling.this.wsConnectTimeout).connectAsynchronously();

Following test:

    @Test
    public void testConnectDisconnect() throws Exception {
        // Create peers
        final SSLContext sslContext = SSLContextHelper.getSSLContext();
        final SaltyRTC initiator = new SaltyRTCBuilder(this.cryptoProvider)
            .connectTo(Config.SALTYRTC_HOST, Config.SALTYRTC_PORT, sslContext)
            .withKeyStore(new KeyStore(this.cryptoProvider))
            .usingTasks(new Task[]{ new DummyTask() })
            .asInitiator();
        final SaltyRTC responder = new SaltyRTCBuilder(this.cryptoProvider)
            .connectTo(Config.SALTYRTC_HOST, Config.SALTYRTC_PORT, sslContext)
            .withKeyStore(new KeyStore(this.cryptoProvider))
            .usingTasks(new Task[]{ new DummyTask() })
            .initiatorInfo(initiator.getPublicPermanentKey(), initiator.getAuthToken())
            .asResponder();

        for (int i = 0; i <= 200; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        initiator.connect();
                        initiator.disconnect();
                    } catch (ConnectionException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

ERROR SaltyRTC.RSignaling - No shared sub-protocol could be found

Why does this come? ERROR SaltyRTC.RSignaling - No shared sub-protocol could be found

public static String HOST = "server.saltyrtc.org";
public static int PORT = 9287;
public static String STUN_SERVER = "stun.services.mozilla.com";

final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        final SaltyRTC client = new SaltyRTCBuilder()
        .connectTo(HOST, PORT, sslContext)
        .withKeyStore(new KeyStore())
        .withPingInterval(60)
        .withServerKey(TRUSTED_KEY)
        .usingTasks(new Task[] { new PingPongTask() })
        .asInitiator();
        
         final SaltyRTC client2 = new SaltyRTCBuilder()
        .connectTo(HOST, PORT, sslContext)
        .withKeyStore(new KeyStore())
        .withPingInterval(60)
        .initiatorInfo(client.getPublicPermanentKey(), client.getAuthToken())
        .usingTasks(new Task[] { new PingPongTask() })
        .asResponder();
        
       
            try {
                client.connect();
                client2.connect();
            } catch (ConnectionException ex) {
                java.util.logging.Logger.getLogger(RTCConnectApp.class.getName()).log(Level.SEVERE, null, ex);
            }

Client-to-Client Messages/Behaviour Changes

The 'drop-responder' message has been changed. An initiator MAY now add a reason field to a 'drop-responder' message the server SHALL close the connection to the corresponding responder with. That field contains a close code described here

When to use the reason field has been described here.

The 'close' message has been added.

All WebRTC-related messages have been removed from the spec and will be moved into a separate task soon. I'll use a new branch for that.

NPE on disconnect

11-02 16:22:22.246 31806-31806/org.saltyrtc.demo.app E/AndroidRuntime: FATAL EXCEPTION: main
                                                                       Process: org.saltyrtc.demo.app, PID: 31806
                                                                       java.lang.NullPointerException: Attempt to invoke virtual method 'com.neovisionaries.ws.client.WebSocket com.neovisionaries.ws.client.WebSocket.disconnect(int)' on a null object reference
                                                                           at org.saltyrtc.client.signaling.Signaling.disconnect(Signaling.java:215)
                                                                           at org.saltyrtc.client.signaling.Signaling.disconnect(Signaling.java:232)
                                                                           at org.saltyrtc.client.SaltyRTC.disconnect(SaltyRTC.java:135)
                                                                           at org.saltyrtc.demo.app.MainActivity.stop(MainActivity.java:253)
                                                                           at org.saltyrtc.demo.app.MainActivity$3$1.run(MainActivity.java:171)
                                                                           at android.os.Handler.handleCallback(Handler.java:739)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                           at android.os.Looper.loop(Looper.java:168)
                                                                           at android.app.ActivityThread.main(ActivityThread.java:5845)
                                                                           at java.lang.reflect.Method.invoke(Native Method)
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)

Websocket: onBinaryMessage can happen before onConnected

11-16 13:11:04.671 7402-7862/ch.threema.app D/Threema:SessionConnectionContext[0]: Signaling state changed to WS_CONNECTING
11-16 13:11:04.673 7402-7862/ch.threema.app D/Threema:Webclient Session Wakeup: Webclient session started
11-16 13:11:04.942 7402-7886/ch.threema.app D/SaltyRTC.RSignaling: New binary message (81 bytes)
11-16 13:11:04.944 7402-7886/ch.threema.app E/SaltyRTC.RSignaling: Protocol error: Invalid incoming message: Cannot validate message nonce in signaling state WS_CONNECTING
11-16 13:11:04.944 7402-7886/ch.threema.app W/System.err: org.saltyrtc.client.exceptions.ValidationError: Cannot validate message nonce in signaling state WS_CONNECTING
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at org.saltyrtc.client.signaling.Signaling.validateNonceSource(Signaling.java:951)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at org.saltyrtc.client.signaling.Signaling.validateNonce(Signaling.java:900)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at org.saltyrtc.client.signaling.Signaling.access$700(Signaling.java:78)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at org.saltyrtc.client.signaling.Signaling$2.onBinaryMessage(Signaling.java:380)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ListenerManager.callOnBinaryMessage(ListenerManager.java:368)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ReadingThread.callOnBinaryMessage(ReadingThread.java:272)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ReadingThread.handleBinaryFrame(ReadingThread.java:992)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ReadingThread.handleFrame(ReadingThread.java:751)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ReadingThread.main(ReadingThread.java:110)
11-16 13:11:04.952 7402-7886/ch.threema.app W/System.err:     at com.neovisionaries.ws.client.ReadingThread.run(ReadingThread.java:66)
11-16 13:11:04.952 7402-7886/ch.threema.app D/Threema:SessionConnectionContext[0]: Signaling state changed to CLOSING

I'm not sure how that can happen, probably a race condition, but let's add a workaround for that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.