Discover the new easier way to develop Kurento video applications

Securing Kurento Applications

Securing Application Servers

Configure a Java server to use HTTPS

  • Obtain a certificate. For this, either request one from a trusted Certification Authority (CA), or generate your own one as explained here: Generating a self-signed certificate.

  • Convert your PEM certificate to either Java KeyStore (JKS) or PKCS#12. The former is a proprietary format limited to the Java ecosystem, while the latter is an industry-wide used format. To make a PKCS#12 file from an already existing PEM certificate, run these commands:

    openssl pkcs12 \
        -export \
        -in cert.pem -inkey key.pem \
        -out cert.p12 -passout pass:123456
    
    chmod 440 *.p12
    
  • Use the certificate in your application.

    Place your PKCS#12 file cert.p12 in src/main/resources/, and add this to the application.properties file:

    server.ssl.key-store=classpath:cert.p12
    server.ssl.key-store-password=123456
    server.ssl.key-store-type=PKCS12
    
  • Start the Spring Boot application:

    mvn spring-boot:run \
        -Dspring-boot.run.jvmArguments="-Dkms.url=ws://{KMS_HOST}:8888/kurento"
    

Note

If you plan on using a webserver as proxy, like Nginx or Apache, you’ll need to setAllowedOrigins when registering the handler. Please read the official Spring documentation entry for more info.

