diff --git a/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java new file mode 100644 index 000000000..4154361cd --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java @@ -0,0 +1,528 @@ +/* + * Copyright 2022, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.UrlEncodedContent; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.json.webtoken.JsonWebToken; +import com.google.api.client.util.GenericData; +import com.google.auth.http.HttpTransportFactory; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.util.Date; +import java.util.Map; +import java.util.Objects; + +public class GdchCredentials extends GoogleCredentials { + static final String SUPPORTED_FORMAT_VERSION = "1"; + private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; + private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; + + private final PrivateKey privateKey; + private final String privateKeyId; + private final String projectId; + private final String serviceIdentityName; + private final URI tokenServerUri; + private final URI apiAudience; + private final int lifetime; + private final String transportFactoryClassName; + private final String caCertPath; + private transient HttpTransportFactory transportFactory; + + /** + * Internal constructor. + * + * @param builder A builder for {@link GdchCredentials} See {@link GdchCredentials.Builder}. + */ + @VisibleForTesting + GdchCredentials(GdchCredentials.Builder builder) { + this.projectId = Preconditions.checkNotNull(builder.projectId); + this.privateKeyId = Preconditions.checkNotNull(builder.privateKeyId); + this.privateKey = Preconditions.checkNotNull(builder.privateKey); + this.serviceIdentityName = Preconditions.checkNotNull(builder.serviceIdentityName); + this.tokenServerUri = Preconditions.checkNotNull(builder.tokenServerUri); + this.transportFactory = Preconditions.checkNotNull(builder.transportFactory); + this.transportFactoryClassName = this.transportFactory.getClass().getName(); + this.caCertPath = builder.caCertPath; + this.apiAudience = builder.apiAudience; + this.lifetime = builder.lifetime; + } + + /** + * Create GDCH service account credentials defined by JSON. + * + * @param json a map from the JSON representing the credentials. + * @return the GDCH service account credentials defined by the JSON. + * @throws IOException if the credential cannot be created from the JSON. + */ + static GdchCredentials fromJson(Map json) throws IOException { + String caCertPath = (String) json.get("ca_cert_path"); + return fromJson(json, new TransportFactoryForGdch(caCertPath)); + } + + /** + * Create GDCH service account credentials defined by JSON. + * + * @param json a map from the JSON representing the credentials. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. + * @return the GDCH service account credentials defined by the JSON. + * @throws IOException if the credential cannot be created from the JSON. + */ + @VisibleForTesting + static GdchCredentials fromJson(Map json, HttpTransportFactory transportFactory) + throws IOException { + String formatVersion = validateField((String) json.get("format_version"), "format_version"); + String projectId = validateField((String) json.get("project"), "project"); + String privateKeyId = validateField((String) json.get("private_key_id"), "private_key_id"); + String privateKeyPkcs8 = validateField((String) json.get("private_key"), "private_key"); + String serviceIdentityName = validateField((String) json.get("name"), "name"); + String tokenServerUriStringFromCreds = + validateField((String) json.get("token_uri"), "token_uri"); + String caCertPath = (String) json.get("ca_cert_path"); + + if (!SUPPORTED_FORMAT_VERSION.equals(formatVersion)) { + throw new IOException( + String.format("Only format version %s is supported.", SUPPORTED_FORMAT_VERSION)); + } + + URI tokenServerUriFromCreds = null; + try { + tokenServerUriFromCreds = new URI(tokenServerUriStringFromCreds); + } catch (URISyntaxException e) { + throw new IOException("Token server URI specified in 'token_uri' could not be parsed."); + } + + GdchCredentials.Builder builder = + GdchCredentials.newBuilder() + .setProjectId(projectId) + .setPrivateKeyId(privateKeyId) + .setTokenServerUri(tokenServerUriFromCreds) + .setServiceIdentityName(serviceIdentityName) + .setCaCertPath(caCertPath) + .setHttpTransportFactory(transportFactory); + + return fromPkcs8(privateKeyPkcs8, builder); + } + + /** + * Internal constructor. + * + * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format. + * @param builder A builder for GdchCredentials. + * @return an instance of GdchCredentials. + */ + static GdchCredentials fromPkcs8(String privateKeyPkcs8, GdchCredentials.Builder builder) + throws IOException { + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8); + builder.setPrivateKey(privateKey); + + return new GdchCredentials(builder); + } + + /** + * Create a copy of GDCH credentials with the specified audience. + * + * @param apiAudience The intended audience for GDCH credentials. + */ + public GdchCredentials createWithGdchAudience(URI apiAudience) throws IOException { + Preconditions.checkNotNull( + apiAudience, "Audience are not configured for GDCH service account credentials."); + return this.toBuilder().setGdchAudience(apiAudience).build(); + } + + /** + * Refresh the OAuth2 access token by getting a new access token using a JSON Web Token (JWT). + * + *

For GDCH credentials, this class creates a self-signed JWT, and sends to the GDCH + * authentication endpoint (tokenServerUri) to exchange an access token for the intended api + * audience (apiAudience). + */ + @Override + public AccessToken refreshAccessToken() throws IOException { + Preconditions.checkNotNull( + this.apiAudience, + "Audience are not configured for GDCH service account. Specify the " + + "audience by calling createWithGDCHAudience."); + + JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; + long currentTime = clock.currentTimeMillis(); + String assertion = createAssertion(jsonFactory, currentTime, getApiAudience()); + + GenericData tokenRequest = new GenericData(); + tokenRequest.set("grant_type", OAuth2Utils.TOKEN_TYPE_TOKEN_EXCHANGE); + tokenRequest.set("assertion", assertion); + UrlEncodedContent content = new UrlEncodedContent(tokenRequest); + + HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); + HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); + + request.setParser(new JsonObjectParser(jsonFactory)); + + HttpResponse response; + String errorTemplate = "Error getting access token for GDCH service account: %s, iss: %s"; + + try { + response = request.execute(); + } catch (HttpResponseException re) { + String message = String.format(errorTemplate, re.getMessage(), getServiceIdentityName()); + throw GoogleAuthException.createWithTokenEndpointResponseException(re, message); + } catch (IOException e) { + throw GoogleAuthException.createWithTokenEndpointIOException( + e, String.format(errorTemplate, e.getMessage(), getServiceIdentityName())); + } + + GenericData responseData = response.parseAs(GenericData.class); + String accessToken = + OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX); + int expiresInSeconds = + OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX); + long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000L; + return new AccessToken(accessToken, new Date(expiresAtMilliseconds)); + } + + /** + * Create a self-signed JWT for GDCH authentication flow. + * + *

The self-signed JWT is used to exchange access token from GDCH authentication + * (tokenServerUri), not for API call. It uses the serviceIdentityName as the `iss` and `sub` + * claim, and the tokenServerUri as the `aud` claim. The JWT is signed with the privateKey. + */ + String createAssertion(JsonFactory jsonFactory, long currentTime, URI apiAudience) + throws IOException { + JsonWebSignature.Header header = new JsonWebSignature.Header(); + header.setAlgorithm("RS256"); + header.setType("JWT"); + header.setKeyId(privateKeyId); + + JsonWebToken.Payload payload = new JsonWebToken.Payload(); + payload.setIssuer(getIssuerSubjectValue(projectId, serviceIdentityName)); + payload.setSubject(getIssuerSubjectValue(projectId, serviceIdentityName)); + payload.setIssuedAtTimeSeconds(currentTime / 1000); + payload.setExpirationTimeSeconds(currentTime / 1000 + this.lifetime); + payload.setAudience(getTokenServerUri().toString()); + + String assertion; + try { + payload.set("api_audience", apiAudience.toString()); + assertion = JsonWebSignature.signUsingRsaSha256(privateKey, jsonFactory, header, payload); + } catch (GeneralSecurityException e) { + throw new IOException( + "Error signing service account access token request with private key.", e); + } + + return assertion; + } + + /** + * Get the issuer and subject value in the format GDCH token server required. + * + *

This value is specific to GDCH and combined parameter used for both `iss` and `sub` fields + * in JWT claim. + */ + @VisibleForTesting + static String getIssuerSubjectValue(String projectId, String serviceIdentityName) { + return String.format("system:serviceaccount:%s:%s", projectId, serviceIdentityName); + } + + public final String getProjectId() { + return projectId; + } + + public final String getPrivateKeyId() { + return privateKeyId; + } + + public final PrivateKey getPrivateKey() { + return privateKey; + } + + public final String getServiceIdentityName() { + return serviceIdentityName; + } + + public final URI getTokenServerUri() { + return tokenServerUri; + } + + public final URI getApiAudience() { + return apiAudience; + } + + public final HttpTransportFactory getTransportFactory() { + return transportFactory; + } + + public final String getCaCertPath() { + return caCertPath; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @SuppressWarnings("unused") + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + // properly deserialize the transient transportFactory. + input.defaultReadObject(); + transportFactory = newInstance(transportFactoryClassName); + } + + @Override + public int hashCode() { + return Objects.hash( + projectId, + privateKeyId, + privateKey, + serviceIdentityName, + tokenServerUri, + transportFactoryClassName, + apiAudience, + caCertPath, + lifetime); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("projectId", projectId) + .add("privateKeyId", privateKeyId) + .add("serviceIdentityName", serviceIdentityName) + .add("tokenServerUri", tokenServerUri) + .add("transportFactoryClassName", transportFactoryClassName) + .add("caCertPath", caCertPath) + .add("apiAudience", apiAudience) + .add("lifetime", lifetime) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GdchCredentials)) { + return false; + } + GdchCredentials other = (GdchCredentials) obj; + return Objects.equals(this.projectId, other.projectId) + && Objects.equals(this.privateKeyId, other.privateKeyId) + && Objects.equals(this.privateKey, other.privateKey) + && Objects.equals(this.serviceIdentityName, other.serviceIdentityName) + && Objects.equals(this.tokenServerUri, other.tokenServerUri) + && Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName) + && Objects.equals(this.apiAudience, other.apiAudience) + && Objects.equals(this.caCertPath, other.caCertPath) + && Objects.equals(this.lifetime, other.lifetime); + } + + static InputStream readStream(File file) throws FileNotFoundException { + return new FileInputStream(file); + } + + public static class Builder extends GoogleCredentials.Builder { + private String projectId; + private String privateKeyId; + private PrivateKey privateKey; + private String serviceIdentityName; + private URI tokenServerUri; + private URI apiAudience; + private HttpTransportFactory transportFactory; + private String caCertPath; + private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; + + protected Builder() {} + + protected Builder(GdchCredentials credentials) { + this.projectId = credentials.projectId; + this.privateKeyId = credentials.privateKeyId; + this.privateKey = credentials.privateKey; + this.serviceIdentityName = credentials.serviceIdentityName; + this.tokenServerUri = credentials.tokenServerUri; + this.transportFactory = credentials.transportFactory; + this.caCertPath = credentials.caCertPath; + this.lifetime = credentials.lifetime; + } + + public Builder setProjectId(String projectId) { + this.projectId = projectId; + return this; + } + + public Builder setPrivateKeyId(String privateKeyId) { + this.privateKeyId = privateKeyId; + return this; + } + + public Builder setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + return this; + } + + public Builder setServiceIdentityName(String name) { + this.serviceIdentityName = name; + return this; + } + + public Builder setTokenServerUri(URI tokenServerUri) { + this.tokenServerUri = tokenServerUri; + return this; + } + + public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) { + this.transportFactory = transportFactory; + return this; + } + + public Builder setCaCertPath(String caCertPath) { + this.caCertPath = caCertPath; + return this; + } + + public Builder setGdchAudience(URI apiAudience) { + this.apiAudience = apiAudience; + return this; + } + + public String getProjectId() { + return projectId; + } + + public String getPrivateKeyId() { + return privateKeyId; + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public String getServiceIdentityName() { + return serviceIdentityName; + } + + public URI getTokenServerUri() { + return tokenServerUri; + } + + public HttpTransportFactory getHttpTransportFactory() { + return transportFactory; + } + + public String getCaCertPath() { + return caCertPath; + } + + public int getLifetime() { + return lifetime; + } + + public GdchCredentials build() { + return new GdchCredentials(this); + } + } + + private static String validateField(String field, String fieldName) throws IOException { + if (field == null || field.isEmpty()) { + throw new IOException( + String.format( + "Error reading GDCH service account credential from JSON, %s is misconfigured.", + fieldName)); + } + return field; + } + + /* + * Internal HttpTransportFactory for GDCH credentials. + * + *

GDCH authentication server could use a self-signed certificate, thus the client could + * provide the CA certificate path through the `ca_cert_path` in GDCH JSON file. + * + *

The TransportFactoryForGdch subclass would read the certificate and create a trust store, + * then use the trust store to create a transport. + * + *

If the GDCH authentication server uses well known CA certificate, then a regular transport + * would be set. + */ + static class TransportFactoryForGdch implements HttpTransportFactory { + HttpTransport transport; + + public TransportFactoryForGdch(String caCertPath) throws IOException { + setTransport(caCertPath); + } + + @Override + public HttpTransport create() { + return transport; + } + + private void setTransport(String caCertPath) throws IOException { + if (caCertPath == null || caCertPath.isEmpty()) { + this.transport = new NetHttpTransport(); + return; + } + try { + InputStream certificateStream = readStream(new File(caCertPath)); + this.transport = + new NetHttpTransport.Builder().trustCertificatesFromStream(certificateStream).build(); + } catch (IOException e) { + throw new IOException( + String.format( + "Error reading certificate file from CA cert path, value '%s': %s", + caCertPath, e.getMessage()), + e); + } catch (GeneralSecurityException e) { + throw new IOException("Error initiating transport with certificate stream.", e); + } + } + } +} diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index efe0480ad..9fc61c29d 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -56,6 +56,7 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; static final String USER_FILE_TYPE = "authorized_user"; static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account"; + static final String GDCH_SERVICE_ACCOUNT_FILE_TYPE = "gdch_service_account"; protected final String quotaProjectId; @@ -174,6 +175,9 @@ public static GoogleCredentials fromStream( if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { return ServiceAccountCredentials.fromJson(fileContents, transportFactory); } + if (GDCH_SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { + return GdchCredentials.fromJson(fileContents); + } if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) { return ExternalAccountCredentials.fromJson(fileContents, transportFactory); } diff --git a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index eaa33a11e..4fe6ff327 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -38,6 +38,9 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.util.PemReader; +import com.google.api.client.util.PemReader.Section; +import com.google.api.client.util.SecurityUtils; import com.google.auth.http.AuthHttpConstants; import com.google.auth.http.HttpTransportFactory; import com.google.common.io.ByteStreams; @@ -47,9 +50,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; import java.math.BigDecimal; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -61,6 +71,8 @@ class OAuth2Utils { static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; static final String TOKEN_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"; + static final String TOKEN_TYPE_TOKEN_EXCHANGE = "urn:ietf:params:oauth:token-type:token-exchange"; + static final String GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer"; static final URI TOKEN_SERVER_URI = URI.create("https://oauth2.googleapis.com/token"); static final URI TOKEN_REVOKE_URI = URI.create("https://oauth2.googleapis.com/revoke"); @@ -202,5 +214,24 @@ static Map validateMap(Map map, String key, Stri return (Map) value; } + /** Helper to convert from a PKCS#8 String to an RSA private key */ + static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOException { + Reader reader = new StringReader(privateKeyPkcs8); + Section section = PemReader.readFirstSectionAndClose(reader, "PRIVATE KEY"); + if (section == null) { + throw new IOException("Invalid PKCS#8 data."); + } + byte[] bytes = section.getBase64DecodedBytes(); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); + Exception unexpectedException; + try { + KeyFactory keyFactory = SecurityUtils.getRsaKeyFactory(); + return keyFactory.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException exception) { + unexpectedException = exception; + } + throw new IOException("Unexpected exception reading PKCS#8 data", unexpectedException); + } + private OAuth2Utils() {} } diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index fd08a9813..479041ead 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -49,10 +49,7 @@ import com.google.api.client.util.ExponentialBackOff; import com.google.api.client.util.GenericData; import com.google.api.client.util.Joiner; -import com.google.api.client.util.PemReader; -import com.google.api.client.util.PemReader.Section; import com.google.api.client.util.Preconditions; -import com.google.api.client.util.SecurityUtils; import com.google.auth.RequestMetadataCallback; import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpTransportFactory; @@ -62,20 +59,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; -import java.io.Reader; -import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; -import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -437,31 +429,12 @@ public static ServiceAccountCredentials fromPkcs8( */ static ServiceAccountCredentials fromPkcs8( String privateKeyPkcs8, ServiceAccountCredentials.Builder builder) throws IOException { - PrivateKey privateKey = privateKeyFromPkcs8(privateKeyPkcs8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8); builder.setPrivateKey(privateKey); return new ServiceAccountCredentials(builder); } - /** Helper to convert from a PKCS#8 String to an RSA private key */ - static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOException { - Reader reader = new StringReader(privateKeyPkcs8); - Section section = PemReader.readFirstSectionAndClose(reader, "PRIVATE KEY"); - if (section == null) { - throw new IOException("Invalid PKCS#8 data."); - } - byte[] bytes = section.getBase64DecodedBytes(); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); - Exception unexpectedException; - try { - KeyFactory keyFactory = SecurityUtils.getRsaKeyFactory(); - return keyFactory.generatePrivate(keySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException exception) { - unexpectedException = exception; - } - throw new IOException("Unexpected exception reading PKCS#8 data", unexpectedException); - } - /** * Returns credentials defined by a Service Account key file in JSON format from the Google * Developers Console. @@ -1038,7 +1011,7 @@ public Builder setPrivateKey(PrivateKey privateKey) { } public Builder setPrivateKeyString(String privateKeyPkcs8) throws IOException { - this.privateKey = privateKeyFromPkcs8(privateKeyPkcs8); + this.privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8); return this; } diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java index 707b657a7..a318ef72f 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java @@ -217,7 +217,7 @@ static ServiceAccountJwtAccessCredentials fromPkcs8( URI defaultAudience, String quotaProjectId) throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(privateKeyPkcs8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8); return new ServiceAccountJwtAccessCredentials( clientId, clientEmail, privateKey, privateKeyId, defaultAudience, quotaProjectId); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java index dfe2d622e..c3a26d260 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java @@ -89,6 +89,18 @@ public class DefaultCredentialsProviderTest { private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String SA_PRIVATE_KEY_PKCS8 = ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8; + private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String GDCH_SA_PROJECT_ID = "gdch-service-account-project-id"; + private static final String GDCH_SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; + private static final String GDCH_SA_PRIVATE_KEY_PKC8 = GdchCredentialsTest.PRIVATE_KEY_PKCS8; + private static final String GDCH_SA_SERVICE_IDENTITY_NAME = + "gdch-service-account-service-identity-name"; + private static final URI GDCH_SA_TOKEN_SERVER_URI = + URI.create("https://service-identity.domain/authenticate"); + private static final String GDCH_SA_CA_CERT_FILE_NAME = "cert.pem"; + private static final String GDCH_SA_CA_CERT_PATH = + GdchCredentialsTest.class.getClassLoader().getResource(GDCH_SA_CA_CERT_FILE_NAME).getPath(); + private static final URI GDCH_SA_API_AUDIENCE = URI.create("https://gdch-api-audience"); private static final Collection SCOPES = Collections.singletonList("dummy.scope"); private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); private static final String QUOTA_PROJECT = "sample-quota-project-id"; @@ -392,6 +404,49 @@ public void getDefaultCredentials_envUser_providesToken() throws IOException { } @Test + public void getDefaultCredentials_GdchServiceAccount() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); + String gdchServiceAccountPath = tempFilePath("gdch_service_account.json"); + testProvider.addFile(gdchServiceAccountPath, gdchServiceAccountStream); + testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, gdchServiceAccountPath); + + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); + + assertNotNull(defaultCredentials); + assertTrue(defaultCredentials instanceof GdchCredentials); + assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) defaultCredentials).getProjectId()); + assertEquals( + GDCH_SA_SERVICE_IDENTITY_NAME, + ((GdchCredentials) defaultCredentials).getServiceIdentityName()); + assertEquals( + GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) defaultCredentials).getTokenServerUri()); + assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) defaultCredentials).getCaCertPath()); + assertNull(((GdchCredentials) defaultCredentials).getApiAudience()); + + defaultCredentials = + ((GdchCredentials) defaultCredentials).createWithGdchAudience(GDCH_SA_API_AUDIENCE); + assertNotNull(defaultCredentials); + assertTrue(defaultCredentials instanceof GdchCredentials); + assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) defaultCredentials).getProjectId()); + assertEquals( + GDCH_SA_SERVICE_IDENTITY_NAME, + ((GdchCredentials) defaultCredentials).getServiceIdentityName()); + assertEquals( + GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) defaultCredentials).getTokenServerUri()); + assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) defaultCredentials).getCaCertPath()); + assertNotNull(((GdchCredentials) defaultCredentials).getApiAudience()); + } + public void getDefaultCredentials_quota_project() throws IOException { InputStream userStream = UserCredentialsTest.writeUserStream( diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java index 77f34eac6..cf778b003 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java @@ -212,7 +212,7 @@ private static GoogleCredentials getServiceAccountSourceCredentials(boolean canR ServiceAccountCredentials sourceCredentials = ServiceAccountCredentials.newBuilder() .setClientEmail(email) - .setPrivateKey(ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8)) + .setPrivateKey(OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8)) .setPrivateKeyId("privateKeyId") .setProjectId("projectId") .setHttpTransportFactory(transportFactory) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java new file mode 100644 index 000000000..84f7e7d85 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/GdchCredentialsTest.java @@ -0,0 +1,940 @@ +/* + * Copyright 2022, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.json.webtoken.JsonWebToken; +import com.google.api.client.testing.http.FixedClock; +import com.google.api.client.util.Clock; +import com.google.auth.TestUtils; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for {@link GdchCredentials}. */ +@RunWith(JUnit4.class) +public class GdchCredentialsTest extends BaseSerializationTest { + private static final String FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; + static final String PRIVATE_KEY_PKCS8 = + "-----BEGIN PRIVATE KEY-----\n" + + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12i" + + "kv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0" + + "zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw" + + "4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/Gr" + + "CtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6" + + "D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrP" + + "SXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAut" + + "LPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEA" + + "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ" + + "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ" + + "==\n-----END PRIVATE KEY-----\n"; + private static final String PROJECT_ID = "project-id"; + private static final String SERVICE_IDENTITY_NAME = "service-identity-name"; + private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; + private static final URI TOKEN_SERVER_URI = + URI.create("https://service-identity.domain/authenticate"); + private static final String CA_CERT_FILE_NAME = "cert.pem"; + private static final String CA_CERT_PATH = + GdchCredentialsTest.class.getClassLoader().getResource(CA_CERT_FILE_NAME).getPath(); + private static final URI API_AUDIENCE = URI.create("https://gdch-api-audience"); + private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); + + @Test + public void fromJSON_getProjectId() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertEquals(PROJECT_ID, credentials.getProjectId()); + } + + @Test + public void fromJSON_getServiceIdentityName() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertEquals(SERVICE_IDENTITY_NAME, credentials.getServiceIdentityName()); + } + + @Test + public void fromJSON_getCaCertPath() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertEquals(CA_CERT_PATH, credentials.getCaCertPath()); + } + + @Test + public void fromJSON_getTokenServerUri() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertEquals(TOKEN_SERVER_URI, credentials.getTokenServerUri()); + } + + @Test + public void fromJSON_nullFormatVersion() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + null, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "format_version"))); + } + } + + @Test + public void fromJSON_nullProjectId() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + null, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "project"))); + } + } + + @Test + public void fromJSON_nullPrivateKeyId() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + null, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "private_key_id"))); + } + } + + @Test + public void fromJSON_nullPrivateKey() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + null, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "private_key"))); + } + } + + @Test + public void fromJSON_nullServiceIdentityName() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + null, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "name"))); + } + } + + @Test + public void fromJSON_nullCaCertPath() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + null, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + assertNull(credentials.getCaCertPath()); + } + + @Test + public void fromJSON_nullTokenServerUri() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + null); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains( + String.format( + "Error reading GDCH service account credential from JSON, " + + "%s is misconfigured.", + "token_uri"))); + } + } + + @Test + public void fromJSON_invalidFormatVersion() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + "100", + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue( + ex.getMessage() + .contains(String.format("Only format version %s is supported", FORMAT_VERSION))); + } + } + + @Test + public void fromJSON_invalidCaCertPath() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + "/path/to/missing/file", + TOKEN_SERVER_URI); + + try { + GdchCredentials credentials = GdchCredentials.fromJson(json); + fail("Should not be able to create GDCH credential without exception."); + } catch (IOException ex) { + assertTrue(ex.getMessage().contains("Error reading certificate file from CA cert path")); + } + } + + @Test + public void fromJSON_emptyCaCertPath() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + "", + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + assertEquals("", credentials.getCaCertPath()); + } + + @Test + public void fromJSON_transportFactoryForGdch() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + assertEquals( + GdchCredentials.TransportFactoryForGdch.class, + credentials.getTransportFactory().getClass()); + } + + @Test + public void fromJSON_hasAccessToken() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + transportFactory.transport.addGdchServiceAccount( + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), ACCESS_TOKEN); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI); + Map> metadata = gdchWithAudience.getRequestMetadata(CALL_URI); + TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); + } + + @Test + public void createWithGdchAudience_correct() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + assertEquals(PROJECT_ID, credentials.getProjectId()); + assertEquals(SERVICE_IDENTITY_NAME, credentials.getServiceIdentityName()); + assertEquals(TOKEN_SERVER_URI, credentials.getTokenServerUri()); + assertEquals(CA_CERT_PATH, credentials.getCaCertPath()); + assertNull(credentials.getApiAudience()); + + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + assertEquals(PROJECT_ID, gdchWithAudience.getProjectId()); + assertEquals(SERVICE_IDENTITY_NAME, gdchWithAudience.getServiceIdentityName()); + assertEquals(TOKEN_SERVER_URI, gdchWithAudience.getTokenServerUri()); + assertEquals(CA_CERT_PATH, credentials.getCaCertPath()); + assertEquals(API_AUDIENCE, gdchWithAudience.getApiAudience()); + } + + @Test + public void createWithGdchAudience_nullApiAudience() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + + try { + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(null); + fail("Should not be able to create GDCH credential without exception."); + } catch (NullPointerException ex) { + assertTrue(ex.getMessage().contains("Audience are not configured for GDCH service account")); + } + } + + @Test + public void createAssertion_correct() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; + long currentTimeMillis = Clock.SYSTEM.currentTimeMillis(); + String assertion = credentials.createAssertion(jsonFactory, currentTimeMillis, API_AUDIENCE); + + JsonWebSignature signature = JsonWebSignature.parse(jsonFactory, assertion); + JsonWebToken.Payload payload = signature.getPayload(); + + String expectedIssSubValue = + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME); + assertEquals(expectedIssSubValue, payload.getIssuer()); + assertEquals(expectedIssSubValue, payload.getSubject()); + assertEquals(TOKEN_SERVER_URI.toString(), payload.getAudience()); + assertEquals(currentTimeMillis / 1000, (long) payload.getIssuedAtTimeSeconds()); + assertEquals(currentTimeMillis / 1000 + 3600, (long) payload.getExpirationTimeSeconds()); + } + + @Test + public void refreshAccessToken_correct() throws IOException { + final String tokenString = "1/MkSJoj1xsli0AccessToken_NKPY2"; + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + GdchCredentials gdchWithAudience = credentials.createWithGdchAudience(API_AUDIENCE); + + gdchWithAudience.clock = new FixedClock(0L); + + transportFactory.transport.addGdchServiceAccount( + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), tokenString); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI); + AccessToken accessToken = gdchWithAudience.refreshAccessToken(); + assertNotNull(accessToken); + assertEquals(tokenString, accessToken.getTokenValue()); + assertEquals(3600 * 1000L, accessToken.getExpirationTimeMillis().longValue()); + + // Test for large expires_in values (should not overflow). + transportFactory.transport.setExpiresInSeconds(3600 * 1000); + accessToken = gdchWithAudience.refreshAccessToken(); + assertNotNull(accessToken); + assertEquals(tokenString, accessToken.getTokenValue()); + assertEquals(3600 * 1000 * 1000L, accessToken.getExpirationTimeMillis().longValue()); + } + + @Test + public void refreshAccessToken_nullApiAudience() throws IOException { + final String tokenString = "1/MkSJoj1xsli0AccessToken_NKPY2"; + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + + credentials.clock = new FixedClock(0L); + + transportFactory.transport.addGdchServiceAccount( + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME), tokenString); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER_URI); + try { + AccessToken accessToken = credentials.refreshAccessToken(); + fail("Should not be able to refresh access token without exception."); + } catch (NullPointerException ex) { + assertTrue( + ex.getMessage() + .contains( + "Audience are not configured for GDCH service account. Specify the " + + "audience by calling createWithGDCHAudience")); + } + } + + @Test + public void getIssuerSubjectValue_correct() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json); + Object expectedIssSubValue = + String.format("system:serviceaccount:%s:%s", PROJECT_ID, SERVICE_IDENTITY_NAME); + assertEquals( + expectedIssSubValue, + GdchCredentials.getIssuerSubjectValue(PROJECT_ID, SERVICE_IDENTITY_NAME)); + } + + @Test + public void equals_same() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_projectId() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + "otherProjectId", + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_keyId() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + "otherId", + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_serviceIdentityName() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + "otherServiceIdentityName", + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_caCertPath() throws IOException { + File tmpDirectory = Files.createTempDirectory("tmpDirectory").toFile(); + File testCaCertFile = File.createTempFile("testCert", ".pem", tmpDirectory); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + testCaCertFile.getPath(), + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + testCaCertFile.delete(); + } + + @Test + public void equals_false_tokenServer() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + URI.create("https://foo1.com/bar")); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_apiAudience() throws IOException { + URI otherApiAudience = URI.create("https://foo1.com/bar"); + + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = + ((GdchCredentials) otherCredentials).createWithGdchAudience(otherApiAudience); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + String expectedToString = + String.format( + "GdchCredentials{projectId=%s, privateKeyId=%s, serviceIdentityName=%s, " + + "tokenServerUri=%s, transportFactoryClassName=%s, caCertPath=%s, apiAudience=%s, lifetime=3600}", + PROJECT_ID, + PRIVATE_KEY_ID, + SERVICE_IDENTITY_NAME, + TOKEN_SERVER_URI, + GdchCredentials.TransportFactoryForGdch.class.getName(), + CA_CERT_PATH, + API_AUDIENCE); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials credentials = GdchCredentials.fromJson(json); + GenericJson otherJson = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + OAuth2Credentials otherCredentials = GdchCredentials.fromJson(otherJson); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(API_AUDIENCE); + otherCredentials = ((GdchCredentials) otherCredentials).createWithGdchAudience(API_AUDIENCE); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize_correct() throws IOException, ClassNotFoundException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + GenericJson json = + writeGdchServiceAccountJson( + FORMAT_VERSION, + PROJECT_ID, + PRIVATE_KEY_ID, + PRIVATE_KEY_PKCS8, + SERVICE_IDENTITY_NAME, + CA_CERT_PATH, + TOKEN_SERVER_URI); + GdchCredentials credentials = GdchCredentials.fromJson(json, transportFactory); + credentials = credentials.createWithGdchAudience(API_AUDIENCE); + + GdchCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + assertEquals( + MockTokenServerTransportFactory.class, + deserializedCredentials.toBuilder().getHttpTransportFactory().getClass()); + } + + static GenericJson writeGdchServiceAccountJson( + String formatVersion, + String project, + String privateKeyId, + String privateKeyPkcs8, + String serviceIdentityName, + String caCertPath, + URI tokenServerUri) { + GenericJson json = new GenericJson(); + + if (formatVersion != null) { + json.put("format_version", formatVersion); + } + if (project != null) { + json.put("project", project); + } + if (privateKeyId != null) { + json.put("private_key_id", privateKeyId); + } + if (privateKeyPkcs8 != null) { + json.put("private_key", privateKeyPkcs8); + } + if (serviceIdentityName != null) { + json.put("name", serviceIdentityName); + } + if (caCertPath != null) { + json.put("ca_cert_path", caCertPath); + } + if (tokenServerUri != null) { + json.put("token_uri", tokenServerUri.toString()); + } + json.put("type", GoogleCredentials.GDCH_SERVICE_ACCOUNT_FILE_TYPE); + return json; + } + + static InputStream writeGdchServiceAccountStream( + String formatVersion, + String project, + String privateKeyId, + String privateKeyPkcs8, + String serviceIdentityName, + String caCertPath, + URI tokenServerUri) + throws IOException { + GenericJson json = + writeGdchServiceAccountJson( + formatVersion, + project, + privateKeyId, + privateKeyPkcs8, + serviceIdentityName, + caCertPath, + tokenServerUri); + return TestUtils.jsonToInputStream(json); + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index c69dfe913..2c311c481 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -69,6 +70,18 @@ public class GoogleCredentialsTest { private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; private static final String SA_PRIVATE_KEY_PKCS8 = ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8; + private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION; + private static final String GDCH_SA_PROJECT_ID = "gdch-service-account-project-id"; + private static final String GDCH_SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; + private static final String GDCH_SA_PRIVATE_KEY_PKC8 = GdchCredentialsTest.PRIVATE_KEY_PKCS8; + private static final String GDCH_SA_SERVICE_IDENTITY_NAME = + "gdch-service-account-service-identity-name"; + private static final URI GDCH_SA_TOKEN_SERVER_URI = + URI.create("https://service-identity.domain/authenticate"); + private static final String GDCH_SA_CA_CERT_FILE_NAME = "cert.pem"; + private static final String GDCH_SA_CA_CERT_PATH = + GdchCredentialsTest.class.getClassLoader().getResource(GDCH_SA_CA_CERT_FILE_NAME).getPath(); + private static final URI GDCH_API_AUDIENCE = URI.create("https://gdch-api-audience"); private static final String USER_CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu"; private static final String USER_CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"; private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY"; @@ -192,6 +205,165 @@ public void fromStream_serviceAccountNoPrivateKeyId_throws() throws IOException testFromStreamException(serviceAccountStream, "private_key_id"); } + @Test + public void fromStream_gdchServiceAccount_correct() throws IOException { + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + GoogleCredentials credentials = + GoogleCredentials.fromStream(gdchServiceAccountStream, transportFactory); + + assertNotNull(credentials); + assertTrue(credentials instanceof GdchCredentials); + assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) credentials).getProjectId()); + assertEquals( + GDCH_SA_SERVICE_IDENTITY_NAME, ((GdchCredentials) credentials).getServiceIdentityName()); + assertEquals(GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) credentials).getTokenServerUri()); + assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) credentials).getCaCertPath()); + assertNull(((GdchCredentials) credentials).getApiAudience()); + + credentials = ((GdchCredentials) credentials).createWithGdchAudience(GDCH_API_AUDIENCE); + assertNotNull(credentials); + assertTrue(credentials instanceof GdchCredentials); + assertEquals(GDCH_SA_PROJECT_ID, ((GdchCredentials) credentials).getProjectId()); + assertEquals( + GDCH_SA_SERVICE_IDENTITY_NAME, ((GdchCredentials) credentials).getServiceIdentityName()); + assertEquals(GDCH_SA_TOKEN_SERVER_URI, ((GdchCredentials) credentials).getTokenServerUri()); + assertEquals(GDCH_SA_CA_CERT_PATH, ((GdchCredentials) credentials).getCaCertPath()); + assertNotNull(((GdchCredentials) credentials).getApiAudience()); + } + + @Test + public void fromStream_gdchServiceAccountNoFormatVersion_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + null, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException(gdchServiceAccountStream, "format_version"); + } + + @Test + public void fromStream_gdchServiceAccountNoProjectId_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + null, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException(gdchServiceAccountStream, "project"); + } + + @Test + public void fromStream_gdchServiceAccountNoPrivateKeyId_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + null, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException(gdchServiceAccountStream, "private_key_id"); + } + + @Test + public void fromStream_gdchServiceAccountNoPrivateKey_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + null, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException(gdchServiceAccountStream, "private_key"); + } + + @Test + public void fromStream_gdchServiceAccountNoServiceIdentityName_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + null, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException(gdchServiceAccountStream, "name"); + } + + @Test + public void fromStream_gdchServiceAccountNoTokenServerUri_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + null); + + testFromStreamException(gdchServiceAccountStream, "token_uri"); + } + + @Test + public void fromStream_gdchServiceAccountInvalidFormatVersion_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + "100", + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + GDCH_SA_CA_CERT_PATH, + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException( + gdchServiceAccountStream, + String.format("Only format version %s is supported", GDCH_SA_FORMAT_VERSION)); + } + + @Test + public void fromStream_gdchServiceAccountInvalidCaCertPath_throws() throws IOException { + InputStream gdchServiceAccountStream = + GdchCredentialsTest.writeGdchServiceAccountStream( + GDCH_SA_FORMAT_VERSION, + GDCH_SA_PROJECT_ID, + GDCH_SA_PRIVATE_KEY_ID, + GDCH_SA_PRIVATE_KEY_PKC8, + GDCH_SA_SERVICE_IDENTITY_NAME, + "/path/to/missing/file", + GDCH_SA_TOKEN_SERVER_URI); + + testFromStreamException( + gdchServiceAccountStream, + String.format("Error reading certificate file from CA cert path")); + } + @Test public void fromStream_user_providesToken() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 8a3819d53..9ff555a47 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -162,7 +162,7 @@ public void setup() throws IOException { private GoogleCredentials getSourceCredentials() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountCredentials sourceCredentials = ServiceAccountCredentials.newBuilder() .setClientEmail(SA_CLIENT_EMAIL) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java index 1c934a8f4..900245492 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/JwtCredentialsTest.java @@ -73,7 +73,7 @@ public class JwtCredentialsTest extends BaseSerializationTest { static PrivateKey getPrivateKey() { try { - return ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY); + return OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY); } catch (IOException ex) { return null; } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java index 297b497b4..2bff4b3b4 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java @@ -57,12 +57,12 @@ public class MockTokenServerTransport extends MockHttpTransport { public static final String REFRESH_TOKEN_WITH_USER_SCOPE = "refresh_token_with_user.email_scope"; - static final String EXPECTED_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; static final JsonFactory JSON_FACTORY = new GsonFactory(); int buildRequestCount; final Map clients = new HashMap(); final Map refreshTokens = new HashMap(); final Map serviceAccounts = new HashMap(); + final Map gdchServiceAccounts = new HashMap(); final Map codes = new HashMap(); URI tokenServerUri = OAuth2Utils.TOKEN_SERVER_URI; private IOException error; @@ -96,6 +96,10 @@ public void addServiceAccount(String email, String accessToken) { serviceAccounts.put(email, accessToken); } + public void addGdchServiceAccount(String serviceIdentityName, String accessToken) { + gdchServiceAccounts.put(serviceIdentityName, accessToken); + } + public String getAccessToken(String refreshToken) { return refreshTokens.get(refreshToken); } @@ -170,7 +174,7 @@ public LowLevelHttpResponse execute() throws IOException { String content = this.getContentAsString(); Map query = TestUtils.parseQuery(content); - String accessToken; + String accessToken = null; String refreshToken = null; boolean generateAccessToken = true; @@ -204,28 +208,40 @@ public LowLevelHttpResponse execute() throws IOException { accessToken = refreshTokens.get(refreshToken); } else if (query.containsKey("grant_type")) { String grantType = query.get("grant_type"); - if (!EXPECTED_GRANT_TYPE.equals(grantType)) { - throw new IOException("Unexpected Grant Type."); - } String assertion = query.get("assertion"); JsonWebSignature signature = JsonWebSignature.parse(JSON_FACTORY, assertion); - String foundEmail = signature.getPayload().getIssuer(); - if (!serviceAccounts.containsKey(foundEmail)) { - throw new IOException("Service Account Email not found as issuer."); - } - accessToken = serviceAccounts.get(foundEmail); - String foundTargetAudience = (String) signature.getPayload().get("target_audience"); - String foundScopes = (String) signature.getPayload().get("scope"); - if ((foundScopes == null || foundScopes.length() == 0) - && (foundTargetAudience == null || foundTargetAudience.length() == 0)) { - throw new IOException("Either target_audience or scopes must be specified."); - } + if (OAuth2Utils.GRANT_TYPE_JWT_BEARER.equals(grantType)) { + String foundEmail = signature.getPayload().getIssuer(); + if (!serviceAccounts.containsKey(foundEmail)) {} + accessToken = serviceAccounts.get(foundEmail); + String foundTargetAudience = (String) signature.getPayload().get("target_audience"); + String foundScopes = (String) signature.getPayload().get("scope"); + if ((foundScopes == null || foundScopes.length() == 0) + && (foundTargetAudience == null || foundTargetAudience.length() == 0)) { + throw new IOException("Either target_audience or scopes must be specified."); + } - if (foundScopes != null && foundTargetAudience != null) { - throw new IOException("Only one of target_audience or scopes must be specified."); - } - if (foundTargetAudience != null) { - generateAccessToken = false; + if (foundScopes != null && foundTargetAudience != null) { + throw new IOException("Only one of target_audience or scopes must be specified."); + } + if (foundTargetAudience != null) { + generateAccessToken = false; + } + + // For GDCH scenario + } else if (OAuth2Utils.TOKEN_TYPE_TOKEN_EXCHANGE.equals(grantType)) { + String foundServiceIdentityName = signature.getPayload().getIssuer(); + if (!gdchServiceAccounts.containsKey(foundServiceIdentityName)) { + throw new IOException( + "GDCH Service Account Service Identity Name not found as issuer."); + } + accessToken = gdchServiceAccounts.get(foundServiceIdentityName); + String foundApiAudience = (String) signature.getPayload().get("api_audience"); + if ((foundApiAudience == null || foundApiAudience.length() == 0)) { + throw new IOException("Api_audience must be specified."); + } + } else { + throw new IOException("Service Account Email not found as issuer."); } } else { throw new IOException("Unknown token type."); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java index f0c95ef6b..14eb16b92 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java @@ -124,7 +124,7 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest { private static final String JWT_ACCESS_PREFIX = "Bearer "; private ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); return ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) .setClientEmail(CLIENT_EMAIL) @@ -171,7 +171,7 @@ public void createWithCustomLifetime() throws IOException { @Test public void createdScoped_clones() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -201,7 +201,7 @@ public void createdScoped_clones() throws IOException { @Test public void createdDelegated_clones() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -232,7 +232,7 @@ public void createdDelegated_clones() throws IOException { @Test public void createAssertion_correct() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); List scopes = Arrays.asList("scope1", "scope2"); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() @@ -261,7 +261,7 @@ public void createAssertion_correct() throws IOException { @Test public void createAssertion_defaultScopes_correct() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); List scopes = Arrays.asList("scope1", "scope2"); ServiceAccountCredentials.Builder builder = ServiceAccountCredentials.newBuilder() @@ -305,7 +305,7 @@ public void createAssertion_custom_lifetime() throws IOException { @Test public void createAssertionForIdToken_correct() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -350,7 +350,7 @@ public void createAssertionForIdToken_custom_lifetime() throws IOException { @Test public void createAssertionForIdToken_incorrect() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); ServiceAccountCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1364,7 +1364,7 @@ public void getRequestMetadataSetsQuotaProjectId() throws IOException { transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1391,7 +1391,7 @@ public void getRequestMetadataNoQuotaProjectId() throws IOException { transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1414,7 +1414,7 @@ public void getRequestMetadataWithCallback() throws IOException { transportFactory.transport.addClient(CLIENT_ID, "unused-client-secret"); transportFactory.transport.addServiceAccount(CLIENT_EMAIL, ACCESS_TOKEN); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1451,7 +1451,7 @@ public void onFailure(Throwable exception) { @Test public void getRequestMetadata_selfSignedJWT_withScopes() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1474,7 +1474,7 @@ public void refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled() thr final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); MockTokenServerTransport transport = transportFactory.transport; - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1506,7 +1506,7 @@ public void refreshAccessToken_withDomainDelegation_selfSignedJWT_disabled() thr @Test public void getRequestMetadata_selfSignedJWT_withAudience() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1523,7 +1523,7 @@ public void getRequestMetadata_selfSignedJWT_withAudience() throws IOException { @Test public void getRequestMetadata_selfSignedJWT_withDefaultScopes() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) @@ -1542,7 +1542,7 @@ public void getRequestMetadata_selfSignedJWT_withDefaultScopes() throws IOExcept @Test public void getRequestMetadataWithCallback_selfSignedJWT() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8); GoogleCredentials credentials = ServiceAccountCredentials.newBuilder() .setClientId(CLIENT_ID) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java index 5020317f2..3e03c0368 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java @@ -97,7 +97,7 @@ public class ServiceAccountJwtAccessCredentialsTest extends BaseSerializationTes @Test public void constructor_allParameters_constructs() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -116,7 +116,7 @@ public void constructor_allParameters_constructs() throws IOException { @Test public void constructor_noClientId_constructs() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials.newBuilder() .setClientEmail(SA_CLIENT_EMAIL) .setPrivateKey(privateKey) @@ -126,7 +126,7 @@ public void constructor_noClientId_constructs() throws IOException { @Test public void constructor_noPrivateKeyId_constructs() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) .setClientEmail(SA_CLIENT_EMAIL) @@ -136,7 +136,7 @@ public void constructor_noPrivateKeyId_constructs() throws IOException { @Test public void constructor_noEmail_throws() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); try { ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -189,7 +189,7 @@ public void hasRequestMetadataOnly_returnsTrue() throws IOException { @Test public void getRequestMetadata_blocking_hasJwtAccess() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -205,7 +205,7 @@ public void getRequestMetadata_blocking_hasJwtAccess() throws IOException { @Test public void getRequestMetadata_blocking_defaultURI_hasJwtAccess() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); Credentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -222,7 +222,7 @@ public void getRequestMetadata_blocking_defaultURI_hasJwtAccess() throws IOExcep @Test public void getRequestMetadata_blocking_noURI_throws() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -243,7 +243,7 @@ public void getRequestMetadata_blocking_noURI_throws() throws IOException { public void getRequestMetadata_blocking_cached() throws IOException { TestClock testClock = new TestClock(); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -268,7 +268,7 @@ public void getRequestMetadata_blocking_cached() throws IOException { public void getRequestMetadata_blocking_cache_expired() throws IOException { TestClock testClock = new TestClock(); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -291,7 +291,7 @@ public void getRequestMetadata_blocking_cache_expired() throws IOException { @Test public void getRequestMetadata_async_hasJwtAccess() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -310,7 +310,7 @@ public void getRequestMetadata_async_hasJwtAccess() throws IOException { @Test public void getRequestMetadata_async_defaultURI_hasJwtAccess() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); Credentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -330,7 +330,7 @@ public void getRequestMetadata_async_defaultURI_hasJwtAccess() throws IOExceptio @Test public void getRequestMetadata_async_noURI_exception() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -350,7 +350,7 @@ public void getRequestMetadata_async_noURI_exception() throws IOException { public void getRequestMetadata_async_cache_expired() throws IOException { TestClock testClock = new TestClock(); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -378,7 +378,7 @@ public void getRequestMetadata_async_cache_expired() throws IOException { public void getRequestMetadata_async_cached() throws IOException { TestClock testClock = new TestClock(); - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -404,7 +404,7 @@ public void getRequestMetadata_async_cached() throws IOException { @Test public void getRequestMetadata_contains_quotaProjectId() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); Credentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -424,7 +424,7 @@ public void getRequestMetadata_contains_quotaProjectId() throws IOException { @Test public void getAccount_sameAs() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -438,7 +438,7 @@ public void getAccount_sameAs() throws IOException { @Test public void sign_sameAs() throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); byte[] toSign = {0xD, 0xE, 0xA, 0xD}; ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() @@ -456,7 +456,7 @@ public void sign_sameAs() @Test public void equals_true() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -479,7 +479,7 @@ public void equals_true() throws IOException { @Test public void equals_false_clientId() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -502,7 +502,7 @@ public void equals_false_clientId() throws IOException { @Test public void equals_false_email() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -525,7 +525,7 @@ public void equals_false_email() throws IOException { @Test public void equals_false_keyId() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -548,7 +548,7 @@ public void equals_false_keyId() throws IOException { @Test public void equals_false_callUri() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); final URI otherCallUri = URI.create("https://foo.com/bar"); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() @@ -572,7 +572,7 @@ public void equals_false_callUri() throws IOException { @Test public void toString_containsFields() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -592,7 +592,7 @@ public void toString_containsFields() throws IOException { @Test public void hashCode_equals() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -614,7 +614,7 @@ public void hashCode_equals() throws IOException { @Test public void serialize() throws IOException, ClassNotFoundException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -709,7 +709,7 @@ public void fromStream_noPrivateKeyId_throws() throws IOException { @Test public void jwtWithClaims_overrideAudience() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -727,7 +727,7 @@ public void jwtWithClaims_overrideAudience() throws IOException { @Test public void jwtWithClaims_noAudience() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -745,7 +745,7 @@ public void jwtWithClaims_noAudience() throws IOException { @Test public void jwtWithClaims_defaultAudience() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -762,7 +762,7 @@ public void jwtWithClaims_defaultAudience() throws IOException { @Test public void getRequestMetadataSetsQuotaProjectId() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -782,7 +782,7 @@ public void getRequestMetadataSetsQuotaProjectId() throws IOException { @Test public void getRequestMetadataNoQuotaProjectId() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) @@ -798,7 +798,7 @@ public void getRequestMetadataNoQuotaProjectId() throws IOException { @Test public void getRequestMetadataWithCallback() throws IOException { - PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); ServiceAccountJwtAccessCredentials credentials = ServiceAccountJwtAccessCredentials.newBuilder() .setClientId(SA_CLIENT_ID) diff --git a/oauth2_http/testresources/cert.pem b/oauth2_http/testresources/cert.pem new file mode 100644 index 000000000..298d9582f --- /dev/null +++ b/oauth2_http/testresources/cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL +MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC +VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx +NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD +TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu +ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j +V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj +gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA +FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE +CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS +BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE +BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju +Wm7DCfrPNGVwFWUQOmsPue9rZBgO +-----END CERTIFICATE----- \ No newline at end of file