diff --git a/.github/release-please.yml b/.github/release-please.yml index ef849857b..b37bc5574 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -33,4 +33,8 @@ branches: - bumpMinorPreMajor: true handleGHRelease: true releaseType: java-backport - branch: 1.23.x \ No newline at end of file + branch: 1.23.x + - bumpMinorPreMajor: true + handleGHRelease: true + releaseType: java-backport + branch: 1.33.x diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 7cf623cb8..cfd6a11b9 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -117,6 +117,18 @@ branchProtectionRules: requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true requiresStrictStatusChecks: true + - pattern: 1.33.x + isAdminEnforced: true + requiredStatusCheckContexts: + - dependencies (17) + - lint + - clirr + - units (8) + - units (11) + - cla/google + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: true permissionRules: - team: Googlers permission: pull diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 005ae5177..71acfd403 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -120,7 +120,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: temurin - java-version: 11 + java-version: 17 - run: java -version - run: .kokoro/build.sh env: diff --git a/.github/workflows/members.json b/.github/workflows/members.json index 7bdb8ff60..c75f18637 100644 --- a/.github/workflows/members.json +++ b/.github/workflows/members.json @@ -20,7 +20,6 @@ {"login":"JoeWang1127"}, {"login":"kmaydeo"}, {"login":"lsirac"}, -{"login":"aeitzman"}, {"login":"sakthivelmanii"}, {"login":"rahul2393"}, {"login":"surbhigarg92"}, @@ -124,5 +123,10 @@ {"login":"CadillacBurgess1"}, {"login":"julieqiu"}, {"login":"coryan"}, -{"login":"devoncarew"} +{"login":"devoncarew"}, +{"login":"sai-sunder-s"}, +{"login":"harkamaljot"}, +{"login":"mrfaizal"}, +{"login":"suzmue"}, +{"login":"viacheslav-rostovtsev"} ] diff --git a/.github/workflows/multi_approvers.yaml b/.github/workflows/multi_approvers.yaml index c4fad51e7..e03cdd0f3 100644 --- a/.github/workflows/multi_approvers.yaml +++ b/.github/workflows/multi_approvers.yaml @@ -26,6 +26,6 @@ concurrency: jobs: multi-approvers: - uses: 'abcxyz/pkg/.github/workflows/multi-approvers.yml@main' + uses: 'abcxyz/pkg/.github/workflows/multi-approvers.yml@1d1cedf7768d17dde23bb2cda24bc1fb950e9f92' with: org-members-path: 'googleapis/google-auth-library-java/main/.github/workflows/members.json' diff --git a/.kokoro/build.sh b/.kokoro/build.sh index ef485eac0..68a12a82f 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -57,7 +57,7 @@ test-logging) RETURN_CODE=$? ;; lint) - mvn com.coveo:fmt-maven-plugin:check -B -ntp + mvn com.spotify.fmt:fmt-maven-plugin:check -B -ntp RETURN_CODE=$? ;; javadoc) @@ -76,14 +76,7 @@ integration) verify RETURN_CODE=$? ;; -graalvmA) - # Run Unit and Integration Tests with Native Image - bash .kokoro/populate-secrets.sh - export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account" - mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test -Pslf4j2x test -pl 'oauth2_http' - RETURN_CODE=$? - ;; -graalvmB) +graalvm) # Run Unit and Integration Tests with Native Image bash .kokoro/populate-secrets.sh export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account" diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg index 760a2c852..9446ae50f 100644 --- a/.kokoro/presubmit/graalvm-native-a.cfg +++ b/.kokoro/presubmit/graalvm-native-a.cfg @@ -4,7 +4,7 @@ build_file: "google-auth-library-java/.kokoro/build.sh" env_vars: { key: "JOB_TYPE" - value: "graalvmA" + value: "graalvm" } # TODO: remove this after we've migrated all tests and scripts @@ -44,6 +44,6 @@ env_vars: { } container_properties { - docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_a:1.14.4" + docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_a:1.15.4" } diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg index 479ea2476..72933832e 100644 --- a/.kokoro/presubmit/graalvm-native-b.cfg +++ b/.kokoro/presubmit/graalvm-native-b.cfg @@ -4,7 +4,7 @@ build_file: "google-auth-library-java/.kokoro/build.sh" env_vars: { key: "JOB_TYPE" - value: "graalvmB" + value: "graalvm" } # TODO: remove this after we've migrated all tests and scripts @@ -44,5 +44,5 @@ env_vars: { } container_properties { - docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_b:1.14.4" + docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_b:1.15.4" } diff --git a/.kokoro/presubmit/graalvm-native-c.cfg b/.kokoro/presubmit/graalvm-native-c.cfg new file mode 100644 index 000000000..5489037be --- /dev/null +++ b/.kokoro/presubmit/graalvm-native-c.cfg @@ -0,0 +1,49 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "google-auth-library-java/.kokoro/build.sh" + +env_vars: { + key: "JOB_TYPE" + value: "graalvm" +} + +# TODO: remove this after we've migrated all tests and scripts +env_vars: { + key: "GCLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "secret_manager/java-it-service-account" +} + +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "java-it-service-account" +} + +env_vars: { + key: "GCS_BUCKET" + value: "byoid-it-bucket" +} + +env_vars: { + key: "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" + value: "1" +} + +env_vars: { + key: "GOOGLE_CLOUD_QUOTA_PROJECT" + value: "gcloud-devel" +} + +container_properties { + docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_c:1.15.4" +} + diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d342df8bc..257e308d6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.33.1" + ".": "1.34.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b37c7b2..305f35bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.34.0](https://github.com/googleapis/google-auth-library-java/compare/v1.33.1...v1.34.0) (2025-04-29) + + +### Features + +* Implement X509 certificate provider ([#1722](https://github.com/googleapis/google-auth-library-java/issues/1722)) ([4340684](https://github.com/googleapis/google-auth-library-java/commit/4340684fe29c9e9bffa90e88d0b1746f19b623ab)) +* Next release from main branch is 1.34.0 ([#1698](https://github.com/googleapis/google-auth-library-java/issues/1698)) ([fe43815](https://github.com/googleapis/google-auth-library-java/commit/fe4381513db1340190c4309a53c6265718682dde)) +* Next release from main branch is 1.34.0 ([#1702](https://github.com/googleapis/google-auth-library-java/issues/1702)) ([4507cf9](https://github.com/googleapis/google-auth-library-java/commit/4507cf9e17e7ff40cf142056d3929c87f5742dd1)) + + +### Bug Fixes + +* Do not add padding in Client-Side CAB tokens. ([#1728](https://github.com/googleapis/google-auth-library-java/issues/1728)) ([8a75ccd](https://github.com/googleapis/google-auth-library-java/commit/8a75ccd1c09191abd8ebf463bc41810a38e185f5)) + ## [1.33.1](https://github.com/googleapis/google-auth-library-java/compare/v1.33.0...v1.33.1) (2025-02-25) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b65dd279c..ff092b68e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,7 @@ Code in this repo is formatted with [google-java-format](https://github.com/google/google-java-format). To run formatting on your project, you can run: ``` -mvn com.coveo:fmt-maven-plugin:format +mvn com.spotify.fmt:fmt-maven-plugin:format ``` [1]: https://cloud.google.com/docs/authentication/getting-started#creating_a_service_account diff --git a/README.md b/README.md index 1658c6619..9cc6ead7d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ information, refer to [documentation](https://cloud.google.com/docs/authenticati * [Gradle](#gradle) * [Scala](#scala) * [Migrating from GoogleCredential to GoogleCredentials](#migrating-from-googlecredential-to-googlecredentials) +* [Troubleshooting](#troubleshooting) * [google-auth-library-oauth2-http](#google-auth-library-oauth2-http) * [Application Default Credentials](#application-default-credentials) * [ImpersonatedCredentials](#impersonatedcredentials) @@ -171,6 +172,9 @@ For [Google Api Client Library](https://cloud.google.com/apis/docs/client-librar For [Cloud Client Libraries](https://cloud.google.com/apis/docs/client-libraries-explained#cloud-client-libraries), the library will follow ADC to create a default GoogleCredential. Users do not need to manually create any Credentials or pass it into the library. +### Troubleshooting +This library provides logging for debugging purposes. Please refer to [this guide](https://cloud.google.com/java/docs/bom#client_library_debug_logging) to enable debug logging feature. + ## google-auth-library-oauth2-http ### Application Default Credentials diff --git a/appengine/pom.xml b/appengine/pom.xml index 44eac0c3e..b4bfb0fd0 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -5,7 +5,7 @@ com.google.auth google-auth-library-parent - 1.33.1 + 1.34.0 ../pom.xml diff --git a/bom/pom.xml b/bom/pom.xml index fd7ee4bdf..1d13cbd28 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.auth google-auth-library-bom - 1.33.1 + 1.34.0 pom Google Auth Library for Java BOM @@ -107,9 +107,9 @@ - com.coveo + com.spotify.fmt fmt-maven-plugin - 2.13 + 2.25 true diff --git a/cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java b/cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java index 3b5745acc..5b40442b5 100644 --- a/cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java +++ b/cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java @@ -206,8 +206,13 @@ public AccessToken generateToken(CredentialAccessBoundary accessBoundary) byte[] encryptedRestrictions = this.encryptRestrictions(rawRestrictions, sessionKey); + // withoutPadding() is used to stay consistent with server-side CAB + // withoutPadding() avoids additional URL encoded token issues (i.e. extra equal signs `=` in + // the path) String tokenValue = - intermediateToken + "." + Base64.getUrlEncoder().encodeToString(encryptedRestrictions); + intermediateToken + + "." + + Base64.getUrlEncoder().withoutPadding().encodeToString(encryptedRestrictions); return new AccessToken(tokenValue, intermediateTokenExpirationTime); } diff --git a/cab-token-generator/java/com/google/auth/credentialaccessboundary/protobuf/ClientSideAccessBoundaryProto.java b/cab-token-generator/java/com/google/auth/credentialaccessboundary/protobuf/ClientSideAccessBoundaryProto.java index 7a26c73c8..307f8bde9 100644 --- a/cab-token-generator/java/com/google/auth/credentialaccessboundary/protobuf/ClientSideAccessBoundaryProto.java +++ b/cab-token-generator/java/com/google/auth/credentialaccessboundary/protobuf/ClientSideAccessBoundaryProto.java @@ -24,6 +24,7 @@ public interface ClientSideAccessBoundaryRuleOrBuilder * @return The availableResource. */ java.lang.String getAvailableResource(); + /** * string available_resource = 1; * @@ -37,12 +38,14 @@ public interface ClientSideAccessBoundaryRuleOrBuilder * @return A list containing the availablePermissions. */ java.util.List getAvailablePermissionsList(); + /** * repeated string available_permissions = 2; * * @return The count of availablePermissions. */ int getAvailablePermissionsCount(); + /** * repeated string available_permissions = 2; * @@ -50,6 +53,7 @@ public interface ClientSideAccessBoundaryRuleOrBuilder * @return The availablePermissions at the given index. */ java.lang.String getAvailablePermissions(int index); + /** * repeated string available_permissions = 2; * @@ -64,15 +68,18 @@ public interface ClientSideAccessBoundaryRuleOrBuilder * @return Whether the compiledAvailabilityCondition field is set. */ boolean hasCompiledAvailabilityCondition(); + /** * .cel.expr.Expr compiled_availability_condition = 4; * * @return The compiledAvailabilityCondition. */ dev.cel.expr.Expr getCompiledAvailabilityCondition(); + /** .cel.expr.Expr compiled_availability_condition = 4; */ dev.cel.expr.ExprOrBuilder getCompiledAvailabilityConditionOrBuilder(); } + /** * Protobuf type {@code * com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule} @@ -83,6 +90,7 @@ public static final class ClientSideAccessBoundaryRule // @@protoc_insertion_point(message_implements:com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule) ClientSideAccessBoundaryRuleOrBuilder { private static final long serialVersionUID = 0L; + // Use ClientSideAccessBoundaryRule.newBuilder() to construct. private ClientSideAccessBoundaryRule( com.google.protobuf.GeneratedMessageV3.Builder builder) { @@ -122,6 +130,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { @SuppressWarnings("serial") private volatile java.lang.Object availableResource_ = ""; + /** * string available_resource = 1; * @@ -139,6 +148,7 @@ public java.lang.String getAvailableResource() { return s; } } + /** * string available_resource = 1; * @@ -162,6 +172,7 @@ public com.google.protobuf.ByteString getAvailableResourceBytes() { @SuppressWarnings("serial") private com.google.protobuf.LazyStringArrayList availablePermissions_ = com.google.protobuf.LazyStringArrayList.emptyList(); + /** * repeated string available_permissions = 2; * @@ -170,6 +181,7 @@ public com.google.protobuf.ByteString getAvailableResourceBytes() { public com.google.protobuf.ProtocolStringList getAvailablePermissionsList() { return availablePermissions_; } + /** * repeated string available_permissions = 2; * @@ -178,6 +190,7 @@ public com.google.protobuf.ProtocolStringList getAvailablePermissionsList() { public int getAvailablePermissionsCount() { return availablePermissions_.size(); } + /** * repeated string available_permissions = 2; * @@ -187,6 +200,7 @@ public int getAvailablePermissionsCount() { public java.lang.String getAvailablePermissions(int index) { return availablePermissions_.get(index); } + /** * repeated string available_permissions = 2; * @@ -199,6 +213,7 @@ public com.google.protobuf.ByteString getAvailablePermissionsBytes(int index) { public static final int COMPILED_AVAILABILITY_CONDITION_FIELD_NUMBER = 4; private dev.cel.expr.Expr compiledAvailabilityCondition_; + /** * .cel.expr.Expr compiled_availability_condition = 4; * @@ -208,6 +223,7 @@ public com.google.protobuf.ByteString getAvailablePermissionsBytes(int index) { public boolean hasCompiledAvailabilityCondition() { return ((bitField0_ & 0x00000001) != 0); } + /** * .cel.expr.Expr compiled_availability_condition = 4; * @@ -219,6 +235,7 @@ public dev.cel.expr.Expr getCompiledAvailabilityCondition() { ? dev.cel.expr.Expr.getDefaultInstance() : compiledAvailabilityCondition_; } + /** .cel.expr.Expr compiled_availability_condition = 4; */ @java.lang.Override public dev.cel.expr.ExprOrBuilder getCompiledAvailabilityConditionOrBuilder() { @@ -450,6 +467,7 @@ protected Builder newBuilderForType( Builder builder = new Builder(parent); return builder; } + /** * Protobuf type {@code * com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule} @@ -717,6 +735,7 @@ public Builder mergeFrom( private int bitField0_; private java.lang.Object availableResource_ = ""; + /** * string available_resource = 1; * @@ -733,6 +752,7 @@ public java.lang.String getAvailableResource() { return (java.lang.String) ref; } } + /** * string available_resource = 1; * @@ -749,6 +769,7 @@ public com.google.protobuf.ByteString getAvailableResourceBytes() { return (com.google.protobuf.ByteString) ref; } } + /** * string available_resource = 1; * @@ -764,6 +785,7 @@ public Builder setAvailableResource(java.lang.String value) { onChanged(); return this; } + /** * string available_resource = 1; * @@ -775,6 +797,7 @@ public Builder clearAvailableResource() { onChanged(); return this; } + /** * string available_resource = 1; * @@ -802,6 +825,7 @@ private void ensureAvailablePermissionsIsMutable() { } bitField0_ |= 0x00000002; } + /** * repeated string available_permissions = 2; * @@ -811,6 +835,7 @@ public com.google.protobuf.ProtocolStringList getAvailablePermissionsList() { availablePermissions_.makeImmutable(); return availablePermissions_; } + /** * repeated string available_permissions = 2; * @@ -819,6 +844,7 @@ public com.google.protobuf.ProtocolStringList getAvailablePermissionsList() { public int getAvailablePermissionsCount() { return availablePermissions_.size(); } + /** * repeated string available_permissions = 2; * @@ -828,6 +854,7 @@ public int getAvailablePermissionsCount() { public java.lang.String getAvailablePermissions(int index) { return availablePermissions_.get(index); } + /** * repeated string available_permissions = 2; * @@ -837,6 +864,7 @@ public java.lang.String getAvailablePermissions(int index) { public com.google.protobuf.ByteString getAvailablePermissionsBytes(int index) { return availablePermissions_.getByteString(index); } + /** * repeated string available_permissions = 2; * @@ -854,6 +882,7 @@ public Builder setAvailablePermissions(int index, java.lang.String value) { onChanged(); return this; } + /** * repeated string available_permissions = 2; * @@ -870,6 +899,7 @@ public Builder addAvailablePermissions(java.lang.String value) { onChanged(); return this; } + /** * repeated string available_permissions = 2; * @@ -883,6 +913,7 @@ public Builder addAllAvailablePermissions(java.lang.Iterable v onChanged(); return this; } + /** * repeated string available_permissions = 2; * @@ -895,6 +926,7 @@ public Builder clearAvailablePermissions() { onChanged(); return this; } + /** * repeated string available_permissions = 2; * @@ -917,6 +949,7 @@ public Builder addAvailablePermissionsBytes(com.google.protobuf.ByteString value private com.google.protobuf.SingleFieldBuilderV3< dev.cel.expr.Expr, dev.cel.expr.Expr.Builder, dev.cel.expr.ExprOrBuilder> compiledAvailabilityConditionBuilder_; + /** * .cel.expr.Expr compiled_availability_condition = 4; * @@ -925,6 +958,7 @@ public Builder addAvailablePermissionsBytes(com.google.protobuf.ByteString value public boolean hasCompiledAvailabilityCondition() { return ((bitField0_ & 0x00000004) != 0); } + /** * .cel.expr.Expr compiled_availability_condition = 4; * @@ -939,6 +973,7 @@ public dev.cel.expr.Expr getCompiledAvailabilityCondition() { return compiledAvailabilityConditionBuilder_.getMessage(); } } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public Builder setCompiledAvailabilityCondition(dev.cel.expr.Expr value) { if (compiledAvailabilityConditionBuilder_ == null) { @@ -953,6 +988,7 @@ public Builder setCompiledAvailabilityCondition(dev.cel.expr.Expr value) { onChanged(); return this; } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public Builder setCompiledAvailabilityCondition(dev.cel.expr.Expr.Builder builderForValue) { if (compiledAvailabilityConditionBuilder_ == null) { @@ -964,6 +1000,7 @@ public Builder setCompiledAvailabilityCondition(dev.cel.expr.Expr.Builder builde onChanged(); return this; } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public Builder mergeCompiledAvailabilityCondition(dev.cel.expr.Expr value) { if (compiledAvailabilityConditionBuilder_ == null) { @@ -983,6 +1020,7 @@ public Builder mergeCompiledAvailabilityCondition(dev.cel.expr.Expr value) { } return this; } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public Builder clearCompiledAvailabilityCondition() { bitField0_ = (bitField0_ & ~0x00000004); @@ -994,12 +1032,14 @@ public Builder clearCompiledAvailabilityCondition() { onChanged(); return this; } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public dev.cel.expr.Expr.Builder getCompiledAvailabilityConditionBuilder() { bitField0_ |= 0x00000004; onChanged(); return getCompiledAvailabilityConditionFieldBuilder().getBuilder(); } + /** .cel.expr.Expr compiled_availability_condition = 4; */ public dev.cel.expr.ExprOrBuilder getCompiledAvailabilityConditionOrBuilder() { if (compiledAvailabilityConditionBuilder_ != null) { @@ -1010,6 +1050,7 @@ public dev.cel.expr.ExprOrBuilder getCompiledAvailabilityConditionOrBuilder() { : compiledAvailabilityCondition_; } } + /** .cel.expr.Expr compiled_availability_condition = 4; */ private com.google.protobuf.SingleFieldBuilderV3< dev.cel.expr.Expr, dev.cel.expr.Expr.Builder, dev.cel.expr.ExprOrBuilder> @@ -1110,6 +1151,7 @@ public interface ClientSideAccessBoundaryOrBuilder com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRule> getAccessBoundaryRulesList(); + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1118,12 +1160,14 @@ public interface ClientSideAccessBoundaryOrBuilder com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRule getAccessBoundaryRules(int index); + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; * */ int getAccessBoundaryRulesCount(); + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1134,6 +1178,7 @@ public interface ClientSideAccessBoundaryOrBuilder com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRuleOrBuilder> getAccessBoundaryRulesOrBuilderList(); + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1143,6 +1188,7 @@ public interface ClientSideAccessBoundaryOrBuilder .ClientSideAccessBoundaryRuleOrBuilder getAccessBoundaryRulesOrBuilder(int index); } + /** * Protobuf type {@code com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundary} */ @@ -1151,6 +1197,7 @@ public static final class ClientSideAccessBoundary extends com.google.protobuf.G // @@protoc_insertion_point(message_implements:com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundary) ClientSideAccessBoundaryOrBuilder { private static final long serialVersionUID = 0L; + // Use ClientSideAccessBoundary.newBuilder() to construct. private ClientSideAccessBoundary(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); @@ -1190,6 +1237,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRule> accessBoundaryRules_; + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1202,6 +1250,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { getAccessBoundaryRulesList() { return accessBoundaryRules_; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1215,6 +1264,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { getAccessBoundaryRulesOrBuilderList() { return accessBoundaryRules_; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1224,6 +1274,7 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { public int getAccessBoundaryRulesCount() { return accessBoundaryRules_.size(); } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1235,6 +1286,7 @@ public int getAccessBoundaryRulesCount() { getAccessBoundaryRules(int index) { return accessBoundaryRules_.get(index); } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1439,6 +1491,7 @@ protected Builder newBuilderForType( Builder builder = new Builder(parent); return builder; } + /** * Protobuf type {@code com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundary} */ @@ -1740,6 +1793,7 @@ private void ensureAccessBoundaryRulesIsMutable() { return accessBoundaryRulesBuilder_.getMessageList(); } } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1752,6 +1806,7 @@ public int getAccessBoundaryRulesCount() { return accessBoundaryRulesBuilder_.getCount(); } } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1766,6 +1821,7 @@ public int getAccessBoundaryRulesCount() { return accessBoundaryRulesBuilder_.getMessage(index); } } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1788,6 +1844,7 @@ public Builder setAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1807,6 +1864,7 @@ public Builder setAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1828,6 +1886,7 @@ public Builder addAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1850,6 +1909,7 @@ public Builder addAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1868,6 +1928,7 @@ public Builder addAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1887,6 +1948,7 @@ public Builder addAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1907,6 +1969,7 @@ public Builder addAllAccessBoundaryRules( } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1922,6 +1985,7 @@ public Builder clearAccessBoundaryRules() { } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1937,6 +2001,7 @@ public Builder removeAccessBoundaryRules(int index) { } return this; } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1947,6 +2012,7 @@ public Builder removeAccessBoundaryRules(int index) { getAccessBoundaryRulesBuilder(int index) { return getAccessBoundaryRulesFieldBuilder().getBuilder(index); } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1961,6 +2027,7 @@ public Builder removeAccessBoundaryRules(int index) { return accessBoundaryRulesBuilder_.getMessageOrBuilder(index); } } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1977,6 +2044,7 @@ public Builder removeAccessBoundaryRules(int index) { return java.util.Collections.unmodifiableList(accessBoundaryRules_); } } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -1990,6 +2058,7 @@ public Builder removeAccessBoundaryRules(int index) { com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRule.getDefaultInstance()); } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; @@ -2004,6 +2073,7 @@ public Builder removeAccessBoundaryRules(int index) { com.google.auth.credentialaccessboundary.protobuf.ClientSideAccessBoundaryProto .ClientSideAccessBoundaryRule.getDefaultInstance()); } + /** * * repeated .com.google.auth.credentialaccessboundary.proto.ClientSideAccessBoundaryRule access_boundary_rules = 1; diff --git a/cab-token-generator/javatests/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactoryTest.java b/cab-token-generator/javatests/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactoryTest.java index 85600041f..3f353db37 100644 --- a/cab-token-generator/javatests/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactoryTest.java +++ b/cab-token-generator/javatests/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactoryTest.java @@ -149,8 +149,7 @@ public void fetchIntermediateCredentials() throws Exception { public void fetchIntermediateCredentials_withCustomUniverseDomain() throws IOException { String universeDomain = "foobar"; GoogleCredentials sourceCredentials = - getServiceAccountSourceCredentials(mockTokenServerTransportFactory) - .toBuilder() + getServiceAccountSourceCredentials(mockTokenServerTransportFactory).toBuilder() .setUniverseDomain(universeDomain) .build(); @@ -745,6 +744,11 @@ public void generateToken_withAvailablityCondition_success() throws Exception { CabToken cabToken = parseCabToken(token); assertEquals("accessToken", cabToken.intermediateToken); + // Base64 encoding output by default has `=` padding at the end if the input length + // is not a multiple of 3. Here we verify the use of `withoutPadding` that removes + // this padding. + assertFalse(cabToken.encryptedRestriction.contains(String.valueOf("="))); + // Checks the encrypted restriction is the correct proto format of the CredentialAccessBoundary. ClientSideAccessBoundary clientSideAccessBoundary = decryptRestriction( @@ -795,6 +799,11 @@ public void generateToken_withoutAvailabilityCondition_success() throws Exceptio CabToken cabToken = parseCabToken(token); assertEquals("accessToken", cabToken.intermediateToken); + // Base64 encoding output by default has `=` padding at the end if the input length + // is not a multiple of 3. Here we verify the use of `withoutPadding` that removes + // this padding. + assertFalse(cabToken.encryptedRestriction.contains(String.valueOf("="))); + // Checks the encrypted restriction is the correct proto format of the CredentialAccessBoundary. ClientSideAccessBoundary clientSideAccessBoundary = decryptRestriction( diff --git a/cab-token-generator/pom.xml b/cab-token-generator/pom.xml index 1b76bcf08..5a6d62848 100644 --- a/cab-token-generator/pom.xml +++ b/cab-token-generator/pom.xml @@ -6,7 +6,7 @@ com.google.auth google-auth-library-parent - 1.33.1 + 1.34.0 google-auth-library-cab-token-generator diff --git a/credentials/pom.xml b/credentials/pom.xml index 33e61ea6b..e92e45a18 100644 --- a/credentials/pom.xml +++ b/credentials/pom.xml @@ -4,7 +4,7 @@ com.google.auth google-auth-library-parent - 1.33.1 + 1.34.0 ../pom.xml diff --git a/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java b/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java index 858de50ca..90a6c86f1 100644 --- a/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java +++ b/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java @@ -63,7 +63,9 @@ public class HttpCredentialsAdapter private final Credentials credentials; - /** @param credentials Credentials instance to adapt for HTTP */ + /** + * @param credentials Credentials instance to adapt for HTTP + */ public HttpCredentialsAdapter(Credentials credentials) { Preconditions.checkNotNull(credentials); this.credentials = credentials; diff --git a/oauth2_http/java/com/google/auth/mtls/CertificateSourceUnavailableException.java b/oauth2_http/java/com/google/auth/mtls/CertificateSourceUnavailableException.java new file mode 100644 index 000000000..71d5b11d6 --- /dev/null +++ b/oauth2_http/java/com/google/auth/mtls/CertificateSourceUnavailableException.java @@ -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); + } +} diff --git a/oauth2_http/java/com/google/auth/mtls/WorkloadCertificateConfiguration.java b/oauth2_http/java/com/google/auth/mtls/WorkloadCertificateConfiguration.java new file mode 100644 index 000000000..5da318ff6 --- /dev/null +++ b/oauth2_http/java/com/google/auth/mtls/WorkloadCertificateConfiguration.java @@ -0,0 +1,101 @@ +/* + * 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 { + Preconditions.checkNotNull(certConfigStream); + + GenericJson fileContents = + parser.parseAndClose(certConfigStream, StandardCharsets.UTF_8, GenericJson.class); + + Map certConfigs = (Map) fileContents.get("cert_configs"); + if (certConfigs == null) { + throw new IllegalArgumentException( + "The cert_configs object must be provided in the certificate configuration file."); + } + + Map workloadConfig = (Map) certConfigs.get("workload"); + if (workloadConfig == null) { + // Throw a CertificateSourceUnavailableException because there is no workload cert source. + // This tells the transport layer that it should check for another certificate source type. + throw new CertificateSourceUnavailableException( + "A workload certificate configuration must be provided in the cert_configs object."); + } + + 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); + } +} diff --git a/oauth2_http/java/com/google/auth/mtls/X509Provider.java b/oauth2_http/java/com/google/auth/mtls/X509Provider.java new file mode 100644 index 000000000..704f85bdd --- /dev/null +++ b/oauth2_http/java/com/google/auth/mtls/X509Provider.java @@ -0,0 +1,199 @@ +/* + * 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.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 { + 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. + * + *
    + *
  • The certificate config override path, if set. + *
  • The path pointed to by the "GOOGLE_API_CERTIFICATE_CONFIG" environment variable + *
  • The well known gcloud location for the certificate configuration file. + *
