Skip to content
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

8346129: Simplify EdDSA & XDH curve name usage #23647

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

ascarpino
Copy link
Contributor

@ascarpino ascarpino commented Feb 14, 2025

Hi,

I need a review for the following change. Naming conventions for EdDSA and XDH have inconsistencies between DisabledAlgorithms and KeyPairGenerator. These internal changes help make it more consistent when parsing the actual curve being used vs the broader algorithm name.

thanks

Tony


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8346129: Simplify EdDSA & XDH curve name usage (Bug - P3)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/23647/head:pull/23647
$ git checkout pull/23647

Update a local copy of the PR:
$ git checkout pull/23647
$ git pull https://git.openjdk.org/jdk.git pull/23647/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23647

View PR using the GUI difftool:
$ git pr show -t 23647

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/23647.diff

Using Webrev

Link to Webrev Comment

# Conflicts:
#	src/java.base/share/classes/sun/security/util/AbstractAlgorithmConstraints.java
#	src/java.base/share/classes/sun/security/util/KeyUtil.java
@bridgekeeper
Copy link

bridgekeeper bot commented Feb 14, 2025

👋 Welcome back ascarpino! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Feb 14, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot changed the title 8346129 Simplify EdDSA & XDH curve name usage 8346129: Simplify EdDSA & XDH curve name usage Feb 14, 2025
@openjdk
Copy link

openjdk bot commented Feb 14, 2025

⚠️ @ascarpino This pull request contains merges that bring in commits not present in the target repository. Since this is not a "merge style" pull request, these changes will be squashed when this pull request in integrated. If this is your intention, then please ignore this message. If you want to preserve the commit structure, you must change the title of this pull request to Merge <project>:<branch> where <project> is the name of another project in the OpenJDK organization (for example Merge jdk:master).

@openjdk
Copy link

openjdk bot commented Feb 14, 2025

@ascarpino The following label will be automatically applied to this pull request:

  • security

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@ascarpino ascarpino marked this pull request as ready for review February 14, 2025 21:23
@openjdk openjdk bot added the rfr Pull request is ready for review label Feb 14, 2025
@mlbridge
Copy link

mlbridge bot commented Feb 14, 2025

Webrevs

private static List<String> aliasEd25519 = null;
private static List<String> aliasXDH = null;
private static List<String> aliasX25519 = null;

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it be more concise to create a static algorithm-to-aliases map and then make getAliases() to do the map lookup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's purely a memory allocation solution here. If I make a Map, I have to populate the entries at initialization. Right now XDH and EdDSA are very unlikely to be disabled as they are relatively new algorithms.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, I thought it was probably done to minimize memory usage. But all those string literals will be stored in a string pool at runtime and simply re-used when we allocate a list with them. So real memory saving should be very small comparing to a static map.

