Skip to content
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

Idtoken time skew #3

Merged
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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()

ID Token validation was introduced in `0.8.0` but not all authorization servers or configurations support it correctly.

- For testing environments [setSkipIssuerHttpsCheck](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L129) can be used to bypass the fact the issuer needs to be HTTPS.
- For testing environments [setSkipIssuerHttpsCheck](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L143) can be used to bypass the fact the issuer needs to be HTTPS.

```java
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
Expand All @@ -635,6 +635,22 @@ AuthorizationRequest authRequest = authRequestBuilder
.build();
```

- To change the default allowed time skew of 10 minutes for the issue time, [setAllowedIssueTimeSkew](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L159) can be used.

```java
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
.setAllowedIssueTimeSkew(THIRTY_MINUTES_IN_SECONDS)
.build()
```

- For testing environments [setSkipIssueTimeValidation](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/AppAuthConfiguration.java#L151) can be used to bypass the issue time validation.

```java
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
.setSkipIssueTimeValidation(true)
.build()
```

## Dynamic client registration

AppAuth supports the
Expand Down
48 changes: 46 additions & 2 deletions library/java/net/openid/appauth/AppAuthConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,21 @@ public class AppAuthConfiguration {

private final boolean mSkipIssuerHttpsCheck;

private final boolean mSkipIssueTimeValidation;

private final Long mAllowedIssueTimeSkew;

private AppAuthConfiguration(
@NonNull BrowserMatcher browserMatcher,
@NonNull ConnectionBuilder connectionBuilder,
Boolean skipIssuerHttpsCheck) {
Boolean skipIssuerHttpsCheck,
Boolean skipIssueTimeValidation,
Long allowedIssueTimeSkew) {
mBrowserMatcher = browserMatcher;
mConnectionBuilder = connectionBuilder;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
mSkipIssueTimeValidation = skipIssueTimeValidation;
mAllowedIssueTimeSkew = allowedIssueTimeSkew;
}

/**
Expand Down Expand Up @@ -76,6 +84,22 @@ public ConnectionBuilder getConnectionBuilder() {
*/
public boolean getSkipIssuerHttpsCheck() { return mSkipIssuerHttpsCheck; }

/**
* Returns <code>true</code> if the ID token issue time validation is disables,
* otherwise <code>false</code>.
*
* @see Builder#setSkipIssueTimeValidation(Boolean)
*/
public boolean getSkipIssueTimeValidation() { return mSkipIssueTimeValidation; }

/**
* Returns the time in seconds that the ID token issue time is allowed to be
* skewed.
*
* @see Builder#setAllowedIssueTimeSkew(Long)
*/
public Long getAllowedIssueTimeSkew() { return mAllowedIssueTimeSkew; }

/**
* Creates {@link AppAuthConfiguration} instances.
*/
Expand All @@ -84,6 +108,8 @@ public static class Builder {
private BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
private ConnectionBuilder mConnectionBuilder = DefaultConnectionBuilder.INSTANCE;
private boolean mSkipIssuerHttpsCheck;
private boolean mSkipIssueTimeValidation;
private Long mAllowedIssueTimeSkew;
private boolean mSkipNonceVerification;

/**
Expand Down Expand Up @@ -119,6 +145,22 @@ public Builder setSkipIssuerHttpsCheck(Boolean skipIssuerHttpsCheck) {
return this;
}

/**
* Disables issue time validation for the id token.
*/
public Builder setSkipIssueTimeValidation(Boolean skipIssueTimeValidation) {
mSkipIssueTimeValidation = skipIssueTimeValidation;
return this;
}

/**
* Sets the allowed time skew in seconds for id token issue time validation.
*/
public Builder setAllowedIssueTimeSkew(Long allowedIssueTimeSkew) {
mAllowedIssueTimeSkew = allowedIssueTimeSkew;
return this;
}

/**
* Creates the instance from the configured properties.
*/
Expand All @@ -127,7 +169,9 @@ public AppAuthConfiguration build() {
return new AppAuthConfiguration(
mBrowserMatcher,
mConnectionBuilder,
mSkipIssuerHttpsCheck
mSkipIssuerHttpsCheck,
mSkipIssueTimeValidation,
mAllowedIssueTimeSkew
);
}

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

Expand Down Expand Up @@ -585,6 +587,8 @@ private static class TokenRequestTask
private TokenResponseCallback mCallback;
private Clock mClock;
private boolean mSkipIssuerHttpsCheck;
private boolean mSkipIssueTimeValidation;
private Long mAllowedIssueTimeSkew;

private AuthorizationException mException;

Expand All @@ -593,13 +597,17 @@ private static class TokenRequestTask
@NonNull ConnectionBuilder connectionBuilder,
Clock clock,
TokenResponseCallback callback,
Boolean skipIssuerHttpsCheck) {
Boolean skipIssuerHttpsCheck,
Boolean skipissueTimeValidation,
Long allowedIssueTimeSkew) {
mRequest = request;
mClientAuthentication = clientAuthentication;
mConnectionBuilder = connectionBuilder;
mClock = clock;
mCallback = callback;
mSkipIssuerHttpsCheck = skipIssuerHttpsCheck;
mSkipIssueTimeValidation = skipissueTimeValidation;
mAllowedIssueTimeSkew = allowedIssueTimeSkew;
}

@Override
Expand Down Expand Up @@ -710,7 +718,9 @@ protected void onPostExecute(JSONObject json) {
idToken.validate(
mRequest,
mClock,
mSkipIssuerHttpsCheck
mSkipIssuerHttpsCheck,
mSkipIssueTimeValidation,
mAllowedIssueTimeSkew
);
} catch (AuthorizationException ex) {
mCallback.onTokenRequestCompleted(null, ex);
Expand Down
23 changes: 14 additions & 9 deletions library/java/net/openid/appauth/IdToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,14 @@ static IdToken from(String token) throws JSONException, IdTokenException {

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

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

Expand Down Expand Up @@ -276,13 +278,16 @@ void validate(@NonNull TokenRequest tokenRequest,
new IdTokenException("ID Token expired"));
}

// OpenID Connect Core Section 3.1.3.7. rule #10
// Validates that the issued at time is not more than +/- 10 minutes on the current
// time.
if (Math.abs(nowInSeconds - this.issuedAt) > TEN_MINUTES_IN_SECONDS) {
throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
new IdTokenException("Issued at time is more than 10 minutes "
+ "before or after the current time"));

if (!skipIssueTimeValidation) {
// OpenID Connect Core Section 3.1.3.7. rule #10
// Validates that the issued at time is not more than the +/- configured allowed time skew,
// or +/- 10 minutes as a default, on the current time.
if (Math.abs(nowInSeconds - this.issuedAt) > (allowedIssueTimeSkew == null ? TEN_MINUTES_IN_SECONDS : allowedIssueTimeSkew)) {
throw AuthorizationException.fromTemplate(GeneralErrors.ID_TOKEN_VALIDATION_ERROR,
new IdTokenException("Issued at time is more than 10 minutes "
+ "before or after the current time"));
}
}

// Only relevant for the authorization_code response type
Expand Down
56 changes: 55 additions & 1 deletion library/javatests/net/openid/appauth/IdTokenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ public void testValidate_shouldSkipNonHttpsIssuer()
.setRedirectUri(TEST_APP_REDIRECT_URI)
.build();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, true);
idToken.validate(tokenRequest, clock, true, false, null);
}

@Test(expected = AuthorizationException.class)
Expand Down Expand Up @@ -464,6 +464,60 @@ public void testValidate_shouldFailOnIssuedAtOverTenMinutesAgo() throws Authoriz
idToken.validate(tokenRequest, clock);
}

