-
Notifications
You must be signed in to change notification settings - Fork 1.1k
chore: [wip] PQC POC 2 #13203
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
base: main
Are you sure you want to change the base?
chore: [wip] PQC POC 2 #13203
Changes from all commits
64be800
7c915c7
f0478ae
408496f
ae47748
a61bd9d
6b8816b
5be6b97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| name: PQC Connectivity Integration Tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main ] | ||
| pull_request: | ||
| branches: [ main ] | ||
|
|
||
| jobs: | ||
| pqc-tests: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| # 1. Checkout sibling HTTP Client repository | ||
| - name: Checkout google-http-java-client | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: googleapis/google-http-java-client | ||
| ref: chore/pqc-poc-2 | ||
| path: google-http-java-client | ||
|
|
||
| # 2. Checkout this monorepo | ||
| - name: Checkout google-cloud-java-pqc | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| path: google-cloud-java-pqc | ||
|
|
||
| # 3. Set up JDK 17 | ||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '17' | ||
| distribution: 'temurin' | ||
| cache: 'maven' | ||
| cache-dependency-path: 'google-cloud-java-pqc/pom.xml' | ||
|
|
||
| # 4. Build and install modified google-http-client SNAPSHOT locally | ||
| - name: Build and Install google-http-java-client | ||
| run: | | ||
| cd google-http-java-client | ||
| mvn clean install -DskipTests=true -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip | ||
|
|
||
| # 5. Build the entire monorepo core components required by the tests | ||
| - name: Build and Install Core Dependency Reactor | ||
| run: | | ||
| cd google-cloud-java-pqc | ||
| mvn clean install -pl sdk-platform-java/pqc-test/pqc-test-snapshot,sdk-platform-java/pqc-test/pqc-test-release -am -T 1.5C -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -DskipTests=true | ||
|
|
||
| # 6. Run Snapshot PQC Tests (EXPECT PASS) | ||
| - name: Run Snapshot PQC Connectivity Tests (Expect PASS) | ||
| run: | | ||
| cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-snapshot | ||
| mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest | ||
|
|
||
| # 7. Run Release PQC Tests (EXPECT FAIL) | ||
| - name: Run Release PQC Connectivity Tests (Expect FAIL) | ||
| # We expect this step to fail. If it passes, it means release libraries are negotiating PQC (which is incorrect). | ||
| # Thus we run it and assert that the maven command fails (exit code != 0). | ||
| run: | | ||
| cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-release | ||
| if mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest; then | ||
| echo "Error: Release tests passed but they were expected to fail!" | ||
| exit 1 | ||
| else | ||
| echo "Success: Release tests failed-fast as expected." | ||
| exit 0 | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| </parent> | ||
|
|
||
| <properties> | ||
| <bouncycastle.version>1.80</bouncycastle.version> | ||
| <skipUnitTests>false</skipUnitTests> | ||
| <checkstyle.header.file>java.header</checkstyle.header.file> | ||
| <maven.compiler.release>8</maven.compiler.release> | ||
|
|
@@ -27,7 +28,7 @@ | |
| consistent across modules in this repository --> | ||
| <javax.annotation-api.version>1.3.2</javax.annotation-api.version> | ||
| <grpc.version>1.81.0</grpc.version> | ||
| <google.http-client.version>2.1.0</google.http-client.version> | ||
| <google.http-client.version>2.1.1-SNAPSHOT</google.http-client.version> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating |
||
| <gson.version>2.13.2</gson.version> | ||
| <guava.version>33.5.0-jre</guava.version> | ||
| <protobuf.version>4.33.2</protobuf.version> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -812,13 +812,158 @@ public ManagedChannelBuilder<?> createDecoratedChannelBuilder() throws IOExcepti | |
| if (interceptorProvider != null) { | ||
| builder.intercept(interceptorProvider.getInterceptors()); | ||
| } | ||
| // Apply PQC configuration by default as a standard feature of GAX. | ||
| builder = applyPqcConfiguration(builder); | ||
|
|
||
| if (channelConfigurator != null) { | ||
| builder = channelConfigurator.apply(builder); | ||
| } | ||
|
|
||
| return builder; | ||
| } | ||
|
|
||
| private static final class OpenSslReflectionHolder { | ||
| private static final Class<?> SHADED_GRPC_SSL_CONTEXTS; | ||
| private static final Class<?> SHADED_SSL_CONTEXT_BUILDER; | ||
| private static final java.lang.reflect.Method SHADED_FOR_CLIENT; | ||
| private static final Object SHADED_GROUPS_OPTION; | ||
| private static final java.lang.reflect.Method SHADED_OPTION_METHOD; | ||
| private static final java.lang.reflect.Method SHADED_BUILD_METHOD; | ||
| private static final java.lang.reflect.Method SHADED_SSL_CONTEXT_METHOD; | ||
| private static final Class<?> SHADED_SSL_CONTEXT; | ||
| private static final boolean SHADED_AVAILABLE; | ||
|
|
||
| private static final Class<?> UNSHADED_GRPC_SSL_CONTEXTS; | ||
| private static final Class<?> UNSHADED_SSL_CONTEXT_BUILDER; | ||
| private static final java.lang.reflect.Method UNSHADED_FOR_CLIENT; | ||
| private static final Object UNSHADED_GROUPS_OPTION; | ||
| private static final java.lang.reflect.Method UNSHADED_OPTION_METHOD; | ||
| private static final java.lang.reflect.Method UNSHADED_BUILD_METHOD; | ||
| private static final java.lang.reflect.Method UNSHADED_SSL_CONTEXT_METHOD; | ||
| private static final Class<?> UNSHADED_SSL_CONTEXT; | ||
| private static final boolean UNSHADED_AVAILABLE; | ||
|
|
||
| static { | ||
| // 1. Shaded Netty Lookups | ||
| Class<?> shadedGrpcSslCtx = null; | ||
| Class<?> shadedSslCtxBuilder = null; | ||
| java.lang.reflect.Method shadedForClient = null; | ||
| Object shadedGroupsOpt = null; | ||
| java.lang.reflect.Method shadedOption = null; | ||
| java.lang.reflect.Method shadedBuild = null; | ||
| java.lang.reflect.Method shadedSslCtxMethod = null; | ||
| Class<?> shadedSslCtx = null; | ||
| boolean shadedAvailable = false; | ||
| try { | ||
| String p = "io.grpc.netty.shaded."; | ||
| shadedGrpcSslCtx = Class.forName(p + "io.grpc.netty.GrpcSslContexts"); | ||
| shadedSslCtxBuilder = Class.forName(p + "io.netty.handler.ssl.SslContextBuilder"); | ||
| Class<?> openSslCtxOpt = Class.forName(p + "io.netty.handler.ssl.OpenSslContextOption"); | ||
| Class<?> sslCtxOpt = Class.forName(p + "io.netty.handler.ssl.SslContextOption"); | ||
| shadedSslCtx = Class.forName(p + "io.netty.handler.ssl.SslContext"); | ||
|
|
||
| shadedForClient = shadedGrpcSslCtx.getMethod("forClient"); | ||
| java.lang.reflect.Field groupsField = openSslCtxOpt.getDeclaredField("GROUPS"); | ||
| shadedGroupsOpt = groupsField.get(null); | ||
| shadedOption = shadedSslCtxBuilder.getMethod("option", sslCtxOpt, Object.class); | ||
| shadedBuild = shadedSslCtxBuilder.getMethod("build"); | ||
|
|
||
| Class<?> nettyBuilderClass = Class.forName(p + "io.grpc.netty.NettyChannelBuilder"); | ||
| shadedSslCtxMethod = nettyBuilderClass.getMethod("sslContext", shadedSslCtx); | ||
|
|
||
| shadedAvailable = true; | ||
| } catch (Throwable t) { | ||
| // Ignore: Shaded Netty is not available | ||
| } | ||
| SHADED_GRPC_SSL_CONTEXTS = shadedGrpcSslCtx; | ||
| SHADED_SSL_CONTEXT_BUILDER = shadedSslCtxBuilder; | ||
| SHADED_FOR_CLIENT = shadedForClient; | ||
| SHADED_GROUPS_OPTION = shadedGroupsOpt; | ||
| SHADED_OPTION_METHOD = shadedOption; | ||
| SHADED_BUILD_METHOD = shadedBuild; | ||
| SHADED_SSL_CONTEXT_METHOD = shadedSslCtxMethod; | ||
| SHADED_SSL_CONTEXT = shadedSslCtx; | ||
| SHADED_AVAILABLE = shadedAvailable; | ||
|
|
||
| // 2. Unshaded Netty Lookups | ||
| Class<?> unshadedGrpcSslCtx = null; | ||
| Class<?> unshadedSslCtxBuilder = null; | ||
| java.lang.reflect.Method unshadedForClient = null; | ||
| Object unshadedGroupsOpt = null; | ||
| java.lang.reflect.Method unshadedOption = null; | ||
| java.lang.reflect.Method unshadedBuild = null; | ||
| java.lang.reflect.Method unshadedSslCtxMethod = null; | ||
| Class<?> unshadedSslCtx = null; | ||
| boolean unshadedAvailable = false; | ||
| try { | ||
| unshadedGrpcSslCtx = Class.forName("io.grpc.netty.GrpcSslContexts"); | ||
| unshadedSslCtxBuilder = Class.forName("io.netty.handler.ssl.SslContextBuilder"); | ||
| Class<?> openSslCtxOpt = Class.forName("io.netty.handler.ssl.OpenSslContextOption"); | ||
| Class<?> sslCtxOpt = Class.forName("io.netty.handler.ssl.SslContextOption"); | ||
| unshadedSslCtx = Class.forName("io.netty.handler.ssl.SslContext"); | ||
|
|
||
| unshadedForClient = unshadedGrpcSslCtx.getMethod("forClient"); | ||
| java.lang.reflect.Field groupsField = openSslCtxOpt.getDeclaredField("GROUPS"); | ||
| unshadedGroupsOpt = groupsField.get(null); | ||
| unshadedOption = unshadedSslCtxBuilder.getMethod("option", sslCtxOpt, Object.class); | ||
| unshadedBuild = unshadedSslCtxBuilder.getMethod("build"); | ||
|
|
||
| Class<?> nettyBuilderClass = Class.forName("io.grpc.netty.NettyChannelBuilder"); | ||
| unshadedSslCtxMethod = nettyBuilderClass.getMethod("sslContext", unshadedSslCtx); | ||
|
|
||
| unshadedAvailable = true; | ||
| } catch (Throwable t) { | ||
| // Ignore: Unshaded Netty is not available | ||
| } | ||
| UNSHADED_GRPC_SSL_CONTEXTS = unshadedGrpcSslCtx; | ||
| UNSHADED_SSL_CONTEXT_BUILDER = unshadedSslCtxBuilder; | ||
| UNSHADED_FOR_CLIENT = unshadedForClient; | ||
| UNSHADED_GROUPS_OPTION = unshadedGroupsOpt; | ||
| UNSHADED_OPTION_METHOD = unshadedOption; | ||
| UNSHADED_BUILD_METHOD = unshadedBuild; | ||
| UNSHADED_SSL_CONTEXT_METHOD = unshadedSslCtxMethod; | ||
| UNSHADED_SSL_CONTEXT = unshadedSslCtx; | ||
| UNSHADED_AVAILABLE = unshadedAvailable; | ||
| } | ||
| } | ||
|
|
||
| private ManagedChannelBuilder<?> applyPqcConfiguration(ManagedChannelBuilder<?> builder) { | ||
| // Configure the PQ and classical hybrid named groups: | ||
| // 1. X25519MLKEM768 (codepoint 4588): Hybrid classical (X25519) + post-quantum (ML-KEM-768) key exchange. | ||
| // Provides defense-in-depth: if ML-KEM is compromised, security reverts to classical strength of X25519. | ||
| // 2. MLKEM768 (codepoint 1896): Pure post-quantum key exchange using ML-KEM-768. | ||
| // 3. X25519 (codepoint 29): Classical elliptic curve Diffie-Hellman key exchange, used as a fallback. | ||
| String[] hybridGroups = new String[] {"X25519MLKEM768", "MLKEM768", "X25519"}; | ||
| String builderClassName = builder.getClass().getName(); | ||
| boolean isShaded = "io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder".equals(builderClassName); | ||
| boolean isUnshaded = "io.grpc.netty.NettyChannelBuilder".equals(builderClassName); | ||
|
|
||
| if (isShaded && OpenSslReflectionHolder.SHADED_AVAILABLE) { | ||
| try { | ||
| Object sslContextBuilder = OpenSslReflectionHolder.SHADED_FOR_CLIENT.invoke(null); | ||
| OpenSslReflectionHolder.SHADED_OPTION_METHOD.invoke( | ||
| sslContextBuilder, OpenSslReflectionHolder.SHADED_GROUPS_OPTION, (Object) hybridGroups); | ||
| Object sslContext = OpenSslReflectionHolder.SHADED_BUILD_METHOD.invoke(sslContextBuilder); | ||
| OpenSslReflectionHolder.SHADED_SSL_CONTEXT_METHOD.invoke(builder, sslContext); | ||
| return builder; | ||
| } catch (java.lang.reflect.InvocationTargetException | IllegalAccessException | RuntimeException e) { | ||
| LOG.log(Level.WARNING, "Failed to configure shaded PQC transport fallback", e); | ||
| } | ||
| } else if (isUnshaded && OpenSslReflectionHolder.UNSHADED_AVAILABLE) { | ||
| try { | ||
| Object sslContextBuilder = OpenSslReflectionHolder.UNSHADED_FOR_CLIENT.invoke(null); | ||
| OpenSslReflectionHolder.UNSHADED_OPTION_METHOD.invoke( | ||
| sslContextBuilder, OpenSslReflectionHolder.UNSHADED_GROUPS_OPTION, (Object) hybridGroups); | ||
| Object sslContext = OpenSslReflectionHolder.UNSHADED_BUILD_METHOD.invoke(sslContextBuilder); | ||
| OpenSslReflectionHolder.UNSHADED_SSL_CONTEXT_METHOD.invoke(builder, sslContext); | ||
| return builder; | ||
| } catch (java.lang.reflect.InvocationTargetException | IllegalAccessException | RuntimeException e) { | ||
| LOG.log(Level.WARNING, "Failed to configure unshaded PQC transport fallback", e); | ||
| } | ||
| } | ||
| return builder; | ||
| } | ||
|
Comment on lines
+930
to
+965
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Consider refactoring References
|
||
|
|
||
| private ManagedChannel createSingleChannel() throws IOException { | ||
| ManagedChannelBuilder<?> builder = createDecoratedChannelBuilder(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,8 @@ | |
| import com.google.auth.mtls.DefaultMtlsProviderFactory; | ||
| import com.google.auth.mtls.MtlsProvider; | ||
| import com.google.common.annotations.VisibleForTesting; | ||
| import javax.net.ssl.SSLContext; | ||
| import java.security.NoSuchAlgorithmException; | ||
|
Comment on lines
+45
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| import java.io.IOException; | ||
| import java.security.GeneralSecurityException; | ||
| import java.security.KeyStore; | ||
|
|
@@ -186,6 +188,8 @@ public TransportChannelProvider withCredentials(Credentials credentials) { | |
|
|
||
| HttpTransport createHttpTransport() throws IOException, GeneralSecurityException { | ||
| if (mtlsProvider == null) { | ||
| // Returning null allows ManagedHttpJsonChannel to instantiate a default NetHttpTransport, | ||
| // which is automatically PQC-hardened if Bouncy Castle JSSE is available on the classpath. | ||
| return null; | ||
| } | ||
| if (certificateBasedAccess.useMtlsClientCertificate()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>com.google.api</groupId> | ||
| <artifactId>gapic-generator-java-pom-parent</artifactId> | ||
| <version>2.73.0-SNAPSHOT</version> | ||
| <relativePath>../gapic-generator-java-pom-parent</relativePath> | ||
| </parent> | ||
|
|
||
| <groupId>com.google.api</groupId> | ||
| <artifactId>pqc-test-parent</artifactId> | ||
| <packaging>pom</packaging> | ||
| <version>2.81.0-SNAPSHOT</version> | ||
|
|
||
| <modules> | ||
| <module>pqc-test-common</module> | ||
| <module>pqc-test-snapshot</module> | ||
| <module>pqc-test-release</module> | ||
| </modules> | ||
| </project> |
Uh oh!
There was an error while loading. Please reload this page.