Generate SSD remote config from typed Jerboa

ober

94e9b0da9e4d80958093243038b8e6f36866a92d

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index e77d984..953c054 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -15528,8 +15528,12 @@
           (modifiers internal const))))
 
     (typed-kotlin-file "com/sfb/ssdreview/TruthStoreLocal.kt"
-      (kotlin-imports (java io File)
+      (kotlin-imports (android content Context)
+                      (android content SharedPreferences)
+                      (android util Base64)
+                      (java io File)
                       (java io InputStream)
+                      (java net URL)
                       (java nio file LinkOption)
                       (java nio file Path)
                       (java security MessageDigest)
@@ -15541,7 +15545,9 @@
                 truthStoreAppendEvent truthStoreApplyTruth
                 truthStoreStorageSummaryLocal
                 truthStoreStorageStatsLocal
-                truthStoreEnsureStorageCapacityLocal)
+                truthStoreEnsureStorageCapacityLocal
+                truthStoreLoadRemoteConfigLocal)
+        (type Context)
         (type Exception)
         (type File)
         (type InputStream)
@@ -15549,9 +15555,12 @@
         (type LinkOption)
         (type MessageDigest)
         (type Path)
+        (type RemoteConfig)
         (type SsdSession)
+        (type SharedPreferences)
         (type StorageStats)
         (type TruthStore)
+        (type URL)
         (type JSONObject)
         (extern (truthStoreRoot
                   (store : TruthStore)) : File
@@ -15575,6 +15584,18 @@
                   (file : File)
                   (json : JSONObject)) : Unit
           (kotlin-member-call appendJsonLine))
+        (extern (truthStoreContextModePrivate) : Int32
+          (kotlin-value Context MODE_PRIVATE))
+        (extern (truthStoreSharedPreferences
+                  (context : Context)
+                  (name : String)
+                  (mode : Int32)) : SharedPreferences
+          (kotlin-member-call getSharedPreferences))
+        (extern (truthStoreSharedPreferencesGetString
+                  (preferences : SharedPreferences)
+                  (key : String)
+                  (default : (Nullable String))) : (Nullable String)
+          (kotlin-member-call getString))
         (extern (truthStoreFileName
                   (file : File)) : String
           (kotlin-member-get name))
@@ -15612,6 +15633,21 @@
         (extern (truthStoreIllegalStateException
                   (message : String)) : Exception
           (kotlin-call IllegalStateException))
+        (extern (truthStoreUrl (text : String)) : URL
+          (kotlin-call URL))
+        (extern (truthStoreUrlProtocol (url : URL)) : String
+          (kotlin-member-get protocol))
+        (extern (truthStoreUrlHost (url : URL)) : String
+          (kotlin-member-get host))
+        (extern (truthStoreUrlUserInfo
+                  (url : URL)) : (Nullable String)
+          (kotlin-member-get userInfo))
+        (extern (truthStoreUrlQuery
+                  (url : URL)) : (Nullable String)
+          (kotlin-member-get query))
+        (extern (truthStoreUrlRef
+                  (url : URL)) : (Nullable String)
+          (kotlin-member-get ref))
         (extern (truthStoreMessageDigestGetInstance
                   (algorithm : String)) : MessageDigest
           (kotlin-call MessageDigest getInstance))
@@ -15625,6 +15661,8 @@
           (kotlin-value MAX_STORAGE_BYTES))
         (extern (truthStoreMaxStorageFiles) : Int32
           (kotlin-value MAX_STORAGE_FILES))
