Skip to content

Add ability to skip issuer https check and token nonce verification #662

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 2 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 59 additions & 2 deletions library/java/net/openid/appauth/AppAuthConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,19 @@ public class AppAuthConfiguration {
@NonNull
private final ConnectionBuilder mConnectionBuilder;

private final boolean mSkipIssuerHttpsCheck;

private final boolean mSkipNonceVerification;

private AppAuthConfiguration(
@NonNull BrowserMatcher browserMatcher,
@NonNull ConnectionBuilder connectionBuilder) {
@NonNull ConnectionBuilder connectionBuilder,
Boolean skipIssuerHttpsCheck,
Boolean skipNonceVerification) {
mBrowserMatcher = browserMatcher;
mConnectionBuilder = connectionBuilder;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
mSkipNonceVerification = skipNonceVerification;
}

/**
Expand All @@ -64,13 +72,31 @@ public ConnectionBuilder getConnectionBuilder() {
return mConnectionBuilder;
}

/**
* Returns <code>true</code> if issuer https validation is disabled, otherwise
* <code>false</code>.
*
* @see Builder#setSkipIssuerHttpsCheck(Boolean)
*/
public boolean getSkipIssuerHttpsCheck() { return mSkipIssuerHttpsCheck; }

/**
* Returns <code>true</code> if nonce verification on response is disabled, otherwise
* <code>false</code>.
*
* @see Builder#setSkipNonceVerification(Boolean)
*/
public boolean getSkipNonceVerification() { return mSkipNonceVerification; }

/**
* Creates {@link AppAuthConfiguration} instances.
*/
public static class Builder {

private BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
private ConnectionBuilder mConnectionBuilder = DefaultConnectionBuilder.INSTANCE;
private boolean mSkipIssuerHttpsCheck;
private boolean mSkipNonceVerification;

/**
* Specify the browser matcher to use, which controls the browsers that can be used
Expand All @@ -94,12 +120,43 @@ public Builder setConnectionBuilder(@NonNull ConnectionBuilder connectionBuilder
return this;
}

/**
* Disables https validation for the issuer identifier.
*
* <p>NOTE: Disabling issuer https validation implies the app is running against an
* insecure environment. Enabling this option is only recommended for testing purposes.
*/
public Builder setSkipIssuerHttpsCheck(Boolean skipIssuerHttpsCheck) {
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
return this;
}

/**
* Disables nonce verification for value sent in the Authentication Request.
*
* <p>NOTE: Some Authorization Servers do not return the requested nonce value thus failing
* ID token validation. Please consider raising an issue with your Identity Provider and
* disabling this option once it is fixed.
*
* <p>Alternatively, you may avoid sending a nonce by passing <code>null</code> to
* {@link AuthorizationRequest.Builder#setNonce}
*/
public Builder setSkipNonceVerification(Boolean skipNonceVerification) {
mSkipNonceVerification = skipNonceVerification;
return this;
}

/**
* Creates the instance from the configured properties.
*/
@NonNull
public AppAuthConfiguration build() {
return new AppAuthConfiguration(mBrowserMatcher, mConnectionBuilder);
return new AppAuthConfiguration(
mBrowserMatcher,
mConnectionBuilder,
mSkipIssuerHttpsCheck,
mSkipNonceVerification
);
}


Expand Down
19 changes: 16 additions & 3 deletions library/java/net/openid/appauth/AuthorizationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,9 @@ public void performTokenRequest(
clientAuthentication,
mClientConfiguration.getConnectionBuilder(),
SystemClock.INSTANCE,
callback)
callback,
mClientConfiguration.getSkipIssuerHttpsCheck(),
mClientConfiguration.getSkipNonceVerification())
.execute();
}

Expand Down Expand Up @@ -567,19 +569,25 @@ private static class TokenRequestTask
private final ConnectionBuilder mConnectionBuilder;
private TokenResponseCallback mCallback;
private Clock mClock;
private boolean mSkipIssuerHttpsCheck;
private boolean mSkipNonceVerification;

private AuthorizationException mException;

TokenRequestTask(TokenRequest request,
@NonNull ClientAuthentication clientAuthentication,
@NonNull ConnectionBuilder connectionBuilder,
Clock clock,
TokenResponseCallback callback) {
TokenResponseCallback callback,
Boolean skipIssuerHttpsCheck,
Boolean skipNonceVerification) {
mRequest = request;
mClientAuthentication = clientAuthentication;
mConnectionBuilder = connectionBuilder;
mClock = clock;
mCallback = callback;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
mSkipNonceVerification = skipNonceVerification;
}

