Feature Request: Ability to add/use self-signed certificates for HLS streams in Custom Channels source

Feature Request: Ability to add/use self-signed certificates for HLS streams in Custom Channels source.

I'm attempting to utilize LLHLS streams from docker-wyze-bridge.

When setting LLHLS=true in my Docker compose, I'm able to access the stream through a browser by IP or DNS hostname (https://nas01.lan:8888/living-room/ or https://172.16.0.15:8888/living-room/) after bypassing the certificate warning (NET::ERR_CERT_AUTHORITY_INVALID).

However, I'm unable to view the HLS stream (HTTPS) through Channels:

hostname

#[ERR] Failed to start stream for ch9340: M3U: Could not fetch playlist: nas01.lan:8888: Get "https://nas01.lan:8888/living-room/stream.m3u8": x509: certificate is not valid for any names, but wanted to match nas01.lan
#[HLS] Couldn't generate stream playlist for ch9340-dANY-ip172.16.0.246: M3U: Could not fetch playlist: nas01.lan:8888: Get "https://nas01.lan:8888/living-room/stream.m3u8": x509: certificate is not valid for any names, but wanted to match nas01.lan

IP Address

#[ERR] Failed to start stream for ch9340: M3U: Could not fetch playlist: 172.16.0.15:8888: Get "https://172.16.0.15:8888/living-room/stream.m3u8": x509: cannot validate certificate for 172.16.0.15 because it doesn't contain any IP SANs
#[HLS] Couldn't generate stream playlist for ch9340-dANY-ip172.16.0.246: M3U: Could not fetch playlist: 172.16.0.15:8888: Get "https://172.16.0.15:8888/living-room/stream.m3u8": x509: cannot validate certificate for 172.16.0.15 because it doesn't contain any IP SANs

Note: I'm able to access the (HTTP) stream through Channels when NOT using LLHLS=true environment variable.

It's my understanding that LLHLS requires a certificate, and I believe that it's automatically generated and stored in /tokens in the container.

I've tried bridge and host networking in Docker, as well as setting the environment variable WB_HLS_URL=https://nas01.lan:8888/ (although I don't believe this is the intended use case).

Inside the Channels container:

# curl -v https://172.16.0.15:8888/living-room/
*   Trying 172.16.0.15:8888...
* Connected to 172.16.0.15 (172.16.0.15) port 8888 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.0 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Unknown (21):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: self-signed certificate
* Closing connection 0
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

I then copied and installed the certificates from the wyze bridge container to the channels container, per the recommendation in the issue that I created.

admin@nas01:/volume1/config/wyze/tokens$ cp hls_server.crt /volume1/config/channels/data/
admin@nas01:/volume1/config/channels/data$ sudo chown root:root hls_server.crt && sudo chmod 644 hls_server.crt

Added bind mount and recreated the container.

    volumes:
      - /volume1/config/channels/data/hls_server.crt:/usr/local/share/ca-certificates/hls_server.crt

Open console for Channels with Portainer:

