• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015, Google Inc. All rights reserved.
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 Inc. 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 
32 package com.google.auth.oauth2;
33 
34 import com.google.api.client.http.HttpHeaders;
35 import com.google.api.client.http.HttpTransport;
36 import com.google.api.client.http.javanet.NetHttpTransport;
37 import com.google.api.client.json.GenericJson;
38 import com.google.api.client.json.JsonFactory;
39 import com.google.api.client.json.JsonObjectParser;
40 import com.google.api.client.json.gson.GsonFactory;
41 import com.google.api.client.util.PemReader;
42 import com.google.api.client.util.PemReader.Section;
43 import com.google.api.client.util.SecurityUtils;
44 import com.google.auth.http.AuthHttpConstants;
45 import com.google.auth.http.HttpTransportFactory;
46 import com.google.common.io.ByteStreams;
47 import java.io.ByteArrayInputStream;
48 import java.io.File;
49 import java.io.FileOutputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.io.Reader;
54 import java.io.StringReader;
55 import java.math.BigDecimal;
56 import java.net.URI;
57 import java.nio.charset.StandardCharsets;
58 import java.security.KeyFactory;
59 import java.security.NoSuchAlgorithmException;
60 import java.security.PrivateKey;
61 import java.security.spec.InvalidKeySpecException;
62 import java.security.spec.PKCS8EncodedKeySpec;
63 import java.util.Arrays;
64 import java.util.Collection;
65 import java.util.HashSet;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Set;
69 
70 /** Internal utilities for the com.google.auth.oauth2 namespace. */
71 class OAuth2Utils {
72   static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
73 
74   static final String TOKEN_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token";
75   static final String TOKEN_TYPE_TOKEN_EXCHANGE = "urn:ietf:params:oauth:token-type:token-exchange";
76   static final String GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
77 
78   static final URI TOKEN_SERVER_URI = URI.create("https://oauth2.googleapis.com/token");
79   static final URI TOKEN_REVOKE_URI = URI.create("https://oauth2.googleapis.com/revoke");
80   static final URI USER_AUTH_URI = URI.create("https://accounts.google.com/o/oauth2/auth");
81 
82   static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
83 
84   static final HttpTransportFactory HTTP_TRANSPORT_FACTORY = new DefaultHttpTransportFactory();
85 
86   static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
87 
88   private static String VALUE_NOT_FOUND_MESSAGE = "%sExpected value %s not found.";
89   private static String VALUE_WRONG_TYPE_MESSAGE = "%sExpected %s value %s of wrong type.";
90 
91   static final String BEARER_PREFIX = AuthHttpConstants.BEARER + " ";
92 
93   static final String TOKEN_RESPONSE_SCOPE = "scope";
94 
95   // Includes expected server errors from Google token endpoint
96   // Other 5xx codes are either not used or retries are unlikely to succeed
97   public static final Set<Integer> TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES =
98       new HashSet<>(Arrays.asList(500, 503, 408, 429));
99 
100   static class DefaultHttpTransportFactory implements HttpTransportFactory {
101 
102     @Override
create()103     public HttpTransport create() {
104       return HTTP_TRANSPORT;
105     }
106   }
107 
108   /**
109    * Returns whether the headers contain the specified value as one of the entries in the specified
110    * header.
111    */
headersContainValue(HttpHeaders headers, String headerName, String value)112   static boolean headersContainValue(HttpHeaders headers, String headerName, String value) {
113     Object values = headers.get(headerName);
114     if (values instanceof Collection) {
115       @SuppressWarnings("unchecked")
116       Collection<Object> valuesCollection = (Collection<Object>) values;
117       return valuesCollection.contains(value);
118     }
119     return false;
120   }
121 
122   /** Parses the specified JSON text. */
parseJson(String json)123   static GenericJson parseJson(String json) throws IOException {
124     JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
125     InputStream stateStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
126     GenericJson stateJson =
127         parser.parseAndClose(stateStream, StandardCharsets.UTF_8, GenericJson.class);
128     return stateJson;
129   }
130 
131   /** Return the specified string from JSON or throw a helpful error message. */
validateString(Map<String, Object> map, String key, String errorPrefix)132   static String validateString(Map<String, Object> map, String key, String errorPrefix)
133       throws IOException {
134     Object value = map.get(key);
135     if (value == null) {
136       throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
137     }
138     if (!(value instanceof String)) {
139       throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "string", key));
140     }
141     return (String) value;
142   }
143 
144   /**
145    * Saves the end user credentials into the given file path.
146    *
147    * @param credentials InputStream containing user credentials in JSON format
148    * @param filePath Path to file where to store the credentials
149    * @throws IOException An error saving the credentials.
150    */
writeInputStreamToFile(InputStream credentials, String filePath)151   static void writeInputStreamToFile(InputStream credentials, String filePath) throws IOException {
152     final OutputStream outputStream = new FileOutputStream(new File(filePath));
153     try {
154       ByteStreams.copy(credentials, outputStream);
155     } finally {
156       outputStream.close();
157     }
158   }
159 
160   /** Return the specified optional string from JSON or throw a helpful error message. */
validateOptionalString(Map<String, Object> map, String key, String errorPrefix)161   static String validateOptionalString(Map<String, Object> map, String key, String errorPrefix)
162       throws IOException {
163     Object value = map.get(key);
164     if (value == null) {
165       return null;
166     }
167     if (!(value instanceof String)) {
168       throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "string", key));
169     }
170     return (String) value;
171   }
172 
173   /** Return the specified list of strings from JSON or throw a helpful error message. */
174   @SuppressWarnings("unchecked")
validateOptionalListString( Map<String, Object> map, String key, String errorPrefix)175   static List<String> validateOptionalListString(
176       Map<String, Object> map, String key, String errorPrefix) throws IOException {
177     Object value = map.get(key);
178     if (value == null) {
179       return null;
180     }
181     if (!(value instanceof List)) {
182       throw new IOException(
183           String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "List<String>", key));
184     }
185     return (List<String>) value;
186   }
187 
188   /** Return the specified integer from JSON or throw a helpful error message. */
validateInt32(Map<String, Object> map, String key, String errorPrefix)189   static int validateInt32(Map<String, Object> map, String key, String errorPrefix)
190       throws IOException {
191     Object value = map.get(key);
192     if (value == null) {
193       throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
194     }
195     if (value instanceof BigDecimal) {
196       BigDecimal bigDecimalValue = (BigDecimal) value;
197       return bigDecimalValue.intValueExact();
198     }
199     if (!(value instanceof Integer)) {
200       throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "integer", key));
201     }
202     return (Integer) value;
203   }
204 
205   /** Return the specified long from JSON or throw a helpful error message. */
validateLong(Map<String, Object> map, String key, String errorPrefix)206   static long validateLong(Map<String, Object> map, String key, String errorPrefix)
207       throws IOException {
208     Object value = map.get(key);
209     if (value == null) {
210       throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
211     }
212     if (value instanceof BigDecimal) {
213       BigDecimal bigDecimalValue = (BigDecimal) value;
214       return bigDecimalValue.longValueExact();
215     }
216     if (!(value instanceof Long)) {
217       throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "long", key));
218     }
219     return (Long) value;
220   }
221 
222   /** Return the specified map from JSON or throw a helpful error message. */
223   @SuppressWarnings({"unchecked", "rawtypes"})
validateMap(Map<String, Object> map, String key, String errorPrefix)224   static Map<String, Object> validateMap(Map<String, Object> map, String key, String errorPrefix)
225       throws IOException {
226     Object value = map.get(key);
227     if (value == null) {
228       throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
229     }
230     if (!(value instanceof Map)) {
231       throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "Map", key));
232     }
233     return (Map) value;
234   }
235 
236   /** Helper to convert from a PKCS#8 String to an RSA private key */
privateKeyFromPkcs8(String privateKeyPkcs8)237   static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOException {
238     Reader reader = new StringReader(privateKeyPkcs8);
239     Section section = PemReader.readFirstSectionAndClose(reader, "PRIVATE KEY");
240     if (section == null) {
241       throw new IOException("Invalid PKCS#8 data.");
242     }
243     byte[] bytes = section.getBase64DecodedBytes();
244     PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
245     Exception unexpectedException;
246     try {
247       KeyFactory keyFactory = SecurityUtils.getRsaKeyFactory();
248       return keyFactory.generatePrivate(keySpec);
249     } catch (NoSuchAlgorithmException | InvalidKeySpecException exception) {
250       unexpectedException = exception;
251     }
252     throw new IOException("Unexpected exception reading PKCS#8 data", unexpectedException);
253   }
254 
OAuth2Utils()255   private OAuth2Utils() {}
256 }
257