diff --git a/guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentsAPIClient.java b/guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentsAPIClient.java index cd5789b..01714ad 100644 --- a/guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentsAPIClient.java +++ b/guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentsAPIClient.java @@ -8,14 +8,17 @@ import androidx.annotation.NonNull; import com.auth0.android.guardian.sdk.networking.RequestFactory; -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.interfaces.RSAPrivateKey; +import java.security.Signature; +import java.security.SignatureException; import java.security.interfaces.RSAPublicKey; import java.util.Date; import java.util.HashMap; @@ -84,11 +87,17 @@ private static String createProofOfPossessionAssertion(HttpUrl url, PrivateKey p claims.put("jti", UUID.randomUUID().toString()); claims.put("iat", currentTime); - Algorithm alg = Algorithm.RSA256(null, (RSAPrivateKey) privateKey); - return JWT.create() - .withHeader(headers) - .withPayload(claims) - .sign(alg); + try { + Gson gson = new GsonBuilder().create(); + String headerAndPayload = base64UrlSafeEncode(gson.toJson(headers).getBytes()) + + "." + base64UrlSafeEncode(gson.toJson(claims).getBytes()); + Signature signer = Signature.getInstance("SHA256withRSA"); + signer.initSign(privateKey); + signer.update(headerAndPayload.getBytes()); + return headerAndPayload + "." + base64UrlSafeEncode(signer.sign()); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException exception) { + throw new GuardianException("Unable to generate the signed DPoP assertion", exception); + } } /** diff --git a/guardian/src/test/java/com/auth0/android/guardian/sdk/RichConsentsAPIClientTest.java b/guardian/src/test/java/com/auth0/android/guardian/sdk/RichConsentsAPIClientTest.java index 3251021..63fd9aa 100644 --- a/guardian/src/test/java/com/auth0/android/guardian/sdk/RichConsentsAPIClientTest.java +++ b/guardian/src/test/java/com/auth0/android/guardian/sdk/RichConsentsAPIClientTest.java @@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -143,6 +144,21 @@ public void shouldFetchRichConsentWithRichAuthorizationDetails() throws Exceptio verifyNoMoreInteractions(fetchCallback); } + @Test + public void shouldFetchRichConsentWithKeystorePrivateKey() throws Exception { + mockAPI.willReturnRichConsent(CONSENT_ID, AUDIENCE, SCOPE, BINDING_MESSAGE); + + // Regression test for PR #150 / ESD-66431: fetch() previously crashed with + // ClassCastException when the enrollment key was an Android Keystore-backed + // RSAPrivateKey. The fix uses Signature directly instead of casting. + richConsentsAPIClient + .fetch(CONSENT_ID, TRANSACTION_TOKEN, keyPair.getPrivate(), keyPair.getPublic()) + .start(fetchCallback); + + verify(fetchCallback, timeout(100)).onSuccess(any()); + verifyNoMoreInteractions(fetchCallback); + } + private void verifyDPoPAssertion(String assertion) { Algorithm algorithm = Algorithm.RSA256((RSAKey) keyPair.getPublic()); HttpUrl htu = HttpUrl.parse(mockAPI.getDomain())