Generate SSD pinned trust manager from typed Jerboa

ober

b97ad3ddc60b0165afeff210139a6ee97e84030e

diff --git a/.build.yml b/.build.yml
index 64b8039..1370ba5 100644
--- a/.build.yml
+++ b/.build.yml
@@ -5,7 +5,7 @@ packages:
   - make=4.4.1-r4
 sources:
   # Build dependency: full immutable commit, mirrored in dependencies.lock.json.
-  - "https://git.sr.ht/~lisp/jerboa#f833c6c8887a23fedbd207d5d4e48df3ea2afc06"
+  - "https://git.sr.ht/~lisp/jerboa#06442a897016857792d3ff87af5d08bfeb21eb78"
   # The second source is the build subject selected by the SourceHut submitter.
   - https://git.sr.ht/~lisp/jerboa-android
 tasks:
@@ -14,6 +14,6 @@ tasks:
       test "$(apk info -v chez-scheme)" = chez-scheme-10.3.0-r2
       test "$(apk info -v git)" = git-2.54.0-r0
       test "$(apk info -v make)" = make-4.4.1-r4
-      test "$(git -C ../jerboa rev-parse HEAD)" = f833c6c8887a23fedbd207d5d4e48df3ea2afc06
-      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 3128143e7858a334acf4ef32d1db19f675f618cd
+      test "$(git -C ../jerboa rev-parse HEAD)" = 06442a897016857792d3ff87af5d08bfeb21eb78
+      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = b1cd91d5bc7427458c2b9321e9155a9bc679daad
       JERBOA="chez --libdirs .:../jerboa/lib --script" make test
