Oh boy, I seem to have broken everything.

Lazyweb, what's the proper way to take an infinite stream of MP3 bytes and publish to an SSL-enabled Icecast server?

It turns out that now that I'm doing Strict-Transport-Security and everything on my web servers is SSL, various browsers are now expecting the Icecast streams on my domain to be SSL too. I'm not sure how backward-compatible that is, but fine, let's do it. So I got Icecast 2.4.3 configured with SSL, I think. At least, at startup it says "SSL certificate found".

But I think the problem now is that I need to publish audio to the server over SSL too, and I can't figure out how to do that. I had been using this "Shout-2.1" Perl module, last modified in 2005. I guess it's just a wrapper over libshout, of which I have 2.2.2. This had been working fine until I turned on SSL, but now I can't figure out how to make it go. Help?

Previously, previously, previously.

Tags: , , ,

17 Responses:

  1. Rodger says:

    If there's no right way (and backporting SSL for libshout isn't going to be it, right?), can you use stunnel to fake it?

  2. Kyzer says:

    libshout supports SSL since version 2.4.0 (released November 2015) - use shout_set_tls(SHOUT_TLS_RFC2818) shout_set_allowed_ciphers() and shout_set_ca_file().

    You're using libshout 2.2.2, published June 2006 (12 years ago). The Right Thing™ would be to upgrade libshout to the current 2.4.1, and update the Shout-2.1 perl module to wrap over these three new functions. There wouldn't be a problem, because its own authors haven't updated it for 14 years.

    • jwz says:

      Well this is all fantastic.

      I added those C bindings to Shout 2.1, and it does not appear to be doing shit with SHOUT_TLS_AUTO_NO_PLAIN or SHOUT_TLS_RFC2817: send_data() never returns. And with SHOUT_TLS_RFC2818, it says "TLS connection can not be established because of bad certificate".

      Is there something else I should be using to stream realtime audio that is not Icecast?

      • Kyzer says:

        According to the libshout code, it returns that error if the hostname (which you set with shout_set_host() doesn't match the one in the certificate. It has its own hand-rolled checker, which supports wildcards, but not Subject Alternative Name extensions. If you know to add #define XXX_HAVE_X509_check_host 1 to config.h (it's not auto-detected), it will use X509_check_host from OpenSSL instead, which will do the check correctly.

        • Nick Lamb says:

          It has its own hand-rolled checker, which supports wildcards, but not Subject Alternative Name extensions.

          Yeah, that's going to have been it, DNA Lounge has a single cert with the and then that plus a bunch of named hosts as SANs. So unless the shout connection goes to raw it won't match with this hand-rolled checker.

          SANs are twenty years old. Even Microsoft's half-arsed SChannel implementation learned how to do them correctly as-of Windows 8 or something. Browsers began phasing out support for the grandfathered "just scribble something in the Common Name and we'll try to parse that" approach back in 2017 and Let's Encrypt is looking at how to do issuance without this back compatibility hack (they probably won't forbid you from using it, just make it optional because it causes real problems for some people).

          Anybody else following: Add that define and let somebody who at least heard of SANs handle the matching. Hand-rolled name matching - especially hand-rolled name matching that tries to parse CN, a field containing arbitrary human readable text - is not a good idea.

  3. I'm pretty sure that I'm not understanding properly your problem but there should be no need to publish over ssl.

    On the other hand, I gave up trying to make ssl work properly on our icecast servers and just put them behind nginx. Our config is basically this:

    location ~ \.mp3$ {
    proxy_buffering off;
    proxy_pass http://localhost:8000;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    And a couple of things added by Certbot.

    • Nick Lamb says:

      Yeah, the chances are that a general purpose web server doing forwarding/proxy does a better job and will continue to do a better job, of implementing this correctly.

      If the Icecast server shares keys with anything else, and there's a bug in the Icecast server, there's a good chance this ruins security of other things it shared a key with. For example somebody recently published a cute "One file" SSL implementation and it doesn't prevent a trivial Bleichenbacher attack on your RSA keys (the requirement to prevent this attack is described in all the TLS RFCs except TLS 1.3, if you only implement TLS 1.3 you can't be attacked so it doesn't need to explain how to prevent the attack). You can easily imagine somebody saying "Yeah, but who'd target my toy IRC server" or whatever. But the bad guys wouldn't target your IRC server, they use the IRC server as a Bleichenbacher Oracle, and get the ability to transparently impersonate any server with the same keys like say your main sign-in page.

      Also the general purpose web server acting as proxy probably either has or will get the capability to sort out Let's Encrypt certs as needed, whereas good luck writing yet more dubious Perl scripts to hack that into Icecast.

      • jwz says:

        "You shouldn't turn on SSL inside the icecast server but should proxy it instead" is interesting advice, but not the problem I am currently facing. I'm trying to make the client speak SSL.

        • Nick Lamb says:

          Looks like you eventually got to the implied next step on your own judging from timestamps on other replies.

          For anybody else: Rather than getting involved in a big re-engineering project, just wrap the public output with a proxy. This doesn't mean "reconnect all the internal parts to the proxy", just the great unwashed. Any toxic dust from the existing Icecast server, Perl, etcetera stays where it is, rather than being thrown up into the air to cause mayhem.

          • jwz says:

            Yeah, I was also able to (I think) solve my remaining websock problem by doing the same thing there. It turns out that IO::Socket::SSL is not suitable for use in servers, because as it tries to transparently hide SSL and pretend it's just a plain old socket, it does blocking reads at random times. Hooray! Anyway, here's how you let Apache wrap SSL on top of a non-SSL websocket server, for the record:

            LoadModule proxy_module modules/
            LoadModule proxy_wstunnel_module modules/

            <VirtualHost *:8003>
              ProxyPass / "ws://localhost:7999/" disablereuse=on
              ProxyPassReverse / "ws://localhost:7999/" disablereuse=on
              SSLEngine on

            You definitely need disablereuse. Without that, Apache reuses .... something? I'm not sure what, or what goes wrong, but something about the websocket protocol is stateful and non-sharable. Probably mod_proxy_wstunnel should be turning that on by default. It took me a while to figure out what was going on there.

            I wonder if it's also needed for icecast? Once you're out of the headers that's just MPEG frames, but you still need the Range header to be processed properly at startup.

    • Doctor Memory says:

      Came here to say the above. The odds of a random teetering collection of perl modules being able to competently and reliably stay on top of the current state of TLS is... well... here we are. Put nginx or apache (or something like tinyproxy) in front of your service and be done.

      Alternative approach: since you're already IN THE CLOWN, spin up an ELB, configure your icecast server as its sole backend, attach a (free!) amazon cert to it and point the relevant domain name at the load balancer.

  4. Kyzer says:

    I missed shout_set_ca_directory(), and you probably do want SHOUT_TLS_RFC2818, because that means "speak TLS immediately".

    However, I'm now starting to err on the side of the other commenters; it would probably be more secure to work out how to proxy Icecast through Apache and leave your Icecast setup untouched.

    • jwz says:

      I'm poking around at that, but this also appears to be rocket surgery. If anyone can tell me how to make Apache 2.4 proxy the icecast running on port 8000 so that, let's say, port 8002 is the https version, that would be swell. Some initial searching shows people saying, "if you try to proxy long-lived streams Apache will fall over."

      • Kyzer says:

        Something like this:

        LoadModule proxy_module /usr/lib/apache/modules/
        Listen 8002

        <VirtualHost *:8002>
        SSLCertificateFile /blah
        SSLCertificateKeyFile /blah
        Include /the/recommended/apache/settings/for/ssl
        ProxyPass / http://localhost:8000

        • jwz says:

          Ok, I think I got it working... Both ProxyPass and ProxyPassReverse seem to be needed, along with

  5. Mike S says:

    You should be able to configure icecast to listen on two sockets (with two listen-socket blocks in the confg): one SSL that you have open to the world, one non-SSL, that is only used by your perl client.

    (from a lapsed former icecast developer...)

    • jwz says:

      Oh, I should have thought of that. Thanks! But I got the apache-proxy thing working, which sounds safer all around.

  • Previously