Configure a Node server to use HTTPS

  • Obtain a certificate. For this, either request one from a trusted Certification Authority (CA), or generate your own one as explained here: Generating a self-signed certificate.

  • Add the following changes to your server.js, in order to enable HTTPS:

    ...
    var express = require('express');
    var ws      = require('ws');
    var fs      = require('fs');
    var https   = require('https');
    ...
    
    var options =
    {
      cert: fs.readFileSync('cert.pem'),
      key:  fs.readFileSync('key.pem'),
    };
    
    var app = express();
    
    var server = https.createServer(options, app).listen(port, function() {
    ...
    });
    ...
    
    var wss = new ws.Server({
     server : server,
     path : '/'
    });
    
    wss.on('connection', function(ws) {
    
    ....
    
  • Start application

npm start

Configure JavaScript applications to use HTTPS

WebRTC requires HTTPS, so your JavaScript application must be served by a secure web server. You can use whichever one you prefer, such as Nginx or Apache. For quick tests, a very straightforward option is to use the simple, zero-configuration http-server based on Node.js:

curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install --yes nodejs
sudo npm install -g http-server
  • Obtain a certificate. For this, either request one from a trusted Certification Authority (CA), or generate your own one as explained here: Generating a self-signed certificate.

  • Start the HTTPS web server, using the SSL certificate:

    http-server -p 8443 --ssl --cert cert.pem --key key.pem
    

Securing Kurento Media Server

Signaling Plane authorization

You should protect the JSON-RPC API control port (WebSocket port, by default TCP 8888) of your Kurento Media Server instances from unauthorized access from public networks.

The Kurento WebSocket server supports using SSL certificates in order to guarantee secure communications between clients and server; however, at the time no authentication mechanism is provided. Kurento doesn’t reinvent the wheel here including its own mechanism, and instead it relies on layers of security that already exist at the system level. This is something we may add (contributions are welcomed!) but for now here are some tips on how other big players are protecting KMS from unauthorized use.

Think of KMS like you would of a database in a traditional web application; there are two levels:

  1. The application level. We usually call this the “Application Server” of Kurento Media Server. It usually is a web application that uses Kurento Client to access Kurento API.
  2. The media level (actual audio/video transmissions to/from KMS).

The idea is that nobody unauthorized should be able to access the exchanged media. At the application level we can use all the available techniques used to protect any web server, for example with a custom user/password mechanism. Regarding KMS, the idea is that only the Application Server can access KMS. We can restrict that at the system level, for example using iptables to restrict all incoming WebSocket connections to KMS only from a given host, or a given subnet, similar to this: Iptables Essentials: Common Firewall Rules and Commands (archive). It may be a good idea to have the Application Server running in the same host than the Media Server, and in that case just restrict incoming connections to the same host.

If you need more flexibility, one idea is to restrict KMS connections to the same host using iptables and then implement a WebSocket proxy in the same machine (e.g. using nginx) that has its resources secured, as in NGINX as a WebSocket Proxy (archive) or WebSocket proxying (archive); this way, the Application Server connects to the WebSocket proxy that can indeed be secured, and thus only authenticated users from remote hosts can gain access to KMS.

Signaling Plane security (WebSocket)

With the default configuration, Kurento Media Server will use the ws:// URI scheme for non-secure WebSocket connections, listening on the port TCP 8888. Application Servers (Kurento clients) will establish a WebSocket connection with KMS, in order to control the media server and send messages conforming to the Kurento API.

This is fine for initial stages of application development, but before deploying on production environments you’ll probably want to move to wss:// connections, i.e. using Secure WebSocket, which by default uses the port TCP 8433.

To enable Secure WebSocket, edit the main KMS configuration file (/etc/kurento/kurento.conf.json), and un-comment the following lines:

"secure": {
  "port": 8433,
  "certificate": "cert+key.pem",
  "password": "KEY_PASSWORD"
}

If you use a signed certificate issued by a trusted Certification Authority (CA) such as Verisign or Let’s Encrypt, then you are done. Just skip to the next section: Connecting to Secure WebSocket.

However, if you are going to use an untrusted self-signed certificate (typically during development), there is still more work to do.

Generate your own certificate as explained here: Generating a self-signed certificate. Now, because self-signed certificates are untrusted by nature, client browsers and server applications will reject it by default. You’ll need to force all consumers of the certificate to accept it:

  • Java applications. Follow the instructions of this link: SunCertPathBuilderException: unable to find valid certification path to requested target (archive).

    Get InstallCert.java from here: https://github.com/escline/InstallCert.

    You’ll need to instruct the KurentoClient to allow using certificates. For this purpose, create an JsonRpcClient:

    SslContextFactory sec = new SslContextFactory(true);
    sec.setValidateCerts(false);
    JsonRpcClientWebSocket rpcClient = new JsonRpcClientWebSocket(uri, sec);
    KurentoClient kurentoClient = KurentoClient.createFromJsonRpcClient(rpcClient);
    
  • Node applications. Take a look at this page: Painless Self Signed Certificates in node.js (archive).

    For a faster but INSECURE alternative, configure Node to accept (instead of reject) invalid TLS certificates by default, setting the environment variable flag NODE_TLS_REJECT_UNAUTHORIZED to 0; this will disable the TLS validation for your whole Node app. You can set this environment variable before executing your app, or directly in your app code by adding the following line before performing the connection:

    process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
    
  • Browser JavaScript. Similar to what happens with self-signed certificates used for HTTPS, browsers also require the user to accept a security warning before Secure WebSocket connections can be established. This is done by directly opening the KMS WebSocket URL: https://{KMS_HOST}:8433/kurento.

Connecting to Secure WebSocket

Now that KMS is listening for Secure WebSocket connections, and (if using a self-signed certificate) your Application Server is configured to accept the certificate used in KMS, you have to change the WebSocket URL used in your application logic.

Make sure your application uses a WebSocket URL that starts with wss:// instead of ws://. Depending on the platform, this is done in different ways:

  • Java: Launch with a kms.url property. For example:

    mvn spring-boot:run \
        -Dspring-boot.run.jvmArguments="-Dkms.url=wss://{KMS_HOST}:8433/kurento"
    
  • Node: Launch with the ws_uri command-line argument. For example:

    npm start -- --ws_uri="wss://{KMS_HOST}:8433/kurento"
    
  • Browser JavaScript: Application-specific method. For example, using hardcoded values:

    const ws_uri: "wss://" + location.hostname + ":8433/kurento";
    

Media Plane security (DTLS)

WebRTC uses DTLS for media data authentication. By default, if no certificate is provided for this, Kurento Media Server will auto-generate its own self-signed certificate for every WebRtcEndpoint instance, but it is also possible to provide an already existing certificate to be used for all endpoints.

To do so, edit the file /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini and set either pemCertificateRSA or pemCertificateECDSA with a file containing the concatenation of your certificate (chain) file(s) and the private key.

Setting a custom certificate for DTLS is needed, for example, for situations where you have to manage multiple media servers and want to make sure that all of them use the same certificate for their connections. Some browsers, such as Firefox, require this in order to allow multiple WebRTC connections from the same tab to different KMS instances.

Generating a self-signed certificate

You need to provide a valid SSL certificate in order to enable all sorts of security features, ranging from HTTPS to Secure WebSocket (wss://). For this, there are two alternatives:

  • Obtain a certificate from a trusted Certification Authority (CA). This should be your primary choice, and will be necessary for production-grade deployments.

  • Create your own untrusted self-signed certificate. This can ease operations during the phase of software development. You can search articles online that explain how to do this, for example this one.

    Alternatively, it is much easier and convenient to use a self-signed certificate generation tool, such as mkcert. This kind of tools already take into account the requisites and limitations of most popular applications and browsers, so that you don’t need to.

    Note that while a self-signed certificate can be used for web development, browsers will show a big security warning. Users will see this warning, and must click to accept the unsafe certificate before proceeding to the page.

    To generate new certificate files with mkcert, run these commands:

    # Generate new untrusted self-signed certificate files:
    CAROOT="$PWD" mkcert -cert-file cert.pem -key-file key.pem \
        "127.0.0.1" \
        "::1"       \
        "localhost" \
        "*.test.local"
    
    # Make a single file to be used with Kurento Media Server:
    cat cert.pem key.pem > cert+key.pem
    
    # Protect against writes
    chmod 440 *.pem
    

    This command is just an example, and includes some useful things for local development: access from localhost in its IPv4, IPv6, and hostname forms; and also the *.test.local wildcard, meant to allow adding any desired subdomains to the /etc/hosts file in your development computer(s), so these cert files can be used not only for localhost but also for remote tests in your LAN.

    Note

    Simply using *.test would be nice, but wildcards are forbidden for global TLDs, so it wouldn’t work. For example, MacOS 10.15 (Catalina) would reject such certificate (see mkcert bug 206). For this reason, we propose using *.test.local.

    You can also publish a new Zeroconf local domain for any development machine. For example, running this in Ubuntu:

    # Get and publish the IP address to the default network gateway.
    IP_ADDRESS="$(ip -4 -oneline route get 1 | grep -Po 'src \K([\d.]+)')"
    avahi-publish --address --no-reverse -v "dev.test.local" "$IP_ADDRESS"
    
  • (Optional) Convert your untrusted self-signed certificate into a trusted one. This is done by installing the Root CA into the client device.

    On computers, installing the Root CA is easy because mkcert does it for you:

    CAROOT="$PWD" mkcert -install
    

    Installing the Root CA on mobile devices is a bit more difficult, because you cannot simply run mkcert:

    • On iOS, you can either use AirDrop, email the CA to yourself, or serve it from an HTTP server. After installing it, you must enable full trust in it. Note: earlier versions of mkcert ran into an iOS bug, if you can’t see the root in “Certificate Trust Settings” you might have to update mkcert and regenerate the root.

      Note that only AirDrop, Apple Mail, or Safari are allowed to download and install certificates on iOS. Other applications will not work for this.

    • For Android, you will have to install the CA and then enable user roots in the development build of your app. See this StackOverflow answer.