GithubHelp home page GithubHelp logo

saltyrtc / saltyrtc-meta Goto Github PK

View Code? Open in Web Editor NEW
74.0 74.0 8.0 18.91 MB

Protocol description and organisational information for SaltyRTC implementations.

License: MIT License

ortc protocol saltyrtc signaling webrtc

saltyrtc-meta's People

Contributors

dbrgn avatar lgrahl avatar oguzhane avatar ovalseven8 avatar rugk avatar threema-danilo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

saltyrtc-meta's Issues

Public API for clients

This is not directly something related to the protocol, but should be implemented consistently across all clients. Therefore the issue in the meta repo.

How should we design the public API?

Right now, it works as follows in the Java version:

// create new instance
KeyStore permanentKey = new KeyStore();
SaltyRTC salty = new SaltyRTC(permanentKey, host, port, sslContext);
salty.connect()

// Wait until connected
salty.events.signalingStateChanged.register(new EventHandler<SignalingStateChangedEvent>() {
    @Override
    public boolean handle(SignalingStateChangedEvent event) {
        if (event.getState() == SignalingState.CONNECTED) {
            LOG.info("Connected!");
        }
    }
});

// Retrieve public permanent key and auth token
byte[] key = salty.getPublicPermanentKey();
byte[] token = salty.getAuthToken();

// Get current state
SignalingState state = salty.getSignalingState();

// Get current signaling channel
SignalingChannel channel = salty.getSignalingChannel();

// Send signaling data (e.g. offer, answer, ICE candidates)
salty.sendSignalingData(new Data("offer", "offer-string"));

// Wait for data (e.g. offer, answer, ICE candidates)
salty.events.data.register(new EventHandler<DataEvent>() {
    @Override
    public boolean handle(DataEvent event) {
        if (event.getData().getType() == "answer") {
            // handle
        }
    }
});

// Do handover
salty.handover(peerConnection);

Now the question is how to send user data. One approach is to wrap a data channel.

SecureDataChannel sc = salty.wrap(dataChannel);

The other approach would be to send data through the SaltyRTC instance.

salty.sendData(dataChannel, data);

I'd favor the second approach because it does not require wrapping (more code, more complexity, presence of encryption is not directly visible, more complexity decreases security). Otherwise users might mix up secure and non-secure data channels.

(Note that this does not yet consider the tasks system. But tasks need to be implementable outside of SaltyRTC, so it would not change much about the basics.)

Detect subprotocol downgrades

In order to detect sub-protocol downgrade attacks, the client must send the same list of sub-protocols it has sent before the WebSocket connection has been established in the client-auth message.

The server uses the same algorithm to calculate the best shared sub-protocol as before and checks that the chosen sub-protocol matches the initially chosen sub-protocol. Otherwise, it is possible that a downgrade attack took place. In this case, the server must close the connection with the close code for protocol error.

WSS is required

Although the signalling server does not need to be trusted, it is still a good idea to use transport encryption on the signalling channel. Otherwise, WebSocket paths, etc. could be intercepted which makes the signalling protocol vulnerable to DoS attacks.

Negotiations in auth (P2P handshake)

Required Negotiations

Task

Specifies which task has to be fulfilled by SaltyRTC's signalling channel. The responder sends a list of tasks sorted by priority in descending order in its auth message. The initiator also holds such a task list and determines the best task match which it then sends in its auth message.

For example, the responder sends the list ['ortc', 'webrtc'] in its auth message. However, the initiator may not have ORTC support but it does support WebRTC. Its task list would be ['webrtc']. Therefore, it would send 'webrtc' in its auth message and both clients know that their task is to set up a WebRTC peer connection.

Optional Negotiations

None so far.

Specify scope of header parts

Should the sequence number be incremented globally, per session or per endpoint (server or responders)? (From what I understand, the sequence number has the same scope as the cookie and should be incremented for every message sent to the same endpoint.)

Should probably be written down in the spec.

Hash of Message for 'send-error' Message

Instead of repeating the whole messages' content which could not be relayed, the signalling server should send a SHA-256 hash of the message which could not be relayed.

Add leading 'b' to denote binary data in examples

The examples should be updated to describe packets in an extended JSON-like format:

{
    "integer": 1,
    "string": "hello",
    "binary": b"ff0eee"
}

'b'-denoted data is binary and is represented as hex to make it easier to read.

Server: Mirror Close Codes

When a client is connected to another client via the server, the close code in case of a protocol error should be somehow sent to the other client.

Repeating messages after 'send-error'

Add this to the protocol:

A client MUST repeat messages after a send-error response from the server at least once but SHOULD send it n times.

Reason: When a handover occurs in some tasks (WebRTC, ORTC) a message may be lost on the signalling channel. After a send-error the message will be repeated at least once over the now established signalling channel the original signalling channel has been handed over to.