@Override
Expand Down Expand Up @@ -687,7 +695,12 @@ protected void onPostExecute(JSONObject json) {
}

try {
idToken.validate(mRequest, mClock);
idToken.validate(
mRequest,
mClock,
mSkipIssuerHttpsCheck,
mSkipNonceVerification
);
} catch (AuthorizationException ex) {
mCallback.onTokenRequestCompleted(null, ex);
return;
Expand Down
13 changes: 11 additions & 2 deletions library/java/net/openid/appauth/IdToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.util.Base64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import net.openid.appauth.AuthorizationException.GeneralErrors;
import org.json.JSONException;
Expand Down Expand Up @@ -109,7 +110,15 @@ static IdToken from(String token) throws JSONException, IdTokenException {
);
}

@VisibleForTesting
void validate(@NonNull TokenRequest tokenRequest, Clock clock) throws AuthorizationException {
validate(tokenRequest, clock, false, false);
}

void validate(@NonNull TokenRequest tokenRequest,
Clock clock,
boolean skipIssuerHttpsCheck,
boolean skipNonceVerification) throws AuthorizationException {
// OpenID Connect Core Section 3.1.3.7. rule #1
// Not enforced: AppAuth does not support JWT encryption.

Expand All @@ -129,7 +138,7 @@ void validate(@NonNull TokenRequest tokenRequest, Clock clock) throws Authorizat
// components.
Uri issuerUri = Uri.parse(this.issuer);

if (!issuerUri.getScheme().equals("https")) {
if (!skipIssuerHttpsCheck && !issuerUri.getScheme().equals("https")) {
throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
new IdTokenException("Issuer must be an https URL"));
}
Expand Down Expand Up @@ -189,7 +198,7 @@ void validate(@NonNull TokenRequest tokenRequest, Clock clock) throws Authorizat
// OpenID Connect Core Section 3.1.3.7. rule #11
// Validates the nonce.
String expectedNonce = tokenRequest.nonce;
if (!TextUtils.equals(this.nonce, expectedNonce)) {
if (!skipNonceVerification && !TextUtils.equals(this.nonce, expectedNonce)) {
throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
new IdTokenException("Nonce mismatch"));
}
Expand Down
47 changes: 47 additions & 0 deletions library/javatests/net/openid/appauth/IdTokenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ public void testValidate_shouldFailOnNonHttpsIssuer()
idToken.validate(tokenRequest, clock);
}

@Test
public void testValidate_shouldSkipNonHttpsIssuer()
throws AuthorizationException, JSONException, MissingArgumentException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Long tenMinutesInSeconds = (long) (10 * 60);
IdToken idToken = new IdToken(
"http://other.issuer",
TEST_SUBJECT,
Collections.singletonList(TEST_CLIENT_ID),
nowInSeconds + tenMinutesInSeconds,
nowInSeconds,
TEST_NONCE
);

String serviceDocJsonWithOtherIssuer = getDiscoveryDocJsonWithIssuer("http://other.issuer");
AuthorizationServiceDiscovery discoveryDoc = new AuthorizationServiceDiscovery(
new JSONObject(serviceDocJsonWithOtherIssuer));
AuthorizationServiceConfiguration serviceConfiguration =
new AuthorizationServiceConfiguration(discoveryDoc);
TokenRequest tokenRequest = new TokenRequest.Builder(serviceConfiguration, TEST_CLIENT_ID)
.setAuthorizationCode(TEST_AUTH_CODE)
.setCodeVerifier(TEST_CODE_VERIFIER)
.setGrantType(GrantTypeValues.AUTHORIZATION_CODE)
.setRedirectUri(TEST_APP_REDIRECT_URI)
.setNonce(TEST_NONCE)
.build();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, true, false);
}

@Test(expected = AuthorizationException.class)
public void testValidate_shouldFailOnIssuerMissingHost()
throws AuthorizationException, JSONException, MissingArgumentException {
Expand Down Expand Up @@ -359,6 +389,23 @@ public void testValidate_shouldFailOnNonceMismatch() throws AuthorizationExcepti
idToken.validate(tokenRequest, clock);
}

@Test
public void testValidate_shouldSkipNonceMismatch() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Long tenMinutesInSeconds = (long) (10 * 60);
IdToken idToken = new IdToken(
TEST_ISSUER,
TEST_SUBJECT,
Collections.singletonList(TEST_CLIENT_ID),
nowInSeconds + tenMinutesInSeconds,
nowInSeconds,
"some_other_nonce"
);
TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, false, true);
}

private static String base64UrlNoPaddingEncode(byte[] data) {
return Base64.encodeToString(data, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
}
Expand Down