ryanheise / just_audio

Audio Player
1.03k stars 647 forks source link

Play audio from an HTTPS server with self-signed certs #442

Open IrosTheBeggar opened 3 years ago

IrosTheBeggar commented 3 years ago

Is your feature request related to a problem? Please describe. I'm working on an app where users can stream songs from their own self-hosted servers. Some users setup their server with self-signed certs and are requesting this feature.

Describe the solution you'd like Right now using an audio source with self-signed certs causes the following error:

I/ExoPlayerImpl(21048): Init ffe29aa [ExoPlayerLib/2.13.1] [generic_x86, Android SDK built for x86, Google, 29]
E/ExoPlayerImplInternal(21048): Playback error
E/ExoPlayerImplInternal(21048):   com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579)
E/ExoPlayerImplInternal(21048):       at android.os.Handler.dispatchMessage(Handler.java:103)
E/ExoPlayerImplInternal(21048):       at android.os.Looper.loop(Looper.java:214)
E/ExoPlayerImplInternal(21048):       at android.os.HandlerThread.run(HandlerThread.java:67)
E/ExoPlayerImplInternal(21048):   Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: Unable to connect
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:356)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:201)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1015)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
E/ExoPlayerImplInternal(21048):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/ExoPlayerImplInternal(21048):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/ExoPlayerImplInternal(21048):       at java.lang.Thread.run(Thread.java:919)
E/ExoPlayerImplInternal(21048):   Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:231)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:196)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90)
E/ExoPlayerImplInternal(21048):       at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:543)
E/ExoPlayerImplInternal(21048):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349)
E/ExoPlayerImplInternal(21048):       ... 7 more
E/ExoPlayerImplInternal(21048):   Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:674)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:551)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:507)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:426)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:354)
E/ExoPlayerImplInternal(21048):       at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
E/ExoPlayerImplInternal(21048):       at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:89)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:224)
E/ExoPlayerImplInternal(21048):       at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSo
E/AudioPlayer(21048): TYPE_SOURCE: Unable to connect
I/ExoPlayerImpl(21048): Release ffe29aa [ExoPlayerLib/2.13.1] [generic_x86, Android SDK built for x86, Google, 29] [goog.exo.core]

Describe alternatives you've considered I do not know of any alternative solutions

Additional context Add any other context or screenshots about the feature request here.

ryanheise commented 3 years ago

Maybe the following will help?

https://developer.android.com/training/articles/security-config#TrustingAdditionalCas

IrosTheBeggar commented 3 years ago

Thanks for the quick reply. I looked over that doc and it looks like i would need to manually add any self singed certs to the app. Please correct me if in wrong.

In that case its not possible for me to do that. The only compromise would be users with self signed certs would have to compile their own version of the app.

defsub commented 3 years ago

Can you suggest they use the free let's encrypt service? I use this in a similar service.

ryanheise commented 3 years ago

That's a good option. ExoPlayer also provides an API to disable certificate checking. If that's useful as a fallback option, I could add this API to just_audio.

IrosTheBeggar commented 3 years ago

I reccommend and use let's encrypt myself (along with with acme.sh). But theres still demand for this feature

@ryanheise an option to disable cert checking would be perfect. Its what I've been doing for the API calls.

ryanheise commented 3 years ago

That option would only work provided ExoPlayer is loading the audio directly rather than through just_audio's proxy (which will happen if you're sending headers in the request).

Speaking of the proxy, that gives me another idea.

You can probably already achieve the desired outcome by implementing a custom audio source that's a subclass of StreamAudioSource. Within it, you could open the HTTPS connection yourself in Dart and stream the bytes into ExoPlayer, while using Dart's own APIs for trusting the self-signed certificate. (LockCachingAudioSource would be a good starting point if you rip out all of the caching part.)

IrosTheBeggar commented 3 years ago

I'm not using any headers, so the disable cert checking option would work fine

ryanheise commented 3 years ago

Looking further, it was not actually an API of ExoPlayer but a separate API that turns of Android's certificate checking for the whole app. This also means you could manage it by implementing the following solution in your app's Application class:

https://stackoverflow.com/questions/53931187/how-to-solve-javax-net-ssl-sslhandshakeexception-error-without-google-play-servi

For example:

package your.domain.name.or.applicationid.etc;

