├── .travis.yml ├── AUTHORS ├── CONTRIBUTING ├── LICENSE ├── pom.xml └── src ├── main └── java │ └── net │ └── oauth │ ├── jsontoken │ ├── AbstractJsonTokenParser.java │ ├── AsyncJsonTokenParser.java │ ├── Checker.java │ ├── Clock.java │ ├── JsonToken.java │ ├── JsonTokenParser.java │ ├── JsonTokenUtil.java │ ├── SystemClock.java │ ├── crypto │ │ ├── AbstractSigner.java │ │ ├── AsciiStringSigner.java │ │ ├── AsciiStringVerifier.java │ │ ├── HmacSHA256Signer.java │ │ ├── HmacSHA256Verifier.java │ │ ├── MagicRsaPublicKey.java │ │ ├── RsaSHA1Verifier.java │ │ ├── RsaSHA256Signer.java │ │ ├── RsaSHA256Verifier.java │ │ ├── SignatureAlgorithm.java │ │ ├── Signer.java │ │ └── Verifier.java │ ├── discovery │ │ ├── AsyncVerifierProvider.java │ │ ├── AsyncVerifierProviders.java │ │ ├── DefaultPublicKeyLocator.java │ │ ├── IdentityServerDescriptorProvider.java │ │ ├── JsonServerInfo.java │ │ ├── ServerDescriptorProvider.java │ │ ├── ServerInfo.java │ │ ├── ServerInfoResolver.java │ │ ├── UrlBasedVerifierProvider.java │ │ ├── VerifierProvider.java │ │ └── VerifierProviders.java │ └── exceptions │ │ ├── ErrorCode.java │ │ └── InvalidJsonTokenException.java │ └── signatures │ ├── NonceChecker.java │ ├── SignedJsonAssertionAudienceChecker.java │ ├── SignedJsonAssertionToken.java │ ├── SignedJsonAssertionTokenParser.java │ ├── SignedOAuthToken.java │ ├── SignedOAuthTokenParser.java │ └── SignedTokenAudienceChecker.java └── test └── java └── net └── oauth ├── jsontoken ├── AbstractJsonTokenParserTest.java ├── AlwaysFailChecker.java ├── AlwaysPassChecker.java ├── AsyncJsonTokenParserTest.java ├── FakeClock.java ├── JsonTokenParserTest.java ├── JsonTokenTest.java ├── JsonTokenTestBase.java └── crypto │ └── HmacSHA256VerifierTest.java └── signatures ├── SignedJsonAssertionBuilderTest.java └── SignedTokenBuilderTest.java /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Jsontoken authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Google LLC 10 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Google LLC 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.googlecode.jsontoken 4 | jsontoken 5 | 1.2-SNAPSHOT 6 | jar 7 | jsontoken 8 | JWT implementation 9 | http://code.google.com/p/jsontoken/ 10 | 11 | 12 | The Apache Software License, Version 2.0 13 | http://www.apache.org/licenses/LICENSE-2.0.txt 14 | repo 15 | 16 | 17 | 18 | 19 | org.sonatype.oss 20 | oss-parent 21 | 7 22 | 23 | 24 | scm:svn:http://jsontoken.googlecode.com/svn/trunk/ 25 | scm:svn:https://jsontoken.googlecode.com/svn/trunk/ 26 | http://jsontoken.googlecode.com/svn/trunk/ 27 | 28 | 29 | 30 | 31 | jcai 32 | Jian Cai 33 | sonicgg@gmail.com 34 | 35 | 36 | 37 | 38 | UTF-8 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-compiler-plugin 46 | 3.8.1 47 | 48 | 1.8 49 | 1.8 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-source-plugin 55 | 2.2.1 56 | 57 | 58 | attach-sources 59 | 60 | jar 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | junit 70 | junit 71 | 4.13.1 72 | test 73 | 74 | 75 | com.google.code.gson 76 | gson 77 | 2.8.6 78 | jar 79 | compile 80 | 81 | 82 | commons-codec 83 | commons-codec 84 | 1.4 85 | jar 86 | compile 87 | 88 | 89 | com.google.guava 90 | guava 91 | 29.0-jre 92 | 93 | 94 | joda-time 95 | joda-time 96 | 1.6 97 | jar 98 | compile 99 | 100 | 101 | javax.servlet 102 | servlet-api 103 | 2.5 104 | jar 105 | compile 106 | 107 | 108 | org.apache.httpcomponents 109 | httpcore 110 | 4.0.1 111 | jar 112 | compile 113 | 114 | 115 | com.google.code.findbugs 116 | jsr305 117 | 3.0.2 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/AbstractJsonTokenParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.common.base.Preconditions; 19 | import com.google.common.base.Splitter; 20 | import com.google.gson.JsonObject; 21 | import com.google.gson.JsonParseException; 22 | import com.google.gson.JsonParser; 23 | import java.security.SignatureException; 24 | import java.time.Instant; 25 | import java.util.List; 26 | import net.oauth.jsontoken.crypto.AsciiStringVerifier; 27 | import net.oauth.jsontoken.crypto.Verifier; 28 | import net.oauth.jsontoken.exceptions.ErrorCode; 29 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 30 | import org.apache.commons.codec.binary.Base64; 31 | 32 | /** 33 | * Class that provides common functions used by {@link JsonTokenParser} and {@link 34 | * AsyncJsonTokenParser}. 35 | */ 36 | abstract class AbstractJsonTokenParser { 37 | private final Clock clock; 38 | private final Checker[] checkers; 39 | 40 | /** 41 | * Creates a new {@link AbstractJsonTokenParser}. 42 | * 43 | * @param clock a clock object that will decide whether a given token is currently valid or not. 44 | * @param checkers an array of checkers that validates the parameters in the JSON token. 45 | */ 46 | AbstractJsonTokenParser(Clock clock, Checker... checkers) { 47 | this.clock = Preconditions.checkNotNull(clock); 48 | this.checkers = checkers; 49 | } 50 | 51 | /** 52 | * Decodes the JWT token string into a JsonToken object. Does not perform any validation of 53 | * headers or claims. 54 | * 55 | * @param tokenString The original encoded representation of a JWT 56 | * @return Unverified contents of the JWT as a JsonToken 57 | * @throws JsonParseException if the header or payload of tokenString is corrupted 58 | * @throws IllegalStateException if tokenString is not a properly formatted JWT 59 | */ 60 | final JsonToken deserializeInternal(String tokenString) { 61 | List pieces = splitTokenString(tokenString); 62 | String jwtHeaderSegment = pieces.get(0); 63 | String jwtPayloadSegment = pieces.get(1); 64 | 65 | JsonObject header = 66 | JsonParser.parseString(JsonTokenUtil.fromBase64ToJsonString(jwtHeaderSegment)) 67 | .getAsJsonObject(); 68 | JsonObject payload = 69 | JsonParser.parseString(JsonTokenUtil.fromBase64ToJsonString(jwtPayloadSegment)) 70 | .getAsJsonObject(); 71 | 72 | return new JsonToken(header, payload, clock, tokenString); 73 | } 74 | 75 | /** 76 | * Verifies that the jsonToken has a valid signature and valid standard claims (iat, exp). Does 77 | * not need VerifierProviders because verifiers are passed in directly. 78 | * 79 | * @param jsonToken the token to verify 80 | * @throws SignatureException when the signature is invalid or if any of the checkers fail 81 | * @throws IllegalStateException when exp or iat are invalid or if tokenString is not a properly 82 | * formatted JWT 83 | */ 84 | final void verifyInternal(JsonToken jsonToken, List verifiers) 85 | throws SignatureException { 86 | if (!signatureIsValidInternal(jsonToken.getTokenString(), verifiers)) { 87 | throw new SignatureException( 88 | "Invalid signature for token: " + jsonToken.getTokenString(), 89 | new InvalidJsonTokenException(ErrorCode.BAD_SIGNATURE)); 90 | } 91 | 92 | Instant issuedAt = jsonToken.getIssuedAt(); 93 | Instant expiration = jsonToken.getExpiration(); 94 | 95 | if (issuedAt == null && expiration != null) { 96 | issuedAt = Instant.EPOCH; 97 | } 98 | 99 | if (issuedAt != null && expiration == null) { 100 | // TODO(kak): Should this be Instant.MAX instead? 101 | expiration = Instant.ofEpochMilli(Long.MAX_VALUE); 102 | } 103 | 104 | if (issuedAt != null && expiration != null) { 105 | String errorMessage = 106 | String.format( 107 | "Invalid iat and/or exp. iat: %s exp: %s now: %s", 108 | jsonToken.getIssuedAt(), jsonToken.getExpiration(), clock.now()); 109 | 110 | if (issuedAt.isAfter(expiration)) { 111 | throw new IllegalStateException( 112 | errorMessage, new InvalidJsonTokenException(ErrorCode.BAD_TIME_RANGE)); 113 | } 114 | 115 | if (!clock.isCurrentTimeInInterval(issuedAt, expiration)) { 116 | if (clock.now().isAfter(expiration)) { 117 | throw new IllegalStateException( 118 | errorMessage, new InvalidJsonTokenException(ErrorCode.EXPIRED_TOKEN)); 119 | } else { 120 | throw new IllegalStateException( 121 | errorMessage, new InvalidJsonTokenException(ErrorCode.BAD_TIME_RANGE)); 122 | } 123 | } 124 | } 125 | 126 | if (checkers != null) { 127 | for (Checker checker : checkers) { 128 | checker.check(jsonToken.getPayloadAsJsonObject()); 129 | } 130 | } 131 | } 132 | 133 | /** 134 | * Verifies that a JSON Web Token's signature is valid. 135 | * 136 | * @param tokenString the encoded and signed JSON Web Token to verify. 137 | * @param verifiers used to verify the signature. These usually encapsulate secret keys. 138 | * @throws IllegalStateException if tokenString is not a properly formatted JWT 139 | */ 140 | final boolean signatureIsValidInternal(String tokenString, List verifiers) { 141 | List pieces = splitTokenString(tokenString); 142 | byte[] signature = Base64.decodeBase64(pieces.get(2)); 143 | String baseString = JsonTokenUtil.toDotFormat(pieces.get(0), pieces.get(1)); 144 | 145 | boolean sigVerified = false; 146 | for (Verifier verifier : verifiers) { 147 | AsciiStringVerifier asciiVerifier = new AsciiStringVerifier(verifier); 148 | try { 149 | asciiVerifier.verifySignature(baseString, signature); 150 | sigVerified = true; 151 | break; 152 | } catch (SignatureException e) { 153 | continue; 154 | } 155 | } 156 | return sigVerified; 157 | } 158 | 159 | /** 160 | * Verifies that a JSON Web Token is not expired. 161 | * 162 | * @param jsonToken the token to verify 163 | * @param now the instant to use as point of reference for current time 164 | * @return false if the token is expired, true otherwise 165 | */ 166 | public boolean expirationIsValid(JsonToken jsonToken, Instant now) { 167 | Instant expiration = jsonToken.getExpiration(); 168 | return expiration == null || expiration.isAfter(now); 169 | } 170 | 171 | /** 172 | * Verifies that a JSON Web Token was issued in the past. 173 | * 174 | * @param jsonToken the token to verify 175 | * @param now the instant to use as point of reference for current time 176 | * @return false if the JWT's 'iat' is later than now, true otherwise 177 | */ 178 | public boolean issuedAtIsValid(JsonToken jsonToken, Instant now) { 179 | Instant issuedAt = jsonToken.getIssuedAt(); 180 | return issuedAt == null || issuedAt.isBefore(now); 181 | } 182 | 183 | /** 184 | * @param tokenString The original encoded representation of a JWT 185 | * @return Three components of the JWT as an array of strings 186 | * @throws IllegalStateException if tokenString is not a properly formatted JWT 187 | */ 188 | private List splitTokenString(String tokenString) { 189 | List pieces = Splitter.on(JsonTokenUtil.DELIMITER).splitToList(tokenString); 190 | if (pieces.size() != 3) { 191 | throw new IllegalStateException( 192 | "Expected JWT to have 3 segments separated by '" 193 | + JsonTokenUtil.DELIMITER 194 | + "', but it has " 195 | + pieces.size() 196 | + " segments", 197 | new InvalidJsonTokenException(ErrorCode.MALFORMED_TOKEN_STRING)); 198 | } 199 | return pieces; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/AsyncJsonTokenParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.common.base.Preconditions; 19 | import com.google.common.util.concurrent.AsyncFunction; 20 | import com.google.common.util.concurrent.Futures; 21 | import com.google.common.util.concurrent.ListenableFuture; 22 | import com.google.gson.JsonObject; 23 | import com.google.gson.JsonParseException; 24 | import java.security.SignatureException; 25 | import java.util.List; 26 | import java.util.concurrent.Callable; 27 | import java.util.concurrent.ExecutionException; 28 | import java.util.concurrent.Executor; 29 | import javax.annotation.Nonnull; 30 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 31 | import net.oauth.jsontoken.crypto.Verifier; 32 | import net.oauth.jsontoken.discovery.AsyncVerifierProvider; 33 | import net.oauth.jsontoken.discovery.AsyncVerifierProviders; 34 | import net.oauth.jsontoken.exceptions.ErrorCode; 35 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 36 | 37 | /** 38 | * The asynchronous counterpart of {@link JsonTokenParser}. Class that parses and verifies JSON 39 | * Tokens asynchronously. 40 | */ 41 | public final class AsyncJsonTokenParser extends AbstractJsonTokenParser { 42 | private final AsyncVerifierProviders asyncVerifierProviders; 43 | private final Executor executor; 44 | 45 | /** 46 | * Creates a new {@link AsyncJsonTokenParser}. 47 | * 48 | * @param clock a clock object that will decide whether a given token is currently valid or not. 49 | * @param asyncVerifierProviders an object that provides signature verifiers asynchronously based 50 | * on a signature algorithm, the signer, and key ids. 51 | * @param executor an executor to run the tasks before and after getting the verifiers 52 | * @param checkers an array of checkers that validates the parameters in the JSON token. 53 | */ 54 | public AsyncJsonTokenParser( 55 | Clock clock, 56 | AsyncVerifierProviders asyncVerifierProviders, 57 | Executor executor, 58 | Checker... checkers) { 59 | super(clock, checkers); 60 | this.asyncVerifierProviders = Preconditions.checkNotNull(asyncVerifierProviders); 61 | this.executor = Preconditions.checkNotNull(executor); 62 | } 63 | 64 | /** 65 | * Verifies that the jsonToken has a valid signature and valid standard claims (iat, exp). Uses 66 | * {@link AsyncVerifierProviders} to obtain the secret key. This method is not expected to throw 67 | * exceptions when returning a future. However, when getting the result of the future, an {@link 68 | * ExecutionException} may be thrown in which {@link ExecutionException#getCause()} is an {@link 69 | * InvalidJsonTokenException} with an error code of: 70 | * 71 | *
    72 | *
  • {@link ErrorCode#BAD_HEADER}: if the header does not have all of the required parameters 73 | *
  • {@link ErrorCode#BAD_SIGNATURE}: if the signature is invalid 74 | *
  • {@link ErrorCode#BAD_TIME_RANGE}: if the IAT is after EXP or the token is in the future 75 | *
  • {@link ErrorCode#EXPIRED_TOKEN}: if the token is expired 76 | *
  • {@link ErrorCode#MALFORMED_TOKEN_STRING}: if the tokenString is not a properly formatted 77 | * JWT 78 | *
  • {@link ErrorCode#NO_VERIFIER}: if there is no valid verifier for the (issuer, keyId) pair 79 | *
  • {@link ErrorCode#UNKNOWN}: if any of the checkers fail 80 | *
  • {@link ErrorCode#UNSUPPORTED_ALGORITHM}: if the signature algorithm is unsupported 81 | *
82 | * 83 | * @param jsonToken 84 | * @return a {@link ListenableFuture} that will fail if the token fails verification. 85 | */ 86 | @Nonnull 87 | public ListenableFuture verify(JsonToken jsonToken) { 88 | ListenableFuture> futureVerifiers = provideVerifiers(jsonToken); 89 | // Use AsyncFunction instead of Function to allow for checked exceptions to propagate forward 90 | AsyncFunction, Void> verifyFunction = 91 | verifiers -> { 92 | verify(jsonToken, verifiers); 93 | return Futures.immediateVoidFuture(); 94 | }; 95 | 96 | ListenableFuture result = 97 | Futures.transformAsync(futureVerifiers, verifyFunction, executor); 98 | return mapExceptions(result); 99 | } 100 | 101 | /** 102 | * Parses and verifies a JSON Token. This method is not expected to throw exceptions when 103 | * returning a future. However, when getting the result of the future, an {@link 104 | * ExecutionException} may be thrown in which {@link ExecutionException#getCause()} is an {@link 105 | * InvalidJsonTokenException} with an error code of: 106 | * 107 | *
    108 | *
  • {@link ErrorCode#BAD_HEADER} if the header does not have all of the required parameters 109 | *
  • {@link ErrorCode#BAD_SIGNATURE} if the signature is invalid 110 | *
  • {@link ErrorCode#BAD_TIME_RANGE} if the IAT is after EXP or the token is in the future 111 | *
  • {@link ErrorCode#EXPIRED_TOKEN} if the token is expired 112 | *
  • {@link ErrorCode#MALFORMED_TOKEN_STRING} if the tokenString is not a properly formed JWT 113 | *
  • {@link ErrorCode#NO_VERIFIER} if there is no valid verifier for the (issuer, keyId) pair 114 | *
  • {@link ErrorCode#UNKNOWN} if any of the checkers fail 115 | *
  • {@link ErrorCode#UNSUPPORTED_ALGORITHM} if the signature algorithm is unsupported 116 | *
117 | * 118 | * @param tokenString the serialized token that is to parsed and verified. 119 | * @return a {@link ListenableFuture} that will return the deserialized {@link JsonObject}, 120 | * suitable for passing to the constructor of {@link JsonToken} or equivalent constructor of 121 | * {@link JsonToken} subclasses. 122 | */ 123 | @Nonnull 124 | public ListenableFuture verifyAndDeserialize(String tokenString) { 125 | JsonToken jsonToken; 126 | try { 127 | jsonToken = deserialize(tokenString); 128 | } catch (InvalidJsonTokenException e) { 129 | return Futures.immediateFailedFuture(e); 130 | } 131 | 132 | ListenableFuture result = 133 | Futures.transform(verify(jsonToken), unused -> jsonToken, executor); 134 | return mapExceptions(result); 135 | } 136 | 137 | /** 138 | * Decodes the JWT token string into a JsonToken object. Does not perform any validation of 139 | * headers or claims. Identical to {@link AbstractJsonTokenParser#deserializeInternal(String)}, 140 | * except exceptions are caught and rethrown as {@link InvalidJsonTokenException}. 141 | * 142 | * @param tokenString The original encoded representation of a JWT 143 | * @return Unverified contents of the JWT as a JsonToken 144 | * @throws InvalidJsonTokenException with {@link ErrorCode#MALFORMED_TOKEN_STRING} if the 145 | * tokenString is not a properly formatted JWT. 146 | */ 147 | public JsonToken deserialize(String tokenString) throws InvalidJsonTokenException { 148 | return mapExceptions(() -> deserializeInternal(tokenString)); 149 | } 150 | 151 | /** 152 | * Verifies that the jsonToken has a valid signature and valid standard claims (iat, exp). Does 153 | * not need VerifierProviders because verifiers are passed in directly. Identical to {@link 154 | * AbstractJsonTokenParser#verifyInternal(JsonToken, List)}, except exceptions are caught and 155 | * rethrown as {@link InvalidJsonTokenException}. 156 | * 157 | * @param jsonToken the token to verify 158 | * @throws InvalidJsonTokenException with the error code 159 | *
    160 | *
  • {@link ErrorCode#BAD_SIGNATURE} if the signature is invalid 161 | *
  • {@link ErrorCode#BAD_TIME_RANGE} if the IAT is after EXP or the token is in the 162 | * future 163 | *
  • {@link ErrorCode#MALFORMED_TOKEN_STRING} if the tokenString is not a properly formed 164 | * JWT 165 | *
  • {@link ErrorCode#UNKNOWN} if any of the checkers fail 166 | *
167 | */ 168 | public void verify(JsonToken jsonToken, List verifiers) 169 | throws InvalidJsonTokenException { 170 | mapExceptions( 171 | () -> { 172 | verifyInternal(jsonToken, verifiers); 173 | return null; 174 | }); 175 | } 176 | 177 | /** 178 | * Verifies that a JSON Web Token's signature is valid. Identical to {@link 179 | * AbstractJsonTokenParser#signatureIsValidInternal(String, List)}, except exceptions are caught 180 | * and rethrown as {@link InvalidJsonTokenException}. 181 | * 182 | * @param tokenString the encoded and signed JSON Web Token to verify. 183 | * @param verifiers used to verify the signature. These usually encapsulate secret keys. 184 | * @throws InvalidJsonTokenException with {@link ErrorCode#MALFORMED_TOKEN_STRING} if the 185 | * tokenString is not a properly formatted JWT. 186 | */ 187 | public boolean signatureIsValid(String tokenString, List verifiers) 188 | throws InvalidJsonTokenException { 189 | return mapExceptions(() -> signatureIsValidInternal(tokenString, verifiers)); 190 | } 191 | 192 | /** 193 | * Use {@link AsyncVerifierProviders} to get future that will return a list of verifiers for this 194 | * token. This method is not expected to throw exceptions when returning a future. However, when 195 | * getting the result of the future, an {@link ExecutionException} may be thrown in which {@link 196 | * ExecutionException#getCause()} is an {@link InvalidJsonTokenException} with an error code of: 197 | * 198 | *
    199 | *
  • {@link ErrorCode#BAD_HEADER} if the header does not have all of the required parameters 200 | *
  • {@link ErrorCode#NO_VERIFIER} if there is no valid verifier for the (issuer, keyId) pair 201 | *
  • {@link ErrorCode#UNSUPPORTED_ALGORITHM} if the signature algorithm is unsupported 202 | *
203 | * 204 | * @param jsonToken 205 | * @return a {@link ListenableFuture} that will return a list of verifiers 206 | */ 207 | @Nonnull 208 | private ListenableFuture> provideVerifiers(JsonToken jsonToken) { 209 | ListenableFuture> futureVerifiers; 210 | try { 211 | SignatureAlgorithm signatureAlgorithm = jsonToken.getSignatureAlgorithm(); 212 | AsyncVerifierProvider provider = 213 | asyncVerifierProviders.getVerifierProvider(signatureAlgorithm); 214 | if (provider == null) { 215 | return Futures.immediateFailedFuture( 216 | new InvalidJsonTokenException( 217 | ErrorCode.UNSUPPORTED_ALGORITHM, 218 | "Signature algorithm not supported: " + signatureAlgorithm)); 219 | } 220 | futureVerifiers = provider.findVerifier(jsonToken.getIssuer(), jsonToken.getKeyId()); 221 | } catch (Exception e) { 222 | return Futures.immediateFailedFuture(e); 223 | } 224 | 225 | // Use AsyncFunction instead of Function to allow for checked exceptions to propagate forward 226 | AsyncFunction, List> checkNullFunction = 227 | verifiers -> { 228 | if (verifiers == null || verifiers.isEmpty()) { 229 | return Futures.immediateFailedFuture( 230 | new InvalidJsonTokenException( 231 | ErrorCode.NO_VERIFIER, 232 | "No valid verifier for issuer: " + jsonToken.getIssuer())); 233 | } 234 | return Futures.immediateFuture(verifiers); 235 | }; 236 | 237 | return Futures.transformAsync(futureVerifiers, checkNullFunction, executor); 238 | } 239 | 240 | /** 241 | * Remaps exceptions, when applicable, to {@link InvalidJsonTokenException} for improved exception 242 | * handling in the asynchronous parser. Otherwise, the original exception is returned. 243 | */ 244 | private Exception mapException(Exception originalException) { 245 | Throwable cause = originalException.getCause(); 246 | if (cause instanceof InvalidJsonTokenException) { 247 | InvalidJsonTokenException invalidJsonTokenException = (InvalidJsonTokenException) cause; 248 | if (invalidJsonTokenException.getErrorCode().equals(ErrorCode.ILLEGAL_STATE)) { 249 | return new IllegalStateException(originalException); 250 | } 251 | 252 | return new InvalidJsonTokenException( 253 | invalidJsonTokenException.getErrorCode(), originalException); 254 | } 255 | 256 | if (originalException instanceof SignatureException) { 257 | return new InvalidJsonTokenException(ErrorCode.UNKNOWN, originalException); 258 | } 259 | 260 | if (originalException instanceof JsonParseException) { 261 | return new InvalidJsonTokenException(ErrorCode.MALFORMED_TOKEN_STRING, originalException); 262 | } 263 | 264 | return originalException; 265 | } 266 | 267 | /** 268 | * Rethrows any {@link SignatureException}, any {@link RuntimeException}s, or any {@link 269 | * Exception} where {@link Exception#getCause()} is {@link InvalidJsonTokenException}. 270 | */ 271 | private T mapExceptions(Callable callable) throws InvalidJsonTokenException { 272 | try { 273 | return callable.call(); 274 | } catch (Exception e) { 275 | Exception rethrownException = mapException(e); 276 | if (rethrownException instanceof InvalidJsonTokenException) { 277 | throw (InvalidJsonTokenException) rethrownException; 278 | } 279 | if (rethrownException instanceof RuntimeException) { 280 | throw (RuntimeException) rethrownException; 281 | } 282 | 283 | throw new IllegalStateException("Unexpected checked exception.", rethrownException); 284 | } 285 | } 286 | 287 | /** 288 | * Catches any failed futures and returns a new future with a mapped exception. Unlike {@link 289 | * #mapExceptions(Callable)}, this function supports all exceptions. 290 | */ 291 | private ListenableFuture mapExceptions(ListenableFuture result) { 292 | return Futures.catchingAsync( 293 | result, 294 | Exception.class, 295 | exception -> { 296 | throw mapException(exception); 297 | }, 298 | executor); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/Checker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.gson.JsonObject; 19 | import java.security.SignatureException; 20 | 21 | /** Token verifiers must implement this interface. */ 22 | public interface Checker { 23 | 24 | /** 25 | * Checks that the given payload satisfies this token verifier. 26 | * 27 | * @param payload the payload component of a JsonToken (JWT) 28 | * @throws SignatureException if the audience doesn't match. 29 | */ 30 | void check(JsonObject payload) throws SignatureException; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/Clock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import java.time.Instant; 19 | 20 | /** Clock interface. */ 21 | public interface Clock { 22 | 23 | /** Returns current time. */ 24 | Instant now(); 25 | 26 | /** 27 | * Determines whether the current time falls within the interval defined by {@code start} and 28 | * {@code end}. Implementations are free to fudge this a little bit to take into account possible 29 | * clock skew. 30 | */ 31 | boolean isCurrentTimeInInterval(Instant start, Instant end); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/JsonToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import com.google.gson.JsonElement; 21 | import com.google.gson.JsonObject; 22 | import com.google.gson.JsonPrimitive; 23 | import java.security.SignatureException; 24 | import java.time.Duration; 25 | import java.time.Instant; 26 | import javax.annotation.Nullable; 27 | import net.oauth.jsontoken.crypto.AsciiStringSigner; 28 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 29 | import net.oauth.jsontoken.crypto.Signer; 30 | import net.oauth.jsontoken.exceptions.ErrorCode; 31 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 32 | import org.apache.commons.codec.binary.Base64; 33 | 34 | /** A JSON Token. */ 35 | public class JsonToken { 36 | // header names 37 | public static final String ALGORITHM_HEADER = "alg"; 38 | public static final String KEY_ID_HEADER = "kid"; 39 | public static final String TYPE_HEADER = "typ"; 40 | 41 | // standard claim names (payload parameters) 42 | public static final String ISSUER = "iss"; 43 | public static final String ISSUED_AT = "iat"; 44 | public static final String EXPIRATION = "exp"; 45 | public static final String AUDIENCE = "aud"; 46 | 47 | // default encoding for all Json token 48 | public static final String BASE64URL_ENCODING = "base64url"; 49 | 50 | public static final Duration DEFAULT_LIFETIME = Duration.ofMinutes(2); 51 | 52 | protected final Clock clock; 53 | private final JsonObject header; 54 | private final JsonObject payload; 55 | private final String tokenString; 56 | 57 | // The following fields are only valid when signing the token. 58 | private final Signer signer; 59 | private String signature; 60 | private String baseString; 61 | 62 | /** 63 | * Public constructor, use empty data type. 64 | * 65 | * @param signer the signer that will sign the token. 66 | */ 67 | public JsonToken(Signer signer) { 68 | this(signer, new SystemClock()); 69 | } 70 | 71 | /** 72 | * Public constructor. 73 | * 74 | * @param signer the signer that will sign the token 75 | * @param clock a clock whose notion of current time will determine the not-before timestamp of 76 | * the token, if not explicitly set. 77 | */ 78 | public JsonToken(Signer signer, Clock clock) { 79 | this.signer = checkNotNull(signer); 80 | this.clock = checkNotNull(clock); 81 | this.header = createHeader(signer); 82 | this.payload = new JsonObject(); 83 | this.signature = null; 84 | this.baseString = null; 85 | this.tokenString = null; 86 | 87 | String issuer = signer.getIssuer(); 88 | if (issuer != null) { 89 | setParam(ISSUER, issuer); 90 | } 91 | } 92 | 93 | /** 94 | * Public constructor used when parsing a JsonToken {@link JsonToken} (as opposed to create a 95 | * token). This constructor takes Json payload and clock as parameters, set all other signing 96 | * related parameters to null. 97 | * 98 | * @param payload A payload JSON object. 99 | * @param clock a clock whose notion of current time will determine the not-before timestamp of 100 | * the token, if not explicitly set. 101 | * @param tokenString The original token string we parsed to get this payload. 102 | */ 103 | public JsonToken(JsonObject header, JsonObject payload, Clock clock, String tokenString) { 104 | this.header = header; 105 | this.payload = payload; 106 | this.clock = clock; 107 | this.baseString = null; 108 | this.signature = null; 109 | this.signer = null; 110 | this.tokenString = tokenString; 111 | } 112 | 113 | /** 114 | * Public constructor used when parsing a JsonToken {@link JsonToken} (as opposed to create a 115 | * token). This constructor takes Json payload as parameter, set all other signing related 116 | * parameters to null. 117 | * 118 | * @param payload A payload JSON object. 119 | */ 120 | public JsonToken(JsonObject payload) { 121 | this.header = null; 122 | this.payload = payload; 123 | this.baseString = null; 124 | this.tokenString = null; 125 | this.signature = null; 126 | this.signer = null; 127 | this.clock = null; 128 | } 129 | 130 | /** 131 | * Public constructor used when parsing a JsonToken {@link JsonToken} (as opposed to create a 132 | * token). This constructor takes Json payload and clock as parameters, set all other signing 133 | * related parameters to null. 134 | * 135 | * @param payload A payload JSON object. 136 | * @param clock a clock whose notion of current time will determine the not-before timestamp of 137 | * the token, if not explicitly set. 138 | */ 139 | public JsonToken(JsonObject payload, Clock clock) { 140 | this.header = null; 141 | this.payload = payload; 142 | this.clock = clock; 143 | this.baseString = null; 144 | this.tokenString = null; 145 | this.signature = null; 146 | this.signer = null; 147 | } 148 | 149 | /** 150 | * Returns the serialized representation of this token, i.e., 151 | * keyId.sig.base64(payload).base64(data_type).base64(encoding).base64(alg) 152 | * 153 | *

This is what a client (token issuer) would send to a token verifier over the wire. 154 | * 155 | * @throws SignatureException if the token can't be signed. 156 | */ 157 | public String serializeAndSign() throws SignatureException { 158 | String baseString = computeSignatureBaseString(); 159 | String sig = getSignature(); 160 | return JsonTokenUtil.toDotFormat(baseString, sig); 161 | } 162 | 163 | /** Returns a human-readable version of the token. */ 164 | @Override 165 | public String toString() { 166 | return JsonTokenUtil.toJson(payload); 167 | } 168 | 169 | @Nullable 170 | public String getIssuer() { 171 | return getParamAsString(ISSUER); 172 | } 173 | 174 | @Nullable 175 | public Instant getIssuedAt() { 176 | return getParamAsInstant(ISSUED_AT); 177 | } 178 | 179 | /** 180 | * Sets the {@code iat} (issued at) timestamp parameter. 181 | * 182 | *

Note: sub-second precision is truncated. 183 | */ 184 | public void setIssuedAt(Instant instant) { 185 | setParam(ISSUED_AT, instant.getEpochSecond()); 186 | } 187 | 188 | @Nullable 189 | public Instant getExpiration() { 190 | return getParamAsInstant(EXPIRATION); 191 | } 192 | 193 | /** 194 | * Sets the {@code exp} (expiration) timestamp parameter. 195 | * 196 | *

Note: sub-second precision is truncated. 197 | */ 198 | public void setExpiration(Instant instant) { 199 | setParam(EXPIRATION, instant.getEpochSecond()); 200 | } 201 | 202 | @Nullable 203 | public String getAudience() { 204 | return getParamAsString(AUDIENCE); 205 | } 206 | 207 | public void setAudience(String audience) { 208 | setParam(AUDIENCE, audience); 209 | } 210 | 211 | public void setParam(String name, String value) { 212 | payload.addProperty(name, value); 213 | } 214 | 215 | public void setParam(String name, Number value) { 216 | payload.addProperty(name, value); 217 | } 218 | 219 | @Nullable 220 | public JsonPrimitive getParamAsPrimitive(String param) { 221 | JsonElement element = payload.get(param); 222 | if (element != null && element.isJsonPrimitive()) { 223 | return (JsonPrimitive) element; 224 | } 225 | return null; 226 | } 227 | 228 | @Nullable 229 | public JsonObject getPayloadAsJsonObject() { 230 | return payload; 231 | } 232 | 233 | @Nullable 234 | public String getKeyId() { 235 | if (header == null) { 236 | return null; 237 | } 238 | 239 | JsonElement keyIdName = header.get(KEY_ID_HEADER); 240 | return keyIdName != null ? keyIdName.getAsString() : null; 241 | } 242 | 243 | /** 244 | * @throws IllegalStateException if the header does not exist 245 | * @throws IllegalArgumentException if the signature algorithm is not supported 246 | */ 247 | public SignatureAlgorithm getSignatureAlgorithm() { 248 | if (header == null) { 249 | throw new IllegalStateException("JWT has no algorithm or header"); 250 | } 251 | 252 | JsonElement algorithmName = header.get(ALGORITHM_HEADER); 253 | if (algorithmName == null) { 254 | throw new IllegalStateException( 255 | "JWT header is missing the required '" + ALGORITHM_HEADER + "' parameter", 256 | new InvalidJsonTokenException(ErrorCode.BAD_HEADER)); 257 | } 258 | 259 | try { 260 | return SignatureAlgorithm.getFromJsonName(algorithmName.getAsString()); 261 | } catch (IllegalArgumentException e) { 262 | throw new IllegalArgumentException( 263 | e.getMessage(), new InvalidJsonTokenException(ErrorCode.UNSUPPORTED_ALGORITHM)); 264 | } 265 | } 266 | 267 | public String getTokenString() { 268 | return tokenString; 269 | } 270 | 271 | /** @throws IllegalStateException if the header does not exist */ 272 | public JsonObject getHeader() { 273 | if (header == null) { 274 | throw new IllegalStateException("JWT has no header"); 275 | } 276 | return header; 277 | } 278 | 279 | @Nullable 280 | private String getParamAsString(String param) { 281 | JsonPrimitive primitive = getParamAsPrimitive(param); 282 | return primitive == null ? null : primitive.getAsString(); 283 | } 284 | 285 | @Nullable 286 | private Instant getParamAsInstant(String param) { 287 | JsonPrimitive primitive = getParamAsPrimitive(param); 288 | if (primitive != null && (primitive.isNumber() || primitive.isString())) { 289 | try { 290 | // JWT represents time in seconds 291 | return Instant.ofEpochSecond(primitive.getAsLong()); 292 | } catch (NumberFormatException e) { 293 | return null; 294 | } 295 | } 296 | return null; 297 | } 298 | 299 | /** @throws IllegalStateException if the header does not exist */ 300 | protected String computeSignatureBaseString() { 301 | if (baseString != null && !baseString.isEmpty()) { 302 | return baseString; 303 | } 304 | baseString = 305 | JsonTokenUtil.toDotFormat( 306 | JsonTokenUtil.toBase64(getHeader()), JsonTokenUtil.toBase64(payload)); 307 | return baseString; 308 | } 309 | 310 | private static JsonObject createHeader(Signer signer) { 311 | JsonObject newHeader = new JsonObject(); 312 | SignatureAlgorithm signatureAlgorithm = signer.getSignatureAlgorithm(); 313 | if (signatureAlgorithm != null) { 314 | newHeader.addProperty(ALGORITHM_HEADER, signatureAlgorithm.getNameForJson()); 315 | } 316 | String keyId = signer.getKeyId(); 317 | if (keyId != null) { 318 | newHeader.addProperty(KEY_ID_HEADER, keyId); 319 | } 320 | return newHeader; 321 | } 322 | 323 | /** @throws SignatureException if the signer does not exist */ 324 | private String getSignature() throws SignatureException { 325 | if (signature != null && !signature.isEmpty()) { 326 | return signature; 327 | } 328 | 329 | if (signer == null) { 330 | throw new SignatureException( 331 | "can't sign JsonToken with signer", 332 | new InvalidJsonTokenException(ErrorCode.ILLEGAL_STATE)); 333 | } 334 | 335 | // now, generate the signature 336 | AsciiStringSigner asciiSigner = new AsciiStringSigner(signer); 337 | return Base64.encodeBase64URLSafeString(asciiSigner.sign(baseString)); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/JsonTokenParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.common.base.Preconditions; 19 | import com.google.gson.JsonObject; 20 | import com.google.gson.JsonParseException; 21 | import java.security.SignatureException; 22 | import java.util.List; 23 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 24 | import net.oauth.jsontoken.crypto.Verifier; 25 | import net.oauth.jsontoken.discovery.VerifierProvider; 26 | import net.oauth.jsontoken.discovery.VerifierProviders; 27 | import net.oauth.jsontoken.exceptions.ErrorCode; 28 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 29 | 30 | /** Class that parses and verifies JSON Tokens. */ 31 | public class JsonTokenParser extends AbstractJsonTokenParser { 32 | private final VerifierProviders verifierProviders; 33 | 34 | /** 35 | * Creates a new {@link JsonTokenParser} with a default system clock. The default system clock 36 | * tolerates a clock skew of up to {@link SystemClock#DEFAULT_ACCEPTABLE_CLOCK_SKEW}. 37 | * 38 | * @param verifierProviders an object that provides signature verifiers based on a signature 39 | * algorithm, the signer, and key ids. 40 | * @param checker an audience checker that validates the audience in the JSON token. 41 | */ 42 | public JsonTokenParser(VerifierProviders verifierProviders, Checker checker) { 43 | this(new SystemClock(), verifierProviders, checker); 44 | } 45 | 46 | /** 47 | * Creates a new {@link JsonTokenParser}. 48 | * 49 | * @param clock a clock object that will decide whether a given token is currently valid or not. 50 | * @param verifierProviders an object that provides signature verifiers based on a signature 51 | * algorithm, the signer, and key ids. 52 | * @param checkers an array of checkers that validates the parameters in the JSON token. 53 | */ 54 | public JsonTokenParser(Clock clock, VerifierProviders verifierProviders, Checker... checkers) { 55 | super(clock, checkers); 56 | this.verifierProviders = verifierProviders; 57 | } 58 | 59 | /** 60 | * Verifies that the jsonToken has a valid signature and valid standard claims (iat, exp). Uses 61 | * VerifierProviders to obtain the secret key. 62 | * 63 | * @param jsonToken 64 | * @throws SignatureException when the signature is invalid or if any of the checkers fail 65 | * @throws IllegalArgumentException if the signature algorithm is not supported 66 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 67 | * valid verifier for the issuer or if the header does not exist 68 | */ 69 | public void verify(JsonToken jsonToken) throws SignatureException { 70 | List verifiers = provideVerifiers(jsonToken); 71 | verify(jsonToken, verifiers); 72 | } 73 | 74 | /** 75 | * Parses, and verifies, a JSON Token. 76 | * 77 | * @param tokenString the serialized token that is to parsed and verified. 78 | * @return the deserialized {@link JsonObject}, suitable for passing to the constructor of {@link 79 | * JsonToken} or equivalent constructor of {@link JsonToken} subclasses. 80 | * @throws SignatureException when the signature is invalid or if any of the checkers fail 81 | * @throws JsonParseException if the header or payload portion of tokenString is corrupted 82 | * @throws IllegalArgumentException if the signature algorithm is not supported 83 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 84 | * valid verifier for the issuer 85 | */ 86 | public JsonToken verifyAndDeserialize(String tokenString) throws SignatureException { 87 | JsonToken jsonToken = deserialize(tokenString); 88 | verify(jsonToken); 89 | return jsonToken; 90 | } 91 | 92 | /** 93 | * Decodes the JWT token string into a JsonToken object. Does not perform any validation of 94 | * headers or claims. Identical to {@link AbstractJsonTokenParser#deserializeInternal(String)}. 95 | * 96 | * @param tokenString The original encoded representation of a JWT 97 | * @return Unverified contents of the JWT as a JsonToken 98 | * @throws JsonParseException if the header or payload of tokenString is corrupted 99 | * @throws IllegalStateException if tokenString is not a properly formatted JWT 100 | */ 101 | public JsonToken deserialize(String tokenString) { 102 | return deserializeInternal(tokenString); 103 | } 104 | 105 | /** 106 | * Verifies that the jsonToken has a valid signature and valid standard claims (iat, exp). Does 107 | * not need VerifierProviders because verifiers are passed in directly. Identical to {@link 108 | * AbstractJsonTokenParser#verifyInternal(JsonToken, List)} 109 | * 110 | * @param jsonToken the token to verify 111 | * @throws SignatureException when the signature is invalid or if any of the checkers fail 112 | * @throws IllegalStateException when exp or iat are invalid or if tokenString is not a properly 113 | * formatted JWT 114 | */ 115 | public void verify(JsonToken jsonToken, List verifiers) throws SignatureException { 116 | verifyInternal(jsonToken, verifiers); 117 | } 118 | 119 | /** 120 | * Verifies that a JSON Web Token's signature is valid. Identical to {@link 121 | * AbstractJsonTokenParser#signatureIsValidInternal(String, List)}. 122 | * 123 | * @param tokenString the encoded and signed JSON Web Token to verify. 124 | * @param verifiers used to verify the signature. These usually encapsulate secret keys. 125 | * @throws IllegalStateException if tokenString is not a properly formatted JWT 126 | */ 127 | public boolean signatureIsValid(String tokenString, List verifiers) { 128 | return signatureIsValidInternal(tokenString, verifiers); 129 | } 130 | 131 | /** 132 | * Use VerifierProviders to get a list of verifiers for this token 133 | * 134 | * @param jsonToken 135 | * @return list of verifiers 136 | * @throws IllegalArgumentException if the signature algorithm is not supported 137 | * @throws IllegalStateException if there is no valid verifier for the issuer or if the header 138 | * does not exist 139 | */ 140 | private List provideVerifiers(JsonToken jsonToken) { 141 | Preconditions.checkNotNull(verifierProviders); 142 | SignatureAlgorithm signatureAlgorithm = jsonToken.getSignatureAlgorithm(); 143 | VerifierProvider provider = verifierProviders.getVerifierProvider(signatureAlgorithm); 144 | if (provider == null) { 145 | throw new IllegalArgumentException( 146 | "Signature algorithm not supported: " + signatureAlgorithm); 147 | } 148 | 149 | List verifiers = provider.findVerifier(jsonToken.getIssuer(), jsonToken.getKeyId()); 150 | if (verifiers == null) { 151 | throw new IllegalStateException( 152 | "No valid verifier for issuer: " + jsonToken.getIssuer(), 153 | new InvalidJsonTokenException(ErrorCode.NO_VERIFIER)); 154 | } 155 | 156 | return verifiers; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/JsonTokenUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.common.base.Joiner; 19 | import com.google.gson.Gson; 20 | import com.google.gson.JsonObject; 21 | import org.apache.commons.codec.binary.Base64; 22 | import org.apache.commons.codec.binary.StringUtils; 23 | 24 | /** Some utility functions for {@link JsonToken}s. */ 25 | final class JsonTokenUtil { 26 | 27 | public static final String DELIMITER = "."; 28 | 29 | public static String toBase64(JsonObject json) { 30 | return convertToBase64(toJson(json)); 31 | } 32 | 33 | public static String toJson(JsonObject json) { 34 | return new Gson().toJson(json); 35 | } 36 | 37 | public static String convertToBase64(String source) { 38 | return Base64.encodeBase64URLSafeString(StringUtils.getBytesUtf8(source)); 39 | } 40 | 41 | public static String decodeFromBase64String(String encoded) { 42 | return new String(Base64.decodeBase64(encoded)); 43 | } 44 | 45 | public static String fromBase64ToJsonString(String source) { 46 | return StringUtils.newStringUtf8(Base64.decodeBase64(source)); 47 | } 48 | 49 | public static String toDotFormat(String... parts) { 50 | return Joiner.on(DELIMITER).useForNull("").join(parts); 51 | } 52 | 53 | private JsonTokenUtil() {} 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/SystemClock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import com.google.common.collect.Range; 21 | import java.time.Duration; 22 | import java.time.Instant; 23 | 24 | /** 25 | * Default implementation of {@link Clock}, which accepts clock skews (when comparing time 26 | * instances) of up to 2 minutes. 27 | */ 28 | public class SystemClock implements Clock { 29 | 30 | public static final Duration DEFAULT_ACCEPTABLE_CLOCK_SKEW = Duration.ofMinutes(2); 31 | 32 | private final Duration acceptableClockSkew; 33 | 34 | /** Public constructor. */ 35 | public SystemClock() { 36 | this(DEFAULT_ACCEPTABLE_CLOCK_SKEW); 37 | } 38 | 39 | /** 40 | * Public constructor. 41 | * 42 | * @param acceptableClockSkew the current time will be considered inside the interval at {@link 43 | * #isCurrentTimeInInterval(Instant, Instant)} even if the current time is up to 44 | * acceptableClockSkew off the ends of the interval. 45 | */ 46 | public SystemClock(Duration acceptableClockSkew) { 47 | this.acceptableClockSkew = checkNotNull(acceptableClockSkew); 48 | } 49 | 50 | /* 51 | * (non-Javadoc) 52 | * @see net.oauth.jsontoken.Clock#now() 53 | */ 54 | @Override 55 | public Instant now() { 56 | return Instant.now(); 57 | } 58 | 59 | /** 60 | * Determines whether the current time (plus minus the {@code acceptableClockSkew}) falls within 61 | * the interval defined by {@code start} and {@code end}. 62 | */ 63 | @Override 64 | public boolean isCurrentTimeInInterval(Instant start, Instant end) { 65 | Range interval = Range.closed(start, end); 66 | Instant now = now(); 67 | Range currentTimeWithSkew = 68 | Range.closed(now.minus(acceptableClockSkew), now.plus(acceptableClockSkew)); 69 | return interval.isConnected(currentTimeWithSkew); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/AbstractSigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | /** Superclass for {@link Signer}s. */ 19 | public abstract class AbstractSigner implements Signer { 20 | 21 | private final String issuer; 22 | private String keyId; 23 | 24 | /** 25 | * Caller can suggest which key should be used for signing by passing 'suggestedKeyId' to signer. 26 | * It's up to signer whether to use the suggestedKeyId or not. The final signing key id can be 27 | * retrieved by calling getKeyId(). 28 | * 29 | * @param issuer 30 | * @param suggestedKeyId 31 | */ 32 | protected AbstractSigner(String issuer, String suggestedKeyId) { 33 | this.issuer = issuer; 34 | this.keyId = suggestedKeyId; 35 | } 36 | 37 | protected void setSigningKeyId(String keyId) { 38 | this.keyId = keyId; 39 | } 40 | 41 | /* 42 | * (non-Javadoc) 43 | * @see net.oauth.jsontoken.crypto.Signer#getKeyId() 44 | */ 45 | @Override 46 | public String getKeyId() { 47 | return keyId; 48 | } 49 | 50 | /* 51 | * (non-Javadoc) 52 | * @see net.oauth.jsontoken.crypto.Signer#getIssuer() 53 | */ 54 | @Override 55 | public String getIssuer() { 56 | return issuer; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/AsciiStringSigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.SignatureException; 19 | import org.apache.commons.codec.binary.StringUtils; 20 | 21 | /** 22 | * A Signer that can sign Strings (as opposed to byte arrays), assuming that the String contains 23 | * characters in the US-ASCII charset. 24 | */ 25 | public class AsciiStringSigner { 26 | 27 | private final Signer signer; 28 | 29 | /** 30 | * Public constructor. 31 | * 32 | * @param signer {@link Signer} that can sign byte arrays. 33 | */ 34 | public AsciiStringSigner(Signer signer) { 35 | this.signer = signer; 36 | } 37 | 38 | /** 39 | * Signs the given ASCII string. 40 | * 41 | * @throws SignatureException when the signature cannot be generated. 42 | */ 43 | public byte[] sign(String source) throws SignatureException { 44 | return signer.sign(StringUtils.getBytesUsAscii(source)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/AsciiStringVerifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.SignatureException; 19 | import org.apache.commons.codec.binary.StringUtils; 20 | 21 | /** 22 | * A Verifier that can verify Strings (as opposed to byte arrays), assuming that the String contains 23 | * characters in the US-ASCII charset. 24 | */ 25 | public class AsciiStringVerifier { 26 | 27 | private final Verifier verifier; 28 | 29 | /** 30 | * Public constructor. 31 | * 32 | * @param verifier A {@link Verifier} that can verify signatures on byte arrays. 33 | */ 34 | public AsciiStringVerifier(Verifier verifier) { 35 | this.verifier = verifier; 36 | } 37 | 38 | /** 39 | * Verifies a signature on an ASCII string. 40 | * 41 | * @param source the source that was signed. 42 | * @param signature the signature on the source. 43 | * @throws SignatureException if the signature doesn't verify. 44 | */ 45 | public void verifySignature(String source, byte[] signature) throws SignatureException { 46 | verifier.verifySignature(StringUtils.getBytesUsAscii(source), signature); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/HmacSHA256Signer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.InvalidKeyException; 19 | import java.security.NoSuchAlgorithmException; 20 | import javax.crypto.Mac; 21 | import javax.crypto.SecretKey; 22 | import javax.crypto.spec.SecretKeySpec; 23 | 24 | /** A signer that can sign byte arrays using HMAC-SHA256. */ 25 | public class HmacSHA256Signer extends AbstractSigner { 26 | 27 | private static final String HMAC_SHA256_ALG = "HmacSHA256"; 28 | 29 | private final Mac hmac; 30 | private final SecretKey signingKey; 31 | 32 | /** 33 | * Public constructor. 34 | * 35 | * @param issuer the id of this signer, to be included in the envelope of the JSON token. 36 | * @param keyId the id of the key that will be included in the envelope. If null, will be omitted 37 | * from the envelope. 38 | * @param keyBytes the actual key. 39 | * @throws InvalidKeyException if the key cannot be used as an HMAC key. 40 | */ 41 | public HmacSHA256Signer(String issuer, String keyId, byte[] keyBytes) throws InvalidKeyException { 42 | super(issuer, keyId); 43 | 44 | this.signingKey = new SecretKeySpec(keyBytes, HMAC_SHA256_ALG); 45 | try { 46 | this.hmac = Mac.getInstance(HMAC_SHA256_ALG); 47 | } catch (NoSuchAlgorithmException e) { 48 | throw new IllegalStateException( 49 | "cannot use Hmac256Signer on system without HmacSHA256 alg", e); 50 | } 51 | 52 | // just to make sure we catch invalid keys early, let's initialize the hmac and throw if 53 | // something goes wrong 54 | hmac.init(signingKey); 55 | } 56 | 57 | /* 58 | * (non-Javadoc) 59 | * @see net.oauth.jsontoken.crypto.Signer#sign(byte[]) 60 | */ 61 | @Override 62 | public byte[] sign(byte[] source) { 63 | try { 64 | hmac.init(signingKey); 65 | } catch (InvalidKeyException e) { 66 | // this should not happen - we tested this in the constructor 67 | throw new IllegalStateException( 68 | "key somehow became invalid since calling the constructor", e); 69 | } 70 | return hmac.doFinal(source); 71 | } 72 | 73 | /* 74 | * (non-Javadoc) 75 | * @see net.oauth.jsontoken.crypto.Signer#getSignatureAlgorithm() 76 | */ 77 | @Override 78 | public SignatureAlgorithm getSignatureAlgorithm() { 79 | return SignatureAlgorithm.HS256; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/HmacSHA256Verifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.InvalidKeyException; 19 | import java.security.SignatureException; 20 | 21 | /** A {@link Verifier} that uses HMAC-SHA256 to verify symmetric-key signatures on byte arrays. */ 22 | public class HmacSHA256Verifier implements Verifier { 23 | 24 | private final HmacSHA256Signer signer; 25 | 26 | /** 27 | * Public constructor. 28 | * 29 | * @param verificationKey the HMAC verification key to be used for signature verification. 30 | * @throws InvalidKeyException if the verificationKey cannot be used as an HMAC key. 31 | */ 32 | public HmacSHA256Verifier(byte[] verificationKey) throws InvalidKeyException { 33 | signer = new HmacSHA256Signer("verifier", null, verificationKey); 34 | } 35 | 36 | /* 37 | * (non-Javadoc) 38 | * @see net.oauth.jsontoken.crypto.Verifier#verifySignature(byte[], byte[]) 39 | */ 40 | @Override 41 | public void verifySignature(byte[] source, byte[] signature) throws SignatureException { 42 | byte[] comparison = signer.sign(source); 43 | if (!compareBytes(signature, comparison)) { 44 | throw new SignatureException("signature did not verify"); 45 | } 46 | } 47 | 48 | /** 49 | * Performs a byte-by-byte comparison of {@code first} and {@code second} parameters. This method 50 | * will "NOT" short-circuit the comparison once it has detected a byte difference in order to 51 | * defend against a "timing attack". 52 | * 53 | * @param first the first byte array used in the comparison 54 | * @param second the second byte array used in the comparison 55 | * @return {@code true} if the {@code first} and {@code second} byte arrays are equal otherwise 56 | * {@code false} 57 | */ 58 | private boolean compareBytes(byte[] first, byte[] second) { 59 | if (first == null || second == null) { 60 | return (first == second); 61 | } else if (first.length != second.length) { 62 | return false; 63 | } else { 64 | byte result = 0; 65 | for (int i = 0; i < first.length; i++) { 66 | result |= first[i] ^ second[i]; 67 | } 68 | return (result == 0); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/MagicRsaPublicKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.math.BigInteger; 19 | import java.security.KeyFactory; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.security.PublicKey; 22 | import java.security.spec.InvalidKeySpecException; 23 | import java.security.spec.RSAPublicKeySpec; 24 | import java.util.regex.Pattern; 25 | import org.apache.commons.codec.binary.Base64; 26 | 27 | /** 28 | * Class that can parse "magic key" RSA public key representations, which are of the form 29 | * RSA... 30 | */ 31 | public class MagicRsaPublicKey { 32 | 33 | private final PublicKey publicKey; 34 | 35 | /** 36 | * Public constructor. 37 | * 38 | * @param magicKey the serialized key (of the form RSA.modulus.exponent). 39 | */ 40 | public MagicRsaPublicKey(String magicKey) { 41 | this.publicKey = parseKey(magicKey); 42 | } 43 | 44 | /** Returns the public key represented by the "magic" serialized key. */ 45 | public PublicKey getKey() { 46 | return publicKey; 47 | } 48 | 49 | private static PublicKey parseKey(String magicKey) { 50 | String[] pieces = magicKey.split(Pattern.quote(".")); 51 | if (pieces.length != 3) { 52 | throw new IllegalStateException("not a valid magic key: " + magicKey); 53 | } 54 | 55 | if (!pieces[0].equals("RSA")) { 56 | throw new IllegalStateException("unkown key type for magic key: " + pieces[0]); 57 | } 58 | 59 | String modulusString = pieces[1]; 60 | String exponentString = pieces[2]; 61 | 62 | byte[] modulusBytes = Base64.decodeBase64(modulusString); 63 | byte[] exponentBytes = Base64.decodeBase64(exponentString); 64 | 65 | BigInteger modulus = new BigInteger(modulusBytes); 66 | BigInteger exponent = new BigInteger(exponentBytes); 67 | 68 | RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); 69 | KeyFactory fac; 70 | try { 71 | fac = KeyFactory.getInstance("RSA"); 72 | } catch (NoSuchAlgorithmException e) { 73 | throw new RuntimeException("RSA key factory missing on platform", e); 74 | } 75 | try { 76 | return fac.generatePublic(spec); 77 | } catch (InvalidKeySpecException e) { 78 | throw new IllegalStateException("bad key in descripor doc: " + magicKey, e); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/RsaSHA1Verifier.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken.crypto; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.security.PublicKey; 6 | import java.security.Signature; 7 | import java.security.SignatureException; 8 | 9 | public class RsaSHA1Verifier implements Verifier { 10 | 11 | private final PublicKey verificationKey; 12 | private final Signature signer; 13 | 14 | /** 15 | * Public Constructor. 16 | * 17 | * @param verificationKey the key used to verify the signature. 18 | */ 19 | public RsaSHA1Verifier(PublicKey verificationKey) { 20 | this.verificationKey = verificationKey; 21 | try { 22 | this.signer = Signature.getInstance("SHA1withRSA"); 23 | this.signer.initVerify(verificationKey); 24 | } catch (NoSuchAlgorithmException e) { 25 | throw new IllegalStateException("platform is missing RSAwithSHA1 signature alg", e); 26 | } catch (InvalidKeyException e) { 27 | throw new IllegalStateException("key is invalid", e); 28 | } 29 | } 30 | 31 | @Override 32 | public void verifySignature(byte[] source, byte[] signature) throws SignatureException { 33 | try { 34 | signer.initVerify(verificationKey); 35 | } catch (InvalidKeyException e) { 36 | throw new RuntimeException("key someone become invalid since calling the constructor"); 37 | } 38 | signer.update(source); 39 | if (!signer.verify(signature)) { 40 | throw new SignatureException("signature did not verify"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/RsaSHA256Signer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.InvalidKeyException; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.security.PrivateKey; 21 | import java.security.Signature; 22 | import java.security.SignatureException; 23 | import java.security.interfaces.RSAPrivateKey; 24 | 25 | /** Signer that can sign byte arrays using RSA and SHA-256. */ 26 | public class RsaSHA256Signer extends AbstractSigner { 27 | 28 | private final Signature signature; 29 | private final PrivateKey signingKey; 30 | 31 | /** 32 | * Public constructor. 33 | * 34 | * @param issuer The id of this signer, to be included in the JSON Token's envelope. 35 | * @param keyId The id of the key used by this signer, to be included in the JSON Token's 36 | * envelope. 37 | * @param key the private key to be used for signing. 38 | * @throws InvalidKeyException if the key is unsuitable for RSA signing. 39 | */ 40 | public RsaSHA256Signer(String issuer, String keyId, RSAPrivateKey key) 41 | throws InvalidKeyException { 42 | super(issuer, keyId); 43 | 44 | this.signingKey = key; 45 | 46 | try { 47 | this.signature = Signature.getInstance("SHA256withRSA"); 48 | this.signature.initSign(signingKey); 49 | } catch (NoSuchAlgorithmException e) { 50 | throw new IllegalStateException( 51 | "platform is missing RSAwithSHA256 signature alg, or key is invalid", e); 52 | } 53 | } 54 | 55 | /* 56 | * (non-Javadoc) 57 | * @see net.oauth.jsontoken.crypto.Signer#getSignatureAlgorithm() 58 | */ 59 | @Override 60 | public SignatureAlgorithm getSignatureAlgorithm() { 61 | return SignatureAlgorithm.RS256; 62 | } 63 | 64 | /* 65 | * (non-Javadoc) 66 | * @see net.oauth.jsontoken.crypto.Signer#sign(byte[]) 67 | */ 68 | @Override 69 | public byte[] sign(byte[] source) throws SignatureException { 70 | try { 71 | signature.initSign(signingKey); 72 | } catch (InvalidKeyException e) { 73 | throw new RuntimeException("key somehow became invalid since calling the constructor"); 74 | } 75 | signature.update(source); 76 | return signature.sign(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/RsaSHA256Verifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.InvalidKeyException; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.security.PublicKey; 21 | import java.security.Signature; 22 | import java.security.SignatureException; 23 | 24 | /** A verifier that can verify signatures on byte arrays using RSA and SHA-256. */ 25 | public class RsaSHA256Verifier implements Verifier { 26 | 27 | private final PublicKey verificationKey; 28 | private final Signature signer; 29 | 30 | /** 31 | * Public Constructor. 32 | * 33 | * @param verificationKey the key used to verify the signature. 34 | */ 35 | public RsaSHA256Verifier(PublicKey verificationKey) { 36 | this.verificationKey = verificationKey; 37 | try { 38 | this.signer = Signature.getInstance("SHA256withRSA"); 39 | this.signer.initVerify(verificationKey); 40 | } catch (NoSuchAlgorithmException e) { 41 | throw new IllegalStateException("platform is missing RSAwithSHA256 signature alg", e); 42 | } catch (InvalidKeyException e) { 43 | throw new IllegalStateException("key is invalid", e); 44 | } 45 | } 46 | 47 | /* 48 | * (non-Javadoc) 49 | * @see net.oauth.jsontoken.crypto.Verifier#verifySignature(byte[], byte[]) 50 | */ 51 | @Override 52 | public void verifySignature(byte[] source, byte[] signature) throws SignatureException { 53 | try { 54 | signer.initVerify(verificationKey); 55 | } catch (InvalidKeyException e) { 56 | throw new RuntimeException("key someone become invalid since calling the constructor"); 57 | } 58 | signer.update(source); 59 | if (!signer.verify(signature)) { 60 | throw new SignatureException("signature did not verify"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/SignatureAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | /** Enum of the signature algorithms supported by this package. */ 19 | public enum SignatureAlgorithm { 20 | HS256("SHA256"), 21 | HS1("SHA1"), 22 | RS256("SHA256"), 23 | RS1("SHA1"); 24 | 25 | private final String hashAlg; 26 | 27 | private SignatureAlgorithm(String hashAlg) { 28 | this.hashAlg = hashAlg; 29 | } 30 | 31 | /** What the signature algorithm is named in the "alg" parameter in a JSON Token's envelope. */ 32 | public String getNameForJson() { 33 | return name(); 34 | } 35 | 36 | /** 37 | * Returns the hash algorithm that should be used when hashing data. When large pieces of data are 38 | * to be included in a JSON Token's payload, it sometimes might make sense to include the hash of 39 | * the data instead. If an issuer wants to do that, they should use this hash algorithm to hash 40 | * the data. 41 | */ 42 | public String getHashAlgorithm() { 43 | return hashAlg; 44 | } 45 | 46 | /** Given the name of the algorithm in the envelope, returns the corresponding enum instance. */ 47 | public static SignatureAlgorithm getFromJsonName(String name) { 48 | return SignatureAlgorithm.valueOf(name); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/Signer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.SignatureException; 19 | 20 | /** 21 | * Interface that a token signer has to implement. A Signer represents a specific signing key, and 22 | * knows the id of this key. (The key id is an identifier by which a verifier can find this 23 | * particular key. It does not need to be globally unique, but must be unique for per token issuer.) 24 | * A Signer also belongs to a certain issuer: An issuer is the entity that issues tokens, and uses 25 | * signers to sign them. 26 | */ 27 | public interface Signer { 28 | 29 | /** 30 | * Returns the id of this signing key. If not null, this will be included in the JSON Token's 31 | * envelope as the key_id parameter. 32 | */ 33 | String getKeyId(); 34 | 35 | /** 36 | * The issuer of the JSON Token. Each signer belongs to an issuer, and an issuer may have one or 37 | * more signers, each with a distinct key id. 38 | */ 39 | String getIssuer(); 40 | 41 | /** Returns the signature algorithm used by this signer. */ 42 | SignatureAlgorithm getSignatureAlgorithm(); 43 | 44 | /** 45 | * Signs an array of bytes. 46 | * 47 | * @param source The bytes that should be signed. 48 | * @return The signature on the bytes. 49 | * @throws SignatureException if the signer could not create the signature. 50 | */ 51 | byte[] sign(byte[] source) throws SignatureException; 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/crypto/Verifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import java.security.SignatureException; 19 | 20 | /** Interface that a JSON Token verifier has to implement. */ 21 | public interface Verifier { 22 | 23 | /** 24 | * Verifies a signature on an array of bytes. 25 | * 26 | * @param source The bytes that were signed. 27 | * @param signature The signature on the bytes. 28 | * @throws SignatureException If the signature doesn't match, or if some other error occurred. 29 | */ 30 | void verifySignature(byte[] source, byte[] signature) throws SignatureException; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/AsyncVerifierProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import com.google.common.util.concurrent.ListenableFuture; 19 | import java.util.List; 20 | import javax.annotation.Nonnull; 21 | import net.oauth.jsontoken.AsyncJsonTokenParser; 22 | import net.oauth.jsontoken.crypto.Verifier; 23 | 24 | /** 25 | * The asynchronous counterpart of {@link VerifierProvider}. An interface that must be implemented 26 | * by JSON Token verifiers. The {@link AsyncJsonTokenParser} uses {@link AsyncVerifierProvider} 27 | * implementations to find verification keys asynchronously with which to verify the parsed JSON 28 | * Token. There are different implementations of this interface for different types of verification 29 | * keys. 30 | * 31 | *

For symmetric signing keys, an implementation of {@link AsyncVerifierProvider} presumably will 32 | * always look up the key in a local database. For public signing keys, the {@link 33 | * AsyncVerifierProvider} implementation may fetch the public verification keys when needed from the 34 | * public internet. 35 | */ 36 | public interface AsyncVerifierProvider { 37 | 38 | /** 39 | * Returns a {@link ListenableFuture}, which asynchronously returns a {@code List} that 40 | * represents a certain verification key, given the key's id and its issuer. 41 | * 42 | * @param issuer the id of the issuer that's using the key. 43 | * @param keyId the id of the key, if keyId mismatches, return a list of possible verification 44 | * keys. 45 | * @return a {@link ListenableFuture} object that asynchronously returns a {@code List} 46 | * that represents the verification key. 47 | */ 48 | @Nonnull 49 | ListenableFuture> findVerifier(String issuer, String keyId); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/AsyncVerifierProviders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import javax.annotation.Nullable; 19 | import net.oauth.jsontoken.AsyncJsonTokenParser; 20 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 21 | 22 | /** 23 | * The asynchronous counterpart of {@link VerifierProviders}. An interface that must be implemented 24 | * by JSON Token verifiers. The {@link AsyncJsonTokenParser} uses the {@link AsyncVerifierProviders} 25 | * implementation to locate verification keys. In particular, it will first look up the {@link 26 | * AsyncVerifierProvider} for the signature algorithm used in the JSON Token and the ask the {@link 27 | * AsyncVerifierProvider} to provide a future that will return a {@code List} to check the 28 | * validity of the JSON Token. 29 | */ 30 | public interface AsyncVerifierProviders { 31 | 32 | /** 33 | * @param alg the signature algorithm of the JSON Token. 34 | * @return a {@link AsyncVerifierProvider} corresponding to a given signature algorithm that 35 | * allows for asynchronous retrieval of a verification key. 36 | */ 37 | @Nullable 38 | AsyncVerifierProvider getVerifierProvider(SignatureAlgorithm alg); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/DefaultPublicKeyLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import com.google.common.collect.Lists; 19 | import java.net.URI; 20 | import java.util.List; 21 | import net.oauth.jsontoken.crypto.RsaSHA256Verifier; 22 | import net.oauth.jsontoken.crypto.Verifier; 23 | 24 | /** 25 | * Default strategy for locating public verification keys. Unlike secret (symmetric) verification 26 | * keys, public verification keys can be published by token issuers at URLs called "server 27 | * descriptors". 28 | * 29 | *

The default strategy to find a public verification key consists of first mapping an issuer id 30 | * to a server descriptor, and then fetching the ServerInfo document from the server descriptor URL. 31 | * Finally, the key is looked up int the ServerInfo document by key id. 32 | */ 33 | public class DefaultPublicKeyLocator implements VerifierProvider { 34 | 35 | private final ServerDescriptorProvider descriptorProvider; 36 | private final ServerInfoResolver descriptorResolver; 37 | 38 | /** 39 | * Public constructor. 40 | * 41 | * @param descriptorProvider A {@link ServerDescriptorProvider} that maps issuer ids to server 42 | * descriptors (URLs). 43 | * @param resolver A {@link ServerInfoResolver}, i.e., an object that can fetch and parse a server 44 | * info document, given a server descriptor. 45 | */ 46 | public DefaultPublicKeyLocator( 47 | ServerDescriptorProvider descriptorProvider, ServerInfoResolver resolver) { 48 | this.descriptorProvider = descriptorProvider; 49 | this.descriptorResolver = resolver; 50 | } 51 | 52 | /* 53 | * (non-Javadoc) 54 | * @see net.oauth.jsontoken.discovery.VerifierProvider#findVerifier(java.lang.String, java.lang.String) 55 | */ 56 | @Override 57 | public List findVerifier(String issuer, String keyId) { 58 | URI serverDescriptor = descriptorProvider.getServerDescriptor(issuer); 59 | Verifier rsaVerifier = 60 | new RsaSHA256Verifier( 61 | descriptorResolver.resolve(serverDescriptor).getVerificationKey(keyId)); 62 | return Lists.newArrayList(rsaVerifier); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/IdentityServerDescriptorProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import java.net.URI; 19 | 20 | /** 21 | * A {@link ServerDescriptorProvider} that returns the issuer id as the server descriptor. If a JSON 22 | * Token issuer uses their own server descriptor as their issuer id, then the JSON Token verifier 23 | * would use this implementation of {@link ServerDescriptorProvider} with the {@link 24 | * DefaultPublicKeyLocator}. 25 | * 26 | *

For example, some OAuth Servers might use their Client's server descriptors as client_ids, and 27 | * then use this implementation of {@link ServerDescriptorProvider} with the {@link 28 | * DefaultPublicKeyLocator}. 29 | */ 30 | public class IdentityServerDescriptorProvider implements ServerDescriptorProvider { 31 | 32 | /* 33 | * (non-Javadoc) 34 | * @see net.oauth.jsontoken.discovery.ServerDescriptorProvider#getServerDescriptor(java.lang.String) 35 | */ 36 | @Override 37 | public URI getServerDescriptor(String issuer) { 38 | return URI.create(issuer); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/JsonServerInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import com.google.common.collect.Maps; 19 | import com.google.gson.Gson; 20 | import com.google.gson.annotations.SerializedName; 21 | import java.security.PublicKey; 22 | import java.util.Map; 23 | import net.oauth.jsontoken.crypto.MagicRsaPublicKey; 24 | 25 | /** 26 | * Implementation of the {@link ServerInfo} interface that assumes the server info document is in 27 | * JSON format. It can parse such a JSON-formatted server info document and exposes its contents 28 | * through the requisite methods of the {@link ServerInfo} interface. 29 | */ 30 | public class JsonServerInfo implements ServerInfo { 31 | 32 | @SerializedName("verification_keys") 33 | private final Map verificationKeys = Maps.newHashMap(); 34 | 35 | /** 36 | * Parses a JSON-formatted server info document and returns it as a {@link JsonServerInfo} object. 37 | * 38 | * @param json the contents of the JSON-formatted server info document. 39 | */ 40 | public static JsonServerInfo getDocument(String json) { 41 | return new Gson().fromJson(json, JsonServerInfo.class); 42 | } 43 | 44 | /* 45 | * (non-Javadoc) 46 | * @see net.oauth.jsontoken.discovery.ServerInfo#getVerificationKey(java.lang.String) 47 | */ 48 | @Override 49 | public PublicKey getVerificationKey(String keyId) { 50 | String magicKey = verificationKeys.get(keyId); 51 | if (magicKey == null) { 52 | return null; 53 | } else { 54 | return new MagicRsaPublicKey(magicKey).getKey(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/ServerDescriptorProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import java.net.URI; 19 | 20 | /** 21 | * Interface that a JSON Token verifier can implement to help with locating public verification 22 | * keys. If a JSON Token verifier wants to take advantage of the {@link DefaultPublicKeyLocator} 23 | * implementation, it needs to provide an implementation of this interface to map issuer ids to 24 | * server descriptors. Server descriptors are URLs that resolve to server info documents (which, 25 | * among other things, contain public verification keys). 26 | */ 27 | public interface ServerDescriptorProvider { 28 | 29 | /** Returns the server descriptor, given the issuer id present in a JSON Token. */ 30 | URI getServerDescriptor(String issuer); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/ServerInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import java.security.PublicKey; 19 | 20 | /** 21 | * Represents the document located at a server's "server descriptor" URL. Such a "server info" 22 | * document contains, among other things, the public verification keys that can be used to verify 23 | * JSON Tokens issued by this server. 24 | */ 25 | public interface ServerInfo { 26 | 27 | /** Returns the verification key with the given key id. */ 28 | PublicKey getVerificationKey(String keyId); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/ServerInfoResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import java.net.URI; 19 | 20 | /** 21 | * Interface that a JSON Token verifier can implement to help with locating public verification 22 | * keys. If a JSON Token verifier wants to take advantage of the {@link DefaultPublicKeyLocator} 23 | * implementation, it needs to provide an implementation of this interface to fetch and parse server 24 | * info documents. The implementation should if possible recognize different encodings of the server 25 | * info document (e.g., JSON and XML). 26 | */ 27 | public interface ServerInfoResolver { 28 | 29 | /** 30 | * Fetches and parses a server info document. 31 | * 32 | * @param serverDescriptor the URL from which the server info document should be fetched. 33 | * @return an object representing the server info document. 34 | */ 35 | ServerInfo resolve(URI serverDescriptor); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/UrlBasedVerifierProvider.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken.discovery; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import java.io.BufferedReader; 8 | import java.io.ByteArrayInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.net.HttpURLConnection; 13 | import java.net.MalformedURLException; 14 | import java.net.URL; 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.CertificateFactory; 17 | import java.security.cert.X509Certificate; 18 | import java.util.List; 19 | import java.util.Map; 20 | import net.oauth.jsontoken.crypto.RsaSHA256Verifier; 21 | import net.oauth.jsontoken.crypto.Verifier; 22 | import org.apache.commons.codec.binary.Base64; 23 | 24 | /** 25 | * Simple certificates finder by fetching from URL. Expects simple json format, for example: 26 | * {"keyid":"x509 certificate in Pem format", "keyid2":"x509 certificate in Pem format"..} 27 | */ 28 | public class UrlBasedVerifierProvider implements VerifierProvider { 29 | 30 | private final String publicCertUrl; 31 | 32 | public UrlBasedVerifierProvider(String publicCertUrl) { 33 | this.publicCertUrl = publicCertUrl; 34 | } 35 | 36 | @Override 37 | public List findVerifier(String issuer, String keyId) { 38 | try { 39 | URL url = new URL(publicCertUrl); 40 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 41 | connection.setRequestMethod("GET"); 42 | if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { 43 | 44 | InputStreamReader in = new InputStreamReader((InputStream) connection.getContent()); 45 | BufferedReader buff = new BufferedReader(in); 46 | StringBuffer content = new StringBuffer(); 47 | String line = ""; 48 | do { 49 | line = buff.readLine(); 50 | content.append(line + "\n"); 51 | } while (line != null); 52 | 53 | JsonObject jsonObject = JsonParser.parseString(content.toString()).getAsJsonObject(); 54 | List verifiers = Lists.newArrayList(); 55 | 56 | for (Map.Entry cert : jsonObject.entrySet()) { 57 | String x509PemCertString = cert.getValue().getAsString(); 58 | // Parse pem format 59 | String[] parts = x509PemCertString.split("\n"); 60 | if (parts.length < 3) { 61 | return null; 62 | } 63 | String x509CertString = ""; 64 | for (int i = 1; i < parts.length - 1; i++) { 65 | x509CertString += parts[i]; 66 | } 67 | // parse x509 68 | byte[] certBytes = Base64.decodeBase64(x509CertString); 69 | CertificateFactory factory = CertificateFactory.getInstance("X509"); 70 | X509Certificate x509Cert = 71 | (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes)); 72 | verifiers.add(new RsaSHA256Verifier(x509Cert.getPublicKey())); 73 | } 74 | return verifiers; 75 | } else { 76 | return null; 77 | } 78 | } catch (MalformedURLException e) { 79 | return null; 80 | } catch (IOException e) { 81 | return null; 82 | } catch (CertificateException e) { 83 | return null; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/VerifierProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import java.util.List; 19 | import net.oauth.jsontoken.JsonTokenParser; 20 | import net.oauth.jsontoken.crypto.Verifier; 21 | 22 | /** 23 | * An interface that must be implemented by JSON Token verifiers. The {@link JsonTokenParser} uses 24 | * {@link VerifierProvider} implementations to find verification keys with which to verify the 25 | * parsed JSON Token. There are different implementations of this interface for different types of 26 | * verification keys. 27 | * 28 | *

For symmetric signing keys, an implementation of {@link VerifierProvider} presumably will 29 | * always look up the key in a local database. For public signing keys, the {@link VerifierProvider} 30 | * implementation may fetch the public verification keys when needed from the public internet. 31 | */ 32 | public interface VerifierProvider { 33 | 34 | /** 35 | * Returns the {@code List} that represents a certain verification key, given the key's 36 | * id and its issuer. 37 | * 38 | * @param issuer the id of the issuer that's using the key. 39 | * @param keyId the id of the key, if keyId mismatches, return a list of possible verification 40 | * keys. 41 | * @return a {@code List} object that represents the verification key. 42 | */ 43 | List findVerifier(String issuer, String keyId); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/discovery/VerifierProviders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.discovery; 17 | 18 | import com.google.common.collect.Maps; 19 | import java.util.Map; 20 | import javax.annotation.Nullable; 21 | import net.oauth.jsontoken.JsonTokenParser; 22 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 23 | 24 | /** 25 | * A collection of {@link VerifierProvider}s, one for each signature algorithm. The {@link 26 | * JsonTokenParser} uses a {@link VerifierProviders} instance to locate verification keys. In 27 | * particular, it will first look up the {@link VerifierProvider} for the signature algorithm used 28 | * in the JSON Token (different signature methods will use different ways to look up verification 29 | * keys - for example, symmetric keys will always be pre-negotiated and looked up in a local 30 | * database, while public verification keys can be looked up on demand), and the ask the {@link 31 | * VerifierProvider} to provide a {@code List} to check the validity of the JSON Token. 32 | */ 33 | public class VerifierProviders { 34 | 35 | private final Map map = Maps.newHashMap(); 36 | 37 | /** Sets a new {@link VerifierProvider} for the given {@link SignatureAlgorithm}. */ 38 | public void setVerifierProvider(SignatureAlgorithm alg, VerifierProvider provider) { 39 | map.put(alg, provider); 40 | } 41 | 42 | /** Returns the {@link VerifierProvider} for the given {@link SignatureAlgorithm}. */ 43 | @Nullable 44 | public VerifierProvider getVerifierProvider(SignatureAlgorithm alg) { 45 | return map.get(alg); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/exceptions/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken.exceptions; 2 | 3 | public enum ErrorCode { 4 | 5 | /** The header is missing required parameters. */ 6 | BAD_HEADER, 7 | 8 | /** Signature failed verification. */ 9 | BAD_SIGNATURE, 10 | 11 | /** IAT is after EXP or IAT is in the future */ 12 | BAD_TIME_RANGE, 13 | 14 | /** IAT and EXP are both in the past. */ 15 | EXPIRED_TOKEN, 16 | 17 | /** 18 | * The token is in an illegal state because of incorrect use of the library. If this error code 19 | * appears, immediately rethrow as an {@link IllegalStateException}. 20 | */ 21 | ILLEGAL_STATE, 22 | 23 | /** Token string is corrupted and/or does not contain three components. */ 24 | MALFORMED_TOKEN_STRING, 25 | 26 | /** There are no verifiers available for a given issuer and keyId. */ 27 | NO_VERIFIER, 28 | 29 | /** Generic catch-all for exceptions with scenarios that are not pre-defined. */ 30 | UNKNOWN, 31 | 32 | /** The signature algorithm is not supported or is unknown. */ 33 | UNSUPPORTED_ALGORITHM 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/jsontoken/exceptions/InvalidJsonTokenException.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken.exceptions; 2 | 3 | public final class InvalidJsonTokenException extends Exception { 4 | private final ErrorCode errorCode; 5 | 6 | public InvalidJsonTokenException(ErrorCode errorCode) { 7 | this.errorCode = errorCode; 8 | } 9 | 10 | public InvalidJsonTokenException(ErrorCode errorCode, String message) { 11 | super(message); 12 | this.errorCode = errorCode; 13 | } 14 | 15 | public InvalidJsonTokenException(ErrorCode errorCode, Throwable cause) { 16 | super(cause); 17 | this.errorCode = errorCode; 18 | } 19 | 20 | public ErrorCode getErrorCode() { 21 | return errorCode; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/NonceChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import java.security.SignatureException; 19 | import net.oauth.jsontoken.JsonToken; 20 | 21 | /** Receivers of Json Tokens may implement this interface. */ 22 | public interface NonceChecker { 23 | 24 | /** 25 | * Throws if the nonce in the {@link JsonToken} has previously been used by the same token issuer. 26 | */ 27 | void checkNonce(String nonce) throws SignatureException; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedJsonAssertionAudienceChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.common.base.Preconditions; 19 | import com.google.gson.JsonObject; 20 | import java.net.URI; 21 | import java.security.SignatureException; 22 | import net.oauth.jsontoken.Checker; 23 | import net.oauth.jsontoken.JsonToken; 24 | 25 | /** Audience checker for signed Json Assertion. */ 26 | public class SignedJsonAssertionAudienceChecker implements Checker { 27 | 28 | // URI that the client is accessing, as seen by the server 29 | private final String tokenEndpointUri; 30 | 31 | /** 32 | * Public constructor. 33 | * 34 | * @param uri the URI against which the signed OAuth token was exercised. 35 | */ 36 | public SignedJsonAssertionAudienceChecker(String uri) { 37 | this.tokenEndpointUri = uri; 38 | } 39 | 40 | /** @see net.oauth.jsontoken.Checker#check(com.google.gson.JsonObject) */ 41 | @Override 42 | public void check(JsonObject payload) throws SignatureException { 43 | checkUri( 44 | tokenEndpointUri, 45 | Preconditions.checkNotNull( 46 | payload.get(JsonToken.AUDIENCE).getAsString(), "Audience cannot be null!")); 47 | } 48 | 49 | private static void checkUri(String ourUriString, String tokenUriString) 50 | throws SignatureException { 51 | URI ourUri = URI.create(ourUriString); 52 | URI tokenUri = URI.create(tokenUriString); 53 | 54 | if (!ourUri.getScheme().equalsIgnoreCase(tokenUri.getScheme())) { 55 | throw new SignatureException("scheme in token URI (" + tokenUri.getScheme() + ") is wrong"); 56 | } 57 | 58 | if (!ourUri.getAuthority().equalsIgnoreCase(tokenUri.getAuthority())) { 59 | throw new SignatureException( 60 | "authority in token URI (" + tokenUri.getAuthority() + ") is wrong"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedJsonAssertionToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.gson.JsonPrimitive; 19 | import java.io.UnsupportedEncodingException; 20 | import java.net.URLEncoder; 21 | import java.security.SignatureException; 22 | import net.oauth.jsontoken.Clock; 23 | import net.oauth.jsontoken.JsonToken; 24 | import net.oauth.jsontoken.crypto.Signer; 25 | 26 | /** A signed Json Assertion */ 27 | public class SignedJsonAssertionToken extends JsonToken { 28 | 29 | public static final String JWT = "jwt"; 30 | 31 | public static final String GRANT_TYPE = "grant_type"; 32 | public static final String GRANT_TYPE_VALUE = "http://oauth.net/grant_type/jwt/1.0/bearer"; 33 | 34 | // addition JSON token payload fields for signed json assertion 35 | public static final String SUBJECT = "subject"; 36 | public static final String SCOPE = "scope"; 37 | public static final String NONCE = "nonce"; 38 | 39 | public SignedJsonAssertionToken(Signer signer, Clock clock) { 40 | super(signer, clock); 41 | } 42 | 43 | public SignedJsonAssertionToken(Signer signer) { 44 | super(signer); 45 | } 46 | 47 | public SignedJsonAssertionToken(JsonToken token) { 48 | super(token.getPayloadAsJsonObject()); 49 | } 50 | 51 | public String getSubject() { 52 | JsonPrimitive subjectJson = getParamAsPrimitive(SUBJECT); 53 | return subjectJson == null ? null : subjectJson.getAsString(); 54 | } 55 | 56 | public void setSubject(String m) { 57 | setParam(SUBJECT, m); 58 | } 59 | 60 | public String getScope() { 61 | JsonPrimitive scopeJson = getParamAsPrimitive(SCOPE); 62 | return scopeJson == null ? null : scopeJson.getAsString(); 63 | } 64 | 65 | public void setScope(String scope) { 66 | setParam(SCOPE, scope); 67 | } 68 | 69 | public String getNonce() { 70 | JsonPrimitive nonceJson = getParamAsPrimitive(NONCE); 71 | return nonceJson == null ? null : nonceJson.getAsString(); 72 | } 73 | 74 | public void setNonce(String n) { 75 | setParam(NONCE, n); 76 | } 77 | 78 | public String getJsonAssertionPostBody() throws SignatureException { 79 | StringBuffer buffer = new StringBuffer(); 80 | buffer.append(GRANT_TYPE).append("=").append(GRANT_TYPE_VALUE); 81 | buffer.append("&"); 82 | try { 83 | buffer.append(JWT).append("=").append(serializeAndSign()); 84 | return URLEncoder.encode(buffer.toString(), "UTF-8"); 85 | } catch (UnsupportedEncodingException e) { 86 | throw new SignatureException("unsupported encoding"); 87 | } 88 | } 89 | 90 | @Override 91 | public String serializeAndSign() throws SignatureException { 92 | return super.serializeAndSign(); 93 | } 94 | 95 | @Override 96 | protected String computeSignatureBaseString() { 97 | if (getIssuedAt() == null) { 98 | setIssuedAt(clock.now()); 99 | } 100 | if (getExpiration() == null) { 101 | setExpiration(getIssuedAt().plus(DEFAULT_LIFETIME)); 102 | } 103 | return super.computeSignatureBaseString(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedJsonAssertionTokenParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.gson.JsonParseException; 19 | import java.security.SignatureException; 20 | import javax.servlet.http.HttpServletRequest; 21 | import net.oauth.jsontoken.Clock; 22 | import net.oauth.jsontoken.JsonTokenParser; 23 | import net.oauth.jsontoken.SystemClock; 24 | import net.oauth.jsontoken.discovery.VerifierProviders; 25 | 26 | /** Parses signed json assertion. */ 27 | public class SignedJsonAssertionTokenParser { 28 | 29 | public static String EXPECTED_CONTENT_TYPE = "application/x-www-form-urlencoded"; 30 | 31 | private final VerifierProviders locators; 32 | private final NonceChecker nonceChecker; 33 | private final Clock clock; 34 | 35 | /** 36 | * Public constructor. 37 | * 38 | * @param locators an object that provides signature verifiers, based signature algorithm, as well 39 | * as on the signer and key ids. 40 | * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce 41 | * checker to make sure that the nonce has not been re-used. 42 | */ 43 | public SignedJsonAssertionTokenParser(VerifierProviders locators, NonceChecker nonceChecker) { 44 | this(locators, nonceChecker, new SystemClock()); 45 | } 46 | 47 | /** 48 | * Public constructor. 49 | * 50 | * @param locators an object that provides signature verifiers, based signature algorithm, as well 51 | * as on the signer and key ids. 52 | * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce 53 | * checker to make sure that the nonce has not been re-used.JsonTokenParser 54 | * @param clock a clock that has implemented the {@link 55 | * Clock#isCurrentTimeInInterval(java.time.Instant, java.time.Instant)} method with a suitable 56 | * slack to account for clock skew when checking token validity. 57 | */ 58 | public SignedJsonAssertionTokenParser( 59 | VerifierProviders locators, NonceChecker nonceChecker, Clock clock) { 60 | this.locators = locators; 61 | this.nonceChecker = nonceChecker; 62 | this.clock = clock; 63 | } 64 | 65 | /** 66 | * Extracts the Json assertion from the Http post body and then verifies it. 67 | * 68 | * @param request the {@link HttpServletRequest} that contains the signed Json assertion in the 69 | * post body. 70 | * @return the Json assertion object. 71 | * @throws SignatureException if the signature doesn't check out, or if authentication fails for 72 | * other reason 73 | * @throws JsonParseException if the header or payload of tokenString is corrupted 74 | * @throws IllegalArgumentException if the signature algorithm is not supported 75 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 76 | * valid verifier for the issuer 77 | */ 78 | public SignedJsonAssertionToken parseToken(HttpServletRequest request) throws SignatureException { 79 | if (!request.getContentType().startsWith(EXPECTED_CONTENT_TYPE)) { 80 | throw new SignatureException("bad content type: " + request.getContentType()); 81 | } 82 | 83 | String grantType = request.getParameter(SignedJsonAssertionToken.GRANT_TYPE); 84 | if (grantType == null 85 | || !grantType.equalsIgnoreCase(SignedJsonAssertionToken.GRANT_TYPE_VALUE)) { 86 | throw new SignatureException("bad grant_type: " + grantType); 87 | } 88 | 89 | String assertion = request.getParameter(SignedJsonAssertionToken.JWT); 90 | if (assertion == null) { 91 | throw new SignatureException("empty json assertion"); 92 | } 93 | 94 | StringBuffer uri = request.getRequestURL(); 95 | if (request.getQueryString() != null) { 96 | uri.append("?"); 97 | uri.append(request.getQueryString()); 98 | } 99 | 100 | return parseToken(assertion, uri.toString()); 101 | } 102 | 103 | /** 104 | * Parses the provided signed Json assertion, and then verifies it against the provided HTTP 105 | * method and audience URI (in addition to checking the signature, and validity period). 106 | * 107 | * @param jsonAssertion the signed Json assertion (in serialized form). 108 | * @param uri the URI against which the token was exercised. 109 | * @return the signed Json assertion token (deserialized) 110 | * @throws SignatureException if the signature (or anything else) doesn't check out 111 | * @throws JsonParseException if the header or payload of tokenString is corrupted 112 | * @throws IllegalArgumentException if the signature algorithm is not supported 113 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 114 | * valid verifier for the issuer 115 | */ 116 | public SignedJsonAssertionToken parseToken(String jsonAssertion, String uri) 117 | throws SignatureException { 118 | JsonTokenParser parser = 119 | new JsonTokenParser(clock, locators, new SignedJsonAssertionAudienceChecker(uri)); 120 | 121 | SignedJsonAssertionToken token = 122 | new SignedJsonAssertionToken(parser.verifyAndDeserialize(jsonAssertion)); 123 | 124 | if (nonceChecker != null) { 125 | nonceChecker.checkNonce(token.getNonce()); 126 | } 127 | 128 | return token; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedOAuthToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.common.base.Preconditions; 19 | import java.security.MessageDigest; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.security.SignatureException; 22 | import net.oauth.jsontoken.Clock; 23 | import net.oauth.jsontoken.JsonToken; 24 | import net.oauth.jsontoken.crypto.Signer; 25 | import org.apache.commons.codec.binary.Base64; 26 | 27 | /** A signed OAuth token. */ 28 | public class SignedOAuthToken extends JsonToken { 29 | 30 | public static final String AUTH_METHOD = "Token"; 31 | public static final String SIGNED_TOKEN_PARAM = "signed_token"; 32 | 33 | // addition JSON token payload fields for signed OAuth tokens 34 | public static final String METHOD = "method"; 35 | public static final String BODY_HASH = "body_hash"; 36 | public static final String OAUTH_TOKEN = "token"; 37 | public static final String NONCE = "nonce"; 38 | 39 | public SignedOAuthToken(Signer signer, Clock clock) { 40 | super(signer, clock); 41 | } 42 | 43 | public SignedOAuthToken(Signer signer) { 44 | super(signer); 45 | } 46 | 47 | public SignedOAuthToken(JsonToken token) { 48 | super(token.getPayloadAsJsonObject()); 49 | } 50 | 51 | public String getMethod() { 52 | return getParamAsPrimitive(METHOD).getAsString(); 53 | } 54 | 55 | public void setMethod(String m) { 56 | setParam(METHOD, m); 57 | } 58 | 59 | public String getBodyHash() { 60 | return getParamAsPrimitive(BODY_HASH).getAsString(); 61 | } 62 | 63 | /** 64 | * @throws IllegalArgumentException if the signature algorithm is not supported 65 | * @throws IllegalStateException if the header does not exist 66 | */ 67 | public void setRequestBody(byte[] body) { 68 | setParam(BODY_HASH, getBodyHash(body)); 69 | } 70 | 71 | public String getOAuthToken() { 72 | return getParamAsPrimitive(OAUTH_TOKEN).getAsString(); 73 | } 74 | 75 | public void setOAuthToken(String t) { 76 | setParam(OAUTH_TOKEN, t); 77 | } 78 | 79 | public String getNonce() { 80 | return getParamAsPrimitive(NONCE).getAsString(); 81 | } 82 | 83 | public void setNonce(String n) { 84 | setParam(NONCE, n); 85 | } 86 | 87 | public String getAuthorizationHeader() throws SignatureException { 88 | return AUTH_METHOD + " " + SIGNED_TOKEN_PARAM + "=" + serializeAndSign(); 89 | } 90 | 91 | @Override 92 | public String serializeAndSign() throws SignatureException { 93 | Preconditions.checkNotNull(getOAuthToken(), "must set OAuth token"); 94 | Preconditions.checkNotNull(getNonce(), "must set nonce"); 95 | Preconditions.checkNotNull(getAudience(), "must set Audience"); 96 | Preconditions.checkNotNull(getMethod(), "must set method"); 97 | return super.serializeAndSign(); 98 | } 99 | 100 | /** 101 | * @throws IllegalArgumentException if the signature algorithm is not supported 102 | * @throws IllegalStateException if the header does not exist 103 | */ 104 | private String getBodyHash(byte[] requestBody) { 105 | Preconditions.checkNotNull(requestBody); 106 | String hashAlg = getSignatureAlgorithm().getHashAlgorithm(); 107 | MessageDigest digest; 108 | try { 109 | digest = MessageDigest.getInstance(hashAlg); 110 | } catch (NoSuchAlgorithmException e) { 111 | throw new IllegalStateException("platform is missing hash algorithm: " + hashAlg); 112 | } 113 | byte[] hash = digest.digest(requestBody); 114 | return Base64.encodeBase64URLSafeString(hash); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedOAuthTokenParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.gson.JsonParseException; 19 | import java.security.SignatureException; 20 | import java.util.Enumeration; 21 | import javax.servlet.http.HttpServletRequest; 22 | import net.oauth.jsontoken.Clock; 23 | import net.oauth.jsontoken.JsonTokenParser; 24 | import net.oauth.jsontoken.SystemClock; 25 | import net.oauth.jsontoken.discovery.VerifierProviders; 26 | import org.apache.http.NameValuePair; 27 | import org.apache.http.message.BasicHeaderValueParser; 28 | 29 | /** Parses signed OAuth tokens. */ 30 | public class SignedOAuthTokenParser { 31 | 32 | private final VerifierProviders locators; 33 | private final NonceChecker nonceChecker; 34 | private final Clock clock; 35 | 36 | /** 37 | * Public constructor. 38 | * 39 | * @param locators an object that provides signature verifiers, based signature algorithm, as well 40 | * as on the signer and key ids. 41 | * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce 42 | * checker to make sure that the nonce has not been re-used. 43 | */ 44 | public SignedOAuthTokenParser(VerifierProviders locators, NonceChecker nonceChecker) { 45 | this(locators, nonceChecker, new SystemClock()); 46 | } 47 | 48 | /** 49 | * Public constructor. 50 | * 51 | * @param locators an object that provides signature verifiers, based signature algorithm, as well 52 | * as on the signer and key ids. 53 | * @param nonceChecker An optional nonce checker. If not null, then the parser will call the nonce 54 | * checker to make sure that the nonce has not been re-used. 55 | * @param clock a clock that has implemented the {@link 56 | * Clock#isCurrentTimeInInterval(org.joda.time.Instant, org.joda.time.Duration)} method with a 57 | * suitable slack to account for clock skew when checking token validity. 58 | */ 59 | public SignedOAuthTokenParser( 60 | VerifierProviders locators, NonceChecker nonceChecker, Clock clock) { 61 | this.locators = locators; 62 | this.nonceChecker = nonceChecker; 63 | this.clock = clock; 64 | } 65 | 66 | /** 67 | * Extracts the signed OAuth token from the Authorization header and then verifies it. 68 | * 69 | * @param request the {@link HttpServletRequest} that contains the signed OAuth token in the 70 | * Authorization header. 71 | * @return the signed OAuth token. 72 | * @throws SignatureException if the signature doesn't check out, or if authentication fails for 73 | * other reason (missing Authorization header, etc.). 74 | * @throws JsonParseException if the header or payload of tokenString is corrupted 75 | * @throws IllegalArgumentException if the signature algorithm is not supported 76 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 77 | * valid verifier for the issuer 78 | */ 79 | public SignedOAuthToken parseToken(HttpServletRequest request) throws SignatureException { 80 | 81 | // this guaranteed to return a string starting with "Token", or null 82 | String header = getAuthHeader(request); 83 | 84 | if (header == null) { 85 | throw new SignatureException("missing Authorization header of type 'Token'"); 86 | } 87 | 88 | String postFix = 89 | header.substring(0, SignedOAuthToken.AUTH_METHOD.length()); // read past "Token" 90 | NameValuePair nvp = BasicHeaderValueParser.parseNameValuePair(postFix.trim(), null); 91 | 92 | if (nvp == null) { 93 | throw new SignatureException("missing signed_token in Authorization header: " + header); 94 | } 95 | 96 | if (!SignedOAuthToken.SIGNED_TOKEN_PARAM.equals(nvp.getName())) { 97 | // Not logging the header in this case. maybe they just mis-spelled "token", but did send the 98 | // actual OAuth token. We don't want to log that. 99 | throw new SignatureException("missing signed_token in Authorization header"); 100 | } 101 | 102 | String token = nvp.getValue().trim(); 103 | 104 | String method = request.getMethod(); 105 | 106 | StringBuffer uri = request.getRequestURL(); 107 | 108 | if (request.getQueryString() != null) { 109 | uri.append("?"); 110 | uri.append(request.getQueryString()); 111 | } 112 | 113 | return parseToken(token, method, uri.toString()); 114 | } 115 | 116 | /** 117 | * Parses the provided signed OAuth token, and then verifies it against the provided HTTP method 118 | * and audience URI (in addition to checking the signature, and validity period). 119 | * 120 | * @param tokenString the signed OAuth token (in serialized form). 121 | * @param method the HTTP method that was used when the token was exercised. 122 | * @param uri the URI against which the token was exercised. 123 | * @return the signed OAuth token (deserialized) 124 | * @throws SignatureException if the signature (or anything else) doesn't check out. 125 | * @throws JsonParseException if the header or payload of tokenString is corrupted 126 | * @throws IllegalArgumentException if the signature algorithm is not supported 127 | * @throws IllegalStateException if tokenString is not a properly formatted JWT or if there is no 128 | * valid verifier for the issuer 129 | */ 130 | public SignedOAuthToken parseToken(String tokenString, String method, String uri) 131 | throws SignatureException { 132 | JsonTokenParser parser = 133 | new JsonTokenParser(clock, locators, new SignedTokenAudienceChecker(uri)); 134 | 135 | SignedOAuthToken token = new SignedOAuthToken(parser.verifyAndDeserialize(tokenString)); 136 | 137 | if (!method.equalsIgnoreCase(token.getMethod())) { 138 | throw new SignatureException("method does not equal in token (" + token.getMethod() + ")"); 139 | } 140 | 141 | if (nonceChecker != null) { 142 | nonceChecker.checkNonce(token.getNonce()); 143 | } 144 | 145 | return token; 146 | } 147 | 148 | private String getAuthHeader(HttpServletRequest request) { 149 | @SuppressWarnings("unchecked") 150 | Enumeration authHeaders = request.getHeaders("Authorization"); 151 | 152 | if (authHeaders == null) { 153 | return null; 154 | } 155 | 156 | while (authHeaders.hasMoreElements()) { 157 | String header = (String) authHeaders.nextElement(); 158 | if (header.trim().startsWith(SignedOAuthToken.AUTH_METHOD)) { 159 | return header.trim(); 160 | } 161 | } 162 | 163 | return null; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/net/oauth/signatures/SignedTokenAudienceChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import com.google.common.base.Objects; 19 | import com.google.common.base.Preconditions; 20 | import com.google.gson.JsonObject; 21 | import java.net.URI; 22 | import java.security.SignatureException; 23 | import net.oauth.jsontoken.Checker; 24 | import net.oauth.jsontoken.JsonToken; 25 | 26 | /** 27 | * Audience checker for signed OAuth tokens. For such tokens, the audience in the token is the URL 28 | * of the accessed resource, and has to match it exactly (save some case-insensitivities in the host 29 | * name). 30 | */ 31 | public class SignedTokenAudienceChecker implements Checker { 32 | 33 | // URI that the client is accessing, as seen by the server 34 | private final String serverUri; 35 | 36 | /** 37 | * Public constructor. 38 | * 39 | * @param uri the URI against which the signed OAuth token was exercised. 40 | */ 41 | public SignedTokenAudienceChecker(String uri) { 42 | this.serverUri = uri; 43 | } 44 | 45 | /** @see net.oauth.jsontoken.Checker#check(com.google.gson.JsonObject) */ 46 | @Override 47 | public void check(JsonObject payload) throws SignatureException { 48 | checkUri( 49 | serverUri, 50 | Preconditions.checkNotNull( 51 | payload.get(JsonToken.AUDIENCE).getAsString(), "Audience cannot be null!")); 52 | } 53 | 54 | private static void checkUri(String ourUriString, String tokenUriString) 55 | throws SignatureException { 56 | URI ourUri = URI.create(ourUriString); 57 | URI tokenUri = URI.create(tokenUriString); 58 | 59 | if (!ourUri.getScheme().equalsIgnoreCase(tokenUri.getScheme())) { 60 | throw new SignatureException("scheme in token URI (" + tokenUri.getScheme() + ") is wrong"); 61 | } 62 | 63 | if (!ourUri.getAuthority().equalsIgnoreCase(tokenUri.getAuthority())) { 64 | throw new SignatureException( 65 | "authority in token URI (" + tokenUri.getAuthority() + ") is wrong"); 66 | } 67 | 68 | if (!Objects.equal(ourUri.getPath(), tokenUri.getPath())) { 69 | throw new SignatureException("path in token URI (" + tokenUri.getAuthority() + ") is wrong"); 70 | } 71 | 72 | if (!Objects.equal(ourUri.getQuery(), tokenUri.getQuery())) { 73 | throw new SignatureException("query string in URI (" + tokenUri.getQuery() + ") is wrong"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/AbstractJsonTokenParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.gson.JsonParseException; 21 | import java.security.SignatureException; 22 | import java.time.Duration; 23 | import java.time.Instant; 24 | import java.util.ArrayList; 25 | import net.oauth.jsontoken.crypto.HmacSHA256Signer; 26 | import net.oauth.jsontoken.exceptions.ErrorCode; 27 | 28 | public class AbstractJsonTokenParserTest extends JsonTokenTestBase { 29 | 30 | private static final String TOKEN_STRING_ISSUER_NULL = 31 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOm51bGwsImJhciI6MTUsImZvbyI6InNvbWUgdmFsdWUiLCJhdWQiOiJodHRwOi8vd3d3Lmdvb2dsZS5jb20iLCJpYXQiOjEyNzY2Njk3MjIsImV4cCI6MTI3NjY2OTcyM30.WPaa6PoLWPzNfnIBisBX9549kWeABSj9tXnwnPE4IJk"; 32 | private static final String TOKEN_STRING_1PART = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ"; 33 | private static final String TOKEN_STRING_2PARTS = 34 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ"; 35 | private static final String TOKEN_STRING_EMPTY_SIG = 36 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ."; 37 | private static final String TOKEN_STRING_CORRUPT_PAYLOAD = 38 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3&&&&&nb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.Xugb4nb5kLV3NTpOLaz9er5PhAI5mFehHst_33EUFHs"; 39 | 40 | public void testVerify_valid() throws Exception { 41 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 42 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 43 | parser.verifyInternal(checkToken, getVerifiers()); 44 | } 45 | 46 | public void testVerify_issuedAtAfterExpiration() throws Exception { 47 | Instant issuedAt = clock.now(); 48 | Instant expiration = issuedAt.minus(Duration.ofSeconds(1)); 49 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 50 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, expiration); 51 | 52 | assertThrowsWithErrorCode( 53 | IllegalStateException.class, 54 | ErrorCode.BAD_TIME_RANGE, 55 | () -> parser.verifyInternal(checkToken, getVerifiers())); 56 | } 57 | 58 | public void testVerify_issuedAtSkew() throws Exception { 59 | Instant issuedAt = clock.now().plus(SKEW.minus(Duration.ofSeconds(1))); 60 | Instant expiration = issuedAt.plus(Duration.ofSeconds(1)); 61 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 62 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, expiration); 63 | 64 | parser.verifyInternal(checkToken, getVerifiers()); 65 | } 66 | 67 | public void testVerify_issuedAtTooMuchSkew() throws Exception { 68 | Instant issuedAt = clock.now().plus(SKEW.plus(Duration.ofSeconds(1))); 69 | Instant expiration = issuedAt.plus(Duration.ofSeconds(1)); 70 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 71 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, expiration); 72 | 73 | assertThrowsWithErrorCode( 74 | IllegalStateException.class, 75 | ErrorCode.BAD_TIME_RANGE, 76 | () -> parser.verifyInternal(checkToken, getVerifiers())); 77 | } 78 | 79 | public void testVerify_issuedAtNull() throws Exception { 80 | Instant expiration = clock.now().minus(SKEW.minus(Duration.ofSeconds(1))); 81 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 82 | JsonToken checkToken = getJsonTokenWithTimeRange(null, expiration); 83 | 84 | parser.verifyInternal(checkToken, getVerifiers()); 85 | } 86 | 87 | public void testVerify_expirationSkew() throws Exception { 88 | Instant expiration = clock.now().minus(SKEW.minus(Duration.ofSeconds(1))); 89 | Instant issuedAt = expiration.minus(Duration.ofSeconds(1)); 90 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 91 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, expiration); 92 | 93 | parser.verifyInternal(checkToken, getVerifiers()); 94 | } 95 | 96 | public void testVerify_expirationTooMuchSkew() throws Exception { 97 | Instant expiration = clock.now().minus(SKEW.plus(Duration.ofSeconds(1))); 98 | Instant issuedAt = expiration.minus(Duration.ofSeconds(1)); 99 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 100 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, expiration); 101 | 102 | assertThrowsWithErrorCode( 103 | IllegalStateException.class, 104 | ErrorCode.EXPIRED_TOKEN, 105 | () -> parser.verifyInternal(checkToken, getVerifiers())); 106 | } 107 | 108 | public void testVerify_expirationNull() throws Exception { 109 | Instant issuedAt = clock.now().plus(SKEW.minus(Duration.ofSeconds(1))); 110 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 111 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, null); 112 | 113 | parser.verifyInternal(checkToken, getVerifiers()); 114 | } 115 | 116 | public void testVerify_issuedAtNullExpirationNull() throws Exception { 117 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 118 | JsonToken checkToken = getJsonTokenWithTimeRange(null, null); 119 | 120 | parser.verifyInternal(checkToken, getVerifiers()); 121 | } 122 | 123 | public void testVerify_badSignature() throws Exception { 124 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 125 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_BAD_SIG); 126 | assertThrowsWithErrorCode( 127 | SignatureException.class, 128 | ErrorCode.BAD_SIGNATURE, 129 | () -> parser.verifyInternal(checkToken, getVerifiers())); 130 | } 131 | 132 | public void testVerify_emptySignature() throws Exception { 133 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 134 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_EMPTY_SIG); 135 | assertThrowsWithErrorCode( 136 | SignatureException.class, 137 | ErrorCode.BAD_SIGNATURE, 138 | () -> parser.verifyInternal(checkToken, getVerifiers())); 139 | } 140 | 141 | public void testVerify_nullSignature() throws Exception { 142 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 143 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_2PARTS); 144 | assertThrowsWithErrorCode( 145 | IllegalStateException.class, 146 | ErrorCode.MALFORMED_TOKEN_STRING, 147 | () -> parser.verifyInternal(checkToken, getVerifiers())); 148 | } 149 | 150 | public void testVerify_unsupportedSignatureAlgorithm() throws Exception { 151 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 152 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_UNSUPPORTED_SIGNATURE_ALGORITHM); 153 | // This function does not explicitly check or access the signature algorithm 154 | assertThrowsWithErrorCode( 155 | SignatureException.class, 156 | ErrorCode.BAD_SIGNATURE, 157 | () -> parser.verifyInternal(checkToken, getVerifiers())); 158 | } 159 | 160 | public void testVerify_failChecker() throws Exception { 161 | AbstractJsonTokenParser parser = 162 | getAbstractJsonTokenParser(new AlwaysPassChecker(), new AlwaysFailChecker()); 163 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 164 | assertThrows(SignatureException.class, () -> parser.verifyInternal(checkToken, getVerifiers())); 165 | } 166 | 167 | public void testVerify_emptyVerifiers() throws Exception { 168 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 169 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 170 | assertThrowsWithErrorCode( 171 | SignatureException.class, 172 | ErrorCode.BAD_SIGNATURE, 173 | () -> parser.verifyInternal(checkToken, new ArrayList<>())); 174 | } 175 | 176 | public void testDeserialize_valid() throws Exception { 177 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 178 | JsonToken token = parser.deserializeInternal(TOKEN_STRING); 179 | 180 | assertHeader(token); 181 | assertPayload(token); 182 | } 183 | 184 | public void testDeserialize_nullIssuer() throws Exception { 185 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(null); 186 | JsonToken token = parser.deserializeInternal(TOKEN_STRING_ISSUER_NULL); 187 | assertNull(token.getIssuer()); 188 | } 189 | 190 | public void testDeserialize_badSignature() throws Exception { 191 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 192 | parser.deserializeInternal(TOKEN_STRING_BAD_SIG); 193 | } 194 | 195 | public void testDeserialize_noSignature() throws Exception { 196 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 197 | assertThrowsWithErrorCode( 198 | IllegalStateException.class, 199 | ErrorCode.MALFORMED_TOKEN_STRING, 200 | () -> parser.deserializeInternal(TOKEN_STRING_2PARTS)); 201 | } 202 | 203 | public void testDeserialize_emptySignature() throws Exception { 204 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 205 | parser.deserializeInternal(TOKEN_STRING_EMPTY_SIG); 206 | } 207 | 208 | public void testDeserialize_unsupportedSignatureAlgorithm() throws Exception { 209 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 210 | parser.deserializeInternal(TOKEN_STRING_UNSUPPORTED_SIGNATURE_ALGORITHM); 211 | } 212 | 213 | public void testDeserialize_headerOnly() throws Exception { 214 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 215 | assertThrowsWithErrorCode( 216 | IllegalStateException.class, 217 | ErrorCode.MALFORMED_TOKEN_STRING, 218 | () -> parser.deserializeInternal(TOKEN_STRING_1PART)); 219 | } 220 | 221 | public void testDeserialize_corruptHeader() throws Exception { 222 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 223 | assertThrows( 224 | JsonParseException.class, () -> parser.deserializeInternal(TOKEN_STRING_CORRUPT_HEADER)); 225 | } 226 | 227 | public void testDeserialize_corruptPayload() throws Exception { 228 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 229 | assertThrows( 230 | JsonParseException.class, () -> parser.deserializeInternal(TOKEN_STRING_CORRUPT_PAYLOAD)); 231 | } 232 | 233 | public void testSignatureIsValid_valid() throws Exception { 234 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 235 | assertTrue(parser.signatureIsValidInternal(TOKEN_STRING, getVerifiers())); 236 | } 237 | 238 | public void testSignatureIsValid_badSignature() throws Exception { 239 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 240 | assertFalse(parser.signatureIsValidInternal(TOKEN_STRING_BAD_SIG, getVerifiers())); 241 | } 242 | 243 | public void testSignatureIsValid_emptySignature() throws Exception { 244 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 245 | assertFalse(parser.signatureIsValidInternal(TOKEN_STRING_EMPTY_SIG, getVerifiers())); 246 | } 247 | 248 | public void testSignatureIsValid_nullSignature() throws Exception { 249 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 250 | assertThrowsWithErrorCode( 251 | IllegalStateException.class, 252 | ErrorCode.MALFORMED_TOKEN_STRING, 253 | () -> parser.signatureIsValidInternal(TOKEN_STRING_2PARTS, getVerifiers())); 254 | } 255 | 256 | public void testExpirationIsValid_futureExpiration() throws Exception { 257 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 258 | Instant expiration = clock.now().plus(Duration.ofSeconds(1)); 259 | JsonToken checkToken = getJsonTokenWithTimeRange(null, expiration); 260 | 261 | assertTrue(parser.expirationIsValid(checkToken, clock.now())); 262 | } 263 | 264 | public void testExpirationIsValid_pastExpiration() throws Exception { 265 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 266 | Instant expiration = clock.now().minus(Duration.ofSeconds(1)); 267 | JsonToken checkToken = getJsonTokenWithTimeRange(null, expiration); 268 | 269 | assertFalse(parser.expirationIsValid(checkToken, clock.now())); 270 | } 271 | 272 | public void testExpirationIsValid_nullExpiration() throws Exception { 273 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 274 | JsonToken checkToken = getJsonTokenWithTimeRange(null, null); 275 | 276 | assertTrue(parser.expirationIsValid(checkToken, clock.now())); 277 | } 278 | 279 | public void testIssuedAtIsValid_pastIssuedAt() throws Exception { 280 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 281 | Instant issuedAt = clock.now().minus(Duration.ofSeconds(1)); 282 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, null); 283 | 284 | assertTrue(parser.issuedAtIsValid(checkToken, clock.now())); 285 | } 286 | 287 | public void testIssuedAtIsValid_futureIssuedAt() throws Exception { 288 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 289 | Instant issuedAt = clock.now().plus(Duration.ofSeconds(1)); 290 | JsonToken checkToken = getJsonTokenWithTimeRange(issuedAt, null); 291 | 292 | assertFalse(parser.issuedAtIsValid(checkToken, clock.now())); 293 | } 294 | 295 | public void testIssuedAtIsValid_nullIssuedAt() throws Exception { 296 | AbstractJsonTokenParser parser = getAbstractJsonTokenParser(); 297 | JsonToken checkToken = getJsonTokenWithTimeRange(null, null); 298 | 299 | assertTrue(parser.issuedAtIsValid(checkToken, clock.now())); 300 | } 301 | 302 | private JsonToken getJsonTokenWithTimeRange(Instant issuedAt, Instant expiration) 303 | throws Exception { 304 | HmacSHA256Signer signer = new HmacSHA256Signer("google.com", "key2", SYMMETRIC_KEY); 305 | JsonToken token = new JsonToken(signer, clock); 306 | if (issuedAt != null) { 307 | token.setIssuedAt(issuedAt); 308 | } 309 | 310 | if (expiration != null) { 311 | token.setExpiration(expiration); 312 | } 313 | 314 | return new JsonToken( 315 | token.getHeader(), token.getPayloadAsJsonObject(), clock, token.serializeAndSign()); 316 | } 317 | 318 | private AbstractJsonTokenParser getAbstractJsonTokenParser() { 319 | return new AbstractJsonTokenParser(clock, new AlwaysPassChecker()) {}; 320 | } 321 | 322 | private AbstractJsonTokenParser getAbstractJsonTokenParser(Checker... checkers) { 323 | return new AbstractJsonTokenParser(clock, checkers) {}; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/AlwaysFailChecker.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken; 2 | 3 | import com.google.gson.JsonObject; 4 | import java.security.SignatureException; 5 | 6 | /** Fails on any audience (even null). */ 7 | public final class AlwaysFailChecker implements Checker { 8 | 9 | @Override 10 | public void check(JsonObject payload) throws SignatureException { 11 | throw new SignatureException(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/AlwaysPassChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import com.google.gson.JsonObject; 19 | import java.security.SignatureException; 20 | 21 | /** Allows any audience (even null). */ 22 | public final class AlwaysPassChecker implements Checker { 23 | 24 | @Override 25 | public void check(JsonObject payload) throws SignatureException { 26 | // don't throw - allow anything 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/AsyncJsonTokenParserTest.java: -------------------------------------------------------------------------------- 1 | package net.oauth.jsontoken; 2 | 3 | import static org.junit.Assert.assertThrows; 4 | 5 | import com.google.common.util.concurrent.Futures; 6 | import com.google.common.util.concurrent.ListenableFuture; 7 | import com.google.common.util.concurrent.MoreExecutors; 8 | import com.google.gson.JsonObject; 9 | import com.google.gson.JsonParseException; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Executor; 12 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 13 | import net.oauth.jsontoken.discovery.AsyncVerifierProvider; 14 | import net.oauth.jsontoken.discovery.AsyncVerifierProviders; 15 | import net.oauth.jsontoken.exceptions.ErrorCode; 16 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 17 | import org.junit.function.ThrowingRunnable; 18 | 19 | public class AsyncJsonTokenParserTest extends JsonTokenTestBase { 20 | 21 | private AsyncVerifierProviders asyncLocators; 22 | private AsyncVerifierProviders asyncLocatorsFromRuby; 23 | private Executor executor; 24 | 25 | @Override 26 | protected void setUp() throws Exception { 27 | super.setUp(); 28 | AsyncVerifierProvider hmacLocator = 29 | (issuer, keyId) -> 30 | Futures.immediateFuture( 31 | locators.getVerifierProvider(SignatureAlgorithm.HS256).findVerifier(issuer, keyId)); 32 | AsyncVerifierProvider rsaLocator = 33 | (issuer, keyId) -> 34 | Futures.immediateFuture( 35 | locators.getVerifierProvider(SignatureAlgorithm.RS256).findVerifier(issuer, keyId)); 36 | 37 | asyncLocators = 38 | alg -> { 39 | if (alg.equals(SignatureAlgorithm.HS256)) { 40 | return hmacLocator; 41 | } else if (alg.equals(SignatureAlgorithm.RS256)) { 42 | return rsaLocator; 43 | } 44 | return null; 45 | }; 46 | 47 | AsyncVerifierProvider hmacLocatorFromRuby = 48 | (issuer, keyId) -> 49 | Futures.immediateFuture( 50 | locatorsFromRuby 51 | .getVerifierProvider(SignatureAlgorithm.HS256) 52 | .findVerifier(issuer, keyId)); 53 | 54 | asyncLocatorsFromRuby = 55 | alg -> { 56 | if (alg.equals(SignatureAlgorithm.HS256)) { 57 | return hmacLocatorFromRuby; 58 | } 59 | return null; 60 | }; 61 | 62 | executor = MoreExecutors.directExecutor(); 63 | } 64 | 65 | public void testVerify_valid() throws Exception { 66 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 67 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 68 | parser.verify(checkToken).get(); 69 | } 70 | 71 | public void testVerify_badSignature() throws Exception { 72 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 73 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_BAD_SIG); 74 | assertFailsWithErrorCode(ErrorCode.BAD_SIGNATURE, parser.verify(checkToken)); 75 | } 76 | 77 | public void testVerify_headerMissingAlg() throws Exception { 78 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 79 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_HEADER_MISSING_ALG); 80 | assertFailsWithErrorCode(ErrorCode.BAD_HEADER, parser.verify(checkToken)); 81 | } 82 | 83 | public void testVerify_unsupportedSignature() throws Exception { 84 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 85 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_UNSUPPORTED_SIGNATURE_ALGORITHM); 86 | assertFailsWithErrorCode(ErrorCode.UNSUPPORTED_ALGORITHM, parser.verify(checkToken)); 87 | } 88 | 89 | public void testVerify_noVerifiers() throws Exception { 90 | AsyncVerifierProvider noLocator = (signerId, keyId) -> Futures.immediateFuture(null); 91 | AsyncVerifierProviders noLocators = 92 | alg -> { 93 | if (alg.equals(SignatureAlgorithm.HS256)) { 94 | return noLocator; 95 | } 96 | return null; 97 | }; 98 | 99 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(noLocators, new AlwaysPassChecker()); 100 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 101 | assertFailsWithErrorCode(ErrorCode.NO_VERIFIER, parser.verify(checkToken)); 102 | } 103 | 104 | public void testVerify_noProviders() throws Exception { 105 | AsyncVerifierProviders noProviders = alg -> null; 106 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(noProviders, new AlwaysPassChecker()); 107 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 108 | assertFailsWithErrorCode(ErrorCode.UNSUPPORTED_ALGORITHM, parser.verify(checkToken)); 109 | } 110 | 111 | public void testVerifyAndDeserialize_valid() throws Exception { 112 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 113 | JsonToken token = parser.verifyAndDeserialize(TOKEN_STRING).get(); 114 | assertHeader(token); 115 | assertPayload(token); 116 | } 117 | 118 | public void testVerifyAndDeserialize_deserializeFail() throws Exception { 119 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 120 | assertFailsWithErrorCode( 121 | ErrorCode.MALFORMED_TOKEN_STRING, parser.verifyAndDeserialize(TOKEN_STRING_2PARTS)); 122 | } 123 | 124 | public void testVerifyAndDeserialize_verifyFail() throws Exception { 125 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 126 | assertFailsWithErrorCode( 127 | ErrorCode.BAD_SIGNATURE, parser.verifyAndDeserialize(TOKEN_STRING_BAD_SIG)); 128 | } 129 | 130 | public void testVerifyAndDeserialize_tokenFromRuby() throws Exception { 131 | AsyncJsonTokenParser parser = 132 | getAsyncJsonTokenParser(asyncLocatorsFromRuby, new AlwaysPassChecker()); 133 | JsonToken token = parser.verifyAndDeserialize(TOKEN_FROM_RUBY).get(); 134 | 135 | assertEquals(SignatureAlgorithm.HS256, token.getSignatureAlgorithm()); 136 | assertEquals("JWT", token.getHeader().get(JsonToken.TYPE_HEADER).getAsString()); 137 | assertEquals("world", token.getParamAsPrimitive("hello").getAsString()); 138 | } 139 | 140 | public void testDeserialize_corruptHeader() throws Exception { 141 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 142 | InvalidJsonTokenException e = 143 | assertErrorCode( 144 | ErrorCode.MALFORMED_TOKEN_STRING, 145 | () -> parser.deserialize(TOKEN_STRING_CORRUPT_HEADER)); 146 | 147 | assertTrue(e.getCause() instanceof JsonParseException); 148 | } 149 | 150 | public void testVerifyWithVerifiers_unknownCause() throws Exception { 151 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(asyncLocators, new AlwaysFailChecker()); 152 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 153 | assertErrorCode(ErrorCode.UNKNOWN, () -> parser.verify(checkToken, getVerifiers())); 154 | } 155 | 156 | public void testVerifyWithVerifiers_nullSignature() throws Exception { 157 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 158 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_2PARTS); 159 | assertErrorCode( 160 | ErrorCode.MALFORMED_TOKEN_STRING, () -> parser.verify(checkToken, getVerifiers())); 161 | } 162 | 163 | public void testSignatureIsValid_nullSignature() throws Exception { 164 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 165 | assertErrorCode( 166 | ErrorCode.MALFORMED_TOKEN_STRING, 167 | () -> parser.signatureIsValid(TOKEN_STRING_2PARTS, getVerifiers())); 168 | } 169 | 170 | /** Ensure that legitimate runtime exceptions get through rethrowing exceptions. */ 171 | public void testRethrownException_runtimeException() throws Exception { 172 | AsyncJsonTokenParser parser = getAsyncJsonTokenParser(); 173 | JsonToken checkToken = new JsonToken(new JsonObject()); 174 | ExecutionException e = 175 | assertThrows(ExecutionException.class, () -> parser.verify(checkToken).get()); 176 | 177 | assertTrue(e.getCause() instanceof IllegalStateException); 178 | assertTrue(e.getCause().getMessage().equals("JWT has no algorithm or header")); 179 | } 180 | 181 | private AsyncJsonTokenParser getAsyncJsonTokenParser() { 182 | return new AsyncJsonTokenParser(clock, asyncLocators, executor, new AlwaysPassChecker()); 183 | } 184 | 185 | private AsyncJsonTokenParser getAsyncJsonTokenParser( 186 | AsyncVerifierProviders providers, Checker... checkers) { 187 | return new AsyncJsonTokenParser(clock, providers, executor, checkers); 188 | } 189 | 190 | private InvalidJsonTokenException assertErrorCode( 191 | ErrorCode errorCode, ThrowingRunnable runnable) { 192 | InvalidJsonTokenException originalException = 193 | assertThrows(InvalidJsonTokenException.class, runnable); 194 | assertTrue(originalException.getErrorCode().equals(errorCode)); 195 | return originalException; 196 | } 197 | 198 | private void assertFailsWithErrorCode(ErrorCode errorCode, ListenableFuture future) { 199 | ExecutionException executionException = 200 | assertThrows(ExecutionException.class, () -> future.get()); 201 | Throwable originalException = executionException.getCause(); 202 | assertTrue(originalException instanceof InvalidJsonTokenException); 203 | 204 | InvalidJsonTokenException invalidJsonTokenException = 205 | (InvalidJsonTokenException) originalException; 206 | assertTrue(invalidJsonTokenException.getErrorCode().equals(errorCode)); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/FakeClock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import java.time.Duration; 19 | import java.time.Instant; 20 | 21 | public class FakeClock extends SystemClock { 22 | 23 | private Instant now = Instant.now(); 24 | 25 | public FakeClock() { 26 | super(Duration.ZERO); 27 | } 28 | 29 | public FakeClock(Duration acceptableClockSkew) { 30 | super(acceptableClockSkew); 31 | } 32 | 33 | public void setNow(Instant i) { 34 | now = i; 35 | } 36 | 37 | @Override 38 | public Instant now() { 39 | return now; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/JsonTokenParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.gson.Gson; 21 | import com.google.gson.JsonObject; 22 | import com.google.gson.JsonParser; 23 | import java.security.SignatureException; 24 | import java.time.Duration; 25 | import java.util.regex.Pattern; 26 | import net.oauth.jsontoken.crypto.RsaSHA256Signer; 27 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 28 | import net.oauth.jsontoken.discovery.VerifierProvider; 29 | import net.oauth.jsontoken.discovery.VerifierProviders; 30 | import net.oauth.jsontoken.exceptions.ErrorCode; 31 | import org.apache.commons.codec.binary.Base64; 32 | import org.apache.commons.codec.binary.StringUtils; 33 | 34 | public class JsonTokenParserTest extends JsonTokenTestBase { 35 | 36 | public void testVerify_valid() throws Exception { 37 | JsonTokenParser parser = getJsonTokenParser(); 38 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 39 | parser.verify(checkToken); 40 | } 41 | 42 | public void testVerify_unsupportedSignature() throws Exception { 43 | JsonTokenParser parser = getJsonTokenParser(); 44 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING_UNSUPPORTED_SIGNATURE_ALGORITHM); 45 | assertThrowsWithErrorCode( 46 | IllegalArgumentException.class, 47 | ErrorCode.UNSUPPORTED_ALGORITHM, 48 | () -> parser.verify(checkToken)); 49 | } 50 | 51 | public void testVerify_noVerifiers() throws Exception { 52 | VerifierProvider noLocator = (signerId, keyId) -> null; 53 | VerifierProviders noLocators = new VerifierProviders(); 54 | noLocators.setVerifierProvider(SignatureAlgorithm.HS256, noLocator); 55 | 56 | JsonTokenParser parser = getJsonTokenParser(noLocators, new AlwaysPassChecker()); 57 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 58 | 59 | assertThrowsWithErrorCode( 60 | IllegalStateException.class, ErrorCode.NO_VERIFIER, () -> parser.verify(checkToken)); 61 | } 62 | 63 | public void testVerify_noProviders() throws Exception { 64 | VerifierProviders noProviders = new VerifierProviders(); 65 | JsonTokenParser parser = getJsonTokenParser(noProviders, new AlwaysPassChecker()); 66 | JsonToken checkToken = naiveDeserialize(TOKEN_STRING); 67 | 68 | assertThrows(IllegalArgumentException.class, () -> parser.verify(checkToken)); 69 | } 70 | 71 | public void testVerifyAndDeserialize_valid() throws Exception { 72 | JsonTokenParser parser = getJsonTokenParser(); 73 | JsonToken token = parser.verifyAndDeserialize(TOKEN_STRING); 74 | assertHeader(token); 75 | assertPayload(token); 76 | } 77 | 78 | public void testVerifyAndDeserialize_deserializeFail() throws Exception { 79 | JsonTokenParser parser = getJsonTokenParser(); 80 | assertThrowsWithErrorCode( 81 | IllegalStateException.class, 82 | ErrorCode.MALFORMED_TOKEN_STRING, 83 | () -> parser.verifyAndDeserialize(TOKEN_STRING_2PARTS)); 84 | } 85 | 86 | public void testVerifyAndDeserialize_verifyFail() throws Exception { 87 | JsonTokenParser parser = getJsonTokenParser(); 88 | assertThrows(SignatureException.class, () -> parser.verifyAndDeserialize(TOKEN_STRING_BAD_SIG)); 89 | } 90 | 91 | public void testVerifyAndDeserialize_tokenFromRuby() throws Exception { 92 | JsonTokenParser parser = getJsonTokenParser(locatorsFromRuby, new AlwaysPassChecker()); 93 | JsonToken token = parser.verifyAndDeserialize(TOKEN_FROM_RUBY); 94 | 95 | assertEquals(SignatureAlgorithm.HS256, token.getSignatureAlgorithm()); 96 | assertEquals("JWT", token.getHeader().get(JsonToken.TYPE_HEADER).getAsString()); 97 | assertEquals("world", token.getParamAsPrimitive("hello").getAsString()); 98 | } 99 | 100 | public void testPublicKey() throws Exception { 101 | RsaSHA256Signer signer = new RsaSHA256Signer("google.com", "key1", privateKey); 102 | 103 | JsonToken token = new JsonToken(signer, clock); 104 | token.setParam("bar", 15); 105 | token.setParam("foo", "some value"); 106 | token.setExpiration(clock.now().plus(Duration.ofMillis(60))); 107 | 108 | String tokenString = token.serializeAndSign(); 109 | 110 | assertNotNull(token.toString()); 111 | 112 | JsonTokenParser parser = getJsonTokenParser(); 113 | token = parser.verifyAndDeserialize(tokenString); 114 | assertEquals("google.com", token.getIssuer()); 115 | assertEquals(15, token.getParamAsPrimitive("bar").getAsLong()); 116 | assertEquals("some value", token.getParamAsPrimitive("foo").getAsString()); 117 | 118 | // now test what happens if we tamper with the token 119 | JsonObject payload = 120 | JsonParser.parseString( 121 | StringUtils.newStringUtf8( 122 | Base64.decodeBase64(tokenString.split(Pattern.quote("."))[1]))) 123 | .getAsJsonObject(); 124 | payload.remove("bar"); 125 | payload.addProperty("bar", 14); 126 | String payloadString = new Gson().toJson(payload); 127 | String[] parts = tokenString.split("\\."); 128 | parts[1] = Base64.encodeBase64URLSafeString(payloadString.getBytes()); 129 | assertEquals(3, parts.length); 130 | 131 | String tamperedToken = parts[0] + "." + parts[1] + "." + parts[2]; 132 | 133 | assertThrows(SignatureException.class, () -> parser.verifyAndDeserialize(tamperedToken)); 134 | } 135 | 136 | private JsonTokenParser getJsonTokenParser() { 137 | return new JsonTokenParser(clock, locators, new AlwaysPassChecker()); 138 | } 139 | 140 | private JsonTokenParser getJsonTokenParser(VerifierProviders providers, Checker... checkers) { 141 | return new JsonTokenParser(clock, providers, checkers); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/JsonTokenTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.gson.JsonObject; 21 | import java.security.SignatureException; 22 | import java.time.Duration; 23 | import net.oauth.jsontoken.crypto.HmacSHA256Signer; 24 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 25 | 26 | public class JsonTokenTest extends JsonTokenTestBase { 27 | 28 | private static final String TOKEN_STRING_NULL_FIELDS = 29 | "eyJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.URnYdSXdAAEukebqZgMq6oFjK4E9cEZlfvO8tBe_WeA"; 30 | private static final String TOKEN_STRING_EMPTY_PAYLOAD = 31 | "eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ"; 32 | 33 | public void testSignAndSerialize() throws Exception { 34 | HmacSHA256Signer signer = new HmacSHA256Signer("google.com", "key2", SYMMETRIC_KEY); 35 | 36 | JsonToken token = new JsonToken(signer, clock); 37 | token.setParam("bar", 15); 38 | token.setParam("foo", "some value"); 39 | token.setAudience("http://www.google.com"); 40 | token.setIssuedAt(clock.now()); 41 | token.setExpiration(clock.now().plus(Duration.ofSeconds(1))); 42 | 43 | assertEquals(TOKEN_STRING, token.serializeAndSign()); 44 | } 45 | 46 | public void testSignAndSerialize_nullFields() throws Exception { 47 | HmacSHA256Signer signer = new HmacSHA256Signer(null, null, "secret".getBytes()); 48 | 49 | JsonToken token = new JsonToken(signer, clock); 50 | token.setParam("hello", "world"); 51 | 52 | assertEquals(TOKEN_STRING_NULL_FIELDS, token.serializeAndSign()); 53 | } 54 | 55 | public void testSignAndSerialize_emptyPayload() throws Exception { 56 | HmacSHA256Signer signer = new HmacSHA256Signer(null, (String) null, "secret".getBytes()); 57 | JsonToken token = new JsonToken(signer, clock); 58 | assertEquals(TOKEN_STRING_EMPTY_PAYLOAD, token.serializeAndSign()); 59 | } 60 | 61 | public void testSignAndSerialize_tokenFromJson() throws Exception { 62 | JsonToken token = new JsonToken(getFullHeader(), getFullPayload(), clock, TOKEN_STRING); 63 | assertThrows(SignatureException.class, () -> token.serializeAndSign()); 64 | } 65 | 66 | public void testConstructFromJson() throws Exception { 67 | JsonToken token = new JsonToken(getFullHeader(), getFullPayload(), clock, TOKEN_STRING); 68 | assertEquals(TOKEN_STRING, token.getTokenString()); 69 | assertHeader(token); 70 | assertPayload(token); 71 | } 72 | 73 | public void testConstructFromJson_onlyPayload() throws Exception { 74 | JsonToken token = new JsonToken(getFullPayload(), clock); 75 | assertPayload(token); 76 | } 77 | 78 | public void testConstructFromJson_nullFields() throws Exception { 79 | JsonObject header = new JsonObject(); 80 | header.addProperty(JsonToken.ALGORITHM_HEADER, "HS256"); 81 | 82 | JsonObject payload = new JsonObject(); 83 | payload.addProperty("hello", "world"); 84 | 85 | JsonToken token = new JsonToken(header, payload, clock, TOKEN_STRING_NULL_FIELDS); 86 | assertEquals(TOKEN_STRING_NULL_FIELDS, token.getTokenString()); 87 | assertEquals("world", token.getParamAsPrimitive("hello").getAsString()); 88 | assertNullPayload(token); 89 | } 90 | 91 | public void testConstructFromJson_emptyPayload() throws Exception { 92 | JsonObject header = new JsonObject(); 93 | header.addProperty(JsonToken.ALGORITHM_HEADER, "HS256"); 94 | 95 | JsonObject payload = new JsonObject(); 96 | 97 | JsonToken token = new JsonToken(header, payload, clock, TOKEN_STRING_EMPTY_PAYLOAD); 98 | assertEquals(TOKEN_STRING_EMPTY_PAYLOAD, token.getTokenString()); 99 | assertNullPayload(token); 100 | } 101 | 102 | private void assertNullPayload(JsonToken token) throws Exception { 103 | assertNull(token.getIssuer()); 104 | assertNull(token.getAudience()); 105 | assertEquals(SignatureAlgorithm.HS256, token.getSignatureAlgorithm()); 106 | assertNull(token.getKeyId()); 107 | assertNull(token.getIssuedAt()); 108 | assertNull(token.getExpiration()); 109 | } 110 | 111 | private JsonObject getFullHeader() { 112 | JsonObject header = new JsonObject(); 113 | header.addProperty(JsonToken.ALGORITHM_HEADER, "HS256"); 114 | header.addProperty(JsonToken.KEY_ID_HEADER, "key2"); 115 | return header; 116 | } 117 | 118 | private JsonObject getFullPayload() { 119 | JsonObject payload = new JsonObject(); 120 | payload.addProperty(JsonToken.ISSUER, "google.com"); 121 | payload.addProperty("bar", 15); 122 | payload.addProperty("foo", "some value"); 123 | payload.addProperty(JsonToken.AUDIENCE, "http://www.google.com"); 124 | payload.addProperty(JsonToken.ISSUED_AT, 1276669722); 125 | payload.addProperty(JsonToken.EXPIRATION, 1276669723); 126 | return payload; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/JsonTokenTestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken; 17 | 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import com.google.common.base.Splitter; 21 | import com.google.common.collect.Lists; 22 | import com.google.gson.JsonObject; 23 | import com.google.gson.JsonParser; 24 | import java.security.KeyFactory; 25 | import java.security.interfaces.RSAPrivateKey; 26 | import java.security.spec.EncodedKeySpec; 27 | import java.security.spec.PKCS8EncodedKeySpec; 28 | import java.time.Duration; 29 | import java.time.Instant; 30 | import java.util.List; 31 | import java.util.regex.Pattern; 32 | import junit.framework.TestCase; 33 | import net.oauth.jsontoken.crypto.HmacSHA256Verifier; 34 | import net.oauth.jsontoken.crypto.SignatureAlgorithm; 35 | import net.oauth.jsontoken.crypto.Verifier; 36 | import net.oauth.jsontoken.discovery.DefaultPublicKeyLocator; 37 | import net.oauth.jsontoken.discovery.IdentityServerDescriptorProvider; 38 | import net.oauth.jsontoken.discovery.JsonServerInfo; 39 | import net.oauth.jsontoken.discovery.VerifierProvider; 40 | import net.oauth.jsontoken.discovery.VerifierProviders; 41 | import net.oauth.jsontoken.exceptions.ErrorCode; 42 | import net.oauth.jsontoken.exceptions.InvalidJsonTokenException; 43 | import org.apache.commons.codec.binary.Base64; 44 | import org.junit.function.ThrowingRunnable; 45 | 46 | public abstract class JsonTokenTestBase extends TestCase { 47 | 48 | protected static final byte[] SYMMETRIC_KEY = "kjdhasdkjhaskdjhaskdjhaskdjh".getBytes(); 49 | 50 | protected static final String PRIVATE_KEY = 51 | "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6nMEXFuxTnM5+yM4Afngybf5Z" 52 | + "89JxlchBA3Ni//Gm1/25MetzfId2Jg8NkthmRDzH6sFaoNS7n6Z6JyNJFszb2PXKBkZdem219F5k" 53 | + "jawoHrfA1Lu8fBmGQYG/aG70aPft2eEZbY+XqW5WUlMk7vFW7BDikwBXyv/5rrFasBfPWd13xozQ" 54 | + "9612IErWGlGMgxmB64jcTbGWMzDgzE/scSmyeQ0vQQMW8J+Nnb/yDpY7loXrVrAgZx8IBv1f9Fv3" 55 | + "p7tirTD/vFgzxE2rIAauM/aU8zBHEyXL1NSNq0I62OAF4DLiDlcEFOvYjqoiCPQIh0NXnQy8Dcs5" 56 | + "xHCj0e1b3X/LAgMBAAECggEBAJ9G5iQQA7xF7ZYXTITtbSgV6+/ZBTi/kEG1cUoBjL9MQZpNOlrC" 57 | + "4lf8mgKK4LtA6OP1wfzZo1lVJeHDAAIFPOs0nm1Ft89XjMauAdcveM5xkYM7z9VL0vlddiHqQDHK" 58 | + "WjsgKVnrwpC/I5b4A1FVxJXdPXg14igM8zioW2Y9QMVPxeUmRJxeGfvlotRlD1At1KNKg7Q2bPoi" 59 | + "1IlRzdae6ky18x/o6FRbTo2WGRehqIAjqmwqNib3u4k/1QfEbKGShVjMtraxdlFBM7kXb/pTfhhU" 60 | + "xlsf4xraVy2LWBLen+BAOYScd0P7vD+5oET+e4YVqILoz/WQqI9BYmTHkzj+LLECgYEA9bVjRrXq" 61 | + "5NtO94w0T2BI9yGnZNRFbCcSocUlc6lgX7lFa6N5JvaoWF5p9CmUPPm7lxGOeSzvLKB4qv3uP/Px" 62 | + "RQzWvAT/isKnSJ2FuKcFYGA527uJ5BlOJAtTKViYhQdYlE2g9KsjLkxJ27aF49jrkhKWqueIdJpF" 63 | + "VfF9w+KYvVkCgYEAwm205fCRH3WEBzii2TrHqm/nVRWZ7Kxis4JppwxUslLKp33bzbHn9uOKFGfN" 64 | + "rtXpSq9hvAcnJlJAEyVFtVNFcazE/+GbUfnrKaC3UeomjYxBk45Lcutt441gOO2SFcra7GHiNgVv" 65 | + "fELNMo/Rr7tk8djcUcYXuDk4Kz/T2AttzcMCgYBg/Z8YtIrqmB+N3Exx4OIsm55GUPyueqYCMZ5d" 66 | + "D8k5QBtFKByU4t0FNQ/CD/+yKiqAsa956eDnztiTNvWrTRI6XZ0OTzLIhZofMf8tKtEWgCWWtWrz" 67 | + "HYIY/FdxhMWADaxLrnEQ49VZW0f0cRJdJK2o1amgARF+Zb9k85TflD0S0QKBgBYFlQrCT72vcs/a" 68 | + "k19lb/4XBK23b6LF97v7VnosRF+aTwffkoje0LY/GYGsLDjUU8M40Coa6U1G3akNfLLIBsKUXg/Z" 69 | + "ft0vIHqrkHf/vHQl4buTz2npzp2Kgs6P4g8D1f4WLCgQP4tkiZdjgM2VvR5DgNjmRgOAv6LubNE4" 70 | + "oiw/AoGAXKfOSrbgx8JQUE7Lt6mhGvP9oTj3uiV16GgxjOtkpP3TfjsdRcmivAuekKMMKufQvGxh" 71 | + "nX9eCYvLqJqZZwPy/002H7So3Yd1/d9ORkKetDKGjXHPDYyEPQQ+ss9OGm53XlViklXb+i9wsdDz" 72 | + "R7tAFexSjyVKnWSDBh52t6lBtHo="; 73 | 74 | private static final String SERVER_INFO_DOCUMENT = 75 | "{ \"verification_keys\": {" 76 | + 77 | // this is the public key that goes with the above private key 78 | "\"key1\":\"RSA.ALqcwRcW7FOczn7IzgB-eDJt_lnz0nGVyEEDc2L_8abX_bkx63N8h3YmDw2S2GZEPMfqwVqg1LufpnonI0kWzNvY9coGRl16bbX0XmSNrCget8DUu7x8GYZBgb9obvRo9-3Z4Rltj5epblZSUyTu8VbsEOKTAFfK__musVqwF89Z3XfGjND3rXYgStYaUYyDGYHriNxNsZYzMODMT-xxKbJ5DS9BAxbwn42dv_IOljuWhetWsCBnHwgG_V_0W_enu2KtMP-8WDPETasgBq4z9pTzMEcTJcvU1I2rQjrY4AXgMuIOVwQU69iOqiII9AiHQ1edDLwNyznEcKPR7Vvdf8s.AQAB\"}," 79 | + " " 80 | + 81 | // some other information that might be in the server info document. 82 | "\"foo\": \"bar\"}"; 83 | 84 | protected VerifierProviders locators; 85 | protected VerifierProviders locatorsFromRuby; 86 | protected RSAPrivateKey privateKey; 87 | 88 | protected static final String TOKEN_STRING = 89 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.Xugb4nb5kLV3NTpOLaz9er5PhAI5mFehHst_33EUFHs"; 90 | protected static final String TOKEN_STRING_BAD_SIG = 91 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.Wugb4nb5kLV3NTpOLaz9er5PhAI5mFehHst_33EUFHs"; 92 | protected static final String TOKEN_STRING_2PARTS = 93 | "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ"; 94 | protected static final String TOKEN_STRING_UNSUPPORTED_SIGNATURE_ALGORITHM = 95 | "eyJhbGciOiJIUzUxMiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.44qsiZg1Hnf95N-2wNqd1htgDlE7X0BSUMMkboMcZ5QLKbmVATozMuzdoE0MAhU-IdWUuICFbzu_wcDEXDTLug"; 96 | protected static final String TOKEN_STRING_CORRUPT_HEADER = 97 | "fyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.Xugb4nb5kLV3NTpOLaz9er5PhAI5mFehHst_33EUFHs"; 98 | protected static final String TOKEN_STRING_HEADER_MISSING_ALG = 99 | "eyJ3cm9uZ1BhcmFtIjoiSFMyNTYifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIzfQ.mBtVwsgT2uZJqu2zyUzbCXF4tfo8jSSlOeRI0Tv222o"; 100 | protected static final String TOKEN_FROM_RUBY = 101 | "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8"; 102 | 103 | protected static final Duration SKEW = Duration.ofMinutes(1); 104 | protected FakeClock clock = new FakeClock(SKEW); 105 | 106 | /** 107 | * Convert encoded tokens into a more human-readable form without verifying. Useful for logging. 108 | */ 109 | protected static String decodeTokenForHumans(String encodedToken) { 110 | String[] pieces = encodedToken.split(Pattern.quote(".")); 111 | if (pieces.length != 3) { 112 | return "invalid token (3 segments expected): " + encodedToken; 113 | } 114 | for (int i = 0; i < 3; i++) { 115 | pieces[i] = new String(Base64.decodeBase64(pieces[i].getBytes())); 116 | } 117 | return pieces[0] + "." + pieces[1] + "." + pieces[2]; 118 | } 119 | 120 | @Override 121 | protected void setUp() throws Exception { 122 | final Verifier hmacVerifier = new HmacSHA256Verifier(SYMMETRIC_KEY); 123 | 124 | VerifierProvider hmacLocator = (signerId, keyId) -> Lists.newArrayList(hmacVerifier); 125 | 126 | VerifierProvider rsaLocator = 127 | new DefaultPublicKeyLocator( 128 | new IdentityServerDescriptorProvider(), 129 | uri -> JsonServerInfo.getDocument(SERVER_INFO_DOCUMENT)); 130 | 131 | locators = new VerifierProviders(); 132 | locators.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocator); 133 | locators.setVerifierProvider(SignatureAlgorithm.RS256, rsaLocator); 134 | 135 | EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decodeBase64(PRIVATE_KEY)); 136 | KeyFactory fac = KeyFactory.getInstance("RSA"); 137 | privateKey = (RSAPrivateKey) fac.generatePrivate(spec); 138 | 139 | final Verifier hmacVerifierFromRuby = new HmacSHA256Verifier("secret".getBytes()); 140 | VerifierProvider hmacLocatorFromRuby = 141 | (signerId, keyId) -> Lists.newArrayList(hmacVerifierFromRuby); 142 | locatorsFromRuby = new VerifierProviders(); 143 | locatorsFromRuby.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocatorFromRuby); 144 | 145 | clock.setNow(Instant.ofEpochSecond(1276669722L)); 146 | } 147 | 148 | protected void assertHeader(JsonToken token) { 149 | assertEquals(SignatureAlgorithm.HS256, token.getSignatureAlgorithm()); 150 | assertEquals("key2", token.getKeyId()); 151 | } 152 | 153 | protected void assertPayload(JsonToken token) { 154 | assertEquals("google.com", token.getIssuer()); 155 | assertEquals("http://www.google.com", token.getAudience()); 156 | assertEquals(Instant.ofEpochSecond(1276669722L), token.getIssuedAt()); 157 | assertEquals(Instant.ofEpochSecond(1276669723L), token.getExpiration()); 158 | assertEquals(15, token.getParamAsPrimitive("bar").getAsLong()); 159 | assertEquals("some value", token.getParamAsPrimitive("foo").getAsString()); 160 | } 161 | 162 | protected void assertThrowsWithErrorCode( 163 | Class throwsClass, ErrorCode errorCode, ThrowingRunnable func) { 164 | Throwable t = assertThrows(throwsClass, func); 165 | assertTrue(t.getCause() instanceof InvalidJsonTokenException); 166 | assertTrue(((InvalidJsonTokenException) t.getCause()).getErrorCode().equals(errorCode)); 167 | } 168 | 169 | /** 170 | * Deserializes the token string such that tokenStrings without signatures are allowed. Only 171 | * supports token strings with at least two parts. 172 | */ 173 | protected JsonToken naiveDeserialize(String tokenString) { 174 | List pieces = Splitter.on(JsonTokenUtil.DELIMITER).splitToList(tokenString); 175 | JsonObject header = 176 | JsonParser.parseString(JsonTokenUtil.fromBase64ToJsonString(pieces.get(0))) 177 | .getAsJsonObject(); 178 | JsonObject payload = 179 | JsonParser.parseString(JsonTokenUtil.fromBase64ToJsonString(pieces.get(1))) 180 | .getAsJsonObject(); 181 | return new JsonToken(header, payload, clock, tokenString); 182 | } 183 | 184 | protected List getVerifiers() { 185 | return locators 186 | .getVerifierProvider(SignatureAlgorithm.HS256) 187 | .findVerifier("google.com", "key2"); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/jsontoken/crypto/HmacSHA256VerifierTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.jsontoken.crypto; 17 | 18 | import static org.junit.Assert.assertThrows; 19 | 20 | import java.security.SignatureException; 21 | import junit.framework.TestCase; 22 | 23 | /** Basic unit tests for the {@link HmacSHA256Verifier} class. */ 24 | public class HmacSHA256VerifierTest extends TestCase { 25 | private static final byte[] SYMMETRIC_KEY = "kjdhasdkjhaskdjhaskdjhaskdjh".getBytes(); 26 | private static final byte[] SOURCE = "randomdatatobesignedfortest".getBytes(); 27 | 28 | public void testGoodSignature() throws Exception { 29 | HmacSHA256Signer signer = new HmacSHA256Signer("test", "test-key", SYMMETRIC_KEY); 30 | HmacSHA256Verifier verifier = new HmacSHA256Verifier(SYMMETRIC_KEY); 31 | 32 | byte[] expectedSignature = signer.sign(SOURCE); 33 | verifier.verifySignature(SOURCE, expectedSignature); 34 | } 35 | 36 | public void testBadSignature() throws Exception { 37 | HmacSHA256Signer signer = new HmacSHA256Signer("test", "test-key", SYMMETRIC_KEY); 38 | HmacSHA256Verifier verifier = new HmacSHA256Verifier(SYMMETRIC_KEY); 39 | 40 | // Generate signature and flip the last bit on the first byte 41 | byte[] signature = signer.sign(SOURCE); 42 | signature[0] ^= 1; 43 | 44 | assertThrows(SignatureException.class, () -> verifier.verifySignature(SOURCE, signature)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/signatures/SignedJsonAssertionBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import net.oauth.jsontoken.JsonTokenTestBase; 19 | import net.oauth.jsontoken.crypto.RsaSHA256Signer; 20 | import net.oauth.jsontoken.crypto.Signer; 21 | 22 | public class SignedJsonAssertionBuilderTest extends JsonTokenTestBase { 23 | 24 | public void testSignature() throws Exception { 25 | 26 | Signer signer = new RsaSHA256Signer("google.com", "key1", privateKey); 27 | 28 | SignedJsonAssertionToken token = new SignedJsonAssertionToken(signer); 29 | token.setNonce("nonce"); 30 | token.setAudience("http://www.example.com/api"); 31 | token.setScope("scope"); 32 | 33 | assertEquals("nonce", token.getNonce()); 34 | assertEquals("http://www.example.com/api", token.getAudience()); 35 | 36 | SignedJsonAssertionTokenParser parser = new SignedJsonAssertionTokenParser(locators, null); 37 | SignedJsonAssertionToken compare = 38 | parser.parseToken(token.serializeAndSign(), "HTTP://www.Example.Com/api"); 39 | 40 | assertEquals("nonce", compare.getNonce()); 41 | assertEquals("http://www.example.com/api", compare.getAudience()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/net/oauth/signatures/SignedTokenBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.oauth.signatures; 17 | 18 | import net.oauth.jsontoken.JsonTokenTestBase; 19 | import net.oauth.jsontoken.crypto.RsaSHA256Signer; 20 | import net.oauth.jsontoken.crypto.Signer; 21 | 22 | public class SignedTokenBuilderTest extends JsonTokenTestBase { 23 | 24 | public void testSignature() throws Exception { 25 | 26 | Signer signer = new RsaSHA256Signer("google.com", "key1", privateKey); 27 | 28 | SignedOAuthToken token = new SignedOAuthToken(signer); 29 | token.setMethod("GET"); 30 | token.setNonce("nonce"); 31 | token.setOAuthToken("token"); 32 | token.setAudience("http://www.example.com/api"); 33 | 34 | assertEquals("GET", token.getMethod()); 35 | assertEquals("nonce", token.getNonce()); 36 | assertEquals("token", token.getOAuthToken()); 37 | assertEquals("http://www.example.com/api", token.getAudience()); 38 | 39 | SignedOAuthTokenParser parser = new SignedOAuthTokenParser(locators, null); 40 | SignedOAuthToken compare = 41 | parser.parseToken(token.serializeAndSign(), "GET", "HTTP://www.Example.Com/api"); 42 | 43 | assertEquals("GET", compare.getMethod()); 44 | assertEquals("nonce", compare.getNonce()); 45 | assertEquals("token", compare.getOAuthToken()); 46 | assertEquals("http://www.example.com/api", compare.getAudience()); 47 | } 48 | } 49 | --------------------------------------------------------------------------------