From f379b93d52bf0dbd3816ec3f64d165314771d2a6 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: adb: reduce and harden wireless transport --- .../muntashirakon/adb/AbsAdbConnectionManager.java | 509 ------------------- .../adb/AdbAuthenticationFailedException.java | 14 - .../io/github/muntashirakon/adb/AdbConnection.java | 550 ++++++--------------- .../github/muntashirakon/adb/AdbInputStream.java | 5 +- .../adb/AdbPairingRequiredException.java | 7 - .../io/github/muntashirakon/adb/AdbProtocol.java | 114 ++--- .../io/github/muntashirakon/adb/AdbStream.java | 199 +++++--- .../io/github/muntashirakon/adb/AndroidPubkey.java | 159 ++---- .../adb/ByteArrayNoThrowOutputStream.java | 24 - .../java/io/github/muntashirakon/adb/KeyPair.java | 9 - .../io/github/muntashirakon/adb/LocalServices.java | 271 ---------- .../io/github/muntashirakon/adb/PRNGFixes.java | 319 ------------ .../github/muntashirakon/adb/PairingAuthCtx.java | 28 +- .../muntashirakon/adb/PairingConnectionCtx.java | 151 +++--- .../java/io/github/muntashirakon/adb/SslUtils.java | 91 ++-- .../io/github/muntashirakon/adb/StringCompat.java | 26 - .../github/muntashirakon/adb/android/AdbMdns.java | 193 -------- .../muntashirakon/adb/android/AndroidUtils.java | 58 --- .../github/muntashirakon/adb/android/package.html | 1 - 19 files changed, 491 insertions(+), 2237 deletions(-) delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java delete mode 100644 vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html (limited to 'vendor/libadb-android/libadb/src/main/java/io/github') diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java deleted file mode 100644 index 8452a86..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AbsAdbConnectionManager.java +++ /dev/null @@ -1,509 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb; - -import android.content.Context; -import android.os.Build; - -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.WorkerThread; - -import java.io.Closeable; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import javax.security.auth.DestroyFailedException; - -import io.github.muntashirakon.adb.android.AdbMdns; - -@SuppressWarnings("unused") -public abstract class AbsAdbConnectionManager implements Closeable { - private final Object mLock = new Object(); - @Nullable - private AdbConnection mAdbConnection; - private String mHostAddress = "127.0.0.1"; - private int mApi = Build.VERSION_CODES.BASE; - private long mTimeout = Long.MAX_VALUE; - private TimeUnit mTimeoutUnit = TimeUnit.MILLISECONDS; - private boolean mThrowOnUnauthorised = false; - - /** - * Return generated/stored private key. - */ - @NonNull - protected abstract PrivateKey getPrivateKey(); - - /** - * Return public key wrapped around a certificate. - */ - @NonNull - protected abstract Certificate getCertificate(); - - /** - * Return a name for the device. This can be the app label, hostname or user@hostname. - */ - @NonNull - protected abstract String getDeviceName(); - - /** - * Set host address for this connection. On the same device, this should be {@code 127.0.0.1}. - */ - @CallSuper - public void setHostAddress(@NonNull String hostAddress) { - mHostAddress = Objects.requireNonNull(hostAddress); - } - - /** - * Get host address for this connection. Default value is {@code 127.0.0.1}. - */ - @NonNull - public String getHostAddress() { - return mHostAddress; - } - - /** - * Set Android API (i.e. SDK) version for this connection. If the daemon and the client are located in the same - * directory, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as security. - * - * @param api The API version, default is {@link Build.VERSION_CODES#BASE}. - */ - public void setApi(int api) { - this.mApi = api; - } - - /** - * Get Android API (i.e. SDK) version for this connection. Default value is {@link Build.VERSION_CODES#BASE}. - */ - public int getApi() { - return mApi; - } - - /** - * Set time to wait for the connection to be made. - * - * @param timeout Timeout value - * @param unit Timeout unit - */ - @CallSuper - public void setTimeout(long timeout, TimeUnit unit) { - mTimeout = timeout; - mTimeoutUnit = unit; - } - - /** - * Get time to wait for the connection to be made. If not set using {@link #setTimeout(long, TimeUnit)}, the default - * timeout is {@link Long#MAX_VALUE} milliseconds. - * - * @return Timeout in milliseconds - */ - public long getTimeout() { - return mTimeoutUnit.toMillis(mTimeout); - } - - /** - * Get the unit for the timeout. If not set using {@link #setTimeout(long, TimeUnit)}, the default timeout unit is - * {@link TimeUnit#MILLISECONDS}. - */ - @NonNull - public TimeUnit getTimeoutUnit() { - return mTimeoutUnit; - } - - /** - * Set whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication - * attempt. - * - * @param throwOnUnauthorised {@code true} to throw {@link AdbAuthenticationFailedException} or {@code false} - * otherwise. - */ - @CallSuper - public void setThrowOnUnauthorised(boolean throwOnUnauthorised) { - mThrowOnUnauthorised = throwOnUnauthorised; - } - - /** - * Get whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication - * attempt. - * - * @return {@code true} if the system is configured to throw {@link AdbAuthenticationFailedException} or - * {@code false} otherwise. The default value is {@code false}. - */ - public boolean isThrowOnUnauthorised() { - return mThrowOnUnauthorised; - } - - /** - * Get the {@link AdbConnection} backed by this object. - * - * @return Underlying {@link AdbConnection}, or {@code null} if the connection hasn't been made yet. - */ - @CallSuper - @Nullable - public AdbConnection getAdbConnection() { - synchronized (mLock) { - return mAdbConnection; - } - } - - /** - * Check if it is connected to an ADB daemon. - * - * @return {@code true} if connected, {@code false} otherwise. - */ - public boolean isConnected() { - synchronized (mLock) { - return mAdbConnection != null && mAdbConnection.isConnected() && mAdbConnection.isConnectionEstablished(); - } - } - - /** - * Attempt to connect to ADB by performing an automatic network discovery of TLS host and port. Host address set by - * {@link #setHostAddress(String)} is ignored. - * - * @param context Application context - * @param timeoutMillis Amount of time spent in searching for a host and a port. - * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection - * attempt is unsuccessful, or it has already been made. - * @throws IOException If the socket connection could not be made. - * @throws InterruptedException If timeout has reached. - * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB - * daemon has rejected the first authentication attempt, which indicates - * that the daemon has not saved the public key from a previous connection. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @WorkerThread - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean connectTls(@NonNull Context context, long timeoutMillis) - throws IOException, InterruptedException, AdbPairingRequiredException { - return autoConnect(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, timeoutMillis); - } - - /** - * Attempt to connect to ADB by performing an automatic network discovery of TCP host and port. Host address set by - * {@link #setHostAddress(String)} is ignored. - * - * @param context Application context - * @param timeoutMillis Amount of time spent in searching for a host and a port. - * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection - * attempt is unsuccessful, or it has already been made. - * @throws IOException If the socket connection could not be made. - * @throws InterruptedException If timeout has reached. - * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB - * daemon has rejected the first authentication attempt, which indicates - * that the daemon has not saved the public key from a previous connection. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @WorkerThread - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean connectTcp(@NonNull Context context, long timeoutMillis) - throws IOException, InterruptedException, AdbPairingRequiredException { - return autoConnect(context, AdbMdns.SERVICE_TYPE_ADB, timeoutMillis); - } - - /** - * Attempt to connect to ADB by performing an automatic network discovery of host and port. Host address set by - * {@link #setHostAddress(String)} is ignored. - * - * @param context Application context - * @param timeoutMillis Amount of time spent in searching for a host and a port. - * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection - * attempt is unsuccessful, or it has already been made. - * @throws IOException If the socket connection could not be made. - * @throws InterruptedException If timeout has reached. - * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB - * daemon has rejected the first authentication attempt, which indicates - * that the daemon has not saved the public key from a previous connection. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @WorkerThread - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) - public boolean autoConnect(@NonNull Context context, long timeoutMillis) - throws IOException, InterruptedException, AdbPairingRequiredException { - synchronized (mLock) { - AtomicInteger atomicPort = new AtomicInteger(-1); - AtomicReference atomicHostAddress = new AtomicReference<>(null); - CountDownLatch resolveHostAndPort = new CountDownLatch(1); - - AdbMdns adbMdnsTcp = new AdbMdns(context, AdbMdns.SERVICE_TYPE_ADB, (hostAddress, port) -> { - if (hostAddress != null) { - atomicHostAddress.set(hostAddress.getHostAddress()); - atomicPort.set(port); - } - resolveHostAndPort.countDown(); - }); - adbMdnsTcp.start(); - - AdbMdns adbMdnsTls = new AdbMdns(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, (hostAddress, port) -> { - if (hostAddress != null) { - atomicHostAddress.set(hostAddress.getHostAddress()); - atomicPort.set(port); - } - resolveHostAndPort.countDown(); - }); - adbMdnsTls.start(); - - try { - if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new InterruptedException("Timed out while trying to find a valid host address and port"); - } - } finally { - adbMdnsTcp.stop(); - adbMdnsTls.stop(); - } - - String host = atomicHostAddress.get(); - int port = atomicPort.get(); - - if (host == null || port == -1) { - throw new IOException("Could not find any valid host address or port"); - } - - mHostAddress = host; - mAdbConnection = new AdbConnection.Builder(host, port) - .setApi(mApi) - .setKeyPair(getAdbKeyPair()) - .setDeviceName(Objects.requireNonNull(getDeviceName())) - .build(); - return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised); - } - } - - @WorkerThread - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) - private boolean autoConnect(@NonNull Context context, @AdbMdns.ServiceType @NonNull String serviceType, long timeoutMillis) - throws IOException, InterruptedException, AdbPairingRequiredException { - synchronized (mLock) { - AtomicInteger atomicPort = new AtomicInteger(-1); - AtomicReference atomicHostAddress = new AtomicReference<>(null); - CountDownLatch resolveHostAndPort = new CountDownLatch(1); - - AdbMdns adbMdns = new AdbMdns(context, serviceType, (hostAddress, port) -> { - if (hostAddress != null) { - atomicHostAddress.set(hostAddress.getHostAddress()); - atomicPort.set(port); - } - resolveHostAndPort.countDown(); - }); - adbMdns.start(); - - try { - if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new InterruptedException("Timed out while trying to find a valid host address and port"); - } - } finally { - adbMdns.stop(); - } - - String host = atomicHostAddress.get(); - int port = atomicPort.get(); - - if (host == null || port == -1) { - throw new IOException("Could not find any valid host address or port"); - } - - mHostAddress = host; - mAdbConnection = new AdbConnection.Builder(host, port) - .setApi(mApi) - .setKeyPair(getAdbKeyPair()) - .setDeviceName(Objects.requireNonNull(getDeviceName())) - .build(); - return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised); - } - } - - /** - * Attempt to connect to ADB given a port number. Host address is set via {@link #setHostAddress(String)}. - * - * @param port Port number - * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection - * attempt is unsuccessful, or it has already been made. - * @throws IOException If the socket connection could not be made. - * @throws InterruptedException If timeout has reached. - * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB - * daemon has rejected the first authentication attempt, which indicates - * that the daemon has not saved the public key from a previous connection. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @WorkerThread - public boolean connect(int port) throws IOException, InterruptedException, AdbPairingRequiredException { - synchronized (mLock) { - if (isConnected()) { - return false; - } - mAdbConnection = new AdbConnection.Builder(mHostAddress, port) - .setApi(mApi) - .setKeyPair(getAdbKeyPair()) - .setDeviceName(Objects.requireNonNull(getDeviceName())) - .build(); - return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised); - } - } - - /** - * Attempt to connect to ADB via a host address and a port number. - * - * @param host Host address to use instead of taking it from the {@link #getHostAddress()} - * @param port Port number - * @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection - * attempt is unsuccessful, or it has already been made. - * @throws IOException If the socket connection could not be made. - * @throws InterruptedException If timeout has reached. - * @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the - * ADB daemon has rejected the first authentication attempt, which - * indicates that the daemon has not saved the public key from a previous - * connection. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @WorkerThread - public boolean connect(@NonNull String host, int port) - throws IOException, InterruptedException, AdbPairingRequiredException { - synchronized (mLock) { - if (isConnected()) { - return false; - } - mHostAddress = host; - mAdbConnection = new AdbConnection.Builder(host, port) - .setApi(mApi) - .setKeyPair(getAdbKeyPair()) - .setDeviceName(Objects.requireNonNull(getDeviceName())) - .build(); - return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised); - } - } - - /** - * Disconnect the underlying {@link AdbConnection}. - * - * @throws IOException If the underlying socket fails to close - */ - public void disconnect() throws IOException { - synchronized (mLock) { - if (mAdbConnection != null) { - mAdbConnection.close(); - mAdbConnection = null; - } - } - } - - /** - * Opens an {@link AdbStream} object corresponding to the specified destination. - * This routine will block until the connection completes. - * - * @param destination The destination to open on the target - * @return {@link AdbStream} object corresponding to the specified destination - * @throws IOException If the steam fails or no connection has been made - * @throws InterruptedException If the stream fails while sending the packet - * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8. - */ - @WorkerThread - @NonNull - public AdbStream openStream(String destination) throws IOException, InterruptedException { - synchronized (mLock) { - if (mAdbConnection != null && mAdbConnection.isConnected()) { - try { - return mAdbConnection.open(destination, mTimeout, mTimeoutUnit); - } catch (AdbPairingRequiredException e) { - throw new IllegalStateException(e); - } - } - throw new IOException("Not connected to ADB."); - } - } - - /** - * Opens an {@link AdbStream} object corresponding to the specified destination. - * This routine will block until the connection completes. - * - * @param service The service to open. One of the services under {@link LocalServices.Services}. - * @param args Additional arguments supported by the service (see the corresponding constant to learn more). - * @return AdbStream object corresponding to the specified destination - * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8 - * @throws IOException If the stream fails while sending the packet - * @throws InterruptedException If we are unable to wait for the connection to finish - */ - @NonNull - public AdbStream openStream(@LocalServices.Services int service, @NonNull String... args) - throws IOException, InterruptedException { - synchronized (mLock) { - if (mAdbConnection != null && mAdbConnection.isConnected()) { - try { - return mAdbConnection.open(LocalServices.getDestination(service, args), - mTimeout, mTimeoutUnit); - } catch (AdbPairingRequiredException e) { - throw new IllegalStateException(e); - } - } - throw new IOException("Not connected to ADB."); - } - } - - /** - * Pair with an ADB daemon given port number and pairing code. - * - * @param port Port number - * @param pairingCode The six-digit pairing code as string - * @return {@code true} if the pairing is successful and {@code false} otherwise. - * @throws Exception If pairing failed for some reason. - */ - @WorkerThread - @RequiresApi(Build.VERSION_CODES.GINGERBREAD) - public boolean pair(int port, @NonNull String pairingCode) throws Exception { - return pair(mHostAddress, port, pairingCode); - } - - /** - * Pair with an ADB daemon given host address, port number and pairing code. - * - * @param host Host address to use instead of taking it from the {@link #getHostAddress()} - * @param port Port number - * @param pairingCode The six-digit pairing code as string - * @return {@code true} if the pairing is successful and {@code false} otherwise. - * @throws Exception If pairing failed for some reason. - */ - @WorkerThread - @RequiresApi(Build.VERSION_CODES.GINGERBREAD) - public boolean pair(@NonNull String host, int port, @NonNull String pairingCode) throws Exception { - synchronized (mLock) { - KeyPair keyPair = getAdbKeyPair(); - try (PairingConnectionCtx pairingClient = new PairingConnectionCtx(Objects.requireNonNull(host), port, - StringCompat.getBytes(Objects.requireNonNull(pairingCode), "UTF-8"), keyPair, getDeviceName())) { - // TODO: 5/12/21 Return true/false instead of only exceptions - pairingClient.start(); - } - return true; - } - } - - /** - * Close the underlying {@link AdbConnection} and destroy the private key. - * - * @throws IOException If socket fails to close. - */ - @Override - public void close() throws IOException { - try { - getPrivateKey().destroy(); - } catch (DestroyFailedException | NoSuchMethodError e) { - e.printStackTrace(); - } - if (mAdbConnection != null) { - mAdbConnection.close(); - mAdbConnection = null; - } - } - - @NonNull - private KeyPair getAdbKeyPair() { - return new KeyPair(Objects.requireNonNull(getPrivateKey()), Objects.requireNonNull(getCertificate())); - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java deleted file mode 100644 index bf50eeb..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbAuthenticationFailedException.java +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0) - -package io.github.muntashirakon.adb; - -/** - * Thrown when the ADB daemon rejects our initial authentication attempt, which typically means that the peer has not - * previously saved our public key. - */ -// Copyright 2020 Sam Palmer -public class AdbAuthenticationFailedException extends RuntimeException { - public AdbAuthenticationFailedException() { - super("Initial authentication attempt rejected by peer."); - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java index 7674adc..2abcb72 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbConnection.java @@ -14,27 +14,24 @@ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; import java.net.ConnectException; -import java.net.SocketTimeoutException; import java.net.Socket; +import java.net.SocketTimeoutException; import java.security.PrivateKey; import java.security.cert.Certificate; -import java.security.interfaces.RSAPublicKey; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; -import javax.security.auth.DestroyFailedException; - /** * This class represents an ADB connection. */ // Copyright 2013 Cameron Gutman -public class AdbConnection implements Closeable { - public static final String TAG = AdbConnection.class.getSimpleName(); +public class AdbConnection implements Closeable, AdbStream.Transport { + public static final String TAG = "scrcpy-android"; /** * The underlying socket that this class uses to communicate with the target device. @@ -47,8 +44,6 @@ public class AdbConnection implements Closeable { private final int mPort; - private final int mApi; - /** * The last allocated local stream ID. The ID chosen for the next stream will be this value + 1. */ @@ -93,15 +88,8 @@ public class AdbConnection implements Closeable { */ private volatile boolean mConnectAttempted; - /** - * Whether the connection thread should give up if the first authentication attempt fails. - */ - private volatile boolean mAbortOnUnauthorised; - - /** - * Whether the first authentication attempt failed and {@link #mAbortOnUnauthorised} was {@code true}. - */ - private volatile boolean mAuthorisationFailed; + /** The worker thread is one-shot, even after a failed connection. */ + private boolean mConnectionThreadStarted; /** * Specifies whether a CNXN packet has been received from the peer. @@ -122,16 +110,12 @@ public class AdbConnection implements Closeable { private volatile int mProtocolVersion; - @NonNull - private final KeyPair mKeyPair; + private final int mLocalMaxData; - @NonNull - private volatile String mDeviceName = "Unknown Device"; + private final int mLocalProtocolVersion; - /** - * Specifies whether this connection has already sent a signed token. - */ - private volatile boolean mSentSignature; + @NonNull + private final KeyPair mKeyPair; /** * A hash map of our opened streams indexed by local ID. @@ -146,68 +130,49 @@ public class AdbConnection implements Closeable { private final Object mLock = new Object(); /** - * Creates a AdbConnection object associated with the socket and crypto object specified. - * - * @return A new AdbConnection object. - * @throws IOException If there is a socket error + * Internal constructor to initialize some internal state */ - @WorkerThread - @NonNull - public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey, - @NonNull Certificate certificate) - throws IOException { - return create(host, port, privateKey, certificate, Build.VERSION_CODES.BASE); - } + // LOCAL PATCH: bound on the TCP connect; see the constructor. + private static final int CONNECT_TIMEOUT_MS = 10_000; + private static final int CLOSE_TIMEOUT_MS = 5_000; - /** - * Creates a AdbConnection object associated with the socket and crypto object specified. - * - * @return A new AdbConnection object. - * @throws IOException If there is a socket error - */ @WorkerThread - @NonNull - public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey, - @NonNull Certificate certificate, int api) + private AdbConnection(@NonNull String host, int port, @NonNull KeyPair keyPair) throws IOException { - return create(host, port, new KeyPair(Objects.requireNonNull(privateKey), Objects.requireNonNull(certificate)), - api); - } - - /** - * Creates a AdbConnection object associated with the socket and crypto object specified. - * - * @return A new AdbConnection object. - * @throws IOException If there is a socket error - */ - @WorkerThread - @NonNull - static AdbConnection create(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException { - return new AdbConnection(host, port, keyPair, api); - } - - /** - * Internal constructor to initialize some internal state - */ - @WorkerThread - private AdbConnection(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException { this.mHost = Objects.requireNonNull(host); this.mPort = port; - this.mApi = api; - this.mProtocolVersion = AdbProtocol.getProtocolVersion(mApi); - this.mMaxData = AdbProtocol.getMaxData(api); + this.mLocalProtocolVersion = AdbProtocol.getProtocolVersion(Build.VERSION_CODES.R); + this.mLocalMaxData = AdbProtocol.getMaxData(Build.VERSION_CODES.R); + this.mProtocolVersion = mLocalProtocolVersion; + this.mMaxData = mLocalMaxData; this.mKeyPair = Objects.requireNonNull(keyPair); + Socket socket = new Socket(); try { - this.mSocket = new Socket(host, port); - } catch (Throwable th) { + // LOCAL PATCH: was `new Socket(host, port)`, which blocks on + // the OS default TCP timeout - minutes on a mobile network - + // with no way to cancel. A target that is off or on another + // network is the common case, not an edge case, so bound it. + socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT_MS); + } catch (IOException e) { + try { socket.close(); } catch (IOException ignored) {} //noinspection UnnecessaryInitCause - throw (IOException) new IOException().initCause(th); + throw (IOException) new IOException("cannot reach " + host + ":" + port + + " within " + CONNECT_TIMEOUT_MS + " ms").initCause(e); } - this.mPlainInputStream = mSocket.getInputStream(); - this.mPlainOutputStream = mSocket.getOutputStream(); - - // Disable Nagle because we're sending tiny packets - mSocket.setTcpNoDelay(true); + InputStream plainInput; + OutputStream plainOutput; + try { + // Disable Nagle because we're sending tiny packets. + socket.setTcpNoDelay(true); + plainInput = socket.getInputStream(); + plainOutput = socket.getOutputStream(); + } catch (IOException e) { + try { socket.close(); } catch (IOException ignored) {} + throw (IOException) new IOException("cannot initialize ADB socket").initCause(e); + } + this.mSocket = socket; + this.mPlainInputStream = plainInput; + this.mPlainOutputStream = plainOutput; this.mOpenedStreams = new ConcurrentHashMap<>(); this.mLastLocalId = 0; @@ -234,7 +199,6 @@ public class AdbConnection implements Closeable { @NonNull private Thread createConnectionThread() { return new Thread(() -> { - loop: while (!mConnectionThread.isInterrupted()) { try { // Read and parse a message off the socket's input stream @@ -253,6 +217,9 @@ public class AdbConnection implements Closeable { // Get the stream object corresponding to the packet AdbStream waitingStream = mOpenedStreams.get(msg.arg1); if (waitingStream == null) { + if (msg.command == AdbProtocol.A_WRTE) { + throw new IOException("WRTE for unknown local stream " + msg.arg1); + } continue; } @@ -261,16 +228,16 @@ public class AdbConnection implements Closeable { // We're ready for writes waitingStream.updateRemoteId(msg.arg0); waitingStream.readyForWrite(); - - // Notify an open/write - waitingStream.notify(); } else if (msg.command == AdbProtocol.A_WRTE) { + if (!waitingStream.hasRemoteId(msg.arg0)) { + throw new IOException("WRTE remote stream ID mismatch"); + } // Got some data from our partner waitingStream.addPayload(msg.payload); - - // Tell it we're ready for more - waitingStream.sendReady(); } else { // if (msg.command == AdbProtocol.A_CLSE) { + if (!waitingStream.hasRemoteId(msg.arg0) && msg.arg0 != 0) { + throw new IOException("CLSE remote stream ID mismatch"); + } mOpenedStreams.remove(msg.arg1); // Notify readers and writers waitingStream.notifyClose(true); @@ -279,71 +246,57 @@ public class AdbConnection implements Closeable { break; } case AdbProtocol.A_STLS: { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - sendPacket(AdbProtocol.generateStls()); - - SSLContext sslContext = SslUtils.getSslContext(mKeyPair); - SSLSocket tlsSocket = (SSLSocket) sslContext.getSocketFactory() - .createSocket(mSocket, mHost, mPort, true); - tlsSocket.startHandshake(); - Log.d(TAG, "Handshake succeeded."); - - synchronized (AdbConnection.this) { - mTlsInputStream = tlsSocket.getInputStream(); - mTlsOutputStream = tlsSocket.getOutputStream(); - mIsTls = true; - } + if (mIsTls || mConnectionEstablished + || msg.arg0 < AdbProtocol.A_STLS_VERSION_MIN) { + throw new IOException("Invalid or repeated STLS request"); } - break; - } - case AdbProtocol.A_AUTH: { - if (mIsTls) { - break; - } - if (msg.arg0 != AdbProtocol.ADB_AUTH_TOKEN) { - break; - } - byte[] packet; - // This is an authentication challenge - if (mSentSignature) { - if (mAbortOnUnauthorised) { - mAuthorisationFailed = true; - break loop; - } + sendPacket(AdbProtocol.generateStls()); - // We've already tried our signature, so send our public key - packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_RSAPUBLICKEY, AndroidPubkey - .encodeWithName((RSAPublicKey) mKeyPair.getPublicKey(), mDeviceName)); - } else { - // Sign the token - packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_SIGNATURE, AndroidPubkey - .adbAuthSign(mKeyPair.getPrivateKey(), msg.payload)); - mSentSignature = true; - } + SSLContext sslContext = SslUtils.getSslContext(mKeyPair); + SSLSocket tlsSocket = (SSLSocket) sslContext.getSocketFactory() + .createSocket(mSocket, mHost, mPort, true); + tlsSocket.startHandshake(); + Log.d(TAG, "Handshake succeeded."); - // Write the AUTH reply - sendPacket(packet); + synchronized (AdbConnection.this) { + mTlsInputStream = tlsSocket.getInputStream(); + mTlsOutputStream = tlsSocket.getOutputStream(); + mIsTls = true; + } break; } + case AdbProtocol.A_AUTH: { + throw new IOException("ADB peer requested legacy authentication without TLS"); + } case AdbProtocol.A_CNXN: { + if (!mIsTls) { + throw new IOException("ADB peer did not negotiate TLS"); + } + if (msg.arg0 < AdbProtocol.A_VERSION_MIN || msg.arg1 <= 0) { + throw new IOException("Invalid CNXN parameters: version=" + + msg.arg0 + " maxData=" + msg.arg1); + } synchronized (AdbConnection.this) { - mProtocolVersion = msg.arg0; - mMaxData = msg.arg1; + // Both fields come from an unauthenticated + // peer. Negotiate down, never let the peer + // raise our allocation or checksum limits. + mProtocolVersion = Math.min(msg.arg0, mLocalProtocolVersion); + mMaxData = Math.min(msg.arg1, mLocalMaxData); mConnectionEstablished = true; AdbConnection.this.notifyAll(); } break; } case AdbProtocol.A_OPEN: - case AdbProtocol.A_SYNC: default: - Log.e(TAG, String.format("Unrecognized command = 0x%x", msg.command)); - // Unrecognized packet, just drop it - break; + throw new IOException(String.format( + "Unexpected ADB command 0x%x", msg.command)); } } catch (Exception e) { - mConnectionException = e; - e.printStackTrace(); + if (!mSocket.isClosed()) { + mConnectionException = e; + Log.e(TAG, "ADB connection failed", e); + } // The cleanup is taken care of by a combination of this thread and close() break; } @@ -359,48 +312,6 @@ public class AdbConnection implements Closeable { }); } - /** - * Set a name for the device. Default is “Unknown Device”. - * - * @param deviceName Name of the device, could be the app label, hostname or user@hostname. - */ - public void setDeviceName(@NonNull String deviceName) { - this.mDeviceName = Objects.requireNonNull(deviceName); - } - - /** - * Get the version of the ADB protocol supported by the ADB daemon. The result may depend on the API version - * specified and whether the connection has been established. In API 29 (Android 9) or later, the daemon returns - * {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM} regardless of the protocol used to create the connection. So, if - * {@link #mApi} is set to API 28 or earlier but the OS version is Android 9 or later, before establishing the - * connection, it returns {@link AdbProtocol#A_VERSION_MIN}, and after establishing the connection, it returns - * {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM}. In other cases, it always returns {@link AdbProtocol#A_VERSION_MIN}. - * - * @see #isConnectionEstablished() - */ - public int getProtocolVersion() { - return mProtocolVersion; - } - - /** - * Get the max data size supported by the ADB daemon. A connection have to be attempted before calling this method - * and shall be blocked if the connection is in progress. - * - * @return The maximum data size indicated in the CONNECT packet. - * @throws InterruptedException If a connection cannot be waited on. - * @throws IOException if the connection fails. - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - public int getMaxData() throws InterruptedException, IOException, AdbPairingRequiredException { - if (!mConnectAttempted) { - throw new IllegalStateException("connect() must be called first"); - } - - waitForConnection(Long.MAX_VALUE, TimeUnit.MILLISECONDS); - - return mMaxData; - } - /** * Whether a connection has been established. A connection has been established if a CONNECT request has been * received from the ADB daemon. @@ -409,101 +320,43 @@ public class AdbConnection implements Closeable { return mConnectionEstablished; } - /** - * Whether the underlying socket is connected to an ADB daemon and is not in a closed state. - */ - public boolean isConnected() { - return !mSocket.isClosed() && mSocket.isConnected(); - } - - /** - * Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication attempt - * fails. - * - * @return {@code true} if the connection was established, or {@code false} if the connection timed out - * @throws IOException If the socket fails while connecting - * @throws InterruptedException If timeout has reached - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - public boolean connect() throws IOException, InterruptedException, AdbPairingRequiredException { - return connect(Long.MAX_VALUE, TimeUnit.MILLISECONDS, false); + @Override + public int getMaxData() { + return mMaxData; } /** * Connects to the remote device. This routine will block until the connection completes or the timeout elapses. * - * @param timeout the time to wait for the lock - * @param unit the time unit of the timeout argument - * @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException} - * if the peer rejects out first authentication attempt + * @param timeout the time to wait for the lock + * @param unit the time unit of the timeout argument * @return {@code true} if the connection was established, or {@code false} if the connection timed out * @throws IOException If the socket fails while connecting * @throws InterruptedException If timeout has reached - * @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects the - * first authentication attempt, which indicates that the peer has not - * saved the public key from a previous connection - * @throws AdbPairingRequiredException If ADB lacks pairing */ - public boolean connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised) - throws IOException, InterruptedException, AdbAuthenticationFailedException, AdbPairingRequiredException { - if (mConnectionEstablished) { - throw new IllegalStateException("Already connected"); + public synchronized boolean connect(long timeout, @NonNull TimeUnit unit) + throws IOException, InterruptedException { + validateTimeout(timeout, unit); + if (mConnectionThreadStarted) { + throw new IllegalStateException("Connection already attempted"); } // Send CONNECT - sendPacket(AdbProtocol.generateConnect(mApi)); + sendPacket(AdbProtocol.generateConnect(Build.VERSION_CODES.R)); // Start the connection thread to respond to the peer mConnectAttempted = true; - mAbortOnUnauthorised = throwOnUnauthorised; - mAuthorisationFailed = false; + mConnectionThreadStarted = true; mConnectionThread.start(); return waitForConnection(timeout, Objects.requireNonNull(unit)); } - /** - * Opens an {@link AdbStream} object corresponding to the specified destination. - * This routine will block until the connection completes. - * - * @param service The service to open. One of the services under {@link LocalServices.Services}. - * @param args Additional arguments supported by the service (see the corresponding constant to learn more). - * @return AdbStream object corresponding to the specified destination - * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8 - * @throws IOException If the stream fails while sending the packet - * @throws InterruptedException If we are unable to wait for the connection to finish - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @NonNull - public AdbStream open(@LocalServices.Services int service, @NonNull String... args) - throws IOException, InterruptedException, AdbPairingRequiredException { - if (service < LocalServices.SERVICE_FIRST || service > LocalServices.SERVICE_LAST) { - throw new IllegalArgumentException("Invalid service: " + service); - } - return open(LocalServices.getDestination(service, args)); - } - - /** - * Opens an AdbStream object corresponding to the specified destination. - * This routine will block until the connection completes. - * - * @param destination The destination to open on the target - * @return AdbStream object corresponding to the specified destination - * @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8 - * @throws IOException If the stream fails while sending the packet - * @throws InterruptedException If we are unable to wait for the connection to finish - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - @NonNull - public AdbStream open(@NonNull String destination) - throws IOException, InterruptedException, AdbPairingRequiredException { - return open(destination, Long.MAX_VALUE, TimeUnit.MILLISECONDS); - } - @NonNull public AdbStream open(@NonNull String destination, long timeout, @NonNull TimeUnit unit) - throws IOException, InterruptedException, AdbPairingRequiredException { - int localId = ++mLastLocalId; + throws IOException, InterruptedException { + validateTimeout(timeout, unit); + Objects.requireNonNull(destination); if (!mConnectAttempted) { throw new IllegalStateException("connect() must be called first"); @@ -513,24 +366,26 @@ public class AdbConnection implements Closeable { throw new SocketTimeoutException("ADB connection timed out."); } + int localId = nextLocalId(); + // Add this stream to this list of half-open streams AdbStream stream = new AdbStream(this, localId); mOpenedStreams.put(localId, stream); long timeoutMillis = unit.toMillis(timeout); - long deadline = timeoutMillis == Long.MAX_VALUE - ? Long.MAX_VALUE : System.currentTimeMillis() + timeoutMillis; + long started = System.nanoTime(); try { // Send OPEN only after publishing the half-open stream so an // immediate response cannot race past the lookup table. - sendPacket(AdbProtocol.generateOpen(localId, Objects.requireNonNull(destination))); + sendPacket(AdbProtocol.generateOpen(localId, destination)); synchronized (stream) { while (!stream.isOpen() && !stream.isClosed()) { - if (deadline == Long.MAX_VALUE) { + if (timeoutMillis == Long.MAX_VALUE) { stream.wait(); continue; } - long remaining = deadline - System.currentTimeMillis(); + long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started); + long remaining = timeoutMillis - elapsed; if (remaining <= 0) { throw new SocketTimeoutException("ADB stream open timed out: " + destination); } @@ -552,32 +407,35 @@ public class AdbConnection implements Closeable { return stream; } + private synchronized int nextLocalId() throws IOException { + if (mLastLocalId == Integer.MAX_VALUE) { + throw new IOException("ADB stream ID space exhausted"); + } + return ++mLastLocalId; + } + private boolean waitForConnection(long timeout, @NonNull TimeUnit unit) - throws InterruptedException, IOException, AdbPairingRequiredException { + throws InterruptedException, IOException { synchronized (this) { // Block if a connection is pending, but not yet complete - long timeoutEndMillis = System.currentTimeMillis() + Objects.requireNonNull(unit).toMillis(timeout); - while (!mConnectionEstablished && mConnectAttempted && timeoutEndMillis - System.currentTimeMillis() > 0) { - wait(timeoutEndMillis - System.currentTimeMillis()); + long timeoutMillis = Objects.requireNonNull(unit).toMillis(timeout); + long started = System.nanoTime(); + while (!mConnectionEstablished && mConnectAttempted) { + if (timeoutMillis == Long.MAX_VALUE) { + wait(); + continue; + } + long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started); + long remainingMillis = timeoutMillis - elapsedMillis; + if (remainingMillis <= 0) break; + wait(remainingMillis); } if (!mConnectionEstablished) { if (mConnectAttempted) { return false; - } else if (mAuthorisationFailed) { - // The peer may not have saved the public key in the past connections, or they've been removed. - throw new AdbAuthenticationFailedException(); } else { - Exception connectionException = mConnectionException; - if (connectionException != null) { - if (connectionException instanceof javax.net.ssl.SSLProtocolException) { - String message = connectionException.getMessage(); - if (message != null && message.contains("protocol error")) { - throw (AdbPairingRequiredException) (new AdbPairingRequiredException("ADB pairing is required.").initCause(connectionException)); - } - } - } - throw new IOException("Connection failed"); + throw new IOException("Connection failed", mConnectionException); } } } @@ -585,16 +443,19 @@ public class AdbConnection implements Closeable { return true; } + private static void validateTimeout(long timeout, @NonNull TimeUnit unit) { + Objects.requireNonNull(unit); + if (timeout < 0) throw new IllegalArgumentException("negative timeout"); + } + /** * This function terminates all I/O on streams associated with this ADB connection */ private void cleanupStreams() { - // Close all streams on this connection + // The socket is already unusable. Wake every stream without trying + // to write CLSE packets back through the failed transport. for (AdbStream s : mOpenedStreams.values()) { - try { - s.close(); - } catch (IOException ignored) { - } + s.notifyClose(false); } mOpenedStreams.clear(); } @@ -612,18 +473,26 @@ public class AdbConnection implements Closeable { // Wait for the connection thread to die mConnectionThread.interrupt(); try { - mConnectionThread.join(); - } catch (InterruptedException ignored) { + if (mConnectionThread != Thread.currentThread()) { + mConnectionThread.join(CLOSE_TIMEOUT_MS); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw (IOException) new IOException("interrupted while closing ADB connection") + .initCause(e); } - - // Destroy keypair - try { - mKeyPair.destroy(); - } catch (DestroyFailedException ignore) { + if (mConnectionThread.isAlive()) { + throw new IOException("ADB connection thread did not stop within " + + CLOSE_TIMEOUT_MS + " ms"); } + + // The connection manager owns the keypair. A disconnect is routine + // during reconnect and must not destroy the key reused by the next + // connection. } - void sendPacket(byte[] packet) throws IOException { + @Override + public void sendPacket(byte[] packet) throws IOException { synchronized (mLock) { OutputStream os = getOutputStream(); os.write(packet); @@ -631,67 +500,24 @@ public class AdbConnection implements Closeable { } } - void flushPacket() throws IOException { + @Override + public void flushPacket() throws IOException { synchronized (mLock) { getOutputStream().flush(); } } public static class Builder { - private String mHost = "127.0.0.1"; - private int mPort = 5555; - private int mApi = Build.VERSION_CODES.BASE; + private final String mHost; + private final int mPort; private PrivateKey mPrivateKey; private Certificate mCertificate; - private KeyPair mKeyPair; - private String mDeviceName; - - public Builder() { - } public Builder(String host, int port) { mHost = host; mPort = port; } - /** - * Set host address. Default is 127.0.0.1 - */ - public Builder setHost(String host) { - this.mHost = host; - return this; - } - - /** - * Set port number. Default is 5555. - */ - public Builder setPort(int port) { - this.mPort = port; - return this; - } - - /** - * Set a name for the device. Default is “Unknown Device”. - * - * @param deviceName Name of the device, could be the app label, hostname or user@hostname. - */ - public Builder setDeviceName(String deviceName) { - this.mDeviceName = deviceName; - return this; - } - - /** - * Set Android API (i.e. SDK) version for this connection. If the ADB daemon and the client are located in the - * same device, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as - * security. - * - * @param api The API version, default is {@link Build.VERSION_CODES#BASE}. - */ - public Builder setApi(int api) { - this.mApi = api; - return this; - } - /** * Set generated/stored private key. */ @@ -708,69 +534,19 @@ public class AdbConnection implements Closeable { return this; } - Builder setKeyPair(KeyPair keyPair) { - this.mKeyPair = keyPair; - return this; - } - /** * Creates a new {@link AdbConnection} associated with the socket and crypto object specified. * * @throws IOException If there was an error while establishing a socket connection */ public AdbConnection build() throws IOException { - if (mKeyPair == null) { - if (mPrivateKey == null || mCertificate == null) { - throw new UnsupportedOperationException("Private key and certificate must be set."); - } - mKeyPair = new KeyPair(mPrivateKey, mCertificate); - } - AdbConnection adbConnection = create(mHost, mPort, mKeyPair, mApi); - if (mDeviceName != null) { - adbConnection.setDeviceName(mDeviceName); - } - return adbConnection; - } - - /** - * Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication - * attempt fails. - * - * @return The underlying {@link AdbConnection} - * @throws IOException If the socket fails while connecting - * @throws InterruptedException If timeout has reached - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - public AdbConnection connect() throws IOException, InterruptedException, AdbPairingRequiredException { - AdbConnection adbConnection = build(); - if (adbConnection.connect()) { - throw new IOException("Unable to establish a new connection."); - } - return adbConnection; - } - - /** - * Connects to the remote device. This routine will block until the connection completes or the timeout elapses. - * - * @param timeout the time to wait for the lock - * @param unit the time unit of the timeout argument - * @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException} - * if the peer rejects out first authentication attempt - * @return {@code true} if the connection was established, or {@code false} if the connection timed out - * @throws IOException If the socket fails while connecting - * @throws InterruptedException If timeout has reached - * @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects - * the first authentication attempt, which indicates that the peer has - * not saved the public key from a previous connection - * @throws AdbPairingRequiredException If ADB lacks pairing - */ - public AdbConnection connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised) - throws IOException, InterruptedException, AdbPairingRequiredException { - AdbConnection adbConnection = build(); - if (adbConnection.connect(timeout, unit, throwOnUnauthorised)) { - throw new IOException("Unable to establish a new connection."); + if (mHost == null || mHost.isEmpty()) throw new IllegalArgumentException("host is empty"); + if (mPort < 1 || mPort > 65535) throw new IllegalArgumentException("port is invalid"); + if (mPrivateKey == null || mCertificate == null) { + throw new UnsupportedOperationException("Private key and certificate must be set."); } - return adbConnection; + return new AdbConnection(mHost, mPort, + new KeyPair(mPrivateKey, mCertificate)); } } } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java index 6d0676f..c609e98 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbInputStream.java @@ -6,7 +6,7 @@ import java.io.IOException; import java.io.InputStream; public class AdbInputStream extends InputStream { - public AdbStream mAdbStream; + private final AdbStream mAdbStream; public AdbInputStream(AdbStream adbStream) { this.mAdbStream = adbStream; @@ -18,7 +18,7 @@ public class AdbInputStream extends InputStream { if (read(bytes) == -1) { return -1; } - return bytes[0]; + return bytes[0] & 0xff; } @Override @@ -28,7 +28,6 @@ public class AdbInputStream extends InputStream { @Override public int read(byte[] b, int off, int len) throws IOException { - if (mAdbStream.isClosed()) return -1; return mAdbStream.read(b, off, len); } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java deleted file mode 100644 index f324cb0..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbPairingRequiredException.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.muntashirakon.adb; - -public class AdbPairingRequiredException extends Exception { - public AdbPairingRequiredException(String message) { - super(message); - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java index 4c58206..79de3b5 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbProtocol.java @@ -15,6 +15,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; /** @@ -27,13 +28,6 @@ final class AdbProtocol { */ public static final int ADB_HEADER_LENGTH = 24; - /** - * SYNC(online, sequence, "") - * - * @deprecated Obsolete, no longer used. Never used on the client side. - */ - public static final int A_SYNC = 0x434e5953; - /** * CNXN is the connect message. No messages (except AUTH) are valid before this message is received. */ @@ -42,7 +36,8 @@ final class AdbProtocol { /** * The payload sent with the CONNECT message. */ - public static final byte[] SYSTEM_IDENTITY_STRING_HOST = StringCompat.getBytes("host::\0", "UTF-8"); + public static final byte[] SYSTEM_IDENTITY_STRING_HOST = + "host::\0".getBytes(StandardCharsets.UTF_8); /** * AUTH is the authentication message. It is part of the RSA public key authentication added in Android 4.2.2 @@ -76,7 +71,7 @@ final class AdbProtocol { public static final int A_STLS = 0x534c5453; @Retention(RetentionPolicy.SOURCE) - @IntDef({A_SYNC, A_CNXN, A_OPEN, A_OKAY, A_CLSE, A_WRTE, A_AUTH, A_STLS}) + @IntDef({A_CNXN, A_OPEN, A_OKAY, A_CLSE, A_WRTE, A_AUTH, A_STLS}) private @interface Command { } @@ -92,11 +87,6 @@ final class AdbProtocol { * Supported payload size since Android 9 (P) */ public static final int MAX_PAYLOAD_V3 = 1024 * 1024; - /** - * Maximum supported payload size is set to the original to support all APIs - */ - public static final int MAX_PAYLOAD = MAX_PAYLOAD_V1; - /** * The original version of the ADB protocol */ @@ -105,34 +95,12 @@ final class AdbProtocol { * The new version of the ADB protocol introduced in Android 9 (P) with the introduction of TLS */ public static final int A_VERSION_SKIP_CHECKSUM = 0x01000001; - public static final int A_VERSION = A_VERSION_MIN; - /** * The current version of the Stream-based TLS */ public static final int A_STLS_VERSION_MIN = 0x01000000; public static final int A_STLS_VERSION = A_STLS_VERSION_MIN; - /** - * This authentication type represents a SHA1 hash to sign. - */ - public static final int ADB_AUTH_TOKEN = 1; - - /** - * This authentication type represents the signed SHA1 hash. - */ - public static final int ADB_AUTH_SIGNATURE = 2; - - /** - * This authentication type represents an RSA public key. - */ - public static final int ADB_AUTH_RSAPUBLICKEY = 3; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ADB_AUTH_TOKEN, ADB_AUTH_SIGNATURE, ADB_AUTH_RSAPUBLICKEY}) - private @interface AuthType { - } - public static int getMaxData(int api) { if (api >= Build.VERSION_CODES.P) { return MAX_PAYLOAD_V3; @@ -202,8 +170,14 @@ final class AdbProtocol { * @return Byte array containing the message */ @NonNull - public static byte[] generateMessage(@Command int command, int arg0, int arg1, @Nullable byte[] data, int offset, int length) { - // Protocol as defined at https://github.com/aosp-mirror/platform_system_core/blob/6072de17cd812daf238092695f26a552d3122f8c/adb/protocol.txt + public static byte[] generateMessage(@Command int command, int arg0, int arg1, + @Nullable byte[] data, int offset, int length) { + if (length < 0 || offset < 0 || data == null && (offset != 0 || length != 0) + || data != null && (offset > data.length || length > data.length - offset)) { + throw new IndexOutOfBoundsException( + "invalid payload range: offset=" + offset + " length=" + length); + } + // Protocol: AOSP platform_system_core commit 6072de17, adb/protocol.txt. // struct message { // unsigned command; // command identifier constant // unsigned arg0; // first argument @@ -255,20 +229,6 @@ final class AdbProtocol { return generateMessage(A_CNXN, getProtocolVersion(api), getMaxData(api), SYSTEM_IDENTITY_STRING_HOST); } - /** - * Generates an AUTH message with the specified type and payload. - *

- * AUTH(type, 0, "data") - * - * @param type Authentication type (see ADB_AUTH_* constants) - * @param data The data - * @return Byte array containing the message - */ - @NonNull - public static byte[] generateAuth(@AuthType int type, byte[] data) { - return generateMessage(A_AUTH, type, 0, data); - } - /** * Generates an STLS message with default parameters. *

@@ -292,8 +252,9 @@ final class AdbProtocol { */ @NonNull public static byte[] generateOpen(int localId, @NonNull String destination) { - ByteBuffer bbuf = ByteBuffer.allocate(destination.length() + 1); - bbuf.put(StringCompat.getBytes(destination, "UTF-8")); + byte[] encoded = destination.getBytes(StandardCharsets.UTF_8); + ByteBuffer bbuf = ByteBuffer.allocate(encoded.length + 1); + bbuf.put(encoded); bbuf.put((byte) 0); return generateMessage(A_OPEN, localId, 0, bbuf.array()); } @@ -389,16 +350,13 @@ final class AdbProtocol { */ @NonNull public static Message parse(@NonNull InputStream in, int protocolVersion, int maxData) throws IOException { + if (maxData <= 0 || maxData > MAX_PAYLOAD_V3) { + throw new IllegalArgumentException("invalid maximum ADB payload: " + maxData); + } ByteBuffer header = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); // Read header - int dataRead = 0; - do { - int bytesRead = in.read(header.array(), dataRead, ADB_HEADER_LENGTH - dataRead); - if (bytesRead < 0) { - throw new IOException("Stream closed"); - } else dataRead += bytesRead; - } while (dataRead < ADB_HEADER_LENGTH); + readFully(in, header.array(), ADB_HEADER_LENGTH); Message msg = new Message(header); @@ -406,13 +364,19 @@ final class AdbProtocol { if (msg.command != (~msg.magic)) { // magic = cmd ^ 0xFFFFFFFF throw new StreamCorruptedException(String.format("Invalid header: Invalid magic 0x%x.", msg.magic)); } - if (msg.command != A_SYNC && msg.command != A_CNXN && msg.command != A_OPEN && msg.command != A_OKAY + if (msg.command != A_CNXN && msg.command != A_OPEN && msg.command != A_OKAY && msg.command != A_CLSE && msg.command != A_WRTE && msg.command != A_AUTH && msg.command != A_STLS) { throw new StreamCorruptedException(String.format("Invalid header: Invalid command 0x%x.", msg.command)); } if (msg.dataLength < 0 || msg.dataLength > maxData) { - throw new StreamCorruptedException(String.format("Invalid header: Invalid data length %d", msg.dataLength)); + throw new StreamCorruptedException( + String.format("Invalid header: Invalid data length %d", msg.dataLength)); + } + if (msg.dataLength != 0 && (msg.command == A_OKAY + || msg.command == A_CLSE || msg.command == A_STLS)) { + throw new StreamCorruptedException( + String.format("Invalid header: Command 0x%x has a payload", msg.command)); } if (msg.dataLength == 0) { @@ -422,13 +386,7 @@ final class AdbProtocol { // Read payload msg.payload = new byte[msg.dataLength]; - dataRead = 0; - do { - int bytesRead = in.read(msg.payload, dataRead, msg.dataLength - dataRead); - if (bytesRead < 0) { - throw new IOException("Stream closed"); - } else dataRead += bytesRead; - } while (dataRead < msg.dataLength); + readFully(in, msg.payload, msg.dataLength); // Verify payload if ((protocolVersion <= A_VERSION_MIN || (msg.command == A_CNXN && msg.arg0 <= A_VERSION_MIN)) @@ -440,6 +398,19 @@ final class AdbProtocol { return msg; } + // InputStream permits unusual implementations to return zero even + // when len is non-zero. Treat that as a broken transport instead of + // spinning forever on an attacker-controlled connection thread. + private static void readFully(InputStream in, byte[] data, int length) throws IOException { + int offset = 0; + while (offset < length) { + int count = in.read(data, offset, length - offset); + if (count < 0) throw new IOException("Stream closed"); + if (count == 0) throw new IOException("ADB input made no progress"); + offset += count; + } + } + private Message(@NonNull ByteBuffer header) { command = header.getInt(); arg0 = header.getInt(); @@ -454,9 +425,6 @@ final class AdbProtocol { public String toString() { String tag; switch (command) { - case A_SYNC: - tag = "SYNC"; - break; case A_CNXN: tag = "CNXN"; break; diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java index 62754b0..1700831 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AdbStream.java @@ -4,6 +4,7 @@ package io.github.muntashirakon.adb; import java.io.Closeable; import java.io.IOException; +import java.io.StreamCorruptedException; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -15,10 +16,18 @@ import java.util.concurrent.atomic.AtomicBoolean; // Copyright 2013 Cameron Gutman public class AdbStream implements Closeable { + // Small transport seam around the connection. It keeps this stream state + // machine testable without a socket or Android runtime. + interface Transport { + int getMaxData(); + void sendPacket(byte[] packet) throws IOException; + void flushPacket() throws IOException; + } + /** * The AdbConnection object that the stream communicates over */ - private final AdbConnection mAdbConnection; + private final Transport mTransport; /** * The local ID of the stream @@ -55,6 +64,9 @@ public class AdbStream implements Closeable { */ private volatile boolean mPendingClose; + /** One WRTE has been received and has not yet been fully consumed. */ + private boolean mReadPacketPending; + /** * Creates a new AdbStream object on the specified AdbConnection * with the given local ID. @@ -62,12 +74,11 @@ public class AdbStream implements Closeable { * @param adbConnection AdbConnection that this stream is running on * @param localId Local ID of the stream */ - AdbStream(AdbConnection adbConnection, int localId) - throws IOException, InterruptedException, AdbPairingRequiredException { - this.mAdbConnection = adbConnection; + AdbStream(Transport transport, int localId) { + this.mTransport = transport; this.mLocalId = localId; this.mReadQueue = new ConcurrentLinkedQueue<>(); - this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(adbConnection.getMaxData()).flip(); + this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(transport.getMaxData()).flip(); this.mWriteReady = new AtomicBoolean(false); this.mIsClosed = false; } @@ -85,8 +96,13 @@ public class AdbStream implements Closeable { * * @param payload Data inside the WRTE message */ - void addPayload(byte[] payload) { + void addPayload(byte[] payload) throws IOException { synchronized (mReadQueue) { + if (mReadPacketPending) { + throw new StreamCorruptedException( + "ADB peer sent WRTE before the previous packet was acknowledged"); + } + mReadPacketPending = true; mReadQueue.add(payload); mReadQueue.notifyAll(); } @@ -100,7 +116,7 @@ public class AdbStream implements Closeable { */ void sendReady() throws IOException { // Generate and send a OKAY packet - mAdbConnection.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId)); + mTransport.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId)); } /** @@ -109,14 +125,25 @@ public class AdbStream implements Closeable { * @param remoteId New remote ID */ void updateRemoteId(int remoteId) { + if (remoteId == 0) throw new IllegalArgumentException("remote stream ID is zero"); + if (mRemoteId != 0 && mRemoteId != remoteId) { + throw new IllegalStateException("remote stream ID changed"); + } this.mRemoteId = remoteId; } + boolean hasRemoteId(int remoteId) { + return mRemoteId != 0 && mRemoteId == remoteId; + } + /** * Called by the connection thread to indicate the stream is okay to send data. */ void readyForWrite() { - mWriteReady.set(true); + synchronized (this) { + mWriteReady.set(true); + notifyAll(); + } } boolean isOpen() { @@ -128,7 +155,7 @@ public class AdbStream implements Closeable { */ void notifyClose(boolean closedByPeer) { // We don't call close() because it sends another CLSE - if (closedByPeer && !mReadQueue.isEmpty()) { + if (closedByPeer && hasUnreadData()) { // The remote peer closed the stream, but we haven't finished reading the remaining data mPendingClose = true; } else { @@ -151,42 +178,51 @@ public class AdbStream implements Closeable { * @throws IOException If the stream fails while waiting */ public int read(byte[] bytes, int offset, int length) throws IOException { + if (bytes == null) throw new NullPointerException("bytes"); + if (offset < 0 || length < 0 || offset > bytes.length || length > bytes.length - offset) { + throw new IndexOutOfBoundsException(); + } + if (length == 0) return 0; if (mReadBuffer.hasRemaining()) { - return readBuffer(bytes, offset, length); + return readBufferAndAcknowledge(bytes, offset, length); } - // Buffer has no data, grab from the queue - synchronized (mReadQueue) { - byte[] data; - // Wait for the connection to close or data to be received - while ((data = mReadQueue.poll()) == null && !mIsClosed) { - try { - mReadQueue.wait(); - } catch (InterruptedException e) { - //noinspection UnnecessaryInitCause - throw (IOException) new IOException().initCause(e); + while (true) { + // Buffer has no data, grab from the queue + synchronized (mReadQueue) { + byte[] data; + // Wait for the connection to close or data to be received + while ((data = mReadQueue.poll()) == null && !mIsClosed) { + try { + mReadQueue.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw (IOException) new IOException("interrupted while reading ADB stream") + .initCause(e); + } } - } - // Add data to the buffer - if (data != null) { - mReadBuffer.clear(); - mReadBuffer.put(data); - mReadBuffer.flip(); - if (mReadBuffer.hasRemaining()) { - return readBuffer(bytes, offset, length); + + // Add data to the buffer + if (data != null) { + mReadBuffer.clear(); + mReadBuffer.put(data); + mReadBuffer.flip(); + if (mReadBuffer.hasRemaining()) { + return readBufferAndAcknowledge(bytes, offset, length); + } + // Empty WRTE packets are legal but carry no bytes. Ack + // and continue instead of returning a false EOF. + finishReadPacket(); } - } - if (mIsClosed) { - throw new IOException("Stream closed."); - } + if (mIsClosed) return -1; - if (mPendingClose && mReadQueue.isEmpty()) { - // The peer closed the stream, and we've finished reading the stream data, so this stream is finished - mIsClosed = true; + if (mPendingClose && !hasUnreadData()) { + mPendingClose = false; + mIsClosed = true; + return -1; + } } } - - return -1; } private int readBuffer(byte[] bytes, int offset, int length) { @@ -200,6 +236,32 @@ public class AdbStream implements Closeable { return count; } + private int readBufferAndAcknowledge(byte[] bytes, int offset, int length) throws IOException { + int count = readBuffer(bytes, offset, length); + if (!mReadBuffer.hasRemaining()) finishReadPacket(); + return count; + } + + private void finishReadPacket() throws IOException { + boolean closeAfterDrain; + synchronized (mReadQueue) { + mReadPacketPending = false; + closeAfterDrain = mPendingClose && mReadQueue.isEmpty(); + if (closeAfterDrain) { + mPendingClose = false; + mIsClosed = true; + } + } + // A peer that already sent CLSE neither needs nor expects OKAY. + if (!closeAfterDrain && !mIsClosed) sendReady(); + } + + private boolean hasUnreadData() { + synchronized (mReadQueue) { + return mReadPacketPending || mReadBuffer.hasRemaining() || !mReadQueue.isEmpty(); + } + } + /** * Sends a WRTE packet with a given byte array payload. It does not flush the stream. * @@ -207,44 +269,37 @@ public class AdbStream implements Closeable { * @throws IOException If the stream fails while sending data */ public void write(byte[] bytes, int offset, int length) throws IOException { + if (bytes == null) throw new NullPointerException("bytes"); + if (offset < 0 || length < 0 || offset > bytes.length || length > bytes.length - offset) { + throw new IndexOutOfBoundsException(); + } + if (length == 0) return; + // Split and send data as WRTE packet + int maxData = mTransport.getMaxData(); + while (length != 0) { + int count = Math.min(length, maxData); + sendWrite(bytes, offset, count); + offset += count; + length -= count; + } + } + + private void sendWrite(byte[] bytes, int offset, int count) throws IOException { synchronized (this) { - // Make sure we're ready for a WRTE while (!mIsClosed && !mWriteReady.compareAndSet(true, false)) { try { wait(); } catch (InterruptedException e) { - //noinspection UnnecessaryInitCause - throw (IOException) new IOException().initCause(e); + Thread.currentThread().interrupt(); + throw (IOException) new IOException("interrupted while writing ADB stream") + .initCause(e); } } - - if (mIsClosed) { - throw new IOException("Stream closed"); - } - } - // Split and send data as WRTE packet - // TODO: A WRITE message may not be sent until a READY message is received. - // Once a WRITE message is sent, an additional WRITE message may not be - // sent until another READY message has been received. Recipients of - // a WRITE message that is in violation of this requirement will CLOSE - // the connection. - int maxData; - try { - maxData = mAdbConnection.getMaxData(); - } catch (InterruptedException | AdbPairingRequiredException e) { - //noinspection UnnecessaryInitCause - throw (IOException) new IOException().initCause(e); - } - while (length != 0) { - if (length <= maxData) { - mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, length)); - offset = offset + length; - length = 0; - } else { // if (length > maxData) { - mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, maxData)); - offset = offset + maxData; - length = length - maxData; - } + if (mIsClosed) throw new IOException("Stream closed"); + // Keep consuming OKAY and sending WRTE atomic with close(). A + // concurrent close must never put WRTE on the wire after CLSE. + mTransport.sendPacket( + AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, count)); } } @@ -252,7 +307,7 @@ public class AdbStream implements Closeable { if (mIsClosed) { throw new IOException("Stream closed"); } - mAdbConnection.flushPacket(); + mTransport.flushPacket(); } /** @@ -271,7 +326,7 @@ public class AdbStream implements Closeable { notifyClose(false); } - mAdbConnection.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId)); + mTransport.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId)); } /** @@ -291,12 +346,10 @@ public class AdbStream implements Closeable { */ public int available() throws IOException { synchronized (this) { - if (mIsClosed) { - throw new IOException("Stream closed."); - } if (mReadBuffer.hasRemaining()) { return mReadBuffer.remaining(); } + if (mIsClosed) return 0; byte[] data = mReadQueue.peek(); return data == null ? 0 : data.length; } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java index 6aa12aa..91227b3 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/AndroidPubkey.java @@ -3,25 +3,18 @@ package io.github.muntashirakon.adb; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import org.bouncycastle.util.encoders.Base64; +import java.io.ByteArrayOutputStream; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.GeneralSecurityException; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPublicKeySpec; -import java.util.Objects; - -import javax.crypto.Cipher; +import java.util.Locale; final class AndroidPubkey { /** @@ -39,61 +32,6 @@ final class AndroidPubkey { */ public static final int ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE / 4; - /** - * The RSA signature padding as an int array. - */ - private static final int[] SIGNATURE_PADDING_AS_INT = new int[]{ - 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, - 0x04, 0x14 - }; - - /** - * The RSA signature padding as a byte array - */ - private static final byte[] RSA_SHA_PKCS1_SIGNATURE_PADDING; - - static { - RSA_SHA_PKCS1_SIGNATURE_PADDING = new byte[SIGNATURE_PADDING_AS_INT.length]; - - for (int i = 0; i < RSA_SHA_PKCS1_SIGNATURE_PADDING.length; i++) - RSA_SHA_PKCS1_SIGNATURE_PADDING[i] = (byte) SIGNATURE_PADDING_AS_INT[i]; - } - - /** - * Signs the ADB SHA1 payload with the private key of this object. - * - * @param privateKey Private key to sign with - * @param payload SHA1 payload to sign - * @return Signed SHA1 payload - * @throws GeneralSecurityException If signing fails - */ - // Taken from adb_auth_sign - @NonNull - public static byte[] adbAuthSign(@NonNull PrivateKey privateKey, byte[] payload) - throws GeneralSecurityException { - Cipher c = Cipher.getInstance("RSA/ECB/NoPadding"); - c.init(Cipher.ENCRYPT_MODE, privateKey); - c.update(RSA_SHA_PKCS1_SIGNATURE_PADDING); - return c.doFinal(payload); - } - /** * Converts a standard RSAPublicKey object to the special ADB format. Available since 4.2.2. * @@ -105,21 +43,24 @@ final class AndroidPubkey { public static byte[] encodeWithName(@NonNull RSAPublicKey publicKey, @NonNull String name) throws InvalidKeyException { int pkeySize = 4 * (int) Math.ceil(ANDROID_PUBKEY_ENCODED_SIZE / 3.0); - try (ByteArrayNoThrowOutputStream bos = new ByteArrayNoThrowOutputStream(pkeySize + name.length() + 2)) { - bos.write(Base64.encode(encode(publicKey))); - bos.write(getUserInfo(name)); - return bos.toByteArray(); - } + ByteArrayOutputStream out = new ByteArrayOutputStream(pkeySize + name.length() + 2); + byte[] encoded = Base64.encode(encode(publicKey)); + out.write(encoded, 0, encoded.length); + byte[] userInfo = getUserInfo(name); + out.write(userInfo, 0, userInfo.length); + return out.toByteArray(); } // Taken from get_user_info except that a custom name is used instead of host@user @VisibleForTesting @NonNull static byte[] getUserInfo(@NonNull String name) { - return StringCompat.getBytes(String.format(" %s\u0000", name), "UTF-8"); + return String.format(Locale.ROOT, " %s\u0000", name) + .getBytes(StandardCharsets.UTF_8); } - // https://android.googlesource.com/platform/system/core/+/e797a5c75afc17024d0f0f488c130128fcd704e2/libcrypto_utils/android_pubkey.cpp + // AOSP platform/system/core commit e797a5c7, + // libcrypto_utils/android_pubkey.cpp: // typedef struct RSAPublicKey { // uint32_t modulus_size_words; // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE. // uint32_t n0inv; // Precomputed montgomery parameter: -1 / n[0] mod 2^32 @@ -128,46 +69,6 @@ final class AndroidPubkey { // uint32_t exponent; // RSA modulus: 3 or 65537 // } RSAPublicKey; - /** - * Allocates a new {@link RSAPublicKey} object, decodes a public RSA key stored in Android's custom binary format, - * and sets the key parameters. The resulting key can be used with the standard Java cryptography API to perform - * public operations. - * - * @param androidPubkey Public RSA key in Android's custom binary format. The size of the key must be at least - * {@link #ANDROID_PUBKEY_ENCODED_SIZE} - * @return {@link RSAPublicKey} object - */ - @NonNull - public static RSAPublicKey decode(@NonNull byte[] androidPubkey) - throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException { - BigInteger n; - BigInteger e; - - // Check size is large enough and the modulus size is correct. - if (androidPubkey.length < ANDROID_PUBKEY_ENCODED_SIZE) { - throw new InvalidKeyException("Invalid key length"); - } - ByteBuffer keyStruct = ByteBuffer.wrap(androidPubkey).order(ByteOrder.LITTLE_ENDIAN); - int modulusSize = keyStruct.getInt(); - if (modulusSize != ANDROID_PUBKEY_MODULUS_SIZE_WORDS) { - throw new InvalidKeyException("Invalid modulus length."); - } - - // Convert the modulus to big-endian byte order as expected by BN_bin2bn. - byte[] modulus = new byte[ANDROID_PUBKEY_MODULUS_SIZE]; - keyStruct.position(8); - keyStruct.get(modulus); - n = new BigInteger(1, swapEndianness(modulus)); - - // Read the exponent. - keyStruct.position(520); - e = BigInteger.valueOf(keyStruct.getInt()); - - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); - return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec); - } - /** * Encodes the given key in the Android RSA public key binary format. * @@ -180,8 +81,13 @@ final class AndroidPubkey { BigInteger n0inv; BigInteger rr; - if (publicKey.getModulus().toByteArray().length < ANDROID_PUBKEY_MODULUS_SIZE) { - throw new InvalidKeyException("Invalid key length " + publicKey.getModulus().toByteArray().length); + if (publicKey.getModulus().bitLength() != ANDROID_PUBKEY_MODULUS_SIZE * 8) { + throw new InvalidKeyException("ADB requires an RSA-2048 key"); + } + BigInteger exponent = publicKey.getPublicExponent(); + if (!BigInteger.valueOf(3).equals(exponent) + && !BigInteger.valueOf(65537).equals(exponent)) { + throw new InvalidKeyException("ADB RSA exponent must be 3 or 65537"); } ByteBuffer keyStruct = ByteBuffer.allocate(ANDROID_PUBKEY_ENCODED_SIZE).order(ByteOrder.LITTLE_ENDIAN); @@ -196,12 +102,13 @@ final class AndroidPubkey { keyStruct.putInt(n0inv.intValue()); // n0inv // Store the modulus. - keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, publicKey.getModulus()))); + keyStruct.put(bigEndianToLittleEndianPadded( + ANDROID_PUBKEY_MODULUS_SIZE, publicKey.getModulus())); // Compute and store rr = (2^(rsa_size)) ^ 2 mod N. rr = BigInteger.ZERO.setBit(ANDROID_PUBKEY_MODULUS_SIZE * 8); // rr = 2^(rsa_size) rr = rr.modPow(BigInteger.valueOf(2), publicKey.getModulus()); // rr = rr^2 mod N - keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, rr))); + keyStruct.put(bigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, rr)); // Store the exponent. keyStruct.putInt(publicKey.getPublicExponent().intValue()); // exponent @@ -209,24 +116,24 @@ final class AndroidPubkey { return keyStruct.array(); } - @Nullable - private static byte[] BigEndianToLittleEndianPadded(int len, @NonNull BigInteger in) { + private static byte[] bigEndianToLittleEndianPadded(int len, @NonNull BigInteger in) + throws InvalidKeyException { byte[] out = new byte[len]; byte[] bytes = swapEndianness(in.toByteArray()); // Convert big endian -> little endian - int num_bytes = bytes.length; - if (len < num_bytes) { - if (!fitsInBytes(bytes, num_bytes, len)) { - return null; + int numBytes = bytes.length; + if (len < numBytes) { + if (!fitsInBytes(bytes, numBytes, len)) { + throw new InvalidKeyException("RSA value does not fit ADB key structure"); } - num_bytes = len; + numBytes = len; } - System.arraycopy(bytes, 0, out, 0, num_bytes); + System.arraycopy(bytes, 0, out, 0, numBytes); return out; } - static boolean fitsInBytes(@NonNull byte[] bytes, int num_bytes, int len) { + static boolean fitsInBytes(@NonNull byte[] bytes, int numBytes, int len) { byte mask = 0; - for (int i = len; i < num_bytes; i++) { + for (int i = len; i < numBytes; i++) { mask |= bytes[i]; } return mask == 0; diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java deleted file mode 100644 index 55cc1bf..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/ByteArrayNoThrowOutputStream.java +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb; - -import java.io.ByteArrayOutputStream; - -class ByteArrayNoThrowOutputStream extends ByteArrayOutputStream { - public ByteArrayNoThrowOutputStream() { - super(); - } - - public ByteArrayNoThrowOutputStream(int size) { - super(size); - } - - @Override - public void write(byte[] b) { - write(b, 0, b.length); - } - - @Override - public void close() { - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java index 4574d8b..6323934 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/KeyPair.java @@ -6,8 +6,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; -import javax.security.auth.DestroyFailedException; - final class KeyPair { private final PrivateKey mPrivateKey; private final Certificate mCertificate; @@ -28,11 +26,4 @@ final class KeyPair { public Certificate getCertificate() { return mCertificate; } - - public void destroy() throws DestroyFailedException { - try { - mPrivateKey.destroy(); - } catch (NoSuchMethodError ignore) { - } - } } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java deleted file mode 100644 index 523bce3..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/LocalServices.java +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb; - -import android.text.TextUtils; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - - -/** - * Local services extracted from the ADB client - * for easy access. - */ -public class LocalServices { - static final int SERVICE_FIRST = 1; - - public static final int SHELL = 1; - /** - * Remount the device's filesystem in read-write mode, instead of read-only. This is usually necessary before - * performing an {@link #SYNC} request. This request may not succeed on certain builds which do not allow that. - *

- * This essentially executes {@code /system/bin/remount} command. Additional arguments such as {@code -R} can be - * passed too. - */ - public static final int REMOUNT = 2; - public static final int FILE = 3; - public static final int TCP_CONNECT = 4; - public static final int LOCAL_UNIX_SOCKET = 5; - public static final int LOCAL_UNIX_SOCKET_RESERVED = 6; - public static final int LOCAL_UNIX_SOCKET_ABSTRACT = 7; - public static final int LOCAL_UNIX_SOCKET_FILE_SYSTEM = 8; - /** - * Receive snapshots of the framebuffer. It requires sufficient privileges (or the connection is closed immediately) - * but works as follows: - *

- * After an {@link AdbStream} is opened, ADB daemon sends a 16-byte binary structure containing the following fields - * (little-endian format): - *

-     * uint32_t depth;     // framebuffer depth = 16
-     * uint32_t size;      // framebuffer size in bytes = 2 * width * height
-     * uint32_t width;     // framebuffer width in pixels
-     * uint32_t height;    // framebuffer height in pixels
-     * 
- * After that, each time a snapshot is wanted, one byte should be sent through the channel, which will trigger the - * daemon to send {@code size} bytes of framebuffer data. - */ - public static final int FRAMEBUFFER = 9; - /** - * Connects to the JDWP thread running in the VM of process PID (specified as an argument). - */ - public static final int CONNECT_JDWP = 10; - /** - * Receive the list of JDWP PIDs periodically. The format of the returned data is the following (in order): - *
    - *
  1. {@code hex4}: The length of all content as a 4-char hexadecimal string i.e. {@code %04zx}. - *
  2. {@code content}: A series of ASCII lines of the following format: - *
    -     *  <pid> "\n"
    -     *  
    - *
- * This service is used by DDMS to know which debuggable processes are running on the device/emulator. - *

- * Note that there is no single-shot service to retrieve the list only once. - */ - public static final int TRACK_JDWP = 11; - public static final int SYNC = 12; - /** - * Reverse socket connections from the device running ADB daemon to this client. This should not be used if both - * the ADB daemon and the client are in the same device. - *

- * It takes an additional argument called {@code forward-command}. It can be one of the following: - *

    - *
  • {@code list-forward}: List all forwarded connections from the device - * This returns something that looks like the following: - *
      - *
    1. {@code hex4}: The length of the payload, as 4 hexadecimal chars i.e. {@code %04zx}. - *
    2. {@code payload}: A series of lines of the following format: - *
      -     *     host " " <local> " " <remote> "\n"
      -     *     
      - * Where <local> is the device-specific endpoint (e.g. {@code tcp:9000}), and <remote> is the - * client-specific endpoint. - *
    - *
  • forward:; - *
  • forward:norebind:; - *
  • killforward-all - *
  • killforward: - *
- */ - public static final int REVERSE = 13; - /** - * Backup some or all packages installed in the device. For this to work, {@code allowBackup=true} must be present - * in the application section of the AndroidManifest.xml of the app. - *

- * It takes additional arguments which can be one of the following: - *

    - *
  • List of packages (as array) - *
  • {@code -all} - *
  • {@code -shared} - *
- * Output is a stream which is in zlib format with 24 bytes at the front (if unencrypted). - */ - public static final int BACKUP = 14; - /** - * Restore a backup. Input is a stream which is in zlib format with 24 bytes at the front (if unencrypted). - */ - public static final int RESTORE = 15; - - static final int SERVICE_LAST = 15; - - @IntDef({ - SHELL, - REMOUNT, - FILE, - TCP_CONNECT, - LOCAL_UNIX_SOCKET, - LOCAL_UNIX_SOCKET_RESERVED, - LOCAL_UNIX_SOCKET_ABSTRACT, - LOCAL_UNIX_SOCKET_FILE_SYSTEM, - FRAMEBUFFER, - CONNECT_JDWP, - TRACK_JDWP, - SYNC, - REVERSE, - BACKUP, - RESTORE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Services { - } - - @NonNull - static String getServiceName(@Services int service) { - switch (service) { - case SHELL: - return "shell:"; - case CONNECT_JDWP: - return "jdwp:"; - case FILE: - return "dev:"; - case FRAMEBUFFER: - return "framebuffer:"; - case LOCAL_UNIX_SOCKET: - return "local:"; - case LOCAL_UNIX_SOCKET_ABSTRACT: - return "localabstract:"; - case LOCAL_UNIX_SOCKET_FILE_SYSTEM: - return "localfilesystem:"; - case LOCAL_UNIX_SOCKET_RESERVED: - return "localreserved:"; - case REMOUNT: - return "remount:"; - case REVERSE: - return "reverse:"; - case SYNC: - return "sync:"; - case TCP_CONNECT: - return "tcp:"; - case TRACK_JDWP: - return "track-jdwp"; - case BACKUP: - return "backup:"; - case RESTORE: - return "restore:"; - default: - throw new IllegalArgumentException("Invalid service: " + service); - } - } - - @NonNull - static String getDestination(@Services int service, @NonNull String... args) { - String serviceName = getServiceName(service); - StringBuilder destination = new StringBuilder(serviceName); - switch (service) { - case SHELL: - for (String arg : args) { - if (arg.contains("\"")) { - throw new IllegalArgumentException("Arguments for inline shell cannot contain double" + - " quotations."); - } - if (arg.contains(" ")) { - destination.append("\"").append(Objects.requireNonNull(arg)).append("\""); - } else destination.append(Objects.requireNonNull(arg)); - } - break; - case FILE: - if (args.length == 0) { - throw new IllegalArgumentException("File name must be specified."); - } else if (args.length != 1) { - throw new IllegalArgumentException("Service expects exactly one argument, " + args.length - + " supplied."); - } - destination.append(Objects.requireNonNull(args[0])); - break; - case TCP_CONNECT: - if (args.length == 0) { - throw new IllegalArgumentException("Port number must be specified."); - } else if (args.length == 1) { - destination.append(args[0]); - } else if (args.length == 2) { - destination.append(Objects.requireNonNull(args[0])) - .append(':') - .append(Objects.requireNonNull(args[1])); - } else { - throw new IllegalArgumentException("Invalid number of arguments supplied."); - } - break; - case LOCAL_UNIX_SOCKET: - case LOCAL_UNIX_SOCKET_ABSTRACT: - case LOCAL_UNIX_SOCKET_FILE_SYSTEM: - case LOCAL_UNIX_SOCKET_RESERVED: - if (args.length == 0) { - throw new IllegalArgumentException("Path must be specified."); - } else if (args.length != 1) { - throw new IllegalArgumentException("Service expects exactly one argument, " + args.length - + " supplied."); - } - destination.append(Objects.requireNonNull(args[0])); - break; - case CONNECT_JDWP: - if (args.length == 0) { - throw new IllegalArgumentException("PID must be specified."); - } else if (args.length != 1) { - throw new IllegalArgumentException("Service expects exactly one argument, " + args.length - + " supplied."); - } - destination.append(Objects.requireNonNull(args[0])); - break; - case REVERSE: - if (args.length == 0) { - throw new IllegalArgumentException("Forward command must be specified."); - } else if (args.length != 1) { - throw new IllegalArgumentException("Service expects exactly one argument, " + args.length - + " supplied."); - } - if (args[0] == null) { - throw new IllegalArgumentException("Forward command is empty"); - } - if ("list-forward".equals(args[0]) || "killforward-all".equals(args[0])) { - destination.append(args[0]); - } else if (args[0].startsWith("forward:") || args[0].startsWith("killforward:")) { - destination.append(args[0]); - } else { - throw new IllegalArgumentException("Invalid forward command."); - } - break; - case BACKUP: - if (args.length == 0) { - throw new IllegalArgumentException("At least one package must be specified or use -shared/-all."); - } - case REMOUNT: - // Additional arguments for the commands - destination.append(TextUtils.join(" ", args)); - break; - case RESTORE: - case FRAMEBUFFER: - case SYNC: - case TRACK_JDWP: - if (args.length != 0) { - throw new IllegalArgumentException("Service expects no arguments."); - } - break; - } - return destination.toString(); - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java deleted file mode 100644 index fe6343f..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PRNGFixes.java +++ /dev/null @@ -1,319 +0,0 @@ -// SPDX-License-Identifier: MIT AND (GPL-3.0-or-later OR Apache-2.0) - -package io.github.muntashirakon.adb; - -import android.os.Build; -import android.os.Process; -import android.util.Log; - -import androidx.annotation.GuardedBy; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.SecureRandomSpi; -import java.security.Security; - -/** - * Fixes for the output of the default PRNG having low entropy. - *

- * The fixes need to be applied via {@link #apply()} before any use of Java - * Cryptography Architecture primitives. A good place to invoke them is in the - * application's {@code onCreate}. - */ -// Copyright 2013 Google Inc. -public final class PRNGFixes { - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); - - /** - * Hidden constructor to prevent instantiation. - */ - private PRNGFixes() { - } - - /** - * Applies all fixes. - * - * @throws SecurityException if a fix is needed but could not be applied. - */ - public static void apply() { - applyOpenSSLFix(); - installLinuxPRNGSecureRandom(); - } - - /** - * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the - * fix is not needed. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) - || (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } - - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); - - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } - - /** - * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the - * default. Does nothing if the implementation is already the default or if - * there is not need to install the implementation. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void installLinuxPRNGSecureRandom() - throws SecurityException { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - // No need to apply the fix - return; - } - - // Install a Linux PRNG-based SecureRandom implementation as the - // default, if not yet installed. - Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); - if ((secureRandomProviders == null) - || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) { - Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); - } - - // Assert that new SecureRandom() and - // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed - // by the Linux PRNG-based SecureRandom implementation. - SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) { - throw new SecurityException("new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); - } - - SecureRandom rng2; - try { - rng2 = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("SHA1PRNG not available", e); - } - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng2.getProvider().getClass())) { - throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong provider: " - + rng2.getProvider().getClass()); - } - } - - /** - * {@code Provider} of {@code SecureRandom} engines which pass through - * all requests to the Linux PRNG. - */ - private static class LinuxPRNGSecureRandomProvider extends Provider { - - public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses /dev/urandom"); - // Although /dev/urandom is not a SHA-1 PRNG, some apps - // explicitly request a SHA1PRNG SecureRandom and we thus need to - // prevent them from getting the default implementation whose output - // may have low entropy. - put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); - put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - } - } - - /** - * {@link SecureRandomSpi} which passes all requests to the Linux PRNG - * ({@code /dev/urandom}). - */ - public static class LinuxPRNGSecureRandom extends SecureRandomSpi { - - /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed - * are passed through to the Linux PRNG (/dev/urandom). Instances of - * this class seed themselves by mixing in the current time, PID, UID, - * build fingerprint, and hardware serial number (where available) into - * Linux PRNG. - * - * Concurrency: Read requests to the underlying Linux PRNG are - * serialized (on sLock) to ensure that multiple threads do not get - * duplicated PRNG output. - */ - - private static final File URANDOM_FILE = new File("/dev/urandom"); - - private static final Object sLock = new Object(); - - /** - * Input stream for reading from Linux PRNG or {@code null} if not yet - * opened. - */ - @GuardedBy("sLock") - private static DataInputStream sUrandomIn; - - /** - * Output stream for writing to Linux PRNG or {@code null} if not yet - * opened. - */ - @GuardedBy("sLock") - private static OutputStream sUrandomOut; - - /** - * Whether this engine instance has been seeded. This is needed because - * each instance needs to seed itself if the client does not explicitly - * seed it. - */ - private boolean mSeeded; - - @Override - protected void engineSetSeed(byte[] bytes) { - try { - OutputStream out; - synchronized (sLock) { - out = getUrandomOutputStream(); - } - out.write(bytes); - out.flush(); - } catch (IOException e) { - // On a small fraction of devices /dev/urandom is not writable. - // Log and ignore. - Log.w(PRNGFixes.class.getSimpleName(), - "Failed to mix seed into " + URANDOM_FILE); - } finally { - mSeeded = true; - } - } - - @Override - protected void engineNextBytes(byte[] bytes) { - if (!mSeeded) { - // Mix in the device- and invocation-specific seed. - engineSetSeed(generateSeed()); - } - - try { - DataInputStream in; - synchronized (sLock) { - in = getUrandomInputStream(); - } - synchronized (in) { - in.readFully(bytes); - } - } catch (IOException e) { - throw new SecurityException( - "Failed to read from " + URANDOM_FILE, e); - } - } - - @Override - protected byte[] engineGenerateSeed(int size) { - byte[] seed = new byte[size]; - engineNextBytes(seed); - return seed; - } - - private DataInputStream getUrandomInputStream() { - synchronized (sLock) { - if (sUrandomIn == null) { - // NOTE: Consider inserting a BufferedInputStream between - // DataInputStream and FileInputStream if you need higher - // PRNG output performance and can live with future PRNG - // output being pulled into this process prematurely. - try { - sUrandomIn = new DataInputStream( - new FileInputStream(URANDOM_FILE)); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for reading", e); - } - } - return sUrandomIn; - } - } - - private OutputStream getUrandomOutputStream() throws IOException { - synchronized (sLock) { - if (sUrandomOut == null) { - sUrandomOut = new FileOutputStream(URANDOM_FILE); - } - return sUrandomOut; - } - } - } - - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = - new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE_1_1) { - seedBufferOut.writeInt(Process.myUid()); - } - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } - - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } - - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } -} \ No newline at end of file diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java index a329c29..412355c 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingAuthCtx.java @@ -2,11 +2,8 @@ package io.github.muntashirakon.adb; -import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import org.bouncycastle.crypto.InvalidCipherTextException; @@ -21,6 +18,7 @@ import org.bouncycastle.crypto.params.KeyParameter; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import javax.security.auth.Destroyable; @@ -28,16 +26,18 @@ import javax.security.auth.Destroyable; import io.github.muntashirakon.crypto.spake2.Spake2Context; import io.github.muntashirakon.crypto.spake2.Spake2Role; -@RequiresApi(Build.VERSION_CODES.GINGERBREAD) class PairingAuthCtx implements Destroyable { // The following values are taken from the following source and are subjected to change // https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/pairing_auth.cpp - private static final byte[] CLIENT_NAME = StringCompat.getBytes("adb pair client\u0000", "UTF-8"); - private static final byte[] SERVER_NAME = StringCompat.getBytes("adb pair server\u0000", "UTF-8"); + private static final byte[] CLIENT_NAME = + "adb pair client\u0000".getBytes(StandardCharsets.UTF_8); + private static final byte[] SERVER_NAME = + "adb pair server\u0000".getBytes(StandardCharsets.UTF_8); // The following values are taken from the following source and are subjected to change // https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/aes_128_gcm.cpp - private static final byte[] INFO = StringCompat.getBytes("adb pairing_auth aes-128-gcm key", "UTF-8"); + private static final byte[] INFO = + "adb pairing_auth aes-128-gcm key".getBytes(StandardCharsets.UTF_8); private static final int HKDF_KEY_LENGTH = 128 / 8; public static final int GCM_IV_LENGTH = 12; // in bytes @@ -83,10 +83,14 @@ class PairingAuthCtx implements Destroyable { if (mIsDestroyed) return false; byte[] keyMaterial = mSpake2Ctx.processMessage(theirMsg); if (keyMaterial == null) return false; - HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest()); - hkdf.init(new HKDFParameters(keyMaterial, null, INFO)); - hkdf.generateBytes(mSecretKey, 0, mSecretKey.length); - return true; + try { + HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest()); + hkdf.init(new HKDFParameters(keyMaterial, null, INFO)); + hkdf.generateBytes(mSecretKey, 0, mSecretKey.length); + return true; + } finally { + Arrays.fill(keyMaterial, (byte) 0); + } } @Nullable @@ -108,7 +112,9 @@ class PairingAuthCtx implements Destroyable { @Override public void destroy() { + if (mIsDestroyed) return; mIsDestroyed = true; + Arrays.fill(mMsg, (byte) 0); Arrays.fill(mSecretKey, (byte) 0); mSpake2Ctx.destroy(); } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java index 732a08e..b009e66 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/PairingConnectionCtx.java @@ -2,19 +2,18 @@ package io.github.muntashirakon.adb; -import android.annotation.SuppressLint; -import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; + +import org.conscrypt.Conscrypt; import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Method; +import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -28,18 +27,17 @@ import java.util.Arrays; import java.util.Objects; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; -// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_connection/pairing_connection.cpp +// AOSP android-11.0.0_r1 adb/pairing_connection/pairing_connection.cpp. // Also based on Shizuku's implementation -@RequiresApi(Build.VERSION_CODES.GINGERBREAD) public final class PairingConnectionCtx implements Closeable { - public static final String TAG = PairingConnectionCtx.class.getSimpleName(); + public static final String TAG = "scrcpy-android"; public static final String EXPORTED_KEY_LABEL = "adb-label\u0000"; public static final int EXPORT_KEY_SIZE = 64; + private static final int CONNECT_TIMEOUT_MS = 10_000; + private static final int IO_TIMEOUT_MS = 15_000; private enum State { Ready, @@ -48,29 +46,29 @@ public final class PairingConnectionCtx implements Closeable { Stopped } - enum Role { - Client, - Server, - } - private final String mHost; private final int mPort; private final byte[] mPswd; private final PeerInfo mPeerInfo; private final SSLContext mSslContext; - private final Role mRole = Role.Client; - private DataInputStream mInputStream; private DataOutputStream mOutputStream; private PairingAuthCtx mPairingAuthCtx; + private Socket mSocket; private State mState = State.Ready; public PairingConnectionCtx(@NonNull String host, int port, @NonNull byte[] pswd, @NonNull KeyPair keyPair, @NonNull String deviceName) throws NoSuchAlgorithmException, KeyManagementException, InvalidKeyException { this.mHost = Objects.requireNonNull(host); + if (host.isEmpty()) throw new IllegalArgumentException("host is empty"); + if (port < 1 || port > 65535) throw new IllegalArgumentException("port is invalid"); + Objects.requireNonNull(pswd); + if (pswd.length == 0 || pswd.length > 1024) { + throw new IllegalArgumentException("password length is invalid"); + } this.mPort = port; - this.mPswd = Objects.requireNonNull(pswd); + this.mPswd = pswd.clone(); this.mPeerInfo = new PeerInfo(PeerInfo.ADB_RSA_PUB_KEY, AndroidPubkey.encodeWithName((RSAPublicKey) keyPair.getPublicKey(), Objects.requireNonNull(deviceName))); this.mSslContext = SslUtils.getSslContext(keyPair); @@ -121,17 +119,19 @@ public final class PairingConnectionCtx implements Closeable { } private void setupTlsConnection() throws IOException { - Socket socket; - if (mRole == Role.Server) { - SSLServerSocket sslServerSocket = (SSLServerSocket) mSslContext.getServerSocketFactory().createServerSocket(mPort); - socket = sslServerSocket.accept(); - // TODO: Write automated test scripts after removing Conscrypt dependency. - } else { // role == Role.Client - socket = new Socket(mHost, mPort); + Socket socket = new Socket(); + try { + socket.connect(new InetSocketAddress(mHost, mPort), CONNECT_TIMEOUT_MS); + socket.setSoTimeout(IO_TIMEOUT_MS); + socket.setTcpNoDelay(true); + } catch (IOException e) { + try { socket.close(); } catch (IOException ignored) {} + throw e; } - socket.setTcpNoDelay(true); + mSocket = socket; - // We use custom SSLContext to allow any SSL certificates + // The PAKE below authenticates the pairing code and binds it to this + // TLS channel through exported key material. SSLSocket sslSocket = (SSLSocket) mSslContext.getSocketFactory().createSocket(socket, mHost, mPort, true); sslSocket.startHandshake(); Log.d(TAG, "Handshake succeeded."); @@ -141,41 +141,22 @@ public final class PairingConnectionCtx implements Closeable { // To ensure the connection is not stolen while we do the PAKE, append the exported key material from the // tls connection to the password. - byte[] keyMaterial = exportKeyingMaterial(sslSocket, EXPORT_KEY_SIZE); + byte[] keyMaterial = Conscrypt.exportKeyingMaterial( + sslSocket, EXPORTED_KEY_LABEL, null, EXPORT_KEY_SIZE); byte[] passwordBytes = new byte[mPswd.length + keyMaterial.length]; - System.arraycopy(mPswd, 0, passwordBytes, 0, mPswd.length); - System.arraycopy(keyMaterial, 0, passwordBytes, mPswd.length, keyMaterial.length); - - PairingAuthCtx pairingAuthCtx = PairingAuthCtx.createAlice(passwordBytes); - if (pairingAuthCtx == null) { - throw new IOException("Unable to create PairingAuthCtx."); - } - this.mPairingAuthCtx = pairingAuthCtx; - } - - @SuppressLint("PrivateApi") // Conscrypt is a stable private API - private byte[] exportKeyingMaterial(SSLSocket sslSocket, int length) throws SSLException { - // Conscrypt#exportKeyingMaterial(SSLSocket socket, String label, byte[] context, int length): byte[] - // throws SSLException + PairingAuthCtx pairingAuthCtx; try { - Class conscryptClass; - if (SslUtils.isCustomConscrypt()) { - conscryptClass = Class.forName("org.conscrypt.Conscrypt"); - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - // Although support for conscrypt has been added in Android 5.0 (Lollipop), - // TLS1.3 isn't supported until Android 9 (Pie). - throw new SSLException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead."); - } else { - conscryptClass = Class.forName("com.android.org.conscrypt.Conscrypt"); + System.arraycopy(mPswd, 0, passwordBytes, 0, mPswd.length); + System.arraycopy(keyMaterial, 0, passwordBytes, mPswd.length, keyMaterial.length); + pairingAuthCtx = PairingAuthCtx.createAlice(passwordBytes); + if (pairingAuthCtx == null) { + throw new IOException("Unable to create PairingAuthCtx."); } - Method exportKeyingMaterial = conscryptClass.getMethod("exportKeyingMaterial", SSLSocket.class, - String.class, byte[].class, int.class); - return (byte[]) exportKeyingMaterial.invoke(null, sslSocket, EXPORTED_KEY_LABEL, null, length); - } catch (SSLException e) { - throw e; - } catch (Throwable th) { - throw new SSLException(th); + } finally { + Arrays.fill(keyMaterial, (byte) 0); + Arrays.fill(passwordBytes, (byte) 0); } + this.mPairingAuthCtx = pairingAuthCtx; } private void writeHeader(@NonNull PairingPacketHeader header, @NonNull byte[] payload) throws IOException { @@ -269,31 +250,50 @@ public final class PairingConnectionCtx implements Closeable { } PeerInfo theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decryptedMsg)); - Log.d(TAG, theirPeerInfo.toString()); + Log.d(TAG, "Received peer info type=" + theirPeerInfo.type); + if (theirPeerInfo.type != PeerInfo.ADB_DEVICE_GUID) { + Log.e(TAG, "Pairing peer did not send a device GUID"); + return false; + } + if (theirPeerInfo.data[0] == 0) { + Log.e(TAG, "Pairing peer sent an empty device GUID"); + return false; + } return true; } @Override public void close() { Arrays.fill(mPswd, (byte) 0); - try { - mInputStream.close(); - } catch (IOException ignore) { + if (mInputStream != null) { + try { + mInputStream.close(); + } catch (IOException ignore) { + } } - try { - mOutputStream.close(); - } catch (IOException ignore) { + if (mOutputStream != null) { + try { + mOutputStream.close(); + } catch (IOException ignore) { + } } - if (mState != State.Ready) { + if (mSocket != null) { + try { + mSocket.close(); + } catch (IOException ignore) { + } + } + if (mPairingAuthCtx != null) { mPairingAuthCtx.destroy(); } + mState = State.Stopped; } private static class PeerInfo { public static final int MAX_PEER_INFO_SIZE = 1 << 13; public static final byte ADB_RSA_PUB_KEY = 0; - public static final byte ADB_DEVICE_GUID = 0; + public static final byte ADB_DEVICE_GUID = 1; @NonNull public static PeerInfo readFrom(@NonNull ByteBuffer buffer) { @@ -314,15 +314,6 @@ public final class PairingConnectionCtx implements Closeable { public void writeTo(@NonNull ByteBuffer buffer) { buffer.put(type).put(data); } - - @NonNull - @Override - public String toString() { - return "PeerInfo{" + - "type=" + type + - ", data=" + Arrays.toString(data) + - '}'; - } } private static class PairingPacketHeader { @@ -370,15 +361,5 @@ public final class PairingConnectionCtx implements Closeable { public void writeTo(@NonNull ByteBuffer buffer) { buffer.put(version).put(type).putInt(payloadSize); } - - @NonNull - @Override - public String toString() { - return "PairingPacketHeader{" + - "version=" + version + - ", type=" + type + - ", payloadSize=" + payloadSize + - '}'; - } } } diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java index aea4e6f..0e5db6b 100644 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java +++ b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/SslUtils.java @@ -2,19 +2,21 @@ package io.github.muntashirakon.adb; -import android.annotation.SuppressLint; -import android.os.Build; - import androidx.annotation.NonNull; +import org.conscrypt.Conscrypt; + +import java.math.BigInteger; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; -import java.security.Provider; import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.util.Objects; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; @@ -22,45 +24,25 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509TrustManager; final class SslUtils { - private static boolean customConscrypt = false; - private static SSLContext sslContext; - - public static boolean isCustomConscrypt() { - return customConscrypt; - } + private SslUtils() {} - @SuppressLint("TrulyRandom") // The users are already instructed to fix this issue @NonNull - public static SSLContext getSslContext(KeyPair keyPair) throws NoSuchAlgorithmException, KeyManagementException { - if (sslContext != null) { - return sslContext; - } - try { - Class providerClass = Class.forName("org.conscrypt.OpenSSLProvider"); - Provider openSslProvder = (Provider) providerClass.getDeclaredConstructor().newInstance(); - sslContext = SSLContext.getInstance("TLSv1.3", openSslProvder); - customConscrypt = true; - } catch (NoSuchAlgorithmException e) { - throw e; - } catch (Throwable e) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - // Custom error message to inform user that they should use custom Conscrypt library. - throw new NoSuchAlgorithmException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead."); - } - sslContext = SSLContext.getInstance("TLSv1.3"); - customConscrypt = false; + static SSLContext getSslContext(KeyPair keyPair) + throws NoSuchAlgorithmException, KeyManagementException { + Objects.requireNonNull(keyPair); + if (!Conscrypt.isAvailable()) { + throw new NoSuchAlgorithmException("bundled Conscrypt is unavailable"); } - System.out.println("Using " + (customConscrypt ? "custom" : "default") + " TLSv1.3 provider..."); - sslContext.init(new KeyManager[]{getKeyManager(keyPair)}, - new X509TrustManager[]{getAllAcceptingTrustManager()}, - new SecureRandom()); - return sslContext; + SSLContext context = SSLContext.getInstance("TLSv1.3", Conscrypt.newProvider()); + context.init(new KeyManager[]{getKeyManager(keyPair)}, + new X509TrustManager[]{getAdbTrustManager()}, new SecureRandom()); + return context; } @NonNull private static KeyManager getKeyManager(KeyPair keyPair) { return new X509ExtendedKeyManager() { - private final String mAlias = "key"; + private static final String ALIAS = "key"; @Override public String[] getClientAliases(String keyType, Principal[] issuers) { @@ -70,7 +52,7 @@ final class SslUtils { @Override public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { for (String keyType : keyTypes) { - if (keyType.equals("RSA")) return mAlias; + if ("RSA".equals(keyType)) return ALIAS; } return null; } @@ -87,32 +69,45 @@ final class SslUtils { @Override public X509Certificate[] getCertificateChain(String alias) { - if (this.mAlias.equals(alias)) { - return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()}; - } - return null; + if (!ALIAS.equals(alias)) return null; + return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()}; } @Override public PrivateKey getPrivateKey(String alias) { - if (this.mAlias.equals(alias)) { - return keyPair.getPrivateKey(); - } - return null; + return ALIAS.equals(alias) ? keyPair.getPrivateKey() : null; } }; } - @SuppressLint("TrustAllX509TrustManager") // Accept all certificates + // ADB does not give the client a stable target certificate to verify. + // The pairing server generates a new key for each pairing operation and + // adbd generates a separate process-scoped key. Pairing authenticates the + // six-digit-code exchange; later TLS connections authenticate this client + // to adbd, but not adbd to this client. Reject malformed certificates and + // keep TLS mandatory without pretending that an ephemeral key is an + // identity pin. @NonNull - private static X509TrustManager getAllAcceptingTrustManager() { + private static X509TrustManager getAdbTrustManager() { return new X509TrustManager() { @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + throw new CertificateException("ADB TLS context is client-only"); } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain == null || chain.length == 0) { + throw new CertificateException("ADB peer presented no certificate"); + } + if (!(chain[0].getPublicKey() instanceof RSAPublicKey key) + || key.getModulus().bitLength() != 2048 + || (!BigInteger.valueOf(3).equals(key.getPublicExponent()) + && !BigInteger.valueOf(65537).equals(key.getPublicExponent()))) { + throw new CertificateException("ADB peer certificate is not RSA-2048"); + } } @Override diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java deleted file mode 100644 index 1e05eee..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/StringCompat.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb; - -import android.os.Build; - -import androidx.annotation.NonNull; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.IllegalCharsetNameException; - -final class StringCompat { - @NonNull - public static byte[] getBytes(@NonNull String text, @NonNull String charsetName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - return text.getBytes(Charset.forName(charsetName)); - } - try { - return text.getBytes(charsetName); - } catch (UnsupportedEncodingException e) { - throw (IllegalCharsetNameException) new IllegalCharsetNameException("Illegal charset " + charsetName) - .initCause(e); - } - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java deleted file mode 100644 index 6b424cb..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AdbMdns.java +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb.android; - -import android.content.Context; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.StringDef; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.ServerSocket; -import java.net.SocketException; -import java.util.Collections; -import java.util.Objects; - -/** - * Automatic discovery of ADB daemons. - */ -// Copyright 2020 南宫雪珊 -// Copyright 2022 Muntashir Al-Islam -// Based on https://android.googlesource.com/platform/packages/modules/adb/+/eddd2d3a386a83f5d1e14f87a318adef4c2f1a9d/adb_mdns.cpp -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -public class AdbMdns { - public static final String SERVICE_TYPE_ADB = "adb"; - public static final String SERVICE_TYPE_TLS_PAIRING = "adb-tls-pairing"; - public static final String SERVICE_TYPE_TLS_CONNECT = "adb-tls-connect"; - - @StringDef({ - SERVICE_TYPE_ADB, - SERVICE_TYPE_TLS_PAIRING, - SERVICE_TYPE_TLS_CONNECT, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ServiceType { - } - - public interface OnAdbDaemonDiscoveredListener { - void onPortChanged(@Nullable InetAddress hostAddress, int port); - } - - @NonNull - private final Context mContext; - @NonNull - private final String mServiceType; - @NonNull - private final OnAdbDaemonDiscoveredListener mAdbDaemonDiscoveredListener; - private final NsdManager.DiscoveryListener mDiscoveryListener; - private final NsdManager mNsdManager; - - private boolean mRegistered; - private boolean mRunning; - @Nullable - private String mServiceName; - - public AdbMdns(@NonNull Context context, @ServiceType @NonNull String serviceType, - @NonNull OnAdbDaemonDiscoveredListener portChangeListener) { - mContext = Objects.requireNonNull(context); - mServiceType = String.format("_%s._tcp", Objects.requireNonNull(serviceType)); - mAdbDaemonDiscoveredListener = Objects.requireNonNull(portChangeListener); - mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE); - mDiscoveryListener = new DiscoveryListener(this); - } - - public void start() { - if (mRunning) return; - mRunning = true; - if (!mRegistered) { - mNsdManager.discoverServices(mServiceType, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); - } - } - - public void stop() { - if (!mRunning) return; - mRunning = false; - if (mRegistered) { - mNsdManager.stopServiceDiscovery(mDiscoveryListener); - } - } - - public boolean isRunning() { - return mRunning; - } - - private void onDiscoveryStart() { - mRegistered = true; - } - - private void onDiscoverStop() { - mRegistered = false; - } - - private void onServiceFound(NsdServiceInfo serviceInfo) { - mNsdManager.resolveService(serviceInfo, new ResolveListener(this)); - } - - private void onServiceLost(NsdServiceInfo serviceInfo) { - if (mServiceName != null && mServiceName.equals(serviceInfo.getServiceName())) { - mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), -1); - } - } - - private void onServiceResolved(NsdServiceInfo serviceInfo) { - if (!mRunning) return; - try { - for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { - for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) { - String inetHost = inetAddress.getHostAddress(); - if (Objects.equals(inetHost, serviceInfo.getHost().getHostAddress()) - && isPortAvailable(serviceInfo.getPort())) { - mServiceName = serviceInfo.getServiceName(); - mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), serviceInfo.getPort()); - } - } - } - } catch (SocketException e) { - e.printStackTrace(); - } - } - - private boolean isPortAvailable(int port) { - try (ServerSocket socket = new ServerSocket()) { - socket.bind(new InetSocketAddress(AndroidUtils.getHostIpAddress(mContext), port), 1); - return false; - } catch (IOException e) { - return true; - } - } - - private static class DiscoveryListener implements NsdManager.DiscoveryListener { - @NonNull - private final AdbMdns mAdbMdns; - - private DiscoveryListener(@NonNull AdbMdns adbMdns) { - mAdbMdns = adbMdns; - } - - @Override - public void onDiscoveryStarted(String serviceType) { - mAdbMdns.onDiscoveryStart(); - } - - @Override - public void onStartDiscoveryFailed(String serviceType, int errorCode) { - } - - @Override - public void onDiscoveryStopped(String serviceType) { - mAdbMdns.onDiscoverStop(); - } - - @Override - public void onStopDiscoveryFailed(String serviceType, int errorCode) { - } - - @Override - public void onServiceFound(NsdServiceInfo serviceInfo) { - mAdbMdns.onServiceFound(serviceInfo); - } - - @Override - public void onServiceLost(NsdServiceInfo serviceInfo) { - mAdbMdns.onServiceLost(serviceInfo); - } - } - - private static class ResolveListener implements NsdManager.ResolveListener { - @NonNull - private final AdbMdns mAdbMdns; - - private ResolveListener(@NonNull AdbMdns adbMdns) { - mAdbMdns = adbMdns; - } - - @Override - public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { - } - - @Override - public void onServiceResolved(NsdServiceInfo serviceInfo) { - mAdbMdns.onServiceResolved(serviceInfo); - } - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java deleted file mode 100644 index bb679d2..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/AndroidUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 - -package io.github.muntashirakon.adb.android; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; -import android.provider.Settings; - -import androidx.annotation.NonNull; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class AndroidUtils { - // https://github.com/firebase/firebase-android-sdk/blob/7d86138304a6573cbe2c61b66b247e930fa05767/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java#L402 - private static final String GOLDFISH = "goldfish"; - private static final String RANCHU = "ranchu"; - private static final String SDK = "sdk"; - - public static boolean isEmulator(@NonNull Context context) { - if (Build.PRODUCT.contains(SDK)) { - return true; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO - && (Build.HARDWARE.contains(GOLDFISH) || Build.HARDWARE.contains(RANCHU))) { - return true; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { - @SuppressLint("HardwareIds") - String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); - return androidId == null; - } - return false; - } - - - @NonNull - public static String getHostIpAddress(@NonNull Context context) { - if (AndroidUtils.isEmulator(context)) { - return "10.0.2.2"; - } - String ipAddress; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - ipAddress = InetAddress.getLoopbackAddress().getHostAddress(); - } else { - try { - ipAddress = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - ipAddress = null; - } - } - if (ipAddress == null || ipAddress.equals("::1")) { - return "127.0.0.1"; - } - return ipAddress; - } -} diff --git a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html b/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html deleted file mode 100644 index 704dd26..0000000 --- a/vendor/libadb-android/libadb/src/main/java/io/github/muntashirakon/adb/android/package.html +++ /dev/null @@ -1 +0,0 @@ -

All Android dependencies are kept under this package for easy reference.

\ No newline at end of file -- cgit v1.2.3