@Test
public void testValidate_withSkipIssueTimeValidation() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Long anHourInSeconds = (long) (60 * 60);
IdToken idToken = new IdToken(
TEST_ISSUER,
TEST_SUBJECT,
Collections.singletonList(TEST_CLIENT_ID),
nowInSeconds,
nowInSeconds - (anHourInSeconds * 2),
TEST_NONCE,
TEST_CLIENT_ID
);
TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, false, true, null);
}

@Test(expected = AuthorizationException.class)
public void testValidate_shouldFailOnIssuedAtOverConfiguredTimeSkew() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Long anHourInSeconds = (long) (60 * 60);
IdToken idToken = new IdToken(
TEST_ISSUER,
TEST_SUBJECT,
Collections.singletonList(TEST_CLIENT_ID),
nowInSeconds,
nowInSeconds - anHourInSeconds - 1,
TEST_NONCE,
TEST_CLIENT_ID
);
TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, false, false, anHourInSeconds);
}

@Test
public void testValidate_withConfiguredTimeSkew() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Long anHourInSeconds = (long) (60 * 60);
IdToken idToken = new IdToken(
TEST_ISSUER,
TEST_SUBJECT,
Collections.singletonList(TEST_CLIENT_ID),
nowInSeconds,
nowInSeconds - anHourInSeconds,
TEST_NONCE,
TEST_CLIENT_ID
);
TokenRequest tokenRequest = getAuthCodeExchangeRequestWithNonce();
Clock clock = SystemClock.INSTANCE;
idToken.validate(tokenRequest, clock, false, false, anHourInSeconds);
}

@Test(expected = AuthorizationException.class)
public void testValidate_shouldFailOnNonceMismatch() throws AuthorizationException {
Long nowInSeconds = SystemClock.INSTANCE.getCurrentTimeMillis() / 1000;
Expand Down