8
0

add certificate pinning support for Android

This commit is contained in:
andre 2026-02-17 10:08:44 +01:00
parent 8baac45187
commit b0cb2b8933
2 changed files with 60 additions and 6 deletions

View File

@ -4,9 +4,6 @@
> >
> This repository contains a proof-of-concept React Native plugin and an example mobile application intended solely for technical evaluation. > This repository contains a proof-of-concept React Native plugin and an example mobile application intended solely for technical evaluation.
> >
> Only the iOS integration has been verified to work at this time.
> The Android bridge and Android example setup exist but have not been tested and are expected to require additional adjustments before they can be considered functional.
>
> This repository is **experimental**, **not formally approved**, **not officially released**, **not published as an npm package**, and **not endorsed by WebID for > This repository is **experimental**, **not formally approved**, **not officially released**, **not published as an npm package**, and **not endorsed by WebID for
> production use**. > production use**.
> >
@ -38,12 +35,14 @@ cp .env.example .env
URL=https://test.webid-solutions.de URL=https://test.webid-solutions.de
USERNAME=your_username USERNAME=your_username
API_KEY=your_api_key API_KEY=your_api_key
CERT_BASE64=On ANdroid it need to be SHA-PINS CERT_BASE64=
# SHA_PINS=sha256/AAAA...,sha256/BBBB...
``` ```
Edit the .env file and add your username and API key. Edit the .env file and add your username and API key.
The provided CERT_BASE64 value is preconfigured for the test system and is valid until 2026-02-21.
In most cases, this value does not need to be modified if you intend to test against the test environment.
### Install Dependencies ### Install Dependencies
From the repository root: From the repository root:

View File

@ -24,6 +24,11 @@ import java.net.URI
import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.bridge.UiThreadUtil
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import java.io.ByteArrayInputStream
import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import android.util.Base64
private const val TAG = "WebIdMetaPlugin" private const val TAG = "WebIdMetaPlugin"
@ -53,6 +58,53 @@ class WebIdMetaPluginModule(
listenerCount = (listenerCount - count).coerceAtLeast(0) listenerCount = (listenerCount - count).coerceAtLeast(0)
} }
private fun toPinningShaKey(inputRaw: String): String {
val input = inputRaw.trim()
if (input.startsWith("sha256/") || input.startsWith("sha1/")) return input
val justValue = input.substringAfter("=", input).trim()
val base64Cert = justValue
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replace("\\s+".toRegex(), "")
require(base64Cert.isNotEmpty()) { "Certificate input is empty after normalization" }
val certBytes = try {
Base64.decode(base64Cert, Base64.DEFAULT)
} catch (e: IllegalArgumentException) {
throw IllegalArgumentException("Certificate is not valid Base64 (DER/PEM).", e)
}
val x509 = try {
val cf = CertificateFactory.getInstance("X.509")
cf.generateCertificate(ByteArrayInputStream(certBytes)) as X509Certificate
} catch (e: Exception) {
throw IllegalArgumentException("Could not parse X.509 certificate bytes.", e)
}
val spkiBytes = x509.publicKey.encoded
val digest = MessageDigest.getInstance("SHA-256").digest(spkiBytes)
val pinB64 = Base64.encodeToString(digest, Base64.NO_WRAP)
return "sha256/$pinB64"
}
private fun toPinningShaKeys(inputs: List<String>): Array<String> =
inputs
.map { it.trim() }
.filter { it.isNotEmpty() }
.map { toPinningShaKey(it) }
.toTypedArray()
private fun shortPreview(s: String): String {
val t = s.trim()
if (t.length <= 12) return t
return "${t.take(6)}...${t.takeLast(6)}"
}
@ReactMethod @ReactMethod
fun createMetaPlugin( fun createMetaPlugin(
uri: String, uri: String,
@ -69,10 +121,13 @@ class WebIdMetaPluginModule(
val shaPinsList = shaPins.toArrayList().map { it.toString() } val shaPinsList = shaPins.toArrayList().map { it.toString() }
val pluginsList = plugins.toArrayList().map { it.toString() } val pluginsList = plugins.toArrayList().map { it.toString() }
val pinningShaKeys = toPinningShaKeys(shaPinsList)
Log.i(TAG, "createMetaPlugin pins(mapped) len=${pinningShaKeys.size} values=${pinningShaKeys.map { shortPreview(it) }}")
try { try {
val environment = WebIdSdkEnvironment( val environment = WebIdSdkEnvironment(
URI.create(cleanUri), URI.create(cleanUri),
*shaPinsList.toTypedArray() *pinningShaKeys
) )
val selectedPlugins = ArrayList<IProductPluginWebId>() val selectedPlugins = ArrayList<IProductPluginWebId>()