diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md index b2b5aab969..b659bf7d33 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/README.md +++ b/remoteauth/service/java/com/android/server/remoteauth/README.md @@ -6,3 +6,5 @@ Provides the connectivity manager to manage connections with the peer device. ## Ranging Provides the ranging manager to perform ranging with the peer devices. +## Util +Common utilities. diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java index 9ef6bda4fc..4db89b3032 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java @@ -16,9 +16,16 @@ package com.android.server.remoteauth.ranging; import android.annotation.NonNull; +import android.content.Context; +import android.util.Log; import androidx.annotation.IntDef; +import com.android.internal.util.Preconditions; +import com.android.server.remoteauth.util.Crypto; + +import com.google.common.hash.Hashing; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; @@ -35,18 +42,50 @@ import java.util.concurrent.Executor; *

Ranging method specific implementation shall be implemented in the extended class. */ public abstract class RangingSession { + private static final String TAG = "RangingSession"; /** Types of ranging error. */ @Retention(RetentionPolicy.SOURCE) @IntDef( value = { RANGING_ERROR_UNKNOWN, + RANGING_ERROR_INVALID_PARAMETERS, + RANGING_ERROR_STOPPED_BY_REQUEST, + RANGING_ERROR_STOPPED_BY_PEER, + RANGING_ERROR_FAILED_TO_START, + RANGING_ERROR_FAILED_TO_STOP, + RANGING_ERROR_SYSTEM_ERROR, + RANGING_ERROR_SYSTEM_TIMEOUT, }) public @interface RangingError {} /** Unknown ranging error type. */ public static final int RANGING_ERROR_UNKNOWN = 0x0; + /** Ranging error due to invalid parameters. */ + public static final int RANGING_ERROR_INVALID_PARAMETERS = 0x1; + + /** Ranging error due to stopped by calling {@link #stop}. */ + public static final int RANGING_ERROR_STOPPED_BY_REQUEST = 0x2; + + /** Ranging error due to stopped by the peer device. */ + public static final int RANGING_ERROR_STOPPED_BY_PEER = 0x3; + + /** Ranging error due to failure to start ranging. */ + public static final int RANGING_ERROR_FAILED_TO_START = 0x4; + + /** Ranging error due to failure to stop ranging. */ + public static final int RANGING_ERROR_FAILED_TO_STOP = 0x5; + + /** + * Ranging error due to system error cause by changes such as privacy policy, power management + * policy, permissions, and more. + */ + public static final int RANGING_ERROR_SYSTEM_ERROR = 0x6; + + /** Ranging error due to system timeout in retry attempts. */ + public static final int RANGING_ERROR_SYSTEM_TIMEOUT = 0x7; + /** Interface for ranging update callbacks. */ public interface RangingCallback { /** @@ -55,7 +94,8 @@ public abstract class RangingSession { * @param sessionInfo info about this ranging session. * @param rangingReport new ranging report */ - void onRangingReport(SessionInfo sessionInfo, RangingReport rangingReport); + void onRangingReport( + @NonNull SessionInfo sessionInfo, @NonNull RangingReport rangingReport); /** * Call upon any ranging error events. @@ -63,7 +103,49 @@ public abstract class RangingSession { * @param sessionInfo info about this ranging session. * @param rangingError error type */ - void onError(SessionInfo sessionInfo, @RangingError int rangingError); + void onError(@NonNull SessionInfo sessionInfo, @RangingError int rangingError); + } + + protected Context mContext; + protected SessionInfo mSessionInfo; + protected float mLowerProximityBoundaryM; + protected float mUpperProximityBoundaryM; + protected boolean mAutoDeriveParams; + protected byte[] mBaseKey; + protected byte[] mSyncData; + protected int mSyncCounter; + protected byte[] mDerivedData; + protected int mDerivedDataLength; + + protected RangingSession( + @NonNull Context context, + @NonNull SessionParameters sessionParameters, + int derivedDataLength) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(sessionParameters); + mContext = context; + mSessionInfo = + new SessionInfo.Builder() + .setDeviceId(sessionParameters.getDeviceId()) + .setRangingMethod(sessionParameters.getRangingMethod()) + .build(); + mLowerProximityBoundaryM = sessionParameters.getLowerProximityBoundaryM(); + mUpperProximityBoundaryM = sessionParameters.getUpperProximityBoundaryM(); + mAutoDeriveParams = sessionParameters.getAutoDeriveParams(); + Log.i( + TAG, + "Creating a new RangingSession {info = " + + mSessionInfo + + ", autoDeriveParams = " + + mAutoDeriveParams + + "}"); + if (mAutoDeriveParams) { + Preconditions.checkArgument( + derivedDataLength > 0, "derivedDataLength must be greater than 0"); + mDerivedDataLength = derivedDataLength; + resetBaseKey(sessionParameters.getBaseKey()); + resetSyncData(sessionParameters.getSyncData()); + } } /** @@ -75,6 +157,8 @@ public abstract class RangingSession { * @param rangingParameters parameters to start the ranging. * @param executor Executor to run the rangingCallback. * @param rangingCallback callback to notify of ranging events. + * @throws NullPointerException if params are null. + * @throws IllegalArgumentException if rangingParameters is invalid. */ public abstract void start( @NonNull RangingParameters rangingParameters, @@ -94,8 +178,22 @@ public abstract class RangingSession { * the secure connection between the devices is lost. * * @param baseKey new baseKey must be 16 or 32 bytes. + * @throws NullPointerException if baseKey is null. + * @throws IllegalArgumentException if baseKey has invalid length. */ - public void resetBaseKey(byte[] baseKey) {} + public void resetBaseKey(@NonNull byte[] baseKey) { + if (!mAutoDeriveParams) { + Log.w(TAG, "autoDeriveParams is disabled, new baseKey is ignored."); + return; + } + Preconditions.checkNotNull(baseKey); + if (baseKey.length != 16 && baseKey.length != 32) { + throw new IllegalArgumentException("Invalid baseKey length: " + baseKey.length); + } + mBaseKey = baseKey; + updateDerivedData(); + Log.i(TAG, "resetBaseKey"); + } /** * Resets the synchronization by giving a new syncData used for ranging parameters derivation. @@ -105,6 +203,52 @@ public abstract class RangingSession { * manner. * * @param syncData new syncData must be 16 bytes. + * @throws NullPointerException if baseKey is null. + * @throws IllegalArgumentException if syncData has invalid length. */ - public void resetSyncData(byte[] syncData) {} + public void resetSyncData(@NonNull byte[] syncData) { + if (!mAutoDeriveParams) { + Log.w(TAG, "autoDeriveParams is disabled, new syncData is ignored."); + return; + } + Preconditions.checkNotNull(syncData); + if (syncData.length != 16) { + throw new IllegalArgumentException("Invalid syncData length: " + syncData.length); + } + mSyncData = syncData; + mSyncCounter = 0; + updateDerivedData(); + Log.i(TAG, "resetSyncData"); + } + + /** Update mDerivedData */ + protected boolean updateDerivedData() { + if (!mAutoDeriveParams) { + Log.w(TAG, "autoDeriveParams is disabled, updateDerivedData is skipped."); + return false; + } + if (mBaseKey == null + || mBaseKey.length == 0 + || mSyncData == null + || mSyncData.length == 0) { + Log.w(TAG, "updateDerivedData: Missing baseKey/syncData"); + return false; + } + byte[] hashedSyncData = + Hashing.sha256() + .newHasher() + .putBytes(mSyncData) + .putInt(mSyncCounter) + .hash() + .asBytes(); + byte[] newDerivedData = Crypto.computeHkdf(mBaseKey, hashedSyncData, mDerivedDataLength); + if (newDerivedData == null) { + Log.w(TAG, "updateDerivedData: computeHkdf failed"); + return false; + } + mDerivedData = newDerivedData; + mSyncCounter++; + Log.i(TAG, "updateDerivedData"); + return true; + } } diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java index 33c3203ab6..2f712448cd 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java @@ -19,9 +19,14 @@ import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_ import android.annotation.NonNull; +import androidx.annotation.IntDef; + import com.android.internal.util.Preconditions; import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * The set of parameters to create a ranging session. * @@ -31,9 +36,29 @@ import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; */ public class SessionParameters { + /** Ranging device role. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = { + DEVICE_ROLE_UNKNOWN, + DEVICE_ROLE_INITIATOR, + DEVICE_ROLE_RESPONDER, + }) + public @interface DeviceRole {} + + /** Unknown device role. */ + public static final int DEVICE_ROLE_UNKNOWN = 0x0; + + /** Device that initiates the ranging. */ + public static final int DEVICE_ROLE_INITIATOR = 0x1; + + /** Device that responds to ranging. */ + public static final int DEVICE_ROLE_RESPONDER = 0x2; + /* Required parameters */ private final String mDeviceId; @RangingMethod private final int mRangingMethod; + @DeviceRole private final int mDeviceRole; /* Optional parameters */ private final float mLowerProximityBoundaryM; @@ -51,6 +76,11 @@ public class SessionParameters { return mRangingMethod; } + @DeviceRole + public int getDeviceRole() { + return mDeviceRole; + } + public float getLowerProximityBoundaryM() { return mLowerProximityBoundaryM; } @@ -74,6 +104,7 @@ public class SessionParameters { private SessionParameters( String deviceId, @RangingMethod int rangingMethod, + @DeviceRole int deviceRole, float lowerProximityBoundaryM, float upperProximityBoundaryM, boolean autoDeriveParams, @@ -81,6 +112,7 @@ public class SessionParameters { byte[] syncData) { mDeviceId = deviceId; mRangingMethod = rangingMethod; + mDeviceRole = deviceRole; mLowerProximityBoundaryM = lowerProximityBoundaryM; mUpperProximityBoundaryM = upperProximityBoundaryM; mAutoDeriveParams = autoDeriveParams; @@ -92,6 +124,7 @@ public class SessionParameters { public static final class Builder { private String mDeviceId = new String(""); @RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN; + @DeviceRole private int mDeviceRole = DEVICE_ROLE_UNKNOWN; private float mLowerProximityBoundaryM; private float mUpperProximityBoundaryM; private boolean mAutoDeriveParams = false; @@ -120,6 +153,12 @@ public class SessionParameters { return this; } + /** Sets the {@link DeviceRole} to be used for the {@link RangingSession}. */ + public Builder setDeviceRole(@DeviceRole int deviceRole) { + mDeviceRole = deviceRole; + return this; + } + /** * Sets the lower proximity boundary in meters, must be greater than or equals to zero. * @@ -191,6 +230,7 @@ public class SessionParameters { Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty."); Preconditions.checkArgument( mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod"); + Preconditions.checkArgument(mDeviceRole != DEVICE_ROLE_UNKNOWN, "Unknown deviceRole"); Preconditions.checkArgument( mLowerProximityBoundaryM >= 0, "Negative lowerProximityBoundaryM: " + mLowerProximityBoundaryM); @@ -212,6 +252,7 @@ public class SessionParameters { return new SessionParameters( mDeviceId, mRangingMethod, + mDeviceRole, mLowerProximityBoundaryM, mUpperProximityBoundaryM, mAutoDeriveParams, diff --git a/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java new file mode 100644 index 0000000000..573597fd07 --- /dev/null +++ b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.remoteauth.util; + +import android.util.Log; + +import androidx.annotation.Nullable; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** Utility class of cryptographic functions. */ +public final class Crypto { + private static final String TAG = "Crypto"; + private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; + + /** + * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of + * given size. + * + * @param ikm the input keying material. + * @param salt A possibly non-secret random value. + * @param size The length of the generated pseudorandom string in bytes. The maximal size is + * 255.DigestSize, where DigestSize is the size of the underlying HMAC. + * @return size pseudorandom bytes, null if failed. + */ + // Based on + // google3/third_party/tink/java_src/src/main/java/com/google/crypto/tink/subtle/Hkdf.java + @Nullable + public static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) { + Mac mac; + try { + mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e); + return null; + } + + if (size > 255 * mac.getMacLength()) { + Log.w(TAG, "Size too large. " + size + " > " + 255 * mac.getMacLength()); + return null; + } + + if (ikm == null || ikm.length == 0) { + Log.w(TAG, "Ikm cannot be empty."); + return null; + } + + if (salt == null || salt.length == 0) { + Log.w(TAG, "Salt cannot be empty."); + return null; + } + + try { + mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM)); + } catch (InvalidKeyException e) { + Log.w(TAG, "Invalid key.", e); + return null; + } + + byte[] prk = mac.doFinal(ikm); + byte[] result = new byte[size]; + try { + mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM)); + } catch (InvalidKeyException e) { + Log.w(TAG, "Invalid key.", e); + return null; + } + + byte[] digest = new byte[0]; + int ctr = 1; + int pos = 0; + while (true) { + mac.update(digest); + mac.update((byte) ctr); + digest = mac.doFinal(); + if (pos + digest.length < size) { + System.arraycopy(digest, 0, result, pos, digest.length); + pos += digest.length; + ctr++; + } else { + System.arraycopy(digest, 0, result, pos, size - pos); + break; + } + } + + return result; + } +} diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java new file mode 100644 index 0000000000..0e547d6c8a --- /dev/null +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.remoteauth.ranging; + +import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB; +import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; +import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; + +/** Unit test for {@link RangingSession}. */ +@RunWith(AndroidJUnit4.class) +public class RangingSessionTest { + + private static final String TEST_DEVICE_ID = "test_device_id"; + @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB; + @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR; + private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f; + private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f; + private static final byte[] TEST_BASE_KEY = + new byte[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f + }; + private static final byte[] TEST_BASE_KEY2 = + new byte[] { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 + }; + private static final byte[] TEST_SYNC_DATA = + new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x00 + }; + private static final byte[] TEST_SYNC_DATA2 = + new byte[] { + 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x00 + }; + + private static final SessionParameters TEST_SESSION_PARAMETER_WITH_AD = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) + .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) + .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) + .setAutoDeriveParams(true) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(TEST_SYNC_DATA) + .build(); + private static final SessionParameters TEST_SESSION_PARAMETER_WO_AD = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) + .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) + .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) + .setAutoDeriveParams(false) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(TEST_SYNC_DATA) + .build(); + private static final int TEST_DERIVE_DATA_LENGTH = 40; + + /** Wrapper class for testing {@link RangingSession}. */ + public static class RangingSessionWrapper extends RangingSession { + public RangingSessionWrapper( + Context context, SessionParameters sessionParameters, int derivedDataLength) { + super(context, sessionParameters, derivedDataLength); + } + + @Override + public void start( + RangingParameters rangingParameters, + Executor executor, + RangingCallback rangingCallback) {} + + @Override + public void stop() {} + + @Override + public boolean updateDerivedData() { + return super.updateDerivedData(); + } + + public byte[] baseKey() { + return mBaseKey; + } + + public byte[] syncData() { + return mSyncData; + } + + public byte[] derivedData() { + return mDerivedData; + } + + public int syncCounter() { + return mSyncCounter; + } + } + + @Mock private Context mContext; + + private RangingSessionWrapper mRangingSessionWithAD; + private RangingSessionWrapper mRangingSessionWithoutAD; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mRangingSessionWithAD = + new RangingSessionWrapper( + mContext, TEST_SESSION_PARAMETER_WITH_AD, TEST_DERIVE_DATA_LENGTH); + mRangingSessionWithoutAD = + new RangingSessionWrapper(mContext, TEST_SESSION_PARAMETER_WO_AD, 0); + } + + @Test + public void testResetBaseKey_autoDeriveDisabled() { + assertNull(mRangingSessionWithoutAD.baseKey()); + mRangingSessionWithoutAD.resetBaseKey(TEST_BASE_KEY2); + assertNull(mRangingSessionWithoutAD.baseKey()); + } + + @Test + public void testResetBaseKey_nullBaseKey() { + assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetBaseKey(null)); + } + + @Test + public void testResetBaseKey_invalidBaseKey() { + assertThrows( + IllegalArgumentException.class, + () -> mRangingSessionWithAD.resetBaseKey(new byte[] {0x1, 0x2, 0x3, 0x4})); + } + + @Test + public void testResetBaseKey_success() { + mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY2); + assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY2); + assertEquals(mRangingSessionWithAD.syncCounter(), 2); + + mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY); + assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY); + assertEquals(mRangingSessionWithAD.syncCounter(), 3); + } + + @Test + public void testResetSyncData_autoDeriveDisabled() { + assertNull(mRangingSessionWithoutAD.syncData()); + mRangingSessionWithoutAD.resetSyncData(TEST_SYNC_DATA2); + assertNull(mRangingSessionWithoutAD.syncData()); + } + + @Test + public void testResetSyncData_nullSyncData() { + assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetSyncData(null)); + } + + @Test + public void testResetSyncData_invalidSyncData() { + assertThrows( + IllegalArgumentException.class, + () -> mRangingSessionWithAD.resetSyncData(new byte[] {0x1, 0x2, 0x3, 0x4})); + } + + @Test + public void testResetSyncData_success() { + mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA2); + assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA2); + assertEquals(mRangingSessionWithAD.syncCounter(), 1); + + mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA); + assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA); + assertEquals(mRangingSessionWithAD.syncCounter(), 1); + } + + @Test + public void testUpdateDerivedData_autoDeriveDisabled() { + assertFalse(mRangingSessionWithoutAD.updateDerivedData()); + assertEquals(mRangingSessionWithoutAD.syncCounter(), 0); + } + + @Test + public void testUpdateDerivedData_hkdfFailed() { + // Max derivedDataLength is 32*255 + RangingSessionWrapper rangingSession = + new RangingSessionWrapper( + mContext, TEST_SESSION_PARAMETER_WITH_AD, /* derivedDataLength= */ 10000); + assertNull(rangingSession.derivedData()); + assertFalse(rangingSession.updateDerivedData()); + assertEquals(rangingSession.syncCounter(), 0); + assertNull(rangingSession.derivedData()); + } + + @Test + public void testUpdateDerivedData_success() { + assertNotNull(mRangingSessionWithAD.derivedData()); + assertTrue(mRangingSessionWithAD.updateDerivedData()); + assertEquals(mRangingSessionWithAD.syncCounter(), 2); + assertNotNull(mRangingSessionWithAD.derivedData()); + } +} diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java index 357fdf976f..522623e18b 100644 --- a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java @@ -17,6 +17,7 @@ package com.android.server.remoteauth.ranging; import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB; +import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -25,6 +26,7 @@ import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; +import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +37,7 @@ public class SessionParametersTest { private static final String TEST_DEVICE_ID = "test_device_id"; @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB; + @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR; private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f; private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f; private static final boolean TEST_AUTO_DERIVE_PARAMS = true; @@ -55,6 +58,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) @@ -82,6 +86,7 @@ public class SessionParametersTest { final SessionParameters.Builder builder = new SessionParameters.Builder() .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setBaseKey(TEST_BASE_KEY) @@ -95,6 +100,21 @@ public class SessionParametersTest { final SessionParameters.Builder builder = new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) + .setDeviceRole(TEST_DEVICE_ROLE) + .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) + .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(TEST_SYNC_DATA); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_invalidDeviceRole() { + final SessionParameters.Builder builder = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setBaseKey(TEST_BASE_KEY) @@ -109,6 +129,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(-1.0f) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setBaseKey(TEST_BASE_KEY) @@ -123,6 +144,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M - 0.1f) .setBaseKey(TEST_BASE_KEY) @@ -138,6 +160,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(autoDeriveParams) @@ -154,6 +177,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) @@ -168,6 +192,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) @@ -183,6 +208,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) @@ -197,6 +223,7 @@ public class SessionParametersTest { new SessionParameters.Builder() .setDeviceId(TEST_DEVICE_ID) .setRangingMethod(TEST_RANGING_METHOD) + .setDeviceRole(TEST_DEVICE_ROLE) .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java new file mode 100644 index 0000000000..eb7a8c5d6b --- /dev/null +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.remoteauth.util; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link Crypto}. */ +@RunWith(AndroidJUnit4.class) +public class CryptoTest { + private static final byte[] TEST_IKM = + new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + private static final byte[] TEST_SALT = + new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00}; + private static final int TEST_SIZE = 40; + + @Test + public void testComputeHkdf_exceedMaxSize() { + // Max size is 32*255 + assertNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, /* size= */ 10000)); + } + + @Test + public void testComputeHkdf_emptySalt() { + assertNull(Crypto.computeHkdf(TEST_IKM, new byte[] {}, TEST_SIZE)); + } + + @Test + public void testComputeHkdf_emptyIkm() { + assertNull(Crypto.computeHkdf(new byte[] {}, TEST_SALT, TEST_SIZE)); + } + + @Test + public void testComputeHkdf_success() { + assertNotNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, TEST_SIZE)); + } +}