From 5061dcf1002987925d86fb5c2419c9bc97200b02 Mon Sep 17 00:00:00 2001 From: Nick Watson Date: Sat, 16 May 2026 13:26:49 -0700 Subject: [PATCH] Delete assertion parser with vulnerabilities. (1) Audience validation is incomplete and does not check path and query. (2) Parser takes both assertion and audience from HttpServletRequest allowing an attacker to modify Host header to match stolen assertion --- .../SignedJsonAssertionAudienceChecker.java | 63 --------- .../signatures/SignedJsonAssertionToken.java | 105 -------------- .../SignedJsonAssertionTokenParser.java | 130 ------------------ .../SignedJsonAssertionBuilderTest.java | 43 ------ 4 files changed, 341 deletions(-) delete mode 100644 src/main/java/net/oauth/signatures/SignedJsonAssertionAudienceChecker.java delete mode 100644 src/main/java/net/oauth/signatures/SignedJsonAssertionToken.java delete mode 100644 src/main/java/net/oauth/signatures/SignedJsonAssertionTokenParser.java delete mode 100644 src/test/java/net/oauth/signatures/SignedJsonAssertionBuilderTest.java diff --git a/src/main/java/net/oauth/signatures/SignedJsonAssertionAudienceChecker.java b/src/main/java/net/oauth/signatures/SignedJsonAssertionAudienceChecker.java deleted file mode 100644 index 3143d6f..0000000 --- a/src/main/java/net/oauth/signatures/SignedJsonAssertionAudienceChecker.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2010 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.oauth.signatures; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonObject; -import java.net.URI; -import java.security.SignatureException; -import net.oauth.jsontoken.Checker; -import net.oauth.jsontoken.JsonToken; - -/** Audience checker for signed Json Assertion. */ -public class SignedJsonAssertionAudienceChecker implements Checker { - - // URI that the client is accessing, as seen by the server - private final String tokenEndpointUri; - - /** - * Public constructor. - * - * @param uri the URI against which the signed OAuth token was exercised. - */ - public SignedJsonAssertionAudienceChecker(String uri) { - this.tokenEndpointUri = uri; - } - - /** @see net.oauth.jsontoken.Checker#check(com.google.gson.JsonObject) */ - @Override - public void check(JsonObject payload) throws SignatureException { - checkUri( - tokenEndpointUri, - Preconditions.checkNotNull( - payload.get(JsonToken.AUDIENCE).getAsString(), "Audience cannot be null!")); - } - - private static void checkUri(String ourUriString, String tokenUriString) - throws SignatureException { - URI ourUri = URI.create(ourUriString); - URI tokenUri = URI.create(tokenUriString); - - if (!ourUri.getScheme().equalsIgnoreCase(tokenUri.getScheme())) { - throw new SignatureException("scheme in token URI (" + tokenUri.getScheme() + ") is wrong"); - } - - if (!ourUri.getAuthority().equalsIgnoreCase(tokenUri.getAuthority())) { - throw new SignatureException( - "authority in token URI (" + tokenUri.getAuthority() + ") is wrong"); - } - } -} diff --git a/src/main/java/net/oauth/signatures/SignedJsonAssertionToken.java b/src/main/java/net/oauth/signatures/SignedJsonAssertionToken.java deleted file mode 100644 index b3285f5..0000000 --- a/src/main/java/net/oauth/signatures/SignedJsonAssertionToken.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2010 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.oauth.signatures; - -import com.google.gson.JsonPrimitive; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.SignatureException; -import net.oauth.jsontoken.Clock; -import net.oauth.jsontoken.JsonToken; -import net.oauth.jsontoken.crypto.Signer; - -/** A signed Json Assertion */ -public class SignedJsonAssertionToken extends JsonToken { - - public static final String JWT = "jwt"; - - public static final String GRANT_TYPE = "grant_type"; - public static final String GRANT_TYPE_VALUE = "http://oauth.net/grant_type/jwt/1.0/bearer"; - - // addition JSON token payload fields for signed json assertion - public static final String SUBJECT = "subject"; - public static final String SCOPE = "scope"; - public static final String NONCE = "nonce"; - - public SignedJsonAssertionToken(Signer signer, Clock clock) { - super(signer, clock); - } - - public SignedJsonAssertionToken(Signer signer) { - super(signer); - } - - public SignedJsonAssertionToken(JsonToken token) { - super(token.getPayloadAsJsonObject()); - } - - public String getSubject() { - JsonPrimitive subjectJson = getParamAsPrimitive(SUBJECT); - return subjectJson == null ? null : subjectJson.getAsString(); - } - - public void setSubject(String m) { - setParam(SUBJECT, m); - } - - public String getScope() { - JsonPrimitive scopeJson = getParamAsPrimitive(SCOPE); - return scopeJson == null ? null : scopeJson.getAsString(); - } - - public void setScope(String scope) { - setParam(SCOPE, scope); - } - - public String getNonce() { - JsonPrimitive nonceJson = getParamAsPrimitive(NONCE); - return nonceJson == null ? null : nonceJson.getAsString(); - } - - public void setNonce(String n) { - setParam(NONCE, n); - } - - public String getJsonAssertionPostBody() throws SignatureException { - StringBuffer buffer = new StringBuffer(); - buffer.append(GRANT_TYPE).append("=").append(GRANT_TYPE_VALUE); - buffer.append("&"); - try { - buffer.append(JWT).append("=").append(serializeAndSign()); - return URLEncoder.encode(buffer.toString(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new SignatureException("unsupported encoding"); - } - } - - @Override - public String serializeAndSign() throws SignatureException { - return super.serializeAndSign(); - } - - @Override - protected String computeSignatureBaseString() { - if (getIssuedAt() == null) { - setIssuedAt(clock.now()); - } - if (getExpiration() == null) { - setExpiration(getIssuedAt().plus(DEFAULT_LIFETIME)); - } - return super.computeSignatureBaseString(); - } -} diff --git a/src/main/java/net/oauth/signatures/SignedJsonAssertionTokenParser.java b/src/main/java/net/oauth/signatures/SignedJsonAssertionTokenParser.java deleted file mode 100644 index 7357ee4..0000000 --- a/src/main/java/net/oauth/signatures/SignedJsonAssertionTokenParser.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2010 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.oauth.signatures; - -import com.google.gson.JsonParseException; -import java.security.SignatureException; -import javax.servlet.http.HttpServletRequest; -import net.oauth.jsontoken.Clock; -import net.oauth.jsontoken.JsonTokenParser; -import net.oauth.jsontoken.SystemClock; -import net.oauth.jsontoken.discovery.VerifierProviders; - -/** Parses signed json assertion. */ -public class SignedJsonAssertionTokenParser { - - public static String EXPECTED_CONTENT_TYPE = "application/x-www-form-urlencoded"; - - private final VerifierProviders locators; - private final NonceChecker nonceChecker; - private final Clock clock; - - /** - * Public constructor. - * - * @param locators an object that provides signature verifiers, based signature algorithm, as well - * as on the signer and key ids. - * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce - * checker to make sure that the nonce has not been re-used. - */ - public SignedJsonAssertionTokenParser(VerifierProviders locators, NonceChecker nonceChecker) { - this(locators, nonceChecker, new SystemClock()); - } - - /** - * Public constructor. - * - * @param locators an object that provides signature verifiers, based signature algorithm, as well - * as on the signer and key ids. - * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce - * checker to make sure that the nonce has not been re-used.JsonTokenParser - * @param clock a clock that has implemented the {@link - * Clock#isCurrentTimeInInterval(java.time.Instant, java.time.Instant)} method with a suitable - * slack to account for clock skew when checking token validity. - */ - public SignedJsonAssertionTokenParser( - VerifierProviders locators, NonceChecker nonceChecker, Clock clock) { - this.locators = locators; - this.nonceChecker = nonceChecker; - this.clock = clock; - } - - /** - * Extracts the Json assertion from the Http post body and then verifies it. - * - * @param request the {@link HttpServletRequest} that contains the signed Json assertion in the - * post body. - * @return the Json assertion object. - * @throws SignatureException if the signature doesn't check out, or if authentication fails for - * other reason - * @throws JsonParseException if the header or payload of tokenString is corrupted - * @throws IllegalArgumentException if the signature algorithm is not supported - * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no - * valid verifier for the issuer - */ - public SignedJsonAssertionToken parseToken(HttpServletRequest request) throws SignatureException { - if (!request.getContentType().startsWith(EXPECTED_CONTENT_TYPE)) { - throw new SignatureException("bad content type: " + request.getContentType()); - } - - String grantType = request.getParameter(SignedJsonAssertionToken.GRANT_TYPE); - if (grantType == null - || !grantType.equalsIgnoreCase(SignedJsonAssertionToken.GRANT_TYPE_VALUE)) { - throw new SignatureException("bad grant_type: " + grantType); - } - - String assertion = request.getParameter(SignedJsonAssertionToken.JWT); - if (assertion == null) { - throw new SignatureException("empty json assertion"); - } - - StringBuffer uri = request.getRequestURL(); - if (request.getQueryString() != null) { - uri.append("?"); - uri.append(request.getQueryString()); - } - - return parseToken(assertion, uri.toString()); - } - - /** - * Parses the provided signed Json assertion, and then verifies it against the provided HTTP - * method and audience URI (in addition to checking the signature, and validity period). - * - * @param jsonAssertion the signed Json assertion (in serialized form). - * @param uri the URI against which the token was exercised. - * @return the signed Json assertion token (deserialized) - * @throws SignatureException if the signature (or anything else) doesn't check out - * @throws JsonParseException if the header or payload of tokenString is corrupted - * @throws IllegalArgumentException if the signature algorithm is not supported - * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no - * valid verifier for the issuer - */ - public SignedJsonAssertionToken parseToken(String jsonAssertion, String uri) - throws SignatureException { - JsonTokenParser parser = - new JsonTokenParser(clock, locators, new SignedJsonAssertionAudienceChecker(uri)); - - SignedJsonAssertionToken token = - new SignedJsonAssertionToken(parser.verifyAndDeserialize(jsonAssertion)); - - if (nonceChecker != null) { - nonceChecker.checkNonce(token.getNonce()); - } - - return token; - } -} diff --git a/src/test/java/net/oauth/signatures/SignedJsonAssertionBuilderTest.java b/src/test/java/net/oauth/signatures/SignedJsonAssertionBuilderTest.java deleted file mode 100644 index d9fffb0..0000000 --- a/src/test/java/net/oauth/signatures/SignedJsonAssertionBuilderTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.oauth.signatures; - -import net.oauth.jsontoken.JsonTokenTestBase; -import net.oauth.jsontoken.crypto.RsaSHA256Signer; -import net.oauth.jsontoken.crypto.Signer; - -public class SignedJsonAssertionBuilderTest extends JsonTokenTestBase { - - public void testSignature() throws Exception { - - Signer signer = new RsaSHA256Signer("google.com", "key1", privateKey); - - SignedJsonAssertionToken token = new SignedJsonAssertionToken(signer); - token.setNonce("nonce"); - token.setAudience("http://www.example.com/api"); - token.setScope("scope"); - - assertEquals("nonce", token.getNonce()); - assertEquals("http://www.example.com/api", token.getAudience()); - - SignedJsonAssertionTokenParser parser = new SignedJsonAssertionTokenParser(locators, null); - SignedJsonAssertionToken compare = - parser.parseToken(token.serializeAndSign(), "HTTP://www.Example.Com/api"); - - assertEquals("nonce", compare.getNonce()); - assertEquals("http://www.example.com/api", compare.getAudience()); - } -}