Skip to content

Update Post Quantum Cryptography (PQC) API#6216

Open
anavarr wants to merge 4 commits into
eclipse-vertx:masterfrom
anavarr:pqc_new_api
Open

Update Post Quantum Cryptography (PQC) API#6216
anavarr wants to merge 4 commits into
eclipse-vertx:masterfrom
anavarr:pqc_new_api

Conversation

@anavarr

@anavarr anavarr commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

The single useHybridKeyExchange is removed in favor of two properties:

  • key-exchange-groups: the user can specify which key exchange groups they want to use
  • pqc-enforcement-policy: the user can specify how PQC will be enforced by Vert.x. The policies are:
    • STRICT: only PQC can be used to establish secure connections. If no SSL engine supports it, application won't start. If clients don't support it, SSL handshake fails
    • CLIENT_NEGOTIATED: if no SSL engine supports PQC, application won't start. If clients don't support it, falls back to other key exchange group
    • RELAXED: no enforcement whatsoever, traditional TLS negotiation

When PQC is required (STRICT or CLIENT_NEGOTIATED), Vert.x auto-selects a PQC-capable engine (JDK or OpenSSL) if none is explicitly configured. If none is available, application fails to start with a VertxException ("PQC enforcement policy requires X25519MLKEM768 but neither JDK nor OpenSSL supports it".

The resolution of key exchange groups and PQC enforcement policy (selecting an available SSL engine, adding X25519MLKEM768 to the list of supported groups for CLIENT_NEGOTIATED or restricting the list of supported groups for STRICT) is performed once in SslContextManager.

- key exchange groups: the user can specify which key exchange groups
  they want to use
- pqc-enforcement-policy: the user can specify how pqc will be enforced
  by Vert.x. The policies are:
	- STRICT: only PQC can be used to establish secure connections.
	  If no ssl engine supports it, application won't start. If clients don't support it, SSL handshake fails
	- CLIENT_NEGOTIATED: if no ssl engine supports pqc, application won't
	  start. If clients don't support it, falls back to other key exchange group
	- RELAXED: no enforcement whatsoever, traditional tls
	  negociation
Comment thread vertx-core/src/main/asciidoc/http.adoc Outdated

// If the user didn't specify any group we return a default list, with X25519MLKEM768 prepended.
// This list is the default when weak named curves are removed (see https://www.java.com/en/configure_crypto.html)
private static final List<String> DEFAULT_KEY_EXCHANGE_GROUPS = List.of(X25519MLKEM768, "X25519", "secp256r1", "x448",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm wondering whether we should maintain this list, or reuse the one defined by java.security or the java.tls.namedGroup variable, adding PQ algorithms first when needed.

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.

this list would be available for all JDK ?

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.

it is null by default. The simplest option is to get the SSL engine's default namedGroups instead.

return engineOptions;
}

private static final String X25519MLKEM768 = "X25519MLKEM768";

@NilsRenaud NilsRenaud Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are more PQ key exchanges than this one, Java 27 have:

  • X25519MLKEM768: Hybrid scheme combining ECDHE with X25519 and ML-KEM-768,
  • SecP256r1MLKEM768: Hybrid scheme combining ECDHE using the secp256r1 curve with ML-KEM-768, and
  • SecP384r1MLKEM1024: Hybrid scheme combining ECDHE using the secp384r1 curve with ML-KEM-1024.

That being said, the JEP says X25519MLKEM768 should be the default and does not enable other algorithms by default. So we're probably good with this one by default, but IMO it can't be the only one checked by isPqcAvailable(). And maybe a better strategy would be to ask the SslEngine which are its supported algorithms, ranking first the one to use by default ?

case STRICT:
if (groups != null && !groups.isEmpty()) {
if (!(groups.size() == 1 && groups.contains(X25519MLKEM768))) {
log.warn("PQC enforcement policy is STRICT: overriding key exchange groups " + groups + " with [" + X25519MLKEM768 + "]");

@NilsRenaud NilsRenaud Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One could only expose SecP256r1MLKEM768, or X25519MLKEM768, SecP256r1MLKEM768 for some reasons and should not have its choice overriden

@NilsRenaud

Copy link
Copy Markdown
Contributor

As I understand the idea of having a pqc-enforcement-policy, I think it's quire redundant with key-exchange-groups, and makes the configuration tricky (I can have my key exchange groups property overriden, or the startup failed because of a incompatible configuration).

If one wants a strict pqc-enforcement-policy, then they only have to list the key exchange groups they want, such as : X25519MLKEM768, SecP256r1MLKEM768

}

/**
* @return if PQC key exchange (X25519MLKEM768) is available via the JDK SSL engine

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This method is trickier than that, what if one wants to use a PQ algorithm that's only implemented by a custom Security Provider ?

IMO we should not guess, and simply let the user write the ordered key exchange algorithm list they want to use and react on algorithm not available (or check their presence upfront, but not sure it's possible 🤔 )

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.

see #6216 (comment). We should definitely add more PQC compliant named groups and not check only on MLKEM, but if a user wants to use obscure PQ algorithm only implemented by custom security providers and not known by Vert.x to be PQ compliant, they should disable PQ enforcement policy and rely on their own vigilance.

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.

in which JDK is the class KEM available ?

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.

27+ I believe

@NilsRenaud NilsRenaud Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@vietj : The KEM class has been added in 21: https://docs.oracle.com/en/java/javase/21/docs/api//java.base/javax/crypto/KEM.html,
the ML-KEM algorithm in java 24: https://openjdk.org/jeps/496
The support of ML-KEM algorithms in TLS 1.3 in java 27: https://openjdk.org/jeps/527

@anavarr

but if a user wants to use obscure PQ algorithm only implemented by custom security providers and not known by Vert.x to be PQ compliant, they should disable PQ enforcement policy and rely on their own vigilance.

Bypassing vert.x PQ enforcement to use their own PQC algorithms, which could be a future well-known NIST approved algorithm, is not very user-friendly in my opinion. And I think either :

  • user don't care about PQC and rely on JDK's default settings, PQ algorithm will be the first choice in JDK 27 anyway
  • user care about PQC and will be able to set the list of algorithms they want.

So we could replace the enforcement option by a note in the doc instead.

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.

By the time it becomes a well-known NIST approved algorithm it will be known as a PQC algorithm in Vert.x.
I see this property as a complement to the list of named groups. If you know what you're doing you just leave it to its default RELAXED mode. If you don't, you set it to STRICT and are forced to use an SSL engine providing PQ compliant algorithms.

try {
long sslPtr = ((ReferenceCountedOpenSslEngine) sslHandler.engine()).sslPointer();
boolean success = SSL.setCurvesList(sslPtr, "X25519MLKEM768");
boolean success = SSL.setCurvesList(sslPtr, curvesList);

@NilsRenaud NilsRenaud Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a netty-tc-native method, will this work with JDK implementation ?
I've created a Netty's issue to have the curve list setter as first class citizen in Netty (netty/netty#16999).
Maybe we should wait for their answer first ?

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.

you're absolutely right I forgot this path. Since the api was added in JDK 21 we have to use reflection or method handles. If the user uses a version of JdkSSL inferior, setNamedGroups won't be available and we should either crash or warn them.

We have the pqc enforcement policy to decide what to do if the user specifically wants to use pqc, but in the case they simply provide a list of named groups and doesn't use a compatible ssl engine I think we should always fail right?

@vietj

@vietj vietj Jun 29, 2026

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.

we can have code in the src/main/java21 tree that provides this if that is JDK 21, without using reflection.

@anavarr

anavarr commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for your comments @NilsRenaud!
Correct me if I'm wrong but I think your points boil down to 2 problems:

  1. We focus entirely on X25519MLKEM768 as the sole hybrid key exchange protocol but should rather use an ordered list (easy fix).
  2. PQC enforcement policy leaves a lot of guess work when the user doesn't specify neither an ssl engine nor a list of named groups.

Item 1 can be solved easily by maintaining a list and updating it as ssl engines versions increase and new hybrid key exchange protocols appear. It would require little work.
Now for item 2. I still think having this property is a good thing as it allows for a very easy experience: without having set an ssl engine or a list of named groups, the user can set a PQC enforcement policy and Vert.x will enumerate the engines and look for available hybrid key exchange protocols if needed. I don't think it is redundant. However, as one of your comment pointed out, setting this policy along with a set of named groups (especially custom ones) can create friction if some groups are not known by Vert.x to be PQC compliant. I reckon this situation would be rare, and the user would simply have to turn off PQC enforcement. In my mind it is not a strong enough argument to dismiss the pros of having a PQC enforcement policy property.

wdyt @NilsRenaud, @vietj and @cescoffier

@vietj vietj added this to the 5.2.0 milestone Jun 29, 2026
* Policy for enforcing post-quantum cryptography (PQC) key exchange.
*/
@VertxGen
public enum PqcEnforcementPolicy {

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.

please add @Unstable

@NilsRenaud

Copy link
Copy Markdown
Contributor

Thanks @anavarr you summarized my points perfectly.

I understand your thinking, but I still think the PQC enforcement policy is not necessary, makes the API unclear for future use case, and forces Vert.x to maintain a list of PQC-resistant alogrithms as new spawn, or become broken, in the future.
For example, what if we found a vulnerability in X25519MLKEM768 making it NOT PQC-resistant ? Users will either have a false sense of security because they rely on the PQC enforcement policy, or disable it since it prevents them to set a really PQC-resistant algorithm.

I'm not maintainer though, and this PR is still valuable since the PQC enforcement is optional ! So thanks for this work that everyone will benefit of anyway :)

@vietj

vietj commented Jun 30, 2026

Copy link
Copy Markdown
Member

Thanks @anavarr you summarized my points perfectly.

I understand your thinking, but I still think the PQC enforcement policy is not necessary, makes the API unclear for future use case, and forces Vert.x to maintain a list of PQC-resistant alogrithms as new spawn, or become broken, in the future. For example, what if we found a vulnerability in X25519MLKEM768 making it NOT PQC-resistant ? Users will either have a false sense of security because they rely on the PQC enforcement policy, or disable it since it prevents them to set a really PQC-resistant algorithm.

I'm not maintainer though, and this PR is still valuable since the PQC enforcement is optional ! So thanks for this work that everyone will benefit of anyway :)

if we can afford to introduce an API that is not strictly necessary, we should do it

@cescoffier

Copy link
Copy Markdown
Contributor

Ignore the JDK for a bit, as we are hoping for Java 29 (as LTS) but we need to deploy quantum safe TLS before that time. So, we will rely on OpenSSL, but it requires your operating system to provide the right version. Without this enforcement we could lead to an environment where the app will be running but fail every handshake, or degrade to non quantum safe. The enforcement will check during the startup that you can handle the handshake. The list of supported key exchange algorithms will be strictly the ones from the NIST.

When the JDK support will be available, we would verify that the key exchange algorithms are available (because it may depends on your distribution. For example, Semuru already provide what we would need).

At some point in the future (hard to say when), this enforcement will become unnecessary because all environments and all JVMs will provide what is required. But we are not there yet. Lots of system will not be quantum safe capable for a bit, and the enforcement is there to make sure you don't fall into that trap.

@anavarr

anavarr commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Well I believe everything is ready for PQC in the JDK anyway. The only modification to do will be to change isPqcAvailable in JdkSslOptions (it returns false currently). But every path has a branch for both openssl and jdkssl engines. I added the two other pq-compliant key exchange protocols to the mix. The default list of named groups is that from the NIST. I used multi-release for specific methods to set named groups. Users are warned if they try to set named groups for jdk <20.


PQC is configured with two properties:

- {@link io.vertx.core.net.SSLOptions#setKeyExchangeGroups} — a list of key exchange group names (e.g. `["X25519MLKEM768", "X25519"]`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe An ordered list instead of A list, or anything that hint that there is a priority order


For both `STRICT` and `CLIENT_NEGOTIATED`, the application fails with either "PQC enforcement policy requires X25519MLKEM768 but the configured SSL engine does not support it" or "PQC enforcement policy requires X25519MLKEM768 but neither JDK nor OpenSSL support it" if pqc is not supported by the SSL engine.

For `STRICT` and `CLIENT_NEGOTIATED` modes, you need an SslEngine supporting X25519MLKEM768. `JdkSslEngine` supports it starting with JDK 27 (preview feature), and OpenSsl supports it starting with OpenSsl 3.5. If no `SslEngine` is specified by the user, the first engine supporting pqc will be selected, starting with `JdkSslEngine`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure it's worth adding this, but one can add BouncyCastle's org.bouncycastle.bcprov-jdk18on + org.bouncycastle.bctls-jdk18on dependancies and add BCJSSE and BC as primary security providers to have a transparent support of PQ-resistant algorithms as would JDK 27+ do. This works today, on java 21 (haven't tried earlier versions)

}

/**
* TODO implement it when JDK add supports

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As said earlier, it depends on the security provider installed :s

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.

The method is in the JdkSSLEngineOptions file, I think it is clear enough we are referring to JdkSSLEngine and thus the SunJSSE provider. I can refine the comment but imo it is explicit enough

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

JdkSSLEngine and thus the SunJSSE provider.

That's where hides the devil: one can change the provider, so BouncyCastle works transparently through the JdkSSLEngine when installed as Security Provider, because Java will delegate TLS work to it as soon as it implements the JSSE interface (which it does).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants