Srt: passphrase on sender start working only after some time

Created on 18 May 2017  路  9Comments  路  Source: Haivision/srt

When you set
ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PBKEYLEN, &aes256, sizeof aes256), SRT_ERROR);
ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PASSPHRASE, passphrase, strlen(passphrase)), SRT_ERROR);
for bind socket you will receive encrypted chunks for some time but after some time you will start getting normal chunks.
You can easily reproduce this case:

  • first start stransmit without passphrase

./stransmit BigBuckBunny.ts "srt://localhost:1234/?mode=server"

and start client to get ts and play it

./stransmit "srt://127.0.0.1:1234/?mode=client" file://con | ffplay -

you will not get any error message from ffplay and video starts playing from beginning.

  • if you set passphrase parameter

./stransmit BigBuckBunny.ts "srt://localhost:1234/?mode=server&passphrase=01234567890"

and then start client with ffplay

./stransmit "srt://127.0.0.1:1234/?mode=client&passphrase=01234567890" file://con | ffplay -

you will get a lot of error message from ffmpeg. video starts from ~10 from beginning. if you try to just store ts into file you will find there is no mpegts_start_mark 'G' in first and many others packets on 188 byte boundary. so message corrupted for some time from beginning. After some time messages starts working and recv get decrypted messages.

if you try to interrupt server you will get core dump and following messages in console:

-------- REQUESTED INTERRUPT!
02:47:43.137041/stransmit!!FATAL!!: SRT.c: sendmsg: UNEXPECTED EXCEPTION: St13runtime_error: Requested exception interrupt
^C
-------- REQUESTED INTERRUPT!
terminate called after throwing an instance of 'std::runtime_error'
what(): Requested exception interrupt
Aborted (core dumped)

in my application I see interesting effect

    SRTSOCKET m_bindsock = srt_socket(AF_INET, SOCK_DGRAM, 0);
    ASSERT_NE(m_bindsock, SRT_ERROR);

    ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect
    // set key len and passphrase for server socket so it should be inherited
    int aes256 = 32;
    ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PBKEYLEN, &aes256, sizeof aes256), SRT_ERROR);
    ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PASSPHRASE, passphrase, strlen(passphrase)), SRT_ERROR);

    sockaddr_in sa;
    memset(&sa, 0, sizeof sa);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(9999);
    sa.sin_addr.s_addr = INADDR_ANY;
    sockaddr* psa = (sockaddr*)&sa;

    ASSERT_NE(srt_bind(m_bindsock, psa, sizeof sa), SRT_ERROR);
    ASSERT_NE(srt_listen(m_bindsock, SOMAXCONN), SRT_ERROR);

    sockaddr_in scl;
    int sclen = sizeof scl;

    SRTSOCKET m_sock = srt_accept(m_bindsock, (sockaddr*)&scl, &sclen);
    ASSERT_NE(m_sock, SRT_INVALID_SOCK);

    {
        // IMPORTANT COMMENT. PLEASE READ IT
        // if I remove sleep then  getsockopt below will return pbkeylen = 0. with sleep we have
        // proper value. so it looks like property inheritance performed in background and 
        // required some time. but what to do to get first package decrypted ?**
        sleep(5);
        int pbkeylen;
        int pbkeylensize = sizeof pbkeylen;
        ASSERT_NE(srt_getsockopt(m_sock, 0, SRTO_PBKEYLEN, &pbkeylen, &pbkeylensize), SRT_ERROR);
        ASSERT_EQ(pbkeylensize , 4);
        ASSERT_EQ(pbkeylen, 32);
    }

it looks like decryption starts working after some time. and before that time both my app and stransmit get encrypted packages on recv calls. After some time recv start returning decrypted messages.

Bug

Most helpful comment

Hi Alex,

thank you for your report, you actually found a regression in the library! We already started tracking this down. The SRT receiver shouldn't output any data before the decryption has been successfully established in case encryption is enabled on the sender. We'll provide a fix as soon as possible.

Thanks,
Marc

All 9 comments

Hi,
problem with pass-phrase reproduced with sync socket too. here is simple test

#include <iostream>
#include <gtest/gtest.h>
#include <boost/thread/thread.hpp>

#include <string.h>

#if defined(NIMBLE_OS_WINDOWS)
#include <winsock2.h>
// ---------------------IMPORTANT NOTE:--------------------------
// WS2tcpip.h should always be included after winsock2.h according to
//  https://support.microsoft.com/en-us/kb/257460
#include <ws2tcpip.h>
#define EWOULDBLOCK WSAEWOULDBLOCK
#else
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif

const char* passphrase = "123456789232340";

void clientSocketSync() {
    int yes = 1;

    m_client_sock = srt_socket(AF_INET, SOCK_DGRAM, 0);
    ASSERT_NE(m_client_sock, SRT_ERROR);

    ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR);

    int aes256 = 32;
    ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_PBKEYLEN, &aes256, sizeof aes256), SRT_ERROR);
    ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_PASSPHRASE, passphrase, strlen(passphrase)), SRT_ERROR);

    sockaddr_in sa;
    memset(&sa, 0, sizeof sa);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(9999);

    ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);

    sockaddr* psa = (sockaddr*)&sa;

    ASSERT_NE(srt_connect(m_client_sock, psa, sizeof sa), SRT_ERROR);

    char buffer[1316] = {1, 2, 3, 4};

    for(int i = 0; i < 500; ++i) {
        ASSERT_NE(srt_sendmsg(m_client_sock, buffer, sizeof buffer,
                                      -1, // infinit ttl
                                      true // in order must be set to true
                                      ),
                  SRT_ERROR);
    }

    LibSrt::srt_close(m_client_sock);
}

void serverSocketSync() {
    SRTSOCKET m_bindsock = srt_socket(AF_INET, SOCK_DGRAM, 0);
    ASSERT_NE(m_bindsock, SRT_ERROR);

    int aes256 = 32;
    ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PBKEYLEN, &aes256, sizeof aes256), SRT_ERROR);
    ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_PASSPHRASE, passphrase, strlen(passphrase)), SRT_ERROR);


    sockaddr_in sa;
    memset(&sa, 0, sizeof sa);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(9999);
    sa.sin_addr.s_addr = INADDR_ANY;
    sockaddr* psa = (sockaddr*)&sa;

    ASSERT_NE(srt_bind(m_bindsock, psa, sizeof sa), SRT_ERROR);
    ASSERT_NE(srt_listen(m_bindsock, SOMAXCONN), SRT_ERROR);

    sockaddr_in scl;
    int sclen = sizeof scl;

    SRTSOCKET m_sock = srt_accept(m_bindsock, (sockaddr*)&scl, &sclen);
    ASSERT_NE(m_sock, SRT_INVALID_SOCK);


    char pattern[4] = {1, 2, 3, 4};

    int eq = 0;
    for(int i = 0; i < 500; ++i) {
        char buffer[1316];
        ASSERT_EQ(srt_recvmsg(m_sock, buffer, sizeof buffer),
                  1316);
        if(ArraysEqual(pattern, buffer, sizeof pattern)) {
            eq++;
        }
    }

    std::cout << "EQ=" << eq << std::endl;

    srt_close(m_sock);
    srt_close(m_bindsock);
}


TEST(SRT, SimpleSync) {
    ASSERT_TRUE(IsSrtAvailable());
    ASSERT_EQ(srt_startup(), 0);

    boost::thread server(&serverSocketSync);
    boost::thread client(&clientSocketSync);

    server.join();
    client.join();

    srt_cleanup();
}

#endif

this test returns EQ values between 470-480 so first 20-30 packages returned encrypted for both sync and async sockets

Hi Alex,

thank you for your report, you actually found a regression in the library! We already started tracking this down. The SRT receiver shouldn't output any data before the decryption has been successfully established in case encryption is enabled on the sender. We'll provide a fix as soon as possible.

Thanks,
Marc

Hi,
you are welcome. Please reply once fix available so I can enable pass-phrase tests

Ok, let me be more precise as to what exactly happens:

The "initial garbage" is received only in case when the receiver did not set the passphrase option, whereas the sender sends the encrypted stream. This happens so because the receiver, when not set a password, it "thinks" that the stream isn't encrypted at all and it delivers it as is (that is, the encrypted garbage). But it eventually receives the SRT_CMD_KMREQ message, which is a clear declaration that the stream is encrypted, and only then the receiver realizes that - and, as the encryption options weren't set, it can't decrypt it, and delivers nothing.

Different thing is when the receiver sets the passphrase, but wrong one. This time it knows since the very beginning that the stream is encrypted, so it will deliver nothing before the KMREQ, because it knows it should decrypt, but it can't, and then nothing as well, due to wrong password. If the password is correct, the delivery will work since the first KMREQ reception.

This is done this way because it was the easiest and least error-prone solution to apply on top of the existing UDT code - but this definitely causes unwanted scenarios. I'm currently working on so-called "integrated handshake", which means that after the URQ_INDUCTION handshake gets the mark of version 5, the URQ_CONCLUSION handshake will contain additionally the blocks of HSREQ and KMREQ. This is a big task and may take time, however it's expected to solve lots of problems we currently have with SRT. When this is done, it's expected that:

  • The "mis-encryption" (only one party declares encryption) case will result in a rejected connection
  • After the conclusion-conclusion exchange (and therefore the return from connect() and accept() in blocking mode) both parties should be encryption-aware and the receiver will decrypt correctly since the very first byte
  • The parameter negotiation and KM exchange is done after one handshake and therefore will happen faster.
  • SRT will be capable of encrypted transmission of ANY kind of media data (including files or some fragile protocols)

We'll share these changes once we test and confirm them.

Hi @ethouris ,

The "initial garbage" is received only in case when the receiver did not set the passphrase option

I hope you are talking from protocol perspective not api perspective because I set passphrase for both parties - please take a look at example.
So what do you recommend for now before you implement api correctly - just skip incoming packets till they become decrypted ?

Some precisions on how the decryption works: First the receiver knows the incoming stream is encrypted from two bits in the unencrypted packet header unfortunately this is not reflected in any status. To decrypt the receiver needs the passphrase and keying material sent by the source in a control packet. This material is sent after connection hanshake, and should most of the time be received before any encrypted packet. If lost, Keying Material control packet is resent but too late for the initial encrypted packets. These initial undecrypted packetswere queued for the application and should have not been delivered to the application and just skipped like any unrecovered lost packet. The original intend was to give more time to receive the resent keying material (time= the SRT receive buffer time or SO_TSBPDDELAY). and decrypt those packet upon delivery to the application instead of upon reception.

Recapitulation: The passphrase on a receiver is only used if keying material is received from the source. The key size (SRTO_PBKEYLEN) does not need to be set on a receiver since this info is part of the received keying material (in the example this is set on both side, maybe this confuses the library). The bug here is to pass through undecrypted packets without telling the application. UDP is a best effort protocol, SRT is 'better effort' but there are severe condition where packets cannot be recovered in time and it is not expected that a recorded SRT stream will always be complete.

Ok, I just checked that because in my previous logs it looked out that the first packet which's sequence is in the log saying "DELIVERED" is the first packet after the KMREQ/KMRSP exchange - so I understood then that the initial garbage is skipped when the receiver is encryption-aware. That's unfortunately not true in general (that is, true only under certain circumstances) - I have just made a very malicious case of transmission: a Makito stream to UDP then flipping UDP to SRT by stransmit and receiving SRT on the same machine. Kinda 2 "screen pages" of single packet reports are received for the time before the KMX is done, and it is the initial garbage - no matter if this is a nonencrypted/encrypted combination or both encrypted but the receiver passed wrong password. Probably then it depends on the stream quality and network RTT, how much of the effective "initial garbage" you get - may happen under some circumstances that not even one packet. When I did Makito -> stransmit over encrypted SRT, I received just one or two packets of "initial garbage", and this is only in the local network.

We probably could make some workaround to get rid of the problem quickly, but this will be a temporary solution anyway. I'm currently working on the "integrated handshake", which should solve this and many other problems.

Don't worry about workaround. I'll skip initial garbage without any problems. Please reply this thread once your effort will be done

This was completed in v1.3

Was this page helpful?
0 / 5 - 0 ratings

Related issues

boxerab picture boxerab  路  5Comments

stevomatthews picture stevomatthews  路  7Comments

oviano picture oviano  路  6Comments

vandanachadha picture vandanachadha  路  6Comments

alexpokotilo picture alexpokotilo  路  3Comments