diff --git a/app/build.gradle b/app/build.gradle index aef7713..ecdbf45 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -336,6 +336,8 @@ dependencies { // ksp implementation "com.google.devtools.ksp:symbol-processing-api:1.8.0-1.0.8" + + implementation "androidx.security:security-crypto:1.1.0-alpha04" } if (hasSentryConfig) { diff --git a/app/src/main/java/com/fox2code/mmm/SetupActivity.java b/app/src/main/java/com/fox2code/mmm/SetupActivity.java index 7e751ab..e3e430e 100644 --- a/app/src/main/java/com/fox2code/mmm/SetupActivity.java +++ b/app/src/main/java/com/fox2code/mmm/SetupActivity.java @@ -3,6 +3,7 @@ package com.fox2code.mmm; import static com.fox2code.mmm.utils.IntentHelper.getActivity; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; @@ -13,6 +14,8 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.FragmentActivity; +import androidx.security.crypto.EncryptedFile; +import androidx.security.crypto.MasterKey; import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.mmm.androidacy.AndroidacyRepoData; @@ -26,6 +29,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.materialswitch.MaterialSwitch; import com.topjohnwu.superuser.internal.UiThreadHandler; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Objects; import io.realm.Realm; @@ -328,5 +334,19 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { } } }); + try { + String cookieFileName = "cookies"; + File cookieFile = new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName); + String initialCookie = "is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=\" + chain.request().url().host() + \"; SameSite=None; Secure;|foxmmm_version=" + BuildConfig.VERSION_CODE + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=\" + chain.request().url().host() + \"; SameSite=None; Secure;"; + Context context = getApplicationContext(); + MasterKey mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); + EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); + encryptedFile.openFileOutput().write(initialCookie.getBytes()); + encryptedFile.openFileOutput().flush(); + encryptedFile.openFileOutput().close(); + } catch (GeneralSecurityException | + IOException e) { + Timber.e(e); + } } } \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java index 24c8540..b49cfc0 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java @@ -7,24 +7,23 @@ package com.fox2code.mmm.utils.io; import android.content.Context; import androidx.annotation.NonNull; +import androidx.security.crypto.EncryptedFile; +import androidx.security.crypto.MasterKey; -import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; -import java.util.HashSet; +import java.io.InputStream; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; -/** - * This interceptor put all the Cookies in Preferences in the Request. - * Your implementation on how to get the Preferences may ary, but this will work 99% of the time. - */ + public class AddCookiesInterceptor implements Interceptor { - public static final String PREF_COOKIES = "PREF_COOKIES"; // We're storing our stuff in a database made just for cookies called PREF_COOKIES. // I reccomend you do this, and don't change this default value. private final Context context; @@ -37,9 +36,32 @@ public class AddCookiesInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); + MasterKey mainKeyAlias; + String cookieFileName = "cookies"; + byte[] plaintext; + try { + // create cookie file if it doesn't exist + mainKeyAlias = new MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); + InputStream inputStream; + inputStream = encryptedFile.openFileInput(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + int nextByte = inputStream.read(); + while (nextByte != -1) { + byteArrayOutputStream.write(nextByte); + nextByte = inputStream.read(); + } - HashSet preferences = (HashSet) MainApplication.getSharedPreferences().getStringSet(PREF_COOKIES, new HashSet<>()); - + plaintext = byteArrayOutputStream.toByteArray(); + inputStream.close(); + } catch ( + Exception e) { + Timber.e(e, "Error while reading cookies"); + plaintext = new byte[0]; + } + String[] preferences = new String(plaintext).split("\\|"); // Use the following if you need everything in one line. // Some APIs die if you do it differently. StringBuilder cookiestring = new StringBuilder(); @@ -50,13 +72,7 @@ public class AddCookiesInterceptor implements Interceptor { } cookiestring.append(cookie).append(" "); } - // if ccokiestring doesn't have is_foxmmm cookie, add a never expiring one for the current domain. - if (!cookiestring.toString().contains("is_foxmmm")) { - cookiestring.append("is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=").append(chain.request().url().host()).append("; SameSite=None; Secure;"); - } - if (BuildConfig.DEBUG_HTTP) { - Timber.d("Sending cookies: %s", cookiestring.toString()); - } + Timber.d("Sending cookies: %s", cookiestring.toString()); builder.addHeader("Cookie", cookiestring.toString()); return chain.proceed(builder.build()); diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java index a962d66..db12a1c 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java @@ -6,14 +6,20 @@ package com.fox2code.mmm.utils.io; import android.annotation.SuppressLint; import android.content.Context; -import android.content.SharedPreferences; import androidx.annotation.NonNull; +import androidx.security.crypto.EncryptedFile; +import androidx.security.crypto.MasterKey; import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.util.Arrays; import java.util.HashSet; import okhttp3.Interceptor; @@ -22,9 +28,11 @@ import timber.log.Timber; public class ReceivedCookiesInterceptor implements Interceptor { private final Context context; + public ReceivedCookiesInterceptor(Context context) { this.context = context; } // AddCookiesInterceptor() + @NonNull @SuppressLint({"MutatingSharedPrefs", "ApplySharedPref"}) @Override @@ -32,19 +40,48 @@ public class ReceivedCookiesInterceptor implements Interceptor { Response originalResponse = chain.proceed(chain.request()); if (!originalResponse.headers("Set-Cookie").isEmpty()) { - HashSet cookies = (HashSet) MainApplication.getSharedPreferences().getStringSet("PREF_COOKIES", new HashSet<>()); + MasterKey mainKeyAlias; + String cookieFileName = "cookies"; + byte[] plaintext; + try { + mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); + EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); + InputStream inputStream = encryptedFile.openFileInput(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + int nextByte = inputStream.read(); + while (nextByte != -1) { + byteArrayOutputStream.write(nextByte); + nextByte = inputStream.read(); + } - cookies.addAll(originalResponse.headers("Set-Cookie")); - if (!cookies.toString().contains("is_foxmmm")) { - cookies.add("is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=" + chain.request().url().host() + "; SameSite=None; Secure;"); + plaintext = byteArrayOutputStream.toByteArray(); + inputStream.close(); + } catch ( + Exception e) { + e.printStackTrace(); + plaintext = new byte[0]; } - - SharedPreferences.Editor memes = MainApplication.getSharedPreferences().edit(); + HashSet cookies = new HashSet<>(Arrays.asList(new String(plaintext).split("\\|"))); + HashSet cookieSet = new HashSet<>(originalResponse.headers("Set-Cookie")); if (BuildConfig.DEBUG_HTTP) { - Timber.d("Received cookies: %s", cookies); + Timber.d("Received cookies: %s", cookieSet); + } + // if we already have the cooki in cookies, remove the one in cookies + cookies.removeIf(cookie -> cookieSet.toString().contains(cookie.split(";")[0])); + // add the new cookies to the cookies + cookies.addAll(cookieSet); + // write the cookies to the file + try { + mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); + EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); + encryptedFile.openFileOutput().write(String.join("|", cookies).getBytes()); + encryptedFile.openFileOutput().flush(); + encryptedFile.openFileOutput().close(); + Timber.d("Storing encrypted cookies: %s", String.join("|", cookies)); + } catch ( + GeneralSecurityException e) { + throw new IllegalStateException("Unable to get master key", e); } - memes.putStringSet("PREF_COOKIES", cookies).apply(); - memes.commit(); } return originalResponse;