+        (extern (truthStoreBase64NoWrap) : Int32
+          (kotlin-value Base64 NO_WRAP))
         (def (truthStoreSha256Hex (bytes : Bytes)) : String
           (bytesToLowerHex
             (truthStoreMessageDigestDigest
@@ -15835,10 +15873,82 @@
                         (truthStoreMaxStorageFiles)
                         (truthStoreMaxStorageBytes))
                     (begin)
-                    (throw
-                      (truthStoreIllegalArgumentException
-                        "Storage quota exceeded")
-                      Unit)))))))))
+                  (throw
+                    (truthStoreIllegalArgumentException
+                      "Storage quota exceeded")
+                      Unit)))))))
+        (def (truthStoreRequired
+               (condition : Bool)
+               (message : String)) : Unit
+          (if condition
+            (begin)
+            (throw
+              (truthStoreIllegalArgumentException message)
+              Unit)))
+        (def (truthStorePreferenceString
+               (preferences : SharedPreferences)
+               (key : String)) : String
+          (string-trim
+            (nullableTextOrEmpty
+              (truthStoreSharedPreferencesGetString
+                preferences
+                key
+                (nullable-none String)))))
+        (def (truthStoreStripTrailingSlashes (text : String)) : String
+          (string-replace-regex text "/+$" ""))
+        (def (truthStoreLoadRemoteConfigLocal
+               (context : Context)) : (Nullable RemoteConfig)
+          (let ((preferences
+                  (truthStoreSharedPreferences
+                    context
+                    "ssd_review_remote"
+                    (truthStoreContextModePrivate))))
+            (let ((endpoint
+                    (normalizeRemoteEndpoint
+                      (truthStorePreferenceString
+                        preferences
+                        "base_url")))
+                  (pinText
+                    (truthStorePreferenceString
+                      preferences
+                      "spki_sha256"))
+                  (token
+                    (truthStorePreferenceString
+                      preferences
+                      "bearer_token")))
+              (let ((url
+                      (truthStoreUrl
+                        (truthStoreStripTrailingSlashes endpoint))))
+                (begin
+                  (truthStoreRequired
+                    (remoteEndpointIsHttps
+                      (truthStoreUrlProtocol url)
+                      (truthStoreUrlHost url))
+                    "HTTPS endpoint required")
+                  (truthStoreRequired
+                    (safeRemoteUrlParts
+                      (truthStoreUrlUserInfo url)
+                      (truthStoreUrlQuery url)
+                      (truthStoreUrlRef url))
+                    "Unsafe endpoint URL")
+                  (truthStoreRequired
+                    (remoteBearerTokenValid token)
+                    "Invalid remote authentication token")
+                  (let ((pin
+                          (remotePinOrNull
+                            pinText
+                            (truthStoreBase64NoWrap))))
+                    (begin
+                      (truthStoreRequired
+                        (remotePinBytesValid pin)
+                        "SPKI SHA-256 pin must decode to 32 bytes")
+                      (truthStoreRequired
+                        (remotePinAllowedForProtocol
+                          (truthStoreUrlProtocol url)
+                          pin)
+                        "SPKI pin requires HTTPS")
+                      (nullable-some
+                        (make-RemoteConfig url pin token)))))))))))
 
     (kotlin-file-lines "com/sfb/ssdreview/TruthStore.kt"
       (
@@ -16013,25 +16123,8 @@
        "    private fun atomicWriteText(destination: File, text: String) ="
        "        atomicWrite(destination, textUtf8Bytes(text))"
        ""
-       "    private fun loadRemoteConfig(): RemoteConfig? {"
-       "        val preferences = context.getSharedPreferences(\"ssd_review_remote\", Context.MODE_PRIVATE)"
-       "        val savedEndpoint = nullableTextOrEmpty(preferences.getString(\"base_url\", null)).trim()"
-       "        val endpoint = normalizeRemoteEndpoint(savedEndpoint)"
-       "        val pinText = nullableTextOrEmpty(preferences.getString(\"spki_sha256\", null)).trim()"
-       "        val token = nullableTextOrEmpty(preferences.getString(\"bearer_token\", null)).trim()"
-       "        val url = URL(endpoint.trimEnd('/'))"
-       "        require(remoteEndpointIsHttps(url.protocol, url.host)) { \"HTTPS endpoint required\" }"
-       "        require(safeRemoteUrlParts(url.userInfo, url.query, url.ref)) { \"Unsafe endpoint URL\" }"
-       "        if (remoteBearerTokenPresent(token)) {"
-       "            require(remoteBearerTokenValid(token)) {"
-       "                \"Invalid remote authentication token\""
-       "            }"
-       "        }"
-       "        val pin = remotePinOrNull(pinText, Base64.NO_WRAP)"
-       "        require(remotePinBytesValid(pin)) { \"SPKI SHA-256 pin must decode to 32 bytes\" }"
-       "        require(remotePinAllowedForProtocol(url.protocol, pin)) { \"SPKI pin requires HTTPS\" }"
-       "        return RemoteConfig(url, pin, token)"
-       "    }"
+       "    private fun loadRemoteConfig(): RemoteConfig? ="
+       "        truthStoreLoadRemoteConfigLocal(context)"
        ""
        "    private fun remoteUrl(relative: String): URL {"
        "        require(remoteRelativePathSafe(relative))"
diff --git a/tests/ssd-security-test.sh b/tests/ssd-security-test.sh
index b55b649..8d24958 100755
--- a/tests/ssd-security-test.sh
+++ b/tests/ssd-security-test.sh
@@ -50,7 +50,7 @@ require_text 'HttpsURLConnection'
 require_text 'X509TrustManager'
 require_generated_text 'MessageDigest.isEqual'
 require_text 'setRequestProperty("Authorization", bearerAuthorizationHeader(config.bearerToken))'
-require_text 'context.getSharedPreferences("ssd_review_remote", Context.MODE_PRIVATE)'
+require_generated_text 'context.getSharedPreferences("ssd_review_remote", Context.MODE_PRIVATE)'
 require_text 'Remote SSD service is not locally configured'
 require_text 'validatedSourceKey'
 require_generated_text 'truthStoreSha256Hex'