ls -l /etc/ssl/certs/ca-certificates.crt
-rw-r--r--    1 root     root        211749 Jan 16 16:21 ca-certificates.crt`
update-ca-certificates
ls -l /etc/ssl/certs/ca-certificates.crt
-rw-r--r--    1 root     root        213052 Feb 19 23:35 ca-certificates.crt
lrwxrwxrwx    1 root     root            47 Feb 19 23:35 ca-cert-hls_server.pem -> /usr/local/share/ca-certificates/hls_server.crt

ca-certificates.crt file did update, and the hls_server.pem file is now installed.

Ran curl inside channels container again:

# curl -v https://172.16.0.15:8888/living-room/
*   Trying 172.16.0.15:8888...
* Connected to 172.16.0.15 (172.16.0.15) port 8888 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.0 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=US; ST=WA; L=Kirkland; O=WYZE BRIDGE; CN=wyze-bridge
*  start date: Feb 20 04:32:14 2023 GMT
*  expire date: Feb 17 04:32:14 2033 GMT
* SSL: certificate subject name 'wyze-bridge' does not match target host name '172.16.0.15'
* Closing connection 0
[12:05 AM]
continued:
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS alert, close notify (256):
curl: (60) SSL: certificate subject name 'wyze-bridge' does not match target host name '172.16.0.15'
More details here: https://curl.se/docs/sslcerts.html

Created a custom DNS for wyze-bridge pointing to the docker host IP (172.16.0.15) in Pi-hole:

# curl -v https://wyze-bridge:8888/living-room/
*   Trying 172.16.0.15:8888...
* Connected to wyze-bridge (172.16.0.15) port 8888 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.0 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=US; ST=WA; L=Kirkland; O=WYZE BRIDGE; CN=wyze-bridge
*  start date: Feb 20 04:32:14 2023 GMT
*  expire date: Feb 17 04:32:14 2033 GMT
*  common name: wyze-bridge (matched)
*  issuer: C=US; ST=WA; L=Kirkland; O=WYZE BRIDGE; CN=wyze-bridge
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* h2h3 [:method: GET]
* h2h3 [:path: /living-room/]
* h2h3 [:scheme: https]
* h2h3 [:authority: wyze-bridge:8888]
* h2h3 [user-agent: curl/7.87.0]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x7f117e088a90)
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /living-room/ HTTP/2
> Host: wyze-bridge:8888
> user-agent: curl/7.87.0
> accept: */*
> 
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 200 
< access-control-allow-credentials: true
< access-control-allow-origin: *
< content-type: text/html
< server: rtsp-simple-server
< content-length: 1240
< date: Mon, 20 Feb 2023 06:02:24 GMT
< 
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body {
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
}
#video {
        width: 100%;
        height: 100%;
        background: black;
}
</style>
</head>
<body>

<video id="video" muted controls autoplay playsinline></video>

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<script>

const create = () => {
        const video = document.getElementById('video');

        // always prefer hls.js over native HLS.
        // this is because some Android versions support native HLS
        // but don't support fMP4s.
        if (Hls.isSupported()) {
                const hls = new Hls({
                        maxLiveSyncPlaybackRate: 1.5,
                });

                hls.on(Hls.Events.ERROR, (evt, data) => {
                        if (data.fatal) {
                                hls.destroy();

                                setTimeout(create, 2000);
                        }
                });

                hls.loadSource('index.m3u8');
                hls.attachMedia(video);

                video.play();

        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                // since it's not possible to detect timeout errors in iOS,
                // wait for the playlist to be available before starting the stream
                fetch('stream.m3u8')
                        .then(() => {
                                video.src = 'index.m3u8';
                                video.play();
                        });
        }
};

window.addEventListener('DOMContentLoaded', create);

</script>

</body>
</html>
* Connection #0 to host wyze-bridge left intact

and the cert seemed to work. However, after updating the M3U with the new URL, https://wyze-bridge:8888/living-room/stream.m3u8, I receive this message:

x509: certificate relies on legacy Common Name field, use SANs instead
2023/02/20 00:12:13.795140 [HLS] Couldn't generate stream playlist for ch9340-dANY-ip172.16.0.246: M3U: Could not fetch playlist: wyze-bridge:8888: Get "https://wyze-bridge:8888/living-room/stream.m3u8": x509: certificate relies on legacy Common Name field, use SANs instead

Thank you @tmm1 for your help so far with this!

sounds like you might be able to generate the cert with subjectAltName set to get around this?

subjectAltName field was added to the dev branch.

Updated docker-compose.yml with
- SUBJECT_ALT_NAME=wyze-bridge

2023/02/24 02:45:07.803644 [ERR] Failed to start stream for ch9416: M3U: Could not fetch playlist: wyze-bridge:8888: Get "https://wyze-bridge:8888/living-room/stream.m3u8": x509: certificate signed by unknown authority
2023/02/24 02:45:07.841480 [HLS] Couldn't generate stream playlist for ch9416-dANY-ip172.16.0.246: M3U: Could not fetch playlist: wyze-bridge:8888: Get "https://wyze-bridge:8888/living-room/stream.m3u8": x509: certificate signed by unknown authority

Curl inside the Channels container:

# curl -v https://wyze-bridge:8888/living-room/
*   Trying 172.16.0.15:8888...
* Connected to wyze-bridge (172.16.0.15) port 8888 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.0 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Finished (20):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=US; ST=WA; L=Kirkland; O=WYZE BRIDGE; CN=wyze-bridge
*  start date: Feb 24 08:38:31 2023 GMT
*  expire date: Feb 21 08:38:31 2033 GMT
*  subjectAltName: host "wyze-bridge" matched cert's "wyze-bridge"
*  issuer: C=US; ST=WA; L=Kirkland; O=WYZE BRIDGE; CN=wyze-bridge
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* h2h3 [:method: GET]
* h2h3 [:path: /living-room/]
* h2h3 [:scheme: https]
* h2h3 [:authority: wyze-bridge:8888]
* h2h3 [user-agent: curl/7.87.0]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x7fcc54faca90)
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /living-room/ HTTP/2
> Host: wyze-bridge:8888
> user-agent: curl/7.87.0
> accept: */*
> 
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 200 
< access-control-allow-credentials: true
< access-control-allow-origin: *
< content-type: text/html
< server: rtsp-simple-server
< content-length: 1240
< date: Fri, 24 Feb 2023 08:53:50 GMT
< 
* [CONN-0-0][CF-SSL] TLSv1.2 (IN), TLS header, Supplemental data (23):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body {
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
}
#video {
        width: 100%;
        height: 100%;
        background: black;
}
</style>
</head>
<body>

<video id="video" muted controls autoplay playsinline></video>

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<script>

const create = () => {
        const video = document.getElementById('video');

        // always prefer hls.js over native HLS.
        // this is because some Android versions support native HLS
        // but don't support fMP4s.
        if (Hls.isSupported()) {
                const hls = new Hls({
                        maxLiveSyncPlaybackRate: 1.5,
                });

                hls.on(Hls.Events.ERROR, (evt, data) => {
                        if (data.fatal) {
                                hls.destroy();

                                setTimeout(create, 2000);
                        }
                });

                hls.loadSource('index.m3u8');
                hls.attachMedia(video);

                video.play();

        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                // since it's not possible to detect timeout errors in iOS,
                // wait for the playlist to be available before starting the stream
                fetch('stream.m3u8')
                        .then(() => {
                                video.src = 'index.m3u8';
                                video.play();
                        });
        }
};

window.addEventListener('DOMContentLoaded', create);

</script>

</body>
</html>
* Connection #0 to host wyze-bridge left intact
1 Like

Looks like this has come up a few times in the past by other community members:

No idea on how much development effort this would take or the best method (e.g. bypassing the certificate verification/validation). Safeguards can be added such as making this option appear on the Custom Channels page only when adding (but not editing) a new source AND requires using an ENV variable to explicitly enable the option.

:ballot_box_with_check: HLS stream (HTTPS) uses a self-signed certificate

Thanks!

With the next pre-release, you can add a file cacert.pem into the /channels-dvr/data folder inside your container and it will be added to the known authorities.

2 Likes

Just tested out the pre-release and it works. Thanks @tmm1!