Trust Pin Kotlin SDK
TrustPin Kotlin SDK Documentation
TrustPin is a modern, lightweight, and secure Kotlin library that enforces SSL Certificate Pinning for Android applications. The Maven Central artifact is an Android AAR with bundled R8/ProGuard consumer rules.
JVM/server/desktop support exists as a separately distributed hardened JAR. This documentation focuses on Android β JVM customers should request access via support@trustpin.cloud.
π Key Features
β Android AAR on Maven Central β Ships with bundled R8/ProGuard consumer rules
β
Context-aware Android setup β Bundle credentials asassets/trustpin.jsonand load them with a single callβ Anti-rollback storage on Android β Pinning configuration is sealed against downgrade attacks
β Integrity check on Android β Refuses to operate on non-production / tampered runtime environments
β Flexible Pinning Modes β Strict validation or permissive mode for development
β Multiple Hash Algorithms β SHA-256 and SHA-512 certificate validation
β Signed Configuration β Cryptographically signed pinning configurations
β Built-in TrustManager & SSLSocketFactory β Drop into OkHttp, Retrofit, Ktor,
HttpsURLConnectionβ Multi-instance support β Isolated pinning contexts for multi-tenant apps and libraries
β Intelligent Caching β 10-minute configuration cache with stale fallback
β Comprehensive Logging β Configurable log levels for debugging and monitoring
β Thread-Safe β Built with coroutines and concurrent-safe operations
π Platform Requirements
| Platform | Minimum Version | Notes |
|---|---|---|
| Android | API 25+ | Full feature support |
| Kotlin | 2.3.0+ | Built with Kotlin 2.3.0 |
A hardened JVM JAR is available separately by request for server/desktop use β see support@trustpin.cloud. This documentation covers the Android distribution only.
π¦ Installation
Android Gradle (Kotlin DSL)
dependencies {
implementation("cloud.trustpin:kotlin-sdk:6.2.0")
}Android Gradle (Groovy)
dependencies {
implementation 'cloud.trustpin:kotlin-sdk:6.2.0'
}Android Maven
<dependency>
<groupId>cloud.trustpin</groupId>
<artifactId>kotlin-sdk</artifactId>
<version>6.2.0</version>
</dependency>π§ Quick Setup
TrustPin.setup accepts a TrustPinConfiguration. On Android there are two recommended ways to build one:
| Path | Recommended path | What you write |
|---|---|---|
| A β credentials bundled with the app | TrustPinConfiguration.fromAssets(context) | A trustpin.json in src/main/assets/ |
| B β credentials supplied at runtime | TrustPinConfiguration(...).withAndroidStorage(context) | Kotlin code |
β οΈ On Android, always use one of the two
Context-aware paths. Constructing a bareTrustPinConfigurationstill works, but the instance runs with a weakened security profile. Treat it as a misconfiguration, not a feature.
Path A β Bundled JSON (Android, recommended)
This is the primary Android entry point. Drop a trustpin.json into the app module's assets directory and load it with one call.
app/src/main/assets/trustpin.json (schema is snake_case):
{
"organization_id": "my-org",
"project_id": "my-project",
"public_key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...",
"mode": "strict"
}organization_id, project_id, public_key are required. mode is "strict" (default) or "permissive". An optional configuration_url overrides the hosted configuration source β when present it must use HTTPS. Unknown top-level keys are ignored for forward compatibility.
Per-flavor / per-build-type overrides ride on standard Android source-set asset merging β no Gradle plugin required:
app/src/main/assets/trustpin.json β default
app/src/debug/assets/trustpin.json β debug build type
app/src/staging/assets/trustpin.json β "staging" product flavorKotlin:
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinConfiguration
import cloud.trustpin.kotlin.sdk.fromAssets
class App : Application() {
override fun onCreate() {
super.onCreate()
applicationScope.launch {
try {
TrustPin.setup(TrustPinConfiguration.fromAssets(this@App))
} catch (e: TrustPinError) {
// Hard stop. Do NOT fall through to an unpinned HTTP client.
showRetryUi(e)
}
}
}
}Path B β Programmatic config (Android)
Use this when credentials come from runtime config (MDM, remote feature flag, A/B routing, β¦) rather than a bundled file. Always chain .withAndroidStorage(context).
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinConfiguration
import cloud.trustpin.kotlin.sdk.TrustPinMode
import cloud.trustpin.kotlin.sdk.withAndroidStorage
suspend fun initializeTrustPin(context: Context, creds: Credentials) {
val config = TrustPinConfiguration(
organizationId = creds.orgId,
projectId = creds.projectId,
publicKey = creds.publicKey,
mode = TrustPinMode.STRICT,
).withAndroidStorage(context)
TrustPin.setup(config)
}β οΈ Single-use contract for Context-decorated configurations
A configuration produced through fromAssets(context) or .withAndroidStorage(context) must be passed to exactly one TrustPin.setup call. Reusing the same decorated instance for a second setup silently downgrades the second instance. Build a fresh configuration for each TrustPin instance:
// β
Correct β one decorated configuration per setup.
TrustPin.instance("payments").setup(TrustPinConfiguration.fromAssets(context))
TrustPin.instance("analytics").setup(
TrustPinConfiguration(orgId, projId, publicKey).withAndroidStorage(context)
)
// β Wrong β second setup is silently downgraded.
val shared = TrustPinConfiguration(orgId, projId, publicKey).withAndroidStorage(context)
TrustPin.default.setup(shared)
TrustPin.instance("payments").setup(shared) // degraded!Fail-closed integration
setup performs local validation only and starts a background fetch of the pinning configuration β it never blocks app launch on the network. If your app must not start networking without a validated payload, gate on TrustPin.awaitConfiguration(timeout) and treat any TrustPinError as a hard stop β do not continue on failure with an unpinned client:
try {
TrustPin.setup(TrustPinConfiguration.fromAssets(context)) // local validation only
TrustPin.awaitConfiguration(timeout = 10_000) // fail-closed gate
} catch (e: TrustPinError) {
return showRetryUi(e) // do NOT fall through to an unpinned client
}
val client = OkHttpClient.Builder()
.sslSocketFactory(TrustPin.makeSSLSocketFactory(), TrustPin.makeTrustManager())
.build()π Usage Examples
OkHttp
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinConfiguration
import cloud.trustpin.kotlin.sdk.fromAssets
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class NetworkManager(context: Context) {
suspend fun initialize() {
TrustPin.setup(TrustPinConfiguration.fromAssets(context))
TrustPin.awaitConfiguration()
}
val httpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(TrustPin.makeSSLSocketFactory(), TrustPin.makeTrustManager())
.build()
}
}Retrofit
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class ApiClient(context: Context) {
suspend fun initialize() {
TrustPin.setup(TrustPinConfiguration.fromAssets(context))
TrustPin.awaitConfiguration()
}
private val okHttpClient by lazy {
OkHttpClient.Builder()
.sslSocketFactory(TrustPin.makeSSLSocketFactory(), TrustPin.makeTrustManager())
.build()
}
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}Ktor
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
class KtorNetworkClient(context: Context) {
suspend fun initialize() {
TrustPin.setup(TrustPinConfiguration.fromAssets(context))
TrustPin.awaitConfiguration()
}
val httpClient by lazy {
HttpClient(OkHttp) {
engine {
preconfigured = OkHttpClient.Builder()
.sslSocketFactory(TrustPin.makeSSLSocketFactory(), TrustPin.makeTrustManager())
.build()
}
}
}
}Manual certificate verification
For custom networking stacks or one-off certificate inspection:
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinError
import java.security.cert.X509Certificate
suspend fun verifyCertificate(domain: String, certificate: X509Certificate) {
try {
TrustPin.verify(domain, certificate)
} catch (e: TrustPinError.DomainNotRegistered) {
// strict mode only
} catch (e: TrustPinError.PinsMismatch) {
// possible MITM
} catch (e: TrustPinError) {
// other verification failure
}
}Multi-instance usage
Libraries or multi-tenant apps can create isolated pinning contexts:
val payments = TrustPin.instance("payments")
payments.setup(
TrustPinConfiguration(orgId = "payments-org", projectId = "payments-api", publicKey = "...")
.withAndroidStorage(context)
)
val analytics = TrustPin.instance("analytics")
analytics.setup(
TrustPinConfiguration(orgId = "analytics-org", projectId = "analytics-api", publicKey = "...")
.withAndroidStorage(context)
)Each instance has its own state and log output tagged with the instance id. fromAssets(context) always applies to TrustPin.default; named instances use the programmatic path.
π― Pinning Modes
| Mode | Behavior | Use case |
|---|---|---|
TrustPinMode.STRICT | Throws TrustPinError.DomainNotRegistered for unregistered domains | Production β all connections must be validated |
TrustPinMode.PERMISSIVE | Allows unregistered domains to bypass pinning | Development / testing, or apps with dynamic endpoints |
Set it via the configuration:
TrustPinConfiguration(orgId, projId, publicKey, mode = TrustPinMode.STRICT)Or in trustpin.json:
{ "organization_id": "...", "project_id": "...", "public_key": "...", "mode": "strict" }π Error Handling
All errors are subtypes of the sealed class TrustPinError. Catch the supertype for blanket handling, or specific cases when you can recover differently:
import cloud.trustpin.kotlin.sdk.TrustPinError
try {
TrustPin.verify("api.example.com", cert)
} catch (e: TrustPinError.DomainNotRegistered) {
// strict mode only
} catch (e: TrustPinError.PinsMismatch) {
// possible MITM β never proceed
} catch (e: TrustPinError.AllPinsExpired) {
// all pins for the domain expired β refresh or fail
} catch (e: TrustPinError.InvalidServerCert) {
// certificate is not a usable X.509
} catch (e: TrustPinError) {
// setup / network / integrity / timeout β see below
}Error reference
| Error | Raised by | Meaning |
|---|---|---|
InvalidProjectConfig | setup | Credentials missing or malformed; on Android, trustpin.json missing/unreadable |
AlreadyInitialized | setup | Instance already completed setup β reconfiguration is not supported; use a named instance |
ErrorFetchingPinningInfo | awaitConfiguration / verify | Configuration could not be fetched from the CDN |
ConfigurationValidationFailed | awaitConfiguration / verify | Configuration signature did not verify |
ConfigIntegrityError | awaitConfiguration / verify | Configuration failed the SDK's integrity check |
SetupInProgress | setup / verify / awaitConfiguration | Another setup call is in flight |
LockTimeout | setup / verify | Internal lock could not be acquired |
NotInitialized | verify / awaitConfiguration | setup has not completed successfully |
PinsMismatch | verify | Certificate does not match any configured pin |
AllPinsExpired | verify | All pins for the domain have expired |
DomainNotRegistered | verify | Domain not configured (strict mode only) |
InvalidServerCert | verify / fetchCertificate | Certificate is not a usable X.509 / TLS handshake failed |
Timeout | verify / fetchCertificate | Operation deadline elapsed |
SSLContextSetupFailed | makeSSLSocketFactory / makeTrustManager | Platform TLS stack could not be configured |
UnsupportedDevice | makeSSLSocketFactory / makeTrustManager | Runtime is not a supported production Android device |
π Logging and Debugging
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinLogLevel
// Set log level before setup for complete coverage.
TrustPin.setLogLevel(TrustPinLogLevel.DEBUG)| Level | Output |
|---|---|
NONE | No logging |
ERROR | Errors only |
INFO | Errors and informational messages |
DEBUG | All messages, including debug detail |
Production guidance: use ERROR or NONE. DEBUG is for development and incident response only.
Example debug output:
[14:30:15] [DEBUG] Starting certificate verification for domain: api.example.com
[14:30:15] [INFO] Using cached configuration
[14:30:15] [DEBUG] Found domain configuration with 2 pins
[14:30:15] [DEBUG] Certificate hash matches sha256 pin for domain api.example.com
[14:30:15] [INFO] Valid pin found for api.example.comπ Best Practices
Setup and initialization
Call
TrustPin.setuponce during app startup (typically inApplication.onCreate).On Android, always use
fromAssets(context)or.withAndroidStorage(context).Treat any
TrustPinErrorfromsetupas a hard stop β never fall through to an unpinned HTTP client.Gate on
TrustPin.awaitConfiguration(timeout)before building any pinned HTTP client your app cannot run without.Set the log level before
setupfor full logging coverage.
Security
Use
TrustPinMode.STRICTin production.Rotate pins before expiration; the SDK falls back to the most-recent valid cached configuration but cannot extend expired pins.
Keep
trustpin.jsonin version control alongside your app source.Monitor pin validation failures via your logging pipeline.
Performance
Configuration caching is automatic (10 min, with stale fallback).
Reuse the OkHttp client β do not build one per request.
Use
ERRORorNONElog level in production.
π§ͺ Testing
import kotlinx.coroutines.test.runTest
import org.junit.Test
class NetworkTest {
@Test
fun `verifies test endpoint`() = runTest {
TrustPin.setup(
TrustPinConfiguration(
organizationId = "test-org",
projectId = "test-project",
publicKey = "test-key",
mode = TrustPinMode.PERMISSIVE, // tests only
)
)
val client = SecureNetworkClient()
val result = client.fetchData()
assert(result.isNotEmpty())
}
}For instrumented Android tests use fromAssets(context) against a dedicated src/androidTest/assets/trustpin.json.
π Troubleshooting
setup throws InvalidProjectConfig
Verify
organization_id,project_id, andpublic_key.Check for whitespace or newlines around the values.
Ensure
public_keyis properly base64-encoded.On Android with
fromAssets: confirmtrustpin.jsonis insrc/main/assets/of the application module (not a library module), and that it isn't being excluded by asset filters.
setup throws ConfigIntegrityError
The downloaded configuration failed the SDK's integrity check. Re-issue credentials in the TrustPin dashboard and check for a misconfigured configuration_url.
makeSSLSocketFactory throws UnsupportedDevice
Release builds of the SDK run only on supported production Android devices. For development and QA:
Use a debug-built variant of your app β debug builds run everywhere, including emulators.
Or use a stock production device for release-shaped testing.
Certificate verification fails
Confirm the domain is registered in the TrustPin Dashboard.
Check the certificate is a valid X.509 leaf.
Verify pins have not expired.
Re-test with
TrustPinMode.PERMISSIVEto isolate whether the domain or the pin is at fault.
OkHttp integration issues
Initialize TrustPin before building the
OkHttpClient.Always pass both
SSLSocketFactoryandX509TrustManagertosslSocketFactory(...).Verify the OkHttp client is reused across requests, not rebuilt.
Debug checklist
TrustPin.setLogLevel(TrustPinLogLevel.DEBUG)beforesetup.Test with
TrustPinMode.PERMISSIVEto isolate domain registration from pin validation.Verify credentials in the TrustPin Dashboard.
Check connectivity to
cdn.trustpin.cloud.
π Documentation
API Reference: index.html
Documentation site: docs.trustpin.cloud
TrustPin Dashboard: trustpin.cloud/dashboard
π License
Commercial License: For enterprise licensing or custom agreements, contact contact@trustpin.cloud.
Attribution Required: When using this software, you must display "Uses TrustPinβ’ technology β https://trustpin.cloud" in your application.
π€ Support & Feedback
π§ Email: support@trustpin.cloud
π Website: https://trustpin.cloud
π Issues: For SDK-related issues, please contact support