We should specify n.

Nonce MUST be validated

When a SaltyRTC client or server receives a message, the nonce MUST be validated (e.g. the Cookie, the Channel Number and the Sequence Number). In case a sequence number would overflow, the Channel Number MUST be changed but MUST also remain unique per peer.

  • Nonce MUST be validated
  • Describe what should happen in case the nonce is not valid

Which message is being sent if both peers trust each other

In case a token is being used, the responder starts sending a 'token' message. But who sends which message (first) if both peers trust each other?

  • Who sends which message (first) if both peers trust each other?
  • Clarify what must happen in case one peer does not trust the other.
  • What happens if the initiator did not expect a token message (the responder is already trusted)
  • What happens if the initiator did not expect an key message (the responder is not trusted)

Packet field value constraints

For easy validation, we should provide constraints (length, value range, ....) for each field where the value is not crystal clear.

Data messages

  • Drop offer / answer / candidates message types
  • Add generic data message type as the only p2p message type after the handshake
  • Specify webrtc / ortc message types as extension

In TypeScript:

interface Message {
  type: string,
}

interface Data extends Message {
  type: 'data',
  data_type: string,
  data: any,
}

The data_type field allows us to register event handlers for specific data messages only.

Open question: Should we make the data_type field optional? If it's not specified, then the use of onData('type', function(msg) { ... }) type handlers is not possible in a generic way.

Related to #33.

Prevent DataChannel ID reuse

We already discussed this sometime, but the discussion was probably not in this issue tracker.

When creating data channels in the browser, the id increments for each new channel. According to a few quick tests I did, once it reaches 1023 every new data channel will get the id 1023.

We should verify whether that's really true and then find out whether there's a way to prevent that behavior.

Client behaviour should be much more precise

In general, the client behaviour is not well documented in the protocol. We should write a reference client implementation and add missing behaviour to the protocol.

What should happen if...

  • a client generates the same cookie as the server.
  • the server receives a client-auth message with two identical cookies. (Impossible when protocol is being followed)
  • a peer generates the same cookie as the other peer.
  • an initiator or responder receives an auth message with two identical cookies. (Impossible when protocol is being followed)
  • a responder does not authenticate itself towards an initiator.
  • a received nonce is invalid.

Bundle Candidates

In the browser, each ICE candidate triggers an event. To prevent sending signalling messages for every single ICE candidate, browser implementations should wait a very short amount of time and bundle candidates before they are being sent.

Add packet types for ORTC

In ORTC, there is no requirement for the packet types offer and answer. Furthermore, the packet type candidates should be updated to cover both SDP-encoded candidates (WebRTC) and JSON-encoded candidates (ORTC).

Negotiations in client-auth and server-auth

We would like to introduce negotiable parameters that are being sent in client-auth and server-auth:

  • Keep alive interval (integer in the range of 1.. seconds) in client-auth.
  • Task (required) (makes no sense between client and server)

Section: Packet Structure, figure is incorrect

As described in Terminology, the NaCl Box is not at the end of the message but at the start. Furthermore, we should remove MAC from the figure to avoid confusion. (Yes, it's authenticated but we don't need to use or pass the MAC as an argument anywhere, NaCl does it for us).

Paket Types: Encrypted / Unencrypted

Right now the mechanism to differentiate between encrypted and unecrypted packets is trial-and-error, since there is no prefix that allows the server to differentiate between the two pakets.

First of all, trial-and-error might be inefficient in some implementations. But the other aspect is that you cannot stream the data directly into a parser, because of the possible need for re-parsing. That means that you need to allocate memory to store the message, then you can try to decrypt it, and if that fails, parse it directly as msgpack.

But if we would add a type flag (e.g. a "flags" byte in the beginning) the implementation could differentiate the two packet types without any lookahead. That allows you to "stream" the bytes directly into the parser/decoder, without any allocation.

In Python terms, you could still follow the EAFP principle by trying to decrypt data[1:] and falling back to parsing the data as msgpack.

Packet: 'send-error', Sequence Number or Hash?

Currently, the 'send-error' message contains a SHA-256 hash of the message that could not be relayed by the server. I'm not completely sure if it would make more sense to send the Channel Number and the Sequence Number instead. Please, comment!

Receiver byte is not authenticated

The leading receiver byte is not part of the HMAC of the NaCl box which means the server could potentially modify it and the clients would not notice it. An alternative would be to make the receiver byte part of the channel number which has no proper use case for the signalling channel anyway.

Awkward Nonce/Header Field Scopes After Handover

Currently, the scope of the nonce fields is awkward after the handover. Basically, the normal human being would transition the peer scope over to a data channel instance scope.