import android.app.Application;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        disableSSLCertificateChecking();
    }

    private static void disableSSLCertificateChecking() {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
        }};

        try {
            SSLContext sc = SSLContext.getInstance("TLS");

            sc.init(null, trustAllCerts, new java.security.SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

Then you can declare this application class name in your android manifest.

IrosTheBeggar commented 3 years ago

I tried the solution you posted on got a new error:

I/ExoPlayerImpl( 4272): Init 365b99b [ExoPlayerLib/2.13.1] [generic_x86, Android SDK built for x86, Google, 29]
E/ExoPlayerImplInternal( 4272): Playback error
E/ExoPlayerImplInternal( 4272):   com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579)
E/ExoPlayerImplInternal( 4272):       at android.os.Handler.dispatchMessage(Handler.java:103)
E/ExoPlayerImplInternal( 4272):       at android.os.Looper.loop(Looper.java:214)
E/ExoPlayerImplInternal( 4272):       at android.os.HandlerThread.run(HandlerThread.java:67)
E/ExoPlayerImplInternal( 4272):   Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: Unable to connect
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:356)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:201)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1015)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
E/ExoPlayerImplInternal( 4272):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/ExoPlayerImplInternal( 4272):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/ExoPlayerImplInternal( 4272):       at java.lang.Thread.run(Thread.java:919)
E/ExoPlayerImplInternal( 4272):   Caused by: javax.net.ssl.SSLPeerUnverifiedException: Hostname 10.0.2.2 not verified:
E/ExoPlayerImplInternal( 4272):       certificate: sha1/5wWUS9PUh+kWwxKPxYOzwafkdiA=
E/ExoPlayerImplInternal( 4272):       DN: 1.2.840.113549.1.9.1=#16036c6f6c,CN=lol,OU=lol,O=lol,L=lol,ST=lol,C=US
E/ExoPlayerImplInternal( 4272):       subjectAltNames: []
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:205)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90)
E/ExoPlayerImplInternal( 4272):       at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:543)
E/ExoPlayerImplInternal( 4272):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349)
E/ExoPlayerImplInternal( 4272):       ... 7 more
E/AudioPlayer( 4272): TYPE_SOURCE: Unable to connect
Connecting to VM Service at ws://127.0.0.1:4664/cXjzRxfpVkI=/ws
W/IInputConnectionWrapper( 4272): getTextBeforeCursor on inactive InputConnection
I/ExoPlayerImpl( 4272): Release 365b99b [ExoPlayerLib/2.13.1] [generic_x86, Android SDK built for x86, Google, 29] [goog.exo.core]
IrosTheBeggar commented 3 years ago

For reference, my MyApplication.java file:

package com.example.mstream_music;

import android.app.Application;
import java.security.KeyStore;
import javax.net.ssl.*;
import java.security.cert.*;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        disableSSLCertificateChecking();
    }

    private static void disableSSLCertificateChecking() {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
        }};

        try {
            SSLContext sc = SSLContext.getInstance("TLS");

            sc.init(null, trustAllCerts, new java.security.SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

And my AndroidManifest.xml

 <application
        android:name=".MyApplication"
defsub commented 3 years ago

Trying calling setDefaultHostnameVerifier on the HttpsURLConnection with your own HostnameVerifier implementation that has a verify which always returns true.

Do-Repo commented 3 years ago

I'm new to all this, but going through the same error when using await _audioPlayer.setUrl(url); So what am I supposed to do now in order to fix this issue?

ryanheise commented 3 years ago

Thanks for the suggestion @defsub . @IrosTheBeggar does that work for you?

If someone wants to share an example URL that fails, I can try testing some of the various solutions.

IrosTheBeggar commented 3 years ago

@ryanheise @defsub

This worked!

Here's the final java code for anyone who needs it

package com.example.mstream_music;

import android.app.Application;
import java.security.KeyStore;
import javax.net.ssl.*;
import java.security.cert.*;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        disableSSLCertificateChecking();
    }

    private static void disableSSLCertificateChecking() {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
        }};

        try {
            SSLContext sc = SSLContext.getInstance("TLS");

            sc.init(null, trustAllCerts, new java.security.SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            HttpsURLConnection.setDefaultHostnameVerifier(
                new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                }
            );
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}
nateshmbhat commented 2 weeks ago

i was getting this error when the mobile network connection was not working. after connecting to working network connection, it got resolved.