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