• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020, Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 package com.google.auth.oauth2;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertNotNull;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assert.fail;
37 
38 import com.google.api.client.http.HttpTransport;
39 import com.google.api.client.http.LowLevelHttpRequest;
40 import com.google.api.client.http.LowLevelHttpResponse;
41 import com.google.api.client.testing.http.MockHttpTransport;
42 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
43 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
44 import com.google.api.client.util.Clock;
45 import com.google.auth.http.HttpTransportFactory;
46 import com.google.auth.oauth2.TokenVerifier.VerificationException;
47 import com.google.common.io.CharStreams;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.Reader;
52 import java.util.Arrays;
53 import java.util.List;
54 import org.junit.Ignore;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.junit.runners.JUnit4;
58 
59 @RunWith(JUnit4.class)
60 public class TokenVerifierTest {
61   private static final String ES256_TOKEN =
62       "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1wZjBEQSJ9.eyJhdWQiOiIvcHJvamVjdHMvNjUyNTYyNzc2Nzk4L2FwcHMvY2xvdWQtc2FtcGxlcy10ZXN0cy1waHAtaWFwIiwiZW1haWwiOiJjaGluZ29yQGdvb2dsZS5jb20iLCJleHAiOjE1ODQwNDc2MTcsImdvb2dsZSI6eyJhY2Nlc3NfbGV2ZWxzIjpbImFjY2Vzc1BvbGljaWVzLzUxODU1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvcmVjZW50U2VjdXJlQ29ubmVjdERhdGEiLCJhY2Nlc3NQb2xpY2llcy81MTg1NTEyODA5MjQvYWNjZXNzTGV2ZWxzL3Rlc3ROb09wIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2Vzc0xldmVscy9ldmFwb3JhdGlvblFhRGF0YUZ1bGx5VHJ1c3RlZCJdfSwiaGQiOiJnb29nbGUuY29tIiwiaWF0IjoxNTg0MDQ3MDE3LCJpc3MiOiJodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vaWFwIiwic3ViIjoiYWNjb3VudHMuZ29vZ2xlLmNvbToxMTIxODE3MTI3NzEyMDE5NzI4OTEifQ.yKNtdFY5EKkRboYNexBdfugzLhC3VuGyFcuFYA8kgpxMqfyxa41zkML68hYKrWu2kOBTUW95UnbGpsIi_u1fiA";
63 
64   private static final String FEDERATED_SIGNON_RS256_TOKEN =
65       "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY5ZDk3YjRjYWU5MGJjZDc2YWViMjAwMjZmNmI3NzBjYWMyMjE3ODMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL3BhdGgiLCJhenAiOiJpbnRlZ3JhdGlvbi10ZXN0c0BjaGluZ29yLXRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6ImludGVncmF0aW9uLXRlc3RzQGNoaW5nb3ItdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1ODc2Mjk4ODgsImlhdCI6MTU4NzYyNjI4OCwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA0MDI5MjkyODUzMDk5OTc4MjkzIn0.Pj4KsJh7riU7ZIbPMcHcHWhasWEcbVjGP4yx_5E0iOpeDalTdri97E-o0dSSkuVX2FeBIgGUg_TNNgJ3YY97T737jT5DUYwdv6M51dDlLmmNqlu_P6toGCSRC8-Beu5gGmqS2Y82TmpHH9Vhoh5PsK7_rVHk8U6VrrVVKKTWm_IzTFhqX1oYKPdvfyaNLsXPbCt_NFE0C3DNmFkgVhRJu7LtzQQN-ghaqd3Ga3i6KH222OEI_PU4BUTvEiNOqRGoMlT_YOsyFN3XwqQ6jQGWhhkArL1z3CG2BVQjHTKpgVsRyy_H6WTZiju2Q-XWobgH-UPSZbyymV8-cFT9XKEtZQ";
66   private static final String LEGACY_FEDERATED_SIGNON_CERT_URL =
67       "https://www.googleapis.com/oauth2/v1/certs";
68 
69   private static final String SERVICE_ACCOUNT_RS256_TOKEN =
70       "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE3MjdiNmI0OTQwMmI5Y2Y5NWJlNGU4ZmQzOGFhN2U3YzExNjQ0YjEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Nsb3VkdGFza3MuZ29vZ2xlYXBpcy5jb20vdjIvcHJvamVjdHMvZ2Nsb3VkLWRldmVsL2xvY2F0aW9ucyIsImF6cCI6InN0aW0tdGVzdEBzdGVsbGFyLWRheS0yNTQyMjIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6InN0aW0tdGVzdEBzdGVsbGFyLWRheS0yNTQyMjIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNjYwODgwNjczLCJpYXQiOjE2NjA4NzcwNzMsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInN1YiI6IjExMjgxMDY3Mjk2MzcyODM2NjQwNiJ9.Q2tG-hN6UHecbzaCIlg58K9msp58nLZWs03CBGO_D6F3cI4LKQEUzsbcztZqmNGWd0ld4zkrKzIP9cQosa_xold4hEzSX_ORRHYQLimLYaQmP3rKqWPMsbIupPdpnGqBDzAYjc7Pw9pQBzuZJj8e3FEG6a5tblDfMcgeklXZIkwzN7ypWCbFDoDP2STSYJYZ-LQIB0-Zlex7dm2KhyB8QSkMQK60YvpXz4L1OtwG7spk3yUCWxul6hYF76klST0iS6DH03YdaDpt4gRXkTUKyTRfB10h-WhCAKKRzmT6d_IT9ApIyqPhimkgkBHhLNyjK8lgAJdk9CLriSEOgVpsow";
71   private static final String SERVICE_ACCOUNT_CERT_URL =
72       "https://www.googleapis.com/oauth2/v3/certs";
73 
74   private static final List<String> ALL_TOKENS =
75       Arrays.asList(ES256_TOKEN, FEDERATED_SIGNON_RS256_TOKEN, SERVICE_ACCOUNT_RS256_TOKEN);
76 
77   // Fixed to 2020-02-26 08:00:00 to allow expiration tests to pass
78   private static final Clock FIXED_CLOCK =
79       new Clock() {
80         @Override
81         public long currentTimeMillis() {
82           return 1582704000000L;
83         }
84       };
85 
86   @Test
verifyExpiredToken()87   public void verifyExpiredToken() {
88     for (String token : ALL_TOKENS) {
89       TokenVerifier tokenVerifier = TokenVerifier.newBuilder().build();
90       try {
91         tokenVerifier.verify(token);
92         fail("Should have thrown a VerificationException");
93       } catch (TokenVerifier.VerificationException e) {
94         assertTrue(e.getMessage().contains("expired"));
95       }
96     }
97   }
98 
99   @Test
verifyExpectedAudience()100   public void verifyExpectedAudience() {
101     TokenVerifier tokenVerifier =
102         TokenVerifier.newBuilder().setAudience("expected audience").build();
103     for (String token : ALL_TOKENS) {
104       try {
105         tokenVerifier.verify(token);
106         fail("Should have thrown a VerificationException");
107       } catch (TokenVerifier.VerificationException e) {
108         assertTrue(e.getMessage().contains("audience does not match"));
109       }
110     }
111   }
112 
113   @Test
verifyExpectedIssuer()114   public void verifyExpectedIssuer() {
115     TokenVerifier tokenVerifier = TokenVerifier.newBuilder().setIssuer("expected issuer").build();
116     for (String token : ALL_TOKENS) {
117       try {
118         tokenVerifier.verify(token);
119         fail("Should have thrown a VerificationException");
120       } catch (TokenVerifier.VerificationException e) {
121         assertTrue(e.getMessage().contains("issuer does not match"));
122       }
123     }
124   }
125 
126   @Test
verifyEs256Token404CertificateUrl()127   public void verifyEs256Token404CertificateUrl() {
128     // Mock HTTP requests
129     HttpTransportFactory httpTransportFactory =
130         new HttpTransportFactory() {
131           @Override
132           public HttpTransport create() {
133             return new MockHttpTransport() {
134               @Override
135               public LowLevelHttpRequest buildRequest(String method, String url)
136                   throws IOException {
137                 return new MockLowLevelHttpRequest() {
138                   @Override
139                   public LowLevelHttpResponse execute() throws IOException {
140                     MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
141                     response.setStatusCode(404);
142                     response.setContentType("application/json");
143                     response.setContent("");
144                     return response;
145                   }
146                 };
147               }
148             };
149           }
150         };
151     TokenVerifier tokenVerifier =
152         TokenVerifier.newBuilder()
153             .setClock(FIXED_CLOCK)
154             .setHttpTransportFactory(httpTransportFactory)
155             .build();
156 
157     try {
158       tokenVerifier.verify(ES256_TOKEN);
159       fail("Should not be able to continue without exception.");
160     } catch (TokenVerifier.VerificationException exception) {
161       assertTrue(
162           exception.getMessage().contains("Error fetching PublicKey from certificate location"));
163     }
164   }
165 
166   @Test
verifyEs256TokenPublicKeyMismatch()167   public void verifyEs256TokenPublicKeyMismatch() {
168     // Mock HTTP requests
169     HttpTransportFactory httpTransportFactory =
170         new HttpTransportFactory() {
171           @Override
172           public HttpTransport create() {
173             return new MockHttpTransport() {
174               @Override
175               public LowLevelHttpRequest buildRequest(String method, String url) {
176                 return new MockLowLevelHttpRequest() {
177                   @Override
178                   public LowLevelHttpResponse execute() throws IOException {
179                     MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
180                     response.setStatusCode(200);
181                     response.setContentType("application/json");
182                     response.setContent("");
183                     return response;
184                   }
185                 };
186               }
187             };
188           }
189         };
190     TokenVerifier tokenVerifier =
191         TokenVerifier.newBuilder()
192             .setClock(FIXED_CLOCK)
193             .setHttpTransportFactory(httpTransportFactory)
194             .build();
195     try {
196       tokenVerifier.verify(ES256_TOKEN);
197       fail("Should have failed verification");
198     } catch (TokenVerifier.VerificationException e) {
199       assertTrue(e.getMessage().contains("Error fetching PublicKey"));
200     }
201   }
202 
203   @Test
verifyPublicKeyStoreIntermittentError()204   public void verifyPublicKeyStoreIntermittentError() throws VerificationException, IOException {
205     // mock responses
206     MockLowLevelHttpResponse response404 =
207         new MockLowLevelHttpResponse()
208             .setStatusCode(404)
209             .setContentType("application/json")
210             .setContent("");
211 
212     MockLowLevelHttpResponse responseEmpty =
213         new MockLowLevelHttpResponse()
214             .setStatusCode(200)
215             .setContentType("application/json")
216             .setContent("{\"keys\":[]}");
217 
218     MockLowLevelHttpResponse responseGood =
219         new MockLowLevelHttpResponse()
220             .setStatusCode(200)
221             .setContentType("application/json")
222             .setContent(readResourceAsString("iap_keys.json"));
223 
224     // Mock HTTP requests
225     MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
226 
227     transportFactory.transport.addResponseSequence(
228         response404, response404, response404, responseEmpty, responseGood);
229 
230     TokenVerifier tokenVerifier =
231         TokenVerifier.newBuilder()
232             .setClock(FIXED_CLOCK)
233             .setHttpTransportFactory(transportFactory)
234             .build();
235 
236     try {
237       tokenVerifier.verify(ES256_TOKEN);
238       fail("Should not be able to continue without exception.");
239     } catch (TokenVerifier.VerificationException exception) {
240       assertTrue(exception.getMessage().contains("Error fetching PublicKey"));
241     }
242 
243     try {
244       tokenVerifier.verify(ES256_TOKEN);
245       fail("Should not be able to continue without exception.");
246     } catch (TokenVerifier.VerificationException exception) {
247       assertTrue(exception.getCause().getMessage().contains("No valid public key"));
248     }
249 
250     assertNotNull(tokenVerifier.verify(ES256_TOKEN));
251   }
252 
253   @Test
verifyEs256Token()254   public void verifyEs256Token() throws VerificationException, IOException {
255     HttpTransportFactory httpTransportFactory =
256         mockTransport(
257             "https://www.gstatic.com/iap/verify/public_key-jwk",
258             readResourceAsString("iap_keys.json"));
259     TokenVerifier tokenVerifier =
260         TokenVerifier.newBuilder()
261             .setClock(FIXED_CLOCK)
262             .setHttpTransportFactory(httpTransportFactory)
263             .build();
264     assertNotNull(tokenVerifier.verify(ES256_TOKEN));
265   }
266 
267   @Test
verifyRs256Token()268   public void verifyRs256Token() throws VerificationException, IOException {
269     HttpTransportFactory httpTransportFactory =
270         mockTransport(
271             "https://www.googleapis.com/oauth2/v3/certs",
272             readResourceAsString("federated_keys.json"));
273     TokenVerifier tokenVerifier =
274         TokenVerifier.newBuilder()
275             .setClock(FIXED_CLOCK)
276             .setHttpTransportFactory(httpTransportFactory)
277             .build();
278     assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN));
279   }
280 
281   @Test
verifyRs256TokenWithLegacyCertificateUrlFormat()282   public void verifyRs256TokenWithLegacyCertificateUrlFormat()
283       throws TokenVerifier.VerificationException, IOException {
284     HttpTransportFactory httpTransportFactory =
285         mockTransport(
286             LEGACY_FEDERATED_SIGNON_CERT_URL, readResourceAsString("legacy_federated_keys.json"));
287     TokenVerifier tokenVerifier =
288         TokenVerifier.newBuilder()
289             .setCertificatesLocation(LEGACY_FEDERATED_SIGNON_CERT_URL)
290             .setClock(FIXED_CLOCK)
291             .setHttpTransportFactory(httpTransportFactory)
292             .build();
293     assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN));
294   }
295 
296   @Test
297   @Ignore
verifyServiceAccountRs256Token()298   public void verifyServiceAccountRs256Token() throws VerificationException {
299     final Clock clock =
300         new Clock() {
301           @Override
302           public long currentTimeMillis() {
303             return 1660880573000L;
304           }
305         };
306     TokenVerifier tokenVerifier =
307         TokenVerifier.newBuilder()
308             .setClock(clock)
309             .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL)
310             .build();
311     assertNotNull(tokenVerifier.verify(SERVICE_ACCOUNT_RS256_TOKEN));
312   }
313 
readResourceAsString(String resourceName)314   static String readResourceAsString(String resourceName) throws IOException {
315     InputStream inputStream =
316         TokenVerifierTest.class.getClassLoader().getResourceAsStream(resourceName);
317     try (final Reader reader = new InputStreamReader(inputStream)) {
318       return CharStreams.toString(reader);
319     }
320   }
321 
mockTransport(String url, String certificates)322   static HttpTransportFactory mockTransport(String url, String certificates) {
323     final String certificatesContent = certificates;
324     final String certificatesUrl = url;
325     return new HttpTransportFactory() {
326       @Override
327       public HttpTransport create() {
328         return new MockHttpTransport() {
329           @Override
330           public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
331             assertEquals(certificatesUrl, url);
332             return new MockLowLevelHttpRequest() {
333               @Override
334               public LowLevelHttpResponse execute() throws IOException {
335                 MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
336                 response.setStatusCode(200);
337                 response.setContentType("application/json");
338                 response.setContent(certificatesContent);
339                 return response;
340               }
341             };
342           }
343         };
344       }
345     };
346   }
347 }
348