// Check `algorithm` against disabled algorithms and their aliases
for (String a : algorithms) {
if (algorithm.equalsIgnoreCase(a) ||
getAliases(a).contains(algorithm)) {
Copy link
Contributor

@artur-oracle artur-oracle Feb 19, 2025

Choose a reason for hiding this comment

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

We do case-insensitive match for the algorithm itself but then we do case-sensitive aliases lookup and case-sensitive match for the aliases contains call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, thanks.

Comment on lines 177 to 184
/*
If the key is a sub-algorithm of a larger group of algorithms, this method
will return that sub-algorithm. For example, key.getAlgorithm() returns
"EdDSA", but the underlying key maybe "Ed448". For
DisabledAlgorithmConstraints (DAC), this distinction is important.
"EdDSA" means all curves for DAC, but when using it with
KeyPairGenerator, EdDSA means Ed25519.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Use the common form for comments here, i.e.:

/**
 * ...
 */

return switch (key) {
case EdECKey ed -> ed.getParams().getName();
case XECKey xe -> ((NamedParameterSpec) xe.getParams()).getName();
default -> key.getAlgorithm();
Copy link
Member

Choose a reason for hiding this comment

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

Do you also want to add cases for ML-KEM and ML-DSA keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wangweij is planning on name usage for those. I'm focusing on these older curves.

Copy link
Member

Choose a reason for hiding this comment

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

They are already defined. I think you just want to add something like:

If (key.getAlgorithm().equals("ML-KEM") || key.getAlgorithm().equals("ML-DSA")) {
   return ((NamedParameterSpec) key.getParams()).getName();
}

Not urgent, but useful if one of these algorithms were to weaken or be broken for some reason.

Copy link
Contributor

@wangweij wangweij Feb 21, 2025

Choose a reason for hiding this comment

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

Or what about this?

        if (key instanceof AsymmetricKey ak) {
            if (ak.getParams() instanceof NamedParameterSpec nps) {
                return nps.getName();
            }
        }
        return key.getAlgorithm();

AsymmetricKey was introduced to make our lives easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I stayed away from that because this is likely being backported

Copy link
Member

Choose a reason for hiding this comment

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

Ok, well our backporters are usually good about extracting only what is necessary, but if not, can you file another issue to add support for disabling PQC algorithms?

/*
If the key is a sub-algorithm of a larger group of algorithms, this method
will return that sub-algorithm. For example, key.getAlgorithm() returns
"EdDSA", but the underlying key maybe "Ed448". For
Copy link
Member

Choose a reason for hiding this comment

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

s/maybe/may be/

}
case "Ed25519" -> {
if (aliasEd25519 == null) {
aliasEd25519 = List.of("EdDSA", "Ed25519");
Copy link
Member

@seanjmullan seanjmullan Feb 21, 2025

Choose a reason for hiding this comment

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

Hmm. Should disabling Ed25519 also disable EdDSA? I can see the reverse, but isn't Ed25519 meant to be a specific curve for EdDSA?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is complicated by KeyPairGenerator.getInstance("EdDSA") returning an Ed25519 key

If someone were to check permits() with "EdDSA" the above code recognizes that "Ed25519" on the disabled algorithm list overlaps with "EdDSA". This is the first test in the test coded included in the PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we call permits before instantiating a KeyPairGenerator? What if people call kpg.initialize(NPS.Ed448) after the instantiation?

In reality, I think it depends on how many permits calls there are. Modern algorithms have the key same algorithm name and signature algorithm name. When a signature operation is carried out, do we check on both the signature algorithm and the key? It seems only checking on the key is enough. It's actually more precise, since you can get the exact parameter set name there. This is why I asked if the method is "never called on a family algorithm name". When checking a key, if we always call permits on the parameter set name, we get the precise result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

permits() are used in situations for jdk[tls|certpath|jar].disabledAlgorithms, and the SSLAlgorithmConstraints. It's not called for APIs like KPG, Signature, etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's what I meant. Suppose in TLS when you verify a signature and you call permits on both the signature algorithm name and the key used to init the signature, it's OK if only one fails.

private static List<String> aliasEdDSA = null;
private static List<String> aliasEd25519 = null;
private static List<String> aliasXDH = null;
private static List<String> aliasX25519 = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a little suspicious in this approach. At least this means for each "family" algorithm name like "EdDSA", we need to hardcode all its parameter set names here. Sounds not very sustainable.

An EdDSA key always has its getAlgorithm being "EdDSA" (at least inside SunEC) and its getParams() being the parameter set name. So it looks like it's enough if we do a name comparison on both.

Also, why no aliasEd448 and aliasX448 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have to give more thought on checking the algorithm and the getParams() against the list. That may eliminate the need for the hardcoded list..

As to why 448 curves didn't need an alias, there is no other way to specify those curves other than their given name, like mentioned with the KPG/Ed25519 example in my comment to Sean

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So using the getAlgorithm() and getParams().getName() work because there is a Key, but not for non-key situation such as permits(Set.of(CryptoPrimitive.SIGNATURE), "EdDSA", null).

But this does bring up a point I had not considered. The permits() call does not specify any key details other than the family name. Perhaps it's ok for permits() to return a passing result with the information given. But for a permit() that had more detail, it could return a failing result. However, the KPG example does make me think that the consistency in the current PR is better.

List<TestCase> expected = switch (algorithm) {
case "Ed25519" ->
Arrays.asList(
new TestCase("EdDSA", false),
Copy link
Contributor

Choose a reason for hiding this comment

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

As Sean mentioned in another comment, disabling "Ed25519" does not imply all EdDSA keys are not permitted. This means the result of permits(primitives, algorithmName, parameters) cannot be determined. That said, I noticed you've used KeyUtil::getAlgorithm in a lot of places. Can we guarantee that this permits method is never called on a family algorithm name? If so, we can get a definitive result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe my comment to Sean answers this question, but I'm not sure I understand the last question in your comment. "never called on a family algorithm name". The change is to make sure these two families return the curve name and not the family name (EdDSA & XDH). But on the other side, someone using the family name of the disabled algorithm list would disable all curves.
The above test code is checking that this call permits(Set.of(CryptoPrimitive.SIGNATURE), "EdDSA", null) will fail for a Ed25519 key because of the precedent set by KPG.

Copy link
Contributor

Choose a reason for hiding this comment

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

We are talking about the same in multiple comments now.

In this case, if both permits(SIGNATURE, "EdDSA", null) and permits(SIGNATURE, key) are called, it's safe to bypass the 1st check as long as the 2nd one blocks the key. So it's not necessary to cover "EdDSA" when only "Ed25519" is disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfr Pull request is ready for review security [email protected]
Development

Successfully merging this pull request may close these issues.

4 participants