Cookie

  • Before: Per peer
  • After: The same for all data channels, also called Cookie 2 now

Source/Destination Address -> Data Channel ID

  • Before: Per peer
  • After: Per data channel

Sequence Numbers

  • Before: Per peer
  • After: Per data channel id (!) - numbers are to be reused for reused data channel ids

This is really complicated and awkward. The spec of the task also only barely depicts the scopes above.

Nonce Validation

Is this correct?


The following things need to be validated on each incoming message:

Source byte

Only applicable to signaling nonces

  • During server handshake:
    • Assert that source byte is 0x00 (server)
  • During peer handshake:
    • Assert that source byte is 0x00 (server), or...
    • ...as initiator:
      • Assert that the source byte is in the range 0x02-0xff (responder)
    • ...as responder:
      • Assert that the source byte is 0x01 (initiator)
  • After peer handshake:
    • Assert that the source byte is the peer address

Receiver byte

Only applicable to signaling nonces

  • Before receiving the server-auth message:
    • Assert that the receiver byte is 0x00 (undefined)
  • For the server-auth message:
    • Store the receiver byte as own address
  • After an address has been assigned in the server-auth message:
    • As initiator:
      • Assert that the receiver byte is 0x01 (initiator)
    • As responder:
      • Assert that the receiver byte always corresponds to the own address

CSN

  • The CSN is stored separately per signaling receiver or secure data channel, as per #30 (comment)
  • On first message:
    • The overflow number must be 0
    • Store the CSN
  • On each subsequent message:
    • The CSN must be higher than the previous CSN

Cookie

  • Assert that the server/peer cookie hasn't changed

Open issues

  • Is it enough to validate that the CSN is higher than before, or should we also check that the increase is within a predefined range?

Provide 'subprotocol'

A WebSocket client can provide a list of subprotocols when connecting. We should choose a name for the protocol (and probably a version number, too).

Note: We do not need such a mechanism for #3 because the subprotocol will remain the same when switching over the communication channel.

Provide 'protocol' in P2P handshake

The negotiation (to use ORTC or WebRTC) needs to be done in the handshake of the peers. In addition, we need to provide the subprotocol during that handshake. The responder sends a list of subprotocols and the initiator chooses which subprotocol to use.

Message Chunking Spec

Although chunked-dc is quite a simple format, we should probably write a short specification for it.

The resulting spec should be referenced in the WebRTC and ORTC task.

Explain nonce composition

The nonce composition is clear in context with the Data Channel. However, the nonce is not mentioned in context with the Signalling Channel.

Data Channel Nonce/Header

This issue is a result of the discussion in #30

  • Source and Destination address become the Data Channel ID
  • Another Cookie must be generated (or alternatively another key pair)
  • CSN should start with 0 (but what about handed over signalling channels?)

You need an icon/logo

I think you need an icon for SaltyRTC. E.g. for using it in the GitHub organisation... ๐Ÿ˜ƒ

And for making SaltyRTC popular. ๐Ÿš€ ๐Ÿ˜‰

WebSocket close codes

The WebSocket protocol allows us to close a connection with a specific close code. The code range [3000..3999] can be used for libraries and frameworks.

Proposed close codes:

  • 1001: CLOSE_GOING_AWAY - The endpoint is going away, either because of a server failure or because the browser is navigating away from the page that opened the connection. Already defined by the WebSocket spec. We use it on the server side to let the client know that the server is being shut down.
  • 1002: CLOSE_PROTOCOL_ERROR - The endpoint is terminating the connection due to a protocol error. Already defined by the WebSocket spec. We use it to let the peer know that no shared sub-protocol could be found.
  • 3000: Path full error (no free responder byte)
  • 3001: Protocol error (invalid message, invalid path length, ...)
  • 3002: Internal error (syntax error, ...)
  • 3003: Handover of the signalling channel (e.g. a data channel when using WebRTC/ORTC)
  • 3004: Dropped by initiator (for an initiator that means another initiator has connected to the path, for a responder it means that an initiator requested to drop the responder)
  • 3005: Initiator Could Not Decrypt (sent when the initiator could not decrypt the first message sent by the responder - usually indicates out of sync between key trusting between initiator and responder)

See: https://github.com/saltyrtc/saltyrtc-server-python/blob/ac0988a80b1e2bd7e4df67c668d92ce511e94bcd/saltyrtc/common.py#L31:L35

Replace hex-encoding in examples

Because we are using MsgPack, no data needs to be hex-encoded anymore (apart from the public key in the WebSocket Path). The protocol and the examples should be updated.

Create client library checklist

Checklist with entries like the following:

  • Support all handshake message types
  • Support correlating send-error to sent messages
  • Support re-sending of messages after a send-error occurred
  • Nonce is always validated
  • Separate CSN for each connection
  • Handover requires ice connection state to be CONNECTED
  • ...

