Update Post Quantum Cryptography (PQC) API#6216
Conversation
- 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
|
|
||
| // 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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
this list would be available for all JDK ?
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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 + "]"); |
There was a problem hiding this comment.
One could only expose SecP256r1MLKEM768, or X25519MLKEM768, SecP256r1MLKEM768 for some reasons and should not have its choice overriden
|
As I understand the idea of having a If one wants a strict |
| } | ||
|
|
||
| /** | ||
| * @return if PQC key exchange (X25519MLKEM768) is available via the JDK SSL engine |
There was a problem hiding this comment.
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 🤔 )
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
in which JDK is the class KEM available ?
There was a problem hiding this comment.
@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
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
we can have code in the src/main/java21 tree that provides this if that is JDK 21, without using reflection.
|
Thank you for your comments @NilsRenaud!
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. wdyt @NilsRenaud, @vietj and @cescoffier |
| * Policy for enforcing post-quantum cryptography (PQC) key exchange. | ||
| */ | ||
| @VertxGen | ||
| public enum PqcEnforcementPolicy { |
|
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. 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 |
|
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. |
|
Well I believe everything is ready for PQC in the JDK anyway. The only modification to do will be to change |
|
|
||
| PQC is configured with two properties: | ||
|
|
||
| - {@link io.vertx.core.net.SSLOptions#setKeyExchangeGroups} — a list of key exchange group names (e.g. `["X25519MLKEM768", "X25519"]`) |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
As said earlier, it depends on the security provider installed :s
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
The single
useHybridKeyExchangeis removed in favor of two properties:key-exchange-groups: the user can specify which key exchange groups they want to usepqc-enforcement-policy: the user can specify how PQC will be enforced by Vert.x. The policies are: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.