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

Does libsrtp keep a static list of managed streams identified by their SSRC? #68

Closed
ibc opened this issue Sep 17, 2014 · 16 comments
Closed

Comments

@ibc
Copy link
Contributor

ibc commented Sep 17, 2014

I create two srtp_t sessions, set policy.ssrc.type = ssrc_any_outbound in both of them, and then call to srtp_protect() to both of them by passing the same RTP packet/data with the same SSRC value in both calls (note that before calling srtp_protect() I do a memcpy of the original RTP data, so the original one remains unchanged when the second call to srtp_protect() takes place in the second session).

In this scenario I get lot of err_status_replay_old errors ("replay check failed (index too old)").

So I wonder: may be libsrtp handles a global/static list of streams so two different and independent srtp_t sessions should NEVER try to protect the same RTP packet?

If this is true, please confirm it. Thanks a lot.

@jfigus
Copy link
Contributor

jfigus commented Sep 22, 2014

Sorry for the delayed response. I was on travel last week. Are you using the same srtp_ctx_t* when invoking srtp_protect() for each packet? If so, it's probably reusing the same stream context for both packets since it uses the SSRC to lookup the stream context. The replay database is under the stream context.

@ibc
Copy link
Contributor Author

ibc commented Sep 22, 2014

Thanks for the response. I think I know what is happening:

  • The server (in which I use libsrtp) receives RTP from peer-A, unprotects it, sets its SSRC value to a fixed "1234" and protects it using the srtp_ctx_t* of peer-B.
  • Later peer-A disconnects and reconnects again, which means a new DTLS negotiation (WebRTC) so also a new SRTP keys and a new srtp_ctx_t* for peer-A (the previous one is properly freed).
  • When the "new" peer-A sends RTP, the server again unprotects it, sets its SSRC value to "1234" and protects it using the srtp_ctx_t* of peer-B.
  • It is in that point when it fails with the err_status_replay_old error.

AFAIU this is ok since the new RTP packets form peer-A are different than the previous ones, but in the server I'm overriding its SSRC value to "1234" and then pass them to the srtp_ctx_t* of peer-B to protect them, so the srtp_ctx_t* of peer-B is provided with a stream already known (SSRC = 1234) and thus it fails to encrypt it given that the new timestamp values and so on don't follow those in the first stream of peer-A (already finished).

So basically, in my scenario I need to set a different SSRC before calling srtp_protect(), am I right?

Thanks a lot.

@jfigus
Copy link
Contributor

jfigus commented Sep 22, 2014

If you are using two srtp_ctx_t* references, one for peer A and one for peer B, then the err_status_replay_old error is likely due to a repeating sequence number on the context for peer B. Changing the SSRC will probably not make a difference. You'll probably have to maintain a separate RTP sequence number for peer B to make sure it's not reused after the peer A session is restarted.

If you're not rekeying the context with peer B, then you shouldn't reuse that context when sending repeated packets. You'll probably want to rekey the peer B context.

@ibc
Copy link
Contributor Author

ibc commented Sep 22, 2014

Thanks for your response. However I do not fully understand.

