Skip to content

Replace AbstractSecretKeyLoader with ISecretKeyProvider, Fixes AB#3297746 #2666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
df9555d
add interface ISecretKeyLoader
p3dr0rv Jun 12, 2025
0b256af
nits
p3dr0rv Jun 12, 2025
1637d5c
nits
p3dr0rv Jun 12, 2025
baf65c7
nits
p3dr0rv Jun 12, 2025
e8a1f8f
fix interface error
p3dr0rv Jun 13, 2025
f644d7a
fix error
p3dr0rv Jun 13, 2025
3e203d5
fix tests
p3dr0rv Jun 13, 2025
53377dc
Merge branch 'dev' into pedroro/interface-for-secretkeyloader
p3dr0rv Jun 16, 2025
6a96645
Update AndroidWrappedKeyLoader.java
p3dr0rv Jun 16, 2025
56aeb2a
Refactor key generation to use a shared AES256SecretKeyGenerator inst…
p3dr0rv Jun 19, 2025
a4a3ed4
Merge branch 'dev' into pedroro/interface-for-secretkeyloader
p3dr0rv Jun 20, 2025
32a1bba
Merge branch 'dev' into pedroro/interface-for-secretkeyloader
p3dr0rv Jun 23, 2025
e689c61
Merge branch 'dev' into pedroro/interface-for-secretkeyloader
p3dr0rv Jun 23, 2025
f994746
Merge branch 'dev' into pedroro/interface-for-secretkeyloader
p3dr0rv Jul 3, 2025
5aea5bc
Refactor key loading mechanism to use ISecretKeyProvider interface, r…
p3dr0rv Jul 3, 2025
2b56431
Remove keySize and keyAlgorithm properties from ISecretKeyGenerator a…
p3dr0rv Jul 3, 2025
cd2b17a
fix issues
p3dr0rv Jul 3, 2025
fe9d4f6
add changelog
p3dr0rv Jul 3, 2025
c2c8273
Refactor key management to implement ISecretKeyProvider interface, re…
p3dr0rv Jul 3, 2025
37a9022
fix tests
p3dr0rv Jul 3, 2025
4176ee0
Refactor key loader to key provider in encryption manager
p3dr0rv Jul 3, 2025
0411dfb
Rename method to setIgnoreKeyProviderNotFoundError for consistency in…
p3dr0rv Jul 3, 2025
1096d86
Rename key loader variable to key provider for consistency in Android…
p3dr0rv Jul 3, 2025
866dd22
Add TODO comment for wrapCipher usage and simplify getKey method in P…
p3dr0rv Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
vNext
----------
- [MINOR] Replace AbstractSecretKeyLoader with ISecretKeyProvider (#2666)
- [MINOR] Update IP phone app teams signature constants to use SHA-512 format (#2700)
- [MINOR] Updating handling of ssl error received in Android WebView's onReceivedSslError callback (#2691)
- [MINOR] Fixing the sign in screens when edge to edge is enabled (#2665)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import com.microsoft.identity.common.adal.internal.AuthenticationSettings;
import com.microsoft.identity.common.internal.util.AndroidKeyStoreUtil;
import com.microsoft.identity.common.java.crypto.key.AES256KeyLoader;
import com.microsoft.identity.common.java.exception.ClientException;
import com.microsoft.identity.common.java.util.FileUtil;

Expand All @@ -50,13 +49,15 @@

import static com.microsoft.identity.common.java.exception.ClientException.INVALID_KEY;

public class AndroidWrappedKeyLoaderTest {
public class AndroidWrappedKeyProviderTest {

final Context context = ApplicationProvider.getApplicationContext();
final String MOCK_KEY_ALIAS = "MOCK_KEY_ALIAS";
final String MOCK_KEY_FILE_PATH = "MOCK_KEY_FILE_PATH";
final int TEST_LOOP = 100;

final String AES_ALGORITHM = "AES";

@Before
public void setUp() throws Exception {
// Everything is on clean slate.
Expand Down Expand Up @@ -110,23 +111,23 @@ private AlgorithmParameterSpec getMockKeyPairGeneratorSpec(final String alias) {

@Test
public void testGenerateKey() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey secretKey = keyLoader.generateRandomKey();
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey secretKey = keyProvider.generateRandomKey();

Assert.assertEquals(AES256KeyLoader.AES_ALGORITHM, secretKey.getAlgorithm());
Assert.assertEquals(AES_ALGORITHM, secretKey.getAlgorithm());
}

@Test
public void testReadKeyDirectly() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = initKeyLoaderWithKeyEntry();
final SecretKey secretKey = keyLoader.getKey();
final SecretKey storedSecretKey = keyLoader.readSecretKeyFromStorage();
final AndroidWrappedKeyProvider keyProvider = initkeyProviderWithKeyEntry();
final SecretKey secretKey = keyProvider.getKey();
final SecretKey storedSecretKey = keyProvider.readSecretKeyFromStorage();

// They're not the same object!
Assert.assertNotSame(secretKey, storedSecretKey);

Assert.assertEquals(AES256KeyLoader.AES_ALGORITHM, secretKey.getAlgorithm());
Assert.assertEquals(AES256KeyLoader.AES_ALGORITHM, storedSecretKey.getAlgorithm());
Assert.assertEquals(AES_ALGORITHM, secretKey.getAlgorithm());
Assert.assertEquals(AES_ALGORITHM, storedSecretKey.getAlgorithm());

Assert.assertNotNull(secretKey.getEncoded());
Assert.assertNotNull(storedSecretKey.getEncoded());
Expand All @@ -138,33 +139,33 @@ public void testReadKeyDirectly() throws ClientException {
public void testLoadKey() throws ClientException {
// Nothing exists. This load key function should generate a key if the key hasn't exist.
Assert.assertNull(AndroidKeyStoreUtil.readKey(MOCK_KEY_ALIAS));
Assert.assertNull(FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyLoader.KEY_FILE_SIZE));
Assert.assertNull(FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyProvider.KEY_FILE_SIZE));

final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey secretKey = keyLoader.getKey();
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey secretKey = keyProvider.getKey();

final SecretKey key = keyLoader.getKeyCache().getData();
final SecretKey key = keyProvider.getKeyCache().getData();
Assert.assertNotNull(key);
Assert.assertEquals(AES256KeyLoader.AES_ALGORITHM, secretKey.getAlgorithm());
Assert.assertEquals(AES_ALGORITHM, secretKey.getAlgorithm());
Assert.assertArrayEquals(secretKey.getEncoded(), key.getEncoded());
Assert.assertEquals(secretKey.getFormat(), key.getFormat());
}

@Test
public void testLoadKeyFromCorruptedFile_TruncatedExisingKey() throws ClientException {
// Create a new Keystore-wrapped key.
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
keyLoader.generateRandomKey();
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
keyProvider.generateRandomKey();

final byte[] wrappedKey = FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyLoader.KEY_FILE_SIZE);
final byte[] wrappedKey = FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyProvider.KEY_FILE_SIZE);
Assert.assertNotNull(wrappedKey);

// Overwrite the key file with corrupted data.
FileUtil.writeDataToFile(Arrays.copyOfRange(wrappedKey, 0, wrappedKey.length/2), getKeyFile());

// It should fail to read, with an exception, and everything should be wiped.
try{
keyLoader.readSecretKeyFromStorage();
keyProvider.readSecretKeyFromStorage();
Assert.fail();
} catch (ClientException e){
Assert.assertEquals(INVALID_KEY, e.getErrorCode());
Expand All @@ -174,24 +175,24 @@ public void testLoadKeyFromCorruptedFile_TruncatedExisingKey() throws ClientExce
Assert.assertFalse(getKeyFile().exists());

// the next read should be unblocked.
Assert.assertNull(keyLoader.readSecretKeyFromStorage());
Assert.assertNull(keyProvider.readSecretKeyFromStorage());
}

@Test
public void testLoadKeyFromCorruptedFile_InjectGarbage() throws ClientException {
// Create a new Keystore-wrapped key.
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
keyLoader.generateRandomKey();
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
keyProvider.generateRandomKey();

final byte[] wrappedKey = FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyLoader.KEY_FILE_SIZE);
final byte[] wrappedKey = FileUtil.readFromFile(getKeyFile(), AndroidWrappedKeyProvider.KEY_FILE_SIZE);
Assert.assertNotNull(wrappedKey);

// Overwrite the key file with corrupted data.
FileUtil.writeDataToFile(new byte[]{10, 20, 30, 40}, getKeyFile());

// It should fail to read, with an exception, and everything should be wiped.
try{
keyLoader.readSecretKeyFromStorage();
keyProvider.readSecretKeyFromStorage();
Assert.fail();
} catch (ClientException e){
Assert.assertEquals(INVALID_KEY, e.getErrorCode());
Expand All @@ -201,18 +202,18 @@ public void testLoadKeyFromCorruptedFile_InjectGarbage() throws ClientException
Assert.assertFalse(getKeyFile().exists());

// the next read should be unblocked.
Assert.assertNull(keyLoader.readSecretKeyFromStorage());
Assert.assertNull(keyProvider.readSecretKeyFromStorage());
}

// 1s With Google Pixel XL, OS Version 29 (100 loop)
@Test
@Ignore
public void testPerf_WithCachedKey() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);

long timeStartLoop = System.nanoTime();
for (int i = 0; i < TEST_LOOP; i++) {
keyLoader.getKey();
keyProvider.getKey();
}
long timeFinishLoop = System.nanoTime();

Expand All @@ -223,12 +224,12 @@ public void testPerf_WithCachedKey() throws ClientException {
@Test
@Ignore
public void testPerf_NoCachedKey() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);

long timeStartLoopNotCached = System.nanoTime();
for (int i = 0; i < 100; i++) {
keyLoader.getKeyCache().clear();
keyLoader.getKey();
keyProvider.getKeyCache().clear();
keyProvider.getKey();
}
long timeFinishLoopNotCached = System.nanoTime();

Expand All @@ -240,31 +241,31 @@ public void testPerf_NoCachedKey() throws ClientException {
*/
@Test
public void testLoadDeletedKeyStoreKey() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = initKeyLoaderWithKeyEntry();
final AndroidWrappedKeyProvider keyProvider = initkeyProviderWithKeyEntry();

AndroidKeyStoreUtil.deleteKey(MOCK_KEY_ALIAS);

// Cached key also be wiped.
final SecretKey key = keyLoader.getKeyCache().getData();
final SecretKey key = keyProvider.getKeyCache().getData();
Assert.assertNull(key);
}

@Test
public void testLoadDeletedKeyFile() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = initKeyLoaderWithKeyEntry();
final AndroidWrappedKeyProvider keyProvider = initkeyProviderWithKeyEntry();

FileUtil.deleteFile(getKeyFile());

// Cached key also be wiped.
final SecretKey key = keyLoader.getKeyCache().getData();
final SecretKey key = keyProvider.getKeyCache().getData();
Assert.assertNull(key);
}

private AndroidWrappedKeyLoader initKeyLoaderWithKeyEntry() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey key = keyLoader.getKey();
private AndroidWrappedKeyProvider initkeyProviderWithKeyEntry() throws ClientException {
final AndroidWrappedKeyProvider keyProvider = new AndroidWrappedKeyProvider(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey key = keyProvider.getKey();
Assert.assertNotNull(key);
Assert.assertNotNull(keyLoader.getKeyCache().getData());
return keyLoader;
Assert.assertNotNull(keyProvider.getKeyCache().getData());
return keyProvider;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ public boolean getDisableWebViewHardwareAcceleration() {
* @param shouldIgnore if true, ignores keyloader not found errors
*/
@SuppressFBWarnings(ME_ENUM_FIELD_SETTER)
public void setIgnoreKeyLoaderNotFoundError(boolean shouldIgnore) {
public void setIgnoreKeyProviderNotFoundError(boolean shouldIgnore) {
mIgnoreKeyLoaderNotFoundError = shouldIgnore;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@

import com.microsoft.identity.common.adal.internal.AuthenticationSettings;
import com.microsoft.identity.common.java.crypto.StorageEncryptionManager;
import com.microsoft.identity.common.java.crypto.key.AES256KeyLoader;
import com.microsoft.identity.common.java.crypto.key.AbstractSecretKeyLoader;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyLoader;
import com.microsoft.identity.common.java.crypto.key.ISecretKeyProvider;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyProvider;
import com.microsoft.identity.common.logging.Logger;

import java.util.Collections;
Expand All @@ -54,49 +53,50 @@ public class AndroidAuthSdkStorageEncryptionManager extends StorageEncryptionMan
*/
public static final String WRAPPED_KEY_FILE_NAME = "adalks";

private final PredefinedKeyLoader mPredefinedKeyLoader;
private final AndroidWrappedKeyLoader mKeyStoreKeyLoader;
private final PredefinedKeyProvider mPredefinedKeyProvider;
private final ISecretKeyProvider mKeyStoreKeyProvider;

public AndroidAuthSdkStorageEncryptionManager(@NonNull final Context context) {
if (AuthenticationSettings.INSTANCE.getSecretKeyData() == null) {
mPredefinedKeyLoader = null;
mPredefinedKeyProvider = null;
} else {
mPredefinedKeyLoader = new PredefinedKeyLoader("USER_DEFINED_KEY",
mPredefinedKeyProvider = new PredefinedKeyProvider("USER_DEFINED_KEY",
AuthenticationSettings.INSTANCE.getSecretKeyData());
}

mKeyStoreKeyLoader = new AndroidWrappedKeyLoader(
mKeyStoreKeyProvider = new AndroidWrappedKeyProvider(
WRAPPING_KEY_ALIAS,
WRAPPED_KEY_FILE_NAME,
context);
context
);
}

@Override
@NonNull
public AES256KeyLoader getKeyLoaderForEncryption() {
if (mPredefinedKeyLoader != null) {
return mPredefinedKeyLoader;
public ISecretKeyProvider getKeyProviderForEncryption() {
if (mPredefinedKeyProvider != null) {
return mPredefinedKeyProvider;
}

return mKeyStoreKeyLoader;
return mKeyStoreKeyProvider;
}

@Override
@NonNull
public List<AbstractSecretKeyLoader> getKeyLoaderForDecryption(byte[] cipherText) {
public List<ISecretKeyProvider> getKeyProviderForDecryption(byte[] cipherText) {
final String methodTag = TAG + ":getKeyLoaderForDecryption";

final String keyIdentifier = getKeyIdentifierFromCipherText(cipherText);
if (PredefinedKeyLoader.USER_PROVIDED_KEY_IDENTIFIER.equalsIgnoreCase(keyIdentifier)) {
if (mPredefinedKeyLoader != null) {
return Collections.<AbstractSecretKeyLoader>singletonList(mPredefinedKeyLoader);
if (PredefinedKeyProvider.USER_PROVIDED_KEY_IDENTIFIER.equalsIgnoreCase(keyIdentifier)) {
if (mPredefinedKeyProvider != null) {
return Collections.singletonList(mPredefinedKeyProvider);
} else {
throw new IllegalStateException(
"Cipher Text is encrypted by USER_PROVIDED_KEY_IDENTIFIER, " +
"but mPredefinedKeyLoader is null.");
"but mPredefinedKeyProvider is null.");
}
} else if (AndroidWrappedKeyLoader.WRAPPED_KEY_KEY_IDENTIFIER.equalsIgnoreCase(keyIdentifier)) {
return Collections.<AbstractSecretKeyLoader>singletonList(mKeyStoreKeyLoader);
} else if (mKeyStoreKeyProvider.getKeyTypeIdentifier().equalsIgnoreCase(keyIdentifier)) {
return Collections.singletonList(mKeyStoreKeyProvider);
}

Logger.warn(methodTag,
Expand Down
Loading