Then we can "rate" client libraries using that checklist and create a support matrix. It's also useful for people that want to write new libraries.

Specify Case of WebSocket Path

The spec only says that the path should be the hex encoded public key of the initiator.

It does not specify whether the letters in the hex string should be upper- or lowercase though.

RTCIceCandidate format

The spec for the candidate message is as follows:

{  
   "type": "candidates",
   "session": b"bb938a583e67923c6e9f2185a6a03773",
   "sdp": [
       "...",
       "..."
   ]
}

That assumes that the candidates are sdp strings. In the WebRTC RTCIceCandidate structure, that is contained in the candidate string attribute. On the other hand, ORTC does not provide such an attribute: http://ortc.org/wp-content/uploads/2015/06/ortc.html#h-rtcicecandidate

Can we simply send the entire RTCIceCandidate structure instead? Or is that too implementation-specific? Maybe we can define a key mapping?

Mitigating MITM between Client and Server

There's a possible MITM attack between client and server I've discovered some time ago. To be honest, it's not much of a problem because messages between clients are still end-to-end encrypted and cannot be decrypted by the server at all. It only works on unsecure WebSocket servers (no TLS) or WebSocket servers with broken TLS. Still, it makes our transport encryption look rather pointless.

Terminology

C = Client
S = Server
A = Attacker

MITM Attack (Initiator)

A generates two key pairs, a1 and a2. pk_a1 is the public key of a1 and sk_a1 is the secret key of a1.

A replaces the path the initiator provided by pk_a2.

S <-- Path: pk_a2 ---------- A <-- Path: pk_c ------------ C

A replaces the key of the server in server-hello by pk_a1.

S --- server-hello: pk_s --> A --- server-hello: pk_a1 --> C

A decrypts client-auth by box(sk_a1, pk_c), then encrypts and sends it to the server by box(sk_a2, pk_s).

S <-- client-auth ---------- A <-- client-auth ----------- C

A decrypts server-auth by box(sk_a2, pk_s), then encrypts and sends it to the initiator by box(sk_a1, pk_c). Any further messages between initiator and server can be decrypted and modified by A.

S --- server-auth ---------> A --- server-auth ----------> C

The benefit for A is questionable at best because A needs to change the path. Therefore, no responder would be able to connect to the initiator. Still, A can take the role of the server for the initiator.

MITM Attack (Responder)

A generates two key pairs, a1 and a2. pk_a1 is the public key of a1 and sk_a1 is the secret key of a1.

A does not need to change the path for responders as the path does not take part during authentication of responders.

S <-- Path: pk_i ----------- A <-- Path: pk_i ------------ C

A replaces the key of the server in server-hello with pk_a1.

S --- server-hello: pk_s --> A --- server-hello: pk_a1 --> C

A replaces the key of the responder in client-hello with pk_a2.

S <-- client-hello: pk_a2 -- A <-- server-hello: pk_c ---- C

A decrypts client-auth by box(sk_a1, pk_c), then encrypts and sends it to the server by box(sk_a2, pk_s).

S <-- client-auth ---------- A <-- client-auth ----------- C

A decrypts server-auth by box(sk_a2, pk_s), then encrypts and sends it to the responder by box(sk_a1, pk_c). Any further messages between responder and server can be decrypted and modified by A.

S --- server-auth ---------> A --- server-auth ----------> C

In this case, the benefit for A is much better than with an initiator as the path does not need to be changed. Therefore, clients that communicate with one another via the server would not even know that their communication is completely visible to A. Still, the benefits are marginal: A would be able to announce new initiators, drop responders and let responders repeat messages.

Mitigation

So, my idea is to add an OPTIONAL but RECOMMENDED section to the server.

The server has an OPTIONAL permanent key pair (sk_sp and pk_sp). Clients know the public key of the server's permanent key pair. The key has no lifetime or any other fancy certificate stuff, it should be as simple as possible.

In the server-auth, we add another field that is REQUIRED to be set by the server if he has a permanent key pair. That field, named signed_keys contains sign(pk_s || pk_c, sk_sp). The client verifies that signature. (Note: I've edited my previous stupid idea here)

And that's it. An attacker would need to have the permanent key pair to sign its own keys.

Discussion

Have I missed something? Is signing the concatenation of the public keys enough to mitigate replay attacks? Or should we add a x byte random field as well and add that to the concatenated data? @dbrgn

Handover to Data Channel

As soon as the signalling channel has done its job, there is no good reason to leave the signalling connection open. Further ICE candidates and other signalling messages can be sent over a dedicated signalling data channel.

  • ID Negotiation
  • Linger Minimum Timeout

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.