
Certificate Transparency verification for apps: verifies Signed Certificate Timestamps (SCTs) during TLS, with interceptor/plugin/trustmanager integrations, configurable Chrome/Apple policies, host filtering, bundled log list, fail-open default.
Certificate Transparency for Kotlin Multiplatform
Seal is a Kotlin Multiplatform library that brings Certificate Transparency (CT) verification to Android, iOS, JVM Desktop, and Web (wasmJs) applications. It verifies Signed Certificate Timestamps (SCTs) to ensure TLS certificates have been publicly logged, protecting users against misissued or rogue certificates.
Certificate Transparency is a public framework of append-only certificate logs that allows anyone to monitor the certificates issued for their domains. When a Certificate Authority (CA) issues a TLS certificate, it submits the certificate to one or more CT logs and receives a Signed Certificate Timestamp (SCT) — a cryptographic proof that the certificate has been logged.
Seal verifies these SCTs during HTTPS connections to ensure that:
Without CT enforcement, a compromised or rogue CA can issue certificates that silently intercept traffic. Seal closes this gap at the application layer.
HttpClient plugin for all platformsCTTrustManager for Android and JVM Desktopgraph TD
composeApp[":composeApp<br>(Demo App)<br>Android · iOS · Desktop · Web"]
ktor[":seal-ktor<br>Ktor Plugin<br>Android · iOS · JVM · wasmJs"]
core[":seal-core<br>Models · ASN.1 · SCT Parser<br>Policy Engine · Platform Integrations<br>Android · iOS · JVM · wasmJs"]
composeApp --> ktor
composeApp --> core
ktor --> core| Module | Type | Targets | Purpose |
|---|---|---|---|
seal-core |
KMP library | Android, iOS, JVM, wasmJs | Data models, ASN.1/SCT parsing, verification engine, CT policies, platform integrations (OkHttp, Conscrypt, SecTrust, URLSession) |
seal-ktor |
KMP library | Android, iOS, JVM, wasmJs | Ktor HttpClient plugin bridging platform implementations |
| Use case | Module(s) |
|---|---|
| Android app with OkHttp | seal-core |
| iOS app with URLSession | seal-core |
| JVM Desktop app with OkHttp | seal-core |
| Any platform with Ktor |
seal-core + seal-ktor
|
| Web (wasmJs) |
seal-core (browser handles CT natively) |
Add the Seal versions to your libs.versions.toml:
[versions]
seal = "<version>"
[libraries]
seal-core = { module = "io.github.jermeyyy:seal-core", version.ref = "seal" }
seal-ktor = { module = "io.github.jermeyyy:seal-ktor", version.ref = "seal" }// build.gradle.kts
// Core — includes all platform integrations
commonMain.dependencies {
implementation(libs.seal.core)
}
// Ktor plugin (optional — multiplatform)
commonMain.dependencies {
implementation(libs.seal.ktor)
}Or with raw coordinates:
implementation("io.github.jermeyyy:seal-core:<version>")
implementation("io.github.jermeyyy:seal-ktor:<version>") // Optional - Ktor pluginimport com.jermey.seal.jvm.okhttp.certificateTransparencyInterceptor
val client = OkHttpClient.Builder()
.addNetworkInterceptor(
certificateTransparencyInterceptor {
// All hosts verified by default — no configuration required.
}
)
.build()Important: The interceptor must be added as a network interceptor (not an application interceptor) so it runs after TLS negotiation and has access to the certificate chain.
import com.jermey.seal.ktor.CertificateTransparency
// Android / JVM Desktop (OkHttp engine)
val client = HttpClient(OkHttp) {
install(CertificateTransparency) {
+"*.example.com"
-"internal.example.com"
failOnError = false
}
}
// iOS (Darwin engine)
val client = HttpClient(Darwin) {
install(CertificateTransparency) {
+"*.example.com"
-"internal.example.com"
failOnError = false
}
}
// Web (Js engine) — browser handles CT natively
val client = HttpClient(Js) {
install(CertificateTransparency)
}The Ktor plugin uses the same configuration DSL on all platforms. The underlying implementation delegates to the appropriate platform engine automatically.
For full guide, see Ktor Integration.
The OkHttp integration provides a CertificateTransparencyInterceptor that verifies SCTs on every HTTPS request.
It supports all three SCT delivery methods: X.509 embedded, TLS extension, and OCSP stapling.
This works on both Android and JVM Desktop.
import com.jermey.seal.jvm.okhttp.certificateTransparencyInterceptor
import com.jermey.seal.jvm.okhttp.installCertificateTransparency
val client = OkHttpClient.Builder()
.installCertificateTransparency {
+"*.example.com"
-"internal.example.com"
policy = ChromeCtPolicy()
failOnError = true
logger = { host, result ->
println("CT: $host: $result")
}
}
.build()Conscrypt is required for TLS extension and OCSP SCT access. Initialize it early:
import com.jermey.seal.jvm.ConscryptInitializer
// Android: in Application.onCreate()
// JVM Desktop: at program start
ConscryptInitializer.initialize()For full guide, see OkHttp Integration.
For apps that need CT verification at the TLS level (instead of the OkHttp interceptor level):
import com.jermey.seal.jvm.trust.CTTrustManagerFactory
val ctTrustManager = CTTrustManagerFactory.create {
+"*.example.com"
failOnError = true
}
// Use with SSLContext, OkHttp, or any TLS library
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(ctTrustManager), null)Both the OkHttp interceptor and the Ktor plugin share the same DSL (CTConfigurationBuilder):
certificateTransparencyInterceptor {
// ── Host Filtering ──────────────────────────────────────────────
+"*.example.com" // include hosts matching pattern
-"internal.example.com" // exclude specific hosts (takes precedence)
// If no includes are specified, all hosts are checked.
// ── Policy ──────────────────────────────────────────────────────
policy = ChromeCtPolicy() // or AppleCtPolicy(), or a custom CTPolicy
// ── Failure Mode ────────────────────────────────────────────────
failOnError = false // false = fail-open (default), true = fail-closed
// ── Logging ─────────────────────────────────────────────────────
logger = { host, result ->
println("CT: $host -> $result")
}
// ── Log List ────────────────────────────────────────────────────
logListUrl = "https://www.gstatic.com/ct/log_list/v3/log_list.json"
logListCache = myDiskCache // optional: provide a custom cache
logListMaxAge = 24.hours // how long to consider a cached list valid
}| Pattern | Matches | Does NOT Match |
|---|---|---|
+"*.example.com" |
api.example.com, www.example.com
|
example.com |
+"**.example.com" |
deep.sub.example.com |
example.com |
| No includes specified | All hosts | — |
-"internal.example.com" |
Excluded (even if another pattern includes it) | — |
Excludes always take precedence over includes.
For full reference, see Configuration.
By default, Seal uses ChromeCtPolicy, which requires SCTs from at least one Google-operated
log and one non-Google log. This mirrors Chrome's behavior but can be too strict for some
certificates.
If you encounter "Too few distinct operators" failures, you can switch to Apple's policy:
// Kotlin DSL
installCertificateTransparency {
useApplePolicy() // or: policy = AppleCtPolicy()
}| Policy | SCT Count (< 180 days) | SCT Count (≥ 180 days) | Operator Diversity |
|---|---|---|---|
ChromeCtPolicy |
≥ 2 | ≥ 3 | 1 Google + 1 non-Google |
AppleCtPolicy |
≥ 2 | ≥ 3 | 2+ distinct (any) |
Seal ships with two built-in CT policies:
| Policy | Description |
|---|---|
ChromeCtPolicy() |
Mirrors Chrome's CT requirements — SCT count depends on certificate lifetime (default) |
AppleCtPolicy() |
Mirrors Apple's CT requirements — stricter operator diversity rules |
You can implement a custom policy by implementing the CTPolicy interface:
val custom = CTPolicy { certificateLifetimeDays, sctResults ->
val validFromDistinct = sctResults
.filterIsInstance<SctVerificationResult.Valid>()
.distinctBy { it.logOperator }
if (validFromDistinct.size >= 2) {
VerificationResult.Success.Trusted(validFromDistinct)
} else {
VerificationResult.Failure.TooFewDistinctOperators(
found = validFromDistinct.size,
required = 2,
)
}
}For more details, see Configuration.
IosCertificateTransparencyVerifier combines manual embedded-SCT verification with OS-level TLS/OCSP CT checking:
import com.jermey.seal.ios.IosCertificateTransparencyVerifier
val configuration = ctConfiguration {
failOnError = true
}
val verifier = IosCertificateTransparencyVerifier(configuration)
val result = verifier.verify(secTrust, host)UrlSessionCtHelper wraps CT verification for use in a URLSessionDelegate:
import com.jermey.seal.ios.urlsession.UrlSessionCtHelper
val helper = UrlSessionCtHelper(configuration, verifier)
// In your URLSessionDelegate:
override fun URLSession(
session: NSURLSession,
didReceiveChallenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit,
) {
val (disposition, credential) = helper.handleServerTrustChallenge(didReceiveChallenge)
completionHandler(disposition, credential)
}For Ktor-based iOS apps, use the CertificateTransparency plugin with the Darwin engine — no additional iOS-specific setup required.
For full guide, see iOS Integration.
Seal reports results through a sealed class hierarchy:
sealed class VerificationResult {
sealed class Success : VerificationResult() {
data class Trusted(val validScts: List<SctVerificationResult.Valid>)
data object InsecureConnection // HTTP — no TLS
data object DisabledForHost // host excluded by matcher
data object DisabledStaleLogList // stale log list, policy allows skip
}
sealed class Failure : VerificationResult() {
data object NoScts // no SCTs found
data class TooFewSctsTrusted(val found: Int, val required: Int)
data class TooFewDistinctOperators(val found: Int, val required: Int)
data class LogServersFailed(val sctResults: List<SctVerificationResult>)
data class UnknownError(val cause: Throwable)
}
}| Min SDK | 24 (Android 7.0) |
| SCT Methods | Embedded, TLS extension, OCSP stapling (all three) |
| Conscrypt | Required for TLS extension and OCSP SCT access |
| Min Target | iOS 15+ |
| Embedded SCTs | Verified manually by Seal against its bundled log list |
| TLS / OCSP SCTs | Delegated to OS via SecTrustCopyResult CT compliance flag |
| JVM Target | 11+ |
| SCT Methods | Embedded, TLS extension, OCSP stapling (all three) |
| Conscrypt | Required (bundled via conscrypt-openjdk-uber) |
| Disk Cache |
JvmDiskLogListCache — defaults to ~/.seal/cache
|
| Runtime | Modern browsers with WebAssembly support |
| CT Verification | Handled natively by the browser — Seal provides audit-mode reporting |
| Note | Browsers enforce their own CT policies; Seal's verification is informational only |
Full KDoc API documentation is available at: jermeyyy.github.io/seal/api/
The repository includes a Compose Multiplatform demo app targeting Android, iOS, JVM Desktop, and Web.
# Android
./gradlew :composeApp:assembleDebug
# JVM Desktop
./gradlew :composeApp:run
# Web (wasmJs)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# iOS — open in Xcode
open iosApp/iosApp.xcodeprojThe demo app makes HTTPS requests and displays CT verification results in real time.
OkHttp network interceptors run after the TLS handshake, which means the certificate chain is available for inspection. Application interceptors run before the connection is established and cannot access TLS state.
By default (failOnError = false), failures are reported through the logger callback but the connection proceeds normally (fail-open).
If you set failOnError = true, a SSLPeerUnverifiedException is thrown and the connection is aborted.
On Android and JVM Desktop, Conscrypt is required to access TLS extension and OCSP-stapled SCTs. Without it, only embedded X.509 SCTs are verified.
Call ConscryptInitializer.initialize() as early as possible.
On iOS and Web, Conscrypt is not used.
Seal ships a bundled log list compiled at build time. If the network fetch fails or the cached list is stale, the bundled list is used as a fallback.
When failOnError = false (default), a stale log list will not block connections.
No. Seal performs Certificate Transparency verification, not certificate pinning. CT checks that certificates were publicly logged; pinning restricts which certificates are accepted. They are complementary techniques.
No. In browsers, Certificate Transparency is enforced natively by the browser engine. The wasmJs target provides the same API surface for code sharing but does not perform additional CT verification — the browser already handles it.
# Build all modules
./gradlew assemble
# Run all tests
./gradlew allTests
# Build the demo app (Android)
./gradlew :composeApp:assembleDebug
# Run the demo app (Desktop)
./gradlew :composeApp:run
# Run the demo app (Web)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# Generate API documentation
./gradlew dokkaGenerateModuleHtmlFor the iOS demo app, open iosApp/iosApp.xcodeproj in Xcode and run from there.
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
git checkout -b feature/my-feature)git commit -am 'Add my feature')git push origin feature/my-feature)Please ensure all tests pass (./gradlew allTests) and follow the existing code style.
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation · API Reference · Certificate Transparency · Kotlin Multiplatform
Certificate Transparency for Kotlin Multiplatform
Seal is a Kotlin Multiplatform library that brings Certificate Transparency (CT) verification to Android, iOS, JVM Desktop, and Web (wasmJs) applications. It verifies Signed Certificate Timestamps (SCTs) to ensure TLS certificates have been publicly logged, protecting users against misissued or rogue certificates.
Certificate Transparency is a public framework of append-only certificate logs that allows anyone to monitor the certificates issued for their domains. When a Certificate Authority (CA) issues a TLS certificate, it submits the certificate to one or more CT logs and receives a Signed Certificate Timestamp (SCT) — a cryptographic proof that the certificate has been logged.
Seal verifies these SCTs during HTTPS connections to ensure that:
Without CT enforcement, a compromised or rogue CA can issue certificates that silently intercept traffic. Seal closes this gap at the application layer.
HttpClient plugin for all platformsCTTrustManager for Android and JVM Desktopgraph TD
composeApp[":composeApp<br>(Demo App)<br>Android · iOS · Desktop · Web"]
ktor[":seal-ktor<br>Ktor Plugin<br>Android · iOS · JVM · wasmJs"]
core[":seal-core<br>Models · ASN.1 · SCT Parser<br>Policy Engine · Platform Integrations<br>Android · iOS · JVM · wasmJs"]
composeApp --> ktor
composeApp --> core
ktor --> core| Module | Type | Targets | Purpose |
|---|---|---|---|
seal-core |
KMP library | Android, iOS, JVM, wasmJs | Data models, ASN.1/SCT parsing, verification engine, CT policies, platform integrations (OkHttp, Conscrypt, SecTrust, URLSession) |
seal-ktor |
KMP library | Android, iOS, JVM, wasmJs | Ktor HttpClient plugin bridging platform implementations |
| Use case | Module(s) |
|---|---|
| Android app with OkHttp | seal-core |
| iOS app with URLSession | seal-core |
| JVM Desktop app with OkHttp | seal-core |
| Any platform with Ktor |
seal-core + seal-ktor
|
| Web (wasmJs) |
seal-core (browser handles CT natively) |
Add the Seal versions to your libs.versions.toml:
[versions]
seal = "<version>"
[libraries]
seal-core = { module = "io.github.jermeyyy:seal-core", version.ref = "seal" }
seal-ktor = { module = "io.github.jermeyyy:seal-ktor", version.ref = "seal" }// build.gradle.kts
// Core — includes all platform integrations
commonMain.dependencies {
implementation(libs.seal.core)
}
// Ktor plugin (optional — multiplatform)
commonMain.dependencies {
implementation(libs.seal.ktor)
}Or with raw coordinates:
implementation("io.github.jermeyyy:seal-core:<version>")
implementation("io.github.jermeyyy:seal-ktor:<version>") // Optional - Ktor pluginimport com.jermey.seal.jvm.okhttp.certificateTransparencyInterceptor
val client = OkHttpClient.Builder()
.addNetworkInterceptor(
certificateTransparencyInterceptor {
// All hosts verified by default — no configuration required.
}
)
.build()Important: The interceptor must be added as a network interceptor (not an application interceptor) so it runs after TLS negotiation and has access to the certificate chain.
import com.jermey.seal.ktor.CertificateTransparency
// Android / JVM Desktop (OkHttp engine)
val client = HttpClient(OkHttp) {
install(CertificateTransparency) {
+"*.example.com"
-"internal.example.com"
failOnError = false
}
}
// iOS (Darwin engine)
val client = HttpClient(Darwin) {
install(CertificateTransparency) {
+"*.example.com"
-"internal.example.com"
failOnError = false
}
}
// Web (Js engine) — browser handles CT natively
val client = HttpClient(Js) {
install(CertificateTransparency)
}The Ktor plugin uses the same configuration DSL on all platforms. The underlying implementation delegates to the appropriate platform engine automatically.
For full guide, see Ktor Integration.
The OkHttp integration provides a CertificateTransparencyInterceptor that verifies SCTs on every HTTPS request.
It supports all three SCT delivery methods: X.509 embedded, TLS extension, and OCSP stapling.
This works on both Android and JVM Desktop.
import com.jermey.seal.jvm.okhttp.certificateTransparencyInterceptor
import com.jermey.seal.jvm.okhttp.installCertificateTransparency
val client = OkHttpClient.Builder()
.installCertificateTransparency {
+"*.example.com"
-"internal.example.com"
policy = ChromeCtPolicy()
failOnError = true
logger = { host, result ->
println("CT: $host: $result")
}
}
.build()Conscrypt is required for TLS extension and OCSP SCT access. Initialize it early:
import com.jermey.seal.jvm.ConscryptInitializer
// Android: in Application.onCreate()
// JVM Desktop: at program start
ConscryptInitializer.initialize()For full guide, see OkHttp Integration.
For apps that need CT verification at the TLS level (instead of the OkHttp interceptor level):
import com.jermey.seal.jvm.trust.CTTrustManagerFactory
val ctTrustManager = CTTrustManagerFactory.create {
+"*.example.com"
failOnError = true
}
// Use with SSLContext, OkHttp, or any TLS library
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(ctTrustManager), null)Both the OkHttp interceptor and the Ktor plugin share the same DSL (CTConfigurationBuilder):
certificateTransparencyInterceptor {
// ── Host Filtering ──────────────────────────────────────────────
+"*.example.com" // include hosts matching pattern
-"internal.example.com" // exclude specific hosts (takes precedence)
// If no includes are specified, all hosts are checked.
// ── Policy ──────────────────────────────────────────────────────
policy = ChromeCtPolicy() // or AppleCtPolicy(), or a custom CTPolicy
// ── Failure Mode ────────────────────────────────────────────────
failOnError = false // false = fail-open (default), true = fail-closed
// ── Logging ─────────────────────────────────────────────────────
logger = { host, result ->
println("CT: $host -> $result")
}
// ── Log List ────────────────────────────────────────────────────
logListUrl = "https://www.gstatic.com/ct/log_list/v3/log_list.json"
logListCache = myDiskCache // optional: provide a custom cache
logListMaxAge = 24.hours // how long to consider a cached list valid
}| Pattern | Matches | Does NOT Match |
|---|---|---|
+"*.example.com" |
api.example.com, www.example.com
|
example.com |
+"**.example.com" |
deep.sub.example.com |
example.com |
| No includes specified | All hosts | — |
-"internal.example.com" |
Excluded (even if another pattern includes it) | — |
Excludes always take precedence over includes.
For full reference, see Configuration.
By default, Seal uses ChromeCtPolicy, which requires SCTs from at least one Google-operated
log and one non-Google log. This mirrors Chrome's behavior but can be too strict for some
certificates.
If you encounter "Too few distinct operators" failures, you can switch to Apple's policy:
// Kotlin DSL
installCertificateTransparency {
useApplePolicy() // or: policy = AppleCtPolicy()
}| Policy | SCT Count (< 180 days) | SCT Count (≥ 180 days) | Operator Diversity |
|---|---|---|---|
ChromeCtPolicy |
≥ 2 | ≥ 3 | 1 Google + 1 non-Google |
AppleCtPolicy |
≥ 2 | ≥ 3 | 2+ distinct (any) |
Seal ships with two built-in CT policies:
| Policy | Description |
|---|---|
ChromeCtPolicy() |
Mirrors Chrome's CT requirements — SCT count depends on certificate lifetime (default) |
AppleCtPolicy() |
Mirrors Apple's CT requirements — stricter operator diversity rules |
You can implement a custom policy by implementing the CTPolicy interface:
val custom = CTPolicy { certificateLifetimeDays, sctResults ->
val validFromDistinct = sctResults
.filterIsInstance<SctVerificationResult.Valid>()
.distinctBy { it.logOperator }
if (validFromDistinct.size >= 2) {
VerificationResult.Success.Trusted(validFromDistinct)
} else {
VerificationResult.Failure.TooFewDistinctOperators(
found = validFromDistinct.size,
required = 2,
)
}
}For more details, see Configuration.
IosCertificateTransparencyVerifier combines manual embedded-SCT verification with OS-level TLS/OCSP CT checking:
import com.jermey.seal.ios.IosCertificateTransparencyVerifier
val configuration = ctConfiguration {
failOnError = true
}
val verifier = IosCertificateTransparencyVerifier(configuration)
val result = verifier.verify(secTrust, host)UrlSessionCtHelper wraps CT verification for use in a URLSessionDelegate:
import com.jermey.seal.ios.urlsession.UrlSessionCtHelper
val helper = UrlSessionCtHelper(configuration, verifier)
// In your URLSessionDelegate:
override fun URLSession(
session: NSURLSession,
didReceiveChallenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit,
) {
val (disposition, credential) = helper.handleServerTrustChallenge(didReceiveChallenge)
completionHandler(disposition, credential)
}For Ktor-based iOS apps, use the CertificateTransparency plugin with the Darwin engine — no additional iOS-specific setup required.
For full guide, see iOS Integration.
Seal reports results through a sealed class hierarchy:
sealed class VerificationResult {
sealed class Success : VerificationResult() {
data class Trusted(val validScts: List<SctVerificationResult.Valid>)
data object InsecureConnection // HTTP — no TLS
data object DisabledForHost // host excluded by matcher
data object DisabledStaleLogList // stale log list, policy allows skip
}
sealed class Failure : VerificationResult() {
data object NoScts // no SCTs found
data class TooFewSctsTrusted(val found: Int, val required: Int)
data class TooFewDistinctOperators(val found: Int, val required: Int)
data class LogServersFailed(val sctResults: List<SctVerificationResult>)
data class UnknownError(val cause: Throwable)
}
}| Min SDK | 24 (Android 7.0) |
| SCT Methods | Embedded, TLS extension, OCSP stapling (all three) |
| Conscrypt | Required for TLS extension and OCSP SCT access |
| Min Target | iOS 15+ |
| Embedded SCTs | Verified manually by Seal against its bundled log list |
| TLS / OCSP SCTs | Delegated to OS via SecTrustCopyResult CT compliance flag |
| JVM Target | 11+ |
| SCT Methods | Embedded, TLS extension, OCSP stapling (all three) |
| Conscrypt | Required (bundled via conscrypt-openjdk-uber) |
| Disk Cache |
JvmDiskLogListCache — defaults to ~/.seal/cache
|
| Runtime | Modern browsers with WebAssembly support |
| CT Verification | Handled natively by the browser — Seal provides audit-mode reporting |
| Note | Browsers enforce their own CT policies; Seal's verification is informational only |
Full KDoc API documentation is available at: jermeyyy.github.io/seal/api/
The repository includes a Compose Multiplatform demo app targeting Android, iOS, JVM Desktop, and Web.
# Android
./gradlew :composeApp:assembleDebug
# JVM Desktop
./gradlew :composeApp:run
# Web (wasmJs)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# iOS — open in Xcode
open iosApp/iosApp.xcodeprojThe demo app makes HTTPS requests and displays CT verification results in real time.
OkHttp network interceptors run after the TLS handshake, which means the certificate chain is available for inspection. Application interceptors run before the connection is established and cannot access TLS state.
By default (failOnError = false), failures are reported through the logger callback but the connection proceeds normally (fail-open).
If you set failOnError = true, a SSLPeerUnverifiedException is thrown and the connection is aborted.
On Android and JVM Desktop, Conscrypt is required to access TLS extension and OCSP-stapled SCTs. Without it, only embedded X.509 SCTs are verified.
Call ConscryptInitializer.initialize() as early as possible.
On iOS and Web, Conscrypt is not used.
Seal ships a bundled log list compiled at build time. If the network fetch fails or the cached list is stale, the bundled list is used as a fallback.
When failOnError = false (default), a stale log list will not block connections.
No. Seal performs Certificate Transparency verification, not certificate pinning. CT checks that certificates were publicly logged; pinning restricts which certificates are accepted. They are complementary techniques.
No. In browsers, Certificate Transparency is enforced natively by the browser engine. The wasmJs target provides the same API surface for code sharing but does not perform additional CT verification — the browser already handles it.
# Build all modules
./gradlew assemble
# Run all tests
./gradlew allTests
# Build the demo app (Android)
./gradlew :composeApp:assembleDebug
# Run the demo app (Desktop)
./gradlew :composeApp:run
# Run the demo app (Web)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# Generate API documentation
./gradlew dokkaGenerateModuleHtmlFor the iOS demo app, open iosApp/iosApp.xcodeproj in Xcode and run from there.
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
git checkout -b feature/my-feature)git commit -am 'Add my feature')git push origin feature/my-feature)Please ensure all tests pass (./gradlew allTests) and follow the existing code style.
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation · API Reference · Certificate Transparency · Kotlin Multiplatform