Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add GDCH support #1087

Merged
merged 27 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83e64aa
feat: add GDCH support
poanc Nov 9, 2022
e58ca72
feat: add GDCH support
poanc Nov 9, 2022
e27297a
Add additional test cases.
poanc Nov 10, 2022
c324427
fix the format
poanc Nov 10, 2022
11269df
change the grant_type to token exchange type
poanc Nov 11, 2022
9284756
add more unit tests
poanc Nov 12, 2022
1c4c14f
remove implementing JwtProvider from GdchCredentials class
poanc Nov 12, 2022
ec8e36f
address the comments
poanc Nov 15, 2022
fd85a6a
add custom httpTransport for ca cert
poanc Nov 17, 2022
b63b9b5
add more unit test
poanc Nov 21, 2022
8b4c153
add more unit test
poanc Nov 21, 2022
75d9781
add an overload method for fromJSON in GdchCredentials
poanc Nov 23, 2022
79705c0
add more unit test
poanc Nov 23, 2022
c013f0d
address the comments
poanc Nov 29, 2022
c2b5ed1
fix test cert path
poanc Nov 29, 2022
1ae23b8
remove `privateKeyFromPkcs8` from ServiceAccountCredentials and refer…
poanc Dec 1, 2022
1d4c27c
Merge branch 'main' into gdch
poanc Dec 1, 2022
342b6eb
add a javadoc for `TransportFactoryForGdch`
poanc Dec 1, 2022
826663e
Merge branch 'gdch' of github.com:poanc/google-auth-library-java into…
poanc Dec 1, 2022
0dfc118
Merge branch 'main' into gdch
TimurSadykov Dec 2, 2022
f22d282
change `ca_cert_path` to optional
poanc Dec 3, 2022
5932e45
Merge branch 'gdch' of github.com:poanc/google-auth-library-java into…
poanc Dec 3, 2022
7e045e9
revert ca cert path to required
poanc Dec 4, 2022
dbbfc57
Merge branch 'main' into gdch
poanc Dec 4, 2022
2e6c280
fix the format
poanc Dec 5, 2022
e3b2a76
Merge branch 'gdch' of github.com:poanc/google-auth-library-java into…
poanc Dec 5, 2022
5b67c95
revert back to ca cert path optional
poanc Dec 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
522 changes: 522 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/GdchCredentials.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
Expand Down
31 changes: 31 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -202,5 +214,24 @@ static Map<String, Object> validateMap(Map<String, Object> 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 {
poanc marked this conversation as resolved.
Show resolved Hide resolved
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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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";
Expand Down Expand Up @@ -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);
poanc marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down