diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index 87c760aa..9bf393b1 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -32,6 +32,7 @@ foregrounder gifv github + hansshake hashtag hashtags hhmm diff --git a/app/build.gradle b/app/build.gradle index a604d80d..1350235e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -116,6 +116,7 @@ dependencies { kapt 'com.github.bumptech.glide:compiler:4.7.1' // kotlin では annotationProcessor の代わりに kapt を使う + implementation "org.conscrypt:conscrypt-android:1.3.0" implementation 'uk.co.chrisjenx:calligraphy:2.3.0' diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt index d5050ba0..ef80bf24 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt @@ -46,6 +46,7 @@ import org.json.JSONObject import java.io.ByteArrayInputStream import java.io.IOException import java.util.* +import javax.net.ssl.HttpsURLConnection class ActMediaViewer : AppCompatActivity(), View.OnClickListener { @@ -330,6 +331,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener { return } + // https://github.com/google/ExoPlayer/issues/1819 + HttpsURLConnection.setDefaultSSLSocketFactory(MySslSocketFactory) + exoView.visibility = View.VISIBLE val defaultBandwidthMeter = DefaultBandwidthMeter() diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index 3c8ccea0..3ddb6839 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -29,8 +29,10 @@ import jp.juggler.subwaytooter.util.CustomEmojiLister import jp.juggler.subwaytooter.util.ProgressResponseBody import jp.juggler.util.* import okhttp3.* +import org.conscrypt.Conscrypt import java.io.File import java.io.InputStream +import java.security.Security import java.util.* import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadFactory @@ -219,20 +221,20 @@ class App1 : Application() { timeoutSecondsConnect : Int, timeoutSecondsRead : Int ) : OkHttpClient.Builder { + val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .cipherSuites(*APPROVED_CIPHER_SUITES) + .allEnabledCipherSuites() + .allEnabledTlsVersions() + .supportsTlsExtensions(true) .build() - val spec_list = ArrayList() - spec_list.add(spec) - spec_list.add(ConnectionSpec.CLEARTEXT) - return OkHttpClient.Builder() .connectTimeout(timeoutSecondsConnect.toLong(), TimeUnit.SECONDS) .readTimeout(timeoutSecondsRead.toLong(), TimeUnit.SECONDS) .writeTimeout(timeoutSecondsRead.toLong(), TimeUnit.SECONDS) .pingInterval(10, TimeUnit.SECONDS) - .connectionSpecs(spec_list) + .connectionSpecs(Collections.singletonList(spec)) + .sslSocketFactory(MySslSocketFactory,MySslSocketFactory.trustManager) .addInterceptor(ProgressResponseBody.makeInterceptor()) .addInterceptor(user_agent_interceptor) } @@ -257,6 +259,12 @@ class App1 : Application() { var state = appStateX if(state != null) return state + // initialize Conscrypt + Security.insertProviderAt( + Conscrypt.newProvider(), + 1 /* 1 means first position */ + ) + initializeFont() pref = Pref.pref(app_context) diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt index 1822c086..86530d04 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt @@ -50,6 +50,7 @@ class TootApiClient( companion object { private val log = LogCategory("TootApiClient") + val debugHandshake = true private const val DEFAULT_CLIENT_NAME = "SubwayTooter" internal const val KEY_CLIENT_CREDENTIAL = "SubwayTooterClientCredential" @@ -266,6 +267,8 @@ class TootApiClient( ////////////////////////////////////////////////////////////////////// // ユーティリティ + + // リクエストをokHttpに渡してレスポンスを取得する internal inline fun sendRequest( result : TootApiResult, @@ -288,7 +291,15 @@ class TootApiClient( ) ) - result.response = httpClient.getResponse(request, tmpOkhttpClient = tmpOkhttpClient) + val response = httpClient.getResponse(request, tmpOkhttpClient = tmpOkhttpClient) + result.response = response + + if(debugHandshake) { + val handshake = response.handshake() + if(handshake != null) { + log.d("handshake ${handshake.tlsVersion()},${handshake.cipherSuite()} ${request.url()}") + } + } null == result.error diff --git a/app/src/main/java/jp/juggler/util/MySslSocketFactory.kt b/app/src/main/java/jp/juggler/util/MySslSocketFactory.kt new file mode 100644 index 00000000..8bbf7fd3 --- /dev/null +++ b/app/src/main/java/jp/juggler/util/MySslSocketFactory.kt @@ -0,0 +1,119 @@ +package jp.juggler.util + +import java.io.IOException +import java.net.InetAddress +import java.net.Socket +import java.net.UnknownHostException +import java.security.KeyStore +import javax.net.ssl.* + +object MySslSocketFactory : SSLSocketFactory() { + + var debugCipherSuites = false + + private val log = LogCategory("MySslSocketFactory") + + private val originalFactory: SSLSocketFactory = + SSLContext.getInstance("TLS").apply { + init(null, null, null) + }.socketFactory + + private fun check(socket: Socket?): Socket? { + + // 端末のデフォルトでは1.3が含まれないので追加する + (socket as? SSLSocket)?.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2", "TLSv1.3") + + // デバッグフラグが変更された後に1回だけ、ソケットの暗号化スイートを列挙する + if (debugCipherSuites) { + debugCipherSuites = false + (socket as? SSLSocket)?.enabledCipherSuites?.forEach { cs -> + log.d("getEnabledCipherSuites : $cs") + } + } + + return socket + } + + override fun getDefaultCipherSuites(): Array { + return originalFactory.defaultCipherSuites + } + + override fun getSupportedCipherSuites(): Array { + return originalFactory.supportedCipherSuites + } + + @Throws(IOException::class) + override fun createSocket(): Socket? { + return check(originalFactory.createSocket()) + } + + @Throws(IOException::class) + override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? { + return check( + originalFactory.createSocket( + s, + host, + port, + autoClose + ) + ) + } + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(host: String, port: Int): Socket? { + return check( + originalFactory.createSocket( + host, + port + ) + ) + } + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? { + return check( + originalFactory.createSocket( + host, + port, + localHost, + localPort + ) + ) + } + + @Throws(IOException::class) + override fun createSocket(host: InetAddress, port: Int): Socket? { + return check( + originalFactory.createSocket( + host, + port + ) + ) + } + + @Throws(IOException::class) + override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? { + return check( + originalFactory.createSocket( + address, + port, + localAddress, + localPort + ) + ) + } + + // + val trustManager: X509TrustManager by lazy { + val trustManagers = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()) + .apply { init(null as KeyStore?) } + .trustManagers + + trustManagers + .find { it is X509TrustManager } + as? X509TrustManager + ?: error("missing X509TrustManager in $trustManagers") + } + +} \ No newline at end of file