Skip to content

Commit 755ccf4

Browse files
committed
[JBEAP-28701] added use of nonce
1 parent f607fb7 commit 755ccf4

File tree

9 files changed

+182
-18
lines changed

9 files changed

+182
-18
lines changed

http/oidc/src/main/java/org/wildfly/security/http/oidc/AuthenticationError.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public enum Reason {
3636
INVALID_TOKEN,
3737
STALE_TOKEN,
3838
NO_AUTHORIZATION_HEADER,
39-
NO_QUERY_PARAMETER_ACCESS_TOKEN
39+
NO_QUERY_PARAMETER_ACCESS_TOKEN,
40+
INVALID_NONCE
4041
}
4142

4243
private Reason reason;

http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,5 +278,11 @@ interface ElytronMessages extends BasicLogger {
278278

279279
@Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.")
280280
RuntimeException invalidAuthenticationRequestFormat();
281+
282+
@Message(id = 23071, value = "Invalid ID token nonce: %s")
283+
String invalidNonceValue(String name);
284+
285+
@Message(id = 23072, value = "No Such algorithm: '%s'")
286+
IllegalArgumentException noSuchAlgorithm(String algorithm);
281287
}
282288

http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class IDToken extends JsonWebToken {
5353
public static final String CLAIMS_LOCALES = "claims_locales";
5454
public static final String ACR = "acr";
5555
public static final String S_HASH = "s_hash";
56+
public static final String NONCE = "nonce";
5657

5758
/**
5859
* Construct a new instance.
@@ -228,4 +229,7 @@ public String getAcr() {
228229
return getClaimValueAsString(ACR);
229230
}
230231

232+
public String getNonce() {
233+
return getClaimValueAsString(NONCE);
234+
}
231235
}

http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
package org.wildfly.security.http.oidc;
2020

2121
import static org.wildfly.security.http.oidc.ElytronMessages.log;
22+
import static org.wildfly.security.jose.jwk.JWKUtil.BASE64_URL;
2223

2324
import java.io.IOException;
2425
import java.io.InputStream;
2526
import java.net.InetAddress;
2627
import java.net.UnknownHostException;
28+
import java.nio.charset.StandardCharsets;
29+
import java.security.MessageDigest;
30+
import java.security.NoSuchAlgorithmException;
2731
import java.util.Map;
2832
import java.util.StringTokenizer;
2933
import java.util.UUID;
@@ -34,6 +38,7 @@
3438
import org.apache.http.HttpResponse;
3539
import org.apache.http.client.methods.HttpRequestBase;
3640
import org.jose4j.jws.AlgorithmIdentifiers;
41+
import org.wildfly.common.iteration.ByteIterator;
3742
import org.wildfly.security.jose.util.JsonSerialization;
3843

3944
/**
@@ -111,6 +116,7 @@ public class Oidc {
111116
public static final String REDIRECT_URI = "redirect_uri";
112117
public static final String REFRESH_TOKEN = "refresh_token";
113118
public static final String RESPONSE_TYPE = "response_type";
119+
public static final String SESSION_RANDOM_VALUE="session_random_value";
114120
public static final String SESSION_STATE = "session_state";
115121
public static final String SOAP_ACTION = "SOAPAction";
116122
public static final String SSL_REQUIRED = "ssl-required";
@@ -119,6 +125,7 @@ public class Oidc {
119125
public static final int INVALID_ISSUED_FOR_CLAIM = -1;
120126
public static final int INVALID_AT_HASH_CLAIM = -2;
121127
public static final int INVALID_TYPE_CLAIM = -3;
128+
public static final int INVALID_SESSION_RANDOM_VALUE = -4;
122129
static final String OIDC_CLIENT_CONFIG_RESOLVER = "oidc.config.resolver";
123130
static final String OIDC_CONFIG_FILE_LOCATION = "oidc.config.file";
124131
static final String OIDC_JSON_FILE = "/WEB-INF/oidc.json";
@@ -445,4 +452,14 @@ protected static boolean checkCachedAccountMatchesRequest(OidcAccount account, O
445452
return true;
446453
}
447454

455+
public static String getCryptographicValue(final String src) {
456+
try {
457+
MessageDigest md = MessageDigest.getInstance(SHA256);
458+
md.update(src.getBytes(StandardCharsets.UTF_8));
459+
return ByteIterator.ofBytes(md.digest())
460+
.base64Encode(BASE64_URL, false).drainToString();
461+
} catch (NoSuchAlgorithmException e) {
462+
throw log.noSuchAlgorithm(e.getMessage());
463+
}
464+
}
448465
}

http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA512;
2424
import static org.jose4j.jws.AlgorithmIdentifiers.NONE;
2525
import static org.wildfly.security.http.oidc.ElytronMessages.log;
26+
import static org.wildfly.security.http.oidc.IDToken.NONCE;
2627
import static org.wildfly.security.http.oidc.Oidc.ALLOW_QUERY_PARAMS_PROPERTY_NAME;
2728
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID;
2829
import static org.wildfly.security.http.oidc.Oidc.CODE;
@@ -39,6 +40,7 @@
3940
import static org.wildfly.security.http.oidc.Oidc.REQUEST;
4041
import static org.wildfly.security.http.oidc.Oidc.REQUEST_URI;
4142
import static org.wildfly.security.http.oidc.Oidc.SCOPE;
43+
import static org.wildfly.security.http.oidc.Oidc.SESSION_RANDOM_VALUE;
4244
import static org.wildfly.security.http.oidc.Oidc.SESSION_STATE;
4345
import static org.wildfly.security.http.oidc.Oidc.STATE;
4446
import static org.wildfly.security.http.oidc.Oidc.UI_LOCALES;
@@ -59,6 +61,7 @@
5961
import java.security.Key;
6062
import java.security.KeyPair;
6163
import java.security.PublicKey;
64+
import java.security.SecureRandom;
6265
import java.util.ArrayList;
6366
import java.util.Arrays;
6467
import java.util.HashSet;
@@ -76,6 +79,7 @@
7679
import org.jose4j.jwt.JwtClaims;
7780
import org.jose4j.keys.HmacKey;
7881
import org.jose4j.lang.JoseException;
82+
import org.wildfly.common.iteration.ByteIterator;
7983
import org.wildfly.security.http.HttpConstants;
8084

8185
/**
@@ -96,6 +100,8 @@ public class OidcRequestAuthenticator {
96100
protected String refreshToken;
97101
protected String strippedOauthParametersRequestUri;
98102

103+
private int NONCE_SIZE = 36;
104+
99105
static final boolean ALLOW_QUERY_PARAMS_PROPERTY;
100106

101107
static {
@@ -181,7 +187,7 @@ protected String getCode() {
181187
return getQueryParamValue(facade, CODE);
182188
}
183189

184-
protected String getRedirectUri(String state) {
190+
protected String getRedirectUri(String state, String cryptoValue) {
185191
String url = getRequestUrl();
186192
log.debugf("callback uri: %s", url);
187193

@@ -222,14 +228,15 @@ protected String getRedirectUri(String state) {
222228
String redirectUri = rewrittenRedirectUri(url);
223229
URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl());
224230
redirectUriBuilder.addParameter(RESPONSE_TYPE, CODE)
225-
.addParameter(CLIENT_ID, deployment.getResourceName());
231+
.addParameter(CLIENT_ID, deployment.getResourceName())
232+
.addParameter(NONCE, cryptoValue);
226233

227234
switch (deployment.getAuthenticationRequestFormat()) {
228235
case REQUEST:
229236
if (deployment.getRequestParameterSupported()) {
230237
// add request objects into request parameter
231238
try {
232-
createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
239+
createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams, cryptoValue);
233240
} catch (IOException | JoseException e) {
234241
throw log.unableToCreateRequestWithRequestParameter(e);
235242
}
@@ -242,7 +249,7 @@ protected String getRedirectUri(String state) {
242249
case REQUEST_URI:
243250
if (deployment.getRequestUriParameterSupported()) {
244251
try {
245-
createRequestWithRequestParameter(REQUEST_URI, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
252+
createRequestWithRequestParameter(REQUEST_URI, redirectUriBuilder, redirectUri, state, forwardedQueryParams, cryptoValue);
246253
} catch (IOException | JoseException e) {
247254
throw log.unableToCreateRequestUriWithRequestParameter(e);
248255
}
@@ -269,8 +276,8 @@ protected URIBuilder createOAuthRequest(URIBuilder redirectUriBuilder, String re
269276
return redirectUriBuilder;
270277
}
271278

272-
protected URIBuilder createRequestWithRequestParameter(String requestFormat, URIBuilder redirectUriBuilder, String redirectUri, String state, List<NameValuePair> forwardedQueryParams) throws JoseException, IOException {
273-
String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
279+
protected URIBuilder createRequestWithRequestParameter(String requestFormat, URIBuilder redirectUriBuilder, String redirectUri, String state, List<NameValuePair> forwardedQueryParams, String cryptoValue) throws JoseException, IOException {
280+
String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams, cryptoValue);
274281

275282
switch (requestFormat) {
276283
case REQUEST:
@@ -296,7 +303,8 @@ protected String getStateCode() {
296303

297304
protected AuthChallenge loginRedirect() {
298305
final String state = getStateCode();
299-
final String redirect = getRedirectUri(state);
306+
final String sessionRandomValue = generateSessionRandomValue();
307+
final String redirect = getRedirectUri(state, Oidc.getCryptographicValue(sessionRandomValue));
300308
if (redirect == null) {
301309
return challenge(HttpStatus.SC_FORBIDDEN, AuthenticationError.Reason.NO_REDIRECT_URI, null);
302310
}
@@ -314,6 +322,8 @@ public boolean challenge(OidcHttpFacade exchange) {
314322
exchange.getResponse().setStatus(HttpStatus.SC_MOVED_TEMPORARILY);
315323
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, "/", null, -1, deployment.getSSLRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
316324
exchange.getResponse().setHeader(HttpConstants.LOCATION, redirect);
325+
exchange.getResponse().setCookie(SESSION_RANDOM_VALUE, sessionRandomValue, "/", null, -1, deployment.getSSLRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
326+
317327
return true;
318328
}
319329
};
@@ -336,6 +346,7 @@ protected AuthChallenge checkStateCookie() {
336346
log.warn("state parameter was null");
337347
return challenge(HttpStatus.SC_BAD_REQUEST, AuthenticationError.Reason.INVALID_STATE_COOKIE, null);
338348
}
349+
339350
if (!state.equals(stateCookieValue)) {
340351
log.warn("state parameter invalid");
341352
log.warn("cookie: " + stateCookieValue);
@@ -441,9 +452,12 @@ protected AuthChallenge resolveCode(String code) {
441452

442453
try {
443454
TokenValidator tokenValidator = TokenValidator.builder(deployment).build();
444-
TokenValidator.VerifiedTokens verifiedTokens = tokenValidator.parseAndVerifyToken(idTokenString, tokenString);
455+
String sessionRandValueStr = getCookieValue(SESSION_RANDOM_VALUE);
456+
457+
TokenValidator.VerifiedTokens verifiedTokens = tokenValidator.parseAndVerifyToken(idTokenString, tokenString, facade);
445458
idToken = verifiedTokens.getIdToken();
446459
token = verifiedTokens.getAccessToken();
460+
447461
log.debug("Token Verification succeeded!");
448462
} catch (OidcException e) {
449463
log.failedVerificationOfToken(e.getMessage());
@@ -456,6 +470,7 @@ protected AuthChallenge resolveCode(String code) {
456470
log.error("Stale token");
457471
return challenge(HttpStatus.SC_FORBIDDEN, AuthenticationError.Reason.STALE_TOKEN, null);
458472
}
473+
459474
log.debug("successfully authenticated");
460475
return null;
461476
}
@@ -535,7 +550,7 @@ private void addScopes(String scopes, Set<String> allScopes) {
535550
}
536551
}
537552

538-
private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List<NameValuePair> forwardedQueryParams) throws JoseException, IOException {
553+
private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List<NameValuePair> forwardedQueryParams, String cryptoValue) throws JoseException, IOException {
539554
redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE);
540555

541556
JwtClaims jwtClaims = new JwtClaims();
@@ -545,10 +560,12 @@ private String convertToRequestParameter(URIBuilder redirectUriBuilder, String r
545560
for ( NameValuePair parameter: forwardedQueryParams) {
546561
jwtClaims.setClaim(parameter.getName(), parameter.getValue());
547562
}
563+
548564
jwtClaims.setClaim(STATE, state);
549565
jwtClaims.setClaim(REDIRECT_URI, redirectUri);
550566
jwtClaims.setClaim(RESPONSE_TYPE, CODE);
551567
jwtClaims.setClaim(CLIENT_ID, deployment.getResourceName());
568+
jwtClaims.setClaim(NONCE, cryptoValue);
552569

553570
// sign JWT first before encrypting
554571
JsonWebSignature signedRequest = signRequest(jwtClaims, deployment);
@@ -622,4 +639,11 @@ private JsonWebEncryption encryptRequest(JsonWebSignature signedRequest) throws
622639
return jsonEncryption;
623640
}
624641
}
642+
643+
private String generateSessionRandomValue() {
644+
SecureRandom random = new SecureRandom();
645+
byte[] nonceData = new byte[NONCE_SIZE];
646+
random.nextBytes(nonceData);
647+
return ByteIterator.ofBytes(nonceData).base64Encode().drainToString();
648+
}
625649
}

http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import static org.wildfly.security.http.oidc.Oidc.DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME;
2424
import static org.wildfly.security.http.oidc.Oidc.INVALID_AT_HASH_CLAIM;
2525
import static org.wildfly.security.http.oidc.Oidc.INVALID_ISSUED_FOR_CLAIM;
26+
import static org.wildfly.security.http.oidc.Oidc.INVALID_SESSION_RANDOM_VALUE;
2627
import static org.wildfly.security.http.oidc.Oidc.INVALID_TYPE_CLAIM;
28+
import static org.wildfly.security.http.oidc.Oidc.SESSION_RANDOM_VALUE;
2729
import static org.wildfly.security.http.oidc.Oidc.getJavaAlgorithmForHash;
2830
import static org.wildfly.security.jose.jwk.JWKUtil.BASE64_URL;
2931

@@ -83,11 +85,17 @@ private TokenValidator(Builder builder) {
8385
* @throws OidcException if the ID token is invalid
8486
*/
8587
public VerifiedTokens parseAndVerifyToken(final String idToken, final String accessToken) throws OidcException {
88+
return parseAndVerifyToken(idToken, accessToken, null);
89+
}
90+
91+
public VerifiedTokens parseAndVerifyToken(final String idToken, final String accessToken, OidcHttpFacade facade) throws OidcException {
8692
try {
8793
JwtContext idJwtContext = setVerificationKey(idToken, jwtConsumerBuilder);
8894
jwtConsumerBuilder.setExpectedAudience(clientConfiguration.getResourceName());
8995
jwtConsumerBuilder.registerValidator(new AzpValidator(clientConfiguration.getResourceName()));
9096
jwtConsumerBuilder.registerValidator(new AtHashValidator(accessToken, clientConfiguration.getTokenSignatureAlgorithm()));
97+
jwtConsumerBuilder.registerValidator(new NonceValidator(facade));
98+
9199
// second pass to validate
92100
jwtConsumerBuilder.build().processContext(idJwtContext);
93101
JwtClaims idJwtClaims = idJwtContext.getJwtClaims();
@@ -288,6 +296,31 @@ private static String getAccessTokenHash(String accessTokenString, String jwsAlg
288296

289297
}
290298

299+
300+
private static class NonceValidator implements ErrorCodeValidator {
301+
private OidcHttpFacade facade;
302+
303+
public NonceValidator(OidcHttpFacade facade) {
304+
this.facade = facade;
305+
}
306+
307+
public ErrorCodeValidator.Error validate(JwtContext jwtContext) throws MalformedClaimException {
308+
JwtClaims idJwtClaims = jwtContext.getJwtClaims();
309+
IDToken idToken = new IDToken(idJwtClaims);
310+
311+
OidcHttpFacade.Cookie cookie = this.facade.getRequest().getCookie(SESSION_RANDOM_VALUE);
312+
if (cookie != null) {
313+
String sessionIdValue = Oidc.getCryptographicValue(cookie.getValue());
314+
String nonceValue = idToken.getNonce();
315+
if (nonceValue == null || !sessionIdValue.equals(nonceValue)) {
316+
return new ErrorCodeValidator.Error(INVALID_SESSION_RANDOM_VALUE,
317+
log.invalidNonceValue(nonceValue == null ? "null" : nonceValue));
318+
}
319+
}
320+
return null;
321+
}
322+
}
323+
291324
private static class TypeValidator implements ErrorCodeValidator {
292325
public static final String TYPE = "typ";
293326
private final String expectedType;

0 commit comments

Comments
 (0)