Skip to content

Commit a998e50

Browse files
committed
Update minSdk from 21 to 26 and remove dead code paths for API < 23
1 parent 6a6c95d commit a998e50

8 files changed

Lines changed: 45 additions & 195 deletions

File tree

V4_MIGRATION_GUIDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update
99
## Table of Contents
1010

1111
- [**Requirements Changes**](#requirements-changes)
12+
+ [Minimum SDK Version](#minimum-sdk-version)
1213
+ [Java Version](#java-version)
1314
+ [Gradle and Android Gradle Plugin](#gradle-and-android-gradle-plugin)
1415
+ [Kotlin Version](#kotlin-version)
@@ -32,6 +33,22 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update
3233

3334
## Requirements Changes
3435

36+
### Minimum SDK Version
37+
38+
v4 requires **API level 26** (Android 8.0 Oreo) or later (previously API 21 / Android 5.0 Lollipop).
39+
40+
Update your `build.gradle` if your `minSdk` is below 26:
41+
42+
```groovy
43+
android {
44+
defaultConfig {
45+
minSdk 26
46+
}
47+
}
48+
```
49+
50+
**Impact:** Apps targeting devices running Android 7.1 (API 25) or lower will need to increase their minimum SDK version, or continue using v3.
51+
3552
### Java Version
3653

3754
v4 requires **Java 17** or later (previously Java 8+).

auth0/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ android {
4242
}
4343

4444
defaultConfig {
45-
minSdkVersion 21
45+
minSdkVersion 26
4646
targetSdk 36
4747
versionCode 1
4848
versionName project.version

auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.auth0.android.authentication.storage;
22

3-
import android.app.KeyguardManager;
43
import android.content.Context;
5-
import android.content.Intent;
64
import android.os.Build;
7-
import android.security.KeyPairGeneratorSpec;
85
import android.security.keystore.KeyGenParameterSpec;
96
import android.security.keystore.KeyProperties;
107
import android.text.TextUtils;
@@ -49,7 +46,7 @@
4946

5047
/**
5148
* Created by lbalmaceda on 8/24/17.
52-
* Class to handle encryption/decryption cryptographic operations using AES and RSA algorithms in devices with API 19 or higher.
49+
* Class to handle encryption/decryption cryptographic operations using AES and RSA algorithms in devices with API 26 or higher.
5350
*/
5451
@SuppressWarnings("WeakerAccess")
5552
class CryptoUtil {
@@ -180,43 +177,18 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws CryptoException, IncompatibleDe
180177
Calendar start = Calendar.getInstance();
181178
Calendar end = Calendar.getInstance();
182179
end.add(Calendar.YEAR, 25);
183-
AlgorithmParameterSpec spec;
184180
X500Principal principal = new X500Principal("CN=Auth0.Android,O=Auth0");
185181

186-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
187-
spec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
188-
.setCertificateSubject(principal)
189-
.setCertificateSerialNumber(BigInteger.ONE)
190-
.setCertificateNotBefore(start.getTime())
191-
.setCertificateNotAfter(end.getTime())
192-
.setKeySize(RSA_KEY_SIZE)
193-
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
194-
.setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256)
195-
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
196-
.build();
197-
} else {
198-
//Following code is for API 18-22
199-
//Generate new RSA KeyPair and save it on the KeyStore
200-
KeyPairGeneratorSpec.Builder specBuilder = new KeyPairGeneratorSpec.Builder(context)
201-
.setAlias(KEY_ALIAS)
202-
.setSubject(principal)
203-
.setKeySize(RSA_KEY_SIZE)
204-
.setSerialNumber(BigInteger.ONE)
205-
.setStartDate(start.getTime())
206-
.setEndDate(end.getTime());
207-
208-
KeyguardManager kManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
209-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
210-
//The next call can return null when the LockScreen is not configured
211-
Intent authIntent = kManager.createConfirmDeviceCredentialIntent(null, null);
212-
boolean keyguardEnabled = kManager.isKeyguardSecure() && authIntent != null;
213-
if (keyguardEnabled) {
214-
//If a ScreenLock is setup, protect this key pair.
215-
specBuilder.setEncryptionRequired();
216-
}
217-
}
218-
spec = specBuilder.build();
219-
}
182+
AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
183+
.setCertificateSubject(principal)
184+
.setCertificateSerialNumber(BigInteger.ONE)
185+
.setCertificateNotBefore(start.getTime())
186+
.setCertificateNotAfter(end.getTime())
187+
.setKeySize(RSA_KEY_SIZE)
188+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
189+
.setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256)
190+
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
191+
.build();
220192

221193
KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM_RSA, ANDROID_KEY_STORE);
222194
generator.initialize(spec);

auth0/src/main/java/com/auth0/android/dpop/DPoPKeyStore.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ internal open class DPoPKeyStore {
3030
}
3131

3232
fun generateKeyPair(context: Context, useStrongBox: Boolean = true) {
33-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
34-
throw DPoPException.UNSUPPORTED_ERROR
35-
}
3633
try {
3734
val keyPairGenerator = KeyPairGenerator.getInstance(
3835
KeyProperties.KEY_ALGORITHM_EC,

auth0/src/main/java/com/auth0/android/provider/BrowserPicker.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import android.content.pm.PackageManager;
55
import android.content.pm.ResolveInfo;
66
import android.net.Uri;
7-
import android.os.Build;
87
import android.os.Parcel;
98
import android.os.Parcelable;
109
import androidx.annotation.NonNull;
@@ -116,7 +115,7 @@ String getBestBrowserPackage(@NonNull PackageManager pm) {
116115
defaultBrowser = webHandler.activityInfo.packageName;
117116
}
118117

119-
final List<ResolveInfo> availableBrowsers = pm.queryIntentActivities(browserIntent, Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL : 0);
118+
final List<ResolveInfo> availableBrowsers = pm.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL);
120119
final List<String> regularBrowsers = new ArrayList<>();
121120
final List<String> customTabsBrowsers = new ArrayList<>();
122121
final boolean isFilterEnabled = allowedPackages != null;

auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java

Lines changed: 2 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616
import static org.mockito.Mockito.times;
1717
import static org.mockito.Mockito.when;
1818

19-
import android.app.KeyguardManager;
2019
import android.content.Context;
21-
import android.content.Intent;
22-
import android.security.KeyPairGeneratorSpec;
2320
import android.security.keystore.KeyGenParameterSpec;
2421
import android.security.keystore.KeyProperties;
2522
import android.text.TextUtils;
@@ -72,7 +69,7 @@
7269
/**
7370
* This test class uses MockedStatic for static method mocking (KeyStore, Cipher, KeyGenerator,
7471
* KeyPairGenerator, Base64, TextUtils) and relies on Robolectric shadows for Android SDK
75-
* builder classes like KeyGenParameterSpec.Builder and KeyPairGeneratorSpec.Builder.
72+
* builder classes like KeyGenParameterSpec.Builder.
7673
* Note: Robolectric 4.x requires SDK 21+ (Android 5.0+).
7774
*/
7875
@RunWith(RobolectricTestRunner.class)
@@ -172,139 +169,7 @@ public void shouldThrowWhenRSAKeyAliasIsInvalid() {
172169
}
173170

174171
@Test
175-
@Config(sdk = 21)
176-
public void shouldNotCreateProtectedRSAKeyPairIfMissingAndLockScreenEnabled() throws Exception {
177-
Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false);
178-
KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class);
179-
Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry);
180-
181-
ArgumentCaptor<AlgorithmParameterSpec> specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class);
182-
183-
//Set LockScreen as Enabled but with null device credential intent
184-
KeyguardManager kService = Mockito.mock(KeyguardManager.class);
185-
Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService);
186-
Mockito.when(kService.isKeyguardSecure()).thenReturn(true);
187-
Mockito.when(kService.createConfirmDeviceCredentialIntent(nullable(CharSequence.class), nullable(CharSequence.class))).thenReturn(null);
188-
189-
final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry();
190-
191-
Mockito.verify(keyPairGenerator).initialize(specCaptor.capture());
192-
Mockito.verify(keyPairGenerator).generateKeyPair();
193-
194-
// Verify the spec properties directly (Robolectric shadows the real builder)
195-
KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue();
196-
assertThat(spec.getKeySize(), is(2048));
197-
assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS));
198-
assertThat(spec.getSerialNumber(), is(BigInteger.ONE));
199-
// Note: setEncryptionRequired was NOT called since authIntent is null
200-
201-
assertThat(spec.getSubjectDN(), is(notNullValue()));
202-
assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL));
203-
204-
assertThat(spec.getStartDate(), is(notNullValue()));
205-
long diffMillis = spec.getStartDate().getTime() - new Date().getTime();
206-
long days = TimeUnit.MILLISECONDS.toDays(diffMillis);
207-
assertThat(days, is(0L)); //Date is Today
208-
209-
assertThat(spec.getEndDate(), is(notNullValue()));
210-
diffMillis = spec.getEndDate().getTime() - new Date().getTime();
211-
days = TimeUnit.MILLISECONDS.toDays(diffMillis);
212-
assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days
213-
214-
assertThat(entry, is(expectedEntry));
215-
}
216-
217-
@Test
218-
@Config(sdk = 21)
219-
public void shouldCreateUnprotectedRSAKeyPairIfMissingAndLockScreenDisabledOnAPI21() throws Exception {
220-
221-
Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false);
222-
KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class);
223-
Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry);
224-
225-
ArgumentCaptor<AlgorithmParameterSpec> specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class);
226-
227-
//Set LockScreen as Disabled
228-
KeyguardManager kService = Mockito.mock(KeyguardManager.class);
229-
Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService);
230-
Mockito.when(kService.isKeyguardSecure()).thenReturn(false);
231-
Mockito.when(kService.createConfirmDeviceCredentialIntent(any(CharSequence.class), any(CharSequence.class))).thenReturn(null);
232-
233-
final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry();
234-
235-
Mockito.verify(keyPairGenerator).initialize(specCaptor.capture());
236-
Mockito.verify(keyPairGenerator).generateKeyPair();
237-
238-
// Verify the spec properties directly
239-
KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue();
240-
assertThat(spec.getKeySize(), is(2048));
241-
assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS));
242-
assertThat(spec.getSerialNumber(), is(BigInteger.ONE));
243-
244-
assertThat(spec.getSubjectDN(), is(notNullValue()));
245-
assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL));
246-
247-
assertThat(spec.getStartDate(), is(notNullValue()));
248-
long diffMillis = spec.getStartDate().getTime() - new Date().getTime();
249-
long days = TimeUnit.MILLISECONDS.toDays(diffMillis);
250-
assertThat(days, is(0L)); //Date is Today
251-
252-
assertThat(spec.getEndDate(), is(notNullValue()));
253-
diffMillis = spec.getEndDate().getTime() - new Date().getTime();
254-
days = TimeUnit.MILLISECONDS.toDays(diffMillis);
255-
assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days
256-
257-
assertThat(entry, is(expectedEntry));
258-
}
259-
260-
@Test
261-
@Config(sdk = 21)
262-
public void shouldCreateProtectedRSAKeyPairIfMissingAndLockScreenEnabledOnAPI21() throws Exception {
263-
264-
Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false);
265-
KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class);
266-
Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry);
267-
268-
ArgumentCaptor<AlgorithmParameterSpec> specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class);
269-
270-
//Set LockScreen as Enabled
271-
KeyguardManager kService = Mockito.mock(KeyguardManager.class);
272-
Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService);
273-
Mockito.when(kService.isKeyguardSecure()).thenReturn(true);
274-
Mockito.when(kService.createConfirmDeviceCredentialIntent(any(), any())).thenReturn(new Intent());
275-
276-
final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry();
277-
278-
Mockito.verify(keyPairGenerator).initialize(specCaptor.capture());
279-
Mockito.verify(keyPairGenerator).generateKeyPair();
280-
281-
// Verify the spec properties directly
282-
KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue();
283-
assertThat(spec.getKeySize(), is(2048));
284-
assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS));
285-
assertThat(spec.getSerialNumber(), is(BigInteger.ONE));
286-
// Note: setEncryptionRequired WAS called since lock screen is enabled with valid authIntent
287-
assertThat(spec.isEncryptionRequired(), is(true));
288-
289-
assertThat(spec.getSubjectDN(), is(notNullValue()));
290-
assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL));
291-
292-
assertThat(spec.getStartDate(), is(notNullValue()));
293-
long diffMillis = spec.getStartDate().getTime() - new Date().getTime();
294-
long days = TimeUnit.MILLISECONDS.toDays(diffMillis);
295-
assertThat(days, is(0L)); //Date is Today
296-
297-
assertThat(spec.getEndDate(), is(notNullValue()));
298-
diffMillis = spec.getEndDate().getTime() - new Date().getTime();
299-
days = TimeUnit.MILLISECONDS.toDays(diffMillis);
300-
assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days
301-
302-
assertThat(entry, is(expectedEntry));
303-
}
304-
305-
@Test
306-
@Config(sdk = 23)
307-
public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception {
172+
public void shouldCreateRSAKeyPairIfMissing() throws Exception {
308173

309174
Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false);
310175
KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class);

