Skip to content

Commit

Permalink
refactor id token verifier classes for brevity
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalmaceda committed Nov 29, 2019
1 parent 24729be commit a7418c4
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.auth0.android.provider;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Base64;

import com.auth0.android.jwt.JWT;
Expand All @@ -11,13 +13,20 @@
import java.security.Signature;
import java.security.SignatureException;

class AsymmetricVerifier extends SignatureVerifier {
/**
* Token signature verifier for HS256 algorithms.
*/
class AsymmetricSignatureVerifier extends SignatureVerifier {

private static final String EXPECTED_ALGORITHM = "RS256";
private Signature publicSignature;

AsymmetricVerifier(PublicKey publicKey) throws InvalidKeyException {
super(EXPECTED_ALGORITHM);
/**
* Creates a new instance of the verifier
*
* @param publicKey the public key to use for verification
* @throws InvalidKeyException if the public key provided is null or not of type RSA
*/
AsymmetricSignatureVerifier(@Nullable PublicKey publicKey) throws InvalidKeyException {
try {
publicSignature = Signature.getInstance("SHA256withRSA");
publicSignature.initVerify(publicKey);
Expand All @@ -27,26 +36,21 @@ class AsymmetricVerifier extends SignatureVerifier {
}
}


@Override
void verifySignature(JWT token) throws TokenValidationException {
super.verifySignature(token);
void verifySignature(@NonNull JWT token) throws TokenValidationException {
String[] parts = token.toString().split("\\.");
String content = parts[0] + "." + parts[1];
byte[] contentBytes = content.getBytes(Charset.defaultCharset());
byte[] signatureBytes = Base64.decode(parts[2], Base64.URL_SAFE | Base64.NO_WRAP);
performCheck(contentBytes, signatureBytes);
}

private void performCheck(byte[] content, byte[] signature) {
boolean valid = false;
try {
publicSignature.update(content);
valid = publicSignature.verify(signature);
publicSignature.update(contentBytes);
valid = publicSignature.verify(signatureBytes);
} catch (SignatureException ignored) {
//safe to ignore: throws when the Signature object is not properly initialized
}
if (!valid) {
throw new TokenValidationException("Invalid token signature.");
throw new TokenValidationException("Invalid ID token signature.");
}
}
}

This file was deleted.

38 changes: 1 addition & 37 deletions auth0/src/main/java/com/auth0/android/provider/OAuthManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@
import com.auth0.android.jwt.JWT;
import com.auth0.android.result.Credentials;

import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -218,16 +215,7 @@ private void assertValidIdToken(String idToken, final AuthenticationCallback<Voi
return;
}

String algorithmName = decodedIdToken.getHeader().get("alg");
String keyId = decodedIdToken.getHeader().get("kid");

if (!Arrays.asList("HS256", "RS256").contains(algorithmName)){
//TODO: Update the error message
validationCallback.onFailure(new TokenValidationException("The algorithm received is not supported"));
return;
}

createSignatureVerifier(algorithmName, keyId, new BaseCallback<SignatureVerifier, TokenValidationException>() {
SignatureVerifier.forToken(decodedIdToken, apiClient, new BaseCallback<SignatureVerifier, TokenValidationException>() {

@Override
public void onFailure(TokenValidationException error) {
Expand Down Expand Up @@ -257,30 +245,6 @@ public void onSuccess(SignatureVerifier signatureVerifier) {
});
}

private void createSignatureVerifier(String algorithmName, final String keyId, final BaseCallback<SignatureVerifier, TokenValidationException> verifierCallback) {
if ("HS256".equals(algorithmName)) {
verifierCallback.onSuccess(new NoSignatureVerifier());
return;
}
//TODO: Check that 'none' is not accepted
apiClient.fetchJsonWebKeys().start(new AuthenticationCallback<Map<String, PublicKey>>() {
@Override
public void onSuccess(Map<String, PublicKey> jwks) {
PublicKey publicKey = jwks.get(keyId);
try {
verifierCallback.onSuccess(new AsymmetricVerifier(publicKey));
} catch (InvalidKeyException e) {
verifierCallback.onFailure(new TokenValidationException(String.format("Could not find a public key for kid \"%s\"", keyId)));
}
}

@Override
public void onFailure(AuthenticationException error) {
verifierCallback.onFailure(new TokenValidationException(String.format("Could not find a public key for kid \"%s\"", keyId)));
}
});
}

private long getCurrentTimeInMillis() {
return currentTimeInMillis != null ? currentTimeInMillis : System.currentTimeMillis();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,62 @@
package com.auth0.android.provider;

import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;

import com.auth0.android.authentication.AuthenticationAPIClient;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.callback.AuthenticationCallback;
import com.auth0.android.callback.BaseCallback;
import com.auth0.android.jwt.JWT;

//TODO: Make pkg private
abstract class SignatureVerifier {

private final String expectedAlgorithm;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.util.Map;

SignatureVerifier(String expectedAlgorithm) {
this.expectedAlgorithm = expectedAlgorithm;
}
/**
* Abstract class meant to verify tokens signed with HS256 and RS256 signatures.
*/
abstract class SignatureVerifier {

private final void checkAlgorithm(JWT token) throws TokenValidationException {
/**
* Verifies that the given token's signature is valid, deeming the payload inside it authentic
*
* @param token the ID token to have its signature validated
* @throws TokenValidationException if the signature is not valid
*/
abstract void verifySignature(@NonNull JWT token) throws TokenValidationException;

/**
* Validates the algorithm of the given token is supported and creates a new instance of a SignatureVerifier
*
* @param token the ID token to create a signature verifier for
* @param apiClient api client instance to fetch the JWKS keys, if necessary
* @param callback the callback to receive the result in
*/
static void forToken(@NonNull JWT token, @NonNull AuthenticationAPIClient apiClient, @NonNull final BaseCallback<SignatureVerifier, TokenValidationException> callback) {
String algorithmName = token.getHeader().get("alg");
if (!expectedAlgorithm.equals(algorithmName)) {
throw new TokenValidationException(String.format("Signature algorithm of \"%s\" is not supported. Expected \"%s\".", algorithmName, expectedAlgorithm));
if ("RS256".equals(algorithmName)) {
final String keyId = token.getHeader().get("kid");
apiClient.fetchJsonWebKeys().start(new AuthenticationCallback<Map<String, PublicKey>>() {
@Override
public void onSuccess(Map<String, PublicKey> jwks) {
PublicKey publicKey = jwks.get(keyId);
try {
callback.onSuccess(new AsymmetricSignatureVerifier(publicKey));
} catch (InvalidKeyException e) {
callback.onFailure(new TokenValidationException(String.format("Could not find a public key for kid \"%s\"", keyId)));
}
}

@Override
public void onFailure(AuthenticationException error) {
callback.onFailure(new TokenValidationException(String.format("Could not find a public key for kid \"%s\"", keyId)));
}
});
} else if ("HS256".equals(algorithmName)) {
callback.onSuccess(new SymmetricSignatureVerifier());
} else {
callback.onFailure(new TokenValidationException(String.format("Signature algorithm of \"%s\" is not supported. Expected either \"RS256\" or \"HS256\".", algorithmName)));
}
}

@CallSuper
void verifySignature(JWT token) throws TokenValidationException {
checkAlgorithm(token);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* AuthCallback.java
* SimpleAuthCallback.java
*
* Copyright (c) 2016 Auth0 (http://auth0.com)
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.auth0.android.provider;

import android.support.annotation.NonNull;

import com.auth0.android.jwt.JWT;

/**
* Token signature verifier for HS256 algorithms.
*/
class SymmetricSignatureVerifier extends SignatureVerifier {

@Override
void verifySignature(@NonNull JWT token) throws TokenValidationException {
//NO-OP
//HS256 (symmetric) signatures cannot be calculated on non-confidential clients
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1997,7 +1997,7 @@ public void shouldFailToResumeLoginWithNotSupportedSigningAlgorithm() throws Exc

assertThat(authExceptionCaptor.getValue(), is(notNullValue()));
assertThat(authExceptionCaptor.getValue().getCode(), is("a0.sdk.internal_error.id_token_validation"));
assertThat(authExceptionCaptor.getValue().getDescription(), is("The algorithm received is not supported"));
assertThat(authExceptionCaptor.getValue().getDescription(), is("Signature algorithm of \"none\" is not supported. Expected either \"RS256\" or \"HS256\"."));
}

@SuppressWarnings("deprecation")
Expand Down

0 comments on commit a7418c4

Please sign in to comment.