diff options
8 files changed, 116 insertions, 214 deletions
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 3fffdbecd0de..9f27f721ea83 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -23,7 +23,6 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER import static android.content.ComponentName.createRelative; import static com.android.server.companion.Utils.prepareForIpc; -import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -92,8 +91,7 @@ public class SystemDataTransferProcessor { mAssociationStore = associationStore; mSystemDataTransferRequestStore = systemDataTransferRequestStore; mTransportManager = transportManager; - mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, - this::onReceivePermissionRestore); + mTransportManager.setListener(this::onReceivePermissionRestore); mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); mExecutor = Executors.newSingleThreadExecutor(); } diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java index 1559a3f8fdf8..adaee757b96a 100644 --- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java +++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java @@ -35,7 +35,7 @@ import java.util.function.BiConsumer; /** * Helper class to perform attestation verification synchronously. */ -public class AttestationVerifier { +class AttestationVerifier { private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system"; diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java index 05b6022ce569..13dba84487e3 100644 --- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java +++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java @@ -110,7 +110,7 @@ public class SecureChannel { this(in, out, callback, null, new AttestationVerifier(context)); } - public SecureChannel( + private SecureChannel( final InputStream in, final OutputStream out, Callback callback, @@ -381,10 +381,9 @@ public class SecureChannel { private void exchangeAuthentication() throws IOException, GeneralSecurityException, BadHandleException, CryptoException { - if (mPreSharedKey != null) { + if (mVerifier == null) { exchangePreSharedKey(); - } - if (mVerifier != null) { + } else { exchangeAttestation(); } } diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 2abdcb172965..6a53adfeea9d 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -19,9 +19,9 @@ package com.android.server.companion.transport; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; -import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.content.Context; @@ -30,17 +30,12 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.Build; import android.os.ParcelFileDescriptor; -import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; -import com.android.server.companion.transport.Transport.Listener; import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; @@ -49,8 +44,6 @@ public class CompanionTransportManager { private static final String TAG = "CDM_CompanionTransportManager"; private static final boolean DEBUG = false; - private static final int NON_ANDROID = -1; - private boolean mSecureTransportEnabled = true; private static boolean isRequest(int message) { @@ -61,29 +54,24 @@ public class CompanionTransportManager { return (message & 0xFF000000) == 0x33000000; } + public interface Listener { + void onRequestPermissionRestore(byte[] data); + } + private final Context mContext; @GuardedBy("mTransports") private final SparseArray<Transport> mTransports = new SparseArray<>(); - @NonNull - private final Map<Integer, Listener> mListeners = new HashMap<>(); - - private Transport mTempTransport; + @Nullable + private Listener mListener; public CompanionTransportManager(Context context) { mContext = context; } - /** - * Add a message listener when a message is received for the message type - */ - @GuardedBy("mTransports") - public void addListener(int message, @NonNull Listener listener) { - mListeners.put(message, listener); - for (int i = 0; i < mTransports.size(); i++) { - mTransports.valueAt(i).addListener(message, listener); - } + public void setListener(@NonNull Listener listener) { + mListener = listener; } /** @@ -117,7 +105,15 @@ public class CompanionTransportManager { detachSystemDataTransport(packageName, userId, associationId); } - initializeTransport(associationId, fd); + final Transport transport; + if (isSecureTransportEnabled(associationId)) { + transport = new SecureTransport(associationId, fd, mContext, mListener); + } else { + transport = new RawTransport(associationId, fd, mContext, mListener); + } + + transport.start(); + mTransports.put(associationId, transport); } } @@ -132,85 +128,13 @@ public class CompanionTransportManager { } } - @GuardedBy("mTransports") - private void initializeTransport(int associationId, ParcelFileDescriptor fd) { - if (!isSecureTransportEnabled()) { - Transport transport = new RawTransport(associationId, fd, mContext); - for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) { - transport.addListener(entry.getKey(), entry.getValue()); - } - transport.start(); - mTransports.put(associationId, transport); - Slog.i(TAG, "RawTransport is created"); - return; - } - - // Exchange platform info to decide which transport should be created - mTempTransport = new RawTransport(associationId, fd, mContext); - for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) { - mTempTransport.addListener(entry.getKey(), entry.getValue()); - } - mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, this::onPlatformInfoReceived); - mTempTransport.start(); - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - // data format: | SDK_INT (int) | release length (int) | release | - final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length) - .putInt(sdk) - .putInt(release.getBytes().length) - .put(release.getBytes()); - - // TODO: it should check if preSharedKey is given - mTempTransport.requestForResponse(MESSAGE_REQUEST_PLATFORM_INFO, data.array()); - } - - /** - * Depending on the remote platform info to decide which transport should be created - */ - @GuardedBy("mTransports") - private void onPlatformInfoReceived(byte[] data) { - // TODO: it should check if preSharedKey is given - - ByteBuffer buffer = ByteBuffer.wrap(data); - int remoteSdk = buffer.getInt(); - byte[] remoteRelease = new byte[buffer.getInt()]; - buffer.get(remoteRelease); - - Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease)); - - Transport transport = mTempTransport; - mTempTransport = null; - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - if (remoteSdk == NON_ANDROID) { - // TODO: pass in a real preSharedKey - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext, null, null); - } else if (sdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE - || remoteSdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - // TODO: depending on the release version, either - // 1) using a RawTransport for old T versions - // 2) or an Ukey2 handshaked transport for UKey2 backported T versions - } else { - Slog.i(TAG, "Creating a secure channel"); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext); - for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) { - transport.addListener(entry.getKey(), entry.getValue()); - } - transport.start(); - } - mTransports.put(transport.getAssociationId(), transport); - } - public Future<?> requestPermissionRestore(int associationId, byte[] data) { synchronized (mTransports) { final Transport transport = mTransports.get(associationId); if (transport == null) { return CompletableFuture.failedFuture(new IOException("Missing transport")); } + return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data); } } @@ -222,9 +146,10 @@ public class CompanionTransportManager { this.mSecureTransportEnabled = enabled; } - private boolean isSecureTransportEnabled() { + private boolean isSecureTransportEnabled(int associationId) { boolean enabled = !Build.IS_DEBUGGABLE || mSecureTransportEnabled; + // TODO: version comparison logic return enabled; } } diff --git a/services/companion/java/com/android/server/companion/transport/CryptoManager.java b/services/companion/java/com/android/server/companion/transport/CryptoManager.java index a15939e52936..b08354afc8ad 100644 --- a/services/companion/java/com/android/server/companion/transport/CryptoManager.java +++ b/services/companion/java/com/android/server/companion/transport/CryptoManager.java @@ -16,51 +16,51 @@ package com.android.server.companion.transport; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.util.Slog; +import java.io.IOException; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; /** - * This class uses Java Cryptography to encrypt and decrypt messages + * This class can be used to encrypt and decrypt bytes using Android Cryptography */ public class CryptoManager { private static final String TAG = "CDM_CryptoManager"; - private static final int SECRET_KEY_LENGTH = 32; - private static final String ALGORITHM = "AES"; - private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding"; - private final byte[] mPreSharedKey; - private Cipher mEncryptCipher; - private Cipher mDecryptCipher; + private static final String KEY_STORE_ALIAS = "cdm_secret"; + private static final String ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; + private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; + private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; + private static final String TRANSFORMATION = ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING; - private SecretKey mSecretKey; + private final KeyStore mKeyStore; - public CryptoManager(byte[] preSharedKey) { - if (preSharedKey == null) { - mPreSharedKey = Arrays.copyOf(new byte[0], SECRET_KEY_LENGTH); - } else { - mPreSharedKey = Arrays.copyOf(preSharedKey, SECRET_KEY_LENGTH); - } - mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM); + public CryptoManager() { + // Initialize KeyStore try { - mEncryptCipher = Cipher.getInstance(TRANSFORMATION); - mEncryptCipher.init(Cipher.ENCRYPT_MODE, mSecretKey); - mDecryptCipher = Cipher.getInstance(TRANSFORMATION); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { - Slog.e(TAG, e.getMessage()); + mKeyStore = KeyStore.getInstance("AndroidKeyStore"); + mKeyStore.load(null); + } catch (KeyStoreException | IOException | NoSuchAlgorithmException + | CertificateException e) { + throw new RuntimeException(e); } } @@ -69,19 +69,21 @@ public class CryptoManager { */ public byte[] encrypt(byte[] input) { try { - if (mEncryptCipher == null) { - return null; - } + // Encrypt using Cipher + Cipher encryptCipher = Cipher.getInstance(TRANSFORMATION); + encryptCipher.init(Cipher.ENCRYPT_MODE, getKey()); + byte[] encryptedBytes = encryptCipher.doFinal(input); - byte[] encryptedBytes = mEncryptCipher.doFinal(input); + // Write to bytes ByteBuffer buffer = ByteBuffer.allocate( - 4 + mEncryptCipher.getIV().length + 4 + encryptedBytes.length) - .putInt(mEncryptCipher.getIV().length) - .put(mEncryptCipher.getIV()) + 4 + encryptCipher.getIV().length + 4 + encryptedBytes.length) + .putInt(encryptCipher.getIV().length) + .put(encryptCipher.getIV()) .putInt(encryptedBytes.length) .put(encryptedBytes); return buffer.array(); - } catch (IllegalBlockSizeException | BadPaddingException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException e) { Slog.e(TAG, e.getMessage()); return null; } @@ -97,20 +99,45 @@ public class CryptoManager { byte[] encryptedBytes = new byte[buffer.getInt()]; buffer.get(encryptedBytes); try { - mDecryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv)); - return mDecryptCipher.doFinal(encryptedBytes); - } catch (InvalidKeyException | InvalidAlgorithmParameterException - | IllegalBlockSizeException | BadPaddingException e) { + Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION); + decryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv)); + return decryptCipher.doFinal(encryptedBytes); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException e) { Slog.e(TAG, e.getMessage()); return null; } } private SecretKey getKey() { - if (mSecretKey != null) { - return mSecretKey; + try { + KeyStore.Entry keyEntry = mKeyStore.getEntry(KEY_STORE_ALIAS, null); + if (keyEntry instanceof KeyStore.SecretKeyEntry + && ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey() != null) { + return ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey(); + } else { + return createKey(); + } + } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) { + throw new RuntimeException(e); + } + } + + private SecretKey createKey() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM); + keyGenerator.init( + new KeyGenParameterSpec.Builder(KEY_STORE_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(BLOCK_MODE) + .setEncryptionPaddings(PADDING) + .setUserAuthenticationRequired(false) + .setRandomizedEncryptionRequired(true) + .build()); + return keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); } - mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM); - return mSecretKey; } } diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java index 4060f6efe0ca..7c0c7cf7ac68 100644 --- a/services/companion/java/com/android/server/companion/transport/RawTransport.java +++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java @@ -21,6 +21,8 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.util.Slog; +import com.android.server.companion.transport.CompanionTransportManager.Listener; + import libcore.io.IoUtils; import libcore.io.Streams; @@ -30,8 +32,8 @@ import java.nio.ByteBuffer; class RawTransport extends Transport { private volatile boolean mStopped; - RawTransport(int associationId, ParcelFileDescriptor fd, Context context) { - super(associationId, fd, context); + RawTransport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) { + super(associationId, fd, context, listener); } @Override @@ -62,7 +64,7 @@ class RawTransport extends Transport { protected void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException { if (DEBUG) { - Slog.e(TAG, "Sending message 0x" + Integer.toHexString(message) + Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message) + " sequence " + sequence + " length " + data.length + " to association " + mAssociationId); } diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java index cca08435c0a5..4194130f7e84 100644 --- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -21,8 +21,8 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.util.Slog; -import com.android.server.companion.securechannel.AttestationVerifier; import com.android.server.companion.securechannel.SecureChannel; +import com.android.server.companion.transport.CompanionTransportManager.Listener; import java.io.IOException; import java.nio.ByteBuffer; @@ -37,17 +37,14 @@ class SecureTransport extends Transport implements SecureChannel.Callback { private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100); - SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) { - super(associationId, fd, context); + SecureTransport(int associationId, + ParcelFileDescriptor fd, + Context context, + Listener listener) { + super(associationId, fd, context, listener); mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context); } - SecureTransport(int associationId, ParcelFileDescriptor fd, Context context, - byte[] preSharedKey, AttestationVerifier verifier) { - super(associationId, fd, context); - mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier); - } - @Override public void start() { mSecureChannel.start(); diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java index e984c637b44c..923d4243a34c 100644 --- a/services/companion/java/com/android/server/companion/transport/Transport.java +++ b/services/companion/java/com/android/server/companion/transport/Transport.java @@ -25,28 +25,23 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.companion.transport.CompanionTransportManager.Listener; import libcore.util.EmptyArray; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; -/** - * This class represents the channel established between two devices. - */ -public abstract class Transport { +abstract class Transport { protected static final String TAG = "CDM_CompanionTransport"; protected static final boolean DEBUG = Build.IS_DEBUGGABLE; static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN - public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807086; // ?PFV - public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES + static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI @@ -54,24 +49,11 @@ public abstract class Transport { protected static final int HEADER_LENGTH = 12; protected final int mAssociationId; - protected final ParcelFileDescriptor mFd; protected final InputStream mRemoteIn; protected final OutputStream mRemoteOut; protected final Context mContext; - /** Message type -> Listener */ - private final Map<Integer, Listener> mListeners; - - /** - * Message listener - */ - public interface Listener { - /** - * Called when a message is received - * @param data data content in the message - */ - void onDataReceived(byte[] data); - } + private final Listener mListener; private static boolean isRequest(int message) { return (message & 0xFF000000) == 0x63000000; @@ -86,36 +68,16 @@ public abstract class Transport { new SparseArray<>(); protected final AtomicInteger mNextSequence = new AtomicInteger(); - Transport(int associationId, ParcelFileDescriptor fd, Context context) { - mAssociationId = associationId; - mFd = fd; - mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd); - mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - mContext = context; - mListeners = new HashMap<>(); - } - - /** - * Add a listener when a message is received for the message type - * @param message Message type - * @param listener Execute when a message with the type is received - */ - public void addListener(int message, Listener listener) { - mListeners.put(message, listener); - } - - public int getAssociationId() { - return mAssociationId; - } - - protected ParcelFileDescriptor getFd() { - return mFd; + Transport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) { + this.mAssociationId = associationId; + this.mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd); + this.mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + this.mContext = context; + this.mListener = listener; } public abstract void start(); public abstract void stop(); - protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) - throws IOException; public Future<byte[]> requestForResponse(int message, byte[] data) { if (DEBUG) Slog.d(TAG, "Requesting for response"); @@ -137,6 +99,9 @@ public abstract class Transport { return pending; } + protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) + throws IOException; + protected final void handleMessage(int message, int sequence, @NonNull byte[] data) throws IOException { if (DEBUG) { @@ -165,11 +130,6 @@ public abstract class Transport { sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); break; } - case MESSAGE_REQUEST_PLATFORM_INFO: { - callback(message, data); - sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); - break; - } case MESSAGE_REQUEST_PERMISSION_RESTORE: { if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) && !Build.isDebuggable()) { @@ -178,7 +138,7 @@ public abstract class Transport { break; } try { - callback(message, data); + mListener.onRequestPermissionRestore(data); sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); } catch (Exception e) { Slog.w(TAG, "Failed to restore permissions"); @@ -194,12 +154,6 @@ public abstract class Transport { } } - private void callback(int message, byte[] data) { - if (mListeners.containsKey(message)) { - mListeners.get(message).onDataReceived(data); - } - } - private void processResponse(int message, int sequence, byte[] data) { final CompletableFuture<byte[]> future; synchronized (mPendingRequests) { |