Skip to content

fix(gdch): support EC private keys#1896

Merged
diegomarquezp merged 65 commits intomainfrom
b/488439640
Apr 8, 2026
Merged

fix(gdch): support EC private keys#1896
diegomarquezp merged 65 commits intomainfrom
b/488439640

Conversation

@diegomarquezp
Copy link
Copy Markdown
Contributor

@diegomarquezp diegomarquezp commented Mar 4, 2026

Context: b/488439640

Implementation originally proposed in b/431924643#comment9

The primary objective is to enable support for Elliptic Curve (EC) keys and non-URI audience formats, aligning the Java SDK with the behavior of the Python and Go implementations. Additionally, the GDCH key creation tool creates EC keys only, meaning the GDCH implementation was not following the convention.

Key Changes

  • Algorithm Support: Updated the credential signing process to use the ES256 (ECDSA) algorithm, which is the required standard for GDCH service accounts.
  • Audience Flexibility: Changed the apiAudience field from a URI to a String to accommodate "magic" non-URI strings (e.g., specific administrative audiences) required by certain GDCH services.
  • OAuth2Utils Refactoring: Enhanced privateKeyFromPkcs8 to accept an algorithm parameter, allowing the library to parse EC keys instead of defaulting exclusively to RSA.
  • Test fixes: Fixes in a couple of test files as per Sonarqube analysis.

Testing

  • Updated GdchCredentialsTest to include test cases for EC key parsing and token signing.

@product-auto-label product-auto-label bot added the size: l Pull request size is large. label Mar 4, 2026
@diegomarquezp diegomarquezp marked this pull request as ready for review March 9, 2026 21:36
@diegomarquezp diegomarquezp requested review from a team as code owners March 9, 2026 21:36
@diegomarquezp diegomarquezp requested a review from a team as a code owner March 10, 2026 21:22
@diegomarquezp diegomarquezp changed the title feat(gdch): support EC private keys fix(gdch): support EC private keys Mar 10, 2026
@marcosgtz7
Copy link
Copy Markdown

marcosgtz7 commented Mar 24, 2026 via email

private static final String SA_PRIVATE_KEY_PKCS8 =
ServiceAccountCredentialsTest.PRIVATE_KEY_PKCS8;
private static final String GDCH_SA_FORMAT_VERSION = GdchCredentials.SUPPORTED_FORMAT_VERSION;
private static final String GDCH_SA_FORMAT_VERSION =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the tests just use GdchCredentials.SUPPORTED_JSON_FORMAT_VERSION constant directly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, done.

Comment on lines +1094 to +1114
// SEQUENCE length doesn't match actual length
byte[] invalidDer = new byte[] {0x30, 0x05, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02};
GoogleAuthException e =
assertThrows(
GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64));
assertEquals("Invalid DER signature length.", e.getMessage());
}

@Test
void transcodeDerToConcat_invalidRInteger() {
// Missing INTEGER for R
byte[] invalidDer = new byte[] {0x30, 0x06, 0x03, 0x01, 0x01, 0x02, 0x01, 0x02};
GoogleAuthException e =
assertThrows(
GoogleAuthException.class, () -> GdchCredentials.transcodeDerToConcat(invalidDer, 64));
assertEquals("Expected INTEGER for R.", e.getMessage());
}