auth0/src/test/java/com/auth0/android/util/Auth0UserAgentTest.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ public class Auth0UserAgentTest {
2626
//Testing Android version only for a few SDKs
2727

2828
@Test
29-
@Config(sdk = 21)
30-
public void shouldAlwaysIncludeAndroidVersionAPI21() {
29+
@Config(sdk = 28)
30+
public void shouldAlwaysIncludeAndroidVersionAPI28() {
3131
Auth0UserAgent auth0UserAgent = new Auth0UserAgent("auth0-java", "1.2.3");
3232
assertThat(auth0UserAgent.getEnvironment(), is(notNullValue()));
33-
assertThat(auth0UserAgent.getEnvironment().get("android"), is("21"));
33+
assertThat(auth0UserAgent.getEnvironment().get("android"), is("28"));
3434
}
3535

3636
@Test
37-
@Config(sdk = 23)
38-
public void shouldAlwaysIncludeAndroidVersionAPI23() {
37+
@Config(sdk = 30)
38+
public void shouldAlwaysIncludeAndroidVersionAPI30() {
3939
Auth0UserAgent auth0UserAgent = new Auth0UserAgent("auth0-java", "1.2.3");
4040
assertThat(auth0UserAgent.getEnvironment(), is(notNullValue()));
41-
assertThat(auth0UserAgent.getEnvironment().get("android"), is("23"));
41+
assertThat(auth0UserAgent.getEnvironment().get("android"), is("30"));
4242
}
4343

4444
@Test
@@ -98,40 +98,40 @@ public void shouldGetLibraryVersion() {
9898
}
9999

100100
@Test
101-
@Config(sdk = 23)
101+
@Config(sdk = 28)
102102
public void shouldGenerateCompleteTelemetryBase64Value() {
103103
Gson gson = new Gson();
104104
Type mapType = new TypeToken<Map<String, Object>>() {
105105
}.getType();
106106

107107
Auth0UserAgent auth0UserAgentComplete = new Auth0UserAgent("auth0-java", "1.0.0", "1.2.3");
108108
String value = auth0UserAgentComplete.getValue();
109-
assertThat(value, is("eyJuYW1lIjoiYXV0aDAtamF2YSIsImVudiI6eyJhbmRyb2lkIjoiMjMiLCJhdXRoMC5hbmRyb2lkIjoiMS4yLjMifSwidmVyc2lvbiI6IjEuMC4wIn0="));
109+
assertThat(value, is(notNullValue()));
110110
String completeString = new String(Base64.decode(value, Base64.URL_SAFE | Base64.NO_WRAP), StandardCharsets.UTF_8);
111111
Map<String, Object> complete = gson.fromJson(completeString, mapType);
112112
assertThat((String) complete.get("name"), is("auth0-java"));
113113
assertThat((String) complete.get("version"), is("1.0.0"));
114114
Map<String, Object> completeEnv = (Map<String, Object>) complete.get("env");
115115
assertThat((String) completeEnv.get("auth0.android"), is("1.2.3"));
116-
assertThat((String) completeEnv.get("android"), is("23"));
116+
assertThat((String) completeEnv.get("android"), is("28"));
117117
}
118118

119119
@Test
120-
@Config(sdk = 23)
120+
@Config(sdk = 28)
121121
public void shouldGenerateBasicTelemetryBase64Value() {
122122
Gson gson = new Gson();
123123
Type mapType = new TypeToken<Map<String, Object>>() {
124124
}.getType();
125125

126126
Auth0UserAgent auth0UserAgentBasic = new Auth0UserAgent("auth0-python", "99.3.1");
127127
String value = auth0UserAgentBasic.getValue();
128-
assertThat(value, is("eyJuYW1lIjoiYXV0aDAtcHl0aG9uIiwiZW52Ijp7ImFuZHJvaWQiOiIyMyJ9LCJ2ZXJzaW9uIjoiOTkuMy4xIn0="));
128+
assertThat(value, is(notNullValue()));
129129
String basicString = new String(Base64.decode(value, Base64.URL_SAFE | Base64.NO_WRAP), StandardCharsets.UTF_8);
130130
Map<String, Object> basic = gson.fromJson(basicString, mapType);
131131
assertThat((String) basic.get("name"), is("auth0-python"));
132132
assertThat((String) basic.get("version"), is("99.3.1"));
133133
Map<String, Object> basicEnv = (Map<String, Object>) basic.get("env");
134134
assertThat(basicEnv.get("auth0.android"), is(nullValue()));
135-
assertThat((String) basicEnv.get("android"), is("23"));
135+
assertThat((String) basicEnv.get("android"), is("28"));
136136
}
137137
}

sample/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ android {
88
compileSdk 36
99

1010
defaultConfig {
11-
minSdk 24
11+
minSdk 26
1212
targetSdk 36
1313
versionCode 1
1414
versionName "1.0"

0 commit comments

Comments
 (0)