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 18 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 Down Expand Up @@ -57,6 +56,8 @@ public class AndroidWrappedKeyLoaderTest {
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 @@ -113,7 +114,7 @@ public void testGenerateKey() throws ClientException {
final AndroidWrappedKeyLoader keyLoader = new AndroidWrappedKeyLoader(MOCK_KEY_ALIAS, MOCK_KEY_FILE_PATH, context);
final SecretKey secretKey = keyLoader.generateRandomKey();

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

@Test
Expand All @@ -125,8 +126,8 @@ public void testReadKeyDirectly() throws ClientException {
// 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 @@ -145,7 +146,7 @@ public void testLoadKey() throws ClientException {

final SecretKey key = keyLoader.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());
}
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,26 +53,27 @@ 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 mPredefinedKeyLoader;
private final ISecretKeyProvider mKeyStoreKeyLoader;

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

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

@Override
@NonNull
public AES256KeyLoader getKeyLoaderForEncryption() {
public ISecretKeyProvider getKeyLoaderForEncryption() {
if (mPredefinedKeyLoader != null) {
return mPredefinedKeyLoader;
}
Expand All @@ -83,20 +83,20 @@ public AES256KeyLoader getKeyLoaderForEncryption() {

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

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

Logger.warn(methodTag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import com.microsoft.identity.common.internal.util.AndroidKeyStoreUtil;
import com.microsoft.identity.common.java.controllers.ExceptionAdapter;
import com.microsoft.identity.common.java.crypto.key.AES256KeyLoader;
import com.microsoft.identity.common.java.crypto.key.AbstractAES256SecretKeyProvider;
import com.microsoft.identity.common.java.crypto.key.KeyUtil;
import com.microsoft.identity.common.java.exception.ClientException;
import com.microsoft.identity.common.java.flighting.CommonFlight;
Expand Down Expand Up @@ -71,7 +71,15 @@
* Instead, the actual key that we use to encrypt/decrypt data is 'wrapped/encrypted' with the keystore key
* before it get saved to the file.
*/
public class AndroidWrappedKeyLoader extends AES256KeyLoader {
public class AndroidWrappedKeyLoader extends AbstractAES256SecretKeyProvider {

/**
* AES is 16 bytes (128 bits), thus PKCS#5 padding should not work, but in
* Java AES/CBC/PKCS5Padding is default(!) algorithm name, thus PKCS5 here
* probably doing PKCS7. We decide to go with Java default string.
*/
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";

private static final String TAG = AndroidWrappedKeyLoader.class.getSimpleName() + "#";

/**
Expand Down Expand Up @@ -177,12 +185,11 @@ public synchronized SecretKey getKey() throws ClientException {
return key;
}

@Override
@NonNull
protected SecretKey generateRandomKey() throws ClientException {
final String methodTag = TAG + ":generateRandomKey";

final SecretKey key = super.generateRandomKey();
final SecretKey key = AES_256_KEY_GENERATOR.generateRandomKey();
saveSecretKeyToStorage(key);

Logger.info(methodTag, "New key is generated with thumbprint: " +
Expand Down Expand Up @@ -217,7 +224,7 @@ protected SecretKey generateRandomKey() throws ClientException {
return null;
}

final SecretKey key = AndroidKeyStoreUtil.unwrap(wrappedSecretKey, getKeySpecAlgorithm(), keyPair, WRAP_ALGORITHM);
final SecretKey key = AndroidKeyStoreUtil.unwrap(wrappedSecretKey, KEY_ALGORITHM, keyPair, WRAP_ALGORITHM);

Logger.info(methodTag, "Key is loaded with thumbprint: " +
KeyUtil.getKeyThumbPrint(key));
Expand Down Expand Up @@ -259,7 +266,7 @@ private void saveSecretKeyToStorage(@NonNull final SecretKey unencryptedKey) thr
if (keyPair == null) {
Logger.info(methodTag, "No existing keypair. Generating a new one.");
final Span span = OTelUtility.createSpanFromParent(SpanName.KeyPairGeneration.name(), SpanExtension.current().getSpanContext());
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
try (final Scope ignored = SpanExtension.makeCurrentSpan(span)) {
keyPair = generateNewKeyPair();
span.setStatus(StatusCode.OK);
} catch (final ClientException e) {
Expand Down Expand Up @@ -329,7 +336,7 @@ private KeyPair generateNewKeyPairAPI23AndAbove() throws ClientException {
* Generate a new key pair wrapping key based on legacy logic. Call this for API < 23 or as fallback
* until new key gen specs are stable.
* @return key pair generated with legacy spec
* @throws ClientException
* @throws ClientException if there is an error generating the key pair.
*/
@NonNull
private KeyPair generateKeyPairWithLegacySpec() throws ClientException{
Expand Down Expand Up @@ -464,4 +471,10 @@ private File getKeyFile() {
mContext.getDir(mContext.getPackageName(), Context.MODE_PRIVATE),
mFilePath);
}

@NonNull
@Override
public String getCipherTransformation() {
return CIPHER_TRANSFORMATION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
import androidx.test.core.app.ApplicationProvider;

import com.microsoft.identity.common.adal.internal.AuthenticationSettings;
import com.microsoft.identity.common.java.crypto.key.AbstractSecretKeyLoader;
import com.microsoft.identity.common.java.crypto.key.ISecretKeyProvider;
import com.microsoft.identity.common.java.crypto.key.KeyUtil;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyLoader;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyProvider;

import org.junit.Assert;
import org.junit.Before;
Expand Down Expand Up @@ -62,7 +62,7 @@ public void setUp() {
public void testGetEncryptionKey() {
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);

final AbstractSecretKeyLoader loader = manager.getKeyLoaderForEncryption();
final ISecretKeyProvider loader = manager.getKeyLoaderForEncryption();
Assert.assertTrue(loader instanceof AndroidWrappedKeyLoader);
Assert.assertNotEquals(KeyUtil.getKeyThumbPrint(secretKeyMock), KeyUtil.getKeyThumbPrint(loader));
}
Expand All @@ -72,8 +72,8 @@ public void testGetEncryptionKey_PreDefinedKeyProvided() {
AuthenticationSettings.INSTANCE.setSecretKey(PREDEFINED_KEY);
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);

final AbstractSecretKeyLoader loader = manager.getKeyLoaderForEncryption();
Assert.assertTrue(loader instanceof PredefinedKeyLoader);
final ISecretKeyProvider loader = manager.getKeyLoaderForEncryption();
Assert.assertTrue(loader instanceof PredefinedKeyProvider);
Assert.assertEquals(KeyUtil.getKeyThumbPrint(secretKeyMock), KeyUtil.getKeyThumbPrint(loader));
}

Expand All @@ -84,7 +84,7 @@ public void testGetEncryptionKey_PreDefinedKeyProvided() {
@Test
public void testGetDecryptionKey_ForDataEncryptedWithKeyStoreKey() {
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);
final List<AbstractSecretKeyLoader> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_ANDROID_WRAPPED_KEY);
final List<ISecretKeyProvider> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_ANDROID_WRAPPED_KEY);

Assert.assertEquals(1, keyLoaderList.size());
Assert.assertTrue(keyLoaderList.get(0) instanceof AndroidWrappedKeyLoader);
Expand All @@ -99,7 +99,7 @@ public void testGetDecryptionKey_ForDataEncryptedWithKeyStoreKey() {
public void testGetDecryptionKey_ForDataEncryptedWithKeyStoreKey_PreDefinedKeyProvided() {
AuthenticationSettings.INSTANCE.setSecretKey(PREDEFINED_KEY);
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);
final List<AbstractSecretKeyLoader> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_ANDROID_WRAPPED_KEY);
final List<ISecretKeyProvider> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_ANDROID_WRAPPED_KEY);

Assert.assertEquals(1, keyLoaderList.size());
Assert.assertTrue(keyLoaderList.get(0) instanceof AndroidWrappedKeyLoader);
Expand All @@ -114,7 +114,7 @@ public void testGetDecryptionKey_ForDataEncryptedWithKeyStoreKey_PreDefinedKeyPr
public void testGetDecryptionKey_ForDataEncryptedWithPreDefinedKey() {
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);
try {
final List<AbstractSecretKeyLoader> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_PREDEFINED_KEY);
final List<ISecretKeyProvider> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_PREDEFINED_KEY);
} catch (IllegalStateException ex) {
Assert.assertEquals(
"Cipher Text is encrypted by USER_PROVIDED_KEY_IDENTIFIER, but mPredefinedKeyLoader is null.",
Expand All @@ -125,7 +125,7 @@ public void testGetDecryptionKey_ForDataEncryptedWithPreDefinedKey() {
public void testGetDecryptionKey_ForUnencryptedText_returns_empty_keyloader() {
AuthenticationSettings.INSTANCE.setIgnoreKeyLoaderNotFoundError(false);
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);
final List<AbstractSecretKeyLoader> keyLoaderList = manager.getKeyLoaderForDecryption("Unencrypted".getBytes(ENCODING_UTF8));
final List<ISecretKeyProvider> keyLoaderList = manager.getKeyLoaderForDecryption("Unencrypted".getBytes(ENCODING_UTF8));
Assert.assertEquals(0, keyLoaderList.size());
}

Expand All @@ -137,10 +137,10 @@ public void testGetDecryptionKey_ForUnencryptedText_returns_empty_keyloader() {
public void testGetDecryptionKey_ForDataEncryptedWithPreDefinedKey_PreDefinedKeyProvided() {
AuthenticationSettings.INSTANCE.setSecretKey(PREDEFINED_KEY);
final AndroidAuthSdkStorageEncryptionManager manager = new AndroidAuthSdkStorageEncryptionManager(context);
final List<AbstractSecretKeyLoader> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_PREDEFINED_KEY);
final List<ISecretKeyProvider> keyLoaderList = manager.getKeyLoaderForDecryption(TEXT_ENCRYPTED_BY_PREDEFINED_KEY);

Assert.assertEquals(1, keyLoaderList.size());
Assert.assertTrue(keyLoaderList.get(0) instanceof PredefinedKeyLoader);
Assert.assertTrue(keyLoaderList.get(0) instanceof PredefinedKeyProvider);
Assert.assertEquals(KeyUtil.getKeyThumbPrint(secretKeyMock), KeyUtil.getKeyThumbPrint(keyLoaderList.get(0)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
package com.microsoft.identity.common.shadows;

import com.microsoft.identity.common.crypto.AndroidAuthSdkStorageEncryptionManager;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyLoader;
import com.microsoft.identity.common.java.crypto.key.AES256KeyLoader;
import com.microsoft.identity.common.java.crypto.key.PredefinedKeyProvider;
import com.microsoft.identity.common.java.crypto.key.AbstractAES256SecretKeyProvider;

import org.robolectric.annotation.Implements;

Expand All @@ -35,14 +35,14 @@
public class ShadowAndroidSdkStorageEncryptionManager {

final byte[] encryptionKey = new byte[]{22, 78, -69, -66, 84, -65, 119, -9, -34, -80, 60, 67, -12, -117, 86, -47, -84, -24, -18, 121, 70, 32, -110, 51, -93, -10, -93, -110, 124, -68, -42, -119};
final AES256KeyLoader mUserDefinedKey = new PredefinedKeyLoader("MOCK_ALIAS", encryptionKey);
final AbstractAES256SecretKeyProvider mUserDefinedKey = new PredefinedKeyProvider("MOCK_ALIAS", encryptionKey);

public AES256KeyLoader getKeyLoaderForEncryption() {
public AbstractAES256SecretKeyProvider getKeyLoaderForEncryption() {
return mUserDefinedKey;
}

public List<AES256KeyLoader> getKeyLoaderForDecryption(byte[] cipherText) {
return new ArrayList<AES256KeyLoader>() {{
public List<AbstractAES256SecretKeyProvider> getKeyLoaderForDecryption(byte[] cipherText) {
return new ArrayList<AbstractAES256SecretKeyProvider>() {{
add(mUserDefinedKey);
}};
}
Expand Down
Loading