Generate SSD pinned trust manager from typed Jerboa
ober
b97ad3ddc60b0165afeff210139a6ee97e84030e
--- 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 --- 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": { --- 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 --- 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"