Generate SSD remote config from typed Jerboa
ober
94e9b0da9e4d80958093243038b8e6f36866a92d
--- 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))" --- 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'