+ * + * @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 { + + 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(); + } + } + } + + 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)) { + // Path will be put in the message from the catch block below + throw new CertificateSourceUnavailableException("File does not exist."); + } + certConfigStream = createInputStream(certConfig); + return WorkloadCertificateConfiguration.fromCertificateConfigurationStream(certConfigStream); + } 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(); + } + + 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() { + 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); + } +} diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java index 320ee4290..2d59208d1 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java @@ -118,10 +118,9 @@ public class ComputeEngineCredentials extends GoogleCredentials * *

Behavior of setting {@link GoogleAuthTransport} / {@link BindingEnforcement}: * - *

MTLS-bound token where binding enforcement depends on IAM policy: MTLS / {}, {} / - * IAM_POLICY, MTLS / IAM_POLICY + *

MTLS-bound token where binding enforcement depends on IAM policy: MTLS / IAM_POLICY * - *

MTLS-bound token where bindings are always enforced: {} / ON, MTLS / ON + *

MTLS-bound token where bindings are always enforced: MTLS / ON * *

DirectPath bound token: ALTS / {} */ diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 8a5084b98..eaa28d6fe 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -584,7 +584,9 @@ public String getServiceAccountImpersonationUrl() { return serviceAccountImpersonationUrl; } - /** @return The service account email to be impersonated, if available */ + /** + * @return The service account email to be impersonated, if available + */ @Nullable public String getServiceAccountEmail() { if (serviceAccountImpersonationUrl == null || serviceAccountImpersonationUrl.isEmpty()) { diff --git a/oauth2_http/java/com/google/auth/oauth2/PKCEProvider.java b/oauth2_http/java/com/google/auth/oauth2/PKCEProvider.java index 4800dd1cd..0a0b2f8dd 100644 --- a/oauth2_http/java/com/google/auth/oauth2/PKCEProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/PKCEProvider.java @@ -47,6 +47,7 @@ public interface PKCEProvider { * @return The code_challenge_method String. */ String getCodeChallengeMethod(); + /** * Get the code_verifier parameter used in PKCE. * diff --git a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java index 894b324a9..00e280983 100644 --- a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java +++ b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java @@ -37,7 +37,7 @@ class PluggableAuthException extends OAuthException { PluggableAuthException(String errorCode, String errorDescription) { - super(errorCode, checkNotNull(errorDescription), /* errorUri=*/ null); + super(errorCode, checkNotNull(errorDescription), /* errorUri= */ null); } /** The message with format: Error code {errorCode}: {errorDescription}. */ diff --git a/oauth2_http/java/com/google/auth/oauth2/QuotaProjectIdProvider.java b/oauth2_http/java/com/google/auth/oauth2/QuotaProjectIdProvider.java index 5456823e0..fac3b97b6 100644 --- a/oauth2_http/java/com/google/auth/oauth2/QuotaProjectIdProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/QuotaProjectIdProvider.java @@ -33,6 +33,8 @@ /** Interface for {@link GoogleCredentials} that return a quota project ID. */ public interface QuotaProjectIdProvider { - /** @return the quota project ID used for quota and billing purposes */ + /** + * @return the quota project ID used for quota and billing purposes + */ String getQuotaProjectId(); } diff --git a/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgent.java b/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgent.java index f39936a31..d879fa7fa 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgent.java +++ b/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgent.java @@ -90,7 +90,9 @@ public SecureSessionAgentConfig getConfig() { return getSecureSessionAgentConfigFromMDS(); } - /** @return default instance of SecureSessionAgent */ + /** + * @return default instance of SecureSessionAgent + */ public static SecureSessionAgent create() { return newBuilder().build(); } diff --git a/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgentConfig.java b/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgentConfig.java index 7deb0d188..1a8c36399 100644 --- a/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgentConfig.java +++ b/oauth2_http/java/com/google/auth/oauth2/SecureSessionAgentConfig.java @@ -44,12 +44,16 @@ public static Builder createBuilder() { return new Builder(); } - /** @return the plaintext S2A Address. */ + /** + * @return the plaintext S2A Address. + */ public String getPlaintextAddress() { return plaintextAddress; } - /** @return the mTLS S2A Address. */ + /** + * @return the mTLS S2A Address. + */ public String getMtlsAddress() { return mtlsAddress; } diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 8fccecbbe..3246f1800 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -510,6 +510,7 @@ private GenericData parseResponseAs(HttpResponse response) throws IOException { LoggingUtils.logResponsePayload(genericData, LOGGER_PROVIDER, "Response payload"); return genericData; } + /** * Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT). */ diff --git a/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java b/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java new file mode 100644 index 000000000..70bb4294e --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/mtls/WorkloadCertificateConfigurationTest.java @@ -0,0 +1,150 @@ +/* + * 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.json.GenericJson; +import com.google.auth.TestUtils; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Assert; +import org.junit.Test; + +public class WorkloadCertificateConfigurationTest { + + @Test + public void workloadCertificateConfig_fromStream_Succeeds() throws IOException { + String certPath = "cert.crt"; + String privateKeyPath = "key.crt"; + InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath); + + WorkloadCertificateConfiguration config = + WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream); + assertNotNull(config); + } + + @Test + public void workloadCertificateConfig_fromStreamMissingCertPath_Fails() throws IOException { + String certPath = ""; + String privateKeyPath = "key.crt"; + InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath); + + IllegalArgumentException exception = + Assert.assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The cert_path field must be provided in the workload certificate configuration.")); + } + + @Test + public void workloadCertificateConfig_fromStreamMissingPrivateKeyPath_Fails() throws IOException { + String certPath = "cert.crt"; + String privateKeyPath = ""; + InputStream configStream = writeWorkloadCertificateConfigStream(certPath, privateKeyPath); + + IllegalArgumentException exception = + Assert.assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The key_path field must be provided in the workload certificate configuration.")); + } + + @Test + public void workloadCertificateConfig_fromStreamMissingWorkload_Fails() throws IOException { + GenericJson json = new GenericJson(); + json.put("cert_configs", new GenericJson()); + InputStream configStream = TestUtils.jsonToInputStream(json); + + CertificateSourceUnavailableException exception = + Assert.assertThrows( + CertificateSourceUnavailableException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); + assertTrue( + exception + .getMessage() + .contains( + "A workload certificate configuration must be provided in the cert_configs object.")); + } + + @Test + public void workloadCertificateConfig_fromStreamMissingCertConfig_Fails() throws IOException { + GenericJson json = new GenericJson(); + InputStream configStream = TestUtils.jsonToInputStream(json); + + IllegalArgumentException exception = + Assert.assertThrows( + IllegalArgumentException.class, + () -> + WorkloadCertificateConfiguration.fromCertificateConfigurationStream(configStream)); + assertTrue( + exception + .getMessage() + .contains( + "The cert_configs object must be provided in the certificate configuration file.")); + } + + static InputStream writeWorkloadCertificateConfigStream(String certPath, String privateKeyPath) + throws IOException { + GenericJson json = writeWorkloadCertificateConfigJson(certPath, privateKeyPath); + return TestUtils.jsonToInputStream(json); + } + + private static GenericJson writeWorkloadCertificateConfigJson( + String certPath, String privateKeyPath) { + GenericJson json = new GenericJson(); + json.put("version", 1); + GenericJson certConfigs = new GenericJson(); + GenericJson workloadConfig = new GenericJson(); + if (certPath != null) { + workloadConfig.put("cert_path", certPath); + } + if (privateKeyPath != null) { + workloadConfig.put("key_path", privateKeyPath); + } + certConfigs.put("workload", workloadConfig); + json.put("cert_configs", certConfigs); + return json; + } +} diff --git a/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java new file mode 100644 index 000000000..de016cf55 --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/mtls/X509ProviderTest.java @@ -0,0 +1,238 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class X509ProviderTest { + + private static final String TEST_CERT = + "-----BEGIN CERTIFICATE-----\n" + + "MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE\n" + + "AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x\n" + + "MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3\n" + + "MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB\n" + + "BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ\n" + + "GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ\n" + + "Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB\n" + + "AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM\n" + + "MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4\n" + + "fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4\n" + + "uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1\n" + + "kWwa9n19NFiV0z3m6isj\n" + + "-----END CERTIFICATE-----\n"; + + private static final String TEST_PRIVATE_KEY = + "-----BEGIN PRIVATE KEY-----\n" + + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/\n" + + "XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ\n" + + "oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu\n" + + "uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy\n" + + "sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK\n" + + "/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY\n" + + "lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1\n" + + "fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq\n" + + "RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB\n" + + "Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno\n" + + "fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L\n" + + "XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw\n" + + "ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE\n" + + "VUV6b25kqrcu\n" + + "-----END PRIVATE KEY-----"; + + @Test + public void x509Provider_fileDoesntExist_throws() { + String certConfigPath = "badfile.txt"; + X509Provider testProvider = new TestX509Provider(certConfigPath); + String expectedErrorMessage = "File does not exist."; + + CertificateSourceUnavailableException exception = + Assert.assertThrows(CertificateSourceUnavailableException.class, testProvider::getKeyStore); + assertTrue(exception.getMessage().contains(expectedErrorMessage)); + } + + @Test + public void x509Provider_emptyFile_throws() { + String certConfigPath = "certConfig.txt"; + InputStream certConfigStream = new ByteArrayInputStream("".getBytes()); + TestX509Provider testProvider = new TestX509Provider(certConfigPath); + testProvider.addFile(certConfigPath, certConfigStream); + String expectedErrorMessage = "no JSON input found"; + + IllegalArgumentException exception = + Assert.assertThrows(IllegalArgumentException.class, testProvider::getKeyStore); + assertTrue(exception.getMessage().contains(expectedErrorMessage)); + } + + @Test + public void x509Provider_succeeds() throws IOException, KeyStoreException, CertificateException { + String certConfigPath = "certConfig.txt"; + String certPath = "cert.crt"; + String keyPath = "key.crt"; + InputStream certConfigStream = + WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( + certPath, keyPath); + + TestX509Provider testProvider = new TestX509Provider(certConfigPath); + testProvider.addFile(certConfigPath, certConfigStream); + testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); + testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert = + cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + + // Assert that the store has the expected certificate and only the expected certificate. + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); + } + + @Test + public void x509Provider_succeeds_withEnvVariable() + throws IOException, KeyStoreException, CertificateException { + String certConfigPath = "certConfig.txt"; + String certPath = "cert.crt"; + String keyPath = "key.crt"; + InputStream certConfigStream = + WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( + certPath, keyPath); + + TestX509Provider testProvider = new TestX509Provider(); + testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath); + testProvider.addFile(certConfigPath, certConfigStream); + testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); + testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert = + cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + + // Assert that the store has the expected certificate and only the expected certificate. + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); + } + + @Test + public void x509Provider_succeeds_withWellKnownPath() + throws IOException, KeyStoreException, CertificateException { + String certConfigPath = "certConfig.txt"; + String certPath = "cert.crt"; + String keyPath = "key.crt"; + InputStream certConfigStream = + WorkloadCertificateConfigurationTest.writeWorkloadCertificateConfigStream( + certPath, keyPath); + + TestX509Provider testProvider = new TestX509Provider(); + testProvider.setEnv("GOOGLE_API_CERTIFICATE_CONFIG", certConfigPath); + testProvider.addFile(certConfigPath, certConfigStream); + testProvider.addFile(certPath, new ByteArrayInputStream(TEST_CERT.getBytes())); + testProvider.addFile(keyPath, new ByteArrayInputStream(TEST_PRIVATE_KEY.getBytes())); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate expectedCert = + cf.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes())); + + // Assert that the store has the expected certificate and only the expected certificate. + KeyStore store = testProvider.getKeyStore(); + assertEquals(1, store.size()); + assertNotNull(store.getCertificateAlias(expectedCert)); + } + + static class TestX509Provider extends X509Provider { + private final Map files; + private final Map variables; + private final Map properties; + + TestX509Provider() { + this(null); + } + + TestX509Provider(String filePathOverride) { + super(filePathOverride); + this.files = new HashMap<>(); + this.variables = new HashMap<>(); + this.properties = new HashMap<>(); + } + + void addFile(String file, InputStream stream) { + files.put(file, stream); + } + + @Override + String getEnv(String name) { + return variables.get(name); + } + + void setEnv(String name, String value) { + variables.put(name, value); + } + + @Override + String getProperty(String property, String def) { + String value = properties.get(property); + return value == null ? def : value; + } + + @Override + boolean isFile(File file) { + return files.containsKey(file.getPath()); + } + + @Override + InputStream createInputStream(File file) throws FileNotFoundException { + InputStream stream = files.get(file.getPath()); + if (stream == null) { + throw new FileNotFoundException(file.getPath()); + } + return stream; + } + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java index 3fee39d1c..be2083200 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java @@ -200,9 +200,9 @@ public void buildTokenUrl_nullTransport() { .setGoogleAuthTransport(null) .setBindingEnforcement(ComputeEngineCredentials.BindingEnforcement.ON) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String tokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?binding-enforcement=on", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?binding-enforcement=on", tokenUrl); } @Test @@ -212,9 +212,9 @@ public void buildTokenUrl_nullBindingEnforcement() { .setGoogleAuthTransport(ComputeEngineCredentials.GoogleAuthTransport.MTLS) .setBindingEnforcement(null) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String tokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?transport=mtls", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?transport=mtls", tokenUrl); } @Test @@ -230,25 +230,25 @@ public void buildTokenUrl_nullTransport_nullBindingEnforcement() { } @Test - public void buildTokenUrlSoftMtlsBound_mtls_transport() { + public void buildTokenUrl_mtls_transport() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder() .setGoogleAuthTransport(ComputeEngineCredentials.GoogleAuthTransport.MTLS) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String tokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?transport=mtls", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?transport=mtls", tokenUrl); } @Test - public void buildTokenUrlSoftMtlsBound_iam_enforcement() { + public void buildTokenUrl_iam_enforcement() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder() .setBindingEnforcement(ComputeEngineCredentials.BindingEnforcement.IAM_POLICY) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String tokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?binding-enforcement=iam-policy", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?binding-enforcement=iam-policy", tokenUrl); } @Test @@ -264,14 +264,14 @@ public void buildTokenUrlSoftMtlsBound_mtls_transport_iam_enforcement() { } @Test - public void buildTokenUrlHardMtlsBound_always_enforced() { + public void buildTokenUrl_always_enforced() { ComputeEngineCredentials credentials = ComputeEngineCredentials.newBuilder() .setBindingEnforcement(ComputeEngineCredentials.BindingEnforcement.ON) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String tokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?binding-enforcement=on", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?binding-enforcement=on", tokenUrl); } @Test @@ -281,9 +281,9 @@ public void buildTokenUrlHardMtlsBound_mtls_transport_always_enforced() { .setGoogleAuthTransport(ComputeEngineCredentials.GoogleAuthTransport.MTLS) .setBindingEnforcement(ComputeEngineCredentials.BindingEnforcement.ON) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String hardBoundTokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?transport=mtls&binding-enforcement=on", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?transport=mtls&binding-enforcement=on", hardBoundTokenUrl); } @Test @@ -292,9 +292,9 @@ public void buildTokenUrlHardDirectPathBound_alts_transport() { ComputeEngineCredentials.newBuilder() .setGoogleAuthTransport(ComputeEngineCredentials.GoogleAuthTransport.ALTS) .build(); - String softBoundTokenUrl = credentials.createTokenUrlWithScopes(); + String hardBoundTokenUrl = credentials.createTokenUrlWithScopes(); - assertEquals(TOKEN_URL + "?transport=alts", softBoundTokenUrl); + assertEquals(TOKEN_URL + "?transport=alts", hardBoundTokenUrl); } @Test diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java index 9dc5d4eea..7f4da0611 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java @@ -120,8 +120,7 @@ public void refreshAccessToken_withCustomUniverseDomain() throws IOException { MockStsTransportFactory transportFactory = new MockStsTransportFactory(); String universeDomain = "foobar"; GoogleCredentials sourceCredentials = - getServiceAccountSourceCredentials(/* canRefresh= */ true) - .toBuilder() + getServiceAccountSourceCredentials(/* canRefresh= */ true).toBuilder() .setUniverseDomain(universeDomain) .build(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 656585dbd..d76491c59 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -530,9 +530,9 @@ public void refreshAccessToken_success_SSJflow() throws IOException, IllegalStat ServiceAccountCredentials sourceCredentialsSSJ = ((ServiceAccountCredentials) sourceCredentials) .toBuilder() - .setHttpTransportFactory(mockTransportFactory) - .setUseJwtAccessWithScope(true) - .build(); + .setHttpTransportFactory(mockTransportFactory) + .setUseJwtAccessWithScope(true) + .build(); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentialsSSJ, @@ -559,9 +559,9 @@ public void refreshAccessToken_success_nonGDU() throws IOException, IllegalState ServiceAccountCredentials sourceCredentialsNonGDU = ((ServiceAccountCredentials) sourceCredentials) .toBuilder() - .setUniverseDomain(TEST_UNIVERSE_DOMAIN) - .setHttpTransportFactory(transportFactory) - .build(); + .setUniverseDomain(TEST_UNIVERSE_DOMAIN) + .setHttpTransportFactory(transportFactory) + .build(); ImpersonatedCredentials impersonatedCredentials = ImpersonatedCredentials.create( sourceCredentialsNonGDU, @@ -922,9 +922,9 @@ public void sign_sameAs_nonGDU() { ServiceAccountCredentials sourceCredentialsNonGDU = ((ServiceAccountCredentials) sourceCredentials) .toBuilder() - .setUniverseDomain("test.com") - .setHttpTransportFactory(transportFactory) - .build(); + .setUniverseDomain("test.com") + .setHttpTransportFactory(transportFactory) + .build(); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentialsNonGDU, @@ -1048,9 +1048,9 @@ public void idTokenWithAudience_sameAs_nonGDU() throws IOException { ServiceAccountCredentials sourceCredentialsNonGDU = ((ServiceAccountCredentials) sourceCredentials) .toBuilder() - .setUniverseDomain("test.com") - .setHttpTransportFactory(transportFactory) - .build(); + .setUniverseDomain("test.com") + .setHttpTransportFactory(transportFactory) + .build(); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentialsNonGDU, diff --git a/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java index 7251f3301..97a215f56 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java @@ -52,6 +52,7 @@ public class Slf4jUtilsTest { public void setup() { testEnvironmentProvider = new TestEnvironmentProvider(); } + // This test mimics GOOGLE_SDK_JAVA_LOGGING != true @Test public void testGetLogger_loggingDisabled_shouldGetNOPLogger() { diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index a28c5fb8f..289b2c9cf 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -7,7 +7,7 @@ com.google.auth google-auth-library-parent - 1.33.1 + 1.34.0 ../pom.xml @@ -146,13 +146,13 @@ ch.qos.logback logback-classic - 1.5.16 + 1.5.18 test ch.qos.logback logback-core - 1.5.16 + 1.5.18 test @@ -234,7 +234,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.5.2 + 3.5.3 1200 sponge_log diff --git a/pom.xml b/pom.xml index fd1d36ae5..bbd8c2e30 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.google.auth google-auth-library-parent - 1.33.1 + 1.34.0 pom Google Auth Library for Java Client libraries providing authentication and @@ -16,7 +16,7 @@ com.google.cloud google-cloud-shared-config - 1.14.4 + 1.15.4 @@ -74,17 +74,17 @@ UTF-8 - 1.46.2 + 1.46.3 4.13.2 33.4.0-android 2.0.33 3.0.2 false - 2.36.0 + 2.37.0 3.25.5 0.9.0-proto3 1.15.0 - 2.0.16 + 2.0.17 2.12.1 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 9c7d50634..aa7212478 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -30,7 +30,7 @@ com.google.cloud libraries-bom - 26.55.0 + 26.59.0 pom import @@ -43,14 +43,14 @@ com.google.auth google-auth-library-oauth2-http - 1.33.0 + 1.33.1 com.google.cloud google-iam-admin - 3.53.0 + 3.55.0 diff --git a/versions.txt b/versions.txt index 490e3ab07..4a8fa7ca6 100644 --- a/versions.txt +++ b/versions.txt @@ -1,9 +1,9 @@ # Format: # module:released-version:current-version -google-auth-library:1.33.1:1.33.1 -google-auth-library-bom:1.33.1:1.33.1 -google-auth-library-parent:1.33.1:1.33.1 -google-auth-library-appengine:1.33.1:1.33.1 -google-auth-library-credentials:1.33.1:1.33.1 -google-auth-library-oauth2-http:1.33.1:1.33.1 +google-auth-library:1.34.0:1.34.0 +google-auth-library-bom:1.34.0:1.34.0 +google-auth-library-parent:1.34.0:1.34.0 +google-auth-library-appengine:1.34.0:1.34.0 +google-auth-library-credentials:1.34.0:1.34.0 +google-auth-library-oauth2-http:1.34.0:1.34.0