@Test
void transcodeDerToConcat_invalidSInteger() {
// Missing INTEGER for S
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these test cases, can the comment show where it's missing the INTEGER value?

// Missing INTEGER for R

Is it missing something from byte[] invalidDer = new byte[] {0x30, 0x06, 0x03, 0x01, 0x01, 0x02, 0x01, 0x02};?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a more specific comment

Copy link
Copy Markdown
Member

@lqiu96 lqiu96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM. added a few comments.

@product-auto-label product-auto-label bot added size: xl Pull request size is extra large. and removed size: l Pull request size is large. labels Mar 26, 2026
Comment on lines +305 to +311
if (algorithm == Pkcs8Algorithm.EC) {
reader = new StringReader(privateKeyPkcs8);
section = PemReader.readFirstSectionAndClose(reader, "EC PRIVATE KEY");
if (section != null) {
return privateKeyFromSec1(section.getBase64DecodedBytes());
}
}
Copy link
Copy Markdown
Member

@lqiu96 lqiu96 Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is PKCS#8 or SEC1 two different algorithms/ flows? Also Is this logic only needed for GDCH?

If so, can this logic exists inside GDCH itself?
e.g.

  1. Check the first section and determine if we should call pckc8 or sec1 method
  2. call privateKeyFromPkcs8 or privateKeyFromSec1

I think it would better to separate these concerns since it looks like privateKeyFromPkcs8 is now also making a call to privateKeyFromSec1. I think we have added proper checks, but since this resides in OAuth2Utils I'm worried this behavior may have unknown impact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I still kept the algorithm parameter in OAuth2Utils to parse EC keys. WDYT?

@diegomarquezp diegomarquezp added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Apr 7, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Apr 7, 2026
@product-auto-label product-auto-label bot added size: l Pull request size is large. and removed size: xl Pull request size is extra large. labels Apr 8, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 8, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@diegomarquezp diegomarquezp merged commit 27dac49 into main Apr 8, 2026
26 of 28 checks passed
@diegomarquezp diegomarquezp deleted the b/488439640 branch April 8, 2026 16:32
lqiu96 pushed a commit to googleapis/google-cloud-java that referenced this pull request Apr 8, 2026
* fix: allow for ES algorithm in GdchCredentials

* test: partially adapt tests

* test: finish adjusting tests

* chore: format

* fix: restore credential name

* docs: restore license

* fix: restore removed code

* test: increase coverage

* test(gdch): parameterize test

* test: remove unused var

* chore: remove unused throw clause

* test: parameterize more

* fix: remove unused parameter

* fix: make variables final as intended

* fix: remove unused throw clause

* fix: remove unused throw clause

* fix: make OAuth2Credentials clock package private for production code

* fix: use non deprecated base 64 encoder

* test: parameterize flagged tests

* chore: format

* test: fix assertion

* build: remove unused dependency

* test: run linux gce only on linux envs

* fix: sonarqube flags (use java.util.Base64)

* fix: improve error message template

* fix: keep overload of "with audience" that takes an URI

* fix: restore public getter of getApiAudience

* docs: add javadoc for signing logic

* test: test private signature and decode methods

* fix: add null and empty check for audience string

* docs: add javadoc for audience getters

* fix: use enum for possible algorithms

* fix: use obsolete javadoc instead of @deprecated

* refactor: use OAuth2Utils validate methods in GdchCredentials

* fix: restore GoogleAuthException throwing in GdchCredentials

* refactor: downgrade Pkcs8Algorithm and privateKeyFromPkcs8 to package-private

* refactor: split parseBody into parseJson and parseQuery in test utilities

* refactor: remove validation reflection by making signUsingEsSha256 package-private

* test: use hardcoded string literal for gdch api audience in test

* test: refactor to use assertThrows in GdchCredentialsTest and remove host OS check in DefaultCredentialsProviderTest

* fix: add comment about EC algorithm support in GdchCredentials

* fix: update GDCH audience error message to be more descriptive

* refactor: rename getApiAudienceString to getGdchAudience

* fix: Remove unused import

* docs: update GDCH audience getter javadocs

* test: add null-checks to builder and corresponding tests

* refactor: consolidate token type constants using OAuth2Utils

* refactor: throw GoogleAuthException for signing and transcoding errors

* docs: add javadoc to related test utils

* fix: use GoogleAuthException

* test: use assertThrows where applicable

* refactor: replace Preconditions with Strings.isNullOrEmpty for audience checks

* fix: consistent exception message

* chore: format

* test: use lowercase os name

* chore: address review comments for PR #1896

* chore: format

* Finalizing GDCH credentials support by addressing reviewer comments

* chore: format

* fix: parse EC private keys with  SEC1 algorithm

* chore: format

* fix: separate PKCs8 vs SEC1 logic in GdchCredentials

* fix: improved exception message, added comments to extractPrivateKeyValue

Original-PR: googleapis/google-auth-library-java#1896
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: l Pull request size is large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants