Skip to content
This repository has been archived by the owner on Apr 27, 2021. It is now read-only.

Commit

Permalink
Add ability to exclude hosts fixing #16 (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Dolan authored Aug 14, 2019
1 parent b9f357f commit d9618d2
Show file tree
Hide file tree
Showing 19 changed files with 336 additions and 51 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ match any sub-domain but not "babylonhealth.com" with no subdomain.

```kotlin
val interceptor = certificateTransparencyInterceptor {
// Enable for the provided hosts
+"*.babylonhealth.com"

// Exclude specific hosts
-"legacy.babylonhealth.com"
}

val client = OkHttpClient.Builder().apply {
Expand Down Expand Up @@ -127,7 +131,11 @@ before calling connect() on the connection:
val connection = URL("https://www.babylonhealth.com").openConnection()
if (connection is HttpsURLConnection) {
connection.hostnameVerifier = certificateTransparencyHostnameVerifier(connection.hostnameVerifier) {
// Enable for the provided hosts
+"*.babylonhealth.com"

// Exclude specific hosts
-"legacy.babylonhealth.com"
}
}
```
Expand All @@ -146,7 +154,11 @@ val requestQueue = Volley.newRequestQueue(applicationContext, object : HurlStack
val connection = super.createConnection(url)
if (connection is HttpsURLConnection) {
connection.hostnameVerifier = certificateTransparencyHostnameVerifier(connection.hostnameVerifier) {
// Enable for the provided hosts
+"*.babylonhealth.com"

// Exclude specific hosts
-"legacy.babylonhealth.com"
}
}
return connection
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ buildscript {
}

plugins {
id("io.gitlab.arturbosch.detekt") version "1.0.0-RC16"
id("io.gitlab.arturbosch.detekt") version "1.0.0"
id("com.github.ben-manes.versions") version "0.22.0"
id("com.appmattus.markdown") version "0.4.1"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ import javax.net.ssl.X509TrustManager
* transparency
* @property delegate [HostnameVerifier] to delegate to before performing certificate transparency checks
*/
@Suppress("TooManyFunctions")
class CTHostnameVerifierBuilder(
@Suppress("MemberVisibilityCanBePrivate") val delegate: HostnameVerifier
) {
private var trustManager: X509TrustManager? = null
private var logListDataSource: DataSource<LogListResult>? = null
private val hosts = mutableSetOf<Host>()
private val includeHosts = mutableSetOf<Host>()
private val excludeHosts = mutableSetOf<Host>()

/**
* Determine if a failure to pass certificate transparency results in the connection being closed. A value of true ensures the connection is
Expand Down Expand Up @@ -117,18 +119,18 @@ class CTHostnameVerifierBuilder(
* @property pattern lower-case host name or wildcard pattern such as `*.example.com`.
*/
@Suppress("MemberVisibilityCanBePrivate")
fun addHost(vararg pattern: String) = apply {
pattern.forEach { hosts.add(Host(it)) }
fun includeHost(vararg pattern: String) = apply {
pattern.forEach { includeHosts.add(Host(it)) }
}

/**
* Verify certificate transparency for host that match [this].
* Verify certificate transparency for hosts that match [this].
*
* @receiver lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun String.unaryPlus() {
addHost(this)
includeHost(this)
}

/**
Expand All @@ -138,15 +140,46 @@ class CTHostnameVerifierBuilder(
*/
@JvmSynthetic
operator fun List<String>.unaryPlus() {
forEach { addHost(it) }
forEach { includeHost(it) }
}

/**
* Ignore certificate transparency for hosts that match [pattern].
*
* @property pattern lower-case host name or wildcard pattern such as `*.example.com`.
*/
@Suppress("MemberVisibilityCanBePrivate")
fun excludeHost(vararg pattern: String) = apply {
pattern.forEach { excludeHosts.add(Host(it)) }
}

/**
* Ignore certificate transparency for hosts that match [this].
*
* @receiver lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun String.unaryMinus() {
excludeHost(this)
}

/**
* Ignore certificate transparency for hosts that match one of [this].
*
* @receiver [List] of lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun List<String>.unaryMinus() {
forEach { excludeHost(it) }
}

/**
* Build the [HostnameVerifier]
*/
fun build(): HostnameVerifier = CertificateTransparencyHostnameVerifier(
delegate,
hosts.toSet(),
includeHosts.toSet(),
excludeHosts.toSet(),
trustManager,
logListDataSource,
failOnError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import javax.net.ssl.X509TrustManager
* Builder to create an OkHttp network interceptor that will verify a host is trusted using
* certificate transparency
*/
@Suppress("TooManyFunctions")
class CTInterceptorBuilder {
private var trustManager: X509TrustManager? = null
private var logListDataSource: DataSource<LogListResult>? = null
private val hosts = mutableSetOf<Host>()
private val includeHosts = mutableSetOf<Host>()
private val excludeHosts = mutableSetOf<Host>()

/**
* Determine if a failure to pass certificate transparency results in the connection being closed. A value of true ensures the connection is
Expand Down Expand Up @@ -114,18 +116,18 @@ class CTInterceptorBuilder {
* @property pattern lower-case host name or wildcard pattern such as `*.example.com`.
*/
@Suppress("MemberVisibilityCanBePrivate")
fun addHost(pattern: String) = apply {
hosts.add(Host(pattern))
fun includeHost(pattern: String) = apply {
includeHosts.add(Host(pattern))
}

/**
* Verify certificate transparency for host that match [this].
* Verify certificate transparency for hosts that match [this].
*
* @receiver lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun String.unaryPlus() {
addHost(this)
includeHost(this)
}

/**
Expand All @@ -135,11 +137,42 @@ class CTInterceptorBuilder {
*/
@JvmSynthetic
operator fun List<String>.unaryPlus() {
forEach { addHost(it) }
forEach { includeHost(it) }
}

/**
* Ignore certificate transparency for hosts that match [pattern].
*
* @property pattern lower-case host name or wildcard pattern such as `*.example.com`.
*/
@Suppress("MemberVisibilityCanBePrivate")
fun excludeHost(vararg pattern: String) = apply {
pattern.forEach { excludeHosts.add(Host(it)) }
}

/**
* Ignore certificate transparency for hosts that match [this].
*
* @receiver lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun String.unaryMinus() {
excludeHost(this)
}

/**
* Ignore certificate transparency for hosts that match one of [this].
*
* @receiver [List] of lower-case host name or wildcard pattern such as `*.example.com`.
*/
@JvmSynthetic
operator fun List<String>.unaryMinus() {
forEach { excludeHost(it) }
}

/**
* Build the network [Interceptor]
*/
fun build(): Interceptor = CertificateTransparencyInterceptor(hosts.toSet(), trustManager, logListDataSource, failOnError, logger)
fun build(): Interceptor =
CertificateTransparencyInterceptor(includeHosts.toSet(), excludeHosts.toSet(), trustManager, logListDataSource, failOnError, logger)
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

internal open class CertificateTransparencyBase(
private val hosts: Set<Host>,
private val includeHosts: Set<Host>,
private val excludeHosts: Set<Host> = emptySet(),
trustManager: X509TrustManager? = null,
logListDataSource: DataSource<LogListResult>? = null,
private val minimumValidSignedCertificateTimestamps: Int = 2
) {
init {
require(hosts.isNotEmpty()) { "Please provide at least one host to enable certificate transparency verification" }
require(includeHosts.isNotEmpty()) { "Please provide at least one host to enable certificate transparency verification" }
excludeHosts.forEach {
require(!it.startsWithWildcard) { "Certificate transparency exclusions cannot use wildcards" }
require(!includeHosts.contains(it)) { "Certificate transparency exclusions must not match include directly" }
}
}

private val cleaner: CertificateChainCleaner by lazy {
Expand Down Expand Up @@ -115,5 +120,5 @@ internal open class CertificateTransparencyBase(
}
}

private fun enabledForCertificateTransparency(host: String) = hosts.any { it.matches(host) }
private fun enabledForCertificateTransparency(host: String) = includeHosts.any { it.matches(host) } && !excludeHosts.any { it.matches(host) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import javax.net.ssl.X509TrustManager

internal class CertificateTransparencyHostnameVerifier(
private val delegate: HostnameVerifier,
hosts: Set<Host>,
includeHosts: Set<Host>,
excludeHosts: Set<Host>,
trustManager: X509TrustManager?,
logListDataSource: DataSource<LogListResult>?,
private val failOnError: Boolean = true,
private val logger: CTLogger? = null
) : CertificateTransparencyBase(hosts, trustManager, logListDataSource), HostnameVerifier {
) : CertificateTransparencyBase(includeHosts, excludeHosts, trustManager, logListDataSource), HostnameVerifier {

override fun verify(host: String, sslSession: SSLSession): Boolean {
if (!delegate.verify(host, sslSession)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ import javax.net.ssl.SSLSocket
import javax.net.ssl.X509TrustManager

internal class CertificateTransparencyInterceptor(
hosts: Set<Host>,
inlcudeHosts: Set<Host>,
excludeHosts: Set<Host>,
trustManager: X509TrustManager?,
logListDataSource: DataSource<LogListResult>?,
private val failOnError: Boolean = true,
private val logger: CTLogger? = null
) : CertificateTransparencyBase(hosts, trustManager, logListDataSource), Interceptor {
) : CertificateTransparencyBase(inlcudeHosts, excludeHosts, trustManager, logListDataSource), Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val host = chain.request().url().host()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.babylon.certificatetransparency.internal.verifier.model

import okhttp3.HttpUrl
import java.util.Arrays

/**
* @property pattern A hostname like `example.com` or a pattern like `*.example.com`.
Expand All @@ -29,16 +30,18 @@ internal data class Host(
*/
private val canonicalHostname: String

val startsWithWildcard = pattern.startsWith(WILDCARD)

init {
this.canonicalHostname = if (pattern.startsWith(WILDCARD)) {
this.canonicalHostname = if (startsWithWildcard) {
HttpUrl.parse("http://" + pattern.substring(WILDCARD.length))?.host()
} else {
HttpUrl.parse("http://$pattern")?.host()
} ?: throw IllegalArgumentException("$pattern is not a well-formed URL")
}

fun matches(hostname: String): Boolean {
if (pattern.startsWith(WILDCARD)) {
if (startsWithWildcard) {
val firstDot = hostname.indexOf('.')
return hostname.length - firstDot - 1 == canonicalHostname.length && hostname.regionMatches(
firstDot + 1, canonicalHostname, 0,
Expand All @@ -49,6 +52,18 @@ internal data class Host(
return hostname == canonicalHostname
}

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}

return (other is Host && canonicalHostname == other.canonicalHostname && startsWithWildcard == other.startsWithWildcard)
}

override fun hashCode(): Int {
return Arrays.hashCode(arrayOf(canonicalHostname, startsWithWildcard))
}

companion object {

private const val WILDCARD = "*."
Expand Down
Loading

0 comments on commit d9618d2

Please sign in to comment.