diff --git a/dependencies.lock.json b/dependencies.lock.json
index f3e0442..7f04505 100644
--- a/dependencies.lock.json
+++ b/dependencies.lock.json
@@ -11,8 +11,8 @@
   "generator_runtime": {
     "name": "jerboa",
     "repository": "https://git.sr.ht/~lisp/jerboa",
-    "commit": "f833c6c8887a23fedbd207d5d4e48df3ea2afc06",
-    "tree": "3128143e7858a334acf4ef32d1db19f675f618cd"
+    "commit": "06442a897016857792d3ff87af5d08bfeb21eb78",
+    "tree": "b1cd91d5bc7427458c2b9321e9155a9bc679daad"
   },
   "assurance_tools": {
     "osv_scanner": {
diff --git a/scripts/verify-supply-chain.sh b/scripts/verify-supply-chain.sh
index df55347..96b9e5a 100755
--- a/scripts/verify-supply-chain.sh
+++ b/scripts/verify-supply-chain.sh
@@ -3,8 +3,8 @@ set -eu
 
 repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
 lock="$repo/dependencies.lock.json"
-jerboa_commit=f833c6c8887a23fedbd207d5d4e48df3ea2afc06 # gitsafe:ignore
-jerboa_tree=3128143e7858a334acf4ef32d1db19f675f618cd # gitsafe:ignore
+jerboa_commit=06442a897016857792d3ff87af5d08bfeb21eb78 # gitsafe:ignore
+jerboa_tree=b1cd91d5bc7427458c2b9321e9155a9bc679daad # gitsafe:ignore
 gradle_sha=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
 jdk_macos_sha=8fa1eff40bb637a33613b2ccb8b12c70dc3661cc22cf8e784943715769a05336
 jdk_linux_sha=d8afc263758141a66e0e3aafc321e783f7016696f4eaea067d340a269037d331
diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index e9d3de7..00bab92 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1996,7 +1996,9 @@
                       (java nio file Files)
                       (java nio file Path)
                       (java security MessageDigest)
-                      (java util zip ZipEntry))
+                      (java security cert X509Certificate)
+                      (java util zip ZipEntry)
+                      (javax net ssl X509TrustManager))
       (typed-library (com sfb ssdreview)
         (export make-RemoteConfig RemoteConfig?
                 RemoteConfig-apiUrl RemoteConfig-spkiSha256 RemoteConfig-bearerToken
@@ -2046,12 +2048,16 @@
                 remoteTruthWriteSkipped
                 localFileMissing localTextMatchesRemote
                 shouldReplaceLocalByTimes
+                truthStoreMakePinnedTrustManager
                 zipEntryPresent
                 zipEntryIsDirectory zipEntryIsFile zipEntryDeclaresExcessiveSize)
         (type File)
         (type Path)
         (type URL)
         (type ZipEntry)
+        (type TruthStore)
+        (type X509Certificate)
+        (type X509TrustManager)
         (type Int32)
         (type Any)
         (extern (fileExistsRaw (file : File)) : Bool
@@ -2088,6 +2094,21 @@
           (kotlin-member-get size))
         (extern (zipEntryCompressedSize (entry : ZipEntry)) : Int
           (kotlin-member-get compressedSize))
+        (extern (x509TrustManagerAcceptedIssuers
+                  (manager : X509TrustManager)) : (Array X509Certificate)
+          (kotlin-member-get acceptedIssuers))
+        (extern (x509TrustManagerCheckClientTrusted
+                  (manager : X509TrustManager)
+                  (chain : (Array X509Certificate))
+                  (authType : String)) : Unit
+          (kotlin-member-call checkClientTrusted))
+        (extern (truthStoreCheckPinnedServerTrusted
+                  (store : TruthStore)
+                  (platform : X509TrustManager)
+                  (expectedPin : Bytes)
+                  (chain : (Array X509Certificate))
+                  (authType : String)) : Unit
+          (kotlin-member-call checkPinnedServerTrusted))
         (record RemoteConfig
           ((apiUrl : URL)
            (spkiSha256 : (Nullable Bytes))
@@ -2260,6 +2281,26 @@
         (def (constantTimeNullableBytesMismatch (actual : Bytes)
                                                 (expected : (Nullable Bytes))) : Bool
           (not (constantTimeNullableBytesEqual actual expected)))
+        (def (truthStoreMakePinnedTrustManager
+               (store : TruthStore)
+               (platform : X509TrustManager)
+               (expectedPin : Bytes)) : X509TrustManager
+          (object X509TrustManager
+            (override (getAcceptedIssuers) : (Array X509Certificate)
+              (x509TrustManagerAcceptedIssuers platform))
+            (override (checkClientTrusted
+                        (chain : (Array X509Certificate))
+                        (authType : String)) : Unit
+              (x509TrustManagerCheckClientTrusted platform chain authType))
+            (override (checkServerTrusted
+                        (chain : (Array X509Certificate))
+                        (authType : String)) : Unit
+              (truthStoreCheckPinnedServerTrusted
+                store
+                platform
+                expectedPin
+                chain
+                authType))))
         (def (remoteEndpointIsHttps (protocol : String) (host : String)) : Bool
           (and (equal? protocol "https") (not (string-blank? host))))
         (def (safeRemoteUrlParts (userInfo : (Nullable String))
@@ -10311,29 +10352,23 @@
        "        return connection"
        "    }"
        ""
+       "    internal fun checkPinnedServerTrusted(platform: X509TrustManager, expectedPin: ByteArray, chain: Array<X509Certificate>, authType: String) {"
+       "        platform.checkServerTrusted(chain, authType)"
+       "        if (!positiveCount(chain.size)) throw CertificateException(\"Server provided no certificate\")"
+       "        val leaf = chain[0]"
+       "        val actual = MessageDigest.getInstance(\"SHA-256\").digest(leaf.publicKey.encoded)"
+       "        if (constantTimeBytesMismatch(actual, expectedPin)) {"
+       "            throw CertificateException(\"SSD service identity pin mismatch\")"
+       "        }"
+       "    }"
+       ""
        "    private fun pinnedSocketFactory(config: RemoteConfig): SSLSocketFactory {"
        "        if (!remotePinBytesPresent(config.spkiSha256)) throw CertificateException(\"SPKI pin is required for pinned TLS\")"
        "        val expectedPin = checkNotNull(config.spkiSha256)"
        "        val managerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())"
        "        managerFactory.init(null as KeyStore?)"
        "        val platform = managerFactory.trustManagers.filterIsInstance<X509TrustManager>().single()"
-       "        val pinned = object : X509TrustManager {"
-       "            override fun getAcceptedIssuers(): Array<X509Certificate> = platform.acceptedIssuers"
-       ""
-       "            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {"
-       "                platform.checkClientTrusted(chain, authType)"
-       "            }"
-       ""
-       "            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {"
-       "                platform.checkServerTrusted(chain, authType)"
-       "                if (!positiveCount(chain.size)) throw CertificateException(\"Server provided no certificate\")"
-       "                val leaf = chain[0]"
-       "                val actual = MessageDigest.getInstance(\"SHA-256\").digest(leaf.publicKey.encoded)"
-       "                if (constantTimeBytesMismatch(actual, expectedPin)) {"
-       "                    throw CertificateException(\"SSD service identity pin mismatch\")"
-       "                }"
-       "            }"
-       "        }"
+       "        val pinned = truthStoreMakePinnedTrustManager(this, platform, expectedPin)"
        "        return SSLContext.getInstance(\"TLS\").apply {"
        "            init(null, arrayOf<TrustManager>(pinned), SecureRandom())"
        "        }.socketFactory"