I expect that, when calling srtp_protect(packet):

  • First the SSRC of the packet is inspected within the srtp_ctx_t session.
  • Then the session internally creates a new "stream" or not (depending on whether the SSRC was previously present or not in another call to srtp_protect() in that session.
  • And then, within the resulting "stream", the packet's sequence number is inspected in order to check for errors (like mine).

Is this the order?

@ibc
Copy link
Contributor Author

ibc commented Sep 22, 2014

I would like to explain better my scenario:

  • My server is a conference server that does not perform media mixing.
  • Peer A, Peer B and Peer C establish a separate and single DTLS-SRTP connection with my server and send a single audio stream on it with an arbitrary SSRC chosen by the peer (Chrome).
  • In the server, for each peer two srtp_ctx_t are created as usual, one for receiving RTP (with policy.ssrc.type = ssrc_any_inbound and another for sending RTP to that peer with policy.ssrc.type = ssrc_any_outbound.
  • When Peer A sends RTP to the server, the server decrypts it using the inbound srtp_ctx_t of Peer A (as usual), then clones the resulting plain RTP packet resulting in two RTP packets rtp-for-peer-B and rtp-for-peer-C.
  • Then it sets the SSRC of rtp-for-peer-B to (let's say) 2222, and the SSRC of rtp-for-peer-C to 3333, and encrypts them with their corresponding outbound srtp_ctx_t of Peer B and Peer C, and sends them to those peers.

All works fine. The problem happens when Peer A is restarted. Its DTLS-SRTP context is deleted (so also its outbound and inbound srtp_ctx_t). Peer A negotiates again DTLS-SRTP resulting in two new srtp_ctx_t for inbound and outbound.

Then Peer A sends again RTP (note that this RTP has nothing to do with the previous one as the client has been restarted).

The server performs the same steps as above, and then sometimes I get the err_status_replay_old when calling srtp_protect() on the outbound srtp_ctx_t of Peer B and Peer C.

So in this case, I understand that, indeed, the sequence numbers of those new RTP packets form Peer A may create a conflict with the previous RTP stream from Peer A, and hence the encrypt error. Am I right?

If so, I understand that when Peer A is restarted and performs again the DTLS-SRTP setup, the server must choose a different SSRC value for the RTP coming from Peer A so the outbound srtp_ctx_t of Peer B and Peer C will see those packets belonging to a new "stream" so there won't be sequence number problems. Am I right?

Thanks a lot.

@jfigus
Copy link
Contributor

jfigus commented Sep 22, 2014

Q: So in this case, I understand that, indeed, the sequence numbers of those new RTP packets form Peer A may create a conflict with the previous RTP stream from Peer A, and hence the encrypt error. Am I right?

A: I believe you're correct. Peer A is likely repeating the sequence number of some packets, causing the error when you re-encrypt it for peer B (or C).

Q: If so, I understand that when Peer A is restarted and performs again the DTLS-SRTP setup, the server must choose a different SSRC value for the RTP coming from Peer A so the outbound srtp_ctx_t of Peer B and Peer C will see those packets belonging to a new "stream" so there won't be sequence number problems. Am I right?

A: Yes, choosing a new SSRC may resolve the issue. But it's highly recommended to rekey the session between the server and peer B (and peer C). Another approach may be to use the EKT feature in libsrtp. Unfortunately the EKT draft was never ratified. But EKT support was added to libsrtp at some point in the past. This is beyond my expertise since the EKT code was contributed by another developer. But my understanding is EKT was developed specifically to solve the problem you're addressing. You may want to take a look at the EKT draft.

@ibc
Copy link
Contributor Author

ibc commented Sep 22, 2014

Thanks a lot!

I will take a look to EKT, but first I want the "simple" case to properly work (and EKT may not be suitable for all the use-cases in my server given that in some cases it will behave as a gateway between DTLS-SRTP peers and SDES-SRTP or plain RTP peers.

Anyhow, you replied:

Yes, choosing a new SSRC may resolve the issue. But it's highly recommended to rekey the session between the server and peer B (and peer C)

I understand that it is recommended not to leak the session between the server and peer B (and peer C) with unused streams. What about if I do the following?

Instead of setting policy.ssrc.type = ssrc_any_outbound I may call to srtp_add_stream() when a new peer joins the "conference" and srtp_remove_stream() when the peer leaves it. This would work even if I set the SSRC from Peer A to the same value when it reconnects since at the time it has been disconnected I've removed its stream (SSRC) from the sessions of both Peer B and Peer C. Does it sound ok?

Really thanks a lot.

@jfigus
Copy link
Contributor

jfigus commented Sep 22, 2014

That should resolve your problem since invoking srtp_add_stream() will result in a new initialization of the anti-replay database for the stream. The streams are indexed by SSRC and normally created implicitly when using ssrc_any_outbound. You'll simply be creating the stream explicitly. But you really should rekey the other SRTP sessions to peers B & C. Otherwise you'll end up reusing the initialization vector for AES encryption, which is fatal when GCM mode is used.

@jesup
Copy link
Contributor

jesup commented Sep 23, 2014

Yes. It is a different source, in fact. ALternatively, make sure the peer-B context doesn't see the source as having stopped, and modify any after-reattachment seqnums and timestamps to make the interrupted source streams appear as a single stream with an interruption.

@ibc
Copy link
Contributor Author

ibc commented Sep 23, 2014

But you really should rekey the other SRTP sessions to peers B & C. Otherwise you'll end up reusing the initialization vector for AES encryption, which is fatal when GCM mode is used.

Does "rekey the other SRTP session to B & C" mean force a new SRTP key exchange with both peers B and C? I cannot do that just because A has re-connected as it would involve a new SDP O/A with all the conference peers every time a peer reconnects.

So, is it enough if I set a new SSRC for the incoming RTP of Peer A after it reconnects to the conference?

ALternatively, make sure the peer-B context doesn't see the source as having stopped, and modify any after-reattachment seqnums and timestamps to make the interrupted source streams appear as a single stream with an interruption.

I understand that this is not needed if I do not reuse a previously used SSRC, am I right?

Thanks a lot to both.

@jfigus
Copy link
Contributor

jfigus commented Sep 24, 2014

Yes, you would have to perform a new SRTP key exchange with both peers (B&C). I'm assuming you're using AES-CTR mode. The problem is CTR mode is a stream cipher. Any nonce reuse is considered catastrophic. Since the nonce is derived from the sequence number in the RTP packet, and you're potentially reusing sequence numbers (indicated by the error you received that started this discussion), then you're at risk for nonce reuse. The same problem is presented when using AES-GCM mode. This needs to be avoided if you wish to provide a secure solution. Take a look at EKT. It was designed with intent to solve the problem you've presented.

@ibc
Copy link
Contributor Author

ibc commented Sep 24, 2014

Thanks a lot, but I would like to confirm that this problem:

Since the nonce is derived from the sequence number in the RTP packet, and you're potentially reusing sequence numbers (indicated by the error you received that started this discussion), then you're at risk for nonce reuse

This problem won't happen in case I do NOT reuse the same SSRC for a new stream (which replaces an old one) within the same calls to srtp_protect(same_session). Am I right?

BTW: How to select AES-CTR, AES-GCM or any other mode? I don't see that in the API. Is it something that I can set when creating a srtp session or policy? or does it depend on the peer?

Regarding EKT, AFAIK it just make sense when both peers use SRTP, which may not be true in my case. Even more, I'm building a conference server (which does not mix but relays all the streams to other participants).

@jfigus
Copy link
Contributor

jfigus commented Sep 24, 2014

The SSRC is also a component into the nonce (a.k.a. IV) used for AES-CTR mode. If you're changing the SSRC, then this may help prevent nonce resuse. Bottom line, you'll need to ensure the nonce is never reused. Please refer to section 4.1.1 of RFC 3711 which warns about this and discusses the need to rekey. You'll need to understand how the nonce is derived and ensure it's never reused. Your approach of cloning the packing and hacking the SSRC may circumvent the nonce reuse checks within libsrtp.

You would select the AES mode on the SRTP policy. The cipher_type member on the policy can be set to AES_ICM, AES_192_ICM, AES_256_ICM, AES_128_GCM, or AES_256_GCM. There are some other settings for this defined in crypto_types.h, but I've never seen them used and not sure if they are supported (SEAL, AES_CBC).

@ibc
Copy link
Contributor Author

ibc commented Sep 24, 2014

Really thanks a lot. I just use crypto_policy_set_aes_cm_128_hmac_sha1_80() and crypto_policy_set_aes_cm_128_hmac_sha1_32() (depending on the SRTP suite negotiated via DTLS).

Definitely I need to go in depth with RFC 3711. Will do it.

I strongly appreciate all the help provided in this thread. I will close the issue given that it is not an issue :)

@ibc ibc closed this as completed Sep 24, 2014
@jfigus
Copy link
Contributor

jfigus commented Sep 25, 2014

Using those policy helper functions, you're using AES-CTR mode. If you want GCM mode, you can use one of the GCM policy helpers (e.g. crypto_policy_set_aes_gcm_128_8_auth). GCM mode is only supported when the --enable-openssl option is used to configure libsrtp.

@ibc
Copy link
Contributor Author

ibc commented Sep 25, 2014

Thanks a lot for the clarification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants