-
Notifications
You must be signed in to change notification settings - Fork 261
feat: Adds an X509 certificate provider in the auth libraries. #1624
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
bc27d41
Adds an X509 certificate provider in the auth libraries.
aeitzman 111f2f1
temporarily remove test case
aeitzman 27c497e
Merge branch 'x509' into x509-java
aeitzman 3756021
fixing formatting
aeitzman 74aaca6
Responding to comments and updating tests
aeitzman c544c88
lint fix
aeitzman c54fedb
Update oauth2_http/java/com/google/auth/mtls/X509Provider.java
aeitzman 889015c
responding to review comments
aeitzman b46d2a1
formatting and changing the tests to use assertThrows
aeitzman b7903d0
Responding to PR comments and adding a new exception type.
aeitzman 008425d
removed catch block
aeitzman 2a21fe4
Add comment and fix test asserts
aeitzman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
oauth2_http/java/com/google/auth/mtls/CertificateSourceUnavailableException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* | ||
| * Copyright 2025, 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.mtls; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| /** | ||
| * This exception is thrown by certificate providers in the Google auth library when the certificate | ||
| * source is unavailable. This means that the transport layer should move on to the next certificate | ||
| * source provider type. | ||
| */ | ||
| public class CertificateSourceUnavailableException extends IOException { | ||
|
|
||
| /** | ||
| * Constructor with a message and throwable cause. | ||
| * | ||
| * @param message The detail message (which is saved for later retrieval by the {@link | ||
| * #getMessage()} method) | ||
| * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). | ||
| * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) | ||
| */ | ||
| public CertificateSourceUnavailableException(String message, Throwable cause) { | ||
| super(message, cause); | ||
| } | ||
|
|
||
| /** | ||
| * Constructor with a throwable cause. | ||
| * | ||
| * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). | ||
| * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) | ||
| */ | ||
| public CertificateSourceUnavailableException(Throwable cause) { | ||
| super(cause); | ||
| } | ||
|
|
||
| /** | ||
| * Constructor with a message. | ||
| * | ||
| * @param message The detail message (which is saved for later retrieval by the {@link | ||
| * #getMessage()} method) | ||
| */ | ||
| public CertificateSourceUnavailableException(String message) { | ||
| super(message); | ||
| } | ||
| } |
99 changes: 99 additions & 0 deletions
99
oauth2_http/java/com/google/auth/mtls/WorkloadCertificateConfiguration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| /* | ||
| * Copyright 2025, 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.mtls; | ||
|
|
||
| import com.google.api.client.json.GenericJson; | ||
| 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.common.base.Preconditions; | ||
| import com.google.common.base.Strings; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Map; | ||
|
|
||
| class WorkloadCertificateConfiguration { | ||
|
|
||
| private String certPath; | ||
| private String privateKeyPath; | ||
|
|
||
| private static JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); | ||
| private static JsonObjectParser parser = new JsonObjectParser(jsonFactory); | ||
|
|
||
| WorkloadCertificateConfiguration(String certPath, String privateKeyPath) { | ||
| this.certPath = certPath; | ||
| this.privateKeyPath = privateKeyPath; | ||
| } | ||
|
|
||
| String getCertPath() { | ||
| return certPath; | ||
| } | ||
|
|
||
| String getPrivateKeyPath() { | ||
| return privateKeyPath; | ||
| } | ||
|
|
||
| static WorkloadCertificateConfiguration fromCertificateConfigurationStream( | ||
| InputStream certConfigStream) throws IOException { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Preconditions.checkNotNull(certConfigStream); | ||
|
|
||
| GenericJson fileContents = | ||
| parser.parseAndClose(certConfigStream, StandardCharsets.UTF_8, GenericJson.class); | ||
|
|
||
| Map<String, Object> certConfigs = (Map<String, Object>) fileContents.get("cert_configs"); | ||
aeitzman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (certConfigs == null) { | ||
| throw new IllegalArgumentException( | ||
| "The cert_configs object must be provided in the certificate configuration file."); | ||
| } | ||
|
|
||
| Map<String, Object> workloadConfig = (Map<String, Object>) certConfigs.get("workload"); | ||
| if (workloadConfig == null) { | ||
| throw new CertificateSourceUnavailableException( | ||
| "A workload certificate configuration must be provided in the cert_configs object."); | ||
| } | ||
aeitzman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| String certPath = (String) workloadConfig.get("cert_path"); | ||
| if (Strings.isNullOrEmpty(certPath)) { | ||
| throw new IllegalArgumentException( | ||
| "The cert_path field must be provided in the workload certificate configuration."); | ||
| } | ||
|
|
||
| String privateKeyPath = (String) workloadConfig.get("key_path"); | ||
| if (Strings.isNullOrEmpty(privateKeyPath)) { | ||
| throw new IllegalArgumentException( | ||
| "The key_path field must be provided in the workload certificate configuration."); | ||
| } | ||
|
|
||
| return new WorkloadCertificateConfiguration(certPath, privateKeyPath); | ||
| } | ||
| } | ||
207 changes: 207 additions & 0 deletions
207
oauth2_http/java/com/google/auth/mtls/X509Provider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| /* | ||
| * Copyright 2025, 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.mtls; | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import com.google.api.client.util.SecurityUtils; | ||
| import com.google.common.base.Strings; | ||
| import java.io.File; | ||
| import java.io.FileInputStream; | ||
| import java.io.FileNotFoundException; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.SequenceInputStream; | ||
| import java.security.KeyStore; | ||
| import java.util.Locale; | ||
|
|
||
| /** | ||
| * This class provides certificate key stores to the Google Auth library transport layer via | ||
| * certificate configuration files. This is only meant to be used internally to Google Cloud | ||
| * libraries, and the public facing methods may be changed without notice, and have no guarantee of | ||
| * backwards compatability. | ||
| */ | ||
| public class X509Provider { | ||
aeitzman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| static final String CERTIFICATE_CONFIGURATION_ENV_VARIABLE = "GOOGLE_API_CERTIFICATE_CONFIG"; | ||
| static final String WELL_KNOWN_CERTIFICATE_CONFIG_FILE = "certificate_config.json"; | ||
| static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud"; | ||
|
|
||
| private String certConfigPathOverride; | ||
|
|
||
| /** | ||
| * Creates an X509 provider with an override path for the certificate configuration, bypassing the | ||
| * normal checks for the well known certificate configuration file path and environment variable. | ||
| * This is meant for internal Google Cloud usage and behavior may be changed without warning. | ||
| * | ||
| * @param certConfigPathOverride the path to read the certificate configuration from. | ||
| */ | ||
| public X509Provider(String certConfigPathOverride) { | ||
| this.certConfigPathOverride = certConfigPathOverride; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new X.509 provider that will check the environment variable path and the well known | ||
| * Gcloud certificate configuration location. This is meant for internal Google Cloud usage and | ||
| * behavior may be changed without warning. | ||
| */ | ||
| public X509Provider() { | ||
| this(null); | ||
| } | ||
|
|
||
| /** | ||
| * Finds the certificate configuration file, then builds a Keystore using the X.509 certificate | ||
| * and private key pointed to by the configuration. This will check the following locations in | ||
| * order. | ||
| * | ||
| * <ul> | ||
| * <li>The certificate config override path, if set. | ||
| * <li>The path pointed to by the "GOOGLE_API_CERTIFICATE_CONFIG" environment variable | ||
| * <li>The well known gcloud location for the certificate configuration file. | ||
| * </ul> | ||
| * | ||
| * @return a KeyStore containing the X.509 certificate specified by the certificate configuration. | ||
| * @throws IOException if there is an error retrieving the certificate configuration. | ||
| */ | ||
| public KeyStore getKeyStore() throws IOException { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| WorkloadCertificateConfiguration workloadCertConfig = getWorkloadCertificateConfiguration(); | ||
|
|
||
| InputStream certStream = null; | ||
| InputStream privateKeyStream = null; | ||
| SequenceInputStream certAndPrivateKeyStream = null; | ||
| try { | ||
| // Read the certificate and private key file paths into separate streams. | ||
| File certFile = new File(workloadCertConfig.getCertPath()); | ||
| File privateKeyFile = new File(workloadCertConfig.getPrivateKeyPath()); | ||
| certStream = createInputStream(certFile); | ||
| privateKeyStream = createInputStream(privateKeyFile); | ||
|
|
||
| // Merge the two streams into a single stream. | ||
| certAndPrivateKeyStream = new SequenceInputStream(certStream, privateKeyStream); | ||
|
|
||
| // Build a key store using the combined stream. | ||
| return SecurityUtils.createMtlsKeyStore(certAndPrivateKeyStream); | ||
| } catch (CertificateSourceUnavailableException e) { | ||
| // Throw the CertificateSourceUnavailableException without wrapping. | ||
| throw e; | ||
| } catch (Exception e) { | ||
| // Wrap all other exception types to an IOException. | ||
| throw new IOException(e); | ||
| } finally { | ||
| if (certStream != null) { | ||
| certStream.close(); | ||
| } | ||
| if (privateKeyStream != null) { | ||
| privateKeyStream.close(); | ||
| } | ||
| if (certAndPrivateKeyStream != null) { | ||
| certAndPrivateKeyStream.close(); | ||
| } | ||
| } | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private WorkloadCertificateConfiguration getWorkloadCertificateConfiguration() | ||
| throws IOException { | ||
| File certConfig; | ||
| if (this.certConfigPathOverride != null) { | ||
| certConfig = new File(certConfigPathOverride); | ||
| } else { | ||
| String envCredentialsPath = getEnv(CERTIFICATE_CONFIGURATION_ENV_VARIABLE); | ||
| if (!Strings.isNullOrEmpty(envCredentialsPath)) { | ||
| certConfig = new File(envCredentialsPath); | ||
| } else { | ||
| certConfig = getWellKnownCertificateConfigFile(); | ||
| } | ||
| } | ||
| InputStream certConfigStream = null; | ||
| try { | ||
| if (!isFile(certConfig)) { | ||
andyrzhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Path will be put in the message from the catch block below | ||
| throw new IOException("File does not exist."); | ||
| } | ||
| certConfigStream = createInputStream(certConfig); | ||
| return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); | ||
| } catch (Exception e) { | ||
| // Although it is also the cause, the message of the caught exception can have very | ||
| // important information for diagnosing errors, so include its message in the | ||
| // outer exception message also. | ||
| throw new CertificateSourceUnavailableException( | ||
aeitzman marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| String.format( | ||
| "Error reading certificate configuration file value '%s': %s", | ||
| certConfig.getPath(), e.getMessage()), | ||
| e); | ||
| } finally { | ||
| if (certConfigStream != null) { | ||
| certConfigStream.close(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Start of methods to allow overriding in the test code to isolate from the environment. | ||
| */ | ||
| boolean isFile(File file) { | ||
| return file.isFile(); | ||
andyrzhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| InputStream createInputStream(File file) throws FileNotFoundException { | ||
| return new FileInputStream(file); | ||
| } | ||
|
|
||
| String getEnv(String name) { | ||
| return System.getenv(name); | ||
| } | ||
|
|
||
| String getOsName() { | ||
| return getProperty("os.name", "").toLowerCase(Locale.US); | ||
| } | ||
|
|
||
| String getProperty(String property, String def) { | ||
| return System.getProperty(property, def); | ||
| } | ||
| /* | ||
| * End of methods to allow overriding in the test code to isolate from the environment. | ||
| */ | ||
|
|
||
| private File getWellKnownCertificateConfigFile() { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| File cloudConfigPath; | ||
| String envPath = getEnv("CLOUDSDK_CONFIG"); | ||
| if (envPath != null) { | ||
| cloudConfigPath = new File(envPath); | ||
| } else if (getOsName().indexOf("windows") >= 0) { | ||
| File appDataPath = new File(getEnv("APPDATA")); | ||
| cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY); | ||
| } else { | ||
| File configPath = new File(getProperty("user.home", ""), ".config"); | ||
| cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); | ||
| } | ||
| return new File(cloudConfigPath, WELL_KNOWN_CERTIFICATE_